/* 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; }