diff --git a/.gitignore b/.gitignore index 3221c2d8..091d7d55 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ CMakeLists.txt.user build resources/CMakeFiles resources/MultiMCLauncher.jar +*~ diff --git a/backend/CMakeLists.txt b/backend/CMakeLists.txt index 68d01f08..7a92d5cf 100644 --- a/backend/CMakeLists.txt +++ b/backend/CMakeLists.txt @@ -28,6 +28,8 @@ BaseUpdate.h BaseInstance.h BaseInstance_p.h MinecraftProcess.h +Mod.h +ModList.h # network stuffs net/DownloadJob.h @@ -38,6 +40,7 @@ net/NetWorker.h LegacyInstance.h LegacyInstance_p.h LegacyUpdate.h +LegacyForge.h # 1.6 instances OneSixAssets.h @@ -68,6 +71,8 @@ InstanceFactory.cpp BaseUpdate.cpp BaseInstance.cpp MinecraftProcess.cpp +Mod.cpp +ModList.cpp # network stuffs net/NetWorker.cpp @@ -76,6 +81,7 @@ net/DownloadJob.cpp # legacy instances LegacyInstance.cpp LegacyUpdate.cpp +LegacyForge.cpp # 1.6 instances OneSixAssets.cpp diff --git a/backend/LegacyForge.cpp b/backend/LegacyForge.cpp new file mode 100644 index 00000000..adcf487c --- /dev/null +++ b/backend/LegacyForge.cpp @@ -0,0 +1,57 @@ +// +// Copyright 2012 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 "LegacyForge.h" + +MinecraftForge::MinecraftForge ( const QString& file ) : Mod ( file ) +{ + +} +bool MinecraftForge::FixVersionIfNeeded ( QString newVersion ) +{/* + wxString reportedVersion = GetModVersion(); + if(reportedVersion == "..." || reportedVersion.empty()) + { + std::auto_ptr in(new wxFFileInputStream("forge.zip")); + wxTempFileOutputStream out("forge.zip"); + wxTextOutputStream textout(out); + wxZipInputStream inzip(*in); + wxZipOutputStream outzip(out); + std::auto_ptr entry; + // preserve metadata + outzip.CopyArchiveMetaData(inzip); + // copy all entries + while (entry.reset(inzip.GetNextEntry()), entry.get() != NULL) + if (!outzip.CopyEntry(entry.release(), inzip)) + return false; + // release last entry + in.reset(); + outzip.PutNextEntry("forgeversion.properties"); + + wxStringTokenizer tokenizer(newVersion,"."); + wxString verFile; + verFile << wxString("forge.major.number=") << tokenizer.GetNextToken() << "\n"; + verFile << wxString("forge.minor.number=") << tokenizer.GetNextToken() << "\n"; + verFile << wxString("forge.revision.number=") << tokenizer.GetNextToken() << "\n"; + verFile << wxString("forge.build.number=") << tokenizer.GetNextToken() << "\n"; + auto buf = verFile.ToUTF8(); + outzip.Write(buf.data(), buf.length()); + // check if we succeeded + return inzip.Eof() && outzip.Close() && out.Commit(); + } + */ + return true; +} diff --git a/backend/LegacyForge.h b/backend/LegacyForge.h new file mode 100644 index 00000000..00a054b8 --- /dev/null +++ b/backend/LegacyForge.h @@ -0,0 +1,25 @@ +// +// Copyright 2012 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. +// + +#pragma once +#include "Mod.h" + +class MinecraftForge : public Mod +{ +public: + MinecraftForge ( const QString& file ); + bool FixVersionIfNeeded(QString newVersion); +}; diff --git a/backend/LegacyUpdate.cpp b/backend/LegacyUpdate.cpp index 8580a2f2..a748bad3 100644 --- a/backend/LegacyUpdate.cpp +++ b/backend/LegacyUpdate.cpp @@ -212,6 +212,13 @@ void LegacyUpdate::jarStart() 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); @@ -260,3 +267,115 @@ void LegacyUpdate::jarFailed() // bad, bad emitFailed("Failed to download the minecraft jar. Try again later."); } + +void LegacyUpdate::ModTheJar() +{ + /* + LegacyInstance * inst = (LegacyInstance *) m_inst; + // Get the mod list + auto modList = inst->getJarModList(); + + QFileInfo mcBin(inst->binDir()); + QFileInfo mcJar(inst->mcJar()); + QFileInfo mcBackup(inst->mcBackup()); + + // 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()) + { + inst->setShouldRebuild(false); + emitSucceeded(); + return; + } + + setStatus("Installing mods - backing up minecraft.jar..."); + if (!mcBackup.exists() && !QFile::copy(mcJar.absoluteFilePath(), mcBackup.absoluteFilePath()) ) + { + emitFailed("Failed to back up minecraft.jar"); + return; + } + + if (mcJar.isFile() && !QFile::remove(mcJar.absoluteFilePath())) + { + emitFailed("Failed to delete old minecraft.jar"); + return; + } + + setStatus("Installing mods - Opening minecraft.jar"); + + wxFFileOutputStream jarStream(mcJar.absoluteFilePath()); + wxZipOutputStream zipOut(jarStream); + + // 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++) + { + wxFileName modFileName = iter->GetFileName(); + setStatus("Installing mods - Adding " + modFileName.GetFullName()); + if (iter->GetModType() == Mod::ModType::MOD_ZIPFILE) + { + wxFFileInputStream modStream(modFileName.GetFullPath()); + wxZipInputStream zipStream(modStream); + std::unique_ptr entry; + while (entry.reset(zipStream.GetNextEntry()), entry.get() != NULL) + { + if (entry->IsDir()) + continue; + + wxString name = entry->GetName(); + if (addedFiles.count(name) == 0) + { + if (!zipOut.CopyEntry(entry.release(), zipStream)) + break; + addedFiles.insert(name); + } + } + } + else + { + wxFileName destFileName = modFileName; + destFileName.MakeRelativeTo(m_inst->GetInstModsDir().GetFullPath()); + wxString destFile = destFileName.GetFullPath(); + + if (addedFiles.count(destFile) == 0) + { + wxFFileInputStream input(modFileName.GetFullPath()); + zipOut.PutNextEntry(destFile); + zipOut.Write(input); + + addedFiles.insert(destFile); + } + } + } + + { + wxFFileInputStream inStream(mcBackup.GetFullPath()); + wxZipInputStream zipIn(inStream); + + std::auto_ptr entry; + while (entry.reset(zipIn.GetNextEntry()), entry.get() != NULL) + { + wxString name = entry->GetName(); + + if (!name.Matches("META-INF*") && + addedFiles.count(name) == 0) + { + if (!zipOut.CopyEntry(entry.release(), zipIn)) + break; + addedFiles.insert(name); + } + } + } + + // Recompress the jar + TaskStep(); // STEP 3 + SetStatus(_("Installing mods - Recompressing jar...")); + + inst->SetNeedsRebuild(false); + inst->UpdateVersion(true); + return (ExitCode)1; + */ +} \ No newline at end of file diff --git a/backend/LegacyUpdate.h b/backend/LegacyUpdate.h index ad58fc85..a48189a6 100644 --- a/backend/LegacyUpdate.h +++ b/backend/LegacyUpdate.h @@ -44,6 +44,8 @@ private slots: void jarFailed(); void extractLwjgl(); + + void ModTheJar(); private: QSharedPointer m_reply; diff --git a/backend/Mod.cpp b/backend/Mod.cpp new file mode 100644 index 00000000..652bbda7 --- /dev/null +++ b/backend/Mod.cpp @@ -0,0 +1,264 @@ +// +// Copyright 2012 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 "Mod.h" +#include +#include + +Mod::Mod( const QFileInfo& file ) +{ + repath(file); +} + +void Mod::repath ( const QFileInfo& file ) +{ + m_file = file; + m_name = file.baseName(); + m_id = file.fileName(); + + m_type = Mod::MOD_UNKNOWN; + if (m_file.isDir()) + m_type = MOD_FOLDER; + else if (m_file.isFile()) + { + QString ext = m_file.suffix().toLower(); + if (ext == "zip" || ext == "jar") + m_type = MOD_ZIPFILE; + else + m_type = MOD_SINGLEFILE; + } + + /* + switch (modType) + { + case MOD_ZIPFILE: + { + wxFFileInputStream fileIn(modFile.GetFullPath()); + wxZipInputStream zipIn(fileIn); + + std::auto_ptr entry; + + bool is_forge = false; + while(true) + { + entry.reset(zipIn.GetNextEntry()); + if (entry.get() == nullptr) + break; + if(entry->GetInternalName().EndsWith("mcmod.info")) + break; + if(entry->GetInternalName().EndsWith("forgeversion.properties")) + { + is_forge = true; + break; + } + } + + if (entry.get() != nullptr) + { + // Read the info file into text + wxString infoFileData; + wxStringOutputStream stringOut(&infoFileData); + zipIn.Read(stringOut); + if(!is_forge) + ReadModInfoData(infoFileData); + else + ReadForgeInfoData(infoFileData); + } + } + break; + + case MOD_FOLDER: + { + wxString infoFile = Path::Combine(modFile, "mcmod.info"); + if (!wxFileExists(infoFile)) + { + infoFile = wxEmptyString; + + wxDir modDir(modFile.GetFullPath()); + + if (!modDir.IsOpened()) + { + wxLogError(_("Can't fine mod info file. Failed to open mod folder.")); + break; + } + + wxString currentFile; + if (modDir.GetFirst(¤tFile)) + { + do + { + if (currentFile.EndsWith("mcmod.info")) + { + infoFile = Path::Combine(modFile.GetFullPath(), currentFile); + break; + } + } while (modDir.GetNext(¤tFile)); + } + } + + if (infoFile != wxEmptyString && wxFileExists(infoFile)) + { + wxString infoStr; + wxFFileInputStream fileIn(infoFile); + wxStringOutputStream strOut(&infoStr); + fileIn.Read(strOut); + ReadModInfoData(infoStr); + } + } + break; + } +*/ +} + + +/* +void ReadModInfoData(QString info) +{ + using namespace boost::property_tree; + + // Read the data + ptree ptRoot; + + std::stringstream stringIn(cStr(info)); + try + { + read_json(stringIn, ptRoot); + + ptree pt = ptRoot.get_child(ptRoot.count("modlist") == 1 ? "modlist" : "").begin()->second; + + modID = wxStr(pt.get("modid")); + modName = wxStr(pt.get("name")); + modVersion = wxStr(pt.get("version")); + } + catch (json_parser_error e) + { + // Silently fail... + } + catch (ptree_error e) + { + // Silently fail... + } +} +*/ + +// FIXME: abstraction violated. +/* +void Mod::ReadForgeInfoData(QString infoFileData) +{ + using namespace boost::property_tree; + + // Read the data + ptree ptRoot; + modName = "Minecraft Forge"; + modID = "Forge"; + std::stringstream stringIn(cStr(infoFileData)); + try + { + read_ini(stringIn, ptRoot); + wxString major, minor, revision, build; + // BUG: boost property tree is bad. won't let us get a key with dots in it + // Likely cause = treating the dots as path separators. + for (auto iter = ptRoot.begin(); iter != ptRoot.end(); iter++) + { + auto &item = *iter; + std::string key = item.first; + std::string value = item.second.get_value(); + if(key == "forge.major.number") + major = value; + if(key == "forge.minor.number") + minor = value; + if(key == "forge.revision.number") + revision = value; + if(key == "forge.build.number") + build = value; + } + modVersion.Empty(); + modVersion << major << "." << minor << "." << revision << "." << build; + } + catch (json_parser_error e) + { + std::cerr << e.what(); + } + catch (ptree_error e) + { + std::cerr << e.what(); + } +} +*/ + +bool Mod::replace ( Mod& with ) +{ + if(!destroy()) + return false; + bool success = false; + auto t = with.type(); + if(t == MOD_ZIPFILE || t == MOD_SINGLEFILE) + { + success = QFile::copy(with.m_file.filePath(), m_file.path()); + } + if(t == MOD_FOLDER) + { + success = copyPath(with.m_file.filePath(), m_file.path()); + } + if(success) + { + m_id = with.m_id; + m_mcversion = with.m_mcversion; + m_type = with.m_type; + m_name = with.m_name; + m_version = with.m_version; + } + return success; +} + +bool Mod::destroy() +{ + if(m_type == MOD_FOLDER) + { + QDir d(m_file.filePath()); + if(d.removeRecursively()) + { + m_type = MOD_UNKNOWN; + return true; + } + return false; + } + else if (m_type == MOD_SINGLEFILE || m_type == MOD_ZIPFILE) + { + QFile f(m_file.filePath()); + if(f.remove()) + { + m_type = MOD_UNKNOWN; + return true; + } + return false; + } + return true; +} + + +QString Mod::version() const +{ + switch(type()) + { + case MOD_ZIPFILE: + return m_version; + case MOD_FOLDER: + return "Folder"; + case MOD_SINGLEFILE: + return "File"; + } +} diff --git a/backend/Mod.h b/backend/Mod.h new file mode 100644 index 00000000..d9d90426 --- /dev/null +++ b/backend/Mod.h @@ -0,0 +1,69 @@ +// +// Copyright 2012 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. +// + +#pragma once +#include + +class Mod +{ +public: + enum ModType + { + MOD_UNKNOWN, //!< Indicates an unspecified mod type. + MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files. + MOD_SINGLEFILE, //!< The mod is a single file (not a zip file). + MOD_FOLDER, //!< The mod is in a folder on the filesystem. + }; + + Mod(const QFileInfo &file); + + QFileInfo filename() const { return m_file; } + QString id() const { return m_id; } + ModType type() const { return m_type; } + QString mcversion() const; + bool valid() {return m_type != MOD_UNKNOWN;} + + QString version() const; + + // delete all the files of this mod + bool destroy(); + // replace this mod with a copy of the other + bool replace(Mod & with); + // change the mod's filesystem path (used by mod lists for *MAGIC* purposes) + void repath(const QFileInfo &file); + + + bool operator ==(const Mod &other) const + { + return filename() == other.filename(); + } + +protected: + + //FIXME: what do do with those? HMM... + /* + void ReadModInfoData(QString info); + void ReadForgeInfoData(QString infoFileData); + */ + + QFileInfo m_file; + QString m_id; + QString m_name; + QString m_version; + QString m_mcversion; + + ModType m_type; +}; diff --git a/backend/ModList.cpp b/backend/ModList.cpp new file mode 100644 index 00000000..851eb940 --- /dev/null +++ b/backend/ModList.cpp @@ -0,0 +1,418 @@ +// +// Copyright 2012 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 "ModList.h" +#include "LegacyInstance.h" +#include + +ModList::ModList ( const QString& dir ) : QObject(), m_dir(dir) +{ + m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | QDir::NoSymLinks); + m_dir.setSorting(QDir::Name); + update(); +} + +bool ModList::update() +{ + if (!isValid()) + return false; + + bool initial = mods.empty(); + + bool listChanged = false; + + auto list = m_dir.entryInfoList(); + for(auto entry: list) + { + Mod mod(entry); + if (initial || !mods.contains(mod)) + { + mods.push_back(mod); + listChanged = true; + } + } + return listChanged; +} + +bool ModList::isValid() +{ + return m_dir.exists() && m_dir.isReadable(); +} + +bool ModList::installMod ( const QFileInfo& filename, size_t index ) +{ + if(!filename.exists() || !filename.isReadable()) + { + return false; + } + Mod m(filename); + if(!m.valid()) + return false; + + // if it's already there, replace the original mod (in place) + int idx = mods.indexOf(m); + if(idx != -1) + { + if(mods[idx].replace(m)) + { + emit changed(); + return true; + } + return false; + } + + auto type = m.type(); + if(type == Mod::MOD_UNKNOWN) + return false; + if(type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE) + { + QString newpath = PathCombine(m_dir.path(), filename.fileName()); + if(!QFile::copy(filename.filePath(), newpath)) + return false; + m.repath(newpath); + mods.append(m); + emit changed(); + return true; + } + else if(type == Mod::MOD_FOLDER) + { + QString newpath = PathCombine(m_dir.path(), filename.fileName()); + if(!copyPath(filename.filePath(), newpath)) + return false; + m.repath(newpath); + mods.append(m); + emit changed(); + return true; + } + return false; +} + +bool ModList::deleteMod ( size_t index ) +{ + if(index >= mods.size()) + return false; + Mod & m = mods[index]; + if(m.destroy()) + { + mods.erase(mods.begin() + index); + emit changed(); + return true; + } + return false; +} + + +/* +ModList::ModList(const QString &dir) + : modsFolder(dir) +{ + +} + +bool ModList::update(bool quickLoad) +{ + bool listChanged = false; + + // Check for mods in the list whose files do not exist and remove them from the list. + // If doing a quickLoad, erase the whole list. + for (size_t i = 0; i < size(); i++) + { + if (quickLoad || !at(i).GetFileName().FileExists()) + { + erase(begin() + i); + i--; + listChanged = true; + } + } + + // Add any mods in the mods folder that aren't already in the list. + if (LoadModListFromDir(QString(), quickLoad)) + listChanged = true; + + return listChanged; +} + +bool ModList::LoadModListFromDir(const QString& loadFrom, bool quickLoad) +{ + QString dir(loadFrom.isEmpty() ? modsFolder : loadFrom); + + QDir modDir(dir); + if (!modDir.exists()) + return false; + + bool listChanged = false; + + auto list = modDir.entryInfoList(QDir::Readable|QDir::NoDotAndDotDot, QDir::Name); + for(auto currentFile: list) + { + if (currentFile.isFile()) + { + if (quickLoad || FindByFilename(currentFile.absoluteFilePath()) == nullptr) + { + Mod mod(currentFile.absoluteFilePath()); + push_back(mod); + listChanged = true; + } + } + else if (currentFile.isDir()) + { + if (LoadModListFromDir(currentFile.absoluteFilePath())) + listChanged = true; + } + } + + return listChanged; +} + +Mod *ModList::FindByFilename(const QString& filename) +{ + // Search the list for a mod with the given filename. + for (auto iter = begin(); iter != end(); ++iter) + { + if (iter->GetFileName() == QFileInfo(filename)) + return &(*iter); + } + + // If nothing is found, return nullptr. + return nullptr; +} + +int ModList::FindIndexByFilename(const QString& filename) +{ + // Search the list for a mod with the given filename. + int i = 0; + for (auto iter = begin(); iter != end(); ++iter, i++) + { + if (iter->GetFileName() == QFileInfo(filename)) + return i; + } + + // If nothing is found, return nullptr. + return -1; +} + +Mod* ModList::FindByID(const QString& modID, const QString& modVersion) +{ + // Search the list for a mod that matches + for (auto iter = begin(); iter != end(); ++iter) + { + QString ID = iter->GetModID(); + QString version = iter->GetModVersion(); + if ( ID == modID && version == modVersion) + return &(*iter); + } + + // If nothing is found, return nullptr. + return nullptr; +} + +void ModList::LoadFromFile(const QString& file) +{ + if (!wxFileExists(file)) + return; + + wxFFileInputStream inputStream(file); + wxArrayString modListFile = ReadAllLines(inputStream); + + for (wxArrayString::iterator iter = modListFile.begin(); iter != modListFile.end(); iter++) + { + // Normalize the path to the instMods dir. + wxFileName modFile(*iter); + modFile.Normalize(wxPATH_NORM_ALL, modsFolder); + modFile.MakeRelativeTo(); + // if the file is gone, do not load it + if(!modFile.Exists()) + { + continue; + } + + if (FindByFilename(modFile.GetFullPath()) == nullptr) + { + push_back(Mod(modFile)); + } + } +} + +void ModList::SaveToFile(const QString& file) +{ + QString text; + for (iterator iter = begin(); iter != end(); ++iter) + { + wxFileName modFile = iter->GetFileName(); + modFile.MakeRelativeTo(modsFolder); + text.append(modFile.GetFullPath()); + text.append("\n"); + } + + wxTempFileOutputStream out(file); + WriteAllText(out, text); + out.Commit(); +} + +bool ModList::InsertMod(size_t index, const QString &filename, const QString& saveToFile) +{ + QFileInfo source(filename); + QFileInfo dest(PathCombine(modsFolder, source.fileName())); + + if (source != dest) + { + QFile::copy(source.absoluteFilePath(), dest.absoluteFilePath()); + } + + int oldIndex = FindIndexByFilename(dest.absoluteFilePath()); + + if (oldIndex != -1) + { + erase(begin() + oldIndex); + } + + if (index >= size()) + push_back(Mod(dest)); + else + insert(begin() + index, Mod(dest)); + + if (!saveToFile.isEmpty()) + SaveToFile(saveToFile); + + return true; +} + +bool ModList::DeleteMod(size_t index, const QString& saveToFile) +{ + Mod *mod = &at(index); + if(mod->GetModType() == Mod::MOD_FOLDER) + { + QDir dir(mod->GetFileName().absoluteFilePath()); + if(dir.removeRecursively()) + { + erase(begin() + index); + + if (!saveToFile.isEmpty()) + SaveToFile(saveToFile); + + return true; + } + else + { + // wxLogError(_("Failed to delete mod.")); + } + } + else if (QFile(mod->GetFileName().absoluteFilePath()).remove()) + { + erase(begin() + index); + + if (!saveToFile.isEmpty()) + SaveToFile(saveToFile); + + return true; + } + else + { + // wxLogError(_("Failed to delete mod.")); + } + return false; +} + +bool JarModList::InsertMod(size_t index, const QString &filename, const QString& saveToFile) +{ + QString saveFile = saveToFile; + if (saveToFile.isEmpty()) + saveFile = m_inst->GetModListFile().GetFullPath(); + + if (ModList::InsertMod(index, filename, saveFile)) + { + m_inst->setLWJGLVersion(true); + return true; + } + return false; +} + +bool JarModList::DeleteMod(size_t index, const QString& saveToFile) +{ + QString saveFile = saveToFile; + if (saveToFile.IsEmpty()) + saveFile = m_inst->GetModListFile().GetFullPath(); + + if (ModList::DeleteMod(index, saveFile)) + { + m_inst->SetNeedsRebuild(); + return true; + } + return false; +} + +bool JarModList::UpdateModList(bool quickLoad) +{ + if (ModList::UpdateModList(quickLoad)) + { + m_inst->SetNeedsRebuild(); + return true; + } + return false; +} + +bool FolderModList::LoadModListFromDir(const QString& loadFrom, bool quickLoad) +{ + QString dir(loadFrom.IsEmpty() ? modsFolder : loadFrom); + + if (!wxDirExists(dir)) + return false; + + bool listChanged = false; + wxDir modDir(dir); + + if (!modDir.IsOpened()) + { + wxLogError(_("Failed to open directory: ") + dir); + return false; + } + + QString currentFile; + if (modDir.GetFirst(¤tFile)) + { + do + { + wxFileName modFile(Path::Combine(dir, currentFile)); + + if (wxFileExists(modFile.GetFullPath()) || wxDirExists(modFile.GetFullPath())) + { + if (quickLoad || FindByFilename(modFile.GetFullPath()) == nullptr) + { + Mod mod(modFile.GetFullPath()); + push_back(mod); + listChanged = true; + } + } + } while (modDir.GetNext(¤tFile)); + } + + return listChanged; +} + +bool ModNameSort (const Mod & i,const Mod & j) +{ + if(i.GetModType() == j.GetModType()) + return (i.GetName().toLower() < j.GetName().toLower()); + return (i.GetModType() < j.GetModType()); +} + +bool FolderModList::UpdateModList ( bool quickLoad ) +{ + bool changed = ModList::UpdateModList(quickLoad); + std::sort(begin(),end(),ModNameSort); + return changed; +} +*/ diff --git a/backend/ModList.h b/backend/ModList.h new file mode 100644 index 00000000..bf65a080 --- /dev/null +++ b/backend/ModList.h @@ -0,0 +1,75 @@ +// +// Copyright 2012 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 +// +#pragma once + +class LegacyInstance; +class BaseInstance; +#include +#include +#include + +#include "Mod.h" + +/** + * A basic mod list. + * Backed by a folder. + */ +class ModList : public QObject +{ + Q_OBJECT +public: + ModList(const QString& dir = QString()); + + size_t size() { return mods.size(); }; + Mod& operator[](size_t index) { return mods[index]; }; + + /// Reloads the mod list and returns true if the list changed. + virtual bool update(); + + /// Adds the given mod to the list at the given index. + virtual bool installMod(const QFileInfo& filename, size_t index = 0); + + /// Deletes the mod at the given index. + virtual bool deleteMod(size_t index); + + /** + * move the mod at index to the position N + * 0 is the beginning of the list, length() is the end of the list. + */ + virtual bool moveMod(size_t from, size_t to) { return false; }; + + virtual bool isValid(); + +signals: + virtual void changed(); +protected: + QDir m_dir; + QList mods; +}; + +/** + * A jar mod list. + * Backed by a folder and a file which specifies the load order. + */ +class JarModList : public ModList +{ + Q_OBJECT +public: + JarModList(const QString& dir, const QString& list_file, LegacyInstance * inst) + : ModList(dir), m_listfile(list_file), m_inst(inst) {} + + virtual bool update(); + virtual bool installMod(const QString &filename, size_t index); + virtual bool deleteMod(size_t index); + virtual bool moveMod(size_t from, size_t to); +protected: + QString m_listfile; + LegacyInstance * m_inst; +}; diff --git a/backend/OneSixUpdate.cpp b/backend/OneSixUpdate.cpp index 5cd2c78c..2bb2f496 100644 --- a/backend/OneSixUpdate.cpp +++ b/backend/OneSixUpdate.cpp @@ -92,12 +92,15 @@ void OneSixUpdate::versionFileFinished() { QString version1 = PathCombine(inst_dir, "/version.json"); ensurePathExists(version1); + // FIXME: detect errors here, download to a temp file, swap QFile vfile1 (version1); vfile1.open(QIODevice::Truncate | QIODevice::WriteOnly ); vfile1.write(DlJob->m_data); vfile1.close(); } + // the version is downloaded safely. update is 'done' at this point + m_inst->setShouldUpdate(false); // save the version file in versions/$version/$version.json /* //QString version2 = QString("versions/") + version_id + "/" + version_id + ".json"; diff --git a/libutil/include/pathutils.h b/libutil/include/pathutils.h index c9a52ced..d4f41da3 100644 --- a/libutil/include/pathutils.h +++ b/libutil/include/pathutils.h @@ -31,4 +31,7 @@ LIBUTIL_EXPORT QString DirNameFromString(QString string, QString inDir = "."); LIBUTIL_EXPORT bool ensurePathExists(QString filenamepath); +LIBUTIL_EXPORT bool copyPath(QString src, QString dst); + + #endif // PATHUTILS_H diff --git a/libutil/src/pathutils.cpp b/libutil/src/pathutils.cpp index 083fe98d..97287840 100644 --- a/libutil/src/pathutils.cpp +++ b/libutil/src/pathutils.cpp @@ -73,3 +73,22 @@ bool ensurePathExists(QString filenamepath) return (dir.mkpath ( a.path() )); } +bool copyPath(QString src, QString dst) +{ + QDir dir(src); + if (!dir.exists()) + return false; + + foreach (QString d, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) + { + QString dst_path = dst + QDir::separator() + d; + dir.mkpath(dst_path); + copyPath(src+ QDir::separator() + d, dst_path); + } + + foreach (QString f, dir.entryList(QDir::Files)) + { + QFile::copy(src + QDir::separator() + f, dst + QDir::separator() + f); + } + return true; +}