Compare commits

...

21 Commits

Author SHA1 Message Date
Sefa Eyeoglu
404b9599fd
Merge pull request #781 from Scrumplex/chore-bump-1.3.2 2022-06-12 18:51:04 +02:00
flow
4a803e4721
Merge pull request #783 from Scrumplex/fix-avoid-settings-register-warnings
Avoid re-registering InstanceType
2022-06-12 18:50:38 +02:00
Sefa Eyeoglu
2bcc81a75b
chore: bump version 2022-06-12 18:23:58 +02:00
Sefa Eyeoglu
0be9244ade
Merge pull request #679 from kthchew/fix/win-install-version 2022-06-12 18:23:39 +02:00
Sefa Eyeoglu
a852d29262
Merge pull request #771 from flowln/modrinth_multiple_downloads 2022-06-12 15:41:16 +02:00
Sefa Eyeoglu
7145a55f83
Merge pull request #632 from ryanccn/macos-app-heuristic 2022-06-12 15:41:16 +02:00
dada513
4f8915da6c
Merge pull request #780 from flowln/guo_ext_2 2022-06-12 15:41:16 +02:00
dada513
973c4327e3
Merge pull request #773 from vancez/fix-launch-button 2022-06-12 15:41:16 +02:00
dada513
820951bce1
Merge pull request #770 from flowln/technic_links 2022-06-12 15:41:16 +02:00
dada513
c8257c632a
Merge pull request #764 from Scrumplex/fix-compile-flags 2022-06-12 15:41:16 +02:00
dada513
a4d69cce43
Merge pull request #720 from icelimetea/fix-legacy-issues 2022-06-12 15:41:16 +02:00
Sefa Eyeoglu
dfe2e5f44e
Merge pull request #709 from Scrumplex/fix-instancetypes
Always store InstanceType
2022-06-12 15:41:16 +02:00
Sefa Eyeoglu
dbdd1c83d9
Merge pull request #732 from MrMelon54/develop 2022-06-12 15:41:16 +02:00
txtsd
2519bb96f9
Merge pull request #762 from muscaln/flake
Bump flake lock
2022-06-12 15:41:16 +02:00
txtsd
f169173afa
Merge pull request #734 from babbaj/extra-jdks
nix: add package argument for extra jdks
2022-06-12 15:41:16 +02:00
flow
1c70b29b3d
Merge pull request #685 from kthchew/fix/technic-quilt
Add Quilt support for Technic modpacks
2022-06-12 15:41:16 +02:00
Sefa Eyeoglu
75b7ca814a
Merge pull request #718 from DioEgizio/revert-unfunny-pr 2022-06-12 15:41:16 +02:00
TheOPtimal
b6b33862ed
Prepare for Nix 2.7 (#286)
* Prepare for Nix 2.7

* Fix embarassing oopsie
2022-06-12 15:25:42 +02:00
Sefa Eyeoglu
0dbd70ba9f
Merge pull request #701 from jn64/patch-1
Add "mc" keyword to desktop file
2022-06-12 15:25:42 +02:00
Sefa Eyeoglu
7a5a1a9af7
Merge pull request #692 from glowiak/patch-6 2022-06-12 15:25:42 +02:00
Sefa Eyeoglu
938f222791
Merge pull request #682 from istudyatuni/fix-add-account-behaviour 2022-06-12 15:25:42 +02:00
30 changed files with 323 additions and 140 deletions

View File

@ -206,7 +206,7 @@ jobs:
shell: msys2 {0} shell: msys2 {0}
run: | run: |
cd ${{ env.INSTALL_DIR }} cd ${{ env.INSTALL_DIR }}
makensis -NOCD "-DVERSION=${{ env.VERSION }}" "-DMUI_ICON=${{ github.workspace }}/program_info/polymc.ico" "-XOutFile ${{ github.workspace }}/PolyMC-Setup.exe" "${{ github.workspace }}/program_info/win_install.nsi" makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"
- name: Package (Linux) - name: Package (Linux)
if: runner.os == 'Linux' && matrix.appimage != true if: runner.os == 'Linux' && matrix.appimage != true

View File

@ -45,8 +45,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_BEFORE=0x050C00") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_BEFORE=0x050C00")
# set CXXFLAGS for build targets # set CXXFLAGS for build targets
set(CMAKE_CXX_FLAGS_DEBUG "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}") set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}")
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}")
option(ENABLE_LTO "Enable Link Time Optimization" off) option(ENABLE_LTO "Enable Link Time Optimization" off)
@ -74,7 +73,7 @@ set(Launcher_HELP_URL "https://polymc.org/wiki/help-pages/%1" CACHE STRING "URL
######## Set version numbers ######## ######## Set version numbers ########
set(Launcher_VERSION_MAJOR 1) set(Launcher_VERSION_MAJOR 1)
set(Launcher_VERSION_MINOR 3) set(Launcher_VERSION_MINOR 3)
set(Launcher_VERSION_HOTFIX 1) set(Launcher_VERSION_HOTFIX 2)
# Build number # Build number
set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.") set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")

View File

@ -34,11 +34,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1653326962, "lastModified": 1654665288,
"narHash": "sha256-W8feCYqKTsMre4nAEpv5Kx1PVFC+hao/LwqtB2Wci/8=", "narHash": "sha256-7blJpfoZEu7GKb84uh3io/5eSJNdaagXD9d15P9iQMs=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "41cc1d5d9584103be4108c1815c350e07c807036", "rev": "43ecbe7840d155fa933ee8a500fb00dbbc651fc8",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -22,15 +22,17 @@
pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system}); pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system});
in in
{ {
packages = forAllSystems (system: { packages = forAllSystems (system: rec {
polymc = pkgs.${system}.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus; }; polymc = pkgs.${system}.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus; };
polymc-qt6 = pkgs.${system}.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus; }; polymc-qt6 = pkgs.${system}.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus; };
default = polymc;
}); });
defaultPackage = forAllSystems (system: self.packages.${system}.polymc); defaultPackage = forAllSystems (system: self.packages.${system}.default);
apps = forAllSystems (system: { polymc = { type = "app"; program = "${self.defaultPackage.${system}}/bin/polymc"; }; }); apps = forAllSystems (system: rec { polymc = { type = "app"; program = "${self.defaultPackage.${system}}/bin/polymc"; }; default = polymc; });
defaultApp = forAllSystems (system: self.apps.${system}.polymc); defaultApp = forAllSystems (system: self.apps.${system}.default);
overlay = final: prev: { polymc = self.defaultPackage.${final.system}; }; overlay = final: prev: { polymc = self.defaultPackage.${final.system}; };
}; };

View File

@ -871,6 +871,12 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_mcedit.reset(new MCEditTool(m_settings)); m_mcedit.reset(new MCEditTool(m_settings));
} }
#ifdef Q_OS_MACOS
connect(this, &Application::clickedOnDock, [this]() {
this->showMainWindow();
});
#endif
connect(this, &Application::aboutToQuit, [this](){ connect(this, &Application::aboutToQuit, [this](){
if(m_instances) if(m_instances)
{ {
@ -954,6 +960,21 @@ bool Application::createSetupWizard()
return false; return false;
} }
bool Application::event(QEvent* event) {
#ifdef Q_OS_MACOS
if (event->type() == QEvent::ApplicationStateChange) {
auto ev = static_cast<QApplicationStateChangeEvent*>(event);
if (m_prevAppState == Qt::ApplicationActive
&& ev->applicationState() == Qt::ApplicationActive) {
emit clickedOnDock();
}
m_prevAppState = ev->applicationState();
}
#endif
return QApplication::event(event);
}
void Application::setupWizardFinished(int status) void Application::setupWizardFinished(int status)
{ {
qDebug() << "Wizard result =" << status; qDebug() << "Wizard result =" << status;

View File

@ -94,6 +94,8 @@ public:
Application(int &argc, char **argv); Application(int &argc, char **argv);
virtual ~Application(); virtual ~Application();
bool event(QEvent* event) override;
std::shared_ptr<SettingsObject> settings() const { std::shared_ptr<SettingsObject> settings() const {
return m_settings; return m_settings;
} }
@ -181,6 +183,10 @@ signals:
void globalSettingsAboutToOpen(); void globalSettingsAboutToOpen();
void globalSettingsClosed(); void globalSettingsClosed();
#ifdef Q_OS_MACOS
void clickedOnDock();
#endif
public slots: public slots:
bool launch( bool launch(
InstancePtr instance, InstancePtr instance,
@ -238,6 +244,10 @@ private:
QString m_rootPath; QString m_rootPath;
Status m_status = Application::StartingUp; Status m_status = Application::StartingUp;
#ifdef Q_OS_MACOS
Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive;
#endif
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
// used on Windows to attach the standard IO streams // used on Windows to attach the standard IO streams
bool consoleAttached = false; bool consoleAttached = false;

View File

@ -59,7 +59,11 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("lastLaunchTime", 0); m_settings->registerSetting("lastLaunchTime", 0);
m_settings->registerSetting("totalTimePlayed", 0); m_settings->registerSetting("totalTimePlayed", 0);
m_settings->registerSetting("lastTimePlayed", 0); m_settings->registerSetting("lastTimePlayed", 0);
m_settings->registerSetting("InstanceType", "OneSix");
// 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 // Custom Commands
auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false); auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false);

View File

@ -454,4 +454,47 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
return false; return false;
#endif #endif
} }
QStringList listFolderPaths(QDir root)
{
auto createAbsPath = [](QFileInfo const& entry) { return FS::PathCombine(entry.path(), entry.fileName()); };
QStringList entries;
root.refresh();
for (auto entry : root.entryInfoList(QDir::Filter::Files)) {
entries.append(createAbsPath(entry));
}
for (auto entry : root.entryInfoList(QDir::Filter::AllDirs | QDir::Filter::NoDotAndDotDot)) {
entries.append(listFolderPaths(createAbsPath(entry)));
}
return entries;
}
bool overrideFolder(QString overwritten_path, QString override_path)
{
if (!FS::ensureFolderPathExists(overwritten_path))
return false;
QStringList paths_to_override;
QDir root_override (override_path);
for (auto file : listFolderPaths(root_override)) {
QString destination = file;
destination.replace(override_path, overwritten_path);
qDebug() << QString("Applying override %1 in %2").arg(file, destination);
if (QFile::exists(destination))
QFile::remove(destination);
if (!QFile::rename(file, destination)) {
qCritical() << QString("Failed to apply override from %1 to %2").arg(file, destination);
return false;
}
}
return true;
}
} }

View File

@ -124,4 +124,8 @@ QString getDesktopDir();
// call it *name* and assign it the icon *icon* // call it *name* and assign it the icon *icon*
// return true if operation succeeded // return true if operation succeeded
bool createShortCut(QString location, QString dest, QStringList args, QString name, QString iconLocation); bool createShortCut(QString location, QString dest, QStringList args, QString name, QString iconLocation);
// Overrides one folder with the contents of another, preserving items exclusive to the first folder
// Equivalent to doing QDir::rename, but allowing for overrides
bool overrideFolder(QString overwritten_path, QString override_path);
} }

View File

@ -582,10 +582,10 @@ void InstanceImportTask::processMultiMC()
emitSucceeded(); emitSucceeded();
} }
// https://docs.modrinth.com/docs/modpacks/format_definition/
void InstanceImportTask::processModrinth() void InstanceImportTask::processModrinth()
{ {
std::vector<Modrinth::File> files; std::vector<Modrinth::File> files;
std::vector<Modrinth::File> non_whitelisted_files;
QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion; QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion;
try { try {
QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json");
@ -600,26 +600,30 @@ void InstanceImportTask::processModrinth()
auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json"); auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
bool had_optional = false; bool had_optional = false;
for (auto& modInfo : jsonFiles) { for (auto modInfo : jsonFiles) {
Modrinth::File file; Modrinth::File file;
file.path = Json::requireString(modInfo, "path"); file.path = Json::requireString(modInfo, "path");
auto env = Json::ensureObject(modInfo, "env"); auto env = Json::ensureObject(modInfo, "env");
QString support = Json::ensureString(env, "client", "unsupported"); // 'env' field is optional
if (support == "unsupported") { if (!env.isEmpty()) {
continue; QString support = Json::ensureString(env, "client", "unsupported");
} else if (support == "optional") { if (support == "unsupported") {
// TODO: Make a review dialog for choosing which ones the user wants! continue;
if (!had_optional) { } else if (support == "optional") {
had_optional = true; // TODO: Make a review dialog for choosing which ones the user wants!
auto info = CustomMessageBox::selectable( if (!had_optional) {
m_parent, tr("Optional mod detected!"), had_optional = true;
tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"), QMessageBox::Information); auto info = CustomMessageBox::selectable(
info->exec(); m_parent, tr("Optional mod detected!"),
} tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"),
QMessageBox::Information);
info->exec();
}
if (file.path.endsWith(".jar")) if (file.path.endsWith(".jar"))
file.path += ".disabled"; file.path += ".disabled";
}
} }
QJsonObject hashes = Json::requireObject(modInfo, "hashes"); QJsonObject hashes = Json::requireObject(modInfo, "hashes");
@ -640,40 +644,31 @@ void InstanceImportTask::processModrinth()
} }
file.hash = QByteArray::fromHex(hash.toLatin1()); file.hash = QByteArray::fromHex(hash.toLatin1());
file.hashAlgorithm = hashAlgorithm; file.hashAlgorithm = hashAlgorithm;
// Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode // Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode
// (as Modrinth seems to incorrectly handle spaces) // (as Modrinth seems to incorrectly handle spaces)
file.download = Json::requireString(Json::ensureArray(modInfo, "downloads").first(), "Download URL for " + file.path); auto download_arr = Json::ensureArray(modInfo, "downloads");
for(auto download : download_arr) {
qWarning() << download.toString();
bool is_last = download.toString() == download_arr.last().toString();
if (!file.download.isValid()) { auto download_url = QUrl(download.toString());
qDebug() << QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(file.download.toString(), file.path);
throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path)); if (!download_url.isValid()) {
} qDebug() << QString("Download URL (%1) for %2 is not a correctly formatted URL")
else if (!Modrinth::validateDownloadUrl(file.download)) { .arg(download_url.toString(), file.path);
qDebug() << QString("Download URL (%1) for %2 is from a non-whitelisted by Modrinth domain").arg(file.download.toString(), file.path); if(is_last && file.downloads.isEmpty())
non_whitelisted_files.push_back(file); throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path));
}
else {
file.downloads.push_back(download_url);
}
} }
files.push_back(file); files.push_back(file);
} }
if (!non_whitelisted_files.empty()) {
QString text;
for (const auto& file : non_whitelisted_files) {
text += tr("Filepath: %1<br>URL: <a href='%2'>%2</a><br>").arg(file.path, file.download.toString());
}
auto message_dialog = new ScrollMessageBox(m_parent, tr("Non-whitelisted mods found"),
tr("The following mods have URLs that are not whitelisted by Modrinth.\n"
"Proceed with caution!"),
text);
message_dialog->setModal(true);
if (message_dialog->exec() == QDialog::Rejected) {
emitFailed("Aborted");
return;
}
}
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json"); auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) { for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
QString name = it.key(); QString name = it.key();
@ -702,15 +697,25 @@ void InstanceImportTask::processModrinth()
return; return;
} }
QString overridePath = FS::PathCombine(m_stagingPath, "overrides"); auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft");
if (QFile::exists(overridePath)) {
QString mcPath = FS::PathCombine(m_stagingPath, ".minecraft"); auto override_path = FS::PathCombine(m_stagingPath, "overrides");
if (!QFile::rename(overridePath, mcPath)) { if (QFile::exists(override_path)) {
if (!QFile::rename(override_path, mcPath)) {
emitFailed(tr("Could not rename the overrides folder:\n") + "overrides"); emitFailed(tr("Could not rename the overrides folder:\n") + "overrides");
return; return;
} }
} }
// Do client overrides
auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides");
if (QFile::exists(client_override_path)) {
if (!FS::overrideFolder(mcPath, client_override_path)) {
emitFailed(tr("Could not rename the client overrides folder:\n") + "client overrides");
return;
}
}
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(configPath); auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
@ -735,13 +740,24 @@ void InstanceImportTask::processModrinth()
instance.saveNow(); instance.saveNow();
m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network()); m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network());
for (auto &file : files) for (auto file : files)
{ {
auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path); auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path);
qDebug() << "Will download" << file.download << "to" << path; qDebug() << "Will try to download" << file.downloads.front() << "to" << path;
auto dl = Net::Download::makeFile(file.download, path); auto dl = Net::Download::makeFile(file.downloads.dequeue(), path);
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
m_filesNetJob->addNetAction(dl); m_filesNetJob->addNetAction(dl);
if (file.downloads.size() > 0) {
// FIXME: This really needs to be put into a ConcurrentTask of
// MultipleOptionsTask's , once those exist :)
connect(dl.get(), &NetAction::failed, [this, &file, path, dl]{
auto dl = Net::Download::makeFile(file.downloads.dequeue(), path);
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
m_filesNetJob->addNetAction(dl);
dl->succeeded();
});
}
} }
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]()
{ {

View File

@ -547,8 +547,20 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id)
auto instanceRoot = FS::PathCombine(m_instDir, id); auto instanceRoot = FS::PathCombine(m_instDir, id);
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instanceRoot, "instance.cfg")); auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instanceRoot, "instance.cfg"));
InstancePtr inst; InstancePtr inst;
// TODO: Handle incompatible instances
inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot)); instanceSettings->registerSetting("InstanceType", "");
QString inst_type = instanceSettings->get("InstanceType").toString();
// NOTE: Some PolyMC versions didn't save the InstanceType properly. We will just bank on the probability that this is probably a OneSix instance
if (inst_type == "OneSix" || inst_type.isEmpty())
{
inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot));
}
else
{
inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot));
}
qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot(); qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot();
return inst; return inst;
} }

View File

@ -105,6 +105,11 @@ void LaunchController::decideAccount()
// Open the account manager. // Open the account manager.
APPLICATION->ShowGlobalSettings(m_parentWidget, "accounts"); APPLICATION->ShowGlobalSettings(m_parentWidget, "accounts");
} }
else if (reply == QMessageBox::No)
{
// Do not open "profile select" dialog.
return;
}
} }
m_accountToUse = accounts->defaultAccount(); m_accountToUse = accounts->defaultAccount();

View File

@ -93,7 +93,7 @@ void UpdateController::installUpdates()
qDebug() << "Installing updates."; qDebug() << "Installing updates.";
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
QString finishCmd = QApplication::applicationFilePath(); QString finishCmd = QApplication::applicationFilePath();
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined (Q_OS_OPENBSD) #elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
QString finishCmd = FS::PathCombine(m_root, BuildConfig.LAUNCHER_NAME); QString finishCmd = FS::PathCombine(m_root, BuildConfig.LAUNCHER_NAME);
#elif defined Q_OS_MAC #elif defined Q_OS_MAC
QString finishCmd = QApplication::applicationFilePath(); QString finishCmd = QApplication::applicationFilePath();

View File

@ -56,6 +56,15 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren
emit iconUpdated({}); emit iconUpdated({});
} }
void IconList::sortIconList()
{
qDebug() << "Sorting icon list...";
std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) {
return a.m_key.localeAwareCompare(b.m_key) < 0;
});
reindex();
}
void IconList::directoryChanged(const QString &path) void IconList::directoryChanged(const QString &path)
{ {
QDir new_dir (path); QDir new_dir (path);
@ -141,6 +150,8 @@ void IconList::directoryChanged(const QString &path)
emit iconUpdated(key); emit iconUpdated(key);
} }
} }
sortIconList();
} }
void IconList::fileChanged(const QString &path) void IconList::fileChanged(const QString &path)

View File

@ -71,6 +71,7 @@ private:
// hide assign op // hide assign op
IconList &operator=(const IconList &) = delete; IconList &operator=(const IconList &) = delete;
void reindex(); void reindex();
void sortIconList();
public slots: public slots:
void directoryChanged(const QString &path); void directoryChanged(const QString &path);

View File

@ -27,10 +27,6 @@ int main(int argc, char *argv[])
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
#endif
// initialize Qt // initialize Qt
Application app(argc, argv); Application app(argc, argv);

View File

@ -168,6 +168,8 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO
m_settings->registerOverride(globalSettings->getSetting("CloseAfterLaunch"), miscellaneousOverride); m_settings->registerOverride(globalSettings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
m_settings->registerOverride(globalSettings->getSetting("QuitAfterGameStop"), miscellaneousOverride); m_settings->registerOverride(globalSettings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
m_settings->set("InstanceType", "OneSix");
m_components.reset(new PackProfile(this)); m_components.reset(new PackProfile(this));
} }

View File

@ -95,19 +95,6 @@ void loadIndexedVersions(Modpack& pack, QJsonDocument& doc)
pack.versionsLoaded = true; pack.versionsLoaded = true;
} }
auto validateDownloadUrl(QUrl url) -> bool
{
static QSet<QString> domainWhitelist{
"cdn.modrinth.com",
"github.com",
"raw.githubusercontent.com",
"gitlab.com"
};
auto domain = url.host();
return domainWhitelist.contains(domain);
}
auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion
{ {
ModpackVersion file; ModpackVersion file;
@ -137,9 +124,6 @@ auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion
auto url = Json::requireString(parent, "url"); auto url = Json::requireString(parent, "url");
if(!validateDownloadUrl(url))
continue;
file.download_url = url; file.download_url = url;
if(is_primary) if(is_primary)
break; break;

View File

@ -40,6 +40,7 @@
#include <QByteArray> #include <QByteArray>
#include <QCryptographicHash> #include <QCryptographicHash>
#include <QQueue>
#include <QString> #include <QString>
#include <QUrl> #include <QUrl>
#include <QVector> #include <QVector>
@ -48,14 +49,12 @@ class MinecraftInstance;
namespace Modrinth { namespace Modrinth {
struct File struct File {
{
QString path; QString path;
QCryptographicHash::Algorithm hashAlgorithm; QCryptographicHash::Algorithm hashAlgorithm;
QByteArray hash; QByteArray hash;
// TODO: should this support multiple download URLs, like the JSON does? QQueue<QUrl> downloads;
QUrl download;
}; };
struct ModpackExtra { struct ModpackExtra {

View File

@ -185,13 +185,22 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const
components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1, 1)); components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1, 1));
} }
} }
else if (libraryName.startsWith("net.minecraftforge:minecraftforge:")) else
{ {
components->setComponentVersion("net.minecraftforge", libraryName.section(':', 2)); static QStringList possibleLoaders{
} "net.minecraftforge:minecraftforge:",
else if (libraryName.startsWith("net.fabricmc:fabric-loader:")) "net.fabricmc:fabric-loader:",
{ "org.quiltmc:quilt-loader:"
components->setComponentVersion("net.fabricmc.fabric-loader", libraryName.section(':', 2)); };
for (const auto& loader : possibleLoaders)
{
if (libraryName.startsWith(loader))
{
auto loaderComponent = loader.chopped(1).replace(":", ".");
components->setComponentVersion(loaderComponent, libraryName.section(':', 2));
break;
}
}
} }
} }
} }

View File

@ -52,7 +52,7 @@ QString MCEditTool::getProgramPath()
#else #else
const QString mceditPath = path(); const QString mceditPath = path();
QDir mceditDir(mceditPath); QDir mceditDir(mceditPath);
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (mceditDir.exists("mcedit.sh")) if (mceditDir.exists("mcedit.sh"))
{ {
return mceditDir.absoluteFilePath("mcedit.sh"); return mceditDir.absoluteFilePath("mcedit.sh");

View File

@ -2101,6 +2101,9 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
selectionBad(); selectionBad();
return; return;
} }
if (m_selectedInstance) {
disconnect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
}
QString id = current.data(InstanceList::InstanceIDRole).toString(); QString id = current.data(InstanceList::InstanceIDRole).toString();
m_selectedInstance = APPLICATION->instances()->getInstanceById(id); m_selectedInstance = APPLICATION->instances()->getInstanceById(id);
if (m_selectedInstance) if (m_selectedInstance)
@ -2127,6 +2130,8 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
updateToolsMenu(); updateToolsMenu();
APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id()); APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id());
connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
} }
else else
{ {
@ -2216,3 +2221,9 @@ void MainWindow::updateStatusCenter()
m_statusCenter->setText(tr("Total playtime: %1").arg(Time::prettifyDuration(timePlayed))); m_statusCenter->setText(tr("Total playtime: %1").arg(Time::prettifyDuration(timePlayed)));
} }
} }
void MainWindow::refreshCurrentInstance(bool running)
{
auto current = view->selectionModel()->currentIndex();
instanceChanged(current, current);
}

View File

@ -192,6 +192,8 @@ private slots:
void keyReleaseEvent(QKeyEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override;
#endif #endif
void refreshCurrentInstance(bool running);
private: private:
void retranslateUi(); void retranslateUi();

View File

@ -110,11 +110,13 @@ void ImportPage::updateState()
{ {
// FIXME: actually do some validation of what's inside here... this is fake AF // FIXME: actually do some validation of what's inside here... this is fake AF
QFileInfo fi(input); QFileInfo fi(input);
// mrpack is a modrinth pack
// Allow non-latin people to use ZIP files! // Allow non-latin people to use ZIP files!
auto zip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip"); bool isZip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip");
if(fi.exists() && (zip || fi.suffix() == "mrpack")) // mrpack is a modrinth pack
bool isMRPack = fi.suffix() == "mrpack";
if(fi.exists() && (isZip || isMRPack))
{ {
QFileInfo fi(url.fileName()); QFileInfo fi(url.fileName());
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url,this)); dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url,this));
@ -149,7 +151,8 @@ void ImportPage::setUrl(const QString& url)
void ImportPage::on_modpackBtn_clicked() void ImportPage::on_modpackBtn_clicked()
{ {
auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString(); auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString();
filter += ";;" + tr("Modrinth pack (*.mrpack)"); //: Option for filtering for *.mrpack files when importing
filter += ";;" + tr("Modrinth pack") + " (*.mrpack)";
const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), filter); const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), filter);
if (url.isValid()) if (url.isValid())
{ {

View File

@ -60,7 +60,11 @@
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QTextBrowser" name="packDescription"/> <widget class="QTextBrowser" name="packDescription">
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item> </item>
</layout> </layout>
</item> </item>

View File

@ -24,24 +24,65 @@ import java.net.URL;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
/*
* WARNING: This class is reflectively accessed by legacy Forge versions.
* Changing field and method declarations without further testing is not recommended.
*/
public final class Launcher extends Applet implements AppletStub { public final class Launcher extends Applet implements AppletStub {
private final Map<String, String> params = new TreeMap<>(); private final Map<String, String> params = new TreeMap<>();
private final Applet wrappedApplet; private Applet wrappedApplet;
private URL documentBase;
private boolean active = false; private boolean active = false;
public Launcher(Applet applet) { public Launcher(Applet applet) {
this(applet, null);
}
public Launcher(Applet applet, URL documentBase) {
this.setLayout(new BorderLayout()); this.setLayout(new BorderLayout());
this.add(applet, "Center"); this.add(applet, "Center");
this.wrappedApplet = applet; this.wrappedApplet = applet;
try {
if (documentBase != null) {
this.documentBase = documentBase;
} else if (applet.getClass().getPackage().getName().startsWith("com.mojang")) {
// Special case only for Classic versions
this.documentBase = new URL("http", "www.minecraft.net", 80, "/game/");
} else {
this.documentBase = new URL("http://www.minecraft.net/game/");
}
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
} }
public void setParameter(String name, String value) public void replace(Applet applet) {
{ this.wrappedApplet = applet;
applet.setStub(this);
applet.setSize(getWidth(), getHeight());
this.setLayout(new BorderLayout());
this.add(applet, "Center");
applet.init();
active = true;
applet.start();
validate();
}
public void setParameter(String name, String value) {
params.put(name, value); params.put(name, value);
} }
@ -54,7 +95,7 @@ public final class Launcher extends Applet implements AppletStub {
try { try {
return super.getParameter(name); return super.getParameter(name);
} catch (Exception ignore) {} } catch (Exception ignored) {}
return null; return null;
} }
@ -108,25 +149,13 @@ public final class Launcher extends Applet implements AppletStub {
try { try {
return new URL("http://www.minecraft.net/game/"); return new URL("http://www.minecraft.net/game/");
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
e.printStackTrace(); throw new RuntimeException(e);
} }
return null;
} }
@Override @Override
public URL getDocumentBase() { public URL getDocumentBase() {
try { return documentBase;
// Special case only for Classic versions
if (wrappedApplet.getClass().getCanonicalName().startsWith("com.mojang"))
return new URL("http", "www.minecraft.net", 80, "/game/");
return new URL("http://www.minecraft.net/game/");
} catch (MalformedURLException e) {
e.printStackTrace();
}
return null;
} }
@Override @Override

View File

@ -14,6 +14,7 @@
, quazip , quazip
, libGL , libGL
, msaClientID ? "" , msaClientID ? ""
, extraJDKs ? [ ]
# flake # flake
, self , self
@ -36,6 +37,8 @@ let
# This variable will be passed to Minecraft by PolyMC # This variable will be passed to Minecraft by PolyMC
gameLibraryPath = libpath + ":/run/opengl-driver/lib"; gameLibraryPath = libpath + ":/run/opengl-driver/lib";
javaPaths = lib.makeSearchPath "bin/java" ([ jdk jdk8 ] ++ extraJDKs);
in in
stdenv.mkDerivation rec { stdenv.mkDerivation rec {
@ -67,7 +70,7 @@ stdenv.mkDerivation rec {
# xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128 # xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128
wrapQtApp $out/bin/polymc \ wrapQtApp $out/bin/polymc \
--set GAME_LIBRARY_PATH ${gameLibraryPath} \ --set GAME_LIBRARY_PATH ${gameLibraryPath} \
--prefix POLYMC_JAVA_PATHS : ${jdk}/lib/openjdk/bin/java:${jdk8}/lib/openjdk/bin/java \ --prefix POLYMC_JAVA_PATHS : ${javaPaths} \
--prefix PATH : ${lib.makeBinPath [ xorg.xrandr ]} --prefix PATH : ${lib.makeBinPath [ xorg.xrandr ]}
''; '';

View File

@ -14,6 +14,8 @@ set(Launcher_MetaInfo "program_info/org.polymc.PolyMC.metainfo.xml" PARENT_SCOPE
set(Launcher_ManPage "program_info/polymc.6.txt" PARENT_SCOPE) set(Launcher_ManPage "program_info/polymc.6.txt" PARENT_SCOPE)
set(Launcher_SVG "program_info/org.polymc.PolyMC.svg" PARENT_SCOPE) set(Launcher_SVG "program_info/org.polymc.PolyMC.svg" PARENT_SCOPE)
set(Launcher_Branding_ICNS "program_info/polymc.icns" PARENT_SCOPE) set(Launcher_Branding_ICNS "program_info/polymc.icns" PARENT_SCOPE)
set(Launcher_Branding_ICO "program_info/polymc.ico")
set(Launcher_Branding_ICO "${Launcher_Branding_ICO}" PARENT_SCOPE)
set(Launcher_Branding_WindowsRC "program_info/polymc.rc" PARENT_SCOPE) set(Launcher_Branding_WindowsRC "program_info/polymc.rc" PARENT_SCOPE)
set(Launcher_Branding_LogoQRC "program_info/polymc.qrc" PARENT_SCOPE) set(Launcher_Branding_LogoQRC "program_info/polymc.qrc" PARENT_SCOPE)
@ -24,3 +26,4 @@ configure_file(org.polymc.PolyMC.metainfo.xml.in org.polymc.PolyMC.metainfo.xml)
configure_file(polymc.rc.in polymc.rc @ONLY) configure_file(polymc.rc.in polymc.rc @ONLY)
configure_file(polymc.manifest.in polymc.manifest @ONLY) configure_file(polymc.manifest.in polymc.manifest @ONLY)
configure_file(polymc.ico polymc.ico COPYONLY) configure_file(polymc.ico polymc.ico COPYONLY)
configure_file(win_install.nsi.in win_install.nsi @ONLY)

View File

@ -8,5 +8,5 @@ Exec=@Launcher_APP_BINARY_NAME@
StartupNotify=true StartupNotify=true
Icon=org.polymc.PolyMC Icon=org.polymc.PolyMC
Categories=Game; Categories=Game;
Keywords=game;minecraft;launcher; Keywords=game;minecraft;launcher;mc;
StartupWMClass=PolyMC StartupWMClass=PolyMC

View File

@ -4,10 +4,13 @@
Unicode true Unicode true
Name "PolyMC" Name "@Launcher_CommonName@"
InstallDir "$LOCALAPPDATA\Programs\PolyMC" InstallDir "$LOCALAPPDATA\Programs\@Launcher_CommonName@"
InstallDirRegKey HKCU "Software\PolyMC" "InstallDir" InstallDirRegKey HKCU "Software\@Launcher_CommonName@" "InstallDir"
RequestExecutionLevel user RequestExecutionLevel user
OutFile "../@Launcher_CommonName@-Setup.exe"
!define MUI_ICON "../@Launcher_Branding_ICO@"
;-------------------------------- ;--------------------------------
@ -18,7 +21,7 @@ RequestExecutionLevel user
!insertmacro MUI_PAGE_COMPONENTS !insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_INSTFILES
!define MUI_FINISHPAGE_RUN "$InstDir\polymc.exe" !define MUI_FINISHPAGE_RUN "$InstDir\@Launcher_APP_BINARY_NAME@.exe"
!insertmacro MUI_PAGE_FINISH !insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_CONFIRM
@ -98,16 +101,23 @@ RequestExecutionLevel user
;-------------------------------- ;--------------------------------
; Version info
VIProductVersion "@Launcher_RELEASE_VERSION_NAME4@"
VIFileVersion "@Launcher_RELEASE_VERSION_NAME4@"
VIAddVersionKey "FileVersion" "@Launcher_RELEASE_VERSION_NAME4@"
;--------------------------------
; The stuff to install ; The stuff to install
Section "PolyMC" Section "@Launcher_CommonName@"
SectionIn RO SectionIn RO
nsExec::Exec /TIMEOUT=2000 'TaskKill /IM polymc.exe /F' nsExec::Exec /TIMEOUT=2000 'TaskKill /IM @Launcher_APP_BINARY_NAME@.exe /F'
SetOutPath $INSTDIR SetOutPath $INSTDIR
File "polymc.exe" File "@Launcher_APP_BINARY_NAME@.exe"
File "qt.conf" File "qt.conf"
File *.dll File *.dll
File /r "iconengines" File /r "iconengines"
@ -117,20 +127,20 @@ Section "PolyMC"
File /r "styles" File /r "styles"
; Write the installation path into the registry ; Write the installation path into the registry
WriteRegStr HKCU Software\PolyMC "InstallDir" "$INSTDIR" WriteRegStr HKCU Software\@Launcher_CommonName@ "InstallDir" "$INSTDIR"
; Write the uninstall keys for Windows ; Write the uninstall keys for Windows
${GetParameters} $R0 ${GetParameters} $R0
${GetOptions} $R0 "/NoUninstaller" $R1 ${GetOptions} $R0 "/NoUninstaller" $R1
${If} ${Errors} ${If} ${Errors}
!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\PolyMC" !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_CommonName@"
WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "PolyMC" WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "@Launcher_CommonName@"
WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\polymc.exe" WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe"
WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"' WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"'
WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S' WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S'
WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR" WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR"
WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "PolyMC Contributors" WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "@Launcher_CommonName@ Contributors"
WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "${VERSION}" WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "@Launcher_RELEASE_VERSION_NAME4@"
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
IntFmt $0 "0x%08X" $0 IntFmt $0 "0x%08X" $0
WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0" WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0"
@ -143,13 +153,13 @@ SectionEnd
Section "Start Menu Shortcut" SM_SHORTCUTS Section "Start Menu Shortcut" SM_SHORTCUTS
CreateShortcut "$SMPROGRAMS\PolyMC.lnk" "$INSTDIR\polymc.exe" "" "$INSTDIR\polymc.exe" 0 CreateShortcut "$SMPROGRAMS\@Launcher_CommonName@.lnk" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" 0
SectionEnd SectionEnd
Section "Desktop Shortcut" DESKTOP_SHORTCUTS Section "Desktop Shortcut" DESKTOP_SHORTCUTS
CreateShortcut "$DESKTOP\PolyMC.lnk" "$INSTDIR\polymc.exe" "" "$INSTDIR\polymc.exe" 0 CreateShortcut "$DESKTOP\@Launcher_CommonName@.lnk" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" 0
SectionEnd SectionEnd
@ -159,12 +169,12 @@ SectionEnd
Section "Uninstall" Section "Uninstall"
nsExec::Exec /TIMEOUT=2000 'TaskKill /IM polymc.exe /F' nsExec::Exec /TIMEOUT=2000 'TaskKill /IM @Launcher_APP_BINARY_NAME@.exe /F'
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\PolyMC" DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_CommonName@"
DeleteRegKey HKCU SOFTWARE\PolyMC DeleteRegKey HKCU SOFTWARE\@Launcher_CommonName@
Delete $INSTDIR\polymc.exe Delete $INSTDIR\@Launcher_APP_BINARY_NAME@.exe
Delete $INSTDIR\uninstall.exe Delete $INSTDIR\uninstall.exe
Delete $INSTDIR\portable.txt Delete $INSTDIR\portable.txt
@ -220,8 +230,8 @@ Section "Uninstall"
RMDir /r $INSTDIR\platforms RMDir /r $INSTDIR\platforms
RMDir /r $INSTDIR\styles RMDir /r $INSTDIR\styles
Delete "$SMPROGRAMS\PolyMC.lnk" Delete "$SMPROGRAMS\@Launcher_CommonName@.lnk"
Delete "$DESKTOP\PolyMC.lnk" Delete "$DESKTOP\@Launcher_CommonName@.lnk"
RMDir "$INSTDIR" RMDir "$INSTDIR"