From e3b55067eb51ee82e72b41a1919406d768e00b89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sat, 24 Aug 2013 03:09:46 +0200 Subject: Legacy jar reassembly, base of proper custom jar support --- logic/BaseInstance.cpp | 54 +++++++++++ logic/BaseInstance.h | 19 ++++ logic/IconListModel.cpp | 4 +- logic/InstanceFactory.cpp | 3 + logic/LegacyInstance.cpp | 25 ++++-- logic/LegacyInstance.h | 37 +------- logic/LegacyUpdate.cpp | 221 +++++++++++++++++++++++++++------------------- logic/LegacyUpdate.h | 7 +- logic/ModList.h | 1 + logic/OneSixInstance.cpp | 12 ++- logic/OneSixInstance.h | 3 + logic/OneSixUpdate.cpp | 2 +- logic/net/DownloadJob.cpp | 2 +- 13 files changed, 249 insertions(+), 141 deletions(-) (limited to 'logic') diff --git a/logic/BaseInstance.cpp b/logic/BaseInstance.cpp index c2df34e1..bd2aad8a 100644 --- a/logic/BaseInstance.cpp +++ b/logic/BaseInstance.cpp @@ -42,6 +42,15 @@ BaseInstance::BaseInstance( BaseInstancePrivate* d_in, settings().registerSetting(new Setting("notes", "")); settings().registerSetting(new Setting("lastLaunchTime", 0)); + /* + * custom base jar has no default. it is determined in code... see the accessor methods for it + * + * for instances that DO NOT have the CustomBaseJar setting (legacy instances), + * [.]minecraft/bin/mcbackup.jar is the default base jar + */ + settings().registerSetting(new Setting("UseCustomBaseJar", true)); + settings().registerSetting(new Setting("CustomBaseJar", "")); + // Java Settings settings().registerSetting(new Setting("OverrideJava", false)); settings().registerSetting(new OverrideSetting("JavaPath", globalSettings->getSetting("JavaPath"))); @@ -121,6 +130,51 @@ SettingsObject &BaseInstance::settings() const return *d->m_settings; } +QString BaseInstance::baseJar() const +{ + I_D(BaseInstance); + bool customJar = d->m_settings->get("UseCustomBaseJar").toBool(); + if(customJar) + { + return customBaseJar(); + } + else + return defaultBaseJar(); +} + +QString BaseInstance::customBaseJar() const +{ + I_D(BaseInstance); + QString value = d->m_settings->get ( "CustomBaseJar" ).toString(); + if(value.isNull() || value.isEmpty()) + { + return defaultCustomBaseJar(); + } + return value; +} + +void BaseInstance::setCustomBaseJar ( QString val ) +{ + I_D(BaseInstance); + if(val.isNull() || val.isEmpty() || val == defaultCustomBaseJar()) + d->m_settings->reset ( "CustomBaseJar" ); + else + d->m_settings->set ( "CustomBaseJar", val ); +} + +void BaseInstance::setShouldUseCustomBaseJar ( bool val ) +{ + I_D(BaseInstance); + d->m_settings->set ( "UseCustomBaseJar", val ); +} + +bool BaseInstance::shouldUseCustomBaseJar() const +{ + I_D(BaseInstance); + return d->m_settings->get ( "UseCustomBaseJar" ).toBool(); +} + + qint64 BaseInstance::lastLaunch() const { I_D(BaseInstance); diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h index af721b04..48481e26 100644 --- a/logic/BaseInstance.h +++ b/logic/BaseInstance.h @@ -89,6 +89,25 @@ public: */ virtual bool shouldUpdate() const = 0; virtual void setShouldUpdate(bool val) = 0; + + /// Get the curent base jar of this instance. By default, it's the versions/$version/$version.jar + QString baseJar() const; + + /// the default base jar of this instance + virtual QString defaultBaseJar() const = 0; + /// the default custom base jar of this instance + virtual QString defaultCustomBaseJar() const = 0; + + /*! + * Whether or not custom base jar is used + */ + bool shouldUseCustomBaseJar() const; + void setShouldUseCustomBaseJar(bool val); + /*! + * The value of the custom base jar + */ + QString customBaseJar() const; + void setCustomBaseJar(QString val); /** * Gets the time that the instance was last launched. diff --git a/logic/IconListModel.cpp b/logic/IconListModel.cpp index 2d2fb6cf..b531df43 100644 --- a/logic/IconListModel.cpp +++ b/logic/IconListModel.cpp @@ -39,8 +39,8 @@ IconList::IconList() : QAbstractListModel(), d(new Private()) } // FIXME: get from settings - ensurePathExists("icons/"); - QDir user_icons("icons/"); + ensureFolderPathExists("icons"); + QDir user_icons("icons"); file_info_list = user_icons.entryInfoList(QDir::Files, QDir::Name); for(auto file_info: file_info_list) { diff --git a/logic/InstanceFactory.cpp b/logic/InstanceFactory.cpp index f3511157..f0630568 100644 --- a/logic/InstanceFactory.cpp +++ b/logic/InstanceFactory.cpp @@ -90,16 +90,19 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance( BaseInstance*& m_settings->set("InstanceType", "Legacy"); inst = new LegacyInstance(instDir, m_settings, this); inst->setIntendedVersionId(version->descriptor); + inst->setShouldUseCustomBaseJar(false); break; case MinecraftVersion::OneSix: m_settings->set("InstanceType", "OneSix"); inst = new OneSixInstance(instDir, m_settings, this); inst->setIntendedVersionId(version->descriptor); + inst->setShouldUseCustomBaseJar(false); break; case MinecraftVersion::Nostalgia: m_settings->set("InstanceType", "Nostalgia"); inst = new NostalgiaInstance(instDir, m_settings, this); inst->setIntendedVersionId(version->descriptor); + inst->setShouldUseCustomBaseJar(false); break; default: { diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp index cf2c622c..9e191740 100644 --- a/logic/LegacyInstance.cpp +++ b/logic/LegacyInstance.cpp @@ -169,30 +169,25 @@ QString LegacyInstance::resourceDir() const return PathCombine(minecraftRoot(), "resources"); } -QString LegacyInstance::mcJar() const +QString LegacyInstance::runnableJar() const { return PathCombine(binDir(), "minecraft.jar"); } -QString LegacyInstance::mcBackup() const -{ - return PathCombine(binDir(), "mcbackup.jar"); -} - QString LegacyInstance::modListFile() const { return PathCombine(instanceRoot(), "modlist"); } - +/* bool LegacyInstance::shouldUpdateCurrentVersion() const { - QFileInfo jar(mcJar()); + QFileInfo jar(runnableJar()); return jar.lastModified().toUTC().toMSecsSinceEpoch() != lastCurrentVersionUpdate(); } void LegacyInstance::updateCurrentVersion(bool keepCurrent) { - QFileInfo jar(mcJar()); + QFileInfo jar(runnableJar()); if(!jar.exists()) { @@ -221,6 +216,7 @@ void LegacyInstance::setLastCurrentVersionUpdate ( qint64 val ) I_D(LegacyInstance); d->m_settings->set ( "lastVersionUpdate", val ); } +*/ bool LegacyInstance::shouldRebuild() const { I_D(LegacyInstance); @@ -278,3 +274,14 @@ void LegacyInstance::setShouldUpdate ( bool val ) { settings().set ( "ShouldUpdate", val ); } + +QString LegacyInstance::defaultBaseJar() const +{ + return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar"; +} + +QString LegacyInstance::defaultCustomBaseJar() const +{ + return PathCombine(binDir(), "mcbackup.jar"); +} + diff --git a/logic/LegacyInstance.h b/logic/LegacyInstance.h index 4c8d8f69..46c531d9 100644 --- a/logic/LegacyInstance.h +++ b/logic/LegacyInstance.h @@ -13,10 +13,7 @@ public: explicit LegacyInstance(const QString &rootDir, SettingsObject * settings, QObject *parent = 0); /// Path to the instance's minecraft.jar - QString mcJar() const; - - //! Path to the instance's mcbackup.jar - QString mcBackup() const; + QString runnableJar() const; //! Path to the instance's modlist file. QString modListFile() const; @@ -34,35 +31,6 @@ public: QString coreModsDir() const; QString resourceDir() const; - /*! - * \brief Checks whether or not the currentVersion of the instance needs to be updated. - * If this returns true, updateCurrentVersion is called. In the - * standard instance, this is determined by checking a timestamp - * stored in the instance config file against the last modified time of Minecraft.jar. - * \return True if updateCurrentVersion() should be called. - */ - bool shouldUpdateCurrentVersion() const; - - /*! - * \brief Updates the current version. - * This function should first set the current version timestamp - * (setCurrentVersionTimestamp()) to the current time. Next, if - * keepCurrent is false, this function should check what the - * instance's current version is and call setCurrentVersion() to - * update it. This function will automatically be called when the - * instance is loaded if shouldUpdateCurrentVersion returns true. - * \param keepCurrent If true, only the version timestamp will be updated. - */ - void updateCurrentVersion(bool keepCurrent = false); - - /*! - * Gets the last time that the current version was checked. - * This is checked against the last modified time on the jar file to see if - * the current version needs to be checked again. - */ - qint64 lastCurrentVersionUpdate() const; - void setLastCurrentVersionUpdate(qint64 val); - /*! * Whether or not the instance's minecraft.jar needs to be rebuilt. * If this is true, when the instance launches, its jar mods will be @@ -90,6 +58,9 @@ public: virtual void cleanupAfterRun(); virtual QDialog * createModEditDialog ( QWidget* parent ); + virtual QString defaultBaseJar() const; + virtual QString defaultCustomBaseJar() const; + protected slots: virtual void jarModsChanged(); }; \ No newline at end of file diff --git a/logic/LegacyUpdate.cpp b/logic/LegacyUpdate.cpp index a748bad3..9644cb80 100644 --- a/logic/LegacyUpdate.cpp +++ b/logic/LegacyUpdate.cpp @@ -4,9 +4,11 @@ #include "BaseInstance.h" #include "LegacyInstance.h" #include "net/NetWorker.h" +#include "ModList.h" #include #include #include +#include LegacyUpdate::LegacyUpdate ( BaseInstance* inst, QObject* parent ) : BaseUpdate ( inst, parent ) {} @@ -22,7 +24,7 @@ void LegacyUpdate::lwjglStart() lwjglVersion = inst->lwjglVersion(); lwjglTargetPath = PathCombine("lwjgl", lwjglVersion ); - lwjglNativesPath = PathCombine( lwjglTargetPath, "natives/"); + lwjglNativesPath = PathCombine( lwjglTargetPath, "natives"); // if the 'done' file exists, we don't have to download this again QFileInfo doneFile(PathCombine(lwjglTargetPath, "done")); @@ -112,7 +114,7 @@ void LegacyUpdate::extractLwjgl() { // make sure the directories are there - bool success = ensurePathExists(lwjglNativesPath); + bool success = ensureFolderPathExists(lwjglNativesPath); if(!success) { @@ -201,33 +203,14 @@ void LegacyUpdate::lwjglFailed() void LegacyUpdate::jarStart() { - setStatus("Checking ..."); LegacyInstance * inst = (LegacyInstance *) m_inst; - QString current_version_id = inst->currentVersionId(); - QString intended_version_id = inst->intendedVersionId(); - bool shouldUpdate = inst->shouldUpdate(); - if(!shouldUpdate) + if(!inst->shouldUpdate() || inst->shouldUseCustomBaseJar()) { - emitSucceeded(); + ModTheJar(); return; } - // nuke the backup file, we are replacing the base jar anyway - QFile mc_backup(inst->mcBackup()); - if (mc_backup.exists()) - { - mc_backup.remove(); - } - - // Get a pointer to the version object that corresponds to the instance's version. - auto targetVersion = MinecraftVersionList::getMainList().findVersion(intended_version_id); - - if(!targetVersion) - { - emitFailed("Not a valid version:" + intended_version_id); - return; - } - + setStatus("Checking for jar updates..."); // Make directories QDir binDir(inst->binDir()); if (!binDir.exists() && !binDir.mkpath(".")) @@ -239,14 +222,11 @@ void LegacyUpdate::jarStart() // Build a list of URLs that will need to be downloaded. setStatus("Downloading new minecraft.jar"); - // This will be either 'minecraft' or the version number, depending on where - // we're downloading from. - QString jarFilename = "minecraft"; - QString download_path = PathCombine(inst->minecraftRoot(), "bin/minecraft.jar"); - QString urlstr("http://s3.amazonaws.com/Minecraft.Download/versions/"); - urlstr += targetVersion->descriptor + "/" + targetVersion->descriptor + ".jar"; - auto dljob = DownloadJob::create(QUrl(urlstr), download_path); + QString intended_version_id = inst->intendedVersionId(); + urlstr += intended_version_id + "/" + intended_version_id + ".jar"; + + auto dljob = DownloadJob::create(QUrl(urlstr), inst->defaultBaseJar()); legacyDownloadJob.reset(new JobList()); legacyDownloadJob->add(dljob); @@ -259,7 +239,7 @@ void LegacyUpdate::jarStart() void LegacyUpdate::jarFinished() { // process the jar - emitSucceeded(); + ModTheJar(); } void LegacyUpdate::jarFailed() @@ -268,74 +248,132 @@ void LegacyUpdate::jarFailed() emitFailed("Failed to download the minecraft jar. Try again later."); } +bool LegacyUpdate::MergeZipFiles( QuaZip* into, QFileInfo from, QSet< QString >& contained ) +{ + setStatus("Installing mods - Adding " + from.fileName()); + + QuaZip modZip(from.filePath()); + modZip.open(QuaZip::mdUnzip); + + QuaZipFile fileInsideMod(&modZip); + QuaZipFile zipOutFile( into ); + for(bool more=modZip.goToFirstFile(); more; more=modZip.goToNextFile()) + { + QString filename = modZip.getCurrentFileName(); + if(filename.contains("META-INF")) + continue; + if(contained.contains(filename)) + continue; + contained.insert(filename); + qDebug() << "Adding file " << filename << " from " << from.fileName(); + + if(!fileInsideMod.open(QIODevice::ReadOnly)) + { + return false; + } + if(!zipOutFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileInsideMod.getActualFileName()))) + { + fileInsideMod.close(); + return false; + } + if(!JlCompress::copyData(fileInsideMod, zipOutFile)) + { + zipOutFile.close(); + fileInsideMod.close(); + return false; + } + zipOutFile.close(); + fileInsideMod.close(); + } + return true; +} + void LegacyUpdate::ModTheJar() { - /* LegacyInstance * inst = (LegacyInstance *) m_inst; + + if(!inst->shouldRebuild()) + { + emitSucceeded(); + return; + } + // Get the mod list - auto modList = inst->getJarModList(); + auto modList = inst->jarModList(); - QFileInfo mcBin(inst->binDir()); - QFileInfo mcJar(inst->mcJar()); - QFileInfo mcBackup(inst->mcBackup()); + QFileInfo runnableJar (inst->runnableJar()); + QFileInfo baseJar (inst->baseJar()); + bool base_is_custom = inst->shouldUseCustomBaseJar(); // Nothing to do if there are no jar mods to install, no backup and just the mc jar - if(mcJar.isFile() && !mcBackup.exists() && modList->empty()) + if(base_is_custom) { - inst->setShouldRebuild(false); - emitSucceeded(); - return; + // yes, this can happen if the instance only has the runnable jar and not the base jar + // it *could* be assumed that such an instance is vanilla, but that wouldn't be safe + // because that's not something mmc4 guarantees + if(runnableJar.isFile() && !baseJar.exists() && modList->empty()) + { + inst->setShouldRebuild(false); + emitSucceeded(); + return; + } + + setStatus("Installing mods - backing up minecraft.jar..."); + if (!baseJar.exists() && !QFile::copy(runnableJar.filePath(), baseJar.filePath())) + { + emitFailed("Failed to back up minecraft.jar"); + return; + } } - setStatus("Installing mods - backing up minecraft.jar..."); - if (!mcBackup.exists() && !QFile::copy(mcJar.absoluteFilePath(), mcBackup.absoluteFilePath()) ) + if (!baseJar.exists()) { - emitFailed("Failed to back up minecraft.jar"); + emitFailed("The base jar " + baseJar.filePath() + " does not exist"); return; } - if (mcJar.isFile() && !QFile::remove(mcJar.absoluteFilePath())) + if (runnableJar.exists() && !QFile::remove(runnableJar.filePath())) { emitFailed("Failed to delete old minecraft.jar"); return; } + //TaskStep(); // STEP 1 setStatus("Installing mods - Opening minecraft.jar"); - wxFFileOutputStream jarStream(mcJar.absoluteFilePath()); - wxZipOutputStream zipOut(jarStream); - + QuaZip zipOut(runnableJar.filePath()); + if(!zipOut.open(QuaZip::mdCreate)) + { + QFile::remove(runnableJar.filePath()); + emitFailed("Failed to open the minecraft.jar for modding"); + return; + } // Files already added to the jar. // These files will be skipped. QSet addedFiles; // Modify the jar setStatus("Installing mods - Adding mod files..."); - for (ModList::const_reverse_iterator iter = modList->rbegin(); iter != modList->rend(); iter++) + for (int i = modList->size() - 1; i >= 0; i--) { - wxFileName modFileName = iter->GetFileName(); - setStatus("Installing mods - Adding " + modFileName.GetFullName()); - if (iter->GetModType() == Mod::ModType::MOD_ZIPFILE) + auto &mod = modList->operator[](i); + if (mod.type() == Mod::MOD_ZIPFILE) { - wxFFileInputStream modStream(modFileName.GetFullPath()); - wxZipInputStream zipStream(modStream); - std::unique_ptr entry; - while (entry.reset(zipStream.GetNextEntry()), entry.get() != NULL) + if(!MergeZipFiles(&zipOut, mod.filename(), addedFiles)) { - if (entry->IsDir()) - continue; - - wxString name = entry->GetName(); - if (addedFiles.count(name) == 0) - { - if (!zipOut.CopyEntry(entry.release(), zipStream)) - break; - addedFiles.insert(name); - } + zipOut.close(); + QFile::remove(runnableJar.filePath()); + emitFailed("Failed to add " + mod.filename().fileName() + " to the jar."); + return; } } - else + else if (mod.type() == Mod::MOD_SINGLEFILE) { + zipOut.close(); + QFile::remove(runnableJar.filePath()); + emitFailed("Loose files are NOT supported as jar mods."); + return; + /* wxFileName destFileName = modFileName; destFileName.MakeRelativeTo(m_inst->GetInstModsDir().GetFullPath()); wxString destFile = destFileName.GetFullPath(); @@ -348,34 +386,35 @@ void LegacyUpdate::ModTheJar() addedFiles.insert(destFile); } + */ } - } - - { - wxFFileInputStream inStream(mcBackup.GetFullPath()); - wxZipInputStream zipIn(inStream); - - std::auto_ptr entry; - while (entry.reset(zipIn.GetNextEntry()), entry.get() != NULL) + else if (mod.type() == Mod::MOD_FOLDER) { - wxString name = entry->GetName(); - - if (!name.Matches("META-INF*") && - addedFiles.count(name) == 0) - { - if (!zipOut.CopyEntry(entry.release(), zipIn)) - break; - addedFiles.insert(name); - } + zipOut.close(); + QFile::remove(runnableJar.filePath()); + emitFailed("Folders are NOT supported as jar mods."); + return; } } + if(!MergeZipFiles(&zipOut, baseJar, addedFiles)) + { + zipOut.close(); + QFile::remove(runnableJar.filePath()); + emitFailed("Failed to insert minecraft.jar contents."); + return; + } + // Recompress the jar - TaskStep(); // STEP 3 - SetStatus(_("Installing mods - Recompressing jar...")); - - inst->SetNeedsRebuild(false); - inst->UpdateVersion(true); - return (ExitCode)1; - */ + zipOut.close(); + if(zipOut.getZipError()!=0) + { + QFile::remove(runnableJar.filePath()); + emitFailed("Failed to finalize minecraft.jar!"); + return; + } + inst->setShouldRebuild(false); + //inst->UpdateVersion(true); + emitSucceeded(); + return; } \ No newline at end of file diff --git a/logic/LegacyUpdate.h b/logic/LegacyUpdate.h index 342d1eab..a68d67bb 100644 --- a/logic/LegacyUpdate.h +++ b/logic/LegacyUpdate.h @@ -25,6 +25,8 @@ class MinecraftVersion; class BaseInstance; +class QuaZip; +class Mod; class LegacyUpdate : public BaseUpdate { @@ -46,6 +48,8 @@ private slots: void ModTheJar(); private: + bool MergeZipFiles(QuaZip *into, QFileInfo from, QSet& contained); +private: QSharedPointer m_reply; @@ -59,9 +63,6 @@ private: private: JobListPtr legacyDownloadJob; JobListQueue download_queue; - - // target version, determined during this task - QSharedPointer targetVersion; }; diff --git a/logic/ModList.h b/logic/ModList.h index 3cba69f5..61f3d596 100644 --- a/logic/ModList.h +++ b/logic/ModList.h @@ -37,6 +37,7 @@ public: virtual int columnCount ( const QModelIndex& parent ) const; size_t size() const { return mods.size(); }; + bool empty() const { return size() == 0; } Mod& operator[](size_t index) { return mods[index]; }; /// Reloads the mod list and returns true if the list changed. diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp index b216c930..2eca7cd7 100644 --- a/logic/OneSixInstance.cpp +++ b/logic/OneSixInstance.cpp @@ -92,7 +92,7 @@ MinecraftProcess* OneSixInstance::prepareForLaunch ( QString user, QString sessi return nullptr; auto libs_to_extract = version->getActiveNativeLibs(); QString natives_dir_raw = PathCombine(instanceRoot(), "natives/"); - bool success = ensurePathExists(natives_dir_raw); + bool success = ensureFolderPathExists(natives_dir_raw); if(!success) { // FIXME: handle errors @@ -216,3 +216,13 @@ QSharedPointer< FullVersion > OneSixInstance::getFullVersion() I_D(OneSixInstance); return d->version; } + +QString OneSixInstance::defaultBaseJar() const +{ + return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar"; +} + +QString OneSixInstance::defaultCustomBaseJar() const +{ + return PathCombine(instanceRoot(), "custom.jar"); +} diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h index 18f03a92..654b91dc 100644 --- a/logic/OneSixInstance.h +++ b/logic/OneSixInstance.h @@ -29,6 +29,9 @@ public: bool reloadFullVersion(); /// get the current full version info QSharedPointer getFullVersion(); + + virtual QString defaultBaseJar() const; + virtual QString defaultCustomBaseJar() const; private: QStringList processMinecraftArgs( QString user, QString session ); }; \ No newline at end of file diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp index 2bb2f496..09d3cb14 100644 --- a/logic/OneSixUpdate.cpp +++ b/logic/OneSixUpdate.cpp @@ -91,7 +91,7 @@ void OneSixUpdate::versionFileFinished() // save the version file in $instanceId/version.json { QString version1 = PathCombine(inst_dir, "/version.json"); - ensurePathExists(version1); + ensureFilePathExists(version1); // FIXME: detect errors here, download to a temp file, swap QFile vfile1 (version1); vfile1.open(QIODevice::Truncate | QIODevice::WriteOnly ); diff --git a/logic/net/DownloadJob.cpp b/logic/net/DownloadJob.cpp index ef842dfd..6d87a132 100644 --- a/logic/net/DownloadJob.cpp +++ b/logic/net/DownloadJob.cpp @@ -48,7 +48,7 @@ void DownloadJob::start() m_expected_md5 = hash; } } - if(!ensurePathExists(filename)) + if(!ensureFilePathExists(filename)) { emit fail(); return; -- cgit v1.2.3