/* SPDX-FileCopyrightText: 2026 Project Tick
* SPDX-FileContributor: Project Tick
* SPDX-License-Identifier: GPL-3.0-or-later
*
* MeshMC - A Custom Launcher for Minecraft
* Copyright (C) 2026 Project Tick
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 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 "LegacyModList.h"
#include
#include
#include
LegacyModList::LegacyModList(const QString& dir, const QString& list_file)
: m_dir(dir), m_list_file(list_file)
{
FS::ensureFolderPathExists(m_dir.absolutePath());
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files |
QDir::Dirs);
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
}
struct OrderItem {
QString id;
bool enabled = false;
};
typedef QList OrderList;
static void internalSort(QList& what)
{
auto predicate = [](const LegacyModList::Mod& left,
const LegacyModList::Mod& right) {
return left.fileName().localeAwareCompare(right.fileName()) < 0;
};
std::sort(what.begin(), what.end(), predicate);
}
static OrderList readListFile(const QString& m_list_file)
{
OrderList itemList;
if (m_list_file.isNull() || m_list_file.isEmpty())
return itemList;
QFile textFile(m_list_file);
if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text))
return OrderList();
QTextStream textStream;
textStream.setAutoDetectUnicode(true);
textStream.setDevice(&textFile);
while (true) {
QString line = textStream.readLine();
if (line.isNull() || line.isEmpty())
break;
else {
OrderItem it;
it.enabled = !line.endsWith(".disabled");
if (!it.enabled) {
line.chop(9);
}
it.id = line;
itemList.append(it);
}
}
textFile.close();
return itemList;
}
bool LegacyModList::update()
{
if (!m_dir.exists() || !m_dir.isReadable())
return false;
QList orderedMods;
QList newMods;
m_dir.refresh();
auto folderContents = m_dir.entryInfoList();
// first, process the ordered items (if any)
OrderList listOrder = readListFile(m_list_file);
for (auto item : listOrder) {
QFileInfo infoEnabled(m_dir.filePath(item.id));
QFileInfo infoDisabled(m_dir.filePath(item.id + ".disabled"));
int idxEnabled = folderContents.indexOf(infoEnabled);
int idxDisabled = folderContents.indexOf(infoDisabled);
bool isEnabled;
// if both enabled and disabled versions are present, it's a special
// case...
if (idxEnabled >= 0 && idxDisabled >= 0) {
// we only process the one we actually have in the order file.
// and exactly as we have it.
// THIS IS A CORNER CASE
isEnabled = item.enabled;
} else {
// only one is present.
// we pick the one that we found.
// we assume the mod was enabled/disabled by external means
isEnabled = idxEnabled >= 0;
}
int idx = isEnabled ? idxEnabled : idxDisabled;
QFileInfo& info = isEnabled ? infoEnabled : infoDisabled;
// if the file from the index file exists
if (idx != -1) {
// remove from the actual folder contents list
folderContents.takeAt(idx);
// append the new mod
orderedMods.append(info);
}
}
// if there are any untracked files... append them sorted at the end
if (folderContents.size()) {
for (auto entry : folderContents) {
newMods.append(entry);
}
internalSort(newMods);
orderedMods.append(newMods);
}
mods.swap(orderedMods);
return true;
}