Files
PrismLauncher/launcher/BaseInstance.cpp
T
arch1t3cht c0c862d94e Add option whether to count instance in total playtime
The use case here is making a copy of your instance as a backup (say,
when migrating to a new modpack version) and wanting to keep the old
copy displayed in Prism. In that case, the old instance can now be
configured to not count towards total playtime. This way, the instance,
including its playtime, can be fully preserved and displayed in Prism,
but the total playtime statistic stays accurate.

Closes #4937.

Signed-off-by: arch1t3cht <arch1t3cht@gmail.com>
2026-07-08 21:16:48 +02:00

494 lines
15 KiB
C++

// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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, version 3.
*
* 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 <https://www.gnu.org/licenses/>.
*
* 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 "BaseInstance.h"
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QJsonDocument>
#include <QJsonObject>
#include "Application.h"
#include "Json.h"
#include "launch/LaunchTask.h"
#include "settings/INISettingsObject.h"
#include "settings/OverrideSetting.h"
#include "settings/Setting.h"
#include "BuildConfig.h"
#include "Commandline.h"
#include "FileSystem.h"
int getConsoleMaxLines(SettingsObject* settings)
{
auto lineSetting = settings->getSetting("ConsoleMaxLines");
bool conversionOk = false;
int maxLines = lineSetting->get().toInt(&conversionOk);
if (!conversionOk) {
maxLines = lineSetting->defValue().toInt();
qWarning() << "ConsoleMaxLines has nonsensical value, defaulting to" << maxLines;
}
return maxLines;
}
bool shouldStopOnConsoleOverflow(SettingsObject* settings)
{
return settings->get("ConsoleOverflowStop").toBool();
}
BaseInstance::BaseInstance(SettingsObject* globalSettings, std::unique_ptr<SettingsObject> settings, const QString& rootDir) : QObject()
{
m_settings = std::move(settings);
m_global_settings = globalSettings;
m_rootDir = rootDir;
m_settings->registerSetting("name", "Unnamed Instance");
m_settings->registerSetting("iconKey", "default");
m_settings->registerSetting("notes", "");
m_settings->registerSetting("lastLaunchTime", 0);
m_settings->registerSetting("totalTimePlayed", 0);
if (m_settings->get("totalTimePlayed").toLongLong() < 0)
m_settings->reset("totalTimePlayed");
m_settings->registerSetting("lastTimePlayed", 0);
m_settings->registerSetting("linkedInstances", "[]");
m_settings->registerSetting("shortcuts", QString());
// Game time override
auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false);
m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride);
m_settings->registerOverride(globalSettings->getSetting("RecordGameTime"), gameTimeOverride);
m_settings->registerSetting("CountGameTime", true);
// NOTE: Sometimees InstanceType is already registered, as it was used to identify the type of
// a locally stored instance
if (!m_settings->getSetting("InstanceType"))
m_settings->registerSetting("InstanceType", "");
// Custom Commands
auto commandSetting = m_settings->registerSetting({ "OverrideCommands", "OverrideLaunchCmd" }, false);
m_settings->registerOverride(globalSettings->getSetting("PreLaunchCommand"), commandSetting);
m_settings->registerOverride(globalSettings->getSetting("WrapperCommand"), commandSetting);
m_settings->registerOverride(globalSettings->getSetting("PostExitCommand"), commandSetting);
// Console
auto consoleSetting = m_settings->registerSetting("OverrideConsole", false);
m_settings->registerOverride(globalSettings->getSetting("ShowConsole"), consoleSetting);
m_settings->registerOverride(globalSettings->getSetting("AutoCloseConsole"), consoleSetting);
m_settings->registerOverride(globalSettings->getSetting("ShowConsoleOnError"), consoleSetting);
m_settings->registerOverride(globalSettings->getSetting("LogPrePostOutput"), consoleSetting);
m_settings->registerPassthrough(globalSettings->getSetting("ConsoleMaxLines"), nullptr);
m_settings->registerPassthrough(globalSettings->getSetting("ConsoleOverflowStop"), nullptr);
// Managed Packs
m_settings->registerSetting("ManagedPack", false);
m_settings->registerSetting("ManagedPackType", "");
m_settings->registerSetting("ManagedPackID", "");
m_settings->registerSetting("ManagedPackName", "");
m_settings->registerSetting("ManagedPackVersionID", "");
m_settings->registerSetting("ManagedPackVersionName", "");
m_settings->registerSetting("ManagedPackURL", "");
m_settings->registerSetting("Profiler", "");
}
BaseInstance::~BaseInstance() {}
QString BaseInstance::getPreLaunchCommand()
{
return settings()->get("PreLaunchCommand").toString();
}
QString BaseInstance::getWrapperCommand()
{
return settings()->get("WrapperCommand").toString();
}
QString BaseInstance::getPostExitCommand()
{
return settings()->get("PostExitCommand").toString();
}
bool BaseInstance::isManagedPack() const
{
return m_settings->get("ManagedPack").toBool();
}
QString BaseInstance::getManagedPackType() const
{
return m_settings->get("ManagedPackType").toString();
}
QString BaseInstance::getManagedPackID() const
{
return m_settings->get("ManagedPackID").toString();
}
QString BaseInstance::getManagedPackName() const
{
return m_settings->get("ManagedPackName").toString();
}
QString BaseInstance::getManagedPackVersionID() const
{
return m_settings->get("ManagedPackVersionID").toString();
}
QString BaseInstance::getManagedPackVersionName() const
{
return m_settings->get("ManagedPackVersionName").toString();
}
void BaseInstance::setManagedPack(const QString& type,
const QString& id,
const QString& name,
const QString& versionId,
const QString& version)
{
m_settings->set("ManagedPack", true);
m_settings->set("ManagedPackType", type);
m_settings->set("ManagedPackID", id);
m_settings->set("ManagedPackName", name);
m_settings->set("ManagedPackVersionID", versionId);
m_settings->set("ManagedPackVersionName", version);
}
void BaseInstance::copyManagedPack(BaseInstance& other)
{
m_settings->set("ManagedPack", other.isManagedPack());
m_settings->set("ManagedPackType", other.getManagedPackType());
m_settings->set("ManagedPackID", other.getManagedPackID());
m_settings->set("ManagedPackName", other.getManagedPackName());
m_settings->set("ManagedPackVersionID", other.getManagedPackVersionID());
m_settings->set("ManagedPackVersionName", other.getManagedPackVersionName());
if (APPLICATION->settings()->get("AutomaticJavaSwitch").toBool() && m_settings->get("AutomaticJava").toBool() &&
m_settings->get("OverrideJavaLocation").toBool()) {
m_settings->set("OverrideJavaLocation", false);
m_settings->set("JavaPath", "");
}
}
QStringList BaseInstance::getLinkedInstances() const
{
auto setting = m_settings->get("linkedInstances").toString();
return Json::toStringList(setting);
}
void BaseInstance::setLinkedInstances(const QStringList& list)
{
m_settings->set("linkedInstances", Json::fromStringList(list));
}
void BaseInstance::addLinkedInstanceId(const QString& id)
{
auto linkedInstances = getLinkedInstances();
linkedInstances.append(id);
setLinkedInstances(linkedInstances);
}
bool BaseInstance::removeLinkedInstanceId(const QString& id)
{
auto linkedInstances = getLinkedInstances();
int numRemoved = linkedInstances.removeAll(id);
setLinkedInstances(linkedInstances);
return numRemoved > 0;
}
bool BaseInstance::isLinkedToInstanceId(const QString& id) const
{
auto linkedInstances = getLinkedInstances();
return linkedInstances.contains(id);
}
void BaseInstance::iconUpdated(QString key)
{
if (iconKey() == key) {
emit propertiesChanged(this);
}
}
void BaseInstance::invalidate()
{
changeStatus(Status::Gone);
qDebug() << "Instance" << id() << "has been invalidated.";
}
void BaseInstance::changeStatus(BaseInstance::Status newStatus)
{
Status status = currentStatus();
if (status != newStatus) {
m_status = newStatus;
emit statusChanged(status, newStatus);
}
}
BaseInstance::Status BaseInstance::currentStatus() const
{
return m_status;
}
QString BaseInstance::id() const
{
return QFileInfo(instanceRoot()).fileName();
}
bool BaseInstance::isRunning() const
{
return m_isRunning;
}
void BaseInstance::setRunning(bool running)
{
if (running == m_isRunning)
return;
m_isRunning = running;
emit runningStatusChanged(running);
}
void BaseInstance::setMinecraftRunning(bool running)
{
if (!settings()->get("RecordGameTime").toBool()) {
return;
}
if (running) {
m_timeStarted = QDateTime::currentDateTime();
setLastLaunch(m_timeStarted.toMSecsSinceEpoch());
} else {
QDateTime timeEnded = QDateTime::currentDateTime();
qint64 current = settings()->get("totalTimePlayed").toLongLong();
settings()->set("totalTimePlayed", current + m_timeStarted.secsTo(timeEnded));
settings()->set("lastTimePlayed", m_timeStarted.secsTo(timeEnded));
emit propertiesChanged(this);
}
}
int64_t BaseInstance::totalTimePlayed() const
{
qint64 current = m_settings->get("totalTimePlayed").toLongLong();
if (m_isRunning) {
QDateTime timeNow = QDateTime::currentDateTime();
return current + m_timeStarted.secsTo(timeNow);
}
return current;
}
int64_t BaseInstance::lastTimePlayed() const
{
if (m_isRunning) {
QDateTime timeNow = QDateTime::currentDateTime();
return m_timeStarted.secsTo(timeNow);
}
return m_settings->get("lastTimePlayed").toLongLong();
}
bool BaseInstance::countTimePlayed() const
{
return m_settings->get("CountGameTime").toBool();
}
void BaseInstance::resetTimePlayed()
{
settings()->reset("totalTimePlayed");
settings()->reset("lastTimePlayed");
}
QString BaseInstance::instanceType() const
{
return m_settings->get("InstanceType").toString();
}
QString BaseInstance::instanceRoot() const
{
return m_rootDir;
}
SettingsObject* BaseInstance::settings()
{
loadSpecificSettings();
return m_settings.get();
}
bool BaseInstance::canLaunch() const
{
return (!hasVersionBroken() && !isRunning());
}
bool BaseInstance::reloadSettings()
{
return m_settings->reload();
}
qint64 BaseInstance::lastLaunch() const
{
return m_settings->get("lastLaunchTime").value<qint64>();
}
void BaseInstance::setLastLaunch(qint64 val)
{
// FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("lastLaunchTime", val);
emit propertiesChanged(this);
}
void BaseInstance::setNotes(QString val)
{
// FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("notes", val);
}
QString BaseInstance::notes() const
{
return m_settings->get("notes").toString();
}
void BaseInstance::setIconKey(QString val)
{
// FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("iconKey", val);
emit propertiesChanged(this);
}
QString BaseInstance::iconKey() const
{
return m_settings->get("iconKey").toString();
}
void BaseInstance::setName(QString val)
{
// FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("name", val);
emit propertiesChanged(this);
}
bool BaseInstance::syncInstanceDirName(const QString& newRoot) const
{
auto oldRoot = instanceRoot();
return oldRoot == newRoot || QFile::rename(oldRoot, newRoot);
}
void BaseInstance::registerShortcut(const ShortcutData& data)
{
auto currentShortcuts = shortcuts();
currentShortcuts.append(data);
qDebug() << "Registering shortcut for instance" << id() << "with name" << data.name << "and path" << data.filePath;
setShortcuts(currentShortcuts);
}
void BaseInstance::setShortcuts(const QList<ShortcutData>& shortcuts)
{
// FIXME: if no change, do not set. setting involves saving a file.
QJsonArray array;
for (const auto& elem : shortcuts) {
array.append(QJsonObject{ { "name", elem.name }, { "filePath", elem.filePath }, { "target", static_cast<int>(elem.target) } });
}
QJsonDocument document;
document.setArray(array);
m_settings->set("shortcuts", QString::fromUtf8(document.toJson(QJsonDocument::Compact)));
}
QList<ShortcutData> BaseInstance::shortcuts() const
{
auto data = m_settings->get("shortcuts").toString().toUtf8();
QJsonParseError parseError;
auto document = QJsonDocument::fromJson(data, &parseError);
if (parseError.error != QJsonParseError::NoError || !document.isArray())
return {};
QList<ShortcutData> results;
for (const auto& elem : document.array()) {
if (!elem.isObject())
continue;
auto dict = elem.toObject();
if (!dict.contains("name") || !dict.contains("filePath") || !dict.contains("target"))
continue;
int value = dict["target"].toInt(-1);
if (!dict["name"].isString() || !dict["filePath"].isString() || value < 0 || value >= 3)
continue;
QString shortcutName = dict["name"].toString();
QString filePath = dict["filePath"].toString();
if (!QDir(filePath).exists()) {
qWarning() << "Shortcut" << shortcutName << "for instance" << name() << "have non-existent path" << filePath;
continue;
}
results.append({ shortcutName, filePath, static_cast<ShortcutTarget>(value) });
}
return results;
}
QString BaseInstance::name() const
{
return m_settings->get("name").toString();
}
QString BaseInstance::windowTitle() const
{
return BuildConfig.LAUNCHER_DISPLAYNAME + ": " + name();
}
// FIXME: why is this here? move it to MinecraftInstance!!!
QStringList BaseInstance::extraArguments()
{
return Commandline::splitArgs(settings()->get("JvmArgs").toString());
}
LaunchTask* BaseInstance::getLaunchTask()
{
return m_launchProcess.get();
}
void BaseInstance::updateRuntimeContext()
{
// NOOP
}
bool BaseInstance::isLegacy()
{
return traits().contains("legacyLaunch") || traits().contains("alphaLaunch");
}