summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--backend/CMakeLists.txt6
-rw-r--r--backend/LegacyForge.cpp57
-rw-r--r--backend/LegacyForge.h25
-rw-r--r--backend/LegacyUpdate.cpp119
-rw-r--r--backend/LegacyUpdate.h2
-rw-r--r--backend/Mod.cpp264
-rw-r--r--backend/Mod.h69
-rw-r--r--backend/ModList.cpp418
-rw-r--r--backend/ModList.h75
-rw-r--r--backend/OneSixUpdate.cpp3
-rw-r--r--libutil/include/pathutils.h3
-rw-r--r--libutil/src/pathutils.cpp19
13 files changed, 1061 insertions, 0 deletions
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<wxFFileInputStream> in(new wxFFileInputStream("forge.zip"));
+ wxTempFileOutputStream out("forge.zip");
+ wxTextOutputStream textout(out);
+ wxZipInputStream inzip(*in);
+ wxZipOutputStream outzip(out);
+ std::auto_ptr<wxZipEntry> 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<QString> 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<wxZipEntry> 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<wxZipEntry> 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<QNetworkReply> 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 <pathutils.h>
+#include <QDir>
+
+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<wxZipEntry> 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(&currentFile))
+ {
+ do
+ {
+ if (currentFile.EndsWith("mcmod.info"))
+ {
+ infoFile = Path::Combine(modFile.GetFullPath(), currentFile);
+ break;
+ }
+ } while (modDir.GetNext(&currentFile));
+ }
+ }
+
+ 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<std::string>("modid"));
+ modName = wxStr(pt.get<std::string>("name"));
+ modVersion = wxStr(pt.get<std::string>("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<std::string>();
+ 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 <QFileInfo>
+
+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 <pathutils.h>
+
+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(&currentFile))
+ {
+ 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(&currentFile));
+ }
+
+ 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 <QList>
+#include <QString>
+#include <QDir>
+
+#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<Mod> 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;
+}