summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt31
-rw-r--r--MultiMC.cpp3
-rw-r--r--gui/ConsoleWindow.cpp3
-rw-r--r--gui/MainWindow.cpp16
-rw-r--r--gui/MainWindow.h2
-rw-r--r--gui/MainWindow.ui28
-rw-r--r--install_prereqs.cmake.in16
-rw-r--r--logic/LegacyUpdate.cpp18
-rw-r--r--logic/OneSixAssets.cpp127
-rw-r--r--logic/OneSixInstance.cpp64
-rw-r--r--logic/OneSixInstance.h2
-rw-r--r--logic/OneSixUpdate.cpp111
-rw-r--r--logic/OneSixUpdate.h7
-rw-r--r--logic/OneSixVersion.cpp16
-rw-r--r--logic/OneSixVersion.h2
-rw-r--r--logic/assets/AssetsUtils.cpp227
-rw-r--r--logic/assets/AssetsUtils.h (renamed from logic/OneSixAssets.h)42
-rw-r--r--logic/net/MD5EtagDownload.cpp (renamed from logic/net/FileDownload.cpp)17
-rw-r--r--logic/net/MD5EtagDownload.h (renamed from logic/net/FileDownload.h)10
-rw-r--r--logic/net/NetJob.cpp2
-rw-r--r--logic/net/NetJob.h2
-rw-r--r--logic/updater/DownloadUpdateTask.cpp2
22 files changed, 512 insertions, 236 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6dbd2371..a413c700 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -285,8 +285,8 @@ logic/InstanceLauncher.cpp
# network stuffs
logic/net/NetAction.h
-logic/net/FileDownload.h
-logic/net/FileDownload.cpp
+logic/net/MD5EtagDownload.h
+logic/net/MD5EtagDownload.cpp
logic/net/ByteArrayDownload.h
logic/net/ByteArrayDownload.cpp
logic/net/CacheDownload.h
@@ -332,8 +332,6 @@ logic/LegacyForge.h
logic/LegacyForge.cpp
# 1.6 instances
-logic/OneSixAssets.h
-logic/OneSixAssets.cpp
logic/OneSixInstance.h
logic/OneSixInstance.cpp
logic/OneSixInstance_p.h
@@ -390,6 +388,10 @@ logic/NagUtils.h
logic/NagUtils.cpp
logic/SkinUtils.h
logic/SkinUtils.cpp
+
+# Assets
+logic/assets/AssetsUtils.h
+logic/assets/AssetsUtils.cpp
)
@@ -574,22 +576,11 @@ FILE(WRITE \"\${CMAKE_INSTALL_PREFIX}/${QTCONF_DEST_DIR}/qt.conf\" \"\")
COMPONENT Runtime
)
-
-INSTALL(
- CODE "
-FILE(GLOB_RECURSE QTPLUGINS \"\${CMAKE_INSTALL_PREFIX}/${PLUGIN_DEST_DIR}/*${CMAKE_SHARED_LIBRARY_SUFFIX}\")
-function(gp_resolved_file_type_override resolved_file type_var)
- if(resolved_file MATCHES \"^/usr/lib/libQt\")
- message(\"resolving \${resolved_file} as other\")
- set(\${type_var} other PARENT_SCOPE)
- endif()
-endfunction()
-
-include(BundleUtilities)
-fixup_bundle(\"${APPS}\" \"\${QTPLUGINS}\" \"${DIRS}\")
-"
- COMPONENT Runtime
-)
+CONFIGURE_FILE(
+ "${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in"
+ "${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake"
+ @ONLY)
+INSTALL(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake" COMPONENT Runtime)
diff --git a/MultiMC.cpp b/MultiMC.cpp
index e3107ac4..6c88a6be 100644
--- a/MultiMC.cpp
+++ b/MultiMC.cpp
@@ -358,7 +358,8 @@ void MultiMC::initGlobalSettings()
void MultiMC::initHttpMetaCache()
{
m_metacache.reset(new HttpMetaCache("metacache"));
- m_metacache->addBase("assets", QDir("assets").absolutePath());
+ m_metacache->addBase("asset_indexes", QDir("assets/indexes").absolutePath());
+ m_metacache->addBase("asset_objects", QDir("assets/objects").absolutePath());
m_metacache->addBase("versions", QDir("versions").absolutePath());
m_metacache->addBase("libraries", QDir("libraries").absolutePath());
m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath());
diff --git a/gui/ConsoleWindow.cpp b/gui/ConsoleWindow.cpp
index 5db4442e..1a0f352d 100644
--- a/gui/ConsoleWindow.cpp
+++ b/gui/ConsoleWindow.cpp
@@ -47,6 +47,7 @@ ConsoleWindow::ConsoleWindow(MinecraftProcess *mcproc, QWidget *parent)
{
show();
}
+ setMayClose(false);
}
ConsoleWindow::~ConsoleWindow()
@@ -174,6 +175,7 @@ void ConsoleWindow::onEnded(BaseInstance *instance, int code, QProcess::ExitStat
}
if(!isVisible())
show();
+ setMayClose(true);
}
void ConsoleWindow::onLaunchFailed(BaseInstance *instance)
@@ -181,6 +183,7 @@ void ConsoleWindow::onLaunchFailed(BaseInstance *instance)
ui->btnKillMinecraft->setEnabled(false);
if(!isVisible())
show();
+ setMayClose(true);
}
void ConsoleWindow::on_btnPaste_clicked()
diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp
index 6db8e464..80bde381 100644
--- a/gui/MainWindow.cpp
+++ b/gui/MainWindow.cpp
@@ -79,7 +79,6 @@
#include "logic/BaseInstance.h"
#include "logic/InstanceFactory.h"
#include "logic/MinecraftProcess.h"
-#include "logic/OneSixAssets.h"
#include "logic/OneSixUpdate.h"
#include "logic/JavaUtils.h"
#include "logic/NagUtils.h"
@@ -87,6 +86,7 @@
#include "logic/LegacyInstance.h"
+#include "logic/assets/AssetsUtils.h"
#include <logic/updater/UpdateChecker.h>
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
@@ -169,7 +169,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
connect(MMC->instances().get(), SIGNAL(dataIsInvalid()), SLOT(selectionBad()));
m_statusLeft = new QLabel(tr("Instance type"), this);
- m_statusRight = new QLabel(tr("Assets information"), this);
+ m_statusRight = new QLabel(this);
m_statusRight->setAlignment(Qt::AlignRight);
statusBar()->addPermanentWidget(m_statusLeft, 1);
statusBar()->addPermanentWidget(m_statusRight, 0);
@@ -247,15 +247,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
// if automatic update checks are allowed, start one.
if(MMC->settings()->get("AutoUpdate").toBool())
on_actionCheckUpdate_triggered();
-
- assets_downloader = new OneSixAssets();
- connect(assets_downloader, SIGNAL(indexStarted()), SLOT(assetsIndexStarted()));
- connect(assets_downloader, SIGNAL(filesStarted()), SLOT(assetsFilesStarted()));
- connect(assets_downloader, SIGNAL(filesProgress(int, int, int)),
- SLOT(assetsFilesProgress(int, int, int)));
- connect(assets_downloader, SIGNAL(failed()), SLOT(assetsFailed()));
- connect(assets_downloader, SIGNAL(finished()), SLOT(assetsFinished()));
- assets_downloader->start();
}
const QString currentInstanceId = MMC->settings()->get("SelectedInstance").toString();
@@ -279,6 +270,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
// removing this looks stupid
view->setFocus();
+
+ AssetsUtils::migrateOldAssets();
}
MainWindow::~MainWindow()
@@ -286,7 +279,6 @@ MainWindow::~MainWindow()
delete ui;
delete proxymodel;
delete drawer;
- delete assets_downloader;
}
void MainWindow::repopulateAccountsMenu()
diff --git a/gui/MainWindow.h b/gui/MainWindow.h
index aa9dae75..befe93e6 100644
--- a/gui/MainWindow.h
+++ b/gui/MainWindow.h
@@ -31,7 +31,6 @@ class KCategorizedView;
class KCategoryDrawer;
class MinecraftProcess;
class ConsoleWindow;
-class OneSixAssets;
namespace Ui
{
@@ -180,7 +179,6 @@ private:
InstanceProxyModel *proxymodel;
MinecraftProcess *proc;
ConsoleWindow *console;
- OneSixAssets *assets_downloader;
LabeledToolButton *renameButton;
QToolButton *changeIconButton;
diff --git a/gui/MainWindow.ui b/gui/MainWindow.ui
index 0537d0e5..82e3b05f 100644
--- a/gui/MainWindow.ui
+++ b/gui/MainWindow.ui
@@ -14,7 +14,7 @@
<string>MultiMC 5</string>
</property>
<property name="windowIcon">
- <iconset resource="../multimc.qrc">
+ <iconset resource="../graphics.qrc">
<normaloff>:/icons/multimc/scalable/apps/multimc.svg</normaloff>:/icons/multimc/scalable/apps/multimc.svg</iconset>
</property>
<widget class="QWidget" name="centralWidget">
@@ -123,7 +123,7 @@
</widget>
<action name="actionAddInstance">
<property name="icon">
- <iconset resource="../multimc.qrc">
+ <iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/new</normaloff>:/icons/toolbar/new</iconset>
</property>
<property name="text">
@@ -138,7 +138,7 @@
</action>
<action name="actionViewInstanceFolder">
<property name="icon">
- <iconset resource="../multimc.qrc">
+ <iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/viewfolder</normaloff>:/icons/toolbar/viewfolder</iconset>
</property>
<property name="text">
@@ -153,7 +153,7 @@
</action>
<action name="actionRefresh">
<property name="icon">
- <iconset resource="../multimc.qrc">
+ <iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/refresh</normaloff>:/icons/toolbar/refresh</iconset>
</property>
<property name="text">
@@ -168,7 +168,7 @@
</action>
<action name="actionViewCentralModsFolder">
<property name="icon">
- <iconset resource="../multimc.qrc">
+ <iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/centralmods</normaloff>:/icons/toolbar/centralmods</iconset>
</property>
<property name="text">
@@ -183,7 +183,7 @@
</action>
<action name="actionCheckUpdate">
<property name="icon">
- <iconset resource="../multimc.qrc">
+ <iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/checkupdate</normaloff>:/icons/toolbar/checkupdate</iconset>
</property>
<property name="text">
@@ -198,7 +198,7 @@
</action>
<action name="actionSettings">
<property name="icon">
- <iconset resource="../multimc.qrc">
+ <iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/settings</normaloff>:/icons/toolbar/settings</iconset>
</property>
<property name="text">
@@ -216,7 +216,7 @@
</action>
<action name="actionReportBug">
<property name="icon">
- <iconset resource="../multimc.qrc">
+ <iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/bug</normaloff>:/icons/toolbar/bug</iconset>
</property>
<property name="text">
@@ -231,7 +231,7 @@
</action>
<action name="actionNews">
<property name="icon">
- <iconset resource="../multimc.qrc">
+ <iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/news</normaloff>:/icons/toolbar/news</iconset>
</property>
<property name="text">
@@ -246,7 +246,7 @@
</action>
<action name="actionAbout">
<property name="icon">
- <iconset resource="../multimc.qrc">
+ <iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/about</normaloff>:/icons/toolbar/about</iconset>
</property>
<property name="text">
@@ -300,7 +300,7 @@
<bool>true</bool>
</property>
<property name="icon">
- <iconset resource="../multimc.qrc">
+ <iconset resource="../graphics.qrc">
<normaloff>:/icons/instances/infinity</normaloff>:/icons/instances/infinity</iconset>
</property>
<property name="text">
@@ -440,7 +440,7 @@
<bool>true</bool>
</property>
<property name="icon">
- <iconset resource="../multimc.qrc">
+ <iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/cat</normaloff>:/icons/toolbar/cat</iconset>
</property>
<property name="text">
@@ -452,7 +452,7 @@
</action>
<action name="actionCopyInstance">
<property name="icon">
- <iconset resource="../multimc.qrc">
+ <iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/copy</normaloff>:/icons/toolbar/copy</iconset>
</property>
<property name="text">
@@ -476,7 +476,7 @@
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources>
- <include location="../multimc.qrc"/>
+ <include location="../graphics.qrc"/>
</resources>
<connections/>
</ui>
diff --git a/install_prereqs.cmake.in b/install_prereqs.cmake.in
new file mode 100644
index 00000000..7565d6cb
--- /dev/null
+++ b/install_prereqs.cmake.in
@@ -0,0 +1,16 @@
+FILE(GLOB_RECURSE QTPLUGINS "${CMAKE_INSTALL_PREFIX}/@PLUGIN_DEST_DIR@/*@CMAKE_SHARED_LIBRARY_SUFFIX@")
+function(gp_resolved_file_type_override resolved_file type_var)
+ if(resolved_file MATCHES "^/usr/lib/libQt")
+ message("resolving ${resolved_file} as other")
+ set(${type_var} other PARENT_SCOPE)
+ endif()
+endfunction()
+
+set(gp_tool "@CMAKE_GP_TOOL@")
+set(gp_cmd_paths ${gp_cmd_paths}
+ "@CMAKE_GP_CMD_PATHS@"
+)
+
+include(BundleUtilities)
+fixup_bundle("@APPS@" "${QTPLUGINS}" "@DIRS@")
+
diff --git a/logic/LegacyUpdate.cpp b/logic/LegacyUpdate.cpp
index 6125101b..60dde3e8 100644
--- a/logic/LegacyUpdate.cpp
+++ b/logic/LegacyUpdate.cpp
@@ -261,16 +261,20 @@ void LegacyUpdate::jarStart()
// Build a list of URLs that will need to be downloaded.
setStatus("Downloading new minecraft.jar");
- QString urlstr("http://s3.amazonaws.com/Minecraft.Download/versions/");
- QString intended_version_id = inst->intendedVersionId();
- urlstr += intended_version_id + "/" + intended_version_id + ".jar";
+ QString version_id = inst->intendedVersionId();
+ QString localPath = version_id + "/" + version_id + ".jar";
+ QString urlstr = "http://s3.amazonaws.com/Minecraft.Download/versions/" + localPath;
- auto dljob = new NetJob("Minecraft.jar for version " + intended_version_id);
- dljob->addNetAction(FileDownload::make(QUrl(urlstr), inst->defaultBaseJar()));
- legacyDownloadJob.reset(dljob);
+ auto dljob = new NetJob("Minecraft.jar for version " + version_id);
+
+
+ auto metacache = MMC->metacache();
+ auto entry = metacache->resolveEntry("versions", localPath);
+ dljob->addNetAction(CacheDownload::make(QUrl(urlstr), entry));
connect(dljob, SIGNAL(succeeded()), SLOT(jarFinished()));
connect(dljob, SIGNAL(failed()), SLOT(jarFailed()));
connect(dljob, SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64)));
+ legacyDownloadJob.reset(dljob);
legacyDownloadJob->start();
}
@@ -482,4 +486,4 @@ void LegacyUpdate::ModTheJar()
// inst->UpdateVersion(true);
emitSucceeded();
return;
-} \ No newline at end of file
+}
diff --git a/logic/OneSixAssets.cpp b/logic/OneSixAssets.cpp
deleted file mode 100644
index 400aff2c..00000000
--- a/logic/OneSixAssets.cpp
+++ /dev/null
@@ -1,127 +0,0 @@
-/* Copyright 2013 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <QString>
-#include "logger/QsLog.h"
-#include <QtXml/QtXml>
-#include "OneSixAssets.h"
-#include "net/NetJob.h"
-#include "net/HttpMetaCache.h"
-#include "net/S3ListBucket.h"
-#include "MultiMC.h"
-
-#define ASSETS_URL "http://resources.download.minecraft.net/"
-
-class ThreadedDeleter : public QThread
-{
- Q_OBJECT
-public:
- void run()
- {
- QLOG_INFO() << "Cleaning up assets folder...";
- QDirIterator iter(m_base, QDirIterator::Subdirectories);
- int base_length = m_base.length();
- while (iter.hasNext())
- {
- QString filename = iter.next();
- QFileInfo current(filename);
- // we keep the dirs... whatever
- if (current.isDir())
- continue;
- QString trimmedf = filename;
- trimmedf.remove(0, base_length + 1);
- if (m_whitelist.contains(trimmedf))
- {
- QLOG_TRACE() << trimmedf << " gets to live";
- }
- else
- {
- // DO NOT TOLERATE JUNK
- QLOG_TRACE() << trimmedf << " dies";
- QFile f(filename);
- f.remove();
- }
- }
- }
- QString m_base;
- QStringList m_whitelist;
-};
-
-void OneSixAssets::downloadFinished()
-{
- deleter = new ThreadedDeleter();
- QDir dir("assets");
- deleter->m_base = dir.absolutePath();
- deleter->m_whitelist = nuke_whitelist;
- connect(deleter, SIGNAL(finished()), SIGNAL(finished()));
- deleter->start();
-}
-
-void OneSixAssets::S3BucketFinished()
-{
- QString prefix(ASSETS_URL);
- nuke_whitelist.clear();
-
- emit filesStarted();
-
- auto firstJob = index_job->first();
- auto objectList = std::dynamic_pointer_cast<S3ListBucket>(firstJob)->objects;
-
- NetJob *job = new NetJob("Assets");
-
- connect(job, SIGNAL(succeeded()), SLOT(downloadFinished()));
- connect(job, SIGNAL(failed()), SIGNAL(failed()));
- connect(job, SIGNAL(filesProgress(int, int, int)), SIGNAL(filesProgress(int, int, int)));
-
- auto metacache = MMC->metacache();
-
- for (auto object : objectList)
- {
- // Filter folder keys (zero size)
- if (object.size == 0)
- continue;
-
- nuke_whitelist.append(object.Key);
-
- auto entry = metacache->resolveEntry("assets", object.Key, object.ETag);
- if (entry->stale)
- {
- job->addNetAction(CacheDownload::make(QUrl(prefix + object.Key), entry));
- }
- }
- if (job->size())
- {
- files_job.reset(job);
- files_job->start();
- }
- else
- {
- delete job;
- emit finished();
- }
-}
-
-void OneSixAssets::start()
-{
- auto job = new NetJob("Assets index");
- job->addNetAction(S3ListBucket::make(QUrl(ASSETS_URL)));
- connect(job, SIGNAL(succeeded()), SLOT(S3BucketFinished()));
- connect(job, SIGNAL(failed()), SIGNAL(failed()));
- emit indexStarted();
- index_job.reset(job);
- job->start();
-}
-
-#include "OneSixAssets.moc"
diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp
index b18dd2ba..b8d85ff5 100644
--- a/logic/OneSixInstance.cpp
+++ b/logic/OneSixInstance.cpp
@@ -26,6 +26,7 @@
#include <JlCompress.h>
#include "gui/dialogs/OneSixModEditDialog.h"
#include "logger/QsLog.h"
+#include "logic/assets/AssetsUtils.h"
OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *setting_obj,
QObject *parent)
@@ -66,6 +67,63 @@ QString replaceTokensIn(QString text, QMap<QString, QString> with)
return result;
}
+QDir OneSixInstance::reconstructAssets(std::shared_ptr<OneSixVersion> version)
+{
+ QDir assetsDir = QDir("assets/");
+ QDir indexDir = QDir(PathCombine(assetsDir.path(), "indexes"));
+ QDir objectDir = QDir(PathCombine(assetsDir.path(), "objects"));
+ QDir virtualDir = QDir(PathCombine(assetsDir.path(), "virtual"));
+
+ QString indexPath = PathCombine(indexDir.path(), version->assets + ".json");
+ QFile indexFile(indexPath);
+ QDir virtualRoot(PathCombine(virtualDir.path(), version->assets));
+
+ if(!indexFile.exists())
+ {
+ QLOG_ERROR() << "No assets index file" << indexPath << "; can't reconstruct assets";
+ return virtualRoot;
+ }
+
+ QLOG_DEBUG() << "reconstructAssets" << assetsDir.path() << indexDir.path() << objectDir.path() << virtualDir.path() << virtualRoot.path();
+
+ AssetsIndex index;
+ bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(indexPath, &index);
+
+ if(loadAssetsIndex)
+ {
+ if(index.isVirtual)
+ {
+ QLOG_INFO() << "Reconstructing virtual assets folder at" << virtualRoot.path();
+
+ for(QString map : index.objects.keys())
+ {
+ AssetObject asset_object = index.objects.value(map);
+ QString target_path = PathCombine(virtualRoot.path(), map);
+ QFile target(target_path);
+
+ QString tlk = asset_object.hash.left(2);
+
+ QString original_path = PathCombine(PathCombine(objectDir.path(), tlk), asset_object.hash);
+ QFile original(original_path);
+ if(!target.exists())
+ {
+ QFileInfo info(target_path);
+ QDir target_dir = info.dir();
+ //QLOG_DEBUG() << target_dir;
+ if(!target_dir.exists()) QDir("").mkpath(target_dir.path());
+
+ bool couldCopy = original.copy(target_path);
+ QLOG_DEBUG() << " Copying" << original_path << "to" << target_path << QString::number(couldCopy);// << original.errorString();
+ }
+ }
+
+ // TODO: Write last used time to virtualRoot/.lastused
+ }
+ }
+
+ return virtualRoot;
+}
+
QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account)
{
I_D(OneSixInstance);
@@ -93,10 +151,14 @@ QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account)
QString absRootDir = QDir(minecraftRoot()).absolutePath();
token_mapping["game_directory"] = absRootDir;
QString absAssetsDir = QDir("assets/").absolutePath();
- token_mapping["game_assets"] = absAssetsDir;
+ token_mapping["game_assets"] = reconstructAssets(d->version).absolutePath();
//TODO: this is something new and not even fully implemented in the vanilla launcher.
token_mapping["user_properties"] = "{ }";
+ // 1.7.3+ assets tokens
+ token_mapping["assets_root"] = absAssetsDir;
+ token_mapping["assets_index_name"] = version->assets;
+
QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts);
for (int i = 0; i < parts.length(); i++)
{
diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h
index cdfdf324..f869e345 100644
--- a/logic/OneSixInstance.h
+++ b/logic/OneSixInstance.h
@@ -16,6 +16,7 @@
#pragma once
#include <QStringList>
+#include <QDir>
#include "BaseInstance.h"
@@ -73,4 +74,5 @@ public:
private:
QStringList processMinecraftArgs(MojangAccountPtr account);
+ QDir reconstructAssets(std::shared_ptr<OneSixVersion> version);
};
diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp
index 7be0c056..44314e1e 100644
--- a/logic/OneSixUpdate.cpp
+++ b/logic/OneSixUpdate.cpp
@@ -29,6 +29,7 @@
#include "OneSixLibrary.h"
#include "OneSixInstance.h"
#include "net/ForgeMirrors.h"
+#include "assets/AssetsUtils.h"
#include "pathutils.h"
#include <JlCompress.h>
@@ -50,7 +51,7 @@ void OneSixUpdate::executeTask()
return;
}
- if(m_only_prepare)
+ if (m_only_prepare)
{
if (m_inst->shouldUpdate())
{
@@ -93,7 +94,7 @@ void OneSixUpdate::checkJavaOnline()
checker.reset(new JavaChecker());
connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this,
- SLOT(checkFinishedOnline(JavaCheckResult)));
+ SLOT(checkFinishedOnline(JavaCheckResult)));
checker->performCheck(java_path);
}
@@ -193,6 +194,89 @@ void OneSixUpdate::versionFileFailed()
emitFailed("Failed to download the version description. Try again.");
}
+void OneSixUpdate::assetIndexStart()
+{
+ setStatus("Updating asset index.");
+ OneSixInstance *inst = (OneSixInstance *)m_inst;
+ std::shared_ptr<OneSixVersion> version = inst->getFullVersion();
+ QString assetName = version->assets;
+ QUrl indexUrl("https://s3.amazonaws.com/Minecraft.Download/indexes/" + assetName + ".json");
+ QString localPath = assetName + ".json";
+ auto job = new NetJob("Asset index for " + inst->name());
+
+ auto metacache = MMC->metacache();
+ auto entry = metacache->resolveEntry("asset_indexes", localPath);
+ job->addNetAction(CacheDownload::make(indexUrl, entry));
+ jarlibDownloadJob.reset(job);
+
+ connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetIndexFinished()));
+ connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(assetIndexFailed()));
+ connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)),
+ SIGNAL(progress(qint64, qint64)));
+
+ jarlibDownloadJob->start();
+}
+
+void OneSixUpdate::assetIndexFinished()
+{
+ AssetsIndex index;
+
+ OneSixInstance *inst = (OneSixInstance *)m_inst;
+ std::shared_ptr<OneSixVersion> version = inst->getFullVersion();
+ QString assetName = version->assets;
+
+ QString asset_fname = "assets/indexes/" + assetName + ".json";
+ if (!AssetsUtils::loadAssetsIndexJson(asset_fname, &index))
+ {
+ emitFailed("Failed to read the assets index!");
+ }
+
+ QList<Md5EtagDownloadPtr> dls;
+ for (auto object : index.objects.values())
+ {
+ QString objectName = object.hash.left(2) + "/" + object.hash;
+ QFileInfo objectFile("assets/objects/" + objectName);
+ if ((!objectFile.isFile()) || (objectFile.size() != object.size))
+ {
+ auto objectDL = MD5EtagDownload::make(
+ QUrl("http://resources.download.minecraft.net/" + objectName),
+ objectFile.filePath());
+ dls.append(objectDL);
+ }
+ }
+ if(dls.size())
+ {
+ auto job = new NetJob("Assets for " + inst->name());
+ for(auto dl: dls)
+ job->addNetAction(dl);
+ jarlibDownloadJob.reset(job);
+ connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetsFinished()));
+ connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(assetsFailed()));
+ connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)),
+ SIGNAL(progress(qint64, qint64)));
+ jarlibDownloadJob->start();
+ return;
+ }
+ assetsFinished();
+}
+
+void OneSixUpdate::assetIndexFailed()
+{
+ emitFailed("Failed to download the assets index!");
+}
+
+void OneSixUpdate::assetsFinished()
+{
+ prepareForLaunch();
+}
+
+void OneSixUpdate::assetsFailed()
+{
+ emitFailed("Failed to download assets!");
+}
+
+
+
void OneSixUpdate::jarlibStart()
{
setStatus("Getting the library files from Mojang.");
@@ -206,17 +290,22 @@ void OneSixUpdate::jarlibStart()
return;
}
+ // Build a list of URLs that will need to be downloaded.
std::shared_ptr<OneSixVersion> version = inst->getFullVersion();
+ // minecraft.jar for this version
+ {
+ QString version_id = version->id;
+ QString localPath = version_id + "/" + version_id + ".jar";
+ QString urlstr = "http://s3.amazonaws.com/Minecraft.Download/versions/" + localPath;
- // download the right jar, save it in versions/$version/$version.jar
- QString urlstr("http://s3.amazonaws.com/Minecraft.Download/versions/");
- urlstr += version->id + "/" + version->id + ".jar";
- QString targetstr("versions/");
- targetstr += version->id + "/" + version->id + ".jar";
+ auto job = new NetJob("Libraries for instance " + inst->name());
- auto job = new NetJob("Libraries for instance " + inst->name());
- job->addNetAction(FileDownload::make(QUrl(urlstr), targetstr));
- jarlibDownloadJob.reset(job);
+ auto metacache = MMC->metacache();
+ auto entry = metacache->resolveEntry("versions", localPath);
+ job->addNetAction(CacheDownload::make(QUrl(urlstr), entry));
+
+ jarlibDownloadJob.reset(job);
+ }
auto libs = version->getActiveNativeLibs();
libs.append(version->getActiveNormalLibs());
@@ -265,7 +354,7 @@ void OneSixUpdate::jarlibStart()
void OneSixUpdate::jarlibFinished()
{
- prepareForLaunch();
+ assetIndexStart();
}
void OneSixUpdate::jarlibFailed()
diff --git a/logic/OneSixUpdate.h b/logic/OneSixUpdate.h
index 7ff9d881..00b769c7 100644
--- a/logic/OneSixUpdate.h
+++ b/logic/OneSixUpdate.h
@@ -43,6 +43,13 @@ slots:
void jarlibFinished();
void jarlibFailed();
+ void assetIndexStart();
+ void assetIndexFinished();
+ void assetIndexFailed();
+
+ void assetsFinished();
+ void assetsFailed();
+
void checkJavaOnline();
void checkFinishedOnline(JavaCheckResult result);
void checkFinishedOffline(JavaCheckResult result);
diff --git a/logic/OneSixVersion.cpp b/logic/OneSixVersion.cpp
index cc5b1de1..e586402b 100644
--- a/logic/OneSixVersion.cpp
+++ b/logic/OneSixVersion.cpp
@@ -17,6 +17,8 @@
#include "logic/OneSixLibrary.h"
#include "logic/OneSixRule.h"
+#include "logger/QsLog.h"
+
std::shared_ptr<OneSixVersion> fromJsonV4(QJsonObject root,
std::shared_ptr<OneSixVersion> fullVersion)
{
@@ -60,6 +62,18 @@ std::shared_ptr<OneSixVersion> fromJsonV4(QJsonObject root,
fullVersion->releaseTime = root.value("releaseTime").toString();
fullVersion->time = root.value("time").toString();
+ auto assetsID = root.value("assets");
+ if (assetsID.isString())
+ {
+ fullVersion->assets = assetsID.toString();
+ }
+ else
+ {
+ fullVersion->assets = "legacy";
+ }
+
+ QLOG_DEBUG() << "Assets version:" << fullVersion->assets;
+
// Iterate through the list, if it's a list.
auto librariesValue = root.value("libraries");
if (!librariesValue.isArray())
@@ -151,7 +165,7 @@ std::shared_ptr<OneSixVersion> OneSixVersion::fromJson(QJsonObject root)
root.value("minimumLauncherVersion").toDouble();
// ADD MORE HERE :D
- if (launcher_ver > 0 && launcher_ver <= 11)
+ if (launcher_ver > 0 && launcher_ver <= 12)
return fromJsonV4(root, readVersion);
else
{
diff --git a/logic/OneSixVersion.h b/logic/OneSixVersion.h
index 5718dafe..036f3d53 100644
--- a/logic/OneSixVersion.h
+++ b/logic/OneSixVersion.h
@@ -56,6 +56,8 @@ public:
QString releaseTime;
/// Release type - "release" or "snapshot"
QString type;
+ /// Assets type - "legacy" or a version ID
+ QString assets;
/**
* DEPRECATED: Old versions of the new vanilla launcher used this
* ex: "username_session_version"
diff --git a/logic/assets/AssetsUtils.cpp b/logic/assets/AssetsUtils.cpp
new file mode 100644
index 00000000..11d928cf
--- /dev/null
+++ b/logic/assets/AssetsUtils.cpp
@@ -0,0 +1,227 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <QDir>
+#include <QDirIterator>
+#include <QCryptographicHash>
+#include <QJsonParseError>
+#include <QJsonDocument>
+#include <QJsonObject>
+
+#include "AssetsUtils.h"
+#include "MultiMC.h"
+
+namespace AssetsUtils
+{
+void migrateOldAssets()
+{
+ QDir assets_dir("assets");
+ if (!assets_dir.exists())
+ return;
+ assets_dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
+ int base_length = assets_dir.path().length();
+
+ QList<QString> blacklist = {"indexes", "objects", "virtual"};
+
+ if (!assets_dir.exists("objects"))
+ assets_dir.mkdir("objects");
+ QDir objects_dir("assets/objects");
+
+ QDirIterator iterator(assets_dir, QDirIterator::Subdirectories);
+ int successes = 0;
+ int failures = 0;
+ while (iterator.hasNext())
+ {
+ QString currentDir = iterator.next();
+ currentDir = currentDir.remove(0, base_length + 1);
+
+ bool ignore = false;
+ for (QString blacklisted : blacklist)
+ {
+ if (currentDir.startsWith(blacklisted))
+ ignore = true;
+ }
+
+ if (!iterator.fileInfo().isDir() && !ignore)
+ {
+ QString filename = iterator.filePath();
+
+ QFile input(filename);
+ input.open(QIODevice::ReadOnly | QIODevice::WriteOnly);
+ QString sha1sum =
+ QCryptographicHash::hash(input.readAll(), QCryptographicHash::Sha1)
+ .toHex()
+ .constData();
+
+ QString object_name = filename.remove(0, base_length + 1);
+ QLOG_DEBUG() << "Processing" << object_name << ":" << sha1sum << input.size();
+
+ QString object_tlk = sha1sum.left(2);
+ QString object_tlk_dir = objects_dir.path() + "/" + object_tlk;
+
+ QDir tlk_dir(object_tlk_dir);
+ if (!tlk_dir.exists())
+ objects_dir.mkdir(object_tlk);
+
+ QString new_filename = tlk_dir.path() + "/" + sha1sum;
+ QFile new_object(new_filename);
+ if (!new_object.exists())
+ {
+ bool rename_success = input.rename(new_filename);
+ QLOG_DEBUG() << " Doesn't exist, copying to" << new_filename << ":"
+ << QString::number(rename_success);
+ if (rename_success)
+ successes++;
+ else
+ failures++;
+ }
+ else
+ {
+ input.remove();
+ QLOG_DEBUG() << " Already exists, deleting original and not copying.";
+ }
+ }
+ }
+
+ if (successes + failures == 0)
+ {
+ QLOG_DEBUG() << "No legacy assets needed importing.";
+ }
+ else
+ {
+ QLOG_DEBUG() << "Finished copying legacy assets:" << successes << "successes and"
+ << failures << "failures.";
+
+ QDirIterator cleanup_iterator(assets_dir);
+
+ while (cleanup_iterator.hasNext())
+ {
+ QString currentDir = cleanup_iterator.next();
+ currentDir = currentDir.remove(0, base_length + 1);
+
+ bool ignore = false;
+ for (QString blacklisted : blacklist)
+ {
+ if (currentDir.startsWith(blacklisted))
+ ignore = true;
+ }
+
+ if (cleanup_iterator.fileInfo().isDir() && !ignore)
+ {
+ QString path = cleanup_iterator.filePath();
+ QDir folder(path);
+
+ QLOG_DEBUG() << "Cleaning up legacy assets folder:" << path;
+
+ folder.removeRecursively();
+ }
+ }
+ }
+}
+
+/*
+ * Returns true on success, with index populated
+ * index is undefined otherwise
+ */
+bool loadAssetsIndexJson(QString path, AssetsIndex *index)
+{
+ /*
+ {
+ "objects": {
+ "icons/icon_16x16.png": {
+ "hash": "bdf48ef6b5d0d23bbb02e17d04865216179f510a",
+ "size": 3665
+ },
+ ...
+ }
+ }
+ }
+ */
+
+ QFile file(path);
+
+ // Try to open the file and fail if we can't.
+ // TODO: We should probably report this error to the user.
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ QLOG_ERROR() << "Failed to read assets index file" << path;
+ return false;
+ }
+
+ // Read the file and close it.
+ QByteArray jsonData = file.readAll();
+ file.close();
+
+ QJsonParseError parseError;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError);
+
+ // Fail if the JSON is invalid.
+ if (parseError.error != QJsonParseError::NoError)
+ {
+ QLOG_ERROR() << "Failed to parse assets index file:" << parseError.errorString()
+ << "at offset " << QString::number(parseError.offset);
+ return false;
+ }
+
+ // Make sure the root is an object.
+ if (!jsonDoc.isObject())
+ {
+ QLOG_ERROR() << "Invalid assets index JSON: Root should be an array.";
+ return false;
+ }
+
+ QJsonObject root = jsonDoc.object();
+
+ QJsonValue isVirtual = root.value("virtual");
+ if (!isVirtual.isUndefined())
+ {
+ index->isVirtual = isVirtual.toBool(false);
+ }
+
+ QJsonValue objects = root.value("objects");
+ QVariantMap map = objects.toVariant().toMap();
+
+ for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter)
+ {
+ // QLOG_DEBUG() << iter.key();
+
+ QVariant variant = iter.value();
+ QVariantMap nested_objects = variant.toMap();
+
+ AssetObject object;
+
+ for (QVariantMap::const_iterator nested_iter = nested_objects.begin();
+ nested_iter != nested_objects.end(); ++nested_iter)
+ {
+ // QLOG_DEBUG() << nested_iter.key() << nested_iter.value().toString();
+ QString key = nested_iter.key();
+ QVariant value = nested_iter.value();
+
+ if (key == "hash")
+ {
+ object.hash = value.toString();
+ }
+ else if (key == "size")
+ {
+ object.size = value.toDouble();
+ }
+ }
+
+ index->objects.insert(iter.key(), object);
+ }
+
+ return true;
+}
+}
diff --git a/logic/OneSixAssets.h b/logic/assets/AssetsUtils.h
index 948068b8..5276d5a5 100644
--- a/logic/OneSixAssets.h
+++ b/logic/assets/AssetsUtils.h
@@ -14,32 +14,26 @@
*/
#pragma once
-#include "net/NetJob.h"
-class Private;
-class ThreadedDeleter;
+#include <QString>
+#include <QMap>
-class OneSixAssets : public QObject
-{
- Q_OBJECT
-signals:
- void failed();
- void finished();
- void indexStarted();
- void filesStarted();
- void filesProgress(int, int, int);
-
-public
-slots:
- void S3BucketFinished();
- void downloadFinished();
+class AssetObject;
-public:
- void start();
+struct AssetObject
+{
+ QString hash;
+ qint64 size;
+};
-private:
- ThreadedDeleter *deleter;
- QStringList nuke_whitelist;
- NetJobPtr index_job;
- NetJobPtr files_job;
+struct AssetsIndex
+{
+ QMap<QString, AssetObject> objects;
+ bool isVirtual = false;
};
+
+namespace AssetsUtils
+{
+void migrateOldAssets();
+bool loadAssetsIndexJson(QString file, AssetsIndex* index);
+}
diff --git a/logic/net/FileDownload.cpp b/logic/net/MD5EtagDownload.cpp
index 38f0b9c2..4b9f52af 100644
--- a/logic/net/FileDownload.cpp
+++ b/logic/net/MD5EtagDownload.cpp
@@ -14,12 +14,12 @@
*/
#include "MultiMC.h"
-#include "FileDownload.h"
+#include "MD5EtagDownload.h"
#include <pathutils.h>
#include <QCryptographicHash>
#include "logger/QsLog.h"
-FileDownload::FileDownload(QUrl url, QString target_path) : NetAction()
+MD5EtagDownload::MD5EtagDownload(QUrl url, QString target_path) : NetAction()
{
m_url = url;
m_target_path = target_path;
@@ -27,7 +27,7 @@ FileDownload::FileDownload(QUrl url, QString target_path) : NetAction()
m_status = Job_NotStarted;
}
-void FileDownload::start()
+void MD5EtagDownload::start()
{
QString filename = m_target_path;
m_output_file.setFileName(filename);
@@ -58,7 +58,7 @@ void FileDownload::start()
return;
}
- QLOG_INFO() << "Downloading " << m_url.toString();
+ QLOG_INFO() << "Downloading " << m_url.toString() << " expecting " << m_expected_md5;
QNetworkRequest request(m_url);
request.setRawHeader(QString("If-None-Match").toLatin1(), m_expected_md5.toLatin1());
request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)");
@@ -84,19 +84,19 @@ void FileDownload::start()
connect(rep, SIGNAL(readyRead()), SLOT(downloadReadyRead()));
}
-void FileDownload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
+void MD5EtagDownload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
emit progress(index_within_job, bytesReceived, bytesTotal);
}
-void FileDownload::downloadError(QNetworkReply::NetworkError error)
+void MD5EtagDownload::downloadError(QNetworkReply::NetworkError error)
{
// error happened during download.
// TODO: log the reason why
m_status = Job_Failed;
}
-void FileDownload::downloadFinished()
+void MD5EtagDownload::downloadFinished()
{
// if the download succeeded
if (m_status != Job_Failed)
@@ -105,6 +105,7 @@ void FileDownload::downloadFinished()
m_status = Job_Finished;
m_output_file.close();
+ QLOG_INFO() << "Finished " << m_url.toString() << " got " << m_reply->rawHeader("ETag").constData();
m_reply.reset();
emit succeeded(index_within_job);
return;
@@ -119,7 +120,7 @@ void FileDownload::downloadFinished()
}
}
-void FileDownload::downloadReadyRead()
+void MD5EtagDownload::downloadReadyRead()
{
if (!m_output_file.isOpen())
{
diff --git a/logic/net/FileDownload.h b/logic/net/MD5EtagDownload.h
index 58380e86..416ab9de 100644
--- a/logic/net/FileDownload.h
+++ b/logic/net/MD5EtagDownload.h
@@ -18,8 +18,8 @@
#include "NetAction.h"
#include <QFile>
-typedef std::shared_ptr<class FileDownload> FileDownloadPtr;
-class FileDownload : public NetAction
+typedef std::shared_ptr<class MD5EtagDownload> Md5EtagDownloadPtr;
+class MD5EtagDownload : public NetAction
{
Q_OBJECT
public:
@@ -35,10 +35,10 @@ public:
QFile m_output_file;
public:
- explicit FileDownload(QUrl url, QString target_path);
- static FileDownloadPtr make(QUrl url, QString target_path)
+ explicit MD5EtagDownload(QUrl url, QString target_path);
+ static Md5EtagDownloadPtr make(QUrl url, QString target_path)
{
- return FileDownloadPtr(new FileDownload(url, target_path));
+ return Md5EtagDownloadPtr(new MD5EtagDownload(url, target_path));
}
protected
slots:
diff --git a/logic/net/NetJob.cpp b/logic/net/NetJob.cpp
index 333cdcbf..8b79bc54 100644
--- a/logic/net/NetJob.cpp
+++ b/logic/net/NetJob.cpp
@@ -16,7 +16,7 @@
#include "NetJob.h"
#include "pathutils.h"
#include "MultiMC.h"
-#include "FileDownload.h"
+#include "MD5EtagDownload.h"
#include "ByteArrayDownload.h"
#include "CacheDownload.h"
diff --git a/logic/net/NetJob.h b/logic/net/NetJob.h
index a7027e10..6e2e7607 100644
--- a/logic/net/NetJob.h
+++ b/logic/net/NetJob.h
@@ -18,7 +18,7 @@
#include <QLabel>
#include "NetAction.h"
#include "ByteArrayDownload.h"
-#include "FileDownload.h"
+#include "MD5EtagDownload.h"
#include "CacheDownload.h"
#include "HttpMetaCache.h"
#include "ForgeXzDownload.h"
diff --git a/logic/updater/DownloadUpdateTask.cpp b/logic/updater/DownloadUpdateTask.cpp
index ef975c93..3fe24482 100644
--- a/logic/updater/DownloadUpdateTask.cpp
+++ b/logic/updater/DownloadUpdateTask.cpp
@@ -280,7 +280,7 @@ void DownloadUpdateTask::processFileLists()
QString dlPath = PathCombine(m_updateFilesDir.path(), 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.
- FileDownloadPtr download = FileDownload::make(source.url, dlPath);
+ auto download = MD5EtagDownload::make(source.url, dlPath);
download->m_check_md5 = true;
download->m_expected_md5 = entry.md5;
netJob->addNetAction(download);