From 8de63b60b1a9d0ba16f5d45f3198c13637151749 Mon Sep 17 00:00:00 2001 From: icelimetea Date: Mon, 2 May 2022 22:36:55 +0100 Subject: [PATCH 001/308] Refactor some parts of NewLaunch (part 2) --- libraries/launcher/CMakeLists.txt | 13 +- .../launcher/net/minecraft/Launcher.java | 92 +++---- .../launcher/org/multimc/EntryPoint.java | 55 ++-- libraries/launcher/org/multimc/Launcher.java | 7 +- .../launcher/org/multimc/LauncherFactory.java | 34 +++ .../launcher/org/multimc/LegacyFrame.java | 176 ------------ libraries/launcher/org/multimc/Utils.java | 86 ------ .../org/multimc/applet/LegacyFrame.java | 167 ++++++++++++ .../ParameterNotFoundException.java} | 10 +- .../{ => exception}/ParseException.java | 12 +- .../org/multimc/impl/OneSixLauncher.java | 183 +++++++++++++ .../org/multimc/onesix/OneSixLauncher.java | 256 ------------------ .../org/multimc/{ => utils}/ParamBucket.java | 36 +-- .../launcher/org/multimc/utils/Utils.java | 49 ++++ 14 files changed, 542 insertions(+), 634 deletions(-) create mode 100644 libraries/launcher/org/multimc/LauncherFactory.java delete mode 100644 libraries/launcher/org/multimc/LegacyFrame.java delete mode 100644 libraries/launcher/org/multimc/Utils.java create mode 100644 libraries/launcher/org/multimc/applet/LegacyFrame.java rename libraries/launcher/org/multimc/{NotFoundException.java => exception/ParameterNotFoundException.java} (73%) rename libraries/launcher/org/multimc/{ => exception}/ParseException.java (81%) create mode 100644 libraries/launcher/org/multimc/impl/OneSixLauncher.java delete mode 100644 libraries/launcher/org/multimc/onesix/OneSixLauncher.java rename libraries/launcher/org/multimc/{ => utils}/ParamBucket.java (68%) create mode 100644 libraries/launcher/org/multimc/utils/Utils.java diff --git a/libraries/launcher/CMakeLists.txt b/libraries/launcher/CMakeLists.txt index 0eccae8be..e01494829 100644 --- a/libraries/launcher/CMakeLists.txt +++ b/libraries/launcher/CMakeLists.txt @@ -9,12 +9,13 @@ set(CMAKE_JAVA_COMPILE_FLAGS -target 8 -source 8 -Xlint:deprecation -Xlint:unche set(SRC org/multimc/EntryPoint.java org/multimc/Launcher.java - org/multimc/LegacyFrame.java - org/multimc/NotFoundException.java - org/multimc/ParamBucket.java - org/multimc/ParseException.java - org/multimc/Utils.java - org/multimc/onesix/OneSixLauncher.java + org/multimc/LauncherFactory.java + org/multimc/impl/OneSixLauncher.java + org/multimc/applet/LegacyFrame.java + org/multimc/exception/ParameterNotFoundException.java + org/multimc/exception/ParseException.java + org/multimc/utils/ParamBucket.java + org/multimc/utils/Utils.java net/minecraft/Launcher.java ) add_jar(NewLaunch ${SRC}) diff --git a/libraries/launcher/net/minecraft/Launcher.java b/libraries/launcher/net/minecraft/Launcher.java index b6b0a574a..042010474 100644 --- a/libraries/launcher/net/minecraft/Launcher.java +++ b/libraries/launcher/net/minecraft/Launcher.java @@ -16,29 +16,28 @@ package net.minecraft; -import java.util.TreeMap; -import java.util.Map; -import java.net.URL; -import java.awt.Dimension; -import java.awt.BorderLayout; -import java.awt.Graphics; import java.applet.Applet; import java.applet.AppletStub; +import java.awt.*; import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; +import java.util.TreeMap; + +public class Launcher extends Applet implements AppletStub { + + private final Map params = new TreeMap<>(); + + private boolean active = false; -public class Launcher extends Applet implements AppletStub -{ private Applet wrappedApplet; private URL documentBase; - private boolean active = false; - private final Map params; - - public Launcher(Applet applet, URL documentBase) - { - params = new TreeMap(); + public Launcher(Applet applet, URL documentBase) { this.setLayout(new BorderLayout()); + this.add(applet, "Center"); + this.wrappedApplet = applet; this.documentBase = documentBase; } @@ -48,8 +47,7 @@ public class Launcher extends Applet implements AppletStub params.put(name, value); } - public void replace(Applet applet) - { + public void replace(Applet applet) { this.wrappedApplet = applet; applet.setStub(this); @@ -65,67 +63,60 @@ public class Launcher extends Applet implements AppletStub } @Override - public String getParameter(String name) - { + public String getParameter(String name) { String param = params.get(name); + if (param != null) return param; - try - { + + try { return super.getParameter(name); - } catch (Exception ignore){} + } catch (Exception ignore) {} + return null; } @Override - public boolean isActive() - { + public boolean isActive() { return active; } @Override - public void appletResize(int width, int height) - { + public void appletResize(int width, int height) { wrappedApplet.resize(width, height); } @Override - public void resize(int width, int height) - { + public void resize(int width, int height) { wrappedApplet.resize(width, height); } @Override - public void resize(Dimension d) - { + public void resize(Dimension d) { wrappedApplet.resize(d); } @Override - public void init() - { + public void init() { if (wrappedApplet != null) - { wrappedApplet.init(); - } } @Override - public void start() - { + public void start() { wrappedApplet.start(); + active = true; } @Override - public void stop() - { + public void stop() { wrappedApplet.stop(); + active = false; } - public void destroy() - { + public void destroy() { wrappedApplet.destroy(); } @@ -136,34 +127,35 @@ public class Launcher extends Applet implements AppletStub } catch (MalformedURLException e) { e.printStackTrace(); } + return null; } @Override - public URL getDocumentBase() - { + public URL getDocumentBase() { try { // Special case only for Classic versions if (wrappedApplet.getClass().getCanonicalName().startsWith("com.mojang")) { return new URL("http", "www.minecraft.net", 80, "/game/", null); } + return new URL("http://www.minecraft.net/game/"); } catch (MalformedURLException e) { e.printStackTrace(); } + return null; } @Override - public void setVisible(boolean b) - { + public void setVisible(boolean b) { super.setVisible(b); + wrappedApplet.setVisible(b); } - public void update(Graphics paramGraphics) - { - } - public void paint(Graphics paramGraphics) - { - } -} \ No newline at end of file + + public void update(Graphics paramGraphics) {} + + public void paint(Graphics paramGraphics) {} + +} diff --git a/libraries/launcher/org/multimc/EntryPoint.java b/libraries/launcher/org/multimc/EntryPoint.java index b626d0958..be06d1b46 100644 --- a/libraries/launcher/org/multimc/EntryPoint.java +++ b/libraries/launcher/org/multimc/EntryPoint.java @@ -14,7 +14,8 @@ package org.multimc;/* * limitations under the License. */ -import org.multimc.onesix.OneSixLauncher; +import org.multimc.exception.ParseException; +import org.multimc.utils.ParamBucket; import java.io.BufferedReader; import java.io.IOException; @@ -23,31 +24,27 @@ import java.nio.charset.StandardCharsets; import java.util.logging.Level; import java.util.logging.Logger; -public class EntryPoint -{ +public final class EntryPoint { private static final Logger LOGGER = Logger.getLogger("EntryPoint"); private final ParamBucket params = new ParamBucket(); - private org.multimc.Launcher launcher; + private String launcherType; - public static void main(String[] args) - { + public static void main(String[] args) { EntryPoint listener = new EntryPoint(); int retCode = listener.listen(); - if (retCode != 0) - { + if (retCode != 0) { LOGGER.info("Exiting with " + retCode); System.exit(retCode); } } - private Action parseLine(String inData) throws ParseException - { + private Action parseLine(String inData) throws ParseException { String[] tokens = inData.split("\\s+", 2); if (tokens.length == 0) @@ -66,15 +63,9 @@ public class EntryPoint if (tokens.length != 2) throw new ParseException("Expected 2 tokens, got " + tokens.length); - if (tokens[1].equals("onesix")) { - launcher = new OneSixLauncher(); + launcherType = tokens[1]; - LOGGER.info("Using onesix launcher."); - - return Action.Proceed; - } else { - throw new ParseException("Invalid launcher type: " + tokens[1]); - } + return Action.Proceed; } default: { @@ -88,8 +79,7 @@ public class EntryPoint } } - public int listen() - { + public int listen() { Action action = Action.Proceed; try (BufferedReader reader = new BufferedReader(new InputStreamReader( @@ -112,16 +102,31 @@ public class EntryPoint } // Main loop - if (action == Action.Abort) - { + if (action == Action.Abort) { LOGGER.info("Launch aborted by the launcher."); return 1; } - if (launcher != null) - { - return launcher.launch(params); + if (launcherType != null) { + try { + Launcher launcher = + LauncherFactory + .getInstance() + .createLauncher(launcherType, params); + + launcher.launch(); + + return 0; + } catch (IllegalArgumentException e) { + LOGGER.log(Level.SEVERE, "Wrong argument.", e); + + return 1; + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Exception caught from launcher.", e); + + return 1; + } } LOGGER.log(Level.SEVERE, "No valid launcher implementation specified."); diff --git a/libraries/launcher/org/multimc/Launcher.java b/libraries/launcher/org/multimc/Launcher.java index c5e8fbc10..bc0b525eb 100644 --- a/libraries/launcher/org/multimc/Launcher.java +++ b/libraries/launcher/org/multimc/Launcher.java @@ -16,7 +16,8 @@ package org.multimc; -public interface Launcher -{ - int launch(ParamBucket params); +public interface Launcher { + + void launch() throws Exception; + } diff --git a/libraries/launcher/org/multimc/LauncherFactory.java b/libraries/launcher/org/multimc/LauncherFactory.java new file mode 100644 index 000000000..b5d0dd5bd --- /dev/null +++ b/libraries/launcher/org/multimc/LauncherFactory.java @@ -0,0 +1,34 @@ +package org.multimc; + +import org.multimc.impl.OneSixLauncher; +import org.multimc.utils.ParamBucket; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +public final class LauncherFactory { + + private static final LauncherFactory INSTANCE = new LauncherFactory(); + + private final Map> launcherRegistry = new HashMap<>(); + + private LauncherFactory() { + launcherRegistry.put("onesix", OneSixLauncher::new); + } + + public Launcher createLauncher(String name, ParamBucket parameters) { + Function launcherCreator = + launcherRegistry.get(name); + + if (launcherCreator == null) + throw new IllegalArgumentException("Invalid launcher type: " + name); + + return launcherCreator.apply(parameters); + } + + public static LauncherFactory getInstance() { + return INSTANCE; + } + +} diff --git a/libraries/launcher/org/multimc/LegacyFrame.java b/libraries/launcher/org/multimc/LegacyFrame.java deleted file mode 100644 index 985a10e6a..000000000 --- a/libraries/launcher/org/multimc/LegacyFrame.java +++ /dev/null @@ -1,176 +0,0 @@ -package org.multimc;/* - * Copyright 2012-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. - */ - -import net.minecraft.Launcher; - -import javax.imageio.ImageIO; -import java.applet.Applet; -import java.awt.*; -import java.awt.event.WindowEvent; -import java.awt.event.WindowListener; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Scanner; - -public class LegacyFrame extends Frame implements WindowListener -{ - private Launcher appletWrap = null; - public LegacyFrame(String title) - { - super ( title ); - BufferedImage image; - try { - image = ImageIO.read ( new File ( "icon.png" ) ); - setIconImage ( image ); - } catch ( IOException e ) { - e.printStackTrace(); - } - this.addWindowListener ( this ); - } - - public void start ( - Applet mcApplet, - String user, - String session, - int winSizeW, - int winSizeH, - boolean maximize, - String serverAddress, - String serverPort - ) - { - try { - appletWrap = new Launcher( mcApplet, new URL ( "http://www.minecraft.net/game" ) ); - } catch ( MalformedURLException ignored ) {} - - // Implements support for launching in to multiplayer on classic servers using a mpticket - // file generated by an external program and stored in the instance's root folder. - File mpticketFile = null; - Scanner fileReader = null; - try { - mpticketFile = new File(System.getProperty("user.dir") + "/../mpticket").getCanonicalFile(); - fileReader = new Scanner(new FileInputStream(mpticketFile), "ascii"); - String[] mpticketParams = new String[3]; - - for(int i=0;i<3;i++) { - if(fileReader.hasNextLine()) { - mpticketParams[i] = fileReader.nextLine(); - } else { - throw new IllegalArgumentException(); - } - } - - // Assumes parameters are valid and in the correct order - appletWrap.setParameter("server", mpticketParams[0]); - appletWrap.setParameter("port", mpticketParams[1]); - appletWrap.setParameter("mppass", mpticketParams[2]); - - fileReader.close(); - mpticketFile.delete(); - } - catch (FileNotFoundException e) {} - catch (IllegalArgumentException e) { - - fileReader.close(); - File mpticketFileCorrupt = new File(System.getProperty("user.dir") + "/../mpticket.corrupt"); - if(mpticketFileCorrupt.exists()) { - mpticketFileCorrupt.delete(); - } - mpticketFile.renameTo(mpticketFileCorrupt); - - System.err.println("Malformed mpticket file, missing argument."); - e.printStackTrace(System.err); - System.exit(-1); - } - catch (Exception e) { - e.printStackTrace(System.err); - System.exit(-1); - } - - if (serverAddress != null) - { - appletWrap.setParameter("server", serverAddress); - appletWrap.setParameter("port", serverPort); - } - - appletWrap.setParameter ( "username", user ); - appletWrap.setParameter ( "sessionid", session ); - appletWrap.setParameter ( "stand-alone", "true" ); // Show the quit button. - appletWrap.setParameter ( "haspaid", "true" ); // Some old versions need this for world saves to work. - appletWrap.setParameter ( "demo", "false" ); - appletWrap.setParameter ( "fullscreen", "false" ); - mcApplet.setStub(appletWrap); - this.add ( appletWrap ); - appletWrap.setPreferredSize ( new Dimension (winSizeW, winSizeH) ); - this.pack(); - this.setLocationRelativeTo ( null ); - this.setResizable ( true ); - if ( maximize ) { - this.setExtendedState ( MAXIMIZED_BOTH ); - } - validate(); - appletWrap.init(); - appletWrap.start(); - setVisible ( true ); - } - - @Override - public void windowActivated ( WindowEvent e ) {} - - @Override - public void windowClosed ( WindowEvent e ) {} - - @Override - public void windowClosing ( WindowEvent e ) - { - new Thread() { - public void run() { - try { - Thread.sleep ( 30000L ); - } catch ( InterruptedException localInterruptedException ) { - localInterruptedException.printStackTrace(); - } - System.out.println ( "FORCING EXIT!" ); - System.exit ( 0 ); - } - } - .start(); - - if ( appletWrap != null ) { - appletWrap.stop(); - appletWrap.destroy(); - } - // old minecraft versions can hang without this >_< - System.exit ( 0 ); - } - - @Override - public void windowDeactivated ( WindowEvent e ) {} - - @Override - public void windowDeiconified ( WindowEvent e ) {} - - @Override - public void windowIconified ( WindowEvent e ) {} - - @Override - public void windowOpened ( WindowEvent e ) {} -} diff --git a/libraries/launcher/org/multimc/Utils.java b/libraries/launcher/org/multimc/Utils.java deleted file mode 100644 index e48029c25..000000000 --- a/libraries/launcher/org/multimc/Utils.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2012-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. - */ - -package org.multimc; - -import java.io.File; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.List; - -public class Utils -{ - /** - * Combine two parts of a path. - * - * @param path1 - * @param path2 - * @return the paths, combined - */ - public static String combine(String path1, String path2) - { - File file1 = new File(path1); - File file2 = new File(file1, path2); - return file2.getPath(); - } - - /** - * Join a list of strings into a string using a separator! - * - * @param strings the string list to join - * @param separator the glue - * @return the result. - */ - public static String join(List strings, String separator) - { - StringBuilder sb = new StringBuilder(); - String sep = ""; - for (String s : strings) - { - sb.append(sep).append(s); - sep = separator; - } - return sb.toString(); - } - - /** - * Finds a field that looks like a Minecraft base folder in a supplied class - * - * @param mc the class to scan - */ - public static Field getMCPathField(Class mc) - { - Field[] fields = mc.getDeclaredFields(); - - for (Field f : fields) - { - if (f.getType() != File.class) - { - // Has to be File - continue; - } - if (f.getModifiers() != (Modifier.PRIVATE + Modifier.STATIC)) - { - // And Private Static. - continue; - } - return f; - } - return null; - } - -} - diff --git a/libraries/launcher/org/multimc/applet/LegacyFrame.java b/libraries/launcher/org/multimc/applet/LegacyFrame.java new file mode 100644 index 000000000..a5e6c1703 --- /dev/null +++ b/libraries/launcher/org/multimc/applet/LegacyFrame.java @@ -0,0 +1,167 @@ +package org.multimc.applet;/* + * Copyright 2012-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. + */ + +import net.minecraft.Launcher; + +import javax.imageio.ImageIO; +import java.applet.Applet; +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.Scanner; +import java.util.logging.Level; +import java.util.logging.Logger; + +public final class LegacyFrame extends Frame { + + private static final Logger LOGGER = Logger.getLogger("LegacyFrame"); + + private Launcher appletWrap; + + public LegacyFrame(String title) { + super(title); + + try { + setIconImage(ImageIO.read(new File("icon.png"))); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Unable to read Minecraft icon!", e); + } + + this.addWindowListener(new ForceExitHandler()); + } + + public void start ( + Applet mcApplet, + String user, + String session, + int winSizeW, + int winSizeH, + boolean maximize, + String serverAddress, + String serverPort + ) { + try { + appletWrap = new Launcher(mcApplet, new URL("http://www.minecraft.net/game")); + } catch (MalformedURLException ignored) {} + + // Implements support for launching in to multiplayer on classic servers using a mpticket + // file generated by an external program and stored in the instance's root folder. + Path mpticketFile = Paths.get(System.getProperty("user.dir") + "/../mpticket"); + Path mpticketFileCorrupt = Paths.get(System.getProperty("user.dir") + "/../mpticket.corrupt"); + + if (Files.exists(mpticketFile)) { + try (Scanner fileScanner = new Scanner( + Files.newInputStream(mpticketFile), + StandardCharsets.US_ASCII.name() + )) { + String[] mpticketParams = new String[3]; + + for (int i = 0; i < mpticketParams.length; i++) { + if (fileScanner.hasNextLine()) { + mpticketParams[i] = fileScanner.nextLine(); + } else { + Files.move( + mpticketFile, + mpticketFileCorrupt, + StandardCopyOption.REPLACE_EXISTING + ); + + throw new IllegalArgumentException("Mpticket file is corrupted!"); + } + } + + Files.delete(mpticketFile); + + // Assumes parameters are valid and in the correct order + appletWrap.setParameter("server", mpticketParams[0]); + appletWrap.setParameter("port", mpticketParams[1]); + appletWrap.setParameter("mppass", mpticketParams[2]); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Unable to read mpticket file!", e); + } + } + + if (serverAddress != null) { + appletWrap.setParameter("server", serverAddress); + appletWrap.setParameter("port", serverPort); + } + + appletWrap.setParameter("username", user); + appletWrap.setParameter("sessionid", session); + appletWrap.setParameter("stand-alone", "true"); // Show the quit button. + appletWrap.setParameter("haspaid", "true"); // Some old versions need this for world saves to work. + appletWrap.setParameter("demo", "false"); + appletWrap.setParameter("fullscreen", "false"); + + mcApplet.setStub(appletWrap); + + add(appletWrap); + + appletWrap.setPreferredSize(new Dimension(winSizeW, winSizeH)); + + pack(); + + setLocationRelativeTo(null); + setResizable(true); + + if (maximize) + this.setExtendedState(MAXIMIZED_BOTH); + + validate(); + + appletWrap.init(); + appletWrap.start(); + + setVisible(true); + } + + private final class ForceExitHandler extends WindowAdapter { + + @Override + public void windowClosing(WindowEvent e) { + new Thread(() -> { + try { + Thread.sleep(30000L); + } catch (InterruptedException localInterruptedException) { + localInterruptedException.printStackTrace(); + } + + LOGGER.info("Forcing exit!"); + + System.exit(0); + }).start(); + + if (appletWrap != null) { + appletWrap.stop(); + appletWrap.destroy(); + } + + // old minecraft versions can hang without this >_< + System.exit(0); + } + + } + +} diff --git a/libraries/launcher/org/multimc/NotFoundException.java b/libraries/launcher/org/multimc/exception/ParameterNotFoundException.java similarity index 73% rename from libraries/launcher/org/multimc/NotFoundException.java rename to libraries/launcher/org/multimc/exception/ParameterNotFoundException.java index ba12951d6..9edbb8261 100644 --- a/libraries/launcher/org/multimc/NotFoundException.java +++ b/libraries/launcher/org/multimc/exception/ParameterNotFoundException.java @@ -14,8 +14,12 @@ * limitations under the License. */ -package org.multimc; +package org.multimc.exception; + +public final class ParameterNotFoundException extends IllegalArgumentException { + + public ParameterNotFoundException(String key) { + super("Unknown parameter name: " + key); + } -public class NotFoundException extends Exception -{ } diff --git a/libraries/launcher/org/multimc/ParseException.java b/libraries/launcher/org/multimc/exception/ParseException.java similarity index 81% rename from libraries/launcher/org/multimc/ParseException.java rename to libraries/launcher/org/multimc/exception/ParseException.java index 7ea44c1f5..c9a4c8562 100644 --- a/libraries/launcher/org/multimc/ParseException.java +++ b/libraries/launcher/org/multimc/exception/ParseException.java @@ -14,12 +14,16 @@ * limitations under the License. */ -package org.multimc; +package org.multimc.exception; + +public final class ParseException extends IllegalArgumentException { + + public ParseException() { + super(); + } -public class ParseException extends java.lang.Exception -{ - public ParseException() { super(); } public ParseException(String message) { super(message); } + } diff --git a/libraries/launcher/org/multimc/impl/OneSixLauncher.java b/libraries/launcher/org/multimc/impl/OneSixLauncher.java new file mode 100644 index 000000000..d2596a698 --- /dev/null +++ b/libraries/launcher/org/multimc/impl/OneSixLauncher.java @@ -0,0 +1,183 @@ +/* Copyright 2012-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. + */ + +package org.multimc.impl; + +import org.multimc.Launcher; +import org.multimc.applet.LegacyFrame; +import org.multimc.utils.ParamBucket; +import org.multimc.utils.Utils; + +import java.applet.Applet; +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +public final class OneSixLauncher implements Launcher { + + private static final int DEFAULT_WINDOW_WIDTH = 854; + private static final int DEFAULT_WINDOW_HEIGHT = 480; + + private static final Logger LOGGER = Logger.getLogger("OneSixLauncher"); + + // parameters, separated from ParamBucket + private final List mcParams; + private final List traits; + private final String appletClass; + private final String mainClass; + private final String userName, sessionId; + private final String windowTitle; + + // secondary parameters + private final int winSizeW; + private final int winSizeH; + private final boolean maximize; + private final String cwd; + + private final String serverAddress; + private final String serverPort; + + private final ClassLoader classLoader; + + public OneSixLauncher(ParamBucket params) { + classLoader = ClassLoader.getSystemClassLoader(); + + mcParams = params.allSafe("param", Collections.emptyList()); + mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft"); + appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet"); + traits = params.allSafe("traits", Collections.emptyList()); + + userName = params.first("userName"); + sessionId = params.first("sessionId"); + windowTitle = params.firstSafe("windowTitle", "Minecraft"); + + serverAddress = params.firstSafe("serverAddress", null); + serverPort = params.firstSafe("serverPort", null); + + cwd = System.getProperty("user.dir"); + + String windowParams = params.firstSafe("windowParams", "854x480"); + + String[] dimStrings = windowParams.split("x"); + + if (windowParams.equalsIgnoreCase("max")) { + maximize = true; + + winSizeW = DEFAULT_WINDOW_WIDTH; + winSizeH = DEFAULT_WINDOW_HEIGHT; + } else if (dimStrings.length == 2) { + maximize = false; + + winSizeW = Integer.parseInt(dimStrings[0]); + winSizeH = Integer.parseInt(dimStrings[1]); + } else { + throw new IllegalArgumentException("Unexpected window size parameter value: " + windowParams); + } + } + + private void invokeMain(Class mainClass) throws Exception { + Method method = mainClass.getMethod("main", String[].class); + + method.invoke(null, (Object) mcParams.toArray(new String[0])); + } + + private void legacyLaunch() throws Exception { + // Get the Minecraft Class and set the base folder + Class minecraftClass = classLoader.loadClass(mainClass); + + Field baseDirField = Utils.getMinecraftBaseDirField(minecraftClass); + + if (baseDirField == null) { + LOGGER.warning("Could not find Minecraft path field."); + } else { + baseDirField.setAccessible(true); + + baseDirField.set(null, new File(cwd)); + } + + System.setProperty("minecraft.applet.TargetDirectory", cwd); + + if (!traits.contains("noapplet")) { + LOGGER.info("Launching with applet wrapper..."); + + try { + Class mcAppletClass = classLoader.loadClass(appletClass); + + Applet mcApplet = (Applet) mcAppletClass.getConstructor().newInstance(); + + LegacyFrame mcWindow = new LegacyFrame(windowTitle); + + mcWindow.start( + mcApplet, + userName, + sessionId, + winSizeW, + winSizeH, + maximize, + serverAddress, + serverPort + ); + + return; + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Applet wrapper failed: ", e); + + LOGGER.warning("Falling back to using main class."); + } + } + + invokeMain(minecraftClass); + } + + void launchWithMainClass() throws Exception { + // window size, title and state, onesix + + // FIXME: there is no good way to maximize the minecraft window in onesix. + // the following often breaks linux screen setups + // mcparams.add("--fullscreen"); + + if (!maximize) { + mcParams.add("--width"); + mcParams.add(Integer.toString(winSizeW)); + mcParams.add("--height"); + mcParams.add(Integer.toString(winSizeH)); + } + + if (serverAddress != null) { + mcParams.add("--server"); + mcParams.add(serverAddress); + mcParams.add("--port"); + mcParams.add(serverPort); + } + + invokeMain(classLoader.loadClass(mainClass)); + } + + @Override + public void launch() throws Exception { + if (traits.contains("legacyLaunch") || traits.contains("alphaLaunch")) { + // legacy launch uses the applet wrapper + legacyLaunch(); + } else { + // normal launch just calls main() + launchWithMainClass(); + } + } + +} diff --git a/libraries/launcher/org/multimc/onesix/OneSixLauncher.java b/libraries/launcher/org/multimc/onesix/OneSixLauncher.java deleted file mode 100644 index 0058bd43f..000000000 --- a/libraries/launcher/org/multimc/onesix/OneSixLauncher.java +++ /dev/null @@ -1,256 +0,0 @@ -/* Copyright 2012-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. - */ - -package org.multimc.onesix; - -import org.multimc.*; - -import java.applet.Applet; -import java.io.File; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -public class OneSixLauncher implements Launcher -{ - - private static final Logger LOGGER = Logger.getLogger("OneSixLauncher"); - - // parameters, separated from ParamBucket - private List libraries; - private List mcparams; - private List mods; - private List jarmods; - private List coremods; - private List traits; - private String appletClass; - private String mainClass; - private String nativePath; - private String userName, sessionId; - private String windowTitle; - private String windowParams; - - // secondary parameters - private int winSizeW; - private int winSizeH; - private boolean maximize; - private String cwd; - - private String serverAddress; - private String serverPort; - - // the much abused system classloader, for convenience (for further abuse) - private ClassLoader cl; - - private void processParams(ParamBucket params) throws NotFoundException - { - libraries = params.all("cp"); - mcparams = params.allSafe("param", new ArrayList() ); - mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft"); - appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet"); - traits = params.allSafe("traits", new ArrayList()); - nativePath = params.first("natives"); - - userName = params.first("userName"); - sessionId = params.first("sessionId"); - windowTitle = params.firstSafe("windowTitle", "Minecraft"); - windowParams = params.firstSafe("windowParams", "854x480"); - - serverAddress = params.firstSafe("serverAddress", null); - serverPort = params.firstSafe("serverPort", null); - - cwd = System.getProperty("user.dir"); - - winSizeW = 854; - winSizeH = 480; - maximize = false; - - String[] dimStrings = windowParams.split("x"); - - if (windowParams.equalsIgnoreCase("max")) - { - maximize = true; - } - else if (dimStrings.length == 2) - { - try - { - winSizeW = Integer.parseInt(dimStrings[0]); - winSizeH = Integer.parseInt(dimStrings[1]); - } catch (NumberFormatException ignored) {} - } - } - - int legacyLaunch() - { - // Get the Minecraft Class and set the base folder - Class mc; - try - { - mc = cl.loadClass(mainClass); - - Field f = Utils.getMCPathField(mc); - - if (f == null) - { - LOGGER.warning("Could not find Minecraft path field."); - } - else - { - f.setAccessible(true); - f.set(null, new File(cwd)); - } - } catch (Exception e) - { - LOGGER.log( - Level.SEVERE, - "Could not set base folder. Failed to find/access Minecraft main class:", - e - ); - - return -1; - } - - System.setProperty("minecraft.applet.TargetDirectory", cwd); - - if(!traits.contains("noapplet")) - { - LOGGER.info("Launching with applet wrapper..."); - try - { - Class MCAppletClass = cl.loadClass(appletClass); - Applet mcappl = (Applet) MCAppletClass.newInstance(); - LegacyFrame mcWindow = new LegacyFrame(windowTitle); - mcWindow.start(mcappl, userName, sessionId, winSizeW, winSizeH, maximize, serverAddress, serverPort); - return 0; - } catch (Exception e) - { - LOGGER.log(Level.SEVERE, "Applet wrapper failed:", e); - - LOGGER.warning("Falling back to using main class."); - } - } - - // init params for the main method to chomp on. - String[] paramsArray = mcparams.toArray(new String[mcparams.size()]); - try - { - mc.getMethod("main", String[].class).invoke(null, (Object) paramsArray); - return 0; - } catch (Exception e) - { - LOGGER.log(Level.SEVERE, "Failed to invoke the Minecraft main class:", e); - - return -1; - } - } - - int launchWithMainClass() - { - // window size, title and state, onesix - if (maximize) - { - // FIXME: there is no good way to maximize the minecraft window in onesix. - // the following often breaks linux screen setups - // mcparams.add("--fullscreen"); - } - else - { - mcparams.add("--width"); - mcparams.add(Integer.toString(winSizeW)); - mcparams.add("--height"); - mcparams.add(Integer.toString(winSizeH)); - } - - if (serverAddress != null) - { - mcparams.add("--server"); - mcparams.add(serverAddress); - mcparams.add("--port"); - mcparams.add(serverPort); - } - - // Get the Minecraft Class. - Class mc; - try - { - mc = cl.loadClass(mainClass); - } catch (ClassNotFoundException e) - { - LOGGER.log(Level.SEVERE, "Failed to find Minecraft main class:", e); - - return -1; - } - - // get the main method. - Method meth; - try - { - meth = mc.getMethod("main", String[].class); - } catch (NoSuchMethodException e) - { - LOGGER.log(Level.SEVERE, "Failed to acquire the main method:", e); - - return -1; - } - - // init params for the main method to chomp on. - String[] paramsArray = mcparams.toArray(new String[mcparams.size()]); - try - { - // static method doesn't have an instance - meth.invoke(null, (Object) paramsArray); - } catch (Exception e) - { - LOGGER.log(Level.SEVERE, "Failed to start Minecraft:", e); - - return -1; - } - return 0; - } - - @Override - public int launch(ParamBucket params) - { - // get and process the launch script params - try - { - processParams(params); - } catch (NotFoundException e) - { - LOGGER.log(Level.SEVERE, "Not enough arguments!"); - - return -1; - } - - // grab the system classloader and ... - cl = ClassLoader.getSystemClassLoader(); - - if (traits.contains("legacyLaunch") || traits.contains("alphaLaunch") ) - { - // legacy launch uses the applet wrapper - return legacyLaunch(); - } - else - { - // normal launch just calls main() - return launchWithMainClass(); - } - } - -} diff --git a/libraries/launcher/org/multimc/ParamBucket.java b/libraries/launcher/org/multimc/utils/ParamBucket.java similarity index 68% rename from libraries/launcher/org/multimc/ParamBucket.java rename to libraries/launcher/org/multimc/utils/ParamBucket.java index 8ff03ddc7..26ff8eef2 100644 --- a/libraries/launcher/org/multimc/ParamBucket.java +++ b/libraries/launcher/org/multimc/utils/ParamBucket.java @@ -14,36 +14,34 @@ * limitations under the License. */ -package org.multimc; +package org.multimc.utils; + +import org.multimc.exception.ParameterNotFoundException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -public class ParamBucket -{ +public final class ParamBucket { private final Map> paramsMap = new HashMap<>(); - public void add(String key, String value) - { + public void add(String key, String value) { paramsMap.computeIfAbsent(key, k -> new ArrayList<>()) .add(value); } - public List all(String key) throws NotFoundException - { + public List all(String key) throws ParameterNotFoundException { List params = paramsMap.get(key); if (params == null) - throw new NotFoundException(); + throw new ParameterNotFoundException(key); return params; } - public List allSafe(String key, List def) - { + public List allSafe(String key, List def) { List params = paramsMap.get(key); if (params == null || params.isEmpty()) @@ -52,23 +50,16 @@ public class ParamBucket return params; } - public List allSafe(String key) - { - return allSafe(key, new ArrayList<>()); - } - - public String first(String key) throws NotFoundException - { + public String first(String key) throws ParameterNotFoundException { List list = all(key); if (list.isEmpty()) - throw new NotFoundException(); + throw new ParameterNotFoundException(key); return list.get(0); } - public String firstSafe(String key, String def) - { + public String firstSafe(String key, String def) { List params = paramsMap.get(key); if (params == null || params.isEmpty()) @@ -77,9 +68,4 @@ public class ParamBucket return params.get(0); } - public String firstSafe(String key) - { - return firstSafe(key, ""); - } - } diff --git a/libraries/launcher/org/multimc/utils/Utils.java b/libraries/launcher/org/multimc/utils/Utils.java new file mode 100644 index 000000000..416eff26b --- /dev/null +++ b/libraries/launcher/org/multimc/utils/Utils.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-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. + */ + +package org.multimc.utils; + +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +public final class Utils { + + private Utils() {} + + /** + * Finds a field that looks like a Minecraft base folder in a supplied class + * + * @param clazz the class to scan + */ + public static Field getMinecraftBaseDirField(Class clazz) { + for (Field f : clazz.getDeclaredFields()) { + // Has to be File + if (f.getType() != File.class) + continue; + + // And Private Static. + if (!Modifier.isStatic(f.getModifiers()) || !Modifier.isPrivate(f.getModifiers())) + continue; + + return f; + } + + return null; + } + +} + From eeb5297284494c03f3b8e3927c5ed6cc3ca09a41 Mon Sep 17 00:00:00 2001 From: icelimetea Date: Tue, 3 May 2022 00:25:26 +0100 Subject: [PATCH 002/308] Use only Java 7 features (in order to deal with #515) --- .../launcher/org/multimc/LauncherFactory.java | 23 +++++++++++++------ .../org/multimc/applet/LegacyFrame.java | 21 +++++++++-------- .../org/multimc/impl/OneSixLauncher.java | 4 ++-- .../org/multimc/utils/ParamBucket.java | 11 +++++++-- 4 files changed, 39 insertions(+), 20 deletions(-) diff --git a/libraries/launcher/org/multimc/LauncherFactory.java b/libraries/launcher/org/multimc/LauncherFactory.java index b5d0dd5bd..2b3700582 100644 --- a/libraries/launcher/org/multimc/LauncherFactory.java +++ b/libraries/launcher/org/multimc/LauncherFactory.java @@ -5,30 +5,39 @@ import org.multimc.utils.ParamBucket; import java.util.HashMap; import java.util.Map; -import java.util.function.Function; public final class LauncherFactory { private static final LauncherFactory INSTANCE = new LauncherFactory(); - private final Map> launcherRegistry = new HashMap<>(); + private final Map launcherRegistry = new HashMap<>(); private LauncherFactory() { - launcherRegistry.put("onesix", OneSixLauncher::new); + launcherRegistry.put("onesix", new LauncherProvider() { + @Override + public Launcher provide(ParamBucket parameters) { + return new OneSixLauncher(parameters); + } + }); } public Launcher createLauncher(String name, ParamBucket parameters) { - Function launcherCreator = - launcherRegistry.get(name); + LauncherProvider launcherProvider = launcherRegistry.get(name); - if (launcherCreator == null) + if (launcherProvider == null) throw new IllegalArgumentException("Invalid launcher type: " + name); - return launcherCreator.apply(parameters); + return launcherProvider.provide(parameters); } public static LauncherFactory getInstance() { return INSTANCE; } + public interface LauncherProvider { + + Launcher provide(ParamBucket parameters); + + } + } diff --git a/libraries/launcher/org/multimc/applet/LegacyFrame.java b/libraries/launcher/org/multimc/applet/LegacyFrame.java index a5e6c1703..d250ce268 100644 --- a/libraries/launcher/org/multimc/applet/LegacyFrame.java +++ b/libraries/launcher/org/multimc/applet/LegacyFrame.java @@ -141,16 +141,19 @@ public final class LegacyFrame extends Frame { @Override public void windowClosing(WindowEvent e) { - new Thread(() -> { - try { - Thread.sleep(30000L); - } catch (InterruptedException localInterruptedException) { - localInterruptedException.printStackTrace(); + new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(30000L); + } catch (InterruptedException localInterruptedException) { + localInterruptedException.printStackTrace(); + } + + LOGGER.info("Forcing exit!"); + + System.exit(0); } - - LOGGER.info("Forcing exit!"); - - System.exit(0); }).start(); if (appletWrap != null) { diff --git a/libraries/launcher/org/multimc/impl/OneSixLauncher.java b/libraries/launcher/org/multimc/impl/OneSixLauncher.java index d2596a698..19253dc04 100644 --- a/libraries/launcher/org/multimc/impl/OneSixLauncher.java +++ b/libraries/launcher/org/multimc/impl/OneSixLauncher.java @@ -58,10 +58,10 @@ public final class OneSixLauncher implements Launcher { public OneSixLauncher(ParamBucket params) { classLoader = ClassLoader.getSystemClassLoader(); - mcParams = params.allSafe("param", Collections.emptyList()); + mcParams = params.allSafe("param", Collections.emptyList()); mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft"); appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet"); - traits = params.allSafe("traits", Collections.emptyList()); + traits = params.allSafe("traits", Collections.emptyList()); userName = params.first("userName"); sessionId = params.first("sessionId"); diff --git a/libraries/launcher/org/multimc/utils/ParamBucket.java b/libraries/launcher/org/multimc/utils/ParamBucket.java index 26ff8eef2..5dbb8775e 100644 --- a/libraries/launcher/org/multimc/utils/ParamBucket.java +++ b/libraries/launcher/org/multimc/utils/ParamBucket.java @@ -28,8 +28,15 @@ public final class ParamBucket { private final Map> paramsMap = new HashMap<>(); public void add(String key, String value) { - paramsMap.computeIfAbsent(key, k -> new ArrayList<>()) - .add(value); + List params = paramsMap.get(key); + + if (params == null) { + params = new ArrayList<>(); + + paramsMap.put(key, params); + } + + params.add(value); } public List all(String key) throws ParameterNotFoundException { From 4fdb21b41400e789ca44a5cc1079469eb2508370 Mon Sep 17 00:00:00 2001 From: icelimetea Date: Tue, 3 May 2022 00:27:14 +0100 Subject: [PATCH 003/308] Compile with Java 7 in mind --- libraries/launcher/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/launcher/CMakeLists.txt b/libraries/launcher/CMakeLists.txt index e01494829..0a0a541c4 100644 --- a/libraries/launcher/CMakeLists.txt +++ b/libraries/launcher/CMakeLists.txt @@ -4,7 +4,7 @@ find_package(Java 1.7 REQUIRED COMPONENTS Development) include(UseJava) set(CMAKE_JAVA_JAR_ENTRY_POINT org.multimc.EntryPoint) -set(CMAKE_JAVA_COMPILE_FLAGS -target 8 -source 8 -Xlint:deprecation -Xlint:unchecked) +set(CMAKE_JAVA_COMPILE_FLAGS -target 7 -source 7 -Xlint:deprecation -Xlint:unchecked) set(SRC org/multimc/EntryPoint.java From 860a7af6796785898926bcf10b034545caa5401b Mon Sep 17 00:00:00 2001 From: icelimetea Date: Tue, 3 May 2022 00:53:22 +0100 Subject: [PATCH 004/308] Fix method access modifier --- libraries/launcher/org/multimc/impl/OneSixLauncher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/launcher/org/multimc/impl/OneSixLauncher.java b/libraries/launcher/org/multimc/impl/OneSixLauncher.java index 19253dc04..a87b116c7 100644 --- a/libraries/launcher/org/multimc/impl/OneSixLauncher.java +++ b/libraries/launcher/org/multimc/impl/OneSixLauncher.java @@ -145,7 +145,7 @@ public final class OneSixLauncher implements Launcher { invokeMain(minecraftClass); } - void launchWithMainClass() throws Exception { + private void launchWithMainClass() throws Exception { // window size, title and state, onesix // FIXME: there is no good way to maximize the minecraft window in onesix. From 9a87ae575ef58bb86d4bbd7bdb8ab7e026ad9a33 Mon Sep 17 00:00:00 2001 From: icelimetea Date: Tue, 3 May 2022 03:19:26 +0100 Subject: [PATCH 005/308] More minor fixes --- libraries/launcher/CMakeLists.txt | 2 +- .../launcher/net/minecraft/Launcher.java | 30 ++++------------ .../launcher/org/multimc/EntryPoint.java | 4 +-- .../launcher/org/multimc/LauncherFactory.java | 8 ++--- .../org/multimc/applet/LegacyFrame.java | 25 +++++++------ .../org/multimc/exception/ParseException.java | 4 --- .../org/multimc/impl/OneSixLauncher.java | 36 +++++++++++-------- .../{ParamBucket.java => Parameters.java} | 2 +- 8 files changed, 47 insertions(+), 64 deletions(-) rename libraries/launcher/org/multimc/utils/{ParamBucket.java => Parameters.java} (98%) diff --git a/libraries/launcher/CMakeLists.txt b/libraries/launcher/CMakeLists.txt index 0a0a541c4..2c859499d 100644 --- a/libraries/launcher/CMakeLists.txt +++ b/libraries/launcher/CMakeLists.txt @@ -14,7 +14,7 @@ set(SRC org/multimc/applet/LegacyFrame.java org/multimc/exception/ParameterNotFoundException.java org/multimc/exception/ParseException.java - org/multimc/utils/ParamBucket.java + org/multimc/utils/Parameters.java org/multimc/utils/Utils.java net/minecraft/Launcher.java ) diff --git a/libraries/launcher/net/minecraft/Launcher.java b/libraries/launcher/net/minecraft/Launcher.java index 042010474..265fa66ac 100644 --- a/libraries/launcher/net/minecraft/Launcher.java +++ b/libraries/launcher/net/minecraft/Launcher.java @@ -24,22 +24,20 @@ import java.net.URL; import java.util.Map; import java.util.TreeMap; -public class Launcher extends Applet implements AppletStub { +public final class Launcher extends Applet implements AppletStub { private final Map params = new TreeMap<>(); + private final Applet wrappedApplet; + private boolean active = false; - private Applet wrappedApplet; - private URL documentBase; - - public Launcher(Applet applet, URL documentBase) { + public Launcher(Applet applet) { this.setLayout(new BorderLayout()); this.add(applet, "Center"); this.wrappedApplet = applet; - this.documentBase = documentBase; } public void setParameter(String name, String value) @@ -47,21 +45,6 @@ public class Launcher extends Applet implements AppletStub { params.put(name, 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(); - } - @Override public String getParameter(String name) { String param = params.get(name); @@ -135,9 +118,8 @@ public class Launcher extends Applet implements AppletStub { public URL getDocumentBase() { try { // Special case only for Classic versions - if (wrappedApplet.getClass().getCanonicalName().startsWith("com.mojang")) { - return new URL("http", "www.minecraft.net", 80, "/game/", null); - } + 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) { diff --git a/libraries/launcher/org/multimc/EntryPoint.java b/libraries/launcher/org/multimc/EntryPoint.java index be06d1b46..416f21890 100644 --- a/libraries/launcher/org/multimc/EntryPoint.java +++ b/libraries/launcher/org/multimc/EntryPoint.java @@ -15,7 +15,7 @@ package org.multimc;/* */ import org.multimc.exception.ParseException; -import org.multimc.utils.ParamBucket; +import org.multimc.utils.Parameters; import java.io.BufferedReader; import java.io.IOException; @@ -28,7 +28,7 @@ public final class EntryPoint { private static final Logger LOGGER = Logger.getLogger("EntryPoint"); - private final ParamBucket params = new ParamBucket(); + private final Parameters params = new Parameters(); private String launcherType; diff --git a/libraries/launcher/org/multimc/LauncherFactory.java b/libraries/launcher/org/multimc/LauncherFactory.java index 2b3700582..17e0d9058 100644 --- a/libraries/launcher/org/multimc/LauncherFactory.java +++ b/libraries/launcher/org/multimc/LauncherFactory.java @@ -1,7 +1,7 @@ package org.multimc; import org.multimc.impl.OneSixLauncher; -import org.multimc.utils.ParamBucket; +import org.multimc.utils.Parameters; import java.util.HashMap; import java.util.Map; @@ -15,13 +15,13 @@ public final class LauncherFactory { private LauncherFactory() { launcherRegistry.put("onesix", new LauncherProvider() { @Override - public Launcher provide(ParamBucket parameters) { + public Launcher provide(Parameters parameters) { return new OneSixLauncher(parameters); } }); } - public Launcher createLauncher(String name, ParamBucket parameters) { + public Launcher createLauncher(String name, Parameters parameters) { LauncherProvider launcherProvider = launcherRegistry.get(name); if (launcherProvider == null) @@ -36,7 +36,7 @@ public final class LauncherFactory { public interface LauncherProvider { - Launcher provide(ParamBucket parameters); + Launcher provide(Parameters parameters); } diff --git a/libraries/launcher/org/multimc/applet/LegacyFrame.java b/libraries/launcher/org/multimc/applet/LegacyFrame.java index d250ce268..c50995f67 100644 --- a/libraries/launcher/org/multimc/applet/LegacyFrame.java +++ b/libraries/launcher/org/multimc/applet/LegacyFrame.java @@ -23,8 +23,6 @@ import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -38,11 +36,15 @@ public final class LegacyFrame extends Frame { private static final Logger LOGGER = Logger.getLogger("LegacyFrame"); - private Launcher appletWrap; + private final Launcher appletWrap; - public LegacyFrame(String title) { + public LegacyFrame(String title, Applet mcApplet) { super(title); + appletWrap = new Launcher(mcApplet); + + mcApplet.setStub(appletWrap); + try { setIconImage(ImageIO.read(new File("icon.png"))); } catch (IOException e) { @@ -53,7 +55,6 @@ public final class LegacyFrame extends Frame { } public void start ( - Applet mcApplet, String user, String session, int winSizeW, @@ -62,14 +63,14 @@ public final class LegacyFrame extends Frame { String serverAddress, String serverPort ) { - try { - appletWrap = new Launcher(mcApplet, new URL("http://www.minecraft.net/game")); - } catch (MalformedURLException ignored) {} - // Implements support for launching in to multiplayer on classic servers using a mpticket // file generated by an external program and stored in the instance's root folder. - Path mpticketFile = Paths.get(System.getProperty("user.dir") + "/../mpticket"); - Path mpticketFileCorrupt = Paths.get(System.getProperty("user.dir") + "/../mpticket.corrupt"); + + Path mpticketFile = + Paths.get(System.getProperty("user.dir"), "..", "mpticket"); + + Path mpticketFileCorrupt = + Paths.get(System.getProperty("user.dir"), "..", "mpticket.corrupt"); if (Files.exists(mpticketFile)) { try (Scanner fileScanner = new Scanner( @@ -115,8 +116,6 @@ public final class LegacyFrame extends Frame { appletWrap.setParameter("demo", "false"); appletWrap.setParameter("fullscreen", "false"); - mcApplet.setStub(appletWrap); - add(appletWrap); appletWrap.setPreferredSize(new Dimension(winSizeW, winSizeH)); diff --git a/libraries/launcher/org/multimc/exception/ParseException.java b/libraries/launcher/org/multimc/exception/ParseException.java index c9a4c8562..848b395de 100644 --- a/libraries/launcher/org/multimc/exception/ParseException.java +++ b/libraries/launcher/org/multimc/exception/ParseException.java @@ -18,10 +18,6 @@ package org.multimc.exception; public final class ParseException extends IllegalArgumentException { - public ParseException() { - super(); - } - public ParseException(String message) { super(message); } diff --git a/libraries/launcher/org/multimc/impl/OneSixLauncher.java b/libraries/launcher/org/multimc/impl/OneSixLauncher.java index a87b116c7..b981e4ff4 100644 --- a/libraries/launcher/org/multimc/impl/OneSixLauncher.java +++ b/libraries/launcher/org/multimc/impl/OneSixLauncher.java @@ -17,7 +17,7 @@ package org.multimc.impl; import org.multimc.Launcher; import org.multimc.applet.LegacyFrame; -import org.multimc.utils.ParamBucket; +import org.multimc.utils.Parameters; import org.multimc.utils.Utils; import java.applet.Applet; @@ -55,7 +55,7 @@ public final class OneSixLauncher implements Launcher { private final ClassLoader classLoader; - public OneSixLauncher(ParamBucket params) { + public OneSixLauncher(Parameters params) { classLoader = ClassLoader.getSystemClassLoader(); mcParams = params.allSafe("param", Collections.emptyList()); @@ -72,22 +72,29 @@ public final class OneSixLauncher implements Launcher { cwd = System.getProperty("user.dir"); - String windowParams = params.firstSafe("windowParams", "854x480"); + String windowParams = params.firstSafe("windowParams", null); - String[] dimStrings = windowParams.split("x"); + if (windowParams != null) { + String[] dimStrings = windowParams.split("x"); - if (windowParams.equalsIgnoreCase("max")) { - maximize = true; + if (windowParams.equalsIgnoreCase("max")) { + maximize = true; + + winSizeW = DEFAULT_WINDOW_WIDTH; + winSizeH = DEFAULT_WINDOW_HEIGHT; + } else if (dimStrings.length == 2) { + maximize = false; + + winSizeW = Integer.parseInt(dimStrings[0]); + winSizeH = Integer.parseInt(dimStrings[1]); + } else { + throw new IllegalArgumentException("Unexpected window size parameter value: " + windowParams); + } + } else { + maximize = false; winSizeW = DEFAULT_WINDOW_WIDTH; winSizeH = DEFAULT_WINDOW_HEIGHT; - } else if (dimStrings.length == 2) { - maximize = false; - - winSizeW = Integer.parseInt(dimStrings[0]); - winSizeH = Integer.parseInt(dimStrings[1]); - } else { - throw new IllegalArgumentException("Unexpected window size parameter value: " + windowParams); } } @@ -121,10 +128,9 @@ public final class OneSixLauncher implements Launcher { Applet mcApplet = (Applet) mcAppletClass.getConstructor().newInstance(); - LegacyFrame mcWindow = new LegacyFrame(windowTitle); + LegacyFrame mcWindow = new LegacyFrame(windowTitle, mcApplet); mcWindow.start( - mcApplet, userName, sessionId, winSizeW, diff --git a/libraries/launcher/org/multimc/utils/ParamBucket.java b/libraries/launcher/org/multimc/utils/Parameters.java similarity index 98% rename from libraries/launcher/org/multimc/utils/ParamBucket.java rename to libraries/launcher/org/multimc/utils/Parameters.java index 5dbb8775e..7be790c29 100644 --- a/libraries/launcher/org/multimc/utils/ParamBucket.java +++ b/libraries/launcher/org/multimc/utils/Parameters.java @@ -23,7 +23,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -public final class ParamBucket { +public final class Parameters { private final Map> paramsMap = new HashMap<>(); From dcc41ef885cbb2a823313a95e48d069c63589a42 Mon Sep 17 00:00:00 2001 From: icelimetea Date: Thu, 5 May 2022 07:14:32 +0100 Subject: [PATCH 006/308] Improve mpticket file parsing code --- .../org/multimc/applet/LegacyFrame.java | 42 +++++++------------ 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/libraries/launcher/org/multimc/applet/LegacyFrame.java b/libraries/launcher/org/multimc/applet/LegacyFrame.java index c50995f67..e3bd5047a 100644 --- a/libraries/launcher/org/multimc/applet/LegacyFrame.java +++ b/libraries/launcher/org/multimc/applet/LegacyFrame.java @@ -28,7 +28,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; -import java.util.Scanner; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -51,7 +51,7 @@ public final class LegacyFrame extends Frame { LOGGER.log(Level.WARNING, "Unable to read Minecraft icon!", e); } - this.addWindowListener(new ForceExitHandler()); + addWindowListener(new ForceExitHandler()); } public void start ( @@ -73,34 +73,24 @@ public final class LegacyFrame extends Frame { Paths.get(System.getProperty("user.dir"), "..", "mpticket.corrupt"); if (Files.exists(mpticketFile)) { - try (Scanner fileScanner = new Scanner( - Files.newInputStream(mpticketFile), - StandardCharsets.US_ASCII.name() - )) { - String[] mpticketParams = new String[3]; + try { + List lines = Files.readAllLines(mpticketFile, StandardCharsets.UTF_8); - for (int i = 0; i < mpticketParams.length; i++) { - if (fileScanner.hasNextLine()) { - mpticketParams[i] = fileScanner.nextLine(); - } else { - Files.move( - mpticketFile, - mpticketFileCorrupt, - StandardCopyOption.REPLACE_EXISTING - ); + if (lines.size() != 3) { + Files.move( + mpticketFile, + mpticketFileCorrupt, + StandardCopyOption.REPLACE_EXISTING + ); - throw new IllegalArgumentException("Mpticket file is corrupted!"); - } + LOGGER.warning("Mpticket file is corrupted!"); + } else { + appletWrap.setParameter("server", lines.get(0)); + appletWrap.setParameter("port", lines.get(1)); + appletWrap.setParameter("mppass", lines.get(2)); } - - Files.delete(mpticketFile); - - // Assumes parameters are valid and in the correct order - appletWrap.setParameter("server", mpticketParams[0]); - appletWrap.setParameter("port", mpticketParams[1]); - appletWrap.setParameter("mppass", mpticketParams[2]); } catch (IOException e) { - LOGGER.log(Level.WARNING, "Unable to read mpticket file!", e); + LOGGER.log(Level.WARNING, "Unable to red mpticket file!", e); } } From 6bffa060637e3620739344925a4681ec494a725b Mon Sep 17 00:00:00 2001 From: icelimetea Date: Thu, 5 May 2022 07:16:16 +0100 Subject: [PATCH 007/308] Fix typo --- libraries/launcher/org/multimc/applet/LegacyFrame.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/launcher/org/multimc/applet/LegacyFrame.java b/libraries/launcher/org/multimc/applet/LegacyFrame.java index e3bd5047a..0283f92cc 100644 --- a/libraries/launcher/org/multimc/applet/LegacyFrame.java +++ b/libraries/launcher/org/multimc/applet/LegacyFrame.java @@ -85,12 +85,13 @@ public final class LegacyFrame extends Frame { LOGGER.warning("Mpticket file is corrupted!"); } else { + // Assumes parameters are valid and in the correct order appletWrap.setParameter("server", lines.get(0)); appletWrap.setParameter("port", lines.get(1)); appletWrap.setParameter("mppass", lines.get(2)); } } catch (IOException e) { - LOGGER.log(Level.WARNING, "Unable to red mpticket file!", e); + LOGGER.log(Level.WARNING, "Unable to read mpticket file!", e); } } From 113528e1f299de951a7223df033bbf390095dba3 Mon Sep 17 00:00:00 2001 From: icelimetea Date: Thu, 5 May 2022 07:20:33 +0100 Subject: [PATCH 008/308] Make line count check more lenient --- libraries/launcher/org/multimc/applet/LegacyFrame.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/launcher/org/multimc/applet/LegacyFrame.java b/libraries/launcher/org/multimc/applet/LegacyFrame.java index 0283f92cc..f82cb6057 100644 --- a/libraries/launcher/org/multimc/applet/LegacyFrame.java +++ b/libraries/launcher/org/multimc/applet/LegacyFrame.java @@ -76,7 +76,7 @@ public final class LegacyFrame extends Frame { try { List lines = Files.readAllLines(mpticketFile, StandardCharsets.UTF_8); - if (lines.size() != 3) { + if (lines.size() < 3) { Files.move( mpticketFile, mpticketFileCorrupt, From 8c8eabf7ac1920b47792b26790f3646cb6693ec0 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 21 Apr 2022 22:12:14 -0300 Subject: [PATCH 009/308] refactor: organize a little more the code in launcher/net/ This also reduces some code duplication by using some Task logic in NetAction. --- launcher/InstanceImportTask.cpp | 14 +- launcher/minecraft/AssetsUtils.cpp | 2 +- launcher/net/ByteArraySink.h | 67 +++-- launcher/net/Download.cpp | 60 ++--- launcher/net/Download.h | 14 +- launcher/net/FileSink.cpp | 50 ++-- launcher/net/FileSink.h | 36 +-- launcher/net/MetaCacheSink.cpp | 22 +- launcher/net/MetaCacheSink.h | 27 +- launcher/net/NetAction.h | 127 ++++----- launcher/net/NetJob.cpp | 274 ++++++++++---------- launcher/net/NetJob.h | 109 ++++---- launcher/net/Sink.h | 54 ++-- launcher/screenshots/ImgurAlbumCreation.cpp | 15 +- launcher/screenshots/ImgurAlbumCreation.h | 12 +- launcher/screenshots/ImgurUpload.cpp | 13 +- launcher/screenshots/ImgurUpload.h | 2 +- launcher/tasks/Task.h | 4 +- launcher/translations/TranslationsModel.cpp | 2 +- 19 files changed, 435 insertions(+), 469 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 1a13c9973..fc3432c19 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -14,27 +14,25 @@ */ #include "InstanceImportTask.h" +#include +#include "Application.h" #include "BaseInstance.h" #include "FileSystem.h" -#include "Application.h" #include "MMCZip.h" #include "NullInstance.h" -#include "settings/INISettingsObject.h" +#include "icons/IconList.h" #include "icons/IconUtils.h" -#include +#include "settings/INISettingsObject.h" // FIXME: this does not belong here, it's Minecraft/Flame specific +#include +#include "Json.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "modplatform/flame/FileResolvingTask.h" #include "modplatform/flame/PackManifest.h" -#include "Json.h" -#include #include "modplatform/technic/TechnicPackProcessor.h" -#include "icons/IconList.h" -#include "Application.h" - InstanceImportTask::InstanceImportTask(const QUrl sourceUrl) { m_sourceUrl = sourceUrl; diff --git a/launcher/minecraft/AssetsUtils.cpp b/launcher/minecraft/AssetsUtils.cpp index 7290aeb4c..281f730f5 100644 --- a/launcher/minecraft/AssetsUtils.cpp +++ b/launcher/minecraft/AssetsUtils.cpp @@ -297,7 +297,7 @@ NetAction::Ptr AssetObject::getDownloadAction() auto rawHash = QByteArray::fromHex(hash.toLatin1()); objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash)); } - objectDL->m_total_progress = size; + objectDL->setProgress(objectDL->getProgress(), size); return objectDL; } return nullptr; diff --git a/launcher/net/ByteArraySink.h b/launcher/net/ByteArraySink.h index 20e6764c0..75a66574d 100644 --- a/launcher/net/ByteArraySink.h +++ b/launcher/net/ByteArraySink.h @@ -3,60 +3,59 @@ #include "Sink.h" namespace Net { + /* * Sink object for downloads that uses an external QByteArray it doesn't own as a target. */ -class ByteArraySink : public Sink -{ -public: - ByteArraySink(QByteArray *output) - :m_output(output) - { - // nil - }; +class ByteArraySink : public Sink { + public: + ByteArraySink(QByteArray* output) : m_output(output){}; - virtual ~ByteArraySink() - { - // nil - } + virtual ~ByteArraySink() = default; -public: - JobStatus init(QNetworkRequest & request) override + public: + auto init(QNetworkRequest& request) -> Task::State override { + if(!m_output) + return Task::State::Failed; + m_output->clear(); - if(initAllValidators(request)) - return Job_InProgress; - return Job_Failed; + if (initAllValidators(request)) + return Task::State::Running; + return Task::State::Failed; }; - JobStatus write(QByteArray & data) override + auto write(QByteArray& data) -> Task::State override { + if(!m_output) + return Task::State::Failed; + m_output->append(data); - if(writeAllValidators(data)) - return Job_InProgress; - return Job_Failed; + if (writeAllValidators(data)) + return Task::State::Running; + return Task::State::Failed; } - JobStatus abort() override + auto abort() -> Task::State override { + if(!m_output) + return Task::State::Failed; + m_output->clear(); failAllValidators(); - return Job_Failed; + return Task::State::Failed; } - JobStatus finalize(QNetworkReply &reply) override + auto finalize(QNetworkReply& reply) -> Task::State override { - if(finalizeAllValidators(reply)) - return Job_Finished; - return Job_Failed; + if (finalizeAllValidators(reply)) + return Task::State::Succeeded; + return Task::State::Failed; } - bool hasLocalData() override - { - return false; - } + auto hasLocalData() -> bool override { return false; } -private: - QByteArray * m_output; + private: + QByteArray* m_output; }; -} +} // namespace Net diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index 65cc8f67a..5b5a04db3 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -30,7 +30,7 @@ namespace Net { Download::Download() : NetAction() { - m_status = Job_NotStarted; + m_state = State::Inactive; } Download::Ptr Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) @@ -68,29 +68,29 @@ void Download::addValidator(Validator* v) m_sink->addValidator(v); } -void Download::startImpl() +void Download::executeTask() { - if (m_status == Job_Aborted) { + if (getState() == Task::State::AbortedByUser) { qWarning() << "Attempt to start an aborted Download:" << m_url.toString(); emit aborted(m_index_within_job); return; } + QNetworkRequest request(m_url); - m_status = m_sink->init(request); - switch (m_status) { - case Job_Finished: + m_state = m_sink->init(request); + switch (m_state) { + case State::Succeeded: emit succeeded(m_index_within_job); qDebug() << "Download cache hit " << m_url.toString(); return; - case Job_InProgress: + case State::Running: qDebug() << "Downloading " << m_url.toString(); break; - case Job_Failed_Proceed: // this is meaningless in this context. We do need a sink. - case Job_NotStarted: - case Job_Failed: + case State::Inactive: + case State::Failed: emit failed(m_index_within_job); return; - case Job_Aborted: + case State::AbortedByUser: return; } @@ -111,8 +111,7 @@ void Download::startImpl() void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - m_total_progress = bytesTotal; - m_progress = bytesReceived; + setProgress(bytesReceived, bytesTotal); emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); } @@ -120,17 +119,17 @@ void Download::downloadError(QNetworkReply::NetworkError error) { if (error == QNetworkReply::OperationCanceledError) { qCritical() << "Aborted " << m_url.toString(); - m_status = Job_Aborted; + m_state = State::AbortedByUser; } else { if (m_options & Option::AcceptLocalFiles) { if (m_sink->hasLocalData()) { - m_status = Job_Failed_Proceed; + m_state = State::Succeeded; return; } } // error happened during download. qCritical() << "Failed " << m_url.toString() << " with reason " << error; - m_status = Job_Failed; + m_state = State::Failed; } } @@ -194,7 +193,8 @@ bool Download::handleRedirect() m_url = QUrl(redirect.toString()); qDebug() << "Following redirect to " << m_url.toString(); - start(m_network); + startAction(m_network); + return true; } @@ -207,19 +207,20 @@ void Download::downloadFinished() } // if the download failed before this point ... - if (m_status == Job_Failed_Proceed) { + if (m_state == State::Succeeded) // pretend to succeed so we continue processing :) + { qDebug() << "Download failed but we are allowed to proceed:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit succeeded(m_index_within_job); return; - } else if (m_status == Job_Failed) { + } else if (m_state == State::Failed) { qDebug() << "Download failed in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit failed(m_index_within_job); return; - } else if (m_status == Job_Aborted) { + } else if (m_state == State::AbortedByUser) { qDebug() << "Download aborted in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); @@ -231,12 +232,12 @@ void Download::downloadFinished() auto data = m_reply->readAll(); if (data.size()) { qDebug() << "Writing extra" << data.size() << "bytes to" << m_target_path; - m_status = m_sink->write(data); + m_state = m_sink->write(data); } // otherwise, finalize the whole graph - m_status = m_sink->finalize(*m_reply.get()); - if (m_status != Job_Finished) { + m_state = m_sink->finalize(*m_reply.get()); + if (m_state != State::Succeeded) { qDebug() << "Download failed to finalize:" << m_url.toString(); m_sink->abort(); m_reply.reset(); @@ -250,10 +251,10 @@ void Download::downloadFinished() void Download::downloadReadyRead() { - if (m_status == Job_InProgress) { + if (m_state == State::Running) { auto data = m_reply->readAll(); - m_status = m_sink->write(data); - if (m_status == Job_Failed) { + m_state = m_sink->write(data); + if (m_state == State::Failed) { qCritical() << "Failed to process response chunk for " << m_target_path; } // qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes"; @@ -269,12 +270,7 @@ bool Net::Download::abort() if (m_reply) { m_reply->abort(); } else { - m_status = Job_Aborted; + m_state = State::AbortedByUser; } return true; } - -bool Net::Download::canAbort() -{ - return true; -} diff --git a/launcher/net/Download.h b/launcher/net/Download.h index 0f9bfe7f7..231ad6a73 100644 --- a/launcher/net/Download.h +++ b/launcher/net/Download.h @@ -27,7 +27,7 @@ class Download : public NetAction { Q_OBJECT -public: /* types */ +public: typedef shared_qobject_ptr Ptr; enum class Option { @@ -36,7 +36,7 @@ public: /* types */ }; Q_DECLARE_FLAGS(Options, Option) -protected: /* con/des */ +protected: explicit Download(); public: virtual ~Download(){}; @@ -44,16 +44,16 @@ public: static Download::Ptr makeByteArray(QUrl url, QByteArray *output, Options options = Option::NoOptions); static Download::Ptr makeFile(QUrl url, QString path, Options options = Option::NoOptions); -public: /* methods */ +public: QString getTargetFilepath() { return m_target_path; } void addValidator(Validator * v); bool abort() override; - bool canAbort() override; + bool canAbort() const override { return true; }; -private: /* methods */ +private: bool handleRedirect(); protected slots: @@ -64,9 +64,9 @@ protected slots: void downloadReadyRead() override; public slots: - void startImpl() override; + void executeTask() override; -private: /* data */ +private: // FIXME: remove this, it has no business being here. QString m_target_path; std::unique_ptr m_sink; diff --git a/launcher/net/FileSink.cpp b/launcher/net/FileSink.cpp index 7e9b8929f..0d8b09bbc 100644 --- a/launcher/net/FileSink.cpp +++ b/launcher/net/FileSink.cpp @@ -1,25 +1,15 @@ #include "FileSink.h" + #include -#include + #include "FileSystem.h" namespace Net { -FileSink::FileSink(QString filename) - :m_filename(filename) -{ - // nil -} - -FileSink::~FileSink() -{ - // nil -} - -JobStatus FileSink::init(QNetworkRequest& request) +Task::State FileSink::init(QNetworkRequest& request) { auto result = initCache(request); - if(result != Job_InProgress) + if(result != Task::State::Running) { return result; } @@ -27,27 +17,27 @@ JobStatus FileSink::init(QNetworkRequest& request) if (!FS::ensureFilePathExists(m_filename)) { qCritical() << "Could not create folder for " + m_filename; - return Job_Failed; + return Task::State::Failed; } wroteAnyData = false; m_output_file.reset(new QSaveFile(m_filename)); if (!m_output_file->open(QIODevice::WriteOnly)) { qCritical() << "Could not open " + m_filename + " for writing"; - return Job_Failed; + return Task::State::Failed; } if(initAllValidators(request)) - return Job_InProgress; - return Job_Failed; + return Task::State::Running; + return Task::State::Failed; } -JobStatus FileSink::initCache(QNetworkRequest &) +Task::State FileSink::initCache(QNetworkRequest &) { - return Job_InProgress; + return Task::State::Running; } -JobStatus FileSink::write(QByteArray& data) +Task::State FileSink::write(QByteArray& data) { if (!writeAllValidators(data) || m_output_file->write(data) != data.size()) { @@ -55,20 +45,20 @@ JobStatus FileSink::write(QByteArray& data) m_output_file->cancelWriting(); m_output_file.reset(); wroteAnyData = false; - return Job_Failed; + return Task::State::Failed; } wroteAnyData = true; - return Job_InProgress; + return Task::State::Running; } -JobStatus FileSink::abort() +Task::State FileSink::abort() { m_output_file->cancelWriting(); failAllValidators(); - return Job_Failed; + return Task::State::Failed; } -JobStatus FileSink::finalize(QNetworkReply& reply) +Task::State FileSink::finalize(QNetworkReply& reply) { bool gotFile = false; QVariant statusCodeV = reply.attribute(QNetworkRequest::HttpStatusCodeAttribute); @@ -86,13 +76,13 @@ JobStatus FileSink::finalize(QNetworkReply& reply) // ask validators for data consistency // we only do this for actual downloads, not 'your data is still the same' cache hits if(!finalizeAllValidators(reply)) - return Job_Failed; + return Task::State::Failed; // nothing went wrong... if (!m_output_file->commit()) { qCritical() << "Failed to commit changes to " << m_filename; m_output_file->cancelWriting(); - return Job_Failed; + return Task::State::Failed; } } // then get rid of the save file @@ -101,9 +91,9 @@ JobStatus FileSink::finalize(QNetworkReply& reply) return finalizeCache(reply); } -JobStatus FileSink::finalizeCache(QNetworkReply &) +Task::State FileSink::finalizeCache(QNetworkReply &) { - return Job_Finished; + return Task::State::Succeeded; } bool FileSink::hasLocalData() diff --git a/launcher/net/FileSink.h b/launcher/net/FileSink.h index 875fe5110..9d77b3d0f 100644 --- a/launcher/net/FileSink.h +++ b/launcher/net/FileSink.h @@ -1,28 +1,30 @@ #pragma once -#include "Sink.h" + #include +#include "Sink.h" + namespace Net { -class FileSink : public Sink -{ -public: /* con/des */ - FileSink(QString filename); - virtual ~FileSink(); +class FileSink : public Sink { + public: + FileSink(QString filename) : m_filename(filename){}; + virtual ~FileSink() = default; -public: /* methods */ - JobStatus init(QNetworkRequest & request) override; - JobStatus write(QByteArray & data) override; - JobStatus abort() override; - JobStatus finalize(QNetworkReply & reply) override; - bool hasLocalData() override; + public: + auto init(QNetworkRequest& request) -> Task::State override; + auto write(QByteArray& data) -> Task::State override; + auto abort() -> Task::State override; + auto finalize(QNetworkReply& reply) -> Task::State override; -protected: /* methods */ - virtual JobStatus initCache(QNetworkRequest &); - virtual JobStatus finalizeCache(QNetworkReply &reply); + auto hasLocalData() -> bool override; -protected: /* data */ + protected: + virtual auto initCache(QNetworkRequest&) -> Task::State; + virtual auto finalizeCache(QNetworkReply& reply) -> Task::State; + + protected: QString m_filename; bool wroteAnyData = false; std::unique_ptr m_output_file; }; -} +} // namespace Net diff --git a/launcher/net/MetaCacheSink.cpp b/launcher/net/MetaCacheSink.cpp index 5cdf04606..34ba9f566 100644 --- a/launcher/net/MetaCacheSink.cpp +++ b/launcher/net/MetaCacheSink.cpp @@ -12,17 +12,13 @@ MetaCacheSink::MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum) addValidator(md5sum); } -MetaCacheSink::~MetaCacheSink() -{ - // nil -} - -JobStatus MetaCacheSink::initCache(QNetworkRequest& request) +Task::State MetaCacheSink::initCache(QNetworkRequest& request) { if (!m_entry->isStale()) { - return Job_Finished; + return Task::State::Succeeded; } + // check if file exists, if it does, use its information for the request QFile current(m_filename); if(current.exists() && current.size() != 0) @@ -36,25 +32,31 @@ JobStatus MetaCacheSink::initCache(QNetworkRequest& request) request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->getETag().toLatin1()); } } - return Job_InProgress; + + return Task::State::Running; } -JobStatus MetaCacheSink::finalizeCache(QNetworkReply & reply) +Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply) { QFileInfo output_file_info(m_filename); + if(wroteAnyData) { m_entry->setMD5Sum(m_md5Node->hash().toHex().constData()); } + m_entry->setETag(reply.rawHeader("ETag").constData()); + if (reply.hasRawHeader("Last-Modified")) { m_entry->setRemoteChangedTimestamp(reply.rawHeader("Last-Modified").constData()); } + m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch()); m_entry->setStale(false); APPLICATION->metacache()->updateEntry(m_entry); - return Job_Finished; + + return Task::State::Succeeded; } bool MetaCacheSink::hasLocalData() diff --git a/launcher/net/MetaCacheSink.h b/launcher/net/MetaCacheSink.h index edcf7ad17..431e10a87 100644 --- a/launcher/net/MetaCacheSink.h +++ b/launcher/net/MetaCacheSink.h @@ -1,22 +1,23 @@ #pragma once -#include "FileSink.h" + #include "ChecksumValidator.h" +#include "FileSink.h" #include "net/HttpMetaCache.h" namespace Net { -class MetaCacheSink : public FileSink -{ -public: /* con/des */ - MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum); - virtual ~MetaCacheSink(); - bool hasLocalData() override; +class MetaCacheSink : public FileSink { + public: + MetaCacheSink(MetaEntryPtr entry, ChecksumValidator* md5sum); + virtual ~MetaCacheSink() = default; -protected: /* methods */ - JobStatus initCache(QNetworkRequest & request) override; - JobStatus finalizeCache(QNetworkReply & reply) override; + auto hasLocalData() -> bool override; -private: /* data */ + protected: + auto initCache(QNetworkRequest& request) -> Task::State override; + auto finalizeCache(QNetworkReply& reply) -> Task::State override; + + private: MetaEntryPtr m_entry; - ChecksumValidator * m_md5Node; + ChecksumValidator* m_md5Node; }; -} +} // namespace Net diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h index efb20953f..e15716f65 100644 --- a/launcher/net/NetAction.h +++ b/launcher/net/NetAction.h @@ -1,108 +1,81 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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. */ #pragma once -#include -#include -#include #include -#include +#include -enum JobStatus -{ - Job_NotStarted, - Job_InProgress, - Job_Finished, - Job_Failed, - Job_Aborted, - /* - * FIXME: @NUKE this confuses the task failing with us having a fallback in the form of local data. Clear up the confusion. - * Same could be true for aborted task - the presence of pre-existing result is a separate concern - */ - Job_Failed_Proceed -}; +#include "QObjectPtr.h" +#include "tasks/Task.h" -class NetAction : public QObject -{ +class NetAction : public Task { Q_OBJECT -protected: - explicit NetAction() : QObject(nullptr) {}; + protected: + explicit NetAction() : Task(nullptr) {}; -public: + public: using Ptr = shared_qobject_ptr; - virtual ~NetAction() {}; + virtual ~NetAction() = default; - bool isRunning() const - { - return m_status == Job_InProgress; - } - bool isFinished() const - { - return m_status >= Job_Finished; - } - bool wasSuccessful() const - { - return m_status == Job_Finished || m_status == Job_Failed_Proceed; - } + QUrl url() { return m_url; } - qint64 totalProgress() const - { - return m_total_progress; - } - qint64 currentProgress() const - { - return m_progress; - } - virtual bool abort() - { - return false; - } - virtual bool canAbort() - { - return false; - } - QUrl url() - { - return m_url; - } - -signals: + signals: void started(int index); void netActionProgress(int index, qint64 current, qint64 total); void succeeded(int index); void failed(int index); void aborted(int index); -protected slots: + protected slots: virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0; virtual void downloadError(QNetworkReply::NetworkError error) = 0; virtual void downloadFinished() = 0; virtual void downloadReadyRead() = 0; -public slots: - void start(shared_qobject_ptr network) { + public slots: + void startAction(shared_qobject_ptr network) + { m_network = network; - startImpl(); + executeTask(); } -protected: - virtual void startImpl() = 0; + protected: + void executeTask() override {}; -public: + public: shared_qobject_ptr m_network; /// index within the parent job, FIXME: nuke @@ -113,10 +86,4 @@ public: /// source URL QUrl m_url; - - qint64 m_progress = 0; - qint64 m_total_progress = 1; - -protected: - JobStatus m_status = Job_NotStarted; }; diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index 9bad89edd..d08d6c4d3 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -1,79 +1,170 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 "NetJob.h" #include "Download.h" -#include +auto NetJob::addNetAction(NetAction::Ptr action) -> bool +{ + action->m_index_within_job = m_downloads.size(); + m_downloads.append(action); + part_info pi; + m_parts_progress.append(pi); + + partProgress(m_parts_progress.count() - 1, action->getProgress(), action->getTotalProgress()); + + if (action->isRunning()) { + connect(action.get(), &NetAction::succeeded, this, &NetJob::partSucceeded); + connect(action.get(), &NetAction::failed, this, &NetJob::partFailed); + connect(action.get(), &NetAction::netActionProgress, this, &NetJob::partProgress); + } else { + m_todo.append(m_parts_progress.size() - 1); + } + + return true; +} + +auto NetJob::canAbort() const -> bool +{ + bool canFullyAbort = true; + + // can abort the downloads on the queue? + for (auto index : m_todo) { + auto part = m_downloads[index]; + canFullyAbort &= part->canAbort(); + } + // can abort the active downloads? + for (auto index : m_doing) { + auto part = m_downloads[index]; + canFullyAbort &= part->canAbort(); + } + + return canFullyAbort; +} + +void NetJob::executeTask() +{ + // hack that delays early failures so they can be caught easier + QMetaObject::invokeMethod(this, "startMoreParts", Qt::QueuedConnection); +} + +auto NetJob::getFailedFiles() -> QStringList +{ + QStringList failed; + for (auto index : m_failed) { + failed.push_back(m_downloads[index]->url().toString()); + } + failed.sort(); + return failed; +} + +auto NetJob::abort() -> bool +{ + bool fullyAborted = true; + + // fail all downloads on the queue + m_failed.unite(m_todo.toSet()); + m_todo.clear(); + + // abort active downloads + auto toKill = m_doing.toList(); + for (auto index : toKill) { + auto part = m_downloads[index]; + fullyAborted &= part->abort(); + } + + return fullyAborted; +} void NetJob::partSucceeded(int index) { // do progress. all slots are 1 in size at least - auto &slot = parts_progress[index]; + auto& slot = m_parts_progress[index]; partProgress(index, slot.total_progress, slot.total_progress); m_doing.remove(index); m_done.insert(index); - downloads[index].get()->disconnect(this); + m_downloads[index].get()->disconnect(this); + startMoreParts(); } void NetJob::partFailed(int index) { m_doing.remove(index); - auto &slot = parts_progress[index]; - if (slot.failures == 3) - { + + auto& slot = m_parts_progress[index]; + // Can try 3 times before failing by definitive + if (slot.failures == 3) { m_failed.insert(index); - } - else - { + } else { slot.failures++; m_todo.enqueue(index); } - downloads[index].get()->disconnect(this); + + m_downloads[index].get()->disconnect(this); + startMoreParts(); } void NetJob::partAborted(int index) { m_aborted = true; + m_doing.remove(index); m_failed.insert(index); - downloads[index].get()->disconnect(this); + m_downloads[index].get()->disconnect(this); + startMoreParts(); } void NetJob::partProgress(int index, qint64 bytesReceived, qint64 bytesTotal) { - auto &slot = parts_progress[index]; + auto& slot = m_parts_progress[index]; slot.current_progress = bytesReceived; slot.total_progress = bytesTotal; int done = m_done.size(); int doing = m_doing.size(); - int all = parts_progress.size(); + int all = m_parts_progress.size(); qint64 bytesAll = 0; qint64 bytesTotalAll = 0; - for(auto & partIdx: m_doing) - { - auto part = parts_progress[partIdx]; + for (auto& partIdx : m_doing) { + auto part = m_parts_progress[partIdx]; // do not count parts with unknown/nonsensical total size - if(part.total_progress <= 0) - { + if (part.total_progress <= 0) { continue; } bytesAll += part.current_progress; @@ -85,134 +176,53 @@ void NetJob::partProgress(int index, qint64 bytesReceived, qint64 bytesTotal) auto current_total = all * 1000; // HACK: make sure it never jumps backwards. // FAIL: This breaks if the size is not known (or is it something else?) and jumps to 1000, so if it is 1000 reset it to inprogress - if(m_current_progress == 1000) { + if (m_current_progress == 1000) { m_current_progress = inprogress; } - if(m_current_progress > current) - { + if (m_current_progress > current) { current = m_current_progress; } m_current_progress = current; setProgress(current, current_total); } -void NetJob::executeTask() -{ - // hack that delays early failures so they can be caught easier - QMetaObject::invokeMethod(this, "startMoreParts", Qt::QueuedConnection); -} - void NetJob::startMoreParts() { - if(!isRunning()) - { - // this actually makes sense. You can put running downloads into a NetJob and then not start it until much later. + if (!isRunning()) { + // this actually makes sense. You can put running m_downloads into a NetJob and then not start it until much later. return; } + // OK. We are actively processing tasks, proceed. // Check for final conditions if there's nothing in the queue. - if(!m_todo.size()) - { - if(!m_doing.size()) - { - if(!m_failed.size()) - { + if (!m_todo.size()) { + if (!m_doing.size()) { + if (!m_failed.size()) { emitSucceeded(); - } - else if(m_aborted) - { + } else if (m_aborted) { emitAborted(); - } - else - { + } else { emitFailed(tr("Job '%1' failed to process:\n%2").arg(objectName()).arg(getFailedFiles().join("\n"))); } } return; } - // There's work to do, try to start more parts. - while (m_doing.size() < 6) - { - if(!m_todo.size()) + + // There's work to do, try to start more parts, to a maximum of 6 concurrent ones. + while (m_doing.size() < 6) { + if (m_todo.size() == 0) return; int doThis = m_todo.dequeue(); m_doing.insert(doThis); - auto part = downloads[doThis]; + + auto part = m_downloads[doThis]; + // connect signals :D - connect(part.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); - connect(part.get(), SIGNAL(failed(int)), SLOT(partFailed(int))); - connect(part.get(), SIGNAL(aborted(int)), SLOT(partAborted(int))); - connect(part.get(), SIGNAL(netActionProgress(int, qint64, qint64)), - SLOT(partProgress(int, qint64, qint64))); - part->start(m_network); + connect(part.get(), &NetAction::succeeded, this, &NetJob::partSucceeded); + connect(part.get(), &NetAction::failed, this, &NetJob::partFailed); + connect(part.get(), &NetAction::aborted, this, &NetJob::partAborted); + connect(part.get(), &NetAction::netActionProgress, this, &NetJob::partProgress); + + part->startAction(m_network); } } - - -QStringList NetJob::getFailedFiles() -{ - QStringList failed; - for (auto index: m_failed) - { - failed.push_back(downloads[index]->url().toString()); - } - failed.sort(); - return failed; -} - -bool NetJob::canAbort() const -{ - bool canFullyAbort = true; - // can abort the waiting? - for(auto index: m_todo) - { - auto part = downloads[index]; - canFullyAbort &= part->canAbort(); - } - // can abort the active? - for(auto index: m_doing) - { - auto part = downloads[index]; - canFullyAbort &= part->canAbort(); - } - return canFullyAbort; -} - -bool NetJob::abort() -{ - bool fullyAborted = true; - // fail all waiting - m_failed.unite(m_todo.toSet()); - m_todo.clear(); - // abort active - auto toKill = m_doing.toList(); - for(auto index: toKill) - { - auto part = downloads[index]; - fullyAborted &= part->abort(); - } - return fullyAborted; -} - -bool NetJob::addNetAction(NetAction::Ptr action) -{ - action->m_index_within_job = downloads.size(); - downloads.append(action); - part_info pi; - parts_progress.append(pi); - partProgress(parts_progress.count() - 1, action->currentProgress(), action->totalProgress()); - - if(action->isRunning()) - { - connect(action.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); - connect(action.get(), SIGNAL(failed(int)), SLOT(partFailed(int))); - connect(action.get(), SIGNAL(netActionProgress(int, qint64, qint64)), SLOT(partProgress(int, qint64, qint64))); - } - else - { - m_todo.append(parts_progress.size() - 1); - } - return true; -} - -NetJob::~NetJob() = default; diff --git a/launcher/net/NetJob.h b/launcher/net/NetJob.h index fdea710fc..c397e2a1f 100644 --- a/launcher/net/NetJob.h +++ b/launcher/net/NetJob.h @@ -1,88 +1,97 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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. */ #pragma once + #include + +#include #include "NetAction.h" -#include "Download.h" -#include "HttpMetaCache.h" #include "tasks/Task.h" -#include "QObjectPtr.h" -class NetJob; +// Those are included so that they are also included by anyone using NetJob +#include "net/Download.h" +#include "net/HttpMetaCache.h" -class NetJob : public Task -{ +class NetJob : public Task { Q_OBJECT -public: + + public: using Ptr = shared_qobject_ptr; explicit NetJob(QString job_name, shared_qobject_ptr network) : Task(), m_network(network) { setObjectName(job_name); } - virtual ~NetJob(); + virtual ~NetJob() = default; - bool addNetAction(NetAction::Ptr action); + void executeTask() override; - NetAction::Ptr operator[](int index) - { - return downloads[index]; - } - const NetAction::Ptr at(const int index) - { - return downloads.at(index); - } - NetAction::Ptr first() - { - if (downloads.size()) - return downloads[0]; - return NetAction::Ptr(); - } - int size() const - { - return downloads.size(); - } - QStringList getFailedFiles(); + auto canAbort() const -> bool override; - bool canAbort() const override; + auto addNetAction(NetAction::Ptr action) -> bool; -private slots: + auto operator[](int index) -> NetAction::Ptr { return m_downloads[index]; } + auto at(int index) -> const NetAction::Ptr { return m_downloads.at(index); } + auto size() const -> int { return m_downloads.size(); } + auto first() -> NetAction::Ptr { return m_downloads.size() != 0 ? m_downloads[0] : NetAction::Ptr{}; } + + auto getFailedFiles() -> QStringList; + + public slots: + // Qt can't handle auto at the start for some reason? + bool abort() override; + + private slots: void startMoreParts(); -public slots: - virtual void executeTask() override; - virtual bool abort() override; - -private slots: void partProgress(int index, qint64 bytesReceived, qint64 bytesTotal); void partSucceeded(int index); void partFailed(int index); void partAborted(int index); -private: + private: shared_qobject_ptr m_network; - struct part_info - { + struct part_info { qint64 current_progress = 0; qint64 total_progress = 1; int failures = 0; }; - QList downloads; - QList parts_progress; + + QList m_downloads; + QList m_parts_progress; QQueue m_todo; QSet m_doing; QSet m_done; diff --git a/launcher/net/Sink.h b/launcher/net/Sink.h index d367fb15c..3b2a7f8dd 100644 --- a/launcher/net/Sink.h +++ b/launcher/net/Sink.h @@ -5,33 +5,30 @@ #include "Validator.h" namespace Net { -class Sink -{ -public: /* con/des */ - Sink() {}; - virtual ~Sink() {}; +class Sink { + public: + Sink() = default; + virtual ~Sink(){}; -public: /* methods */ - virtual JobStatus init(QNetworkRequest & request) = 0; - virtual JobStatus write(QByteArray & data) = 0; - virtual JobStatus abort() = 0; - virtual JobStatus finalize(QNetworkReply & reply) = 0; + public: + virtual Task::State init(QNetworkRequest& request) = 0; + virtual Task::State write(QByteArray& data) = 0; + virtual Task::State abort() = 0; + virtual Task::State finalize(QNetworkReply& reply) = 0; virtual bool hasLocalData() = 0; - void addValidator(Validator * validator) + void addValidator(Validator* validator) { - if(validator) - { + if (validator) { validators.push_back(std::shared_ptr(validator)); } } -protected: /* methods */ - bool finalizeAllValidators(QNetworkReply & reply) + protected: /* methods */ + bool finalizeAllValidators(QNetworkReply& reply) { - for(auto & validator: validators) - { - if(!validator->validate(reply)) + for (auto& validator : validators) { + if (!validator->validate(reply)) return false; } return true; @@ -39,32 +36,29 @@ protected: /* methods */ bool failAllValidators() { bool success = true; - for(auto & validator: validators) - { + for (auto& validator : validators) { success &= validator->abort(); } return success; } - bool initAllValidators(QNetworkRequest & request) + bool initAllValidators(QNetworkRequest& request) { - for(auto & validator: validators) - { - if(!validator->init(request)) + for (auto& validator : validators) { + if (!validator->init(request)) return false; } return true; } - bool writeAllValidators(QByteArray & data) + bool writeAllValidators(QByteArray& data) { - for(auto & validator: validators) - { - if(!validator->write(data)) + for (auto& validator : validators) { + if (!validator->write(data)) return false; } return true; } -protected: /* data */ + protected: /* data */ std::vector> validators; }; -} +} // namespace Net diff --git a/launcher/screenshots/ImgurAlbumCreation.cpp b/launcher/screenshots/ImgurAlbumCreation.cpp index d5de302a0..81fac929d 100644 --- a/launcher/screenshots/ImgurAlbumCreation.cpp +++ b/launcher/screenshots/ImgurAlbumCreation.cpp @@ -13,12 +13,12 @@ ImgurAlbumCreation::ImgurAlbumCreation(QList screenshots) : NetAction(), m_screenshots(screenshots) { m_url = BuildConfig.IMGUR_BASE_URL + "album.json"; - m_status = Job_NotStarted; + m_state = State::Inactive; } -void ImgurAlbumCreation::startImpl() +void ImgurAlbumCreation::executeTask() { - m_status = Job_InProgress; + m_state = State::Running; QNetworkRequest request(m_url); request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); @@ -43,11 +43,11 @@ void ImgurAlbumCreation::startImpl() void ImgurAlbumCreation::downloadError(QNetworkReply::NetworkError error) { qDebug() << m_reply->errorString(); - m_status = Job_Failed; + m_state = State::Failed; } void ImgurAlbumCreation::downloadFinished() { - if (m_status != Job_Failed) + if (m_state != State::Failed) { QByteArray data = m_reply->readAll(); m_reply.reset(); @@ -68,7 +68,7 @@ void ImgurAlbumCreation::downloadFinished() } m_deleteHash = object.value("data").toObject().value("deletehash").toString(); m_id = object.value("data").toObject().value("id").toString(); - m_status = Job_Finished; + m_state = State::Succeeded; emit succeeded(m_index_within_job); return; } @@ -82,7 +82,6 @@ void ImgurAlbumCreation::downloadFinished() } void ImgurAlbumCreation::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - m_total_progress = bytesTotal; - m_progress = bytesReceived; + setProgress(bytesReceived, bytesTotal); emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); } diff --git a/launcher/screenshots/ImgurAlbumCreation.h b/launcher/screenshots/ImgurAlbumCreation.h index cb048a233..4cb0ed5dd 100644 --- a/launcher/screenshots/ImgurAlbumCreation.h +++ b/launcher/screenshots/ImgurAlbumCreation.h @@ -24,16 +24,14 @@ public: protected slots: - virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); - virtual void downloadError(QNetworkReply::NetworkError error); - virtual void downloadFinished(); - virtual void downloadReadyRead() - { - } + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; + void downloadError(QNetworkReply::NetworkError error) override; + void downloadFinished() override; + void downloadReadyRead() override {} public slots: - virtual void startImpl(); + void executeTask() override; private: QList m_screenshots; diff --git a/launcher/screenshots/ImgurUpload.cpp b/launcher/screenshots/ImgurUpload.cpp index 76a84947b..0f0fd79c1 100644 --- a/launcher/screenshots/ImgurUpload.cpp +++ b/launcher/screenshots/ImgurUpload.cpp @@ -13,13 +13,13 @@ ImgurUpload::ImgurUpload(ScreenShot::Ptr shot) : NetAction(), m_shot(shot) { m_url = BuildConfig.IMGUR_BASE_URL + "upload.json"; - m_status = Job_NotStarted; + m_state = State::Inactive; } -void ImgurUpload::startImpl() +void ImgurUpload::executeTask() { finished = false; - m_status = Job_InProgress; + m_state = Task::State::Running; QNetworkRequest request(m_url); request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); request.setRawHeader("Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toStdString().c_str()); @@ -63,7 +63,7 @@ void ImgurUpload::downloadError(QNetworkReply::NetworkError error) qCritical() << "Double finished ImgurUpload!"; return; } - m_status = Job_Failed; + m_state = Task::State::Failed; finished = true; m_reply.reset(); emit failed(m_index_within_job); @@ -99,14 +99,13 @@ void ImgurUpload::downloadFinished() m_shot->m_imgurId = object.value("data").toObject().value("id").toString(); m_shot->m_url = object.value("data").toObject().value("link").toString(); m_shot->m_imgurDeleteHash = object.value("data").toObject().value("deletehash").toString(); - m_status = Job_Finished; + m_state = Task::State::Succeeded; finished = true; emit succeeded(m_index_within_job); return; } void ImgurUpload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - m_total_progress = bytesTotal; - m_progress = bytesReceived; + setProgress(bytesReceived, bytesTotal); emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); } diff --git a/launcher/screenshots/ImgurUpload.h b/launcher/screenshots/ImgurUpload.h index cf54f58dc..a10405510 100644 --- a/launcher/screenshots/ImgurUpload.h +++ b/launcher/screenshots/ImgurUpload.h @@ -21,7 +21,7 @@ slots: public slots: - void startImpl() override; + void executeTask() override; private: ScreenShot::Ptr m_shot; diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 344a024ee..618551601 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -52,6 +52,8 @@ class Task : public QObject { virtual bool canAbort() const { return false; } + auto getState() const -> State { return m_state; } + QString getStatus() { return m_status; } virtual auto getStepStatus() const -> QString { return m_status; } @@ -90,7 +92,7 @@ class Task : public QObject { void setStatus(const QString& status); void setProgress(qint64 current, qint64 total); - private: + protected: State m_state = State::Inactive; QStringList m_Warnings; QString m_failReason = ""; diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 250854d3b..fbd170607 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -667,7 +667,7 @@ void TranslationsModel::downloadTranslation(QString key) auto dl = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATIONS_BASE_URL + lang->file_name), entry); auto rawHash = QByteArray::fromHex(lang->file_sha1.toLatin1()); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash)); - dl->m_total_progress = lang->file_size; + dl->setProgress(dl->getProgress(), lang->file_size); d->m_dl_job = new NetJob("Translation for " + key, APPLICATION->network()); d->m_dl_job->addNetAction(dl); From efa3fbff39bf0dabebdf1c6330090ee320895a4d Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 26 Apr 2022 21:25:42 -0300 Subject: [PATCH 010/308] refactor: remove some superfluous signals Since now we're inheriting from Task, some signals can be reused. --- launcher/net/Download.cpp | 21 ++++++++++----------- launcher/net/NetAction.h | 10 ++-------- launcher/net/NetJob.cpp | 14 +++++++------- launcher/screenshots/ImgurAlbumCreation.cpp | 10 +++++----- launcher/screenshots/ImgurUpload.cpp | 12 ++++++------ launcher/tasks/Task.cpp | 3 +-- launcher/tasks/Task.h | 3 ++- 7 files changed, 33 insertions(+), 40 deletions(-) diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index 5b5a04db3..5e5d64fac 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -72,7 +72,7 @@ void Download::executeTask() { if (getState() == Task::State::AbortedByUser) { qWarning() << "Attempt to start an aborted Download:" << m_url.toString(); - emit aborted(m_index_within_job); + emitAborted(); return; } @@ -80,7 +80,7 @@ void Download::executeTask() m_state = m_sink->init(request); switch (m_state) { case State::Succeeded: - emit succeeded(m_index_within_job); + emit succeeded(); qDebug() << "Download cache hit " << m_url.toString(); return; case State::Running: @@ -88,7 +88,7 @@ void Download::executeTask() break; case State::Inactive: case State::Failed: - emit failed(m_index_within_job); + emitFailed(); return; case State::AbortedByUser: return; @@ -102,8 +102,8 @@ void Download::executeTask() QNetworkReply* rep = m_network->get(request); m_reply.reset(rep); - connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64))); - connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); + connect(rep, &QNetworkReply::downloadProgress, this, &Download::downloadProgress); + connect(rep, &QNetworkReply::finished, this, &Download::downloadFinished); connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors); connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead); @@ -112,7 +112,6 @@ void Download::executeTask() void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { setProgress(bytesReceived, bytesTotal); - emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); } void Download::downloadError(QNetworkReply::NetworkError error) @@ -212,19 +211,19 @@ void Download::downloadFinished() qDebug() << "Download failed but we are allowed to proceed:" << m_url.toString(); m_sink->abort(); m_reply.reset(); - emit succeeded(m_index_within_job); + emit succeeded(); return; } else if (m_state == State::Failed) { qDebug() << "Download failed in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); - emit failed(m_index_within_job); + emitFailed(); return; } else if (m_state == State::AbortedByUser) { qDebug() << "Download aborted in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); - emit aborted(m_index_within_job); + emitAborted(); return; } @@ -241,12 +240,12 @@ void Download::downloadFinished() qDebug() << "Download failed to finalize:" << m_url.toString(); m_sink->abort(); m_reply.reset(); - emit failed(m_index_within_job); + emitFailed(); return; } m_reply.reset(); qDebug() << "Download succeeded:" << m_url.toString(); - emit succeeded(m_index_within_job); + emit succeeded(); } void Download::downloadReadyRead() diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h index e15716f65..86a37ee6d 100644 --- a/launcher/net/NetAction.h +++ b/launcher/net/NetAction.h @@ -43,7 +43,7 @@ class NetAction : public Task { Q_OBJECT protected: - explicit NetAction() : Task(nullptr) {}; + explicit NetAction() : Task() {}; public: using Ptr = shared_qobject_ptr; @@ -51,13 +51,7 @@ class NetAction : public Task { virtual ~NetAction() = default; QUrl url() { return m_url; } - - signals: - void started(int index); - void netActionProgress(int index, qint64 current, qint64 total); - void succeeded(int index); - void failed(int index); - void aborted(int index); + auto index() -> int { return m_index_within_job; } protected slots: virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0; diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index d08d6c4d3..a9f89da4c 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -45,9 +45,9 @@ auto NetJob::addNetAction(NetAction::Ptr action) -> bool partProgress(m_parts_progress.count() - 1, action->getProgress(), action->getTotalProgress()); if (action->isRunning()) { - connect(action.get(), &NetAction::succeeded, this, &NetJob::partSucceeded); - connect(action.get(), &NetAction::failed, this, &NetJob::partFailed); - connect(action.get(), &NetAction::netActionProgress, this, &NetJob::partProgress); + connect(action.get(), &NetAction::succeeded, [this, action]{ partSucceeded(action->index()); }); + connect(action.get(), &NetAction::failed, [this, action](QString){ partFailed(action->index()); }); + connect(action.get(), &NetAction::progress, [this, action](qint64 done, qint64 total) { partProgress(action->index(), done, total); }); } else { m_todo.append(m_parts_progress.size() - 1); } @@ -218,10 +218,10 @@ void NetJob::startMoreParts() auto part = m_downloads[doThis]; // connect signals :D - connect(part.get(), &NetAction::succeeded, this, &NetJob::partSucceeded); - connect(part.get(), &NetAction::failed, this, &NetJob::partFailed); - connect(part.get(), &NetAction::aborted, this, &NetJob::partAborted); - connect(part.get(), &NetAction::netActionProgress, this, &NetJob::partProgress); + connect(part.get(), &NetAction::succeeded, this, [this, part]{ partSucceeded(part->index()); }); + connect(part.get(), &NetAction::failed, this, [this, part](QString){ partFailed(part->index()); }); + connect(part.get(), &NetAction::aborted, this, [this, part]{ partAborted(part->index()); }); + connect(part.get(), &NetAction::progress, this, [this, part](qint64 done, qint64 total) { partProgress(part->index(), done, total); }); part->startAction(m_network); } diff --git a/launcher/screenshots/ImgurAlbumCreation.cpp b/launcher/screenshots/ImgurAlbumCreation.cpp index 81fac929d..f94527c8b 100644 --- a/launcher/screenshots/ImgurAlbumCreation.cpp +++ b/launcher/screenshots/ImgurAlbumCreation.cpp @@ -56,32 +56,32 @@ void ImgurAlbumCreation::downloadFinished() if (jsonError.error != QJsonParseError::NoError) { qDebug() << jsonError.errorString(); - emit failed(m_index_within_job); + emitFailed(); return; } auto object = doc.object(); if (!object.value("success").toBool()) { qDebug() << doc.toJson(); - emit failed(m_index_within_job); + emitFailed(); return; } m_deleteHash = object.value("data").toObject().value("deletehash").toString(); m_id = object.value("data").toObject().value("id").toString(); m_state = State::Succeeded; - emit succeeded(m_index_within_job); + emit succeeded(); return; } else { qDebug() << m_reply->readAll(); m_reply.reset(); - emit failed(m_index_within_job); + emitFailed(); return; } } void ImgurAlbumCreation::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { setProgress(bytesReceived, bytesTotal); - emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); + emit progress(bytesReceived, bytesTotal); } diff --git a/launcher/screenshots/ImgurUpload.cpp b/launcher/screenshots/ImgurUpload.cpp index 0f0fd79c1..05314de75 100644 --- a/launcher/screenshots/ImgurUpload.cpp +++ b/launcher/screenshots/ImgurUpload.cpp @@ -28,7 +28,7 @@ void ImgurUpload::executeTask() QFile f(m_shot->m_file.absoluteFilePath()); if (!f.open(QFile::ReadOnly)) { - emit failed(m_index_within_job); + emitFailed(); return; } @@ -66,7 +66,7 @@ void ImgurUpload::downloadError(QNetworkReply::NetworkError error) m_state = Task::State::Failed; finished = true; m_reply.reset(); - emit failed(m_index_within_job); + emitFailed(); } void ImgurUpload::downloadFinished() { @@ -84,7 +84,7 @@ void ImgurUpload::downloadFinished() qDebug() << "imgur server did not reply with JSON" << jsonError.errorString(); finished = true; m_reply.reset(); - emit failed(m_index_within_job); + emitFailed(); return; } auto object = doc.object(); @@ -93,7 +93,7 @@ void ImgurUpload::downloadFinished() qDebug() << "Screenshot upload not successful:" << doc.toJson(); finished = true; m_reply.reset(); - emit failed(m_index_within_job); + emitFailed(); return; } m_shot->m_imgurId = object.value("data").toObject().value("id").toString(); @@ -101,11 +101,11 @@ void ImgurUpload::downloadFinished() m_shot->m_imgurDeleteHash = object.value("data").toObject().value("deletehash").toString(); m_state = Task::State::Succeeded; finished = true; - emit succeeded(m_index_within_job); + emit succeeded(); return; } void ImgurUpload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { setProgress(bytesReceived, bytesTotal); - emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); + emit progress(bytesReceived, bytesTotal); } diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp index 57307b431..68e0e8a7d 100644 --- a/launcher/tasks/Task.cpp +++ b/launcher/tasks/Task.cpp @@ -99,8 +99,7 @@ void Task::emitAborted() m_state = State::AbortedByUser; m_failReason = "Aborted."; qDebug() << "Task" << describe() << "aborted."; - emit failed(m_failReason); - emit finished(); + emit aborted(); } void Task::emitSucceeded() diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 618551601..e09c57aec 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -73,6 +73,7 @@ class Task : public QObject { virtual void progress(qint64 current, qint64 total); void finished(); void succeeded(); + void aborted(); void failed(QString reason); void status(QString status); @@ -86,7 +87,7 @@ class Task : public QObject { protected slots: virtual void emitSucceeded(); virtual void emitAborted(); - virtual void emitFailed(QString reason); + virtual void emitFailed(QString reason = ""); public slots: void setStatus(const QString& status); From 040ee919e5ea71364daa08c30e09c843976f5734 Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 27 Apr 2022 18:36:11 -0300 Subject: [PATCH 011/308] refactor: more net cleanup This runs clang-tidy on some other files in launcher/net/. This also makes use of some JSON wrappers in HttpMetaCache, instead of using the Qt stuff directly. Lastly, this removes useless null checks (crashes don't occur because of this, but because of concurrent usage / free of the QByteArray pointer), and fix a fixme in Download.h --- launcher/net/ByteArraySink.h | 11 +- launcher/net/ChecksumValidator.h | 48 ++++---- launcher/net/Download.cpp | 24 ++-- launcher/net/Download.h | 59 ++++------ launcher/net/FileSink.cpp | 47 ++++---- launcher/net/HttpMetaCache.cpp | 190 ++++++++++++++----------------- launcher/net/HttpMetaCache.h | 107 +++++++---------- launcher/net/Mode.h | 9 +- launcher/net/Sink.h | 33 +++--- 9 files changed, 228 insertions(+), 300 deletions(-) diff --git a/launcher/net/ByteArraySink.h b/launcher/net/ByteArraySink.h index 75a66574d..8ae30bb31 100644 --- a/launcher/net/ByteArraySink.h +++ b/launcher/net/ByteArraySink.h @@ -6,6 +6,8 @@ namespace Net { /* * Sink object for downloads that uses an external QByteArray it doesn't own as a target. + * FIXME: It is possible that the QByteArray is freed while we're doing some operation on it, + * causing a segmentation fault. */ class ByteArraySink : public Sink { public: @@ -16,9 +18,6 @@ class ByteArraySink : public Sink { public: auto init(QNetworkRequest& request) -> Task::State override { - if(!m_output) - return Task::State::Failed; - m_output->clear(); if (initAllValidators(request)) return Task::State::Running; @@ -27,9 +26,6 @@ class ByteArraySink : public Sink { auto write(QByteArray& data) -> Task::State override { - if(!m_output) - return Task::State::Failed; - m_output->append(data); if (writeAllValidators(data)) return Task::State::Running; @@ -38,9 +34,6 @@ class ByteArraySink : public Sink { auto abort() -> Task::State override { - if(!m_output) - return Task::State::Failed; - m_output->clear(); failAllValidators(); return Task::State::Failed; diff --git a/launcher/net/ChecksumValidator.h b/launcher/net/ChecksumValidator.h index 0d6b19c21..8a8b10d57 100644 --- a/launcher/net/ChecksumValidator.h +++ b/launcher/net/ChecksumValidator.h @@ -1,55 +1,47 @@ #pragma once #include "Validator.h" + #include -#include #include namespace Net { -class ChecksumValidator: public Validator -{ -public: /* con/des */ +class ChecksumValidator : public Validator { + public: ChecksumValidator(QCryptographicHash::Algorithm algorithm, QByteArray expected = QByteArray()) - :m_checksum(algorithm), m_expected(expected) - { - }; - virtual ~ChecksumValidator() {}; + : m_checksum(algorithm), m_expected(expected){}; + virtual ~ChecksumValidator() = default; -public: /* methods */ - bool init(QNetworkRequest &) override + public: + auto init(QNetworkRequest&) -> bool override { m_checksum.reset(); return true; } - bool write(QByteArray & data) override + + auto write(QByteArray& data) -> bool override { m_checksum.addData(data); return true; } - bool abort() override + + auto abort() -> bool override { return true; } + + auto validate(QNetworkReply&) -> bool override { - return true; - } - bool validate(QNetworkReply &) override - { - if(m_expected.size() && m_expected != hash()) - { + if (m_expected.size() && m_expected != hash()) { qWarning() << "Checksum mismatch, download is bad."; return false; } return true; } - QByteArray hash() - { - return m_checksum.result(); - } - void setExpected(QByteArray expected) - { - m_expected = expected; - } -private: /* data */ + auto hash() -> QByteArray { return m_checksum.result(); } + + void setExpected(QByteArray expected) { m_expected = expected; } + + private: QCryptographicHash m_checksum; QByteArray m_expected; }; -} \ No newline at end of file +} // namespace Net diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index 5e5d64fac..3d6ca3382 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -33,30 +33,29 @@ Download::Download() : NetAction() m_state = State::Inactive; } -Download::Ptr Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) +auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr { - Download* dl = new Download(); + auto* dl = new Download(); dl->m_url = url; dl->m_options = options; auto md5Node = new ChecksumValidator(QCryptographicHash::Md5); auto cachedNode = new MetaCacheSink(entry, md5Node); dl->m_sink.reset(cachedNode); - dl->m_target_path = entry->getFullPath(); return dl; } -Download::Ptr Download::makeByteArray(QUrl url, QByteArray* output, Options options) +auto Download::makeByteArray(QUrl url, QByteArray* output, Options options) -> Download::Ptr { - Download* dl = new Download(); + auto* dl = new Download(); dl->m_url = url; dl->m_options = options; dl->m_sink.reset(new ByteArraySink(output)); return dl; } -Download::Ptr Download::makeFile(QUrl url, QString path, Options options) +auto Download::makeFile(QUrl url, QString path, Options options) -> Download::Ptr { - Download* dl = new Download(); + auto* dl = new Download(); dl->m_url = url; dl->m_options = options; dl->m_sink.reset(new FileSink(path)); @@ -143,7 +142,7 @@ void Download::sslErrors(const QList& errors) } } -bool Download::handleRedirect() +auto Download::handleRedirect() -> bool { QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl(); if (!redirect.isValid()) { @@ -230,7 +229,7 @@ void Download::downloadFinished() // make sure we got all the remaining data, if any auto data = m_reply->readAll(); if (data.size()) { - qDebug() << "Writing extra" << data.size() << "bytes to" << m_target_path; + qDebug() << "Writing extra" << data.size() << "bytes"; m_state = m_sink->write(data); } @@ -243,6 +242,7 @@ void Download::downloadFinished() emitFailed(); return; } + m_reply.reset(); qDebug() << "Download succeeded:" << m_url.toString(); emit succeeded(); @@ -254,17 +254,17 @@ void Download::downloadReadyRead() auto data = m_reply->readAll(); m_state = m_sink->write(data); if (m_state == State::Failed) { - qCritical() << "Failed to process response chunk for " << m_target_path; + qCritical() << "Failed to process response chunk"; } // qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes"; } else { - qCritical() << "Cannot write to " << m_target_path << ", illegal status" << m_status; + qCritical() << "Cannot write download data! illegal status " << m_status; } } } // namespace Net -bool Net::Download::abort() +auto Net::Download::abort() -> bool { if (m_reply) { m_reply->abort(); diff --git a/launcher/net/Download.h b/launcher/net/Download.h index 231ad6a73..9fb671275 100644 --- a/launcher/net/Download.h +++ b/launcher/net/Download.h @@ -15,63 +15,54 @@ #pragma once -#include "NetAction.h" #include "HttpMetaCache.h" -#include "Validator.h" +#include "NetAction.h" #include "Sink.h" +#include "Validator.h" #include "QObjectPtr.h" namespace Net { -class Download : public NetAction -{ +class Download : public NetAction { Q_OBJECT -public: - typedef shared_qobject_ptr Ptr; - enum class Option - { - NoOptions = 0, - AcceptLocalFiles = 1 - }; + public: + using Ptr = shared_qobject_ptr; + enum class Option { NoOptions = 0, AcceptLocalFiles = 1 }; Q_DECLARE_FLAGS(Options, Option) -protected: + protected: explicit Download(); -public: - virtual ~Download(){}; - static Download::Ptr makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions); - static Download::Ptr makeByteArray(QUrl url, QByteArray *output, Options options = Option::NoOptions); - static Download::Ptr makeFile(QUrl url, QString path, Options options = Option::NoOptions); -public: - QString getTargetFilepath() - { - return m_target_path; - } - void addValidator(Validator * v); - bool abort() override; - bool canAbort() const override { return true; }; + public: + ~Download() override = default; -private: - bool handleRedirect(); + static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr; + static auto makeByteArray(QUrl url, QByteArray* output, Options options = Option::NoOptions) -> Download::Ptr; + static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr; -protected slots: + public: + void addValidator(Validator* v); + auto abort() -> bool override; + auto canAbort() const -> bool override { return true; }; + + private: + auto handleRedirect() -> bool; + + protected slots: void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; void downloadError(QNetworkReply::NetworkError error) override; - void sslErrors(const QList & errors); + void sslErrors(const QList& errors); void downloadFinished() override; void downloadReadyRead() override; -public slots: + public slots: void executeTask() override; -private: - // FIXME: remove this, it has no business being here. - QString m_target_path; + private: std::unique_ptr m_sink; Options m_options; }; -} +} // namespace Net Q_DECLARE_OPERATORS_FOR_FLAGS(Net::Download::Options) diff --git a/launcher/net/FileSink.cpp b/launcher/net/FileSink.cpp index 0d8b09bbc..d2d2b06fb 100644 --- a/launcher/net/FileSink.cpp +++ b/launcher/net/FileSink.cpp @@ -1,7 +1,5 @@ #include "FileSink.h" -#include - #include "FileSystem.h" namespace Net { @@ -9,44 +7,38 @@ namespace Net { Task::State FileSink::init(QNetworkRequest& request) { auto result = initCache(request); - if(result != Task::State::Running) - { + if (result != Task::State::Running) { return result; } + // create a new save file and open it for writing - if (!FS::ensureFilePathExists(m_filename)) - { + if (!FS::ensureFilePathExists(m_filename)) { qCritical() << "Could not create folder for " + m_filename; return Task::State::Failed; } + wroteAnyData = false; m_output_file.reset(new QSaveFile(m_filename)); - if (!m_output_file->open(QIODevice::WriteOnly)) - { + if (!m_output_file->open(QIODevice::WriteOnly)) { qCritical() << "Could not open " + m_filename + " for writing"; return Task::State::Failed; } - if(initAllValidators(request)) + if (initAllValidators(request)) return Task::State::Running; return Task::State::Failed; } -Task::State FileSink::initCache(QNetworkRequest &) -{ - return Task::State::Running; -} - Task::State FileSink::write(QByteArray& data) { - if (!writeAllValidators(data) || m_output_file->write(data) != data.size()) - { + if (!writeAllValidators(data) || m_output_file->write(data) != data.size()) { qCritical() << "Failed writing into " + m_filename; m_output_file->cancelWriting(); m_output_file.reset(); wroteAnyData = false; return Task::State::Failed; } + wroteAnyData = true; return Task::State::Running; } @@ -64,34 +56,39 @@ Task::State FileSink::finalize(QNetworkReply& reply) QVariant statusCodeV = reply.attribute(QNetworkRequest::HttpStatusCodeAttribute); bool validStatus = false; int statusCode = statusCodeV.toInt(&validStatus); - if(validStatus) - { + if (validStatus) { // this leaves out 304 Not Modified gotFile = statusCode == 200 || statusCode == 203; } + // if we wrote any data to the save file, we try to commit the data to the real file. // if it actually got a proper file, we write it even if it was empty - if (gotFile || wroteAnyData) - { + if (gotFile || wroteAnyData) { // ask validators for data consistency // we only do this for actual downloads, not 'your data is still the same' cache hits - if(!finalizeAllValidators(reply)) + if (!finalizeAllValidators(reply)) return Task::State::Failed; + // nothing went wrong... - if (!m_output_file->commit()) - { + if (!m_output_file->commit()) { qCritical() << "Failed to commit changes to " << m_filename; m_output_file->cancelWriting(); return Task::State::Failed; } } + // then get rid of the save file m_output_file.reset(); return finalizeCache(reply); } -Task::State FileSink::finalizeCache(QNetworkReply &) +Task::State FileSink::initCache(QNetworkRequest&) +{ + return Task::State::Running; +} + +Task::State FileSink::finalizeCache(QNetworkReply&) { return Task::State::Succeeded; } @@ -101,4 +98,4 @@ bool FileSink::hasLocalData() QFileInfo info(m_filename); return info.exists() && info.size() != 0; } -} +} // namespace Net diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp index 8734e0bfb..b41a18b14 100644 --- a/launcher/net/HttpMetaCache.cpp +++ b/launcher/net/HttpMetaCache.cpp @@ -15,29 +15,26 @@ #include "HttpMetaCache.h" #include "FileSystem.h" +#include "Json.h" -#include -#include -#include #include +#include +#include +#include #include -#include -#include -#include - -QString MetaEntry::getFullPath() +auto MetaEntry::getFullPath() -> QString { // FIXME: make local? return FS::PathCombine(basePath, relativePath); } -HttpMetaCache::HttpMetaCache(QString path) : QObject() +HttpMetaCache::HttpMetaCache(QString path) : QObject(), m_index_file(path) { - m_index_file = path; saveBatchingTimer.setSingleShot(true); saveBatchingTimer.setTimerType(Qt::VeryCoarseTimer); + connect(&saveBatchingTimer, SIGNAL(timeout()), SLOT(SaveNow())); } @@ -47,45 +44,42 @@ HttpMetaCache::~HttpMetaCache() SaveNow(); } -MetaEntryPtr HttpMetaCache::getEntry(QString base, QString resource_path) +auto HttpMetaCache::getEntry(QString base, QString resource_path) -> MetaEntryPtr { // no base. no base path. can't store - if (!m_entries.contains(base)) - { + if (!m_entries.contains(base)) { // TODO: log problem - return MetaEntryPtr(); + return {}; } - EntryMap &map = m_entries[base]; - if (map.entry_list.contains(resource_path)) - { + + EntryMap& map = m_entries[base]; + if (map.entry_list.contains(resource_path)) { return map.entry_list[resource_path]; } - return MetaEntryPtr(); + + return {}; } -MetaEntryPtr HttpMetaCache::resolveEntry(QString base, QString resource_path, QString expected_etag) +auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString expected_etag) -> MetaEntryPtr { auto entry = getEntry(base, resource_path); // it's not present? generate a default stale entry - if (!entry) - { + if (!entry) { return staleEntry(base, resource_path); } - auto &selected_base = m_entries[base]; + auto& selected_base = m_entries[base]; QString real_path = FS::PathCombine(selected_base.base_path, resource_path); QFileInfo finfo(real_path); // is the file really there? if not -> stale - if (!finfo.isFile() || !finfo.isReadable()) - { + if (!finfo.isFile() || !finfo.isReadable()) { // if the file doesn't exist, we disown the entry selected_base.entry_list.remove(resource_path); return staleEntry(base, resource_path); } - if (!expected_etag.isEmpty() && expected_etag != entry->etag) - { + if (!expected_etag.isEmpty() && expected_etag != entry->etag) { // if the etag doesn't match expected, we disown the entry selected_base.entry_list.remove(resource_path); return staleEntry(base, resource_path); @@ -93,18 +87,15 @@ MetaEntryPtr HttpMetaCache::resolveEntry(QString base, QString resource_path, QS // if the file changed, check md5sum qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch(); - if (file_last_changed != entry->local_changed_timestamp) - { + if (file_last_changed != entry->local_changed_timestamp) { QFile input(real_path); input.open(QIODevice::ReadOnly); - QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5) - .toHex() - .constData(); - if (entry->md5sum != md5sum) - { + QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5).toHex().constData(); + if (entry->md5sum != md5sum) { selected_base.entry_list.remove(resource_path); return staleEntry(base, resource_path); } + // md5sums matched... keep entry and save the new state to file entry->local_changed_timestamp = file_last_changed; SaveEventually(); @@ -115,42 +106,42 @@ MetaEntryPtr HttpMetaCache::resolveEntry(QString base, QString resource_path, QS return entry; } -bool HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) +auto HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) -> bool { - if (!m_entries.contains(stale_entry->baseId)) - { - qCritical() << "Cannot add entry with unknown base: " - << stale_entry->baseId.toLocal8Bit(); + if (!m_entries.contains(stale_entry->baseId)) { + qCritical() << "Cannot add entry with unknown base: " << stale_entry->baseId.toLocal8Bit(); return false; } - if (stale_entry->stale) - { + + if (stale_entry->stale) { qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit(); return false; } + m_entries[stale_entry->baseId].entry_list[stale_entry->relativePath] = stale_entry; SaveEventually(); + + return true; +} + +auto HttpMetaCache::evictEntry(MetaEntryPtr entry) -> bool +{ + if (!entry) + return false; + + entry->stale = true; + SaveEventually(); return true; } -bool HttpMetaCache::evictEntry(MetaEntryPtr entry) -{ - if(entry) - { - entry->stale = true; - SaveEventually(); - return true; - } - return false; -} - -MetaEntryPtr HttpMetaCache::staleEntry(QString base, QString resource_path) +auto HttpMetaCache::staleEntry(QString base, QString resource_path) -> MetaEntryPtr { auto foo = new MetaEntry(); foo->baseId = base; foo->basePath = getBasePath(base); foo->relativePath = resource_path; foo->stale = true; + return MetaEntryPtr(foo); } @@ -159,24 +150,25 @@ void HttpMetaCache::addBase(QString base, QString base_root) // TODO: report error if (m_entries.contains(base)) return; + // TODO: check if the base path is valid EntryMap foo; foo.base_path = base_root; m_entries[base] = foo; } -QString HttpMetaCache::getBasePath(QString base) +auto HttpMetaCache::getBasePath(QString base) -> QString { - if (m_entries.contains(base)) - { + if (m_entries.contains(base)) { return m_entries[base].base_path; } - return QString(); + + return {}; } void HttpMetaCache::Load() { - if(m_index_file.isNull()) + if (m_index_file.isNull()) return; QFile index(m_index_file); @@ -184,41 +176,35 @@ void HttpMetaCache::Load() return; QJsonDocument json = QJsonDocument::fromJson(index.readAll()); - if (!json.isObject()) - return; - auto root = json.object(); + + auto root = Json::requireObject(json, "HttpMetaCache root"); + // check file version first - auto version_val = root.value("version"); - if (!version_val.isString()) - return; - if (version_val.toString() != "1") + auto version_val = Json::ensureString(root, "version"); + if (version_val != "1") return; // read the entry array - auto entries_val = root.value("entries"); - if (!entries_val.isArray()) - return; - QJsonArray array = entries_val.toArray(); - for (auto element : array) - { - if (!element.isObject()) - return; - auto element_obj = element.toObject(); - QString base = element_obj.value("base").toString(); + auto array = Json::ensureArray(root, "entries"); + for (auto element : array) { + auto element_obj = Json::ensureObject(element); + auto base = Json::ensureString(element_obj, "base"); if (!m_entries.contains(base)) continue; - auto &entrymap = m_entries[base]; + + auto& entrymap = m_entries[base]; + auto foo = new MetaEntry(); foo->baseId = base; - QString path = foo->relativePath = element_obj.value("path").toString(); - foo->md5sum = element_obj.value("md5sum").toString(); - foo->etag = element_obj.value("etag").toString(); - foo->local_changed_timestamp = element_obj.value("last_changed_timestamp").toDouble(); - foo->remote_changed_timestamp = - element_obj.value("remote_changed_timestamp").toString(); + foo->relativePath = Json::ensureString(element_obj, "path"); + foo->md5sum = Json::ensureString(element_obj, "md5sum"); + foo->etag = Json::ensureString(element_obj, "etag"); + foo->local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp"); + foo->remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp"); // presumed innocent until closer examination foo->stale = false; - entrymap.entry_list[path] = MetaEntryPtr(foo); + + entrymap.entry_list[foo->relativePath] = MetaEntryPtr(foo); } } @@ -231,42 +217,36 @@ void HttpMetaCache::SaveEventually() void HttpMetaCache::SaveNow() { - if(m_index_file.isNull()) + if (m_index_file.isNull()) return; + QJsonObject toplevel; - toplevel.insert("version", QJsonValue(QString("1"))); + Json::writeString(toplevel, "version", "1"); + QJsonArray entriesArr; - for (auto group : m_entries) - { - for (auto entry : group.entry_list) - { + for (auto group : m_entries) { + for (auto entry : group.entry_list) { // do not save stale entries. they are dead. - if(entry->stale) - { + if (entry->stale) { continue; } + QJsonObject entryObj; - entryObj.insert("base", QJsonValue(entry->baseId)); - entryObj.insert("path", QJsonValue(entry->relativePath)); - entryObj.insert("md5sum", QJsonValue(entry->md5sum)); - entryObj.insert("etag", QJsonValue(entry->etag)); - entryObj.insert("last_changed_timestamp", - QJsonValue(double(entry->local_changed_timestamp))); + Json::writeString(entryObj, "base", entry->baseId); + Json::writeString(entryObj, "path", entry->relativePath); + Json::writeString(entryObj, "md5sum", entry->md5sum); + Json::writeString(entryObj, "etag", entry->etag); + entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->local_changed_timestamp))); if (!entry->remote_changed_timestamp.isEmpty()) - entryObj.insert("remote_changed_timestamp", - QJsonValue(entry->remote_changed_timestamp)); + entryObj.insert("remote_changed_timestamp", QJsonValue(entry->remote_changed_timestamp)); entriesArr.append(entryObj); } } toplevel.insert("entries", entriesArr); - QJsonDocument doc(toplevel); - try - { - FS::write(m_index_file, doc.toJson()); - } - catch (const Exception &e) - { + try { + Json::write(toplevel, m_index_file); + } catch (const Exception& e) { qWarning() << e.what(); } } diff --git a/launcher/net/HttpMetaCache.h b/launcher/net/HttpMetaCache.h index 1c10e8c79..d8d1608e7 100644 --- a/launcher/net/HttpMetaCache.h +++ b/launcher/net/HttpMetaCache.h @@ -14,109 +14,88 @@ */ #pragma once -#include -#include #include +#include +#include #include class HttpMetaCache; -class MetaEntry -{ -friend class HttpMetaCache; -protected: - MetaEntry() {} -public: - bool isStale() - { - return stale; - } - void setStale(bool stale) - { - this->stale = stale; - } - QString getFullPath(); - QString getRemoteChangedTimestamp() - { - return remote_changed_timestamp; - } - void setRemoteChangedTimestamp(QString remote_changed_timestamp) - { - this->remote_changed_timestamp = remote_changed_timestamp; - } - void setLocalChangedTimestamp(qint64 timestamp) - { - local_changed_timestamp = timestamp; - } - QString getETag() - { - return etag; - } - void setETag(QString etag) - { - this->etag = etag; - } - QString getMD5Sum() - { - return md5sum; - } - void setMD5Sum(QString md5sum) - { - this->md5sum = md5sum; - } -protected: +class MetaEntry { + friend class HttpMetaCache; + + protected: + MetaEntry() = default; + + public: + auto isStale() -> bool { return stale; } + void setStale(bool stale) { this->stale = stale; } + + auto getFullPath() -> QString; + + auto getRemoteChangedTimestamp() -> QString { return remote_changed_timestamp; } + void setRemoteChangedTimestamp(QString remote_changed_timestamp) { this->remote_changed_timestamp = remote_changed_timestamp; } + void setLocalChangedTimestamp(qint64 timestamp) { local_changed_timestamp = timestamp; } + + auto getETag() -> QString { return etag; } + void setETag(QString etag) { this->etag = etag; } + + auto getMD5Sum() -> QString { return md5sum; } + void setMD5Sum(QString md5sum) { this->md5sum = md5sum; } + + protected: QString baseId; QString basePath; QString relativePath; QString md5sum; QString etag; qint64 local_changed_timestamp = 0; - QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time + QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time bool stale = true; }; -typedef std::shared_ptr MetaEntryPtr; +using MetaEntryPtr = std::shared_ptr; -class HttpMetaCache : public QObject -{ +class HttpMetaCache : public QObject { Q_OBJECT -public: + public: // supply path to the cache index file HttpMetaCache(QString path = QString()); - ~HttpMetaCache(); + ~HttpMetaCache() override; // get the entry solely from the cache // you probably don't want this, unless you have some specific caching needs. - MetaEntryPtr getEntry(QString base, QString resource_path); + auto getEntry(QString base, QString resource_path) -> MetaEntryPtr; // get the entry from cache and verify that it isn't stale (within reason) - MetaEntryPtr resolveEntry(QString base, QString resource_path, - QString expected_etag = QString()); + auto resolveEntry(QString base, QString resource_path, QString expected_etag = QString()) -> MetaEntryPtr; // add a previously resolved stale entry - bool updateEntry(MetaEntryPtr stale_entry); + auto updateEntry(MetaEntryPtr stale_entry) -> bool; // evict selected entry from cache - bool evictEntry(MetaEntryPtr entry); + auto evictEntry(MetaEntryPtr entry) -> bool; void addBase(QString base, QString base_root); // (re)start a timer that calls SaveNow later. void SaveEventually(); void Load(); - QString getBasePath(QString base); -public -slots: + + auto getBasePath(QString base) -> QString; + + public slots: void SaveNow(); -private: + private: // create a new stale entry, given the parameters - MetaEntryPtr staleEntry(QString base, QString resource_path); - struct EntryMap - { + auto staleEntry(QString base, QString resource_path) -> MetaEntryPtr; + + struct EntryMap { QString base_path; QMap entry_list; }; + QMap m_entries; QString m_index_file; QTimer saveBatchingTimer; diff --git a/launcher/net/Mode.h b/launcher/net/Mode.h index 9a95f5ad4..3d75981fb 100644 --- a/launcher/net/Mode.h +++ b/launcher/net/Mode.h @@ -1,10 +1,5 @@ #pragma once -namespace Net -{ -enum class Mode -{ - Offline, - Online -}; +namespace Net { +enum class Mode { Offline, Online }; } diff --git a/launcher/net/Sink.h b/launcher/net/Sink.h index 3b2a7f8dd..c88002203 100644 --- a/launcher/net/Sink.h +++ b/launcher/net/Sink.h @@ -8,14 +8,15 @@ namespace Net { class Sink { public: Sink() = default; - virtual ~Sink(){}; + virtual ~Sink() = default; public: - virtual Task::State init(QNetworkRequest& request) = 0; - virtual Task::State write(QByteArray& data) = 0; - virtual Task::State abort() = 0; - virtual Task::State finalize(QNetworkReply& reply) = 0; - virtual bool hasLocalData() = 0; + virtual auto init(QNetworkRequest& request) -> Task::State = 0; + virtual auto write(QByteArray& data) -> Task::State = 0; + virtual auto abort() -> Task::State = 0; + virtual auto finalize(QNetworkReply& reply) -> Task::State = 0; + + virtual auto hasLocalData() -> bool = 0; void addValidator(Validator* validator) { @@ -24,7 +25,15 @@ class Sink { } } - protected: /* methods */ + protected: + bool initAllValidators(QNetworkRequest& request) + { + for (auto& validator : validators) { + if (!validator->init(request)) + return false; + } + return true; + } bool finalizeAllValidators(QNetworkReply& reply) { for (auto& validator : validators) { @@ -41,14 +50,6 @@ class Sink { } return success; } - bool initAllValidators(QNetworkRequest& request) - { - for (auto& validator : validators) { - if (!validator->init(request)) - return false; - } - return true; - } bool writeAllValidators(QByteArray& data) { for (auto& validator : validators) { @@ -58,7 +59,7 @@ class Sink { return true; } - protected: /* data */ + protected: std::vector> validators; }; } // namespace Net From 57d65177c8ebb5463c88dd8e26f1e0a33f648bed Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 1 May 2022 11:05:31 -0300 Subject: [PATCH 012/308] fix: abort and fail logic in tasks Also sets up correctly the status connections --- launcher/net/Download.cpp | 9 ++++++--- launcher/net/NetJob.cpp | 3 +++ launcher/tasks/Task.cpp | 1 + launcher/tasks/Task.h | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index 3d6ca3382..9c01fa8dd 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -69,6 +69,8 @@ void Download::addValidator(Validator* v) void Download::executeTask() { + setStatus(tr("Downloading %1").arg(m_url.toString())); + if (getState() == Task::State::AbortedByUser) { qWarning() << "Attempt to start an aborted Download:" << m_url.toString(); emitAborted(); @@ -90,6 +92,7 @@ void Download::executeTask() emitFailed(); return; case State::AbortedByUser: + emitAborted(); return; } @@ -216,13 +219,13 @@ void Download::downloadFinished() qDebug() << "Download failed in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); - emitFailed(); + emit failed(""); return; } else if (m_state == State::AbortedByUser) { qDebug() << "Download aborted in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); - emitAborted(); + emit aborted(); return; } @@ -239,7 +242,7 @@ void Download::downloadFinished() qDebug() << "Download failed to finalize:" << m_url.toString(); m_sink->abort(); m_reply.reset(); - emitFailed(); + emit failed(""); return; } diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index a9f89da4c..906a735f6 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -47,7 +47,9 @@ auto NetJob::addNetAction(NetAction::Ptr action) -> bool if (action->isRunning()) { connect(action.get(), &NetAction::succeeded, [this, action]{ partSucceeded(action->index()); }); connect(action.get(), &NetAction::failed, [this, action](QString){ partFailed(action->index()); }); + connect(action.get(), &NetAction::aborted, [this, action](){ partAborted(action->index()); }); connect(action.get(), &NetAction::progress, [this, action](qint64 done, qint64 total) { partProgress(action->index(), done, total); }); + connect(action.get(), &NetAction::status, this, &NetJob::status); } else { m_todo.append(m_parts_progress.size() - 1); } @@ -222,6 +224,7 @@ void NetJob::startMoreParts() connect(part.get(), &NetAction::failed, this, [this, part](QString){ partFailed(part->index()); }); connect(part.get(), &NetAction::aborted, this, [this, part]{ partAborted(part->index()); }); connect(part.get(), &NetAction::progress, this, [this, part](qint64 done, qint64 total) { partProgress(part->index(), done, total); }); + connect(part.get(), &NetAction::status, this, &NetJob::status); part->startAction(m_network); } diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp index 68e0e8a7d..d2d62c9eb 100644 --- a/launcher/tasks/Task.cpp +++ b/launcher/tasks/Task.cpp @@ -100,6 +100,7 @@ void Task::emitAborted() m_failReason = "Aborted."; qDebug() << "Task" << describe() << "aborted."; emit aborted(); + emit finished(); } void Task::emitSucceeded() diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index e09c57aec..0ca37e021 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -79,7 +79,7 @@ class Task : public QObject { public slots: virtual void start(); - virtual bool abort() { return false; }; + virtual bool abort() { if(canAbort()) emitAborted(); return canAbort(); }; protected: virtual void executeTask() = 0; From 0bce08d30f2bbdeca19c375840880f69ffeac81b Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 2 May 2022 12:56:24 -0300 Subject: [PATCH 013/308] chore: add polymc license headers to launcher/net files --- launcher/net/ByteArraySink.h | 35 ++++++++++++++++++++++++++ launcher/net/ChecksumValidator.h | 35 ++++++++++++++++++++++++++ launcher/net/Download.cpp | 41 ++++++++++++++++++++++-------- launcher/net/Download.h | 40 +++++++++++++++++++++-------- launcher/net/FileSink.cpp | 35 ++++++++++++++++++++++++++ launcher/net/FileSink.h | 35 ++++++++++++++++++++++++++ launcher/net/HttpMetaCache.cpp | 40 +++++++++++++++++++++-------- launcher/net/HttpMetaCache.h | 43 ++++++++++++++++++++++++-------- launcher/net/MetaCacheSink.cpp | 35 ++++++++++++++++++++++++++ launcher/net/MetaCacheSink.h | 35 ++++++++++++++++++++++++++ launcher/net/NetAction.h | 1 + launcher/net/NetJob.cpp | 1 + launcher/net/NetJob.h | 1 + launcher/net/PasteUpload.cpp | 35 ++++++++++++++++++++++++++ launcher/net/PasteUpload.h | 36 ++++++++++++++++++++++++++ launcher/net/Sink.h | 35 ++++++++++++++++++++++++++ launcher/net/Validator.h | 35 ++++++++++++++++++++++++++ 17 files changed, 476 insertions(+), 42 deletions(-) diff --git a/launcher/net/ByteArraySink.h b/launcher/net/ByteArraySink.h index 8ae30bb31..501318a11 100644 --- a/launcher/net/ByteArraySink.h +++ b/launcher/net/ByteArraySink.h @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 . + * + * 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. + */ + #pragma once #include "Sink.h" diff --git a/launcher/net/ChecksumValidator.h b/launcher/net/ChecksumValidator.h index 8a8b10d57..a2ca2c7a4 100644 --- a/launcher/net/ChecksumValidator.h +++ b/launcher/net/ChecksumValidator.h @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 . + * + * 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. + */ + #pragma once #include "Validator.h" diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index 9c01fa8dd..97033de1a 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -1,22 +1,41 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 "Download.h" #include -#include #include #include "ByteArraySink.h" diff --git a/launcher/net/Download.h b/launcher/net/Download.h index 9fb671275..209329445 100644 --- a/launcher/net/Download.h +++ b/launcher/net/Download.h @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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. */ #pragma once diff --git a/launcher/net/FileSink.cpp b/launcher/net/FileSink.cpp index d2d2b06fb..ba0caf6c0 100644 --- a/launcher/net/FileSink.cpp +++ b/launcher/net/FileSink.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 . + * + * 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 "FileSink.h" #include "FileSystem.h" diff --git a/launcher/net/FileSink.h b/launcher/net/FileSink.h index 9d77b3d0f..dffbdca67 100644 --- a/launcher/net/FileSink.h +++ b/launcher/net/FileSink.h @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 . + * + * 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. + */ + #pragma once #include diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp index b41a18b14..4d86c0b84 100644 --- a/launcher/net/HttpMetaCache.cpp +++ b/launcher/net/HttpMetaCache.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 "HttpMetaCache.h" diff --git a/launcher/net/HttpMetaCache.h b/launcher/net/HttpMetaCache.h index d8d1608e7..e944b3d5d 100644 --- a/launcher/net/HttpMetaCache.h +++ b/launcher/net/HttpMetaCache.h @@ -1,22 +1,43 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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. */ #pragma once -#include + #include #include +#include #include class HttpMetaCache; diff --git a/launcher/net/MetaCacheSink.cpp b/launcher/net/MetaCacheSink.cpp index 34ba9f566..f86dd8704 100644 --- a/launcher/net/MetaCacheSink.cpp +++ b/launcher/net/MetaCacheSink.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 . + * + * 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 "MetaCacheSink.h" #include #include diff --git a/launcher/net/MetaCacheSink.h b/launcher/net/MetaCacheSink.h index 431e10a87..c9f7edfe7 100644 --- a/launcher/net/MetaCacheSink.h +++ b/launcher/net/MetaCacheSink.h @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 . + * + * 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. + */ + #pragma once #include "ChecksumValidator.h" diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h index 86a37ee6d..729d41329 100644 --- a/launcher/net/NetAction.h +++ b/launcher/net/NetAction.h @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln * * 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 diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index 906a735f6..df899178f 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln * * 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 diff --git a/launcher/net/NetJob.h b/launcher/net/NetJob.h index c397e2a1f..63c1cf517 100644 --- a/launcher/net/NetJob.h +++ b/launcher/net/NetJob.h @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln * * 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 diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index 52b82a0e1..e88c89877 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 . + * + * 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 "PasteUpload.h" #include "BuildConfig.h" #include "Application.h" diff --git a/launcher/net/PasteUpload.h b/launcher/net/PasteUpload.h index 62b2dc361..53979352c 100644 --- a/launcher/net/PasteUpload.h +++ b/launcher/net/PasteUpload.h @@ -1,4 +1,40 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 . + * + * 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. + */ + #pragma once + #include "tasks/Task.h" #include #include diff --git a/launcher/net/Sink.h b/launcher/net/Sink.h index c88002203..3870f29bc 100644 --- a/launcher/net/Sink.h +++ b/launcher/net/Sink.h @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 . + * + * 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. + */ + #pragma once #include "net/NetAction.h" diff --git a/launcher/net/Validator.h b/launcher/net/Validator.h index 59b72a0b0..e1d71d1ce 100644 --- a/launcher/net/Validator.h +++ b/launcher/net/Validator.h @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 . + * + * 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. + */ + #pragma once #include "net/NetAction.h" From dd2b324d8f7081f52decd90210ce11ef37625315 Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 2 May 2022 14:33:21 -0300 Subject: [PATCH 014/308] chore: add license header to remaining files Also remove some unused imports --- launcher/InstanceImportTask.cpp | 40 ++++++++++++++----- launcher/minecraft/AssetsUtils.cpp | 40 ++++++++++++++----- launcher/screenshots/ImgurAlbumCreation.cpp | 35 ++++++++++++++++ launcher/screenshots/ImgurAlbumCreation.h | 37 ++++++++++++++++- launcher/screenshots/ImgurUpload.cpp | 35 ++++++++++++++++ launcher/screenshots/ImgurUpload.h | 37 ++++++++++++++++- launcher/tasks/Task.cpp | 40 ++++++++++++++----- launcher/tasks/Task.h | 44 ++++++++++++++------- launcher/translations/TranslationsModel.cpp | 35 ++++++++++++++++ 9 files changed, 297 insertions(+), 46 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index fc3432c19..ca7e05903 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 "InstanceImportTask.h" diff --git a/launcher/minecraft/AssetsUtils.cpp b/launcher/minecraft/AssetsUtils.cpp index 281f730f5..15062c2b4 100644 --- a/launcher/minecraft/AssetsUtils.cpp +++ b/launcher/minecraft/AssetsUtils.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 diff --git a/launcher/screenshots/ImgurAlbumCreation.cpp b/launcher/screenshots/ImgurAlbumCreation.cpp index f94527c8b..7afdc5ccf 100644 --- a/launcher/screenshots/ImgurAlbumCreation.cpp +++ b/launcher/screenshots/ImgurAlbumCreation.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 . + * + * 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 "ImgurAlbumCreation.h" #include diff --git a/launcher/screenshots/ImgurAlbumCreation.h b/launcher/screenshots/ImgurAlbumCreation.h index 4cb0ed5dd..0228b6e4a 100644 --- a/launcher/screenshots/ImgurAlbumCreation.h +++ b/launcher/screenshots/ImgurAlbumCreation.h @@ -1,7 +1,42 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 . + * + * 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. + */ + #pragma once + #include "net/NetAction.h" #include "Screenshot.h" -#include "QObjectPtr.h" typedef shared_qobject_ptr ImgurAlbumCreationPtr; class ImgurAlbumCreation : public NetAction diff --git a/launcher/screenshots/ImgurUpload.cpp b/launcher/screenshots/ImgurUpload.cpp index 05314de75..fbcfb95f5 100644 --- a/launcher/screenshots/ImgurUpload.cpp +++ b/launcher/screenshots/ImgurUpload.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 . + * + * 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 "ImgurUpload.h" #include "BuildConfig.h" diff --git a/launcher/screenshots/ImgurUpload.h b/launcher/screenshots/ImgurUpload.h index a10405510..404dc8765 100644 --- a/launcher/screenshots/ImgurUpload.h +++ b/launcher/screenshots/ImgurUpload.h @@ -1,5 +1,40 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 . + * + * 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. + */ + #pragma once -#include "QObjectPtr.h" + #include "net/NetAction.h" #include "Screenshot.h" diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp index d2d62c9eb..bb71b98c8 100644 --- a/launcher/tasks/Task.cpp +++ b/launcher/tasks/Task.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 "Task.h" diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 0ca37e021..f0e6e4023 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -1,24 +1,40 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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. */ #pragma once -#include -#include -#include - #include "QObjectPtr.h" class Task : public QObject { diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index fbd170607..53722d690 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 . + * + * 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 "TranslationsModel.h" #include From 067484a6a8647e6012f3fdad61653716cfb44470 Mon Sep 17 00:00:00 2001 From: icelimetea Date: Fri, 13 May 2022 16:59:00 +0100 Subject: [PATCH 015/308] Fix formatting --- libraries/launcher/org/multimc/EntryPoint.java | 4 +++- libraries/launcher/org/multimc/applet/LegacyFrame.java | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/libraries/launcher/org/multimc/EntryPoint.java b/libraries/launcher/org/multimc/EntryPoint.java index 416f21890..0244a04d8 100644 --- a/libraries/launcher/org/multimc/EntryPoint.java +++ b/libraries/launcher/org/multimc/EntryPoint.java @@ -1,4 +1,4 @@ -package org.multimc;/* +/* * Copyright 2012-2021 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +14,8 @@ package org.multimc;/* * limitations under the License. */ +package org.multimc; + import org.multimc.exception.ParseException; import org.multimc.utils.Parameters; diff --git a/libraries/launcher/org/multimc/applet/LegacyFrame.java b/libraries/launcher/org/multimc/applet/LegacyFrame.java index f82cb6057..caec079c3 100644 --- a/libraries/launcher/org/multimc/applet/LegacyFrame.java +++ b/libraries/launcher/org/multimc/applet/LegacyFrame.java @@ -1,4 +1,4 @@ -package org.multimc.applet;/* +/* * Copyright 2012-2021 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +14,8 @@ package org.multimc.applet;/* * limitations under the License. */ +package org.multimc.applet; + import net.minecraft.Launcher; import javax.imageio.ImageIO; From c054d0f329a9d1d3ae76a605d82f0ad8e0ebdc99 Mon Sep 17 00:00:00 2001 From: icelimetea Date: Fri, 13 May 2022 17:21:35 +0100 Subject: [PATCH 016/308] Add the license header to LauncherFactory --- .../launcher/org/multimc/LauncherFactory.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/libraries/launcher/org/multimc/LauncherFactory.java b/libraries/launcher/org/multimc/LauncherFactory.java index 17e0d9058..007ce7e83 100644 --- a/libraries/launcher/org/multimc/LauncherFactory.java +++ b/libraries/launcher/org/multimc/LauncherFactory.java @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 icelimetea, + * + * 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 . + * + * 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. + */ + package org.multimc; import org.multimc.impl.OneSixLauncher; From c3336251e0789fae6da5935c0e2b7f38eab08763 Mon Sep 17 00:00:00 2001 From: icelimetea Date: Fri, 13 May 2022 18:10:11 +0100 Subject: [PATCH 017/308] Add the license header to EntryPoint --- .../launcher/org/multimc/EntryPoint.java | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/libraries/launcher/org/multimc/EntryPoint.java b/libraries/launcher/org/multimc/EntryPoint.java index 0244a04d8..ba5b0926f 100644 --- a/libraries/launcher/org/multimc/EntryPoint.java +++ b/libraries/launcher/org/multimc/EntryPoint.java @@ -1,17 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0-only /* - * Copyright 2012-2021 MultiMC Contributors + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 icelimetea, * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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. */ package org.multimc; From fac0b027b31ba2ac29730f6091b3d19ba78b40d2 Mon Sep 17 00:00:00 2001 From: icelimetea Date: Sat, 14 May 2022 16:46:57 +0100 Subject: [PATCH 018/308] Fix the license header --- .../launcher/org/multimc/LauncherFactory.java | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/libraries/launcher/org/multimc/LauncherFactory.java b/libraries/launcher/org/multimc/LauncherFactory.java index 007ce7e83..1b30a4151 100644 --- a/libraries/launcher/org/multimc/LauncherFactory.java +++ b/libraries/launcher/org/multimc/LauncherFactory.java @@ -14,23 +14,6 @@ * * 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. */ package org.multimc; From 3f259eb97a207c6d4d0ae3ad481541eda96df798 Mon Sep 17 00:00:00 2001 From: icelimetea Date: Sat, 14 May 2022 16:48:14 +0100 Subject: [PATCH 019/308] Refactor script parsing --- .../launcher/org/multimc/EntryPoint.java | 43 ++++++------------- .../launcher/org/multimc/LauncherFactory.java | 4 +- 2 files changed, 16 insertions(+), 31 deletions(-) diff --git a/libraries/launcher/org/multimc/EntryPoint.java b/libraries/launcher/org/multimc/EntryPoint.java index ba5b0926f..c0500bbec 100644 --- a/libraries/launcher/org/multimc/EntryPoint.java +++ b/libraries/launcher/org/multimc/EntryPoint.java @@ -51,8 +51,6 @@ public final class EntryPoint { private final Parameters params = new Parameters(); - private String launcherType; - public static void main(String[] args) { EntryPoint listener = new EntryPoint(); @@ -80,15 +78,6 @@ public final class EntryPoint { return Action.Abort; } - case "launcher": { - if (tokens.length != 2) - throw new ParseException("Expected 2 tokens, got " + tokens.length); - - launcherType = tokens[1]; - - return Action.Proceed; - } - default: { if (tokens.length != 2) throw new ParseException("Error while parsing:" + inData); @@ -129,30 +118,24 @@ public final class EntryPoint { return 1; } - if (launcherType != null) { - try { - Launcher launcher = - LauncherFactory - .getInstance() - .createLauncher(launcherType, params); + try { + Launcher launcher = + LauncherFactory + .getInstance() + .createLauncher(params); - launcher.launch(); + launcher.launch(); - return 0; - } catch (IllegalArgumentException e) { - LOGGER.log(Level.SEVERE, "Wrong argument.", e); + return 0; + } catch (IllegalArgumentException e) { + LOGGER.log(Level.SEVERE, "Wrong argument.", e); - return 1; - } catch (Exception e) { - LOGGER.log(Level.SEVERE, "Exception caught from launcher.", e); + return 1; + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Exception caught from launcher.", e); - return 1; - } + return 1; } - - LOGGER.log(Level.SEVERE, "No valid launcher implementation specified."); - - return 1; } private enum Action { diff --git a/libraries/launcher/org/multimc/LauncherFactory.java b/libraries/launcher/org/multimc/LauncherFactory.java index 1b30a4151..a2af8581a 100644 --- a/libraries/launcher/org/multimc/LauncherFactory.java +++ b/libraries/launcher/org/multimc/LauncherFactory.java @@ -39,7 +39,9 @@ public final class LauncherFactory { }); } - public Launcher createLauncher(String name, Parameters parameters) { + public Launcher createLauncher(Parameters parameters) { + String name = parameters.first("launcher"); + LauncherProvider launcherProvider = launcherRegistry.get(name); if (launcherProvider == null) From 2b52cf01f5999db4a8b1ea009cb5d24dd4eb4e1c Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Sat, 14 May 2022 19:51:23 -0400 Subject: [PATCH 020/308] Build Windows installer --- .github/workflows/build.yml | 17 +++- program_info/win_install.nsi | 169 +++++++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 program_info/win_install.nsi diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0590b3480..7ab30d456 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,6 +63,7 @@ jobs: ninja:p qt5:p ccache:p + nsis:p - name: Setup ccache if: runner.os != 'Windows' && inputs.build_type == 'Debug' @@ -100,7 +101,7 @@ jobs: run: | brew update brew install qt@5 ninja - + - name: Update Qt (AppImage) if: runner.os == 'Linux' && matrix.appimage == true run: | @@ -190,6 +191,13 @@ jobs: cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable + - name: Package (Windows, installer) + if: runner.os == 'Windows' + shell: msys2 {0} + run: | + cd ${{ env.INSTALL_PORTABLE_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" + - name: Package (Linux) if: runner.os == 'Linux' && matrix.appimage != true run: | @@ -257,6 +265,13 @@ jobs: name: PolyMC-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }} path: ${{ env.INSTALL_PORTABLE_DIR }}/** + - name: Upload installer (Windows) + if: runner.os == 'Windows' + uses: actions/upload-artifact@v3 + with: + name: PolyMC-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}-Setup + path: PolyMC-Setup.exe + - name: Upload binary tarball (Linux) if: runner.os == 'Linux' && matrix.appimage != true uses: actions/upload-artifact@v3 diff --git a/program_info/win_install.nsi b/program_info/win_install.nsi new file mode 100644 index 000000000..2b0d97603 --- /dev/null +++ b/program_info/win_install.nsi @@ -0,0 +1,169 @@ +!define MULTIUSER_EXECUTIONLEVEL Highest +!define MULTIUSER_MUI +!define MULTIUSER_INSTALLMODE_COMMANDLINE + +!define MULTIUSER_INSTALLMODE_INSTDIR PolyMC +!define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY Software\PolyMC +!define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME InstallDir + +!include "FileFunc.nsh" +!include "MUI2.nsh" +!include "MultiUser.nsh" + +Name "PolyMC" +RequestExecutionLevel highest + +;-------------------------------- + +; Pages + +!insertmacro MUI_PAGE_WELCOME +!insertmacro MULTIUSER_PAGE_INSTALLMODE +!define MUI_COMPONENTSPAGE_NODESC +!insertmacro MUI_PAGE_COMPONENTS +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!define MUI_FINISHPAGE_RUN "$InstDir\polymc.exe" +!insertmacro MUI_PAGE_FINISH + +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES + +!insertmacro MUI_LANGUAGE "English" + +;-------------------------------- + +; The stuff to install +Section "PolyMC" + + SectionIn RO + + nsExec::Exec /TIMEOUT=2000 'TaskKill /IM polymc.exe /F' + + SetOutPath $INSTDIR + + File "polymc.exe" + File "qt.conf" + File *.dll + File /r "iconengines" + File /r "imageformats" + File /r "jars" + File /r "platforms" + File /r "styles" + + ; Write the installation path into the registry + WriteRegStr SHCTX SOFTWARE\PolyMC "InstallDir" "$INSTDIR" + + ; Write the uninstall keys for Windows + !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\PolyMC" + WriteRegStr SHCTX "${UNINST_KEY}" "DisplayName" "PolyMC" + WriteRegStr SHCTX "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\polymc.exe" + WriteRegStr SHCTX "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe" /$MultiUser.InstallMode' + WriteRegStr SHCTX "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /$MultiUser.InstallMode /S' + WriteRegStr SHCTX "${UNINST_KEY}" "InstallLocation" "$INSTDIR" + WriteRegStr SHCTX "${UNINST_KEY}" "Publisher" "PolyMC Contributors" + WriteRegStr SHCTX "${UNINST_KEY}" "ProductVersion" "${VERSION}" + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD SHCTX "${UNINST_KEY}" "EstimatedSize" "$0" + WriteRegDWORD SHCTX "${UNINST_KEY}" "NoModify" 1 + WriteRegDWORD SHCTX "${UNINST_KEY}" "NoRepair" 1 + WriteUninstaller "$INSTDIR\uninstall.exe" + +SectionEnd + +Section "Start Menu Shortcuts" + + CreateShortcut "$SMPROGRAMS\PolyMC.lnk" "$INSTDIR\polymc.exe" "" "$INSTDIR\polymc.exe" 0 + +SectionEnd + +Section /o "Portable" + + SetOutPath $INSTDIR + File "portable.txt" + +SectionEnd + +;-------------------------------- + +; Uninstaller + +Section "Uninstall" + + nsExec::Exec /TIMEOUT=2000 'TaskKill /IM polymc.exe /F' + + DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\PolyMC" + DeleteRegKey SHCTX SOFTWARE\PolyMC + + Delete $INSTDIR\polymc.exe + Delete $INSTDIR\uninstall.exe + Delete $INSTDIR\portable.txt + + Delete $INSTDIR\libbrotlicommon.dll + Delete $INSTDIR\libbrotlidec.dll + Delete $INSTDIR\libbz2-1.dll + Delete $INSTDIR\libcrypto-1_1-x64.dll + Delete $INSTDIR\libcrypto-1_1.dll + Delete $INSTDIR\libdouble-conversion.dll + Delete $INSTDIR\libfreetype-6.dll + Delete $INSTDIR\libgcc_s_seh-1.dll + Delete $INSTDIR\libgcc_s_dw2-1.dll + Delete $INSTDIR\libglib-2.0-0.dll + Delete $INSTDIR\libgraphite2.dll + Delete $INSTDIR\libharfbuzz-0.dll + Delete $INSTDIR\libiconv-2.dll + Delete $INSTDIR\libicudt69.dll + Delete $INSTDIR\libicuin69.dll + Delete $INSTDIR\libicuuc69.dll + Delete $INSTDIR\libintl-8.dll + Delete $INSTDIR\libjasper-4.dll + Delete $INSTDIR\libjpeg-8.dll + Delete $INSTDIR\libmd4c.dll + Delete $INSTDIR\libpcre-1.dll + Delete $INSTDIR\libpcre2-16-0.dll + Delete $INSTDIR\libpng16-16.dll + Delete $INSTDIR\libssl-1_1-x64.dll + Delete $INSTDIR\libssl-1_1.dll + Delete $INSTDIR\libssp-0.dll + Delete $INSTDIR\libstdc++-6.dll + Delete $INSTDIR\libwebp-7.dll + Delete $INSTDIR\libwebpdemux-2.dll + Delete $INSTDIR\libwebpmux-3.dll + Delete $INSTDIR\libwinpthread-1.dll + Delete $INSTDIR\libzstd.dll + Delete $INSTDIR\Qt5Core.dll + Delete $INSTDIR\Qt5Gui.dll + Delete $INSTDIR\Qt5Network.dll + Delete $INSTDIR\Qt5Qml.dll + Delete $INSTDIR\Qt5QmlModels.dll + Delete $INSTDIR\Qt5Quick.dll + Delete $INSTDIR\Qt5Svg.dll + Delete $INSTDIR\Qt5WebSockets.dll + Delete $INSTDIR\Qt5Widgets.dll + Delete $INSTDIR\Qt5Xml.dll + Delete $INSTDIR\zlib1.dll + + Delete $INSTDIR\qt.conf + + RMDir /r $INSTDIR\iconengines + RMDir /r $INSTDIR\imageformats + RMDir /r $INSTDIR\jars + RMDir /r $INSTDIR\platforms + RMDir /r $INSTDIR\styles + + Delete "$SMPROGRAMS\PolyMC.lnk" + + RMDir "$INSTDIR" + +SectionEnd + +; Multi-user + +Function .onInit + !insertmacro MULTIUSER_INIT +FunctionEnd + +Function un.onInit + !insertmacro MULTIUSER_UNINIT +FunctionEnd From 2993318d195812f4b03c703aa9e68aeff941aece Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Mon, 16 May 2022 15:29:37 -0400 Subject: [PATCH 021/308] Remove admin requirement (no multi-user install option) --- program_info/win_install.nsi | 54 +++++++++++++----------------------- 1 file changed, 20 insertions(+), 34 deletions(-) diff --git a/program_info/win_install.nsi b/program_info/win_install.nsi index 2b0d97603..18a1b64ec 100644 --- a/program_info/win_install.nsi +++ b/program_info/win_install.nsi @@ -1,24 +1,16 @@ -!define MULTIUSER_EXECUTIONLEVEL Highest -!define MULTIUSER_MUI -!define MULTIUSER_INSTALLMODE_COMMANDLINE - -!define MULTIUSER_INSTALLMODE_INSTDIR PolyMC -!define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY Software\PolyMC -!define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME InstallDir - !include "FileFunc.nsh" !include "MUI2.nsh" -!include "MultiUser.nsh" Name "PolyMC" -RequestExecutionLevel highest +InstallDir "$LOCALAPPDATA\PolyMC" +InstallDirRegKey HKCU "Software\PolyMC" "InstallDir" +RequestExecutionLevel user ;-------------------------------- ; Pages !insertmacro MUI_PAGE_WELCOME -!insertmacro MULTIUSER_PAGE_INSTALLMODE !define MUI_COMPONENTSPAGE_NODESC !insertmacro MUI_PAGE_COMPONENTS !insertmacro MUI_PAGE_DIRECTORY @@ -29,6 +21,10 @@ RequestExecutionLevel highest !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES +;-------------------------------- + +; Languages + !insertmacro MUI_LANGUAGE "English" ;-------------------------------- @@ -52,22 +48,22 @@ Section "PolyMC" File /r "styles" ; Write the installation path into the registry - WriteRegStr SHCTX SOFTWARE\PolyMC "InstallDir" "$INSTDIR" + WriteRegStr HKCU Software\PolyMC "InstallDir" "$INSTDIR" ; Write the uninstall keys for Windows !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\PolyMC" - WriteRegStr SHCTX "${UNINST_KEY}" "DisplayName" "PolyMC" - WriteRegStr SHCTX "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\polymc.exe" - WriteRegStr SHCTX "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe" /$MultiUser.InstallMode' - WriteRegStr SHCTX "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /$MultiUser.InstallMode /S' - WriteRegStr SHCTX "${UNINST_KEY}" "InstallLocation" "$INSTDIR" - WriteRegStr SHCTX "${UNINST_KEY}" "Publisher" "PolyMC Contributors" - WriteRegStr SHCTX "${UNINST_KEY}" "ProductVersion" "${VERSION}" + WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "PolyMC" + WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\polymc.exe" + WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"' + WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S' + WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR" + WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "PolyMC Contributors" + WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "${VERSION}" ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 IntFmt $0 "0x%08X" $0 - WriteRegDWORD SHCTX "${UNINST_KEY}" "EstimatedSize" "$0" - WriteRegDWORD SHCTX "${UNINST_KEY}" "NoModify" 1 - WriteRegDWORD SHCTX "${UNINST_KEY}" "NoRepair" 1 + WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0" + WriteRegDWORD HKCU "${UNINST_KEY}" "NoModify" 1 + WriteRegDWORD HKCU "${UNINST_KEY}" "NoRepair" 1 WriteUninstaller "$INSTDIR\uninstall.exe" SectionEnd @@ -93,8 +89,8 @@ Section "Uninstall" nsExec::Exec /TIMEOUT=2000 'TaskKill /IM polymc.exe /F' - DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\PolyMC" - DeleteRegKey SHCTX SOFTWARE\PolyMC + DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\PolyMC" + DeleteRegKey HKCU SOFTWARE\PolyMC Delete $INSTDIR\polymc.exe Delete $INSTDIR\uninstall.exe @@ -157,13 +153,3 @@ Section "Uninstall" RMDir "$INSTDIR" SectionEnd - -; Multi-user - -Function .onInit - !insertmacro MULTIUSER_INIT -FunctionEnd - -Function un.onInit - !insertmacro MULTIUSER_UNINIT -FunctionEnd From 6dfec4db40f09697f34f65419edb7d689e3c5dc7 Mon Sep 17 00:00:00 2001 From: Lenny McLennington Date: Tue, 17 May 2022 00:21:57 +0100 Subject: [PATCH 022/308] Fix toolbar disappearing in a certain circumstance. --- launcher/ui/MainWindow.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index ca345b1f6..3f8545119 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1865,6 +1865,9 @@ void MainWindow::globalSettingsClosed() updateMainToolBar(); updateToolsMenu(); updateStatusCenter(); + // This needs to be done to prevent UI elements disappearing in the event the config is changed + // but PolyMC exits abnormally, causing the window state to never be saved: + APPLICATION->settings()->set("MainWindowState", saveState().toBase64()); update(); } From 85ec9d95a43cc884224095477e7321b84d2cc99f Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Mon, 16 May 2022 19:28:04 -0400 Subject: [PATCH 023/308] Support installer languages other than English --- program_info/win_install.nsi | 68 ++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/program_info/win_install.nsi b/program_info/win_install.nsi index 18a1b64ec..ce13b8b00 100644 --- a/program_info/win_install.nsi +++ b/program_info/win_install.nsi @@ -1,6 +1,8 @@ !include "FileFunc.nsh" !include "MUI2.nsh" +Unicode true + Name "PolyMC" InstallDir "$LOCALAPPDATA\PolyMC" InstallDirRegKey HKCU "Software\PolyMC" "InstallDir" @@ -26,6 +28,72 @@ RequestExecutionLevel user ; Languages !insertmacro MUI_LANGUAGE "English" +!insertmacro MUI_LANGUAGE "French" +!insertmacro MUI_LANGUAGE "German" +!insertmacro MUI_LANGUAGE "Spanish" +!insertmacro MUI_LANGUAGE "SpanishInternational" +!insertmacro MUI_LANGUAGE "SimpChinese" +!insertmacro MUI_LANGUAGE "TradChinese" +!insertmacro MUI_LANGUAGE "Japanese" +!insertmacro MUI_LANGUAGE "Korean" +!insertmacro MUI_LANGUAGE "Italian" +!insertmacro MUI_LANGUAGE "Dutch" +!insertmacro MUI_LANGUAGE "Danish" +!insertmacro MUI_LANGUAGE "Swedish" +!insertmacro MUI_LANGUAGE "Norwegian" +!insertmacro MUI_LANGUAGE "NorwegianNynorsk" +!insertmacro MUI_LANGUAGE "Finnish" +!insertmacro MUI_LANGUAGE "Greek" +!insertmacro MUI_LANGUAGE "Russian" +!insertmacro MUI_LANGUAGE "Portuguese" +!insertmacro MUI_LANGUAGE "PortugueseBR" +!insertmacro MUI_LANGUAGE "Polish" +!insertmacro MUI_LANGUAGE "Ukrainian" +!insertmacro MUI_LANGUAGE "Czech" +!insertmacro MUI_LANGUAGE "Slovak" +!insertmacro MUI_LANGUAGE "Croatian" +!insertmacro MUI_LANGUAGE "Bulgarian" +!insertmacro MUI_LANGUAGE "Hungarian" +!insertmacro MUI_LANGUAGE "Thai" +!insertmacro MUI_LANGUAGE "Romanian" +!insertmacro MUI_LANGUAGE "Latvian" +!insertmacro MUI_LANGUAGE "Macedonian" +!insertmacro MUI_LANGUAGE "Estonian" +!insertmacro MUI_LANGUAGE "Turkish" +!insertmacro MUI_LANGUAGE "Lithuanian" +!insertmacro MUI_LANGUAGE "Slovenian" +!insertmacro MUI_LANGUAGE "Serbian" +!insertmacro MUI_LANGUAGE "SerbianLatin" +!insertmacro MUI_LANGUAGE "Arabic" +!insertmacro MUI_LANGUAGE "Farsi" +!insertmacro MUI_LANGUAGE "Hebrew" +!insertmacro MUI_LANGUAGE "Indonesian" +!insertmacro MUI_LANGUAGE "Mongolian" +!insertmacro MUI_LANGUAGE "Luxembourgish" +!insertmacro MUI_LANGUAGE "Albanian" +!insertmacro MUI_LANGUAGE "Breton" +!insertmacro MUI_LANGUAGE "Belarusian" +!insertmacro MUI_LANGUAGE "Icelandic" +!insertmacro MUI_LANGUAGE "Malay" +!insertmacro MUI_LANGUAGE "Bosnian" +!insertmacro MUI_LANGUAGE "Kurdish" +!insertmacro MUI_LANGUAGE "Irish" +!insertmacro MUI_LANGUAGE "Uzbek" +!insertmacro MUI_LANGUAGE "Galician" +!insertmacro MUI_LANGUAGE "Afrikaans" +!insertmacro MUI_LANGUAGE "Catalan" +!insertmacro MUI_LANGUAGE "Esperanto" +!insertmacro MUI_LANGUAGE "Asturian" +!insertmacro MUI_LANGUAGE "Basque" +!insertmacro MUI_LANGUAGE "Pashto" +!insertmacro MUI_LANGUAGE "ScotsGaelic" +!insertmacro MUI_LANGUAGE "Georgian" +!insertmacro MUI_LANGUAGE "Vietnamese" +!insertmacro MUI_LANGUAGE "Welsh" +!insertmacro MUI_LANGUAGE "Armenian" +!insertmacro MUI_LANGUAGE "Corsican" +!insertmacro MUI_LANGUAGE "Tatar" +!insertmacro MUI_LANGUAGE "Hindi" ;-------------------------------- From 96deb5b09d6729f4b5f3a5c1880b526ee9882326 Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 17 May 2022 06:36:30 -0300 Subject: [PATCH 024/308] chore: remove copyright from files i didnt mess with This is what happens when you auto-pilot stuff xdd --- launcher/net/PasteUpload.cpp | 1 - launcher/net/PasteUpload.h | 1 - launcher/net/Validator.h | 1 - 3 files changed, 3 deletions(-) diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index e88c89877..3d106c927 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher - * Copyright (c) 2022 flowln * * 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 diff --git a/launcher/net/PasteUpload.h b/launcher/net/PasteUpload.h index 53979352c..ea3a06d3d 100644 --- a/launcher/net/PasteUpload.h +++ b/launcher/net/PasteUpload.h @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher - * Copyright (c) 2022 flowln * * 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 diff --git a/launcher/net/Validator.h b/launcher/net/Validator.h index e1d71d1ce..6b3d46352 100644 --- a/launcher/net/Validator.h +++ b/launcher/net/Validator.h @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher - * Copyright (c) 2022 flowln * * 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 From 17bbfe8d8951ddc7acca0222c6d2e38fb29eef25 Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 17 May 2022 06:47:00 -0300 Subject: [PATCH 025/308] fix: virtual signal in Task.h --- launcher/tasks/Task.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index f0e6e4023..f7765c3db 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -86,7 +86,7 @@ class Task : public QObject { signals: void started(); - virtual void progress(qint64 current, qint64 total); + void progress(qint64 current, qint64 total); void finished(); void succeeded(); void aborted(); From 441075f61051cce8e5d6b0311febdefc087fdbbf Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 18 May 2022 17:17:16 -0300 Subject: [PATCH 026/308] fix: version field in technic pack manifest being null Sometimes, the version field, that is supposed to be a string, was a null instead. Inspecting other entries, seems like the default for not having a version should be "", so I made it like that in case the version was null. I hope this fixes the issue :^) --- launcher/modplatform/technic/SolderPackManifest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/modplatform/technic/SolderPackManifest.cpp b/launcher/modplatform/technic/SolderPackManifest.cpp index 16fe0b0e6..e52a7ec07 100644 --- a/launcher/modplatform/technic/SolderPackManifest.cpp +++ b/launcher/modplatform/technic/SolderPackManifest.cpp @@ -37,7 +37,7 @@ void loadPack(Pack& v, QJsonObject& obj) static void loadPackBuildMod(PackBuildMod& b, QJsonObject& obj) { b.name = Json::requireString(obj, "name"); - b.version = Json::requireString(obj, "version"); + b.version = Json::ensureString(obj, "version", ""); b.md5 = Json::requireString(obj, "md5"); b.url = Json::requireString(obj, "url"); } From 77caaca50dab7ba8e455d641ac6b448052bc6799 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 19 May 2022 08:09:18 +0200 Subject: [PATCH 027/308] fix: only consider enabled mod loaders --- launcher/minecraft/PackProfile.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index d53f41e1b..87d11c4c7 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -36,6 +36,13 @@ #include "ComponentUpdateTask.h" #include "Application.h" +#include "modplatform/ModAPI.h" + +static const QMap modloaderMapping{ + {"net.minecraftforge", ModAPI::Forge}, + {"net.fabricmc.fabric-loader", ModAPI::Fabric}, + {"org.quiltmc.quilt-loader", ModAPI::Quilt} +}; PackProfile::PackProfile(MinecraftInstance * instance) : QAbstractListModel() @@ -973,17 +980,15 @@ void PackProfile::disableInteraction(bool disable) ModAPI::ModLoaderType PackProfile::getModLoader() { - if (!getComponentVersion("net.minecraftforge").isEmpty()) + QMapIterator i(modloaderMapping); + + while (i.hasNext()) { - return ModAPI::Forge; - } - else if (!getComponentVersion("net.fabricmc.fabric-loader").isEmpty()) - { - return ModAPI::Fabric; - } - else if (!getComponentVersion("org.quiltmc.quilt-loader").isEmpty()) - { - return ModAPI::Quilt; + i.next(); + Component* c = getComponent(i.key()); + if (c != nullptr && c->isEnabled()) { + return i.value(); + } } return ModAPI::Unspecified; } From 943090db98dbbe969afed8a4fb59f4bbb43449cc Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 19 May 2022 08:40:28 +0200 Subject: [PATCH 028/308] refactor: allow tracking multiple mod loaders --- launcher/minecraft/PackProfile.cpp | 8 +++-- launcher/minecraft/PackProfile.h | 2 +- launcher/modplatform/ModAPI.h | 15 +++++--- launcher/modplatform/flame/FlameAPI.h | 17 +++++---- launcher/modplatform/modrinth/ModrinthAPI.h | 35 ++++++++----------- launcher/ui/pages/instance/ModFolderPage.cpp | 2 +- launcher/ui/pages/modplatform/ModModel.cpp | 4 +-- launcher/ui/pages/modplatform/ModPage.cpp | 2 +- launcher/ui/pages/modplatform/ModPage.h | 2 +- .../pages/modplatform/flame/FlameModPage.cpp | 4 +-- .../ui/pages/modplatform/flame/FlameModPage.h | 2 +- .../modplatform/modrinth/ModrinthModPage.cpp | 4 +-- .../modplatform/modrinth/ModrinthModPage.h | 2 +- 13 files changed, 54 insertions(+), 45 deletions(-) diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index 87d11c4c7..125048f05 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -978,8 +978,10 @@ void PackProfile::disableInteraction(bool disable) } } -ModAPI::ModLoaderType PackProfile::getModLoader() +ModAPI::ModLoaderTypes PackProfile::getModLoaders() { + ModAPI::ModLoaderTypes result = ModAPI::Unspecified; + QMapIterator i(modloaderMapping); while (i.hasNext()) @@ -987,8 +989,8 @@ ModAPI::ModLoaderType PackProfile::getModLoader() i.next(); Component* c = getComponent(i.key()); if (c != nullptr && c->isEnabled()) { - return i.value(); + result |= i.value(); } } - return ModAPI::Unspecified; + return result; } diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h index ab4cd5c88..918e7f7ad 100644 --- a/launcher/minecraft/PackProfile.h +++ b/launcher/minecraft/PackProfile.h @@ -118,7 +118,7 @@ public: // todo(merged): is this the best approach void appendComponent(ComponentPtr component); - ModAPI::ModLoaderType getModLoader(); + ModAPI::ModLoaderTypes getModLoaders(); private: void scheduleSave(); diff --git a/launcher/modplatform/ModAPI.h b/launcher/modplatform/ModAPI.h index 8e6cd45c9..4230df0bc 100644 --- a/launcher/modplatform/ModAPI.h +++ b/launcher/modplatform/ModAPI.h @@ -16,14 +16,21 @@ class ModAPI { public: virtual ~ModAPI() = default; - // https://docs.curseforge.com/?http#tocS_ModLoaderType - enum ModLoaderType { Unspecified = 0, Forge = 1, Cauldron = 2, LiteLoader = 3, Fabric = 4, Quilt = 5 }; + enum ModLoaderType { + Unspecified = 0, + Forge = 1 << 0, + Cauldron = 1 << 1, + LiteLoader = 1 << 2, + Fabric = 1 << 3, + Quilt = 1 << 4 + }; + Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType) struct SearchArgs { int offset; QString search; QString sorting; - ModLoaderType mod_loader; + ModLoaderTypes loaders; std::list versions; }; @@ -33,7 +40,7 @@ class ModAPI { struct VersionSearchArgs { QString addonId; std::list mcVersions; - ModLoaderType loader; + ModLoaderTypes loaders; }; virtual void getVersions(CallerType* caller, VersionSearchArgs&& args) const = 0; diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 61628e603..8bb33d477 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -37,14 +37,14 @@ class FlameAPI : public NetworkModAPI { .arg(args.offset) .arg(args.search) .arg(getSortFieldInt(args.sorting)) - .arg(getMappedModLoader(args.mod_loader)) + .arg(getMappedModLoader(args.loaders)) .arg(gameVersionStr); }; inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override { QString gameVersionQuery = args.mcVersions.size() == 1 ? QString("gameVersion=%1&").arg(args.mcVersions.front().toString()) : ""; - QString modLoaderQuery = QString("modLoaderType=%1&").arg(getMappedModLoader(args.loader)); + QString modLoaderQuery = QString("modLoaderType=%1&").arg(getMappedModLoader(args.loaders)); return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&%2%3") .arg(args.addonId) @@ -53,11 +53,16 @@ class FlameAPI : public NetworkModAPI { }; public: - static auto getMappedModLoader(const ModLoaderType type) -> const ModLoaderType + static auto getMappedModLoader(const ModLoaderTypes loaders) -> const int { + // https://docs.curseforge.com/?http#tocS_ModLoaderType + if (loaders & Forge) + return 1; + if (loaders & Fabric) + return 4; // TODO: remove this once Quilt drops official Fabric support - if (type == Quilt) // NOTE: Most if not all Fabric mods should work *currently* - return Fabric; - return type; + if (loaders & Quilt) // NOTE: Most if not all Fabric mods should work *currently* + return 4; // Quilt would probably be 5 + return 0; } }; diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index 6d642b5e8..39f6c49a0 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -28,30 +28,25 @@ class ModrinthAPI : public NetworkModAPI { public: inline auto getAuthorURL(const QString& name) const -> QString { return "https://modrinth.com/user/" + name; }; - static auto getModLoaderStrings(ModLoaderType type) -> const QStringList + static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList { QStringList l; - switch (type) + for (auto loader : {Forge, Fabric, Quilt}) { - case Unspecified: - for (auto loader : {Forge, Fabric, Quilt}) - { - l << ModAPI::getModLoaderString(loader); - } - break; - - case Quilt: - l << ModAPI::getModLoaderString(Fabric); - default: - l << ModAPI::getModLoaderString(type); + if (types & loader || types == Unspecified) + { + l << ModAPI::getModLoaderString(loader); + } } + if (types & Quilt && ~types & Fabric) // Add Fabric if Quilt is in use, if Fabric isn't already there + l << ModAPI::getModLoaderString(Fabric); return l; } - static auto getModLoaderFilters(ModLoaderType type) -> const QString + static auto getModLoaderFilters(ModLoaderTypes types) -> const QString { QStringList l; - for (auto loader : getModLoaderStrings(type)) + for (auto loader : getModLoaderStrings(types)) { l << QString("\"categories:%1\"").arg(loader); } @@ -61,7 +56,7 @@ class ModrinthAPI : public NetworkModAPI { private: inline auto getModSearchURL(SearchArgs& args) const -> QString override { - if (!validateModLoader(args.mod_loader)) { + if (!validateModLoaders(args.loaders)) { qWarning() << "Modrinth only have Forge and Fabric-compatible mods!"; return ""; } @@ -76,7 +71,7 @@ class ModrinthAPI : public NetworkModAPI { .arg(args.offset) .arg(args.search) .arg(args.sorting) - .arg(getModLoaderFilters(args.mod_loader)) + .arg(getModLoaderFilters(args.loaders)) .arg(getGameVersionsArray(args.versions)); }; @@ -88,7 +83,7 @@ class ModrinthAPI : public NetworkModAPI { "loaders=[\"%3\"]") .arg(args.addonId) .arg(getGameVersionsString(args.mcVersions)) - .arg(getModLoaderStrings(args.loader).join("\",\"")); + .arg(getModLoaderStrings(args.loaders).join("\",\"")); }; auto getGameVersionsArray(std::list mcVersions) const -> QString @@ -101,9 +96,9 @@ class ModrinthAPI : public NetworkModAPI { return s.isEmpty() ? QString() : QString("[%1],").arg(s); } - inline auto validateModLoader(ModLoaderType modLoader) const -> bool + inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool { - return modLoader == Unspecified || modLoader == Forge || modLoader == Fabric || modLoader == Quilt; + return loaders == Unspecified || loaders & (Forge | Fabric | Quilt); } }; diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 8113fe857..5574f9d2f 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -391,7 +391,7 @@ void ModFolderPage::on_actionInstall_mods_triggered() return; //this is a null instance or a legacy instance } auto profile = ((MinecraftInstance *)m_inst)->getPackProfile(); - if (profile->getModLoader() == ModAPI::Unspecified) { + if (profile->getModLoaders() == ModAPI::Unspecified) { QMessageBox::critical(this,tr("Error"),tr("Please install a mod loader first!")); return; } diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 540ee2fdc..9dd8f7379 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -68,7 +68,7 @@ void ListModel::requestModVersions(ModPlatform::IndexedPack const& current) { auto profile = (dynamic_cast((dynamic_cast(parent()))->m_instance))->getPackProfile(); - m_parent->apiProvider()->getVersions(this, { current.addonId.toString(), getMineVersions(), profile->getModLoader() }); + m_parent->apiProvider()->getVersions(this, { current.addonId.toString(), getMineVersions(), profile->getModLoaders() }); } void ListModel::performPaginatedSearch() @@ -76,7 +76,7 @@ void ListModel::performPaginatedSearch() auto profile = (dynamic_cast((dynamic_cast(parent()))->m_instance))->getPackProfile(); m_parent->apiProvider()->searchMods( - this, { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoader(), getMineVersions() }); + this, { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoaders(), getMineVersions() }); } void ListModel::refresh() diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 6dd3a4535..ad36cf2f8 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -175,7 +175,7 @@ void ModPage::updateModVersions(int prev_count) bool valid = false; for(auto& mcVer : m_filter->versions){ //NOTE: Flame doesn't care about loader, so passing it changes nothing. - if (validateVersion(version, mcVer.toString(), packProfile->getModLoader())) { + if (validateVersion(version, mcVer.toString(), packProfile->getModLoaders())) { valid = true; break; } diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index eb89b0e28..0e658a8de 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -37,7 +37,7 @@ class ModPage : public QWidget, public BasePage { void retranslate() override; auto shouldDisplay() const -> bool override = 0; - virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader = ModAPI::Unspecified) const -> bool = 0; + virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool = 0; auto apiProvider() const -> const ModAPI* { return api.get(); }; auto getFilter() const -> const std::shared_ptr { return m_filter; } diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp index 70759994c..1c160fd4b 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp @@ -61,9 +61,9 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance) connect(ui->modSelectionButton, &QPushButton::clicked, this, &FlameModPage::onModSelected); } -auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader) const -> bool +auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool { - Q_UNUSED(loader); + Q_UNUSED(loaders); return ver.mcVersion.contains(mineVer); } diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.h b/launcher/ui/pages/modplatform/flame/FlameModPage.h index 27cbdb8cf..86e1a17b3 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.h +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.h @@ -55,7 +55,7 @@ class FlameModPage : public ModPage { inline auto debugName() const -> QString override { return "Flame"; } inline auto metaEntryBase() const -> QString override { return "FlameMods"; }; - auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader = ModAPI::Unspecified) const -> bool override; + auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool override; auto shouldDisplay() const -> bool override; }; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp index d3a1f8594..0b81ea931 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp @@ -61,9 +61,9 @@ ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instan connect(ui->modSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onModSelected); } -auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader) const -> bool +auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool { - auto loaderStrings = ModrinthAPI::getModLoaderStrings(loader); + auto loaderStrings = ModrinthAPI::getModLoaderStrings(loaders); auto loaderCompatible = false; for (auto remoteLoader : ver.loaders) diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h index b1e72bfea..c39acaa0b 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h @@ -55,7 +55,7 @@ class ModrinthModPage : public ModPage { inline auto debugName() const -> QString override { return "Modrinth"; } inline auto metaEntryBase() const -> QString override { return "ModrinthPacks"; }; - auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader = ModAPI::Unspecified) const -> bool override; + auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool override; auto shouldDisplay() const -> bool override; }; From 36045a8b0aa5c99e8520a39e6cc372ab9b549668 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 19 May 2022 12:35:44 +0200 Subject: [PATCH 029/308] chore: improve readability Co-authored-by: flow --- launcher/modplatform/modrinth/ModrinthAPI.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index 39f6c49a0..79bc5175a 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -33,12 +33,12 @@ class ModrinthAPI : public NetworkModAPI { QStringList l; for (auto loader : {Forge, Fabric, Quilt}) { - if (types & loader || types == Unspecified) + if ((types & loader) || types == Unspecified) { l << ModAPI::getModLoaderString(loader); } } - if (types & Quilt && ~types & Fabric) // Add Fabric if Quilt is in use, if Fabric isn't already there + if ((types & Quilt) && (~types & Fabric)) // Add Fabric if Quilt is in use, if Fabric isn't already there l << ModAPI::getModLoaderString(Fabric); return l; } @@ -98,7 +98,7 @@ class ModrinthAPI : public NetworkModAPI { inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool { - return loaders == Unspecified || loaders & (Forge | Fabric | Quilt); + return (loaders == Unspecified) || (loaders & (Forge | Fabric | Quilt)); } }; From 97a83c9b7a72d37218acfbf5c325245eab0b5b23 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Sun, 1 May 2022 18:12:21 +0100 Subject: [PATCH 030/308] ATLauncher: Avoid downloading Forge twice for older packs This resolves a quirk where Forge would still be downloaded for use as a jarmod, even when we detected Forge as a component. --- launcher/modplatform/atlauncher/ATLPackInstallTask.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 9dcb35048..991d737c1 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -574,8 +574,6 @@ void PackInstallTask::downloadMods() jobPtr->addNetAction(dl); auto path = FS::PathCombine(m_stagingPath, "minecraft", relpath, mod.file); - qDebug() << "Will download" << url << "to" << path; - modsToCopy[entry->getFullPath()] = path; if(mod.type == ModType::Forge) { auto vlist = APPLICATION->metadataIndex()->get("net.minecraftforge"); @@ -597,6 +595,10 @@ void PackInstallTask::downloadMods() qDebug() << "Jarmod: " + path; jarmods.push_back(path); } + + // Download after Forge handling, to avoid downloading Forge twice. + qDebug() << "Will download" << url << "to" << path; + modsToCopy[entry->getFullPath()] = path; } } From c329730de848f9ecf864aa4edbbc650faad7f21a Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Sun, 1 May 2022 19:32:34 +0100 Subject: [PATCH 031/308] ATLauncher: Install LiteLoader as a component where possible --- .../atlauncher/ATLPackInstallTask.cpp | 91 ++++++++++++++++--- 1 file changed, 80 insertions(+), 11 deletions(-) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 991d737c1..e9e3b872e 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -1,18 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only /* - * Copyright 2020-2021 Jamie Mansfield - * Copyright 2021 Petr Mrazek + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 Jamie Mansfield * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 2020-2021 Jamie Mansfield + * Copyright 2021 Petr Mrazek + * + * 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 "ATLPackInstallTask.h" @@ -305,7 +324,55 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared auto f = std::make_shared(); f->name = m_pack + " " + m_version_name + " (libraries)"; + const static QMap liteLoaderMap = { + { "61179803bcd5fb7790789b790908663d", "1.12-SNAPSHOT" }, + { "1420785ecbfed5aff4a586c5c9dd97eb", "1.12.2-SNAPSHOT" }, + { "073f68e2fcb518b91fd0d99462441714", "1.6.2_03" }, + { "10a15b52fc59b1bfb9c05b56de1097d6", "1.6.2_02" }, + { "b52f90f08303edd3d4c374e268a5acf1", "1.6.2_04" }, + { "ea747e24e03e24b7cad5bc8a246e0319", "1.6.2_01" }, + { "55785ccc82c07ff0ba038fe24be63ea2", "1.7.10_01" }, + { "63ada46e033d0cb6782bada09ad5ca4e", "1.7.10_04" }, + { "7983e4b28217c9ae8569074388409c86", "1.7.10_03" }, + { "c09882458d74fe0697c7681b8993097e", "1.7.10_02" }, + { "db7235aefd407ac1fde09a7baba50839", "1.7.10_00" }, + { "6e9028816027f53957bd8fcdfabae064", "1.8" }, + { "5e732dc446f9fe2abe5f9decaec40cde", "1.10-SNAPSHOT" }, + { "3a98b5ed95810bf164e71c1a53be568d", "1.11.2-SNAPSHOT" }, + { "ba8e6285966d7d988a96496f48cbddaa", "1.8.9-SNAPSHOT" }, + { "8524af3ac3325a82444cc75ae6e9112f", "1.11-SNAPSHOT" }, + { "53639d52340479ccf206a04f5e16606f", "1.5.2_01" }, + { "1fcdcf66ce0a0806b7ad8686afdce3f7", "1.6.4_00" }, + { "531c116f71ae2b11033f9a11a0f8e668", "1.6.4_01" }, + { "4009eeb99c9068f608d3483a6439af88", "1.7.2_03" }, + { "66f343354b8417abce1a10d557d2c6e9", "1.7.2_04" }, + { "ab554c21f28fbc4ae9b098bcb5f4cceb", "1.7.2_05" }, + { "e1d76a05a3723920e2f80a5e66c45f16", "1.7.2_02" }, + { "00318cb0c787934d523f63cdfe8ddde4", "1.9-SNAPSHOT" }, + { "986fd1ee9525cb0dcab7609401cef754", "1.9.4-SNAPSHOT" }, + { "571ad5e6edd5ff40259570c9be588bb5", "1.9.4" }, + { "1cdd72f7232e45551f16cc8ffd27ccf3", "1.10.2-SNAPSHOT" }, + { "8a7c21f32d77ee08b393dd3921ced8eb", "1.10.2" }, + { "b9bef8abc8dc309069aeba6fbbe58980", "1.12.1-SNAPSHOT" } + }; + for(const auto & lib : m_version.libraries) { + // If the library is LiteLoader, we need to ignore it and handle it separately. + if (liteLoaderMap.contains(lib.md5)) { + auto vlist = APPLICATION->metadataIndex()->get("com.mumfrey.liteloader"); + if (vlist) { + if (!vlist->isLoaded()) + vlist->load(Net::Mode::Online); + + auto ver = vlist->getVersion(liteLoaderMap.value(lib.md5)); + if (ver) { + ver->load(Net::Mode::Online); + componentsToInstall.insert("com.mumfrey.liteloader", ver); + continue; + } + } + } + auto libName = detectLibrary(lib); GradleSpecifier libSpecifier(libName); @@ -579,6 +646,8 @@ void PackInstallTask::downloadMods() auto vlist = APPLICATION->metadataIndex()->get("net.minecraftforge"); if(vlist) { + if (!vlist->isLoaded()) + vlist->load(Net::Mode::Online); auto ver = vlist->getVersion(mod.version); if(ver) { ver->load(Net::Mode::Online); From f5f59203a203318371fbc5257234b8c2c5eeb300 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Sun, 1 May 2022 22:42:29 +0100 Subject: [PATCH 032/308] ATLauncher: Reduce boilerplate code for fetching versions --- .../atlauncher/ATLPackInstallTask.cpp | 63 +++++++++---------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index e9e3b872e..4b8b8eb01 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -58,6 +58,8 @@ namespace ATLauncher { +static Meta::VersionPtr getComponentVersion(const QString& uid, const QString& version); + PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString pack, QString version) { m_support = support; @@ -115,19 +117,11 @@ void PackInstallTask::onDownloadSucceeded() } m_version = version; - auto vlist = APPLICATION->metadataIndex()->get("net.minecraft"); - if(!vlist) - { - emitFailed(tr("Failed to get local metadata index for %1").arg("net.minecraft")); - return; - } - - auto ver = vlist->getVersion(m_version.minecraft); + auto ver = getComponentVersion("net.minecraft", m_version.minecraft); if (!ver) { - emitFailed(tr("Failed to get local metadata index for '%1' v%2").arg("net.minecraft").arg(m_version.minecraft)); + emitFailed(tr("Failed to get local metadata index for '%1' v%2").arg("net.minecraft", m_version.minecraft)); return; } - ver->load(Net::Mode::Online); minecraftVersion = ver; if(m_version.noConfigs) { @@ -359,17 +353,10 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared for(const auto & lib : m_version.libraries) { // If the library is LiteLoader, we need to ignore it and handle it separately. if (liteLoaderMap.contains(lib.md5)) { - auto vlist = APPLICATION->metadataIndex()->get("com.mumfrey.liteloader"); - if (vlist) { - if (!vlist->isLoaded()) - vlist->load(Net::Mode::Online); - - auto ver = vlist->getVersion(liteLoaderMap.value(lib.md5)); - if (ver) { - ver->load(Net::Mode::Online); - componentsToInstall.insert("com.mumfrey.liteloader", ver); - continue; - } + auto ver = getComponentVersion("com.mumfrey.liteloader", liteLoaderMap.value(lib.md5)); + if (ver) { + componentsToInstall.insert("com.mumfrey.liteloader", ver); + continue; } } @@ -643,17 +630,10 @@ void PackInstallTask::downloadMods() auto path = FS::PathCombine(m_stagingPath, "minecraft", relpath, mod.file); if(mod.type == ModType::Forge) { - auto vlist = APPLICATION->metadataIndex()->get("net.minecraftforge"); - if(vlist) - { - if (!vlist->isLoaded()) - vlist->load(Net::Mode::Online); - auto ver = vlist->getVersion(mod.version); - if(ver) { - ver->load(Net::Mode::Online); - componentsToInstall.insert("net.minecraftforge", ver); - continue; - } + auto ver = getComponentVersion("net.minecraftforge", mod.version); + if (ver) { + componentsToInstall.insert("net.minecraftforge", ver); + continue; } qDebug() << "Jarmod: " + path; @@ -850,4 +830,23 @@ void PackInstallTask::install() emitSucceeded(); } +static Meta::VersionPtr getComponentVersion(const QString& uid, const QString& version) +{ + auto vlist = APPLICATION->metadataIndex()->get(uid); + if (!vlist) + return {}; + + if (!vlist->isLoaded()) + vlist->load(Net::Mode::Online); + + auto ver = vlist->getVersion(version); + if (!ver) + return {}; + + if (!ver->isLoaded()) + ver->load(Net::Mode::Online); + + return ver; +} + } From 188c5aaa356323392be1100d74f62d70ab298695 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Tue, 17 May 2022 18:43:35 +0100 Subject: [PATCH 033/308] Launch: Match Vanilla launcher version string behaviour This removes a means of profiling users. --- launcher/minecraft/MinecraftInstance.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index e20dc24c7..61326fac8 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 Jamie Mansfield * * 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 @@ -487,9 +488,8 @@ QStringList MinecraftInstance::processMinecraftArgs( } } - // blatant self-promotion. - token_mapping["profile_name"] = token_mapping["version_name"] = BuildConfig.LAUNCHER_NAME; - + token_mapping["profile_name"] = name(); + token_mapping["version_name"] = profile->getMinecraftVersion(); token_mapping["version_type"] = profile->getMinecraftVersionType(); QString absRootDir = QDir(gameRoot()).absolutePath(); From 96f16069a93afa320de174e740bc6b915e9a1103 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Tue, 17 May 2022 21:03:15 +0100 Subject: [PATCH 034/308] Launch: Apply the Minecraft version correctly It was previously using a deprecated field. --- launcher/minecraft/VersionFile.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/minecraft/VersionFile.cpp b/launcher/minecraft/VersionFile.cpp index 9db30ba2f..f242fbe7b 100644 --- a/launcher/minecraft/VersionFile.cpp +++ b/launcher/minecraft/VersionFile.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 Jamie Mansfield * * 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 @@ -55,7 +56,7 @@ void VersionFile::applyTo(LaunchProfile *profile) // Only real Minecraft can set those. Don't let anything override them. if (isMinecraftVersion(uid)) { - profile->applyMinecraftVersion(minecraftVersion); + profile->applyMinecraftVersion(version); profile->applyMinecraftVersionType(type); // HACK: ignore assets from other version files than Minecraft // workaround for stupid assets issue caused by amazon: From 2847cefff701dad137cd04f628c76a9282d04a83 Mon Sep 17 00:00:00 2001 From: dada513 Date: Fri, 20 May 2022 19:56:27 +0200 Subject: [PATCH 035/308] Add cursefrog key override --- launcher/Application.cpp | 11 +++++ launcher/Application.h | 1 + launcher/net/Download.cpp | 3 +- launcher/ui/pages/global/APIPage.cpp | 4 ++ launcher/ui/pages/global/APIPage.ui | 64 ++++++++++++++++++++++------ 5 files changed, 68 insertions(+), 15 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index dc8a7b0d3..ce62c41af 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -679,6 +679,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // Custom MSA credentials m_settings->registerSetting("MSAClientIDOverride", ""); + m_settings->registerSetting("CFKeyOverride", ""); // Init page provider { @@ -1508,3 +1509,13 @@ QString Application::getMSAClientID() return BuildConfig.MSA_CLIENT_ID; } + +QString Application::getCurseKey() +{ + QString keyOverride = m_settings->get("CFKeyOverride").toString(); + if (!keyOverride.isEmpty()) { + return keyOverride; + } + + return BuildConfig.CURSEFORGE_API_KEY; +} diff --git a/launcher/Application.h b/launcher/Application.h index 172321c02..3129b4fb8 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -155,6 +155,7 @@ public: QString getJarsPath(); QString getMSAClientID(); + QString getCurseKey(); /// this is the root of the 'installation'. Used for automatic updates const QString &root() { diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index 65cc8f67a..7a4016094 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -25,6 +25,7 @@ #include "MetaCacheSink.h" #include "BuildConfig.h" +#include "Application.h" namespace Net { @@ -96,7 +97,7 @@ void Download::startImpl() request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT); if (request.url().host().contains("api.curseforge.com")) { - request.setRawHeader("x-api-key", BuildConfig.CURSEFORGE_API_KEY.toUtf8()); + request.setRawHeader("x-api-key", APPLICATION->getCurseKey().toUtf8()); }; QNetworkReply* rep = m_network->get(request); diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index 287eb74f2..8b806bcf1 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -70,6 +70,8 @@ void APIPage::loadSettings() ui->urlChoices->setCurrentText(pastebinURL); QString msaClientID = s->get("MSAClientIDOverride").toString(); ui->msaClientID->setText(msaClientID); + QString curseKey = s->get("CFKeyOverride").toString(); + ui->curseKey->setText(curseKey); } void APIPage::applySettings() @@ -79,6 +81,8 @@ void APIPage::applySettings() s->set("PastebinURL", pastebinURL); QString msaClientID = ui->msaClientID->text(); s->set("MSAClientIDOverride", msaClientID); + QString curseKey = ui->curseKey->text(); + s->set("CFKeyOverride", curseKey); } bool APIPage::apply() diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index acde9aef8..eaa44c888 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -6,8 +6,8 @@ 0 0 - 491 - 474 + 603 + 530 @@ -148,17 +148,56 @@ - - - Qt::Vertical + + + true - - - 20 - 40 - + + &CurseForge Core API - + + + + + Qt::Horizontal + + + + + + + Note: you probably don't need to set this if CurseForge already works. + + + + + + + true + + + (Default) + + + + + + + Enter a custom API Key for CurseForge here. + + + Qt::RichText + + + true + + + true + + + + + @@ -166,9 +205,6 @@ - - tabWidget - From 6afe59e76b6a5d44b8706e8e030ecd0396dc8801 Mon Sep 17 00:00:00 2001 From: timoreo Date: Fri, 20 May 2022 21:19:19 +0200 Subject: [PATCH 036/308] Very Temporary Fix for curseforge --- .../modplatform/flame/FileResolvingTask.cpp | 16 +- launcher/modplatform/flame/PackManifest.cpp | 16 +- .../ui/pages/modplatform/flame/FlamePage.ui | 179 +++++++++--------- 3 files changed, 118 insertions(+), 93 deletions(-) diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index 95924a681..0deb99c4a 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -31,7 +31,21 @@ void Flame::FileResolvingTask::netJobFinished() for (auto& bytes : results) { auto& out = m_toProcess.files[index]; try { - failed &= (!out.parseFromBytes(bytes)); + bool fail = (!out.parseFromBytes(bytes)); + if(fail){ + //failed :( probably disabled mod, try to add to the list + auto doc = Json::requireDocument(bytes); + if (!doc.isObject()) { + throw JSONValidationError(QString("data is not an object? that's not supposed to happen")); + } + auto obj = Json::ensureObject(doc.object(), "data"); + //FIXME : HACK, MAY NOT WORK FOR LONG + out.url = QUrl(QString("https://media.forgecdn.net/files/%1/%2/%3") + .arg(QString::number(QString::number(out.fileId).leftRef(4).toInt()) + ,QString::number(QString::number(out.fileId).rightRef(3).toInt()) + ,QUrl::toPercentEncoding(out.fileName)), QUrl::TolerantMode); + } + failed &= fail; } catch (const JSONValidationError& e) { qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:"; qCritical() << e.cause(); diff --git a/launcher/modplatform/flame/PackManifest.cpp b/launcher/modplatform/flame/PackManifest.cpp index e4f90c1a1..c78783a0a 100644 --- a/launcher/modplatform/flame/PackManifest.cpp +++ b/launcher/modplatform/flame/PackManifest.cpp @@ -71,11 +71,6 @@ bool Flame::File::parseFromBytes(const QByteArray& bytes) fileName = Json::requireString(obj, "fileName"); - QString rawUrl = Json::requireString(obj, "downloadUrl"); - url = QUrl(rawUrl, QUrl::TolerantMode); - if (!url.isValid()) { - throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl)); - } // This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience // It is also optional type = File::Type::SingleFile; @@ -87,7 +82,16 @@ bool Flame::File::parseFromBytes(const QByteArray& bytes) // this is probably a mod, dunno what else could modpacks download targetFolder = "mods"; } - + if(!obj.contains("downloadUrl") || obj["downloadUrl"].isNull() || !obj["downloadUrl"].isString() || obj["downloadUrl"].toString().isEmpty()){ + //either there somehow is an emtpy string as a link, or it's null either way it's invalid + //soft failing + return false; + } + QString rawUrl = Json::requireString(obj, "downloadUrl"); + url = QUrl(rawUrl, QUrl::TolerantMode); + if (!url.isValid()) { + throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl)); + } resolved = true; return true; } diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.ui b/launcher/ui/pages/modplatform/flame/FlamePage.ui index 6d8d8e10d..b337d6720 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.ui +++ b/launcher/ui/pages/modplatform/flame/FlamePage.ui @@ -1,90 +1,97 @@ - FlamePage - - - - 0 - 0 - 837 - 685 - - - - - - - - - - 48 - 48 - - - - Qt::ScrollBarAlwaysOff - - - true - - - - - - - true - - - true - - - - - - - - - - - - - - Version selected: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - Search - - - - - - - Search and filter... - - - + FlamePage + + + + 0 + 0 + 1989 + 685 + + + + + + + Search + + + + + + + Search and filter... + + + + + + + + + Qt::ScrollBarAlwaysOff + + + true + + + + 48 + 48 + + + + + + + + true + + + true + + + - - - searchEdit - searchButton - packView - packDescription - sortByBox - versionSelectionBox - - - + + + + + + + + + + Version selected: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + WARNING !! Curseforge is very unreliable and low quality. Some mod authors have disabled the ability for third party apps (like polymc) to download the mods, you may need to manually download some mods + + + + + + + searchEdit + searchButton + packView + packDescription + sortByBox + versionSelectionBox + + + From cbc8c1aed63e9cd106b468ae720aa650beec646a Mon Sep 17 00:00:00 2001 From: Kenneth Chew <79120643+kthchew@users.noreply.github.com> Date: Fri, 20 May 2022 15:56:13 -0400 Subject: [PATCH 037/308] Use consistent naming scheme Co-authored-by: Sefa Eyeoglu --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7ab30d456..d12f176c6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -269,7 +269,7 @@ jobs: if: runner.os == 'Windows' uses: actions/upload-artifact@v3 with: - name: PolyMC-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}-Setup + name: PolyMC-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }} path: PolyMC-Setup.exe - name: Upload binary tarball (Linux) From 30b56dbcbd3bb7d61210405a469c7efb28581904 Mon Sep 17 00:00:00 2001 From: timoreo Date: Fri, 20 May 2022 22:00:38 +0200 Subject: [PATCH 038/308] Port temp fix to mods too --- launcher/modplatform/flame/FlameModIndex.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index ba0824cf5..9846b1562 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -56,8 +56,15 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, file.fileId = Json::requireInteger(obj, "id"); file.date = Json::requireString(obj, "fileDate"); file.version = Json::requireString(obj, "displayName"); - file.downloadUrl = Json::requireString(obj, "downloadUrl"); file.fileName = Json::requireString(obj, "fileName"); + file.downloadUrl = Json::ensureString(obj, "downloadUrl", ""); + if(file.downloadUrl.isEmpty()){ + //FIXME : HACK, MAY NOT WORK FOR LONG + file.downloadUrl = QString("https://media.forgecdn.net/files/%1/%2/%3") + .arg(QString::number(QString::number(file.fileId.toInt()).leftRef(4).toInt()) + ,QString::number(QString::number(file.fileId.toInt()).rightRef(3).toInt()) + ,QUrl::toPercentEncoding(file.fileName)); + } unsortedVersions.append(file); } From 6542f5f15af31e493c9b46afb3a5b4b330cc9cee Mon Sep 17 00:00:00 2001 From: timoreo Date: Fri, 20 May 2022 22:06:36 +0200 Subject: [PATCH 039/308] Apply suggestions --- launcher/modplatform/flame/PackManifest.cpp | 5 +- .../ui/pages/modplatform/flame/FlamePage.ui | 69 ++++++++++--------- 2 files changed, 40 insertions(+), 34 deletions(-) diff --git a/launcher/modplatform/flame/PackManifest.cpp b/launcher/modplatform/flame/PackManifest.cpp index c78783a0a..3217a7569 100644 --- a/launcher/modplatform/flame/PackManifest.cpp +++ b/launcher/modplatform/flame/PackManifest.cpp @@ -82,12 +82,13 @@ bool Flame::File::parseFromBytes(const QByteArray& bytes) // this is probably a mod, dunno what else could modpacks download targetFolder = "mods"; } - if(!obj.contains("downloadUrl") || obj["downloadUrl"].isNull() || !obj["downloadUrl"].isString() || obj["downloadUrl"].toString().isEmpty()){ + QString rawUrl = Json::ensureString(obj, "downloadUrl"); + + if(rawUrl.isEmpty()){ //either there somehow is an emtpy string as a link, or it's null either way it's invalid //soft failing return false; } - QString rawUrl = Json::requireString(obj, "downloadUrl"); url = QUrl(rawUrl, QUrl::TolerantMode); if (!url.isValid()) { throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl)); diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.ui b/launcher/ui/pages/modplatform/flame/FlamePage.ui index b337d6720..4c7a6495a 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.ui +++ b/launcher/ui/pages/modplatform/flame/FlamePage.ui @@ -6,26 +6,39 @@ 0 0 - 1989 + 2445 685 - - - - Search - - + + + + + + + + + Version selected: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + - + Search and filter... - + @@ -55,30 +68,22 @@ - - - - - - - - - Version selected: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - + + - WARNING !! Curseforge is very unreliable and low quality. Some mod authors have disabled the ability for third party apps (like polymc) to download the mods, you may need to manually download some mods + Search + + + + + + + + true + + + + WARNING: CurseForge's API is very unreliable and low quality. Also, some mod authors have disabled the ability for third party apps (like PolyMC) to download their mods. As such, you may need to manually download some mods to be able to use the modpack. From 3b4b34b3695e655f591347754a724804bea96d71 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Fri, 20 May 2022 22:46:35 +0200 Subject: [PATCH 040/308] fix(ui): make CF and MR modpack dialogs more consistent --- .../ui/pages/modplatform/flame/FlamePage.ui | 108 ++++++++++-------- .../modplatform/modrinth/ModrinthPage.ui | 7 +- 2 files changed, 63 insertions(+), 52 deletions(-) diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.ui b/launcher/ui/pages/modplatform/flame/FlamePage.ui index 4c7a6495a..9fab97737 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.ui +++ b/launcher/ui/pages/modplatform/flame/FlamePage.ui @@ -6,41 +6,50 @@ 0 0 - 2445 - 685 + 800 + 600 - - - - - - - - - Version selected: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - Search and filter... + + + + + true + + + + Note: CurseForge's API is very unreliable. CurseForge and some mod authors have disallowed downloading mods in third-party applications like PolyMC. As such, you may need to manually download some mods to be able to install a modpack. + + + Qt::AlignCenter + + + true - - - + + + + + + Search and filter... + + + + + + + Search + + + + + + + + Qt::ScrollBarAlwaysOff @@ -56,7 +65,7 @@ - + true @@ -68,30 +77,29 @@ - - - - Search - - - - - - - - true - - - - WARNING: CurseForge's API is very unreliable and low quality. Also, some mod authors have disabled the ability for third party apps (like PolyMC) to download their mods. As such, you may need to manually download some mods to be able to use the modpack. - - + + + + + + + + + Version selected: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + - searchEdit - searchButton packView packDescription sortByBox diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui index 4fb59cdf0..ae9556edf 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui @@ -6,8 +6,8 @@ 0 0 - 837 - 685 + 800 + 600 @@ -24,6 +24,9 @@ Qt::AlignCenter + + true + From 2bc6da038dea701699ba9fc46eb68b3a74d5f488 Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Fri, 20 May 2022 17:09:26 -0400 Subject: [PATCH 041/308] Add installer to release workflow --- .github/workflows/trigger_release.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml index ff0d2b3fa..91cd04742 100644 --- a/.github/workflows/trigger_release.yml +++ b/.github/workflows/trigger_release.yml @@ -43,10 +43,12 @@ jobs: for d in PolyMC-Windows-*; do cd "${d}" || continue ARCH="$(echo -n ${d} | cut -d '-' -f 3)" + INST="$(echo -n ${d} | grep -o Setup || true)" PORT="$(echo -n ${d} | grep -o Portable || true)" NAME="PolyMC-Windows-${ARCH}" test -z "${PORT}" || NAME="${NAME}-Portable" - zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" * + test -z "${INST}" || mv PolyMC-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe + test -n "${INST}" || zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" * cd .. done @@ -66,7 +68,9 @@ jobs: PolyMC-Linux-${{ env.VERSION }}-x86_64.AppImage PolyMC-Windows-i686-${{ env.VERSION }}.zip PolyMC-Windows-i686-Portable-${{ env.VERSION }}.zip + PolyMC-Windows-i686-Setup-${{ env.VERSION }}.exe PolyMC-Windows-x86_64-${{ env.VERSION }}.zip PolyMC-Windows-x86_64-Portable-${{ env.VERSION }}.zip + PolyMC-Windows-x86_64-Setup-${{ env.VERSION }}.exe PolyMC-macOS-${{ env.VERSION }}.tar.gz PolyMC-${{ env.VERSION }}.tar.gz From 12cadf3af0a4e3a01330283fae2d6267d3c3f525 Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Fri, 20 May 2022 17:09:42 -0400 Subject: [PATCH 042/308] Add `/NoUninstaller` parameter for Windows installer --- program_info/win_install.nsi | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/program_info/win_install.nsi b/program_info/win_install.nsi index ce13b8b00..2d3f0f576 100644 --- a/program_info/win_install.nsi +++ b/program_info/win_install.nsi @@ -1,4 +1,5 @@ !include "FileFunc.nsh" +!include "LogicLib.nsh" !include "MUI2.nsh" Unicode true @@ -119,20 +120,24 @@ Section "PolyMC" WriteRegStr HKCU Software\PolyMC "InstallDir" "$INSTDIR" ; Write the uninstall keys for Windows - !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\PolyMC" - WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "PolyMC" - WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\polymc.exe" - WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"' - WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S' - WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR" - WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "PolyMC Contributors" - WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "${VERSION}" - ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 - IntFmt $0 "0x%08X" $0 - WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0" - WriteRegDWORD HKCU "${UNINST_KEY}" "NoModify" 1 - WriteRegDWORD HKCU "${UNINST_KEY}" "NoRepair" 1 - WriteUninstaller "$INSTDIR\uninstall.exe" + ${GetParameters} $R0 + ${GetOptions} $R0 "/NoUninstaller" $R1 + ${If} ${Errors} + !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\PolyMC" + WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "PolyMC" + WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\polymc.exe" + WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"' + WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S' + WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR" + WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "PolyMC Contributors" + WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "${VERSION}" + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0" + WriteRegDWORD HKCU "${UNINST_KEY}" "NoModify" 1 + WriteRegDWORD HKCU "${UNINST_KEY}" "NoRepair" 1 + WriteUninstaller "$INSTDIR\uninstall.exe" + ${EndIf} SectionEnd From cdd83c279cafdacee6c863d7fb0ae94a6bf34e3e Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Fri, 20 May 2022 17:12:08 -0400 Subject: [PATCH 043/308] Remove portable option in Windows installer --- .github/workflows/build.yml | 2 +- program_info/win_install.nsi | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d12f176c6..53db7d761 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -195,7 +195,7 @@ jobs: if: runner.os == 'Windows' shell: msys2 {0} run: | - cd ${{ env.INSTALL_PORTABLE_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" - name: Package (Linux) diff --git a/program_info/win_install.nsi b/program_info/win_install.nsi index 2d3f0f576..a47d4ae3f 100644 --- a/program_info/win_install.nsi +++ b/program_info/win_install.nsi @@ -147,13 +147,6 @@ Section "Start Menu Shortcuts" SectionEnd -Section /o "Portable" - - SetOutPath $INSTDIR - File "portable.txt" - -SectionEnd - ;-------------------------------- ; Uninstaller From 1ec7878c07a8ba7d04a9fe860761872547fd5a0d Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Fri, 20 May 2022 17:22:30 -0400 Subject: [PATCH 044/308] Add `/NoShortcuts` parameter for Windows installer --- program_info/win_install.nsi | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/program_info/win_install.nsi b/program_info/win_install.nsi index a47d4ae3f..7d48ccf24 100644 --- a/program_info/win_install.nsi +++ b/program_info/win_install.nsi @@ -141,7 +141,7 @@ Section "PolyMC" SectionEnd -Section "Start Menu Shortcuts" +Section "Start Menu Shortcuts" SHORTCUTS CreateShortcut "$SMPROGRAMS\PolyMC.lnk" "$INSTDIR\polymc.exe" "" "$INSTDIR\polymc.exe" 0 @@ -219,3 +219,15 @@ Section "Uninstall" RMDir "$INSTDIR" SectionEnd + +;-------------------------------- + +; Extra command line parameters + +Function .onInit +${GetParameters} $R0 +${GetOptions} $R0 "/NoShortcuts" $R1 +${IfNot} ${Errors} + !insertmacro UnselectSection ${SHORTCUTS} +${EndIf} +FunctionEnd From 3cab0e69f1c299e385330d97ab5159a0c8c904ec Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Fri, 20 May 2022 17:23:11 -0400 Subject: [PATCH 045/308] Fix default install location --- program_info/win_install.nsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/program_info/win_install.nsi b/program_info/win_install.nsi index 7d48ccf24..4ca4de1ad 100644 --- a/program_info/win_install.nsi +++ b/program_info/win_install.nsi @@ -5,7 +5,7 @@ Unicode true Name "PolyMC" -InstallDir "$LOCALAPPDATA\PolyMC" +InstallDir "$LOCALAPPDATA\Programs\PolyMC" InstallDirRegKey HKCU "Software\PolyMC" "InstallDir" RequestExecutionLevel user From c04adf74521127bc50c67f3e2ddd1edfe2330358 Mon Sep 17 00:00:00 2001 From: timoreo Date: Sat, 21 May 2022 08:31:07 +0200 Subject: [PATCH 046/308] Do the url trick on initial modpack download too --- launcher/modplatform/flame/FlamePackIndex.cpp | 10 +++++++++- launcher/modplatform/flame/FlamePackIndex.h | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/launcher/modplatform/flame/FlamePackIndex.cpp b/launcher/modplatform/flame/FlamePackIndex.cpp index ac24c6471..6d48a3bf2 100644 --- a/launcher/modplatform/flame/FlamePackIndex.cpp +++ b/launcher/modplatform/flame/FlamePackIndex.cpp @@ -65,7 +65,15 @@ void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr) // pick the latest version supported file.mcVersion = versionArray[0].toString(); file.version = Json::requireString(version, "displayName"); - file.downloadUrl = Json::requireString(version, "downloadUrl"); + file.fileName = Json::requireString(version, "fileName"); + file.downloadUrl = Json::ensureString(version, "downloadUrl"); + if(file.downloadUrl.isEmpty()){ + //FIXME : HACK, MAY NOT WORK FOR LONG + file.downloadUrl = QString("https://media.forgecdn.net/files/%1/%2/%3") + .arg(QString::number(QString::number(file.fileId).leftRef(4).toInt()) + ,QString::number(QString::number(file.fileId).rightRef(3).toInt()) + ,QUrl::toPercentEncoding(file.fileName)); + } unsortedVersions.append(file); } diff --git a/launcher/modplatform/flame/FlamePackIndex.h b/launcher/modplatform/flame/FlamePackIndex.h index 7ffa29c3d..a8bb15be4 100644 --- a/launcher/modplatform/flame/FlamePackIndex.h +++ b/launcher/modplatform/flame/FlamePackIndex.h @@ -18,6 +18,7 @@ struct IndexedVersion { QString version; QString mcVersion; QString downloadUrl; + QString fileName; }; struct IndexedPack From 613f2fc4479dbfc3cf3149b0a2ceb0df1f26095f Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 22 Apr 2022 13:20:31 -0300 Subject: [PATCH 047/308] feat: allow deselecting mods from the mod confirmation dialog This adds a checkbox to each mod on the dialog that shows up when confirming the mods to download, so you can deselect some of those if you want to. --- launcher/ui/dialogs/ModDownloadDialog.cpp | 18 ++--- launcher/ui/dialogs/ReviewMessageBox.cpp | 27 +++++++- launcher/ui/dialogs/ReviewMessageBox.h | 12 +++- launcher/ui/dialogs/ReviewMessageBox.ui | 81 ++++++++--------------- 4 files changed, 71 insertions(+), 67 deletions(-) diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index 305e85c06..436f51f96 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -77,18 +77,20 @@ void ModDownloadDialog::confirm() auto keys = modTask.keys(); keys.sort(Qt::CaseInsensitive); - auto confirm_dialog = ReviewMessageBox::create( - this, - tr("Confirm mods to download") - ); + auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm mods to download")); - for(auto& task : keys){ - confirm_dialog->appendMod(task, modTask.find(task).value()->getFilename()); + for (auto& task : keys) { + confirm_dialog->appendMod({ task, modTask.find(task).value()->getFilename() }); } - connect(confirm_dialog, &QDialog::accepted, this, &ModDownloadDialog::accept); + if (confirm_dialog->exec()) { + auto deselected = confirm_dialog->deselectedMods(); + for (auto name : deselected) { + modTask.remove(name); + } - confirm_dialog->open(); + this->accept(); + } } void ModDownloadDialog::accept() diff --git a/launcher/ui/dialogs/ReviewMessageBox.cpp b/launcher/ui/dialogs/ReviewMessageBox.cpp index 2bfd02e0c..c92234a40 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.cpp +++ b/launcher/ui/dialogs/ReviewMessageBox.cpp @@ -5,6 +5,9 @@ ReviewMessageBox::ReviewMessageBox(QWidget* parent, QString const& title, QStrin : QDialog(parent), ui(new Ui::ReviewMessageBox) { ui->setupUi(this); + + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &ReviewMessageBox::accept); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &ReviewMessageBox::reject); } ReviewMessageBox::~ReviewMessageBox() @@ -17,15 +20,33 @@ auto ReviewMessageBox::create(QWidget* parent, QString&& title, QString&& icon) return new ReviewMessageBox(parent, title, icon); } -void ReviewMessageBox::appendMod(const QString& name, const QString& filename) +void ReviewMessageBox::appendMod(ModInformation&& info) { auto itemTop = new QTreeWidgetItem(ui->modTreeWidget); - itemTop->setText(0, name); + itemTop->setCheckState(0, Qt::CheckState::Checked); + itemTop->setText(0, info.name); auto filenameItem = new QTreeWidgetItem(itemTop); - filenameItem->setText(0, tr("Filename: %1").arg(filename)); + filenameItem->setText(0, tr("Filename: %1").arg(info.filename)); itemTop->insertChildren(0, { filenameItem }); ui->modTreeWidget->addTopLevelItem(itemTop); } + +auto ReviewMessageBox::deselectedMods() -> QStringList +{ + QStringList list; + + auto* item = ui->modTreeWidget->topLevelItem(0); + + for (int i = 0; item != nullptr; ++i) { + if (item->checkState(0) == Qt::CheckState::Unchecked) { + list.append(item->text(0)); + } + + item = ui->modTreeWidget->topLevelItem(i); + } + + return list; +} diff --git a/launcher/ui/dialogs/ReviewMessageBox.h b/launcher/ui/dialogs/ReviewMessageBox.h index 48742cd9f..9cfa679a5 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.h +++ b/launcher/ui/dialogs/ReviewMessageBox.h @@ -6,17 +6,23 @@ namespace Ui { class ReviewMessageBox; } -class ReviewMessageBox final : public QDialog { +class ReviewMessageBox : public QDialog { Q_OBJECT public: static auto create(QWidget* parent, QString&& title, QString&& icon = "") -> ReviewMessageBox*; - void appendMod(const QString& name, const QString& filename); + using ModInformation = struct { + QString name; + QString filename; + }; + + void appendMod(ModInformation&& info); + auto deselectedMods() -> QStringList; ~ReviewMessageBox(); - private: + protected: ReviewMessageBox(QWidget* parent, const QString& title, const QString& icon); Ui::ReviewMessageBox* ui; diff --git a/launcher/ui/dialogs/ReviewMessageBox.ui b/launcher/ui/dialogs/ReviewMessageBox.ui index d04f3b3f4..ab3bcc2fa 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.ui +++ b/launcher/ui/dialogs/ReviewMessageBox.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 300 + 500 + 350 @@ -20,24 +20,7 @@ true - - - - You're about to download the following mods: - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - true @@ -58,41 +41,33 @@ + + + + You're about to download the following mods: + + + + + + + + + Only mods with a check will be downloaded! + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + - - - buttonBox - accepted() - ReviewMessageBox - accept() - - - 200 - 265 - - - 199 - 149 - - - - - buttonBox - rejected() - ReviewMessageBox - reject() - - - 200 - 265 - - - 199 - 149 - - - - + From 8f2c485c926e53f8b31f420f3d5caec090982498 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 28 Apr 2022 20:14:03 -0300 Subject: [PATCH 048/308] feat(ui): make selected mods in downloader bold with underline Makes it easier to find which mods are selected in case you want to change those. --- launcher/ui/dialogs/ModDownloadDialog.cpp | 6 +++ launcher/ui/dialogs/ModDownloadDialog.h | 3 +- launcher/ui/pages/modplatform/ModModel.cpp | 55 ++++++++++++++-------- launcher/ui/pages/modplatform/ModPage.h | 1 + 4 files changed, 44 insertions(+), 21 deletions(-) diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index 436f51f96..f01c9c07f 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -134,6 +134,12 @@ bool ModDownloadDialog::isModSelected(const QString &name, const QString& filena return iter != modTask.end() && (iter.value()->getFilename() == filename); } +bool ModDownloadDialog::isModSelected(const QString &name) const +{ + auto iter = modTask.find(name); + return iter != modTask.end(); +} + ModDownloadDialog::~ModDownloadDialog() { } diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h index 782dc3619..5c565ad31 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.h +++ b/launcher/ui/dialogs/ModDownloadDialog.h @@ -32,6 +32,7 @@ public: void addSelectedMod(const QString & name = QString(), ModDownloadTask * task = nullptr); void removeSelectedMod(const QString & name = QString()); bool isModSelected(const QString & name, const QString & filename) const; + bool isModSelected(const QString & name) const; const QList getTasks(); const std::shared_ptr &mods; @@ -41,8 +42,6 @@ public slots: void accept() override; void reject() override; -//private slots: - private: Ui::ModDownloadDialog *ui = nullptr; PageContainer * m_container = nullptr; diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 540ee2fdc..67d1de3eb 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -38,27 +38,44 @@ auto ListModel::data(const QModelIndex& index, int role) const -> QVariant } ModPlatform::IndexedPack pack = modpacks.at(pos); - if (role == Qt::DisplayRole) { - return pack.name; - } else if (role == Qt::ToolTipRole) { - if (pack.description.length() > 100) { - // some magic to prevent to long tooltips and replace html linebreaks - QString edit = pack.description.left(97); - edit = edit.left(edit.lastIndexOf("
")).left(edit.lastIndexOf(" ")).append("..."); - return edit; + switch (role) { + case Qt::DisplayRole: { + return pack.name; } - return pack.description; - } else if (role == Qt::DecorationRole) { - if (m_logoMap.contains(pack.logoName)) { - return (m_logoMap.value(pack.logoName)); + case Qt::ToolTipRole: { + if (pack.description.length() > 100) { + // some magic to prevent to long tooltips and replace html linebreaks + QString edit = pack.description.left(97); + edit = edit.left(edit.lastIndexOf("
")).left(edit.lastIndexOf(" ")).append("..."); + return edit; + } + return pack.description; } - QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); - ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); - return icon; - } else if (role == Qt::UserRole) { - QVariant v; - v.setValue(pack); - return v; + case Qt::DecorationRole: { + if (m_logoMap.contains(pack.logoName)) { + return (m_logoMap.value(pack.logoName)); + } + QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); + // un-const-ify this + ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); + return icon; + } + case Qt::UserRole: { + QVariant v; + v.setValue(pack); + return v; + } + case Qt::FontRole: { + QFont font; + if (m_parent->getDialog()->isModSelected(pack.name)) { + font.setBold(true); + font.setUnderline(true); + } + + return font; + } + default: + break; } return {}; diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index eb89b0e28..8ffc4a537 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -41,6 +41,7 @@ class ModPage : public QWidget, public BasePage { auto apiProvider() const -> const ModAPI* { return api.get(); }; auto getFilter() const -> const std::shared_ptr { return m_filter; } + auto getDialog() const -> const ModDownloadDialog* { return dialog; } auto getCurrent() -> ModPlatform::IndexedPack& { return current; } void updateModVersions(int prev_count = -1); From 166f8727121399f7604d25580ced39472e9a3034 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 1 May 2022 11:08:00 -0300 Subject: [PATCH 049/308] fix: various issues with ProgressDialog and SequentialTasks - Fix aborting sequential tasks - Fix displaying wrong number of tasks concluded - Fix text cutting when the URL is too big --- launcher/tasks/SequentialTask.cpp | 14 ++- launcher/ui/dialogs/ProgressDialog.cpp | 91 ++++++++------------ launcher/ui/dialogs/ProgressDialog.ui | 6 ++ launcher/ui/pages/instance/ModFolderPage.cpp | 5 ++ 4 files changed, 58 insertions(+), 58 deletions(-) diff --git a/launcher/tasks/SequentialTask.cpp b/launcher/tasks/SequentialTask.cpp index 1573e476c..e7d585246 100644 --- a/launcher/tasks/SequentialTask.cpp +++ b/launcher/tasks/SequentialTask.cpp @@ -33,11 +33,17 @@ void SequentialTask::executeTask() bool SequentialTask::abort() { - bool succeeded = true; - for (auto& task : m_queue) { - if (!task->abort()) succeeded = false; + if(m_currentIndex == -1 || m_currentIndex >= m_queue.size()) { + m_queue.clear(); + return true; } + bool succeeded = m_queue[m_currentIndex]->abort(); + m_queue.clear(); + + if(succeeded) + emitAborted(); + return succeeded; } @@ -76,7 +82,7 @@ void SequentialTask::subTaskProgress(qint64 current, qint64 total) setProgress(0, 100); return; } - setProgress(m_currentIndex, m_queue.count()); + setProgress(m_currentIndex + 1, m_queue.count()); m_stepProgress = current; m_stepTotalProgress = total; diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 648bd88bb..e5226016e 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -16,12 +16,12 @@ #include "ProgressDialog.h" #include "ui_ProgressDialog.h" -#include #include +#include #include "tasks/Task.h" -ProgressDialog::ProgressDialog(QWidget *parent) : QDialog(parent), ui(new Ui::ProgressDialog) +ProgressDialog::ProgressDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ProgressDialog) { ui->setupUi(this); this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); @@ -44,6 +44,7 @@ void ProgressDialog::on_skipButton_clicked(bool checked) { Q_UNUSED(checked); task->abort(); + QDialog::reject(); } ProgressDialog::~ProgressDialog() @@ -53,24 +54,22 @@ ProgressDialog::~ProgressDialog() void ProgressDialog::updateSize() { - QSize qSize = QSize(480, minimumSizeHint().height()); + QSize qSize = QSize(480, minimumSizeHint().height()); resize(qSize); - setFixedSize(qSize); + setFixedSize(qSize); } -int ProgressDialog::execWithTask(Task *task) +int ProgressDialog::execWithTask(Task* task) { this->task = task; QDialog::DialogCode result; - if(!task) - { + if (!task) { qDebug() << "Programmer error: progress dialog created with null task."; return Accepted; } - if(handleImmediateResult(result)) - { + if (handleImmediateResult(result)) { return result; } @@ -78,58 +77,51 @@ int ProgressDialog::execWithTask(Task *task) connect(task, SIGNAL(started()), SLOT(onTaskStarted())); connect(task, SIGNAL(failed(QString)), SLOT(onTaskFailed(QString))); connect(task, SIGNAL(succeeded()), SLOT(onTaskSucceeded())); - connect(task, SIGNAL(status(QString)), SLOT(changeStatus(const QString &))); + connect(task, SIGNAL(status(QString)), SLOT(changeStatus(const QString&))); + connect(task, SIGNAL(stepStatus(QString)), SLOT(changeStatus(const QString&))); connect(task, SIGNAL(progress(qint64, qint64)), SLOT(changeProgress(qint64, qint64))); + connect(task, &Task::aborted, [this] { onTaskFailed(tr("Aborted by user")); }); + m_is_multi_step = task->isMultiStep(); - if(!m_is_multi_step){ + if (!m_is_multi_step) { ui->globalStatusLabel->setHidden(true); ui->globalProgressBar->setHidden(true); } // if this didn't connect to an already running task, invoke start - if(!task->isRunning()) - { + if (!task->isRunning()) { task->start(); } - if(task->isRunning()) - { + if (task->isRunning()) { changeProgress(task->getProgress(), task->getTotalProgress()); changeStatus(task->getStatus()); return QDialog::exec(); - } - else if(handleImmediateResult(result)) - { + } else if (handleImmediateResult(result)) { return result; - } - else - { + } else { return QDialog::Rejected; } } // TODO: only provide the unique_ptr overloads -int ProgressDialog::execWithTask(std::unique_ptr &&task) +int ProgressDialog::execWithTask(std::unique_ptr&& task) { connect(this, &ProgressDialog::destroyed, task.get(), &Task::deleteLater); return execWithTask(task.release()); } -int ProgressDialog::execWithTask(std::unique_ptr &task) +int ProgressDialog::execWithTask(std::unique_ptr& task) { connect(this, &ProgressDialog::destroyed, task.get(), &Task::deleteLater); return execWithTask(task.release()); } -bool ProgressDialog::handleImmediateResult(QDialog::DialogCode &result) +bool ProgressDialog::handleImmediateResult(QDialog::DialogCode& result) { - if(task->isFinished()) - { - if(task->wasSuccessful()) - { + if (task->isFinished()) { + if (task->wasSuccessful()) { result = QDialog::Accepted; - } - else - { + } else { result = QDialog::Rejected; } return true; @@ -137,14 +129,12 @@ bool ProgressDialog::handleImmediateResult(QDialog::DialogCode &result) return false; } -Task *ProgressDialog::getTask() +Task* ProgressDialog::getTask() { return task; } -void ProgressDialog::onTaskStarted() -{ -} +void ProgressDialog::onTaskStarted() {} void ProgressDialog::onTaskFailed(QString failure) { @@ -156,10 +146,11 @@ void ProgressDialog::onTaskSucceeded() accept(); } -void ProgressDialog::changeStatus(const QString &status) +void ProgressDialog::changeStatus(const QString& status) { + ui->globalStatusLabel->setText(task->getStatus()); ui->statusLabel->setText(task->getStepStatus()); - ui->globalStatusLabel->setText(status); + updateSize(); } @@ -168,27 +159,22 @@ void ProgressDialog::changeProgress(qint64 current, qint64 total) ui->globalProgressBar->setMaximum(total); ui->globalProgressBar->setValue(current); - if(!m_is_multi_step){ + if (!m_is_multi_step) { ui->taskProgressBar->setMaximum(total); ui->taskProgressBar->setValue(current); - } - else{ + } else { ui->taskProgressBar->setMaximum(task->getStepProgress()); ui->taskProgressBar->setValue(task->getStepTotalProgress()); } } -void ProgressDialog::keyPressEvent(QKeyEvent *e) +void ProgressDialog::keyPressEvent(QKeyEvent* e) { - if(ui->skipButton->isVisible()) - { - if (e->key() == Qt::Key_Escape) - { + if (ui->skipButton->isVisible()) { + if (e->key() == Qt::Key_Escape) { on_skipButton_clicked(true); return; - } - else if(e->key() == Qt::Key_Tab) - { + } else if (e->key() == Qt::Key_Tab) { ui->skipButton->setFocusPolicy(Qt::StrongFocus); ui->skipButton->setFocus(); ui->skipButton->setAutoDefault(true); @@ -199,14 +185,11 @@ void ProgressDialog::keyPressEvent(QKeyEvent *e) QDialog::keyPressEvent(e); } -void ProgressDialog::closeEvent(QCloseEvent *e) +void ProgressDialog::closeEvent(QCloseEvent* e) { - if (task && task->isRunning()) - { + if (task && task->isRunning()) { e->ignore(); - } - else - { + } else { QDialog::closeEvent(e); } } diff --git a/launcher/ui/dialogs/ProgressDialog.ui b/launcher/ui/dialogs/ProgressDialog.ui index bf119a785..34ab71e32 100644 --- a/launcher/ui/dialogs/ProgressDialog.ui +++ b/launcher/ui/dialogs/ProgressDialog.ui @@ -40,6 +40,12 @@
+ + + 0 + 0 + + Task Status... diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 8113fe857..cba255645 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -402,6 +402,10 @@ void ModFolderPage::on_actionInstall_mods_triggered() CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); + connect(tasks, &Task::aborted, [this, tasks]() { + CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); + tasks->deleteLater(); + }); connect(tasks, &Task::succeeded, [this, tasks]() { QStringList warnings = tasks->warnings(); if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); } @@ -411,6 +415,7 @@ void ModFolderPage::on_actionInstall_mods_triggered() for (auto task : mdownload.getTasks()) { tasks->addTask(task); } + ProgressDialog loadDialog(this); loadDialog.setSkipButton(true, tr("Abort")); loadDialog.execWithTask(tasks); From 7c251efc473ee90069d1e87a056bde64f1d6fbf7 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Mon, 2 May 2022 20:27:20 +0100 Subject: [PATCH 050/308] ATLauncher: Display mod colours in optional mod dialog --- .../atlauncher/ATLPackInstallTask.cpp | 2 +- .../atlauncher/ATLPackInstallTask.h | 2 +- .../atlauncher/ATLPackManifest.cpp | 6 ++++++ .../modplatform/atlauncher/ATLPackManifest.h | 6 +++++- .../atlauncher/AtlOptionalModDialog.cpp | 21 +++++++++++++------ .../atlauncher/AtlOptionalModDialog.h | 6 ++++-- .../pages/modplatform/atlauncher/AtlPage.cpp | 5 +++-- .../ui/pages/modplatform/atlauncher/AtlPage.h | 2 +- 8 files changed, 36 insertions(+), 14 deletions(-) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 4b8b8eb01..90dc13654 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -556,7 +556,7 @@ void PackInstallTask::downloadMods() QVector selectedMods; if (!optionalMods.isEmpty()) { setStatus(tr("Selecting optional mods...")); - selectedMods = m_support->chooseOptionalMods(optionalMods); + selectedMods = m_support->chooseOptionalMods(m_version, optionalMods); } setStatus(tr("Downloading mods...")); diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h index 783ec19b0..6bc30689e 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h @@ -37,7 +37,7 @@ public: /** * Requests a user interaction to select which optional mods should be installed. */ - virtual QVector chooseOptionalMods(QVector mods) = 0; + virtual QVector chooseOptionalMods(PackVersion version, QVector mods) = 0; /** * Requests a user interaction to select a component version from a given version list diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.cpp b/launcher/modplatform/atlauncher/ATLPackManifest.cpp index 40be6d537..a8f2711b0 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.cpp +++ b/launcher/modplatform/atlauncher/ATLPackManifest.cpp @@ -178,6 +178,7 @@ static void loadVersionMod(ATLauncher::VersionMod & p, QJsonObject & obj) { p.depends.append(Json::requireString(depends)); } } + p.colour = Json::ensureString(obj, QString("colour"), ""); p.client = Json::ensureBoolean(obj, QString("client"), false); @@ -232,4 +233,9 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj) auto configsObj = Json::requireObject(obj, "configs"); loadVersionConfigs(v.configs, configsObj); } + + auto colourObj = Json::ensureObject(obj, "colours"); + for (const auto &key : colourObj.keys()) { + v.colours[key] = Json::requireString(colourObj.value(key), "colour"); + } } diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.h b/launcher/modplatform/atlauncher/ATLPackManifest.h index 673f2f8bc..2911107ed 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.h +++ b/launcher/modplatform/atlauncher/ATLPackManifest.h @@ -16,9 +16,10 @@ #pragma once +#include +#include #include #include -#include namespace ATLauncher { @@ -109,6 +110,7 @@ struct VersionMod bool library; QString group; QVector depends; + QString colour; bool client; @@ -134,6 +136,8 @@ struct PackVersion QVector libraries; QVector mods; VersionConfigs configs; + + QMap colours; }; void loadVersion(PackVersion & v, QJsonObject & obj); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp index 26aa60af5..aee5a78e5 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp @@ -43,8 +43,11 @@ #include "modplatform/atlauncher/ATLShareCode.h" #include "Application.h" -AtlOptionalModListModel::AtlOptionalModListModel(QWidget *parent, QVector mods) - : QAbstractListModel(parent), m_mods(mods) { +AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, ATLauncher::PackVersion version, QVector mods) + : QAbstractListModel(parent) + , m_version(version) + , m_mods(mods) +{ // fill mod index for (int i = 0; i < m_mods.size(); i++) { auto mod = m_mods.at(i); @@ -97,6 +100,11 @@ QVariant AtlOptionalModListModel::data(const QModelIndex &index, int role) const return mod.description; } } + else if (role == Qt::ForegroundRole) { + if (!mod.colour.isEmpty() && m_version.colours.contains(mod.colour)) { + return QColor(QString("#%1").arg(m_version.colours[mod.colour])); + } + } else if (role == Qt::CheckStateRole) { if (index.column() == EnabledColumn) { return m_selection[mod.name] ? Qt::Checked : Qt::Unchecked; @@ -287,12 +295,13 @@ void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool } } - -AtlOptionalModDialog::AtlOptionalModDialog(QWidget *parent, QVector mods) - : QDialog(parent), ui(new Ui::AtlOptionalModDialog) { +AtlOptionalModDialog::AtlOptionalModDialog(QWidget* parent, ATLauncher::PackVersion version, QVector mods) + : QDialog(parent) + , ui(new Ui::AtlOptionalModDialog) +{ ui->setupUi(this); - listModel = new AtlOptionalModListModel(this, mods); + listModel = new AtlOptionalModListModel(this, version, mods); ui->treeView->setModel(listModel); ui->treeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h index 953b288ea..8e02444e4 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h @@ -56,7 +56,7 @@ public: DescriptionColumn, }; - AtlOptionalModListModel(QWidget *parent, QVector mods); + AtlOptionalModListModel(QWidget *parent, ATLauncher::PackVersion version, QVector mods); QVector getResult(); @@ -86,7 +86,9 @@ private: NetJob::Ptr m_jobPtr; QByteArray m_response; + ATLauncher::PackVersion m_version; QVector m_mods; + QMap m_selection; QMap m_index; QMap> m_dependants; @@ -96,7 +98,7 @@ class AtlOptionalModDialog : public QDialog { Q_OBJECT public: - AtlOptionalModDialog(QWidget *parent, QVector mods); + AtlOptionalModDialog(QWidget *parent, ATLauncher::PackVersion version, QVector mods); ~AtlOptionalModDialog() override; QVector getResult() { diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp index df9b92070..03923ed9c 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp @@ -169,8 +169,9 @@ void AtlPage::onVersionSelectionChanged(QString data) suggestCurrent(); } -QVector AtlPage::chooseOptionalMods(QVector mods) { - AtlOptionalModDialog optionalModDialog(this, mods); +QVector AtlPage::chooseOptionalMods(ATLauncher::PackVersion version, QVector mods) +{ + AtlOptionalModDialog optionalModDialog(this, version, mods); optionalModDialog.exec(); return optionalModDialog.getResult(); } diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.h b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h index c95b01275..eac86b51b 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h @@ -84,7 +84,7 @@ private: void suggestCurrent(); QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) override; - QVector chooseOptionalMods(QVector mods) override; + QVector chooseOptionalMods(ATLauncher::PackVersion version, QVector mods) override; private slots: void triggerSearch(); From 305973c0e7c07693a8b08d1908e64fc4986e13e0 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Thu, 5 May 2022 20:14:19 +0100 Subject: [PATCH 051/308] ATLauncher: Display install messages if applicable --- .../atlauncher/ATLPackInstallTask.cpp | 7 ++- .../atlauncher/ATLPackInstallTask.h | 45 +++++++++++++---- .../atlauncher/ATLPackManifest.cpp | 50 +++++++++++++++---- .../modplatform/atlauncher/ATLPackManifest.h | 46 +++++++++++++---- .../pages/modplatform/atlauncher/AtlPage.cpp | 13 ++++- .../ui/pages/modplatform/atlauncher/AtlPage.h | 1 + 6 files changed, 126 insertions(+), 36 deletions(-) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 90dc13654..9b14f3557 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -95,14 +95,13 @@ void PackInstallTask::onDownloadSucceeded() qDebug() << "PackInstallTask::onDownloadSucceeded: " << QThread::currentThreadId(); jobPtr.reset(); - QJsonParseError parse_error; + QJsonParseError parse_error {}; QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); if(parse_error.error != QJsonParseError::NoError) { qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << response; return; } - auto obj = doc.object(); ATLauncher::PackVersion version; @@ -117,6 +116,10 @@ void PackInstallTask::onDownloadSucceeded() } m_version = version; + // Display install message if one exists + if (!m_version.messages.install.isEmpty()) + m_support->displayMessage(m_version.messages.install); + auto ver = getComponentVersion("net.minecraft", m_version.minecraft); if (!ver) { emitFailed(tr("Failed to get local metadata index for '%1' v%2").arg("net.minecraft", m_version.minecraft)); diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h index 6bc30689e..f0af4e3a2 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h @@ -1,18 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only /* - * Copyright 2020-2021 Jamie Mansfield - * Copyright 2021 Petr Mrazek + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 Jamie Mansfield * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 2020-2021 Jamie Mansfield + * Copyright 2021 Petr Mrazek + * + * 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 @@ -45,6 +64,10 @@ public: */ virtual QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) = 0; + /** + * Requests a user interaction to display a message. + */ + virtual void displayMessage(QString message) = 0; }; class PackInstallTask : public InstanceTask diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.cpp b/launcher/modplatform/atlauncher/ATLPackManifest.cpp index a8f2711b0..259c170cc 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.cpp +++ b/launcher/modplatform/atlauncher/ATLPackManifest.cpp @@ -1,18 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only /* - * Copyright 2020-2021 Jamie Mansfield - * Copyright 2021 Petr Mrazek + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 Jamie Mansfield * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 2020-2021 Jamie Mansfield + * Copyright 2021 Petr Mrazek + * + * 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 "ATLPackManifest.h" @@ -186,6 +205,12 @@ static void loadVersionMod(ATLauncher::VersionMod & p, QJsonObject & obj) { p.effectively_hidden = p.hidden || p.library; } +static void loadVersionMessages(ATLauncher::VersionMessages& m, QJsonObject& obj) +{ + m.install = Json::ensureString(obj, "install", ""); + m.update = Json::ensureString(obj, "update", ""); +} + void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj) { v.version = Json::requireString(obj, "version"); @@ -238,4 +263,7 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj) for (const auto &key : colourObj.keys()) { v.colours[key] = Json::requireString(colourObj.value(key), "colour"); } + + auto messages = Json::ensureObject(obj, "messages"); + loadVersionMessages(v.messages, messages); } diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.h b/launcher/modplatform/atlauncher/ATLPackManifest.h index 2911107ed..931a11dc3 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.h +++ b/launcher/modplatform/atlauncher/ATLPackManifest.h @@ -1,17 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0-only /* - * Copyright 2020 Jamie Mansfield + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 Jamie Mansfield * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 2020 Jamie Mansfield + * + * 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 @@ -124,6 +143,12 @@ struct VersionConfigs QString sha1; }; +struct VersionMessages +{ + QString install; + QString update; +}; + struct PackVersion { QString version; @@ -138,6 +163,7 @@ struct PackVersion VersionConfigs configs; QMap colours; + VersionMessages messages; }; void loadVersion(PackVersion & v, QJsonObject & obj); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp index 03923ed9c..7bc6fc6b8 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp @@ -45,8 +45,12 @@ #include -AtlPage::AtlPage(NewInstanceDialog* dialog, QWidget *parent) - : QWidget(parent), ui(new Ui::AtlPage), dialog(dialog) +#include + +AtlPage::AtlPage(NewInstanceDialog* dialog, QWidget* parent) + : QWidget(parent) + , ui(new Ui::AtlPage) + , dialog(dialog) { ui->setupUi(this); @@ -211,3 +215,8 @@ QString AtlPage::chooseVersion(Meta::VersionListPtr vlist, QString minecraftVers vselect.exec(); return vselect.selectedVersion()->descriptor(); } + +void AtlPage::displayMessage(QString message) +{ + QMessageBox::information(this, tr("Installing"), message); +} diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.h b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h index eac86b51b..aa6d5da15 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h @@ -85,6 +85,7 @@ private: QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) override; QVector chooseOptionalMods(ATLauncher::PackVersion version, QVector mods) override; + void displayMessage(QString message) override; private slots: void triggerSearch(); From b84d52be3d1109efc2c9e35304831314050bd398 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Thu, 5 May 2022 20:58:12 +0100 Subject: [PATCH 052/308] ATLauncher: Display warnings when selecting optional mods --- .../modplatform/atlauncher/ATLPackManifest.cpp | 6 ++++++ .../modplatform/atlauncher/ATLPackManifest.h | 2 ++ .../atlauncher/AtlOptionalModDialog.cpp | 16 +++++++++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.cpp b/launcher/modplatform/atlauncher/ATLPackManifest.cpp index 259c170cc..d01ec32cf 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.cpp +++ b/launcher/modplatform/atlauncher/ATLPackManifest.cpp @@ -198,6 +198,7 @@ static void loadVersionMod(ATLauncher::VersionMod & p, QJsonObject & obj) { } } p.colour = Json::ensureString(obj, QString("colour"), ""); + p.warning = Json::ensureString(obj, QString("warning"), ""); p.client = Json::ensureBoolean(obj, QString("client"), false); @@ -264,6 +265,11 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj) v.colours[key] = Json::requireString(colourObj.value(key), "colour"); } + auto warningsObj = Json::ensureObject(obj, "warnings"); + for (const auto &key : warningsObj.keys()) { + v.warnings[key] = Json::requireString(warningsObj.value(key), "warning"); + } + auto messages = Json::ensureObject(obj, "messages"); loadVersionMessages(v.messages, messages); } diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.h b/launcher/modplatform/atlauncher/ATLPackManifest.h index 931a11dc3..23e162e30 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.h +++ b/launcher/modplatform/atlauncher/ATLPackManifest.h @@ -130,6 +130,7 @@ struct VersionMod QString group; QVector depends; QString colour; + QString warning; bool client; @@ -163,6 +164,7 @@ struct PackVersion VersionConfigs configs; QMap colours; + QMap warnings; VersionMessages messages; }; diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp index aee5a78e5..004fdc57a 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp @@ -231,7 +231,21 @@ void AtlOptionalModListModel::clearAll() { } void AtlOptionalModListModel::toggleMod(ATLauncher::VersionMod mod, int index) { - setMod(mod, index, !m_selection[mod.name]); + auto enable = !m_selection[mod.name]; + + // If there is a warning for the mod, display that first (if we would be enabling the mod) + if (enable && !mod.warning.isEmpty() && m_version.warnings.contains(mod.warning)) { + auto message = QString("%1

%2") + .arg(m_version.warnings[mod.warning], tr("Are you sure that you want to enable this mod?")); + + // fixme: avoid casting here + auto result = QMessageBox::warning((QWidget*) this->parent(), tr("Warning"), message, QMessageBox::Yes | QMessageBox::No); + if (result != QMessageBox::Yes) { + return; + } + } + + setMod(mod, index, enable); } void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit) { From b2a89ee4b99f1d89dddee2918195f73b6b92c9db Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Sat, 21 May 2022 16:59:01 +0200 Subject: [PATCH 053/308] change cf icon to a more fancy one taken from QuiltMC/art in the emoji folder, so it's licensed under CC0 --- .../multimc/128x128/instances/flame.png | Bin 3375 -> 6226 bytes .../multimc/32x32/instances/flame.png | Bin 849 -> 0 bytes launcher/resources/multimc/multimc.qrc | 4 +--- 3 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 launcher/resources/multimc/32x32/instances/flame.png diff --git a/launcher/resources/multimc/128x128/instances/flame.png b/launcher/resources/multimc/128x128/instances/flame.png index 8a50a0b418e8dc27ebb91ab3886d8bea40987104..6482975c494e02522f34a10894bb434f1ccc7194 100644 GIT binary patch literal 6226 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 YzkS2@~ literal 3375 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 b(Z!BG!B5Qc$A}(=00000NkvXXu0mjfi>7~8 diff --git a/launcher/resources/multimc/multimc.qrc b/launcher/resources/multimc/multimc.qrc index e22fe7eef..2337acd60 100644 --- a/launcher/resources/multimc/multimc.qrc +++ b/launcher/resources/multimc/multimc.qrc @@ -6,8 +6,7 @@ scalable/reddit-alien.svg - - 32x32/instances/flame.png + 128x128/instances/flame.png @@ -272,7 +271,6 @@ 32x32/instances/ftb_logo.png 128x128/instances/ftb_logo.png - 32x32/instances/flame.png 128x128/instances/flame.png 32x32/instances/gear.png From 35f71f5793ee91a71e00464932ff95eb5e5e4d5e Mon Sep 17 00:00:00 2001 From: Lenny McLennington Date: Wed, 11 May 2022 21:44:06 +0100 Subject: [PATCH 054/308] Support paste.gg, hastebin, and mclo.gs --- launcher/Application.cpp | 33 +++++- launcher/net/PasteUpload.cpp | 165 ++++++++++++++++++++++++--- launcher/net/PasteUpload.h | 28 ++++- launcher/ui/GuiUtil.cpp | 5 +- launcher/ui/pages/global/APIPage.cpp | 51 ++++++++- launcher/ui/pages/global/APIPage.h | 1 + launcher/ui/pages/global/APIPage.ui | 64 +++-------- 7 files changed, 272 insertions(+), 75 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index ce62c41af..b36fd89a3 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -36,6 +36,7 @@ #include "Application.h" #include "BuildConfig.h" +#include "net/PasteUpload.h" #include "ui/MainWindow.h" #include "ui/InstanceWindow.h" @@ -671,8 +672,36 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("UpdateDialogGeometry", ""); - // pastebin URL - m_settings->registerSetting("PastebinURL", "https://0x0.st"); + // This code feels so stupid is there a less stupid way of doing this? + { + m_settings->registerSetting("PastebinURL", ""); + QString pastebinURL = m_settings->get("PastebinURL").toString(); + + // If PastebinURL hasn't been set before then use the new default: mclo.gs + if (pastebinURL == "") { + m_settings->registerSetting("PastebinType", PasteUpload::PasteType::Mclogs); + m_settings->registerSetting("PastebinCustomAPIBase", ""); + } + // Otherwise: use 0x0.st + else { + // The default custom endpoint would usually be "" (meaning there is no custom endpoint specified) + // But if the user had customised the paste URL then that should be carried over into the custom endpoint. + QString defaultCustomEndpoint = (pastebinURL == "https://0x0.st") ? "" : pastebinURL; + m_settings->registerSetting("PastebinType", PasteUpload::PasteType::NullPointer); + m_settings->registerSetting("PastebinCustomAPIBase", defaultCustomEndpoint); + + m_settings->reset("PastebinURL"); + } + + bool ok; + unsigned int pasteType = m_settings->get("PastebinType").toUInt(&ok); + // If PastebinType is invalid then reset the related settings. + if (!ok || !(PasteUpload::PasteType::First <= pasteType && pasteType <= PasteUpload::PasteType::Last)) + { + m_settings->reset("PastebinType"); + m_settings->reset("PastebinCustomAPIBase"); + } + } m_settings->registerSetting("CloseAfterLaunch", false); m_settings->registerSetting("QuitAfterGameStop", false); diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index 3d106c927..d583216d8 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -42,8 +42,22 @@ #include #include -PasteUpload::PasteUpload(QWidget *window, QString text, QString url) : m_window(window), m_uploadUrl(url), m_text(text.toUtf8()) +std::array PasteUpload::PasteTypes = { + {{"0x0", "https://0x0.st", ""}, + {"hastebin", "https://hastebin.com", "/documents"}, + {"paste (paste.gg)", "https://paste.gg", "/api/v1/pastes"}, + {"mclogs", "https://api.mclo.gs", "/1/log"}}}; + +PasteUpload::PasteUpload(QWidget *window, QString text, QString baseUrl, PasteType pasteType) : m_window(window), m_baseUrl(baseUrl), m_pasteType(pasteType), m_text(text.toUtf8()) { + if (m_baseUrl == "") + m_baseUrl = PasteTypes.at(pasteType).defaultBase; + + // HACK: Paste's docs say the standard API path is at /api/ but the official instance paste.gg doesn't follow that?? + if (pasteType == PasteGG && m_baseUrl == PasteTypes.at(pasteType).defaultBase) + m_uploadUrl = "https://api.paste.gg/v1/pastes"; + else + m_uploadUrl = m_baseUrl + PasteTypes.at(pasteType).endpointPath; } PasteUpload::~PasteUpload() @@ -53,26 +67,73 @@ PasteUpload::~PasteUpload() void PasteUpload::executeTask() { QNetworkRequest request{QUrl(m_uploadUrl)}; + QNetworkReply *rep{}; + request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); - QHttpMultiPart *multiPart = new QHttpMultiPart{QHttpMultiPart::FormDataType}; + switch (m_pasteType) { + case NullPointer: { + QHttpMultiPart *multiPart = + new QHttpMultiPart{QHttpMultiPart::FormDataType}; - QHttpPart filePart; - filePart.setBody(m_text); - filePart.setHeader(QNetworkRequest::ContentTypeHeader, "text/plain"); - filePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"file\"; filename=\"log.txt\""); + QHttpPart filePart; + filePart.setBody(m_text); + filePart.setHeader(QNetworkRequest::ContentTypeHeader, "text/plain"); + filePart.setHeader(QNetworkRequest::ContentDispositionHeader, + "form-data; name=\"file\"; filename=\"log.txt\""); + multiPart->append(filePart); - multiPart->append(filePart); + rep = APPLICATION->network()->post(request, multiPart); + multiPart->setParent(rep); - QNetworkReply *rep = APPLICATION->network()->post(request, multiPart); - multiPart->setParent(rep); + break; + } + case Hastebin: { + request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); + rep = APPLICATION->network()->post(request, m_text); + break; + } + case Mclogs: { + QUrlQuery postData; + postData.addQueryItem("content", m_text); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + rep = APPLICATION->network()->post(request, postData.toString().toUtf8()); + break; + } + case PasteGG: { + QJsonObject obj; + QJsonDocument doc; + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - m_reply = std::shared_ptr(rep); - setStatus(tr("Uploading to %1").arg(m_uploadUrl)); + obj.insert("expires", QDateTime::currentDateTimeUtc().addDays(100).toString(Qt::DateFormat::ISODate)); + + QJsonArray files; + QJsonObject logFileInfo; + QJsonObject logFileContentInfo; + logFileContentInfo.insert("format", "text"); + logFileContentInfo.insert("value", QString::fromUtf8(m_text)); + logFileInfo.insert("name", "log.txt"); + logFileInfo.insert("content", logFileContentInfo); + files.append(logFileInfo); + + obj.insert("files", files); + + doc.setObject(obj); + rep = APPLICATION->network()->post(request, doc.toJson()); + break; + } + } connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); - connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); + connect(rep, &QNetworkReply::finished, this, &PasteUpload::downloadFinished); + // This function call would be a lot shorter if we were using the latest Qt + connect(rep, + static_cast(&QNetworkReply::error), + this, &PasteUpload::downloadError); + + m_reply = std::shared_ptr(rep); + + setStatus(tr("Uploading to %1").arg(m_uploadUrl)); } void PasteUpload::downloadError(QNetworkReply::NetworkError error) @@ -102,6 +163,82 @@ void PasteUpload::downloadFinished() return; } - m_pasteLink = QString::fromUtf8(data).trimmed(); + switch (m_pasteType) + { + case NullPointer: + m_pasteLink = QString::fromUtf8(data).trimmed(); + break; + case Hastebin: { + QJsonDocument jsonDoc{QJsonDocument::fromJson(data)}; + QJsonObject jsonObj{jsonDoc.object()}; + if (jsonObj.contains("key") && jsonObj["key"].isString()) + { + QString key = jsonDoc.object()["key"].toString(); + m_pasteLink = m_baseUrl + "/" + key; + } + else + { + emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl)); + qCritical() << m_uploadUrl << " returned malformed response body: " << data; + return; + } + break; + } + case Mclogs: { + QJsonDocument jsonDoc{QJsonDocument::fromJson(data)}; + QJsonObject jsonObj{jsonDoc.object()}; + if (jsonObj.contains("success") && jsonObj["success"].isBool()) + { + bool success = jsonObj["success"].toBool(); + if (success) + { + m_pasteLink = jsonObj["url"].toString(); + } + else + { + QString error = jsonObj["error"].toString(); + emitFailed(tr("Error: %1 returned an error: %2").arg(m_uploadUrl, error)); + qCritical() << m_uploadUrl << " returned error: " << error; + qCritical() << "Response body: " << data; + return; + } + } + else + { + emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl)); + qCritical() << m_uploadUrl << " returned malformed response body: " << data; + return; + } + break; + } + case PasteGG: + QJsonDocument jsonDoc{QJsonDocument::fromJson(data)}; + QJsonObject jsonObj{jsonDoc.object()}; + if (jsonObj.contains("status") && jsonObj["status"].isString()) + { + QString status = jsonObj["status"].toString(); + if (status == "success") + { + m_pasteLink = m_baseUrl + "/p/anonymous/" + jsonObj["result"].toObject()["id"].toString(); + } + else + { + QString error = jsonObj["error"].toString(); + QString message = (jsonObj.contains("message") && jsonObj["message"].isString()) ? jsonObj["message"].toString() : "none"; + emitFailed(tr("Error: %1 returned an error code: %2\nError message: %3").arg(m_uploadUrl, error, message)); + qCritical() << m_uploadUrl << " returned error: " << error; + qCritical() << "Error message: " << message; + qCritical() << "Response body: " << data; + return; + } + } + else + { + emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl)); + qCritical() << m_uploadUrl << " returned malformed response body: " << data; + return; + } + break; + } emitSucceeded(); } diff --git a/launcher/net/PasteUpload.h b/launcher/net/PasteUpload.h index ea3a06d3d..e276234f4 100644 --- a/launcher/net/PasteUpload.h +++ b/launcher/net/PasteUpload.h @@ -36,14 +36,38 @@ #include "tasks/Task.h" #include +#include #include #include +#include class PasteUpload : public Task { Q_OBJECT public: - PasteUpload(QWidget *window, QString text, QString url); + enum PasteType : unsigned int { + // 0x0.st + NullPointer, + // hastebin.com + Hastebin, + // paste.gg + PasteGG, + // mclo.gs + Mclogs, + // Helpful to get the range of valid values on the enum for input sanitisation: + First = NullPointer, + Last = Mclogs + }; + + struct PasteTypeInfo { + const QString name; + const QString defaultBase; + const QString endpointPath; + }; + + static std::array PasteTypes; + + PasteUpload(QWidget *window, QString text, QString url, PasteType pasteType); virtual ~PasteUpload(); QString pasteLink() @@ -56,7 +80,9 @@ protected: private: QWidget *m_window; QString m_pasteLink; + QString m_baseUrl; QString m_uploadUrl; + PasteType m_pasteType; QByteArray m_text; std::shared_ptr m_reply; public diff --git a/launcher/ui/GuiUtil.cpp b/launcher/ui/GuiUtil.cpp index 9eb658e23..5e9d1eda9 100644 --- a/launcher/ui/GuiUtil.cpp +++ b/launcher/ui/GuiUtil.cpp @@ -16,8 +16,9 @@ QString GuiUtil::uploadPaste(const QString &text, QWidget *parentWidget) { ProgressDialog dialog(parentWidget); - auto pasteUrlSetting = APPLICATION->settings()->get("PastebinURL").toString(); - std::unique_ptr paste(new PasteUpload(parentWidget, text, pasteUrlSetting)); + auto pasteTypeSetting = static_cast(APPLICATION->settings()->get("PastebinType").toUInt()); + auto pasteCustomAPIBaseSetting = APPLICATION->settings()->get("PastebinCustomAPIBase").toString(); + std::unique_ptr paste(new PasteUpload(parentWidget, text, pasteCustomAPIBaseSetting, pasteTypeSetting)); dialog.execWithTask(paste.get()); if (!paste->wasSuccessful()) diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index 8b806bcf1..b2827a19f 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -3,6 +3,7 @@ * PolyMC - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu * Copyright (c) 2022 Jamie Mansfield + * Copyright (c) 2022 Lenny McLennington * * 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 @@ -46,15 +47,34 @@ #include "settings/SettingsObject.h" #include "tools/BaseProfiler.h" #include "Application.h" +#include "net/PasteUpload.h" APIPage::APIPage(QWidget *parent) : QWidget(parent), ui(new Ui::APIPage) { + // this is here so you can reorder the entries in the combobox without messing stuff up + unsigned int comboBoxEntries[] = { + PasteUpload::PasteType::Mclogs, + PasteUpload::PasteType::NullPointer, + PasteUpload::PasteType::PasteGG, + PasteUpload::PasteType::Hastebin + }; + static QRegularExpression validUrlRegExp("https?://.+"); + ui->setupUi(this); - ui->urlChoices->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->urlChoices)); - ui->tabWidget->tabBar()->hide();\ + + for (auto pasteType : comboBoxEntries) { + ui->pasteTypeComboBox->addItem(PasteUpload::PasteTypes.at(pasteType).name, pasteType); + } + + connect(ui->pasteTypeComboBox, static_cast(&QComboBox::currentIndexChanged), this, &APIPage::updateBaseURLPlaceholder); + // This function needs to be called even when the ComboBox's index is still in its default state. + updateBaseURLPlaceholder(ui->pasteTypeComboBox->currentIndex()); + ui->baseURLEntry->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->baseURLEntry)); + ui->tabWidget->tabBar()->hide(); + loadSettings(); } @@ -63,11 +83,28 @@ APIPage::~APIPage() delete ui; } +void APIPage::updateBaseURLPlaceholder(int index) +{ + ui->baseURLEntry->setPlaceholderText(PasteUpload::PasteTypes.at(ui->pasteTypeComboBox->itemData(index).toUInt()).defaultBase); +} + void APIPage::loadSettings() { auto s = APPLICATION->settings(); - QString pastebinURL = s->get("PastebinURL").toString(); - ui->urlChoices->setCurrentText(pastebinURL); + + unsigned int pasteType = s->get("PastebinType").toUInt(); + QString pastebinURL = s->get("PastebinCustomAPIBase").toString(); + + ui->baseURLEntry->setText(pastebinURL); + int pasteTypeIndex = ui->pasteTypeComboBox->findData(pasteType); + if (pasteTypeIndex == -1) + { + pasteTypeIndex = ui->pasteTypeComboBox->findData(PasteUpload::PasteType::Mclogs); + ui->baseURLEntry->clear(); + } + + ui->pasteTypeComboBox->setCurrentIndex(pasteTypeIndex); + QString msaClientID = s->get("MSAClientIDOverride").toString(); ui->msaClientID->setText(msaClientID); QString curseKey = s->get("CFKeyOverride").toString(); @@ -77,8 +114,10 @@ void APIPage::loadSettings() void APIPage::applySettings() { auto s = APPLICATION->settings(); - QString pastebinURL = ui->urlChoices->currentText(); - s->set("PastebinURL", pastebinURL); + + s->set("PastebinType", ui->pasteTypeComboBox->currentData().toUInt()); + s->set("PastebinCustomAPIBase", ui->baseURLEntry->text()); + QString msaClientID = ui->msaClientID->text(); s->set("MSAClientIDOverride", msaClientID); QString curseKey = ui->curseKey->text(); diff --git a/launcher/ui/pages/global/APIPage.h b/launcher/ui/pages/global/APIPage.h index 203560097..0bb84c895 100644 --- a/launcher/ui/pages/global/APIPage.h +++ b/launcher/ui/pages/global/APIPage.h @@ -73,6 +73,7 @@ public: void retranslate() override; private: + void updateBaseURLPlaceholder(int index); void loadSettings(); void applySettings(); diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index eaa44c888..d986c2e22 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -6,8 +6,8 @@ 0 0 - 603 - 530 + 512 + 538 @@ -36,59 +36,30 @@ - &Pastebin URL + Pastebin Service - - - Qt::Horizontal - - - - - - - - 10 - - + - <html><head/><body><p>Note: only input that starts with <span style=" font-weight:600;">http://</span> or <span style=" font-weight:600;">https://</span> will be accepted.</p></body></html> - - - false + Paste Service Type - - - true - - - QComboBox::NoInsert - - - - https://0x0.st - - - + - + - <html><head/><body><p>Here you can choose from a predefined list of paste services, or input the URL of a different paste service of your choice, provided it supports the same protocol as 0x0.st, that is POST a file parameter to the URL and return a link in the response body.</p></body></html> + Base URL - - Qt::RichText - - - true - - - true + + + + + + @@ -101,13 +72,6 @@ &Microsoft Authentication - - - - Qt::Horizontal - - - From caf6d027282392a58b935185d787c4c22a861409 Mon Sep 17 00:00:00 2001 From: Lenny McLennington Date: Fri, 13 May 2022 17:48:19 +0100 Subject: [PATCH 055/308] Change paste settings and add copyright headers - There's now a notice reminding people to change the base URL if they had a custom base URL and change the paste type (that was something I personally had problems with when I was testing, so a reminder was helpful for me). - Broke down some of the long lines on APIPage.cpp to be more readable. - Added copyright headers where they were missing. - Changed the paste service display names to the names they are more commonly known by. - Changed the default hastebin base URL to https://hst.sh due to the acquisition of https://hastebin.com by Toptal. --- launcher/Application.cpp | 5 ++-- launcher/net/PasteUpload.cpp | 10 +++++--- launcher/net/PasteUpload.h | 3 ++- launcher/ui/GuiUtil.cpp | 37 +++++++++++++++++++++++++++- launcher/ui/pages/global/APIPage.cpp | 37 +++++++++++++++++++++++----- launcher/ui/pages/global/APIPage.h | 4 +++ launcher/ui/pages/global/APIPage.ui | 13 ++++++++++ 7 files changed, 95 insertions(+), 14 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index b36fd89a3..40c6e7609 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 Lenny McLennington * * 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 @@ -672,7 +673,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("UpdateDialogGeometry", ""); - // This code feels so stupid is there a less stupid way of doing this? + // HACK: This code feels so stupid is there a less stupid way of doing this? { m_settings->registerSetting("PastebinURL", ""); QString pastebinURL = m_settings->get("PastebinURL").toString(); @@ -694,7 +695,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) } bool ok; - unsigned int pasteType = m_settings->get("PastebinType").toUInt(&ok); + int pasteType = m_settings->get("PastebinType").toInt(&ok); // If PastebinType is invalid then reset the related settings. if (!ok || !(PasteUpload::PasteType::First <= pasteType && pasteType <= PasteUpload::PasteType::Last)) { diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index d583216d8..3855190ab 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Lenny McLennington + * Copyright (C) 2022 Swirl * * 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 @@ -43,10 +45,10 @@ #include std::array PasteUpload::PasteTypes = { - {{"0x0", "https://0x0.st", ""}, - {"hastebin", "https://hastebin.com", "/documents"}, - {"paste (paste.gg)", "https://paste.gg", "/api/v1/pastes"}, - {"mclogs", "https://api.mclo.gs", "/1/log"}}}; + {{"0x0.st", "https://0x0.st", ""}, + {"hastebin", "https://hst.sh", "/documents"}, + {"paste.gg", "https://paste.gg", "/api/v1/pastes"}, + {"mclo.gs", "https://api.mclo.gs", "/1/log"}}}; PasteUpload::PasteUpload(QWidget *window, QString text, QString baseUrl, PasteType pasteType) : m_window(window), m_baseUrl(baseUrl), m_pasteType(pasteType), m_text(text.toUtf8()) { diff --git a/launcher/net/PasteUpload.h b/launcher/net/PasteUpload.h index e276234f4..eb315c2b8 100644 --- a/launcher/net/PasteUpload.h +++ b/launcher/net/PasteUpload.h @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Lenny McLennington * * 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 @@ -45,7 +46,7 @@ class PasteUpload : public Task { Q_OBJECT public: - enum PasteType : unsigned int { + enum PasteType : int { // 0x0.st NullPointer, // hastebin.com diff --git a/launcher/ui/GuiUtil.cpp b/launcher/ui/GuiUtil.cpp index 5e9d1eda9..320f1502a 100644 --- a/launcher/ui/GuiUtil.cpp +++ b/launcher/ui/GuiUtil.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Lenny McLennington + * + * 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 . + * + * 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 "GuiUtil.h" #include @@ -16,7 +51,7 @@ QString GuiUtil::uploadPaste(const QString &text, QWidget *parentWidget) { ProgressDialog dialog(parentWidget); - auto pasteTypeSetting = static_cast(APPLICATION->settings()->get("PastebinType").toUInt()); + auto pasteTypeSetting = static_cast(APPLICATION->settings()->get("PastebinType").toInt()); auto pasteCustomAPIBaseSetting = APPLICATION->settings()->get("PastebinCustomAPIBase").toString(); std::unique_ptr paste(new PasteUpload(parentWidget, text, pasteCustomAPIBaseSetting, pasteTypeSetting)); diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index b2827a19f..2841544fc 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -53,8 +53,8 @@ APIPage::APIPage(QWidget *parent) : QWidget(parent), ui(new Ui::APIPage) { - // this is here so you can reorder the entries in the combobox without messing stuff up - unsigned int comboBoxEntries[] = { + // This is here so you can reorder the entries in the combobox without messing stuff up + int comboBoxEntries[] = { PasteUpload::PasteType::Mclogs, PasteUpload::PasteType::NullPointer, PasteUpload::PasteType::PasteGG, @@ -69,13 +69,18 @@ APIPage::APIPage(QWidget *parent) : ui->pasteTypeComboBox->addItem(PasteUpload::PasteTypes.at(pasteType).name, pasteType); } - connect(ui->pasteTypeComboBox, static_cast(&QComboBox::currentIndexChanged), this, &APIPage::updateBaseURLPlaceholder); + void (QComboBox::*currentIndexChangedSignal)(int) (&QComboBox::currentIndexChanged); + connect(ui->pasteTypeComboBox, currentIndexChangedSignal, this, &APIPage::updateBaseURLPlaceholder); // This function needs to be called even when the ComboBox's index is still in its default state. updateBaseURLPlaceholder(ui->pasteTypeComboBox->currentIndex()); ui->baseURLEntry->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->baseURLEntry)); ui->tabWidget->tabBar()->hide(); loadSettings(); + + resetBaseURLNote(); + connect(ui->pasteTypeComboBox, currentIndexChangedSignal, this, &APIPage::updateBaseURLNote); + connect(ui->baseURLEntry, &QLineEdit::textEdited, this, &APIPage::resetBaseURLNote); } APIPage::~APIPage() @@ -83,16 +88,36 @@ APIPage::~APIPage() delete ui; } +void APIPage::resetBaseURLNote() +{ + ui->baseURLNote->hide(); + baseURLPasteType = ui->pasteTypeComboBox->currentIndex(); +} + +void APIPage::updateBaseURLNote(int index) +{ + if (baseURLPasteType == index) + { + ui->baseURLNote->hide(); + } + else if (!ui->baseURLEntry->text().isEmpty()) + { + ui->baseURLNote->show(); + } +} + void APIPage::updateBaseURLPlaceholder(int index) { - ui->baseURLEntry->setPlaceholderText(PasteUpload::PasteTypes.at(ui->pasteTypeComboBox->itemData(index).toUInt()).defaultBase); + int pasteType = ui->pasteTypeComboBox->itemData(index).toInt(); + QString pasteDefaultURL = PasteUpload::PasteTypes.at(pasteType).defaultBase; + ui->baseURLEntry->setPlaceholderText(pasteDefaultURL); } void APIPage::loadSettings() { auto s = APPLICATION->settings(); - unsigned int pasteType = s->get("PastebinType").toUInt(); + int pasteType = s->get("PastebinType").toInt(); QString pastebinURL = s->get("PastebinCustomAPIBase").toString(); ui->baseURLEntry->setText(pastebinURL); @@ -115,7 +140,7 @@ void APIPage::applySettings() { auto s = APPLICATION->settings(); - s->set("PastebinType", ui->pasteTypeComboBox->currentData().toUInt()); + s->set("PastebinType", ui->pasteTypeComboBox->currentData().toInt()); s->set("PastebinCustomAPIBase", ui->baseURLEntry->text()); QString msaClientID = ui->msaClientID->text(); diff --git a/launcher/ui/pages/global/APIPage.h b/launcher/ui/pages/global/APIPage.h index 0bb84c895..17e62ae7f 100644 --- a/launcher/ui/pages/global/APIPage.h +++ b/launcher/ui/pages/global/APIPage.h @@ -3,6 +3,7 @@ * PolyMC - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu * Copyright (c) 2022 Jamie Mansfield + * Copyright (c) 2022 Lenny McLennington * * 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 @@ -73,6 +74,9 @@ public: void retranslate() override; private: + int baseURLPasteType; + void resetBaseURLNote(); + void updateBaseURLNote(int index); void updateBaseURLPlaceholder(int index); void loadSettings(); void applySettings(); diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index d986c2e22..b6af19588 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -61,6 +61,19 @@ + + true + + + + + + + Note: you probably want to change or clear the Base URL after changing the paste service type. + + + true + From e2ad3b01837e52a55e859412474978fa8a1e9625 Mon Sep 17 00:00:00 2001 From: Lenny McLennington Date: Tue, 17 May 2022 05:00:06 +0100 Subject: [PATCH 056/308] Add migration wizard, fix migration from custom paste instance - Very basic wizard just to allow the user to choose whether to keep their old paste settings or use the new default settings. - People who used custom 0x0 instances would just be kept on those settings and won't see the wizard. --- launcher/Application.cpp | 31 ++++---- launcher/CMakeLists.txt | 3 + launcher/ui/setupwizard/PasteWizardPage.cpp | 42 +++++++++++ launcher/ui/setupwizard/PasteWizardPage.h | 27 +++++++ launcher/ui/setupwizard/PasteWizardPage.ui | 80 +++++++++++++++++++++ 5 files changed, 169 insertions(+), 14 deletions(-) create mode 100644 launcher/ui/setupwizard/PasteWizardPage.cpp create mode 100644 launcher/ui/setupwizard/PasteWizardPage.h create mode 100644 launcher/ui/setupwizard/PasteWizardPage.ui diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 40c6e7609..438c7d613 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -63,6 +63,7 @@ #include "ui/setupwizard/SetupWizard.h" #include "ui/setupwizard/LanguageWizardPage.h" #include "ui/setupwizard/JavaWizardPage.h" +#include "ui/setupwizard/PasteWizardPage.h" #include "ui/dialogs/CustomMessageBox.h" @@ -676,21 +677,17 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // HACK: This code feels so stupid is there a less stupid way of doing this? { m_settings->registerSetting("PastebinURL", ""); + m_settings->registerSetting("PastebinType", PasteUpload::PasteType::Mclogs); + m_settings->registerSetting("PastebinCustomAPIBase", ""); + QString pastebinURL = m_settings->get("PastebinURL").toString(); - // If PastebinURL hasn't been set before then use the new default: mclo.gs - if (pastebinURL == "") { - m_settings->registerSetting("PastebinType", PasteUpload::PasteType::Mclogs); - m_settings->registerSetting("PastebinCustomAPIBase", ""); - } - // Otherwise: use 0x0.st - else { - // The default custom endpoint would usually be "" (meaning there is no custom endpoint specified) - // But if the user had customised the paste URL then that should be carried over into the custom endpoint. - QString defaultCustomEndpoint = (pastebinURL == "https://0x0.st") ? "" : pastebinURL; - m_settings->registerSetting("PastebinType", PasteUpload::PasteType::NullPointer); - m_settings->registerSetting("PastebinCustomAPIBase", defaultCustomEndpoint); - + bool userHadNoPastebin = pastebinURL == ""; + bool userHadDefaultPastebin = pastebinURL == "https://0x0.st"; + if (!(userHadNoPastebin || userHadDefaultPastebin)) + { + m_settings->set("PastebinType", PasteUpload::PasteType::NullPointer); + m_settings->set("PastebinCustomAPIBase", pastebinURL); m_settings->reset("PastebinURL"); } @@ -929,7 +926,8 @@ bool Application::createSetupWizard() return true; return false; }(); - bool wizardRequired = javaRequired || languageRequired; + bool pasteInterventionRequired = settings()->get("PastebinURL") != ""; + bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired; if(wizardRequired) { @@ -943,6 +941,11 @@ bool Application::createSetupWizard() { m_setupWizard->addPage(new JavaWizardPage(m_setupWizard)); } + + if (pasteInterventionRequired) + { + m_setupWizard->addPage(new PasteWizardPage(m_setupWizard)); + } connect(m_setupWizard, &QDialog::finished, this, &Application::setupWizardFinished); m_setupWizard->show(); return true; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 8e75be204..15534c71e 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -661,6 +661,8 @@ SET(LAUNCHER_SOURCES ui/setupwizard/JavaWizardPage.h ui/setupwizard/LanguageWizardPage.cpp ui/setupwizard/LanguageWizardPage.h + ui/setupwizard/PasteWizardPage.cpp + ui/setupwizard/PasteWizardPage.h # GUI - themes ui/themes/FusionTheme.cpp @@ -890,6 +892,7 @@ SET(LAUNCHER_SOURCES ) qt5_wrap_ui(LAUNCHER_UI + ui/setupwizard/PasteWizardPage.ui ui/pages/global/AccountListPage.ui ui/pages/global/JavaPage.ui ui/pages/global/LauncherPage.ui diff --git a/launcher/ui/setupwizard/PasteWizardPage.cpp b/launcher/ui/setupwizard/PasteWizardPage.cpp new file mode 100644 index 000000000..0f47da4b1 --- /dev/null +++ b/launcher/ui/setupwizard/PasteWizardPage.cpp @@ -0,0 +1,42 @@ +#include "PasteWizardPage.h" +#include "ui_PasteWizardPage.h" + +#include "Application.h" +#include "net/PasteUpload.h" + +PasteWizardPage::PasteWizardPage(QWidget *parent) : + BaseWizardPage(parent), + ui(new Ui::PasteWizardPage) +{ + ui->setupUi(this); +} + +PasteWizardPage::~PasteWizardPage() +{ + delete ui; +} + +void PasteWizardPage::initializePage() +{ +} + +bool PasteWizardPage::validatePage() +{ + auto s = APPLICATION->settings(); + QString prevPasteURL = s->get("PastebinURL").toString(); + s->reset("PastebinURL"); + if (ui->previousSettingsRadioButton->isChecked()) + { + bool usingDefaultBase = prevPasteURL == PasteUpload::PasteTypes.at(PasteUpload::PasteType::NullPointer).defaultBase; + s->set("PastebinType", PasteUpload::PasteType::NullPointer); + if (!usingDefaultBase) + s->set("PastebinCustomAPIBase", prevPasteURL); + } + + return true; +} + +void PasteWizardPage::retranslate() +{ + ui->retranslateUi(this); +} diff --git a/launcher/ui/setupwizard/PasteWizardPage.h b/launcher/ui/setupwizard/PasteWizardPage.h new file mode 100644 index 000000000..513a14cb5 --- /dev/null +++ b/launcher/ui/setupwizard/PasteWizardPage.h @@ -0,0 +1,27 @@ +#ifndef PASTEDEFAULTSCONFIRMATIONWIZARD_H +#define PASTEDEFAULTSCONFIRMATIONWIZARD_H + +#include +#include "BaseWizardPage.h" + +namespace Ui { +class PasteWizardPage; +} + +class PasteWizardPage : public BaseWizardPage +{ + Q_OBJECT + +public: + explicit PasteWizardPage(QWidget *parent = nullptr); + ~PasteWizardPage(); + + void initializePage() override; + bool validatePage() override; + void retranslate() override; + +private: + Ui::PasteWizardPage *ui; +}; + +#endif // PASTEDEFAULTSCONFIRMATIONWIZARD_H diff --git a/launcher/ui/setupwizard/PasteWizardPage.ui b/launcher/ui/setupwizard/PasteWizardPage.ui new file mode 100644 index 000000000..247d3a757 --- /dev/null +++ b/launcher/ui/setupwizard/PasteWizardPage.ui @@ -0,0 +1,80 @@ + + + PasteWizardPage + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + The default paste service has changed to mclo.gs, please choose what you want to do with your settings. + + + true + + + + + + + Qt::Horizontal + + + + + + + Use new default service + + + true + + + buttonGroup + + + + + + + Keep previous settings + + + false + + + buttonGroup + + + + + + + Qt::Vertical + + + + 20 + 156 + + + + + + + + + + + + From de02deac989cc5efc135dc3c817fe72cc2499eca Mon Sep 17 00:00:00 2001 From: LennyMcLennington Date: Fri, 20 May 2022 22:30:00 +0100 Subject: [PATCH 057/308] Make if statement condition more readable Co-authored-by: Sefa Eyeoglu --- launcher/Application.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 438c7d613..91f5ef9df 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -682,9 +682,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) QString pastebinURL = m_settings->get("PastebinURL").toString(); - bool userHadNoPastebin = pastebinURL == ""; bool userHadDefaultPastebin = pastebinURL == "https://0x0.st"; - if (!(userHadNoPastebin || userHadDefaultPastebin)) + if (!pastebinURL.isEmpty() && !userHadDefaultPastebin) { m_settings->set("PastebinType", PasteUpload::PasteType::NullPointer); m_settings->set("PastebinCustomAPIBase", pastebinURL); From bfffcb3910b7f8429da16ae503fc79722d32ded6 Mon Sep 17 00:00:00 2001 From: txtsd Date: Sun, 22 May 2022 13:42:33 +0530 Subject: [PATCH 058/308] fix(workflow): Avoid invoking ccache on Release builds --- .github/workflows/build.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0590b3480..38868b39f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,6 +39,7 @@ jobs: INSTALL_PORTABLE_DIR: "install-portable" INSTALL_APPIMAGE_DIR: "install-appdir" BUILD_DIR: "build" + CCACHE_VAR: "" steps: ## @@ -80,6 +81,12 @@ jobs: ccache -p # Show config ccache -z # Zero stats + - name: Use ccache on Debug builds only + if: inputs.build_type == 'Debug' + shell: bash + run: | + echo "CCACHE_VAR=ccache" >> $GITHUB_ENV + - name: Retrieve ccache cache (Windows) if: runner.os == 'Windows' && inputs.build_type == 'Debug' uses: actions/cache@v3.0.2 @@ -128,18 +135,18 @@ jobs: - name: Configure CMake (macOS) if: runner.os == 'macOS' run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DQt5_DIR=/usr/local/opt/qt@5 -DCMAKE_PREFIX_PATH=/usr/local/opt/qt@5 -DLauncher_BUILD_PLATFORM=macOS -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -G Ninja + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DQt5_DIR=/usr/local/opt/qt@5 -DCMAKE_PREFIX_PATH=/usr/local/opt/qt@5 -DLauncher_BUILD_PLATFORM=macOS -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -G Ninja - name: Configure CMake (Windows) if: runner.os == 'Windows' shell: msys2 {0} run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -G Ninja + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -G Ninja - name: Configure CMake (Linux) if: runner.os == 'Linux' run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=Linux -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -G Ninja + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=Linux -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -G Ninja ## # BUILD From 90007e2d9d4f63cfc9dc73888af34a17657b5102 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 22 May 2022 16:03:21 +0200 Subject: [PATCH 059/308] fix: temporarily ignore stringop-overflow warning --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index e07d2aa64..e6d66b8d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,10 @@ set(CMAKE_CXX_FLAGS " -Wall -pedantic -Werror -Wno-deprecated-declarations -D_GL if(UNIX AND APPLE) set(CMAKE_CXX_FLAGS " -stdlib=libc++ ${CMAKE_CXX_FLAGS}") endif() +# FIXME: GCC 12 complains about some random stuff in QuaZip. Need to fix this later +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(CMAKE_CXX_FLAGS "-Wno-error=stringop-overflow ${CMAKE_CXX_FLAGS}") +endif() set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror=return-type") # Fix build with Qt 5.13 From c988b4d213b4125d298c893637a2362a7f192fce Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Sun, 22 May 2022 17:16:00 +0200 Subject: [PATCH 060/308] fix appimage not having imageformats fixes stuff like the iris icon --- .github/workflows/build.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5b70256a1..6cbd5c21c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -113,12 +113,15 @@ jobs: if: runner.os == 'Linux' && matrix.appimage == true run: | sudo add-apt-repository ppa:savoury1/qt-5-15 + sudo add-apt-repository ppa:savoury1/kde-5-80 + sudo add-apt-repository ppa:savoury1/gpg + sudo add-apt-repository ppa:savoury1/ffmpeg4 - name: Install Qt (Linux) if: runner.os == 'Linux' run: | sudo apt-get -y update - sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 ninja-build + sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 ninja-build qt5-image-formats-plugins - name: Prepare AppImage (Linux) if: runner.os == 'Linux' && matrix.appimage == true From 0922a7f410d8675778bcf4720438efaa128b662b Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 22 May 2022 20:50:37 +0200 Subject: [PATCH 061/308] refactor: use -O2 for release and -O1 for debug builds --- CMakeLists.txt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e6d66b8d1..f54dd7baf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,21 +34,24 @@ set(CMAKE_C_STANDARD_REQUIRED true) set(CMAKE_CXX_STANDARD 11) set(CMAKE_C_STANDARD 11) include(GenerateExportHeader) -set(CMAKE_CXX_FLAGS " -Wall -pedantic -Werror -Wno-deprecated-declarations -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 -O3 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}") +set(CMAKE_CXX_FLAGS "-Wall -pedantic -Werror -Wno-deprecated-declarations -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}") if(UNIX AND APPLE) - set(CMAKE_CXX_FLAGS " -stdlib=libc++ ${CMAKE_CXX_FLAGS}") + set(CMAKE_CXX_FLAGS "-stdlib=libc++ ${CMAKE_CXX_FLAGS}") endif() -# FIXME: GCC 12 complains about some random stuff in QuaZip. Need to fix this later +# FIXME: GCC 12 complains about some random stuff in bundled QuaZip. Need to fix this later if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(CMAKE_CXX_FLAGS "-Wno-error=stringop-overflow ${CMAKE_CXX_FLAGS}") endif() -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror=return-type") # Fix build with Qt 5.13 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 CXXFLAGS for build targets +set(CMAKE_CXX_FLAGS_DEBUG "-O1 ${CMAKE_CXX_FLAGS}") +set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}") + option(ENABLE_LTO "Enable Link Time Optimization" off) if(ENABLE_LTO) From 309dcc82cade6aee1af04534c8e307b56fcac848 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 22 May 2022 20:57:52 +0200 Subject: [PATCH 062/308] Revert "fix: temporarily ignore stringop-overflow warning" This reverts commit 90007e2d9d4f63cfc9dc73888af34a17657b5102. --- CMakeLists.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f54dd7baf..ef4adf903 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,10 +38,6 @@ set(CMAKE_CXX_FLAGS "-Wall -pedantic -Werror -Wno-deprecated-declarations -D_GLI if(UNIX AND APPLE) set(CMAKE_CXX_FLAGS "-stdlib=libc++ ${CMAKE_CXX_FLAGS}") endif() -# FIXME: GCC 12 complains about some random stuff in bundled QuaZip. Need to fix this later -if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set(CMAKE_CXX_FLAGS "-Wno-error=stringop-overflow ${CMAKE_CXX_FLAGS}") -endif() # Fix build with Qt 5.13 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y") From f00dbdc215c2de3b6906d8182388c27bbc657e24 Mon Sep 17 00:00:00 2001 From: dada513 Date: Wed, 13 Apr 2022 23:00:32 +0200 Subject: [PATCH 063/308] Make Metaserver changable in settings Co-authored-by: Sefa Eyeoglu Co-authored-by: flow --- launcher/Application.cpp | 2 + launcher/meta/BaseEntity.cpp | 11 +++- launcher/ui/pages/global/APIPage.cpp | 10 ++++ launcher/ui/pages/global/APIPage.ui | 75 +++++++++++++++++++++++++--- 4 files changed, 89 insertions(+), 9 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 91f5ef9df..ba4096b64 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -699,6 +699,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->reset("PastebinCustomAPIBase"); } } + // meta URL + m_settings->registerSetting("MetaURLOverride", ""); m_settings->registerSetting("CloseAfterLaunch", false); m_settings->registerSetting("QuitAfterGameStop", false); diff --git a/launcher/meta/BaseEntity.cpp b/launcher/meta/BaseEntity.cpp index 841559221..de4e1012d 100644 --- a/launcher/meta/BaseEntity.cpp +++ b/launcher/meta/BaseEntity.cpp @@ -75,7 +75,16 @@ Meta::BaseEntity::~BaseEntity() QUrl Meta::BaseEntity::url() const { - return QUrl(BuildConfig.META_URL).resolved(localFilename()); + auto s = APPLICATION->settings(); + QString metaOverride = s->get("MetaURLOverride").toString(); + if(metaOverride.isEmpty()) + { + return QUrl(BuildConfig.META_URL).resolved(localFilename()); + } + else + { + return QUrl(metaOverride).resolved(localFilename()); + } } bool Meta::BaseEntity::loadLocalFile() diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index 2841544fc..af58b8cdf 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -132,6 +132,8 @@ void APIPage::loadSettings() QString msaClientID = s->get("MSAClientIDOverride").toString(); ui->msaClientID->setText(msaClientID); + QString metaURL = s->get("MetaURLOverride").toString(); + ui->metaURL->setText(metaURL); QString curseKey = s->get("CFKeyOverride").toString(); ui->curseKey->setText(curseKey); } @@ -145,6 +147,14 @@ void APIPage::applySettings() QString msaClientID = ui->msaClientID->text(); s->set("MSAClientIDOverride", msaClientID); + QUrl metaURL = ui->metaURL->text(); + // Don't allow HTTP, since meta is basically RCE with all the jar files. + if(!metaURL.isEmpty() && metaURL.scheme() == "http") + { + metaURL.setScheme("https"); + } + + s->set("MetaURLOverride", metaURL); QString curseKey = ui->curseKey->text(); s->set("CFKeyOverride", curseKey); } diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index b6af19588..8d80df657 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -6,8 +6,8 @@ 0 0 - 512 - 538 + 800 + 600 @@ -85,6 +85,13 @@ &Microsoft Authentication + + + + Qt::Horizontal + + + @@ -125,12 +132,9 @@ - - - true - + - &CurseForge Core API + Meta&data Server @@ -140,8 +144,63 @@ + + + + You can set this to a third-party metadata server to use patched libraries or other hacks. + + + Qt::RichText + + + true + + + + + + + (Default) + + + + + Enter a custom URL for meta here. + + + Qt::RichText + + + true + + + true + + + + + + + + + + true + + + &CurseForge Core API + + + + + + Qt::Horizontal + + + + + Note: you probably don't need to set this if CurseForge already works. @@ -158,7 +217,7 @@ - + Enter a custom API Key for CurseForge here. From b181f4bc30f36778f9680eb54e6f3514739161e8 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 22 May 2022 13:41:44 +0200 Subject: [PATCH 064/308] fix: improve spacing in APIPage --- launcher/ui/pages/global/APIPage.ui | 34 +++++++++++------------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index 8d80df657..24189c5c5 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -85,13 +85,6 @@ &Microsoft Authentication - - - - Qt::Horizontal - - - @@ -137,13 +130,6 @@ Meta&data Server - - - - Qt::Horizontal - - - @@ -192,13 +178,6 @@ &CurseForge Core API - - - - Qt::Horizontal - - - @@ -235,6 +214,19 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + From f2e205313485e458e2f5186f743d527d28609c5e Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 22 May 2022 13:55:19 +0200 Subject: [PATCH 065/308] feat: add trailing slash to meta URL if it is missing --- launcher/ui/pages/global/APIPage.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index af58b8cdf..6ad243ddc 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -148,6 +148,13 @@ void APIPage::applySettings() QString msaClientID = ui->msaClientID->text(); s->set("MSAClientIDOverride", msaClientID); QUrl metaURL = ui->metaURL->text(); + // Add required trailing slash + if (!metaURL.isEmpty() && !metaURL.path().endsWith('/')) + { + QString path = metaURL.path(); + path.append('/'); + metaURL.setPath(path); + } // Don't allow HTTP, since meta is basically RCE with all the jar files. if(!metaURL.isEmpty() && metaURL.scheme() == "http") { From 0b85051a2363f4fad29477e3a0ccd3fda18fee01 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 22 May 2022 21:41:41 +0200 Subject: [PATCH 066/308] fix: more generous optimizations for debug builds --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ef4adf903..a8c28e990 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,7 +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 CXXFLAGS for build targets -set(CMAKE_CXX_FLAGS_DEBUG "-O1 ${CMAKE_CXX_FLAGS}") +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}") option(ENABLE_LTO "Enable Link Time Optimization" off) From cb69869836d2b4ed4b50a43694e95c4a801332f7 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 22 May 2022 22:04:24 +0200 Subject: [PATCH 067/308] revert: remove CurseForge workaround We have been asked by CurseForge to remove this workaround as it violates their terms of service. This is just a partial revert, as the UI changes were otherwise unrelated. This reverts commit 92e8aaf36f72b7527322add169b253d0698939d0, reversing changes made to 88a93945d4c9a11bf53016133335d359b819585e. --- launcher/modplatform/flame/FileResolvingTask.cpp | 16 +--------------- launcher/modplatform/flame/FlameModIndex.cpp | 9 +-------- launcher/modplatform/flame/PackManifest.cpp | 15 +++++---------- 3 files changed, 7 insertions(+), 33 deletions(-) diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index 0deb99c4a..95924a681 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -31,21 +31,7 @@ void Flame::FileResolvingTask::netJobFinished() for (auto& bytes : results) { auto& out = m_toProcess.files[index]; try { - bool fail = (!out.parseFromBytes(bytes)); - if(fail){ - //failed :( probably disabled mod, try to add to the list - auto doc = Json::requireDocument(bytes); - if (!doc.isObject()) { - throw JSONValidationError(QString("data is not an object? that's not supposed to happen")); - } - auto obj = Json::ensureObject(doc.object(), "data"); - //FIXME : HACK, MAY NOT WORK FOR LONG - out.url = QUrl(QString("https://media.forgecdn.net/files/%1/%2/%3") - .arg(QString::number(QString::number(out.fileId).leftRef(4).toInt()) - ,QString::number(QString::number(out.fileId).rightRef(3).toInt()) - ,QUrl::toPercentEncoding(out.fileName)), QUrl::TolerantMode); - } - failed &= fail; + failed &= (!out.parseFromBytes(bytes)); } catch (const JSONValidationError& e) { qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:"; qCritical() << e.cause(); diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 9846b1562..ba0824cf5 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -56,15 +56,8 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, file.fileId = Json::requireInteger(obj, "id"); file.date = Json::requireString(obj, "fileDate"); file.version = Json::requireString(obj, "displayName"); + file.downloadUrl = Json::requireString(obj, "downloadUrl"); file.fileName = Json::requireString(obj, "fileName"); - file.downloadUrl = Json::ensureString(obj, "downloadUrl", ""); - if(file.downloadUrl.isEmpty()){ - //FIXME : HACK, MAY NOT WORK FOR LONG - file.downloadUrl = QString("https://media.forgecdn.net/files/%1/%2/%3") - .arg(QString::number(QString::number(file.fileId.toInt()).leftRef(4).toInt()) - ,QString::number(QString::number(file.fileId.toInt()).rightRef(3).toInt()) - ,QUrl::toPercentEncoding(file.fileName)); - } unsortedVersions.append(file); } diff --git a/launcher/modplatform/flame/PackManifest.cpp b/launcher/modplatform/flame/PackManifest.cpp index 3217a7569..e4f90c1a1 100644 --- a/launcher/modplatform/flame/PackManifest.cpp +++ b/launcher/modplatform/flame/PackManifest.cpp @@ -71,6 +71,11 @@ bool Flame::File::parseFromBytes(const QByteArray& bytes) fileName = Json::requireString(obj, "fileName"); + QString rawUrl = Json::requireString(obj, "downloadUrl"); + url = QUrl(rawUrl, QUrl::TolerantMode); + if (!url.isValid()) { + throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl)); + } // This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience // It is also optional type = File::Type::SingleFile; @@ -82,17 +87,7 @@ bool Flame::File::parseFromBytes(const QByteArray& bytes) // this is probably a mod, dunno what else could modpacks download targetFolder = "mods"; } - QString rawUrl = Json::ensureString(obj, "downloadUrl"); - if(rawUrl.isEmpty()){ - //either there somehow is an emtpy string as a link, or it's null either way it's invalid - //soft failing - return false; - } - url = QUrl(rawUrl, QUrl::TolerantMode); - if (!url.isValid()) { - throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl)); - } resolved = true; return true; } From d72c75db239dbff7e41c0d4a20df5337b9685a16 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 22 May 2022 22:56:52 +0200 Subject: [PATCH 068/308] chore: bump version --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a8c28e990..e2635c3fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,8 +73,8 @@ set(Launcher_HELP_URL "https://polymc.org/wiki/help-pages/%1" CACHE STRING "URL ######## Set version numbers ######## set(Launcher_VERSION_MAJOR 1) -set(Launcher_VERSION_MINOR 2) -set(Launcher_VERSION_HOTFIX 2) +set(Launcher_VERSION_MINOR 3) +set(Launcher_VERSION_HOTFIX 0) # Build number set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.") From 2a0018e730a382a0d5dc963f30bf46cc0a240291 Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Mon, 23 May 2022 08:29:30 +0800 Subject: [PATCH 069/308] add a .clang-format --- .clang-format | 211 ++++++++++++++++++++++++++++++++++++++++++++++++++ .gitignore | 1 - 2 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..ed96c0303 --- /dev/null +++ b/.clang-format @@ -0,0 +1,211 @@ +--- +Language: Cpp +# BasedOnStyle: Chromium +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveMacros: false # changed +AlignConsecutiveAssignments: false # changed +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: false # changed +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: true # changed + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: false # changed + SplitEmptyRecord: false # changed + SplitEmptyNamespace: false # changed +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: true +BreakBeforeBraces: Custom # changed +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: true +BreakConstructorInitializers: BeforeComma # changed +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 140 # changed +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false # changed +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*\.h>' + Priority: 1 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 3 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '([-_](test|unittest))?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseLabels: true +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentRequires: false +IndentWidth: 4 # changed +IndentWrappedFunctionNames: false +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Left +PPIndentWidth: -1 +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + - ParseTestProto + - ParsePartialTestProto + CanonicalDelimiter: pb + BasedOnStyle: google +ReferenceAlignment: Pointer +ReflowComments: true +ShortNamespaceLines: 1 +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: Auto +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... + diff --git a/.gitignore b/.gitignore index 2a715656a..f5917a46f 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,6 @@ CMakeLists.txt.user.* /.settings /.idea /.vscode -.clang-format cmake-build-*/ Debug From 6d0ea13f97570f837f11022e3ef0fbfb6d0482f5 Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Mon, 23 May 2022 16:50:17 +0800 Subject: [PATCH 070/308] make JVM args `PlainTextEdit` --- launcher/ui/pages/global/JavaPage.cpp | 6 +- launcher/ui/pages/global/JavaPage.ui | 99 +++++++++++++++------------ 2 files changed, 60 insertions(+), 45 deletions(-) diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index b5e8de6c1..54bfb3cfc 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -95,7 +95,7 @@ void JavaPage::applySettings() // Java Settings s->set("JavaPath", ui->javaPathTextBox->text()); - s->set("JvmArgs", ui->jvmArgsTextBox->text()); + s->set("JvmArgs", ui->jvmArgsTextBox->toPlainText()); s->set("IgnoreJavaCompatibility", ui->skipCompatibilityCheckbox->isChecked()); s->set("IgnoreJavaWizard", ui->skipJavaWizardCheckbox->isChecked()); JavaCommon::checkJVMArgs(s->get("JvmArgs").toString(), this->parentWidget()); @@ -120,7 +120,7 @@ void JavaPage::loadSettings() // Java Settings ui->javaPathTextBox->setText(s->get("JavaPath").toString()); - ui->jvmArgsTextBox->setText(s->get("JvmArgs").toString()); + ui->jvmArgsTextBox->setPlainText(s->get("JvmArgs").toString()); ui->skipCompatibilityCheckbox->setChecked(s->get("IgnoreJavaCompatibility").toBool()); ui->skipJavaWizardCheckbox->setChecked(s->get("IgnoreJavaWizard").toBool()); } @@ -166,7 +166,7 @@ void JavaPage::on_javaTestBtn_clicked() return; } checker.reset(new JavaCommon::TestCheck( - this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->text(), + this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->toPlainText(), ui->minMemSpinBox->value(), ui->maxMemSpinBox->value(), ui->permGenSpinBox->value())); connect(checker.get(), SIGNAL(finished()), SLOT(checkerFinished())); checker->run(); diff --git a/launcher/ui/pages/global/JavaPage.ui b/launcher/ui/pages/global/JavaPage.ui index 3e4b12a15..6ccffed4d 100644 --- a/launcher/ui/pages/global/JavaPage.ui +++ b/launcher/ui/pages/global/JavaPage.ui @@ -150,6 +150,35 @@ Java Runtime + + + + + 0 + 0 + + + + &Auto-detect... + + + + + + + + 0 + 0 + + + + JVM arguments: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + @@ -166,40 +195,8 @@ - - - - - 0 - 0 - - - - J&VM arguments: - - - jvmArgsTextBox - - - - - - - - 0 - 0 - - - - If enabled, the launcher will not check if an instance is compatible with the selected Java version. - - - &Skip Java compatibility checks - - - - - + + 0 @@ -207,7 +204,7 @@ - &Auto-detect... + &Test @@ -237,22 +234,22 @@ - - + + 0 0 + + If enabled, the launcher will not check if an instance is compatible with the selected Java version. + - &Test + &Skip Java compatibility checks - - - @@ -263,6 +260,25 @@ + + + + true + + + + 0 + 0 + + + + + 16777215 + 100 + + + + @@ -291,7 +307,6 @@ permGenSpinBox javaBrowseBtn javaPathTextBox - jvmArgsTextBox javaDetectBtn javaTestBtn tabWidget From c3e6b1aa8acdb7818bb678780f04ace0b68efad0 Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Mon, 23 May 2022 18:40:46 +0800 Subject: [PATCH 071/308] use light bigsur icon --- program_info/org.polymc.PolyMC.bigsur.svg | 132 +++++++++++++++++----- program_info/polymc.icns | Bin 261369 -> 331581 bytes 2 files changed, 101 insertions(+), 31 deletions(-) diff --git a/program_info/org.polymc.PolyMC.bigsur.svg b/program_info/org.polymc.PolyMC.bigsur.svg index 1d6800323..8297049be 100644 --- a/program_info/org.polymc.PolyMC.bigsur.svg +++ b/program_info/org.polymc.PolyMC.bigsur.svg @@ -1,32 +1,102 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/program_info/polymc.icns b/program_info/polymc.icns index a090c1b0d4be086a066f82a1f21514ccb974be0f..7365e919dec4f8241110641dedd7f849a9a8b95a 100644 GIT binary patch literal 331581 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%0D^ka4iDVgpbd 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%0D1kohoXW^QSi1w61HJ{5wh!SF-c}(^S_C#{ z8(q%5mN$%H;0A2z{2`A0szLm!Yn@o7*j3}VI$oBn*tzoDZj(jK%c8HYz(QwmH zofFR$g1E)w^DiQc%t;T+L%?Ef!l#EkaF&AP)*CPQky0mar7II;PsHe?3%~eYD3v{u zKi`}G&YK`Ey1+R2lX#VGO6m7UU!81jtRYxlk1J&5nlK0uk}o|^s%hLBi|GY7s+)}u7WzODO zMm^GWc&PPz6BjDs6a*tp%3a?!2`uC}S4cZLrw@1%t`429mkyD6rc6Fa>96 zCW*)u)&xdSCP6AjEWm$kAMQ0X^uAVEQ#D7m=z@_93c1jUD>=&e=XN|#5iStlMmAxW zkFT|MKRa5Z^0MK%e{2pF{Knd$hMNuiEb_k_&O~xA#K3az9J9;m)h9ay1S(J|;si)C zpMRSctz2f=$eX;yWYLCoZeo&gng2Uk6(UO3#YRIjcCmFl^bPNB(fumGF)f|nc&8VP zOk6p@bx$>PB;(cxZ70P6ONXU7mM{{AotCAs5S54XSMIx_i=@qf*3-X4rU_OhIZxd0 zy8ca_UBKlOn1ZV@h4R^hAX155Sd(6O0o(Efj7S^qUkB4I{~bI0l8j!0T`TZs#bIJw zplpC+ChNeKhdu*`a&Kz4zhiGfJ)a_wL?kDpfUJDLIN5icmn#PVOCNZw& z%|H_^$JKENjc6#uU)^jLpwt-{Ze_aWMQw{L68q{V_K--IRYf z)^?hYy8n@`mt#0o2WSvY$$m5RV#GzFrBj|ibh?+AmQm{n%_^LSiQNfY`HGzU;r^QGjr~5 zMX}>@)r3ukwF0uQMlQ|YsT}_WnrsHn{73fs{hK|Hng=cV588Ixnv_xB1}~J+>0aQ) z5F+P4B~FU5YqPbW4#$g7nw9sl z>`qISKzGD(Ik8py0B#uNeV}h^DS?Q`YA>7BpIv--4HRC)@{`TXbGZubzJXFYytdFy z2-A7*AXXiLo_yxH8`&@K6l3buMBlyYq(9BM4B5dvvgWGgcX`-Vd?QWgu;t5v@vK?h zF}F|p>BFqkUn&$j)!9b&Tw`=OduwkOgHmJU7v-=e;Si=t^2($#ydNKGy*V+V6L|+7 ze_|mg&r*Le9mu`wrv0&(i@Zxn_X66m@mH$JzR+)TEge#}p7A{)hOeH~DVcy) zUSIBQ!yZ}gvH>E0CeF;==}bN@syb|6NuWK-OW8YOn8=tt)!&bO{U!Vr##%Cm(2*+6bMZ}AiMsip>PffgKh=}&j@PG(f2yZg;$EJ# z9l7H|hQYx6@STiT5hOwMU)vL{->6y*C(argCwC~8lu>3pW)nf{@kK(DF|}PnBb*{q zX?NCFo8xdGBIK%t(0aAh5=3@xHHaTRBy?V6 z{qL6d``<XKH)>_JpytQI_P0i!a!~7#Dv~E59=pv>@Sow9SH}rSKrr<)>+cq&nvp~)Au%OVy1=tx^SQ2 zZSt zl_4ybx}+t{3;KR%A24b)^*rcg|8nGO_bWRMpBq=d+BIgN zQ-rU3Oy^>r#c0>erpYE%7=2(Y0l6s*0LO(bZax!u8LnP-a&wXd^4Ipww1rnPhlf%? znMiM|7!B2$oKNbSLuCXlMhU1LIx!yZvmF70w-4^K?AxxY4XT^RVe;6yRz2ZmZ{*ClL4r!2IKI_*X$Km@?$%Q(K)H@5X%AqP#M;i{wH zrU5Cbe8TMf}vyN)VGr6PDKnfbKsFM}+?% zD~465qKz~DXaml+%oVR?QY}g4yV?PZ-6M$IgNq??+0j773*x6j_>IHq?t*bDb3rOI z*}6fv<5V8MT@UflPgnm5BpCMp6G$)wx|yF5%em!I72>d#D<-0Atr4Jn+;`_N!*=b;>Kn=I4g53@4@SIDgpsq>nI zlFMYP?q5Znzi9)9HY?pm88oXT6 z76=z%-y<$fS&P#8Tckxn6FjGy#_rQ%Tmz1ZhsehKK3fL^qRa8>TbZOQwt!3$Nu>d@ zse+yoq77Y@FvVi{R|ZvT^FJMe_Qv2rEq5p`yDV4hSLrMX!mVrce~u}8(|s*uG4}zo zL_;+XX#7i=?Mz;cYit!h$$WEpIV4F*Zf8gzl*7QMHQy#;37^@mavEkKEQb|;?yxl$ zqqWW25pVL1R71x%;rXsCAeunen%Mlat-IoYNg?=}nz^G9^_f*zNIt?}W???w*lS9R zP^&2n;Iou0q>DF0o{TkKP#c-u=H-W@uM6~9&fEI$m^9JsKJ-mwK0eF)#Mi65c~{q2EC-*J|M(sH zI(mu#B>Qk*Y@0joLQ@m__$_S$#bFUbvfS0}or`e1q(!Z}qxb&VhgaRp5_Cl``4m8L z$78ARx6np$l`!Yrm`Fu=Z|HUFn-Q;qYX_{78su34Y>-%4FxILrKbxz#DY5g{>u`Fb zmtmq3;3?YbmGuJZEi*uA1!=*TAjc4YljRO_Kdvq^7B#WG8xM8=nyGRm)!FLjV1y)2 zFQ6Z|e=-J+Mg8=atRYc&yqIaPjGFaqbHqrf>nQivcJ*2Zo z0Nl#_r-CatqwQw?IYUBK?jyyOa(6i+XKa)UNX}?qqL7Kxq}1A`LlAJRmoPaQWLDu5 zzov8fqk=vYSLPAplb9GbGN}ggs2It^gv7yTehIqmAw%DF5e0|8Tno!SS>4H*S^LJ1 zX_Ho#3cD+IX7J@&FJA-sd5&N=UDIfwaiE2|f1HY((d8Jqc#w@6z;kzmmwd#uaF;z1 zP(dLO@Ff6jP2r3|TwX5uhlzLTwAGz0U8&4hj5Lvn{|mvhY&349n&Xm{N2&5)#|8tG zPxvBNLh|V@n=%I}z(Cs#GQJ>GInIZCQvBM^)o(wuDe4_-`p!48>bdBl<=0l7tkThT z@ZsCV>2(J*;+`wmVru;T2%VCHD}omo4l;u7vZLv zm;qYl9CP5cSEib55Pr)0SUtDNvJ-N{l2u<^plwQG(F56JLz8{sC=;X^$D6J`qv>gC zbNJp^LZBE(1dpS(2ccQ4{0>)ReY^jBjW+|0&#S@7D9RY8S}Uhai`}5vwDoEZx77Cs zb3FEKNRo6tLbLp)(@m7uNFJlSx_@ZX$vSSEZW7}bX6J)~E9fb7ZFaPrP{OM7c`df> zH)Ujhyze(Yvzp20Kh128%kq2Ir4Ew)NT()u{duF~;6+kdx#^%X(y)_sR8ZFcn_|d^ zUL9z^MSOB0$RlLJp1qx6sTE~C*hB-ubucY0@=S+hE)`l{?T}psg2RsZc&^sgOGm&C zCTDK{@!@v=eAa_!YCcL$WX4>WnN;w`z)|^b0%3Z1eHa%&ku&aZMAUd{Uj?_>D-~o% z>Z_p~RzTF*jb0E6{r84y3)!X#4l-np;UyTUTR95eYmeI~2h5B`2JY%@ek;wIVJqo% zj6ked&%s= zHpd@COxW?do~I-gjA$S;mTqP2_Kr~;_~@~d`pf-+&z?uN)YvN^w_cfCftpKr0G zE!#W>ZRm=|Hfz-+Ed8sRQJ)jvO^|BWjWQIk$7e%J`34zbVYmx|S7&$H+?( zU{>$+m|nITeI{8o0iE(*=<_ao6W7c8KnvH*BneO5j#9-*vLYVjg^F{p%oi+l=HO;c z=aS#1Iwt}KLPWqBhHezrILx!X=i z0q2~Mm&h(C6>{p{>WY6h1|UIf^x@U$=XqQV2|20BL^{k z?{ds<$?@d^o^7E_ECANNW=o%TTshhP0y#a)T6gpL%Nx@yo2D!#W|?V8e|!ubg&bL; znn^~5h=TT_2HHoz8fc4yo+X@*eao$+bu-fK=!Vjb%n#Ny-B=gvtESHlxD^MqQSELj z^mrnsbdc^ zud8x7DWbeA098LDEowq;LA|?Ebb~_8iubbUTE!FfJ?#9tON*&0iaXzA9PI*z6fY;!| z-iVhqLbd*F?QK18QgA&M-vOUp@f@DD^x@hQKzm0G;+L3Mt9jUeWZD16n^0eAK_{Bv zDi{sP0a55Pyv_&nQDRoUze~q`ToN&e$Z7FjaiOuYbDfgw^i*5qe!-?J_1&l+i{mW_ zRoe6aAnGD_(kb?JTo#G~J^0}fbi+1Yl^Vo zV(EzKyKV!jSho8$+J))G>_SZy2W0PIbkBU-k}XdIZ6v?>?PznmVsEl=PaS3}0F6fp z47FnV4=o7#2|U_s-7G5andL?@J|&3#?W@U{;9NKHAKfZvNImj`V3xOaQT=>e;ZutX zdan;>^1}Q(t}|PSqt?>A+$i{z_U##1a1p>W{})8#1H0o#z~+(WZkx~&)lTGD7q`+d z(O*j?a~U2D?mKoP7VUD{sWO~GnE-5StCo(uM&fTBH=BE0Q<>{2V6#I$JB2qPc5_L5 zwWdHPl*a}i$W4@|IY6m|3}yr*fE`=6Bg3zsmg24m`BxSDf&aZi>;L--o$L6#Y$B_Z zN6B2ylRQ6(273{;TUSZ*6Z2T}YZ!_8N9?7!CaT2dm$!MdY>aK!p=8fa$Moi$)ef5? z9_)RtWS#I3D;K$!auY+IqzULBQ&ps;=?)d;H_*`Qt&)7oGpmPqqhV8$ou!U(ry_;W zofFbRbDIH2*QsOWB_3r6_&s}4r8B86I$#PI5Y$!b8VOsteDGl}^zOju;@?fw?-U7^ zDM9rkTSRD?vUbzQA&d4cQmxy#2CwJoFFb?pCwycAQ&6WZdMz0ox*7|eQ*s3D`*SB4 zs%J7gGnN0Qq=zS8#2_9t=6|FNngXq9(P9T>F2|A-^({D~+Y^STPPj{5$-^ z;75`J1#`C*<6FXYJ<)aKTRgW-R1jI@oDPvA+ZX2i8r?wYXTgz7>g3i*7u+BJyC#*;UFR7uIa^%ddf z&)dv{cloX!G0n7B1&zKtyP&&0w6{2;dwyTK_ZGZeubCi}q$7~bYx~eim zp69`z%G*b8koC}&)bR%i+w5~}zB4L(T{JM1+2FGcY4PcbVM<6Em~-3R(B3etuBoF{ z=~$-9j-co?*`~MZIjVVsw@(zSQlSg0MX&J?=p=FUHeULuK>j$LNw(%1#o+PbYA?wT#dxwC;@9?$m-|LyNz@~; zvm|~$;#37AS7g_XG`|W~BKl=&&&0Y^z3i$t_hjV)5z42l(n};N%U_f zGz2n>{V`PPfCt<@H=A$VRamS+IW@lrb=hpGIVZ!~lNn?;?-?#<<%>MeNgWow~E=uWjjr(*C|EzsWgS?DL+N*%%q=_bXpH!o;RKx|~$9 z?LKlleJqGr{Jt>aJe13#yKO9;8>amweoPS+L2d4Fmz~eGNFlVc0`5D~Gh6rgD|hyz zw;~)+xM=q1{GvD8^H=?(tr=Vi8%QtezGiPoACA4%pn(MLc1Vc|CU`u#QfZP|d-jBA zQoa~mSqZx0y5jZI87gTjX}bfH4Tv`lF%1!Z6S7HE60{zJkiCOP9p@L4s6c~ks&>q` zU|Cmpz!KMUxyfm4Mwp9}7yhMDuT}gO8IL>9$E+wfBn^jRQ~}8x!p#0%y;q8uI_ z9=La*Zu)bmNh08GZ)Ha0s9c03i$Db}#O~|z3%=Et50(-=+QdJtEPHXwm#5aevxZ1` zH?W_w8|7BA9|dEVT}mnoS!4NIZpL5X4em1C{dzo`j1Uh9PQ?4}Z z>A%BAPlkv=Pm(_Cf{vR3F-11_U-)rVRlK@od^go~7q&aZSHSbW3b;)gzBE*4x6|Aa zv|OAWua2)3<=)9MeO0``s8GA{W%hK-1WVd^J^Kv^8C8nfDxnkc4o|st7FJho>q0xS z6{{MQ^z7W4!5>Rif)x7#nlncKU`O-rb}CA`9Lpf0@-RA>url8<8bx=^yogd-J)T;I z{sD{JWO&1#%>mZD2P9jYStQ)O>xz*ld>6qNDV&) zVwQCTs!%2APdmfT_}ID5pVI!zNjSap>O}1j-*th=!wpT=k7G6+srxr3m6u6J9&*?! zq@HvaOQGu99x14K$W6l^WCbR3DI0TkO<=2qc`BrV(QYD3II~VTt1l>ggu(mvckyn+ z8Sc7!Zg8UPRXU=gL37EB0xo~S1?ak_OZ~3L;m}tzUPt*&4~e+u{4aHGk4=%4(e_-N z(_V)j>xnDoc~s6g<*LaO&BP$bhR!ZZAQwW?<5G+X6Ip=E>4gzsBj_#zUGbcYWMDv! z$OOp9%E$^RBmpFuNfnS_-z;it4DtmQI~-#^37K3=e;`6In8?P+Z~_I-f#4}W?? zK#nwAby|U91!|L6TumC*;Pjl9Bf!Q7pCxV!=hrepNwS;ojeZw5iuY{_r#fsx8Jh|;C2Z&t5+Y?5OiFPImo);>j%t@UuGhLJ$744r0Qyd!(c#~b@( z?$h4S;ciilc1cl=^)|~^kBB*=Uw28Wj#N9p#Szz9MK#yEeIH+kDbcPbPL>Yi>DjWX#!)LL(PGvFmX}7HRRL6R)N!G?48yadKa<3%DKr%S`vX29@8tvB=CTMaI zefp8n=+Rz#vbcxU$n6CC{YObDXZqX zA})CR*rkq&GA9aiD%;k*Nw%lP8LxWMLt{z3{4&Hhgx8XD`Q^1~7tX`4EOuV3k)s^y zh*ZQ9Q4Vmydu8~NrbVaJkK`b7;_F&^+RK`U<5UUbNRuyhuirh++O%ZnckiS}&%nmTTQsDzC-rLLV9mSB@gFkvOh>yLZTO-q}|he3JS87k=5M5b9IjLSy|b_=A`3iU>1-y z@1-{cBdiSB`r(Ysad0x-X1={4y1zAe+yyDgd{eGCB2N5ey5r{aAiEof%U5;vKl_0{ z`&BTeLn_fq+Jxc+7-ij|Vg=}bs545fo@*1O=hUgpW5?D~d@2OFv3 z9=D!fl+`QPnGG*FJhgo4bgSn^k=q-4evZ~(F{L_gkv17{DPZ*O2grITJC)O7N-RT! z?tLjr%J!6d1}yyUz3N&o?;QWoiZef^;h~^P} zFu6`2<2(-B*;zq|7__H8tAoJLSyts3m?2?1_<`4s{Rv?;6NVjES74Vs*2o$vEI&He zt0cx7%()3&#Zr%Mmtk#0UEq(+Gcz2`PCA$&6(=MQp{&TBnGvMBWw9}-=bH=6&F$C1 zCrJ>8w!Vd_hqtn%c~N^-W~B7uDvmG~O!BnBp8nkL&r?ArwA2ciUmqnMJ!{*UljQ_u z7cnbYN@aM_kiPs7e1q7?%FlO_HZiCRPSb#pXueB+uA)A>QoKsf?()J({_;!%)k!%) ze!}`^0V8blKZSq9ue6`bIQ0r0c>)Qo_a!#RdN;H&?fifrp*lRGNk_NWD#KNFSbewr zFCG)CXd~iD=c^xcV50yxm6Q~cd`MeU6CWr-&WfJSrdQg7*6w_1n~abhMq3n8*go-k z9>t%G;-b3ic=A>&u&S!wgQcs6qmlXeO@m@Wm)uSIlAvPx5zYZ~xr&`l4-u7)^a}}$ zrSQ&|R6&<0PSn%SH9K=Q-}-J@F?Y#H(;hw`+Iw052a&+F^NC@IZhKE)N>prCpytnM zwynVWw}QC96Ln_>{hR9f+J@osVVWKkY;Y%Ol&*277I|cmCPe6CARMh*H;BxB1n*)WoTD@(qOz=L^RA#fe-=*6`t|wGP%@`tN`eaY$ zV69Wn+ie2aei~dXo5K=35?8so1it9}DCcRR>nG6O-vM0Z-bOfEv3dz4m1yRj=Qx^o zX{;I{mkFDWz`}|`%X@9|N{W{+^e27N3J<$W3Arj6{am)#pCdHy;7!2;!EIV*uCoiR zHia{$CGN+L`&*aH>wbd%Z89y&;8H6 zcUWV1ey?ay&c}`LW7tXjRYQHK=aNS5`>*^cUMYSOpRtTuJUlTDmCcl0kjHmrIM{PP zV}DDk$*XX>5^Jnj*x{u|TQPhT=~a)OrXh(|TYPVqS8Lbl z8)jpcNWl>4MHv0}T-0wdZuatN_MrV!(37#eJ5QYUYp$M@$Y4an2#uGiuHF1_ZC2kq z1F3u8AE3Vh&SsXyR-bC27;~^;26#Q=^}742P8?@*k+u632DeDCz`j-gCsHoMzuwn^ z0Zgntf8^eXfWj#qxou~W9uG6g`p!|&Tj|czIbA<6w-kFKOuZ8wPbl*(g$hxz@SBH? zz7t`oCfI?0kx2$puP@AjIMiKm!?7Z13B{esm@URUwB=I4?d-N7qTHgI}G6x zqfWEr(cZ8Ux#+B6;*igR9bDdlS~)Lt7RfR@I(3XIz5BhS)gDI?>s!%dS5d37Nc9Z= zA$es4Gz{N>SUqOmj}h72A`LRD-%-m>)sJu4ICeO1O4T4JAJSgNjf|}Azn2js+WZCc z+c?@AFvBM*$N5Gn2#E{f1O&xHs1U_pF-uvDQ^k}xvDuMxOy-dQhWb#zA7^ywU88h9 zLO}(O!+mjO7Y3h(C8Z@xXJW4=2eudWx*B zXkb<%qBI4(7%lAOH`r91=k?Z=+k#}c)#_R33~aCOQ{MNUJ1za+Zq9x*=6tWetIt!^ zR-P*Rq6+I}wa8MKH1wtBA*7rnu9W-ckH%?Uu2-AFf=n4C9U>k*DLHdwPkv!E68mC! z8P`>ujd`S!ls$FYb}|yPDBi7|3Wj$YO zHiLtT<_PB_uBV3hAMh}$W-GmON>@;=!H(SNJS=?}PnLO*RMy9+zv(1-7nbd^*3A}wYA??X}z>_6{84Y+z5$VJFdW& z&6^SaO=fJ!Vw`{dkiqDj~0wy9B z=-eddX%NZeupkWQfWomKZb*hJc{i_Get)7_b`<;euijmn=Wn(i^zO^c9NqRrm(#r2 z^|FHC-N5@tMOy-!$?_0fK)(Cbgy$2Xi-hPC;Pi<>6j4={?q3Xf8^?;i6K##mQWrSJ zgO7jHC%vZ4;00|`Km6);f-cpF2t!}K$%Lv! znl?w6*6#}_`>qNq?u#=|Kx3)0kDXW=eKxsjxaC`2p#yYQA{rN{&Ulpnr+<1%NnLU~ zk%H7e4-njXT;MZ0i?L(#L988J%3pC;WaL&Xfl7HP66pi}eU<)ll62Eju?}dLJ6ZiY zQ;6#eI#V&tcD-DkM_!nC?bPW*fM#L4^BQ5P1CT_18$9a*Ci+Jo)~sH zCH15{!A>8Tf$nw+hQS=*T6c9+ID}RK`RUPI=g4T&REf7S zq-%U19B5wjOGJ6rHM5U@YR_&E-O&!ylUU;Drr;Ar>h313zB*%X^`Igk3=AceCgl0l zc&oE26mQR}Cs)?4On=V~S-$K#(ZAFjy#$-VYXf)F7iPZijhNeAX?PB;YYm@Vnv`av zXI?Gn_AW38KUxNd=|dKT400|r+daJ(`mbtG?(PGk=mn*{o=>wB8}3to**&~My>WRb z9c~u%k`%V$PFnQeyTyygOn)0&Sq;k^hbvq?TEOk+#s{t)62~P8pF7f1;;9TB?vjlM zZg296IZZqqO&$1G1EB4>Z>bFGmavhI=C|xAwtABIoU04!qWZeD&xn}c$Eo)&5swP4 zi|EWkJSHz3yDjNx7?JN5o&YP-Y(LO%eqUQiQ;L1`Jn+bh<9*d*4HEXTAS_irh0reV z+b@!=@4>k1Fyv>2baN80d+XLjX@up{bd~@h5hfy+m;-+zRS#u4G$Js{R57WPl zIwKmkSDng+{*p6~aXpz?I-7temz%L#vuph0ZFF++{;Jx!Xvax<3%q%* zHE8vYwS;cg6?*lZqRjjI4R67ddvdM7d)|Z%>{$^ptjC^kmNK{R@a$WOGrs~Kqlok3yF7;8$J$kp6kn$NO9uSjZ0u54dRP<= z1FRr|h>32WQIr1AsMO7eWKwD!=U$X|JSsCbr?q}RW>>G9nCCgo>pdy;70T7kEG5rv zz~C7BkVoXhugAvFWT&qY_c+sh?_?YTOe^*a9G-Jv;u7upOpL-Uk0f!=LamfBnjnu8 zC0SNv-QJ_^{O<_VTNi?AKX#lhq=LM0slZs;i9sm1!rH|+ynbm-2|dF4vIoexkC5UZ zgr;h_XZ0x^`YxG4(T3L~fZ4pBYt)$dS2E|M>sxw|1=YCF8u3^LlZTvDMlMTb7D-nUjXbT~k-FlMD5|WXi zm4Si9yQhEQf#T>il^OPz;A5-LPnI=Ee7Wv#sWqGCH)AWldP;V}^eF7vi}u~LD!jZ$ zYLKtBEE^bO>LA0UZ%6)6ID!WseIT!U|G@pWP9rZ@bhMiM8f=-{Q}qYSy7UefVFnsj zv3i;?=+628vnTDu_`JwcTXDj`FFH^9foEw|cE%Iw**#JX+Aj>Ui{ac`aXi9Eu>WPp z)8>zD{NoKW75U+ZgC-M`f5w0LP6D!S9C=(Z6@aXZ?H09hlSJnX>`BtE6_5 zvO2owN%Ml_lEOsjS`B*3CmMY&R#KhKyJHKJlD)vFnQE*=`6|z|RS|B>I~*qak%JJd zMy-_^b#12BP(_1A59c;;4^X=Ffi*3(gA66d;u+4rbQB~O^k9Q!S|4efk7t+lh-%*D zF=GsHxMjh~t8iP5xW!oJBzmm=mJqaYtwZ8W!f9tEB*yrXaHn+#P8srUYi94e+4Cz3xa0>u3_Bz+ zzjr*eGmdZtP;|IECzxVOL8|bd@tTqWMIr5IC-RN4yUye7S&P|d4=~K@fLSJ2q?x}P z)@`(FormMA-K6Im>H6eN$4e_Guc*_FM3i_xJghaI` z07%ume{vb_hrL5}%}=4M)WzE^Ke0MAYV{6&gLN1&2UdLPX^hgF&pgLb6v$O1w}~_v z$AO*j9i>)VI)}u}q%NC3Bst8Gq$qzFiVOwIN~3v~wek^UZs}%*7i3Y0vK&DUo96sv z1i0VDYEL^e`g~1UXI)`*-HMHr(@DH#HcOc9F~BPzO^bA0rlu#Kwk?YcGukf5?6P?R zuA$tK>Xs3Q$h_I21 z?d%kblF+a0s9$G#tv3^JzzMilih-=gYQgdb~c z$|bVTtPDukX}b%orAUE5)Z1^OUD)c0W{r@O^=wczyZPa}PSdKgmL5tFdJ0%7+wLd3 z6+z{C@9u%97fQX+&*FYg`yA7kTSr3m+z^JwD-3FQ*}GZk;8`Lf#}FIyVGGjsK*6Xf z$>K?lmc4INXh;89BCD7M5M#VJa?uxo$Cudr70O6exrkf6QAMCRXVQm8x8sBg>fDvs zTRM?ZzRd_A9Jz@s(6|^nVgFIR{pbe(wDcE$bV*S)eq)%@7zk1ap}rtyEPl03P(slVId#Hx1B1@8m9&IqjmuQ}+%AoL)TjO6FHJO1 z%gSFhk6}w<4Opn9lm8iz(^p7VP8;{}GO0taSw5-XOj_NJqbHuY7>1xxY1nRFmw)vv zOJ3BXKXXrD5T_5#LMVhZ9@!~cCu|P{N4RfxKT9|fWRat>5==@`6)Sis$YCilspslq zYo&LDTNDe-m#MLM$1ySQlOKBa?#y*p?D7((Lkth7h>ngP$hs~9+Neg7)sE!iD?v1snMQmCy`0o{I;gdrvIlSTaE+98ht2Fjso z0i!WixzW}Ppr-xLi~~$$cx?rlH}})=9UU_wdCS`hFAJ+CF9c;a&I=h_gBc6qAZX$* zzIP25y2lPSrQ^z!N(8oXG(nX^DmBZJVSbq=i&xK|1x${k>y%SNdZTwREEj{di;@(7 z%!NGs>O#aukRnBs&f>%K5PTla~5oU)b<@fU3KHAy?fJtT5E`(AXkvH$istV1EF+%?D`V|yS?JXb`6F% zy^xU7lw2Jk7EKY+F0SO$7c&my?kp5@Tk#-F(Wrb*WQW0O#&T}4GZU88PUBQJI+0SA z)rdzW3IlQ=S#NDdo zy&5(h{VpeFED2i~%UK)RJ4~CR`l2lGklxA4;zBN$-?Mj)x~Sf;zudy{rBw}chqw!r zT?gMim#7hQ^XQ?|(l-@unRU^S$$^X4au$-*OHP^}y=Yjs;gzp(GIuUMpK z$T**!@+9*v%=o)P&-jX}=ndz(_3Pz(Kpw4h>~enZl(bxE1AEQPm%i7qtP2Dg=v~Z$ zO8RmI-v@`Yv_GhtD)%f-XK5p3{4&o(YiZRhtm$-2T5JZCS(2*?OKM8EJIt8 z!|NENn5FKu_;ovn(-j?$3hHc*mqd_PJvYZ_a_4>noy6QGif?%IO26u5j(e24x;s{- zlHQ4Fu_XQ(Nl{RiAuU^Y8=;$8tC9Wf)TeLRHq^MsZi1o{tXzOcrotZVZVA*Zo=@vg zHx(xv*Hy&~aqse$*J8eHw8}6Cm}OX)rP3Ikz2N3?(U%TFQ?&(3QG1v$sHUV{86qnu zQm(KXy=m&Bxjh|#Av4}7kEb!kD_<@)^F&a6yu>N;k;}8fj=YudtOrSnK@dsx{KapY zKijp@qmFD%x0y+B(%((z%U%WRpH9K5l0E^W2tNpmZkxZcJ%^!Vtwb!!NpD)nZ}ql# z+)|d@6=^*%p5qMsl&q+zwOP-}D4T zdY8dlw^;)U&5p7NhMk?<~CHYRh;$8aA288FPJn=Z6vc-AxPf`+4iktXUorq8H(ccugzqE}8w| zrhUNu{cnT^)}WQ)!iMwWOwcS$&o>m}#Lo`v9|7^aI`g@h=TC&$(EHD3kZ$SM)fwWs zp%_&=+Me)MJZag0oD*eyTTtwX_e%LmN1uN0kPWTvKd!SVPp3{Y^|MjQ?oW@FlfhAV zt9ws2rDgoDaQ%IAcCiiED096f1G9(KFZzJpc);`Z^So4m$uv+nyb~REeVN|84=eV$q5(T416Zu(QJ} z=`wKYWM^7W0{G7~>U!m9X-6h%Pj&(d4UTDXvfncz-XVGHS=(ZyTm2+_x$h~#Z z{(BXVlLE!H=Y%O`=@*xja{d^<$?Ldh3l9y4ZIG5zZ4`z2?7wxEYOGzo-MS0WhNAmlts6@@>qQLFr|+DmJn9$@}i%FK*l4Hr8i1#hIPH0BaVA6&^`%)JVc2v4Q_ zV(W=q(iV5>7T8ak^XizOw&&qd0)Oj^vR zZn3&)hAySfM_(veYtWAZj&Zyu{WhH)>;*q$i@jY)ge!_wDosy|Ey%J(h}+Q~!ZRff z;x$13CsYWA#^J>2IY%Y+7WHQ1GDyx-x7oHui06h&x5~1PaoXp>-<|`rzgR;k^AA#Ip$WN73UbcgiZ}=NJYrvzvVaIz@^+XyV zX`aq9oD)z>_UO*)WVDC6s&G=gNFDGlQ}s^ zZDDdo)^IX=Ok!l}z+KF*$GG=!9GXv-WK?B^B+oy6RQEDiz zOZR4nS`ODRW!@t)Q&b8aI`!7dq3B;dzEA^PAJy`6Vp|8G4{gLQKO9X z@-4|zN+!cEuda^w4mXZcKPhZ^Kz>u#OmXSwuWk*LLA!V;cMbqArZ8J=d6!Q8S$A<( zC)8?II*DA~rMX7rDQo_ekA5eLW_&GN-1;MYP9Z992qRB}?hVwIQXm(nu#M*t1&o*b zBFdoxLf$;m(ZSM`q)bCK(l>*QIVxZ=c3n~g7RSq(++qTdrw}>mu9*C?RLm(BEV>^# zvpco&!*?-b-o1skBOH`l0QaaDOK`l&wvieVU%V=0hIBS^&j430)_!QVd?15a_YWjf z$9uuEEUkop{^m)W!~3hxLuyUk5jJKOvo-k-k&6vpNk~sg{jI4DLz&29o=aUq_&R>p zta!T6hG7*A?}igD4}J)4>9FoFtp}c6w!D7(&+h-n2DXjD2q2=Qz)T+{JT`n84{wo^ z=Rmd)xgcAJtB`}g#wc8s!zevL6;{<)`?-``ROA-M2lSo&qKBl|43g&wwoIiik@1fs zie6TQLnXembtHi;XnTb6BO$8=oVr<%KkBz_Xjwi{-;gqU^BR~QvI43rdg1UUv?P|2 zFT#D7LT5HBf|@OGD%tKGbq|j`L0Vzh@uX)4k+TS4Ca#x}33v5(iuvnaTcvuV|E{ek zqIm92;F^=o-`+yGp=f|yt$=;z%eGaoLEWTg7K2}=gJ*o?WSdE(AOG@7VV0(Y?};V*VysE_mks zor{T`Hs*SJ$!mpuOXyl*M*zG^rG#*%f^Hk@s~KgczUysS+v!783n~T$gvoe%i9!mu zjWv{qB$SOad2VA0#RVNM!#)R7S;*HrRGM0jKX$X7v@nf6woH=dzNG*sckM@#2hrH`e{JEoi^MIjc?5$~AUSjtj> z?-=)E`mD1k;EtQk-v)xC>3i-$EW(Ea|4w?-*!H6P(MA{3oZrz}$pDFg&EJLM%WQ3q zUk`f=P5zaLh4eDGivF;bxBT8PP;n|XyG^CIs`?B=H1P9Xw@CBh66$r{3TZK@vZvs7 zK%Zx(ccU2FbQHDd#LC?1QQj7?w+Z&yHxq|En>id zr^XViwt>PFAx2%1#`Vh($-HPg>%F1RiNn9FB)X3V+eZyu=H>@484geD2hKb%gK5Qr zPdx|r!2!Q!w?Qd02sI$FPu}~XLqkAtE03M}Zw_^r8B(qem$8yxmE0|RzdO3CJcRkTF zKsuRR6}LF9gS`zx`2FBGoKP$%_cVZp-mYne>FE4qe{Y@mFr*2;lGRS&o&C*)~A7$g8? zHgN?sc9LcjBm|e1!nDu5CE|W4?)FdI2zb zZ#2o?VkomcNc}8C(ewr^_#KGmK6z$z0Bfp4R=EbJ3l|2}tOD4+=LIe*jc&h>aSDEi zTc|CLU1Vmz6Ykm^#_V{re`4A6Hc6*SNw4x%`K?viOAB zxpbesq%T!4G7X8cHT2Uz=UH9FZme=Podom{9_lGtvGQN z1Xx~hN1@XW?IGSu8<-afP9k0HyH7Y27Fmz`MVI*9xO9cTkXGmIVu`{2t9l-jkNrYp zA>xcas-A+>SB_E7+X_EqLm%7x>qU4QOdasG}Qq8NDx7FF(45;NfNx|8WaeZ`!>3b!x9C`gi=U= zyT7y7l`CZZ&3K)+>p9BlmUY}{n;`t1NrNI}DXRodMDbY8Wwz?Ul#|k1)~NQ`-UQ&L zTrO!jMwep(wXkGq*Cv$AC zXFe;-M9s5#_=xyn_!P|2eBm&ODkw8R`M`#%Cq=#qqu%peX({;v3Uo798qlLITL6u% z&rpe;*MCK-JU<^$f$qiXh=PN5nMFfBOLkok928a8z&cD*I;@^VznDaSxrfL%XXJb* ze0e>DrDv7uFcdEx=7V_@y30cx!7@1uLJL`Ih>pJnmb<^z3QcNu*7@vru)M9#bGMIu zgS$GI3&*8q96a^CmQH_qJ7)Yb^*5MIgm`D!O>ygo!p*(vky)f4wQjXfm%b>~%`Lm@)Mg%%@xGOe~=D)T5t%}!HSr1??_GQ%|6VTyF#;Ec$VXcPW8kr~Y)F^Xcudc>B z3f0k?7^8ai!x-YvWv9{Hd}d$tMQgX+Z>l-1SNgQxKes4E~knGzv)dsfc=_Ux3+_hzmP}<7BP!% zPn(yh-?|ZH@zg%18d;D#ilEnD(GR6tg+Bgqzv!}iz&Uf3&VJE0n#DBA&N+}OsOL=r z{I=~hYtx5JVu|(m>a%|*96BpfO8tdF&jIV<$ze+RGmrW329JX8Y~rk3tIA2mdm@rM zgdW*H7>?6Ctua}A>N1G9EjD?ty=Dh5q>r`a0EHPXC%g9Cl67!tu$5Qu)l)LZg@Fo) zJSxSzfM2sHj@#VA%W$Mu7s$(5wAVH5e2SS+=?`ur90Mzw@KkZosDEG6WfaXaRm=^eR<%&5nKcbVN#YxmFz%3mQ5ipcrz zXS>eS%@bSksID}p0W!udr98C$sh5s848Qk5Tm9vs&;;fzt4xk6P~b35N=L*=kRzOs zj!`DZGvi`n$1_dKWVIgrA0{^_WV64cKFk-Mz_o z)98ZLHgfCGvW*hT@(vIXv=tE?x$e6sa%tt_ufqJWBGEqO+yJF=3MN>*T0A^^ZK?9o zZ&aW#V9{zo6MnK6-pPLz|JOsuy}EW!9&sEz^VCCI2wt-`FnRm&l#z;x0rcW;d_GG0 z{4t_(d1f(Hdru)oqc?BX>wYz<5)DA@cmimxY0kc1wfHnG#ipmGULy=xpl}jE^S1S) z`B)0_W!F9;Lf!o-m6;`@H#yz=PV1Vu9(%@0>-V!Le40+EH#7=qV6bZ3DqAEgvljRS z91#6w?v1$-$9`(Ed=qfz%g$Wwb{90dq9AfBwVp}Twl9{3qsHc~@;;@mKc9tzn4%mE z2r14~K0|Z(zB@^<2x)8ro!$2A>hFg>bo*TxBl~kWGSV*!q`j&W_^8uqFiX*A5d_~S zkFVUMveIr0PC~tAic`haG^t?rM&k@v*y(54pT1dlyeoQe!rr=atJzxla;4tWDJUmC zrnM2EqB$^BFSg%N*eyhKsZ=o79&z4|$#cC7FU0JY=*8w=qv$uAZ^&~rOBx1(3 zVA7(OBLti|9<5R!=K(+FhLR6-$#pLzjgVbgfy#gjsqv%YN8M)8!8R{x!f5w94+(Yl zq@PKlrv~w;Jj~PBT%zx5+Uh(A)<+5=H)-f;1X!!4XpWJFj~QMPBg&q=&BDFdwHFoK zd`OPqbs3GKzX#%}X9F^1%=!y$7+85%{hmZW7mlqPy!f$jBD2tDduG9uY)5#W0<^Zz zbKv8Wp0`MNM5Z^ALR0KJ*pVQ%70aBkUp{T%v<#+w=YCHITT2$+I$^FCu=dMYW7@oI zpmvJcGeIP?_1iUL z4oBL{o0y_)?6zDTTZ<$SlT}Y@<6X*H0KtV*DOz2)?uNe9k3A$`n`?_V-`$hDc|Xdg z?b0*$Sct@9HJdrTd07L$(3(s9X$L6=ub6DA8OG;rMaCO! zzy~<9kfF^8v(S|rs1vTT`bUP_4F)}nRQ1pW?(hBdN}$5{~{3A zyqaltGRNGS{~t-OKzzOG_vRlhMX!A$|0|Pjy82)%-2mKef10+SU|iMaw>zdBw*bI( zH_Q&Ww*tjkZ+DhXn{Ovl29zia77w8!8)(j6|E|EAfF_)J>3MhD|I|& z=M#pLxeEank7>mWZX}?}lp$A9Fv@t){lPe`Qbzi{xa2SLXHG_*-W+Zp24I`JU7Sp? z_(FrHl1t&=#U}=!);LLCvG&?bKG#Mzygs$P$$e|LZtw0_gwo&pmbxBCD@^2g`#FIt z()Q~ILDu-_OO_hH*-A>i^3UBiLhdg@k6oK_Lr3&w;*OZU2tIq~+M@{iZ(w!xE4^B& z`loj_L77#K5aZII!9j-5+i(_hF!_sl=+D}@IKZu?9#W});yRM4ADq5=WWMADQI7`` z$HiZ$lGh%6g5)MR%oFi`nse)lNG<1P1=7EN{ONVLStB~`9?@@mZAs+aaykd>NiR1; zYP2LZm1HfbizZ?9(^oR6&YJN^XWppKhnV;|m=c0MSAA%ra8Tv>aOKU2N?09&%O3dj zbCv@~J7u^j&(RxtwTi8PZj#>M-`v>wZt7@fz4m_ZwZYo_T@#5Ogq|1o%J1-9uN<(` zD1EXOYl92`LTnxJ%yCau?1gf5{|gCw43k99>S>)UxSf$%;>00i4zwIyxfPL)_;5qh z)K_jZV4w_065Rk1DvjyFvnaZW6i{=)X)7`ljA#bf>+ux}Wmkm|-UcH0zWx?Oc^QN} z?Z09yXXOi0S7GnL|HS@EnWASdqc51z_Y_-b=mI_LIPR!{m+G9and5N_)k$b(1(vIR z^s91N04k7DT$S7+zq98CzK_)rS|o+`{ZfI2g%ahn5+5=m4Loq~ENu05xVZA(@D@F? zly`6^RWm@bXv(~f`9eYuV#$@kk|*zp@7@bQERC{d6?mGl73V>mTAf(X(TVS$HoyDQ z^iXN#t@jG>6vG83udk|c52{yJzgc<$+8?FJe9`!KrT9b^+EInE-_4N4dm7{2`qhy4 z;8Qf=2HmE`eM4N12e}3%nfA-B3C$ne=az|+IG{rs{JJi~eV|CNKNIyq7(1k1w1RN+&UZ{#H{_}dj->m!Nx{uUGDo5$lMh#j>?$B< zQeeWPKijUxeL-=A^PbZ%y8;vFexzB@<-;_K>(>5K@4B_$18+QY&_lm?#hHU1F=+#n zx-CQZny%LTRN5!qXTQcvg0CdhT(h@h^KF$oHSsxJ)nc;H7V@yJwsF5_q61lT#x3{g zywj>?mBp{dl2{+bmJhr#m`9I^Aew+=>pNt8e`+TaTqn?5t{TaD3bT%2*!w{LX$=-8+ zvb&wjCj3FCYE`HbX;QAKbZ*7mzotU0QVghDytkBZ{{HnG@dd?ex86{~B{*(AG9_sn zF@3YHTF;f!kQ&SxK%hxay(KQLRtfXqKbz`eu}G|)Y5LjQbc_ra!+z12Pm53y$5p#^ z8TGc&Dk5LVU{qI?2a+lcq83K`Mwrx|K@hjt9No%R0FAh*1fb=;*|9we+n~XzDS)xB z+4`c-=9I-)P)4HoU1s%~H<8MDsHs8%r`>^p&{f1z8Me3R@9SE4Ukbqp2w1fqPRbmF zvU%g)rZ!U`YUHFr>HtJiqj~S`rw$ zJl=@+R#y~qYrOt1&vR}oYcdRLD@RC*1-mo{uYPNb!@nR8AfW1654O4@0Tgp(?t#9} ze%x0q>I5gyIgB39e5m-L#o^{-hb6rn2d0$5x8&u-#C>-&e%erFf<`4Mp>B0y3zXq+ zbh%&5Nl@=M+kfLz>Po)Kf608mjdHQf(qYf&2?RvQ)wG#)@dnK;_2k{O%97Rm*dRMu zP99eIG3qbk$p`-v1#Qr>?oZ)29uc4l%x8|rxg17)N|_w+qp&S>B+ro>XBsG%btfkT zUGst(*Zt+BKe&Ru_}t=U=BA@aJ;oyO(o_#>fNIXJo5dV+r21uSVV5oMdm5UPsP z7j_#r#s4Yr0w>XAV^P&|)Yc1@il4;t#UmL@r?bk>*7J;%&r&ayeSZ!;G655=wuKi) z^u}Aieu8Ge3Vi>qFE5Lk{`J{W&iJhT+b~R1?^++-4JeY#Hw@D-3=8))!tWQJH)uR* z_A&Me{wXZholLqpi`Ks}AInU9ji-TrHOXp_OLv%l^m}g6;r?-0f++7&F^d3~I4+CG z4!|6s?#S**!FT^NGxu&6GjHcZNv8XeudOR|`|^12^`m;*mBKCM3MibTK3d6nR`q>2 zK63|_y_v9ev&NOj?@+m^CU63mC|U8Z9pal*Aeoqd50s<)jFhHWt6}k^Y$`&qrSC|tmKjCc z>9R#pK1=NTV#~2omv2vk@l?$YPIORVp@4&_FX~U-!|MSS{bGCL0?p^t1$$)C`;xX( zvPQ_+BT`+e-R{#HJxsp8jzc)cFV5mQmavIClfnBw8O1!NgnX;3N_kZ)53YHrcJ+YY z(;$@G2(Wa=Fzk9|J=mtA`X8q2^2{8MfF~3mo2G}2-0+ovvW4ntCO$5C(Wd5oSBmT( zSNSc#$K1$Y)V*#LWkhR@jJvYco01OE@XoQXz0E|mwiz)ikO7C+f=^LW2NrZZv*j`a z&&&Tz&L<9OtKB^0elWC_=UBHI+>7=IePnI2+As+E9r;)Aazzv0Q{em2^yGGrOy-I; z@-HkZ(8^J_Ckl-0dZr!O#KEy1Yhy=k&=N+~#(Otf<3wgQuX%M@y8fKe4`oH`nJaK~ zWYH?0CHWwHvfkZX4_3n?yTP7(TvKCfZr5z{@}u!aXF<7Z`}?@J^MdM&epe6qBo)i81|#H#uL>)n<2)F$LEv#n90i_$W=MK!uIg%@3yU{-P_4kU>v;Z=UkvM-d!rw;_L$7whcp!W-+F zG6EQ^_}a(q1` z$rE$T17E0L)RITLtU)+SacZWR4st&>WjpNiaIlxfP$@jua zZqnpbx@G~{RE`cZ;%ZkXVEs`YpeZEY_$t2$kFN(nRHA^c3x1aP&P@0zFpGcrT1ncJ zUn4n;@{rKQc&obO*|sf$*{YIBb!`q^~Dh;;jrIs_mihcoP`}&$wXz7R3OCd zL@+y0dbs27)h6GkdMu)MK6ugoXR3amMo@xvNg*92#Zq({^no=tW>7rzQuLUhcEff{}+n0xOSYGcRro&9KX;_dc;v7SAN0zOatAlw+D?O<)k7_{S z#$NW#&9R2b%q96+GL>=DS4QASaNdKQ^6$aPD9bh>(Q+th&lY zG3e1*UOmp?$G$f{bv2d%cWcE|{@!WBr<=u-<<(dS6;uNJzUKPXRBIk3k&0IT3SjrZ zSGS#a{DUbKvpM2$z8~9cFshS~TJ>$Aab#GVyp>G7@8Dls6-~K_SQfVNTZJc-t)lmX zK592m%tkfSc_lV?oB38N50suD96yR}2p`Wmryd5~*<3hB>nxbko-~Ab3SWiD+s{LS zS*~}8S_7^>+Ky?l@{!Npri$p*iNvYoEoNm* z+U_oKn2t$H{hU%l$h~qt^Ks4-;UUnspEiT0UCq_2`A>?h!u2GauUw%!%xU*??kK?^ z+E^?}WKPT?*Lzc;blwa{gz#WdkOXf81H(vu`G%b%qshSlD52Rm1wpMWf_n zx=y!smphomfm$>6$w0rlbOqz51wG$$jovw-3JP2rAgj_)@%dZB_~ z_bI6Luvs&O_mXQ!q{*_Gaj8W{JJrlgbFY5y73B!pX;@XS4;k2b6m;qFdiQ)_@%E#@ zbJWmsCVY=AImY?-f16k<|3zWv$MOr!J$DQG(v#P4YrcNl%pr&UlO1FyQ$BzUqg~0e zGpGyuUxR`e@GZGG<{N*gA9{Z7kji1@m9lPof0o@0f(JqBhGGZ!!fv;_Dpb3fKGFKW(4^su-e* z-gM+JUeXve7draSfnCwlcM?P%xFDV_X>3WN5*RgB*P+ zN-^Rpz;`*0BJ8lvQvYZx6>+DT`=af9o=R1>fYbDL35*@-wbkMaU|9*CJO0wU?ygd|m__a}tZ&MDxFhSDDw_|NPMB$USg< z65fvEMu6%`uK#xalQ1o>*ESFLUF++s@dqbFj9G9RbN!Mo-|Xv*h_J@)M(J_T)jC!D z!X38&`6Mun8~qpB$kM1K3@VR?cA+38`r*Pie@Qj^kCr&68{hbzNH}>C6TPy`A}&t2 z9L%A)-LiMD)xe5OiOt9v=Cpj z6%uW1wAeanQ;$yGwnGa+kABvO&4+~vL|F=m9+|iXECr}wBn>lMyJgX```Oz;&}7cA zmaBc+`K0I^)q~TZEGhGDkqudqn5}cOFL*(JR7mQ&QEC` zua^&f)={k}+(ULd;PUp zvCuOev?&W*6w7AvVrws_EpmeQ-&;EF?o13arl{S<&rbHnXCYAcSAbidXhDuyMg5&) zkCnfADzVS-%$HdU_{ppg(3vJqv_j)hDXDtFp#7xZRax+7-+w`bzW=z6FJLO9(5$uB z_M!ey5{=!yPVnhLwXn*G+2!)n$xr+tTZuM)n8fhqSg_F>ii52A7-kaF77nqS-5UHj z43QXR4ZK&3c24PMl@AGFX(%M#_TJ2w6PG`^oOK8BL@nOYwht)}B~HoJ)#yL_lv@zz z2K8;v<~m2!id(#fW{0z6@<-+7kJ*OTzUzvhu83NltK9dTqbXK8g^FY9gl)HK8$@Ae ztoj&>uzYujBS6C`g7P8?n zjFe(ur#h+1`zD1l+qG|BFf_5)46Q{5$&EOxO|?JgvofDH?3Pv|w>y(g{)eZx3~0jd z-iL|NDJTs?0g+a^K@kxVP`W4G-8m)*D&1YujE+&#-95Uy8%K@*_}Jt{mzb1vD&DB8c4Wp zo@N}^>_ZKmF@37hW?b)?sEOJ&dD*i{Kw2r7GT{2jbYURA=^#z2C|X|gI^rfT@|Ma$ zmj@iqL(24^L(95O5pacYx+9B~fYtmn$v9|==fSi(I;Coaq#noDeygjG01ts#kpdYR z>-n=8E$zqm63LCA+xvfVuS*^mBumx)$AcX0AC6i)o(%siwZTfiJxECCugq!grv0YS9E!YYZZ?&k*z0G{Q3B3XQwh}F*vl){$=7(?U+La#2B78%mz{PF2ns54W65A_*;?G%$ zuhuMl+RNoyR%t4X!)gB#86tsB(`pC8NAy`(}8LWsHZ z8b~bz>5^Q-LnLGnznQR%fTx?p-==DPz6)%Z6D+Po2nhP2MpG#|JiU+VhP!kmzxQ-V z+FJYVCp8wAjhO<9haEfjg9enDSRXhf6{Q|3M_P7TYK8@vo&S#D)9E+DP^bmI^{xDT z{`hjCYL96*miOFgf@f+UcPD6-vY3}IHRKD#dfJ|Q#P2L29()JSFbH(u)+hWc?7&Jq z;I6?#+zF9tW=*|f!tz13()L7@5k5}#TKC8ej4a(;pLNgstUV8Q7t-UGstB{!-1~vc zMA5i@kDXvOi_25=KEmux@51Ql(o4+F*$aes_8X_vy{SbXif~^6&u4-;Ry)`kM024^ zSG^w-p>P=O-S=1&Tv_e(qf*&5yTrJRuQ_O)udTNyLhxP@h#|LDM1+U)G@H9vIH`;e z%dP@AUjHVLV7lsun!UGpn(+pAwN#tqYADzPpXpXBBfe$*6--^Ci&Ct7d+^lfDtGefX}jdeH!~!uYoBK+Sf}Y} zVI6h@s<(+wk%aZ#4(9?z_WDC{i^Q64zk?B0Ad}zA*0;FE*Up0onu@ulzdP?4BQ==} zdMr!ZR6jbV@R@|Gx;?M8@395J#WY6XGCZLAmGBi4WbY|R#F{(FcuO{dq1I?ma7ALp zFmUP0rpD}R_Lu(JvRq=~ZB`J1>Vtk}}`-MKnoa zKd59(;_5Wu)T#5lx84oH{tDCjRuRC&WBh?BHV|)NVU=1Ko(t;3J$fqQ7Z{=~ zd1?$Y4UnAp>0SJT57>|=G<-UcTTy`yRdJEx;-2kUswkf>NbHsj)JY)$ZsOGYOs4w; z(Tl^_QeB=W5Di!b8}<5E_$ zX5n|h6eZ8QR<1ZA^4xQI!+z>y`kaKtsjiX7hFOMzazwQ*QcQ6BmHz7Bk9#+&e^>K& zF?ZCVh>iOI`z7eL6z0Q3R}sd4yHwzHca6&WB-F|SYMZ(Ck-RUJyfZIbPNy7}{J!g` zu_F0QxY^U&b1Qdb8Rw0X9u>9AmMuE=`7A?Wec)@@L&_Rr5$VKke5g{^U3&F-x5g!&vbJ{6 zZf+w0oAXCU!kgc0g5jq0Z~IRzn-1zO?~I|s3VA-gg=b`RAZBA^@Yeh~& zQ%TnvXS3JIlzYPjfKK~Aelx7AAUv>u$r20`d>hgyDcJNs1i4xkYF=Ejb&nDz(rGptzJJl-dnkRGEItVk|!%o&fyl)fMq3cqjp98BRSNBjr8Sb z^!TW5p8!?r@CD3-snVb(+&w1=y?@thyDjhL6JrSw6aj;@5%<4%94ep3o2}Qyu)A@A zWz~EFpg!yj=YVeUbUDt=h$fK@rYlTZQ~+Cxwm&zf+y7dtGtg|@+7+1*$#*yS)c#Og zVSbGwUbh!DWi>;ZFe=%FL*lo>25uea`>dxY*b+dRx{Nrh>m zA$Kb(l-K?}`lsMB@`0gWP!Wa8K3KzBogSM5cVDpTrju5(KECwOSV6tD>k(g;;i~ib zp7r9>4kxMGh=H>%MPE(9l5G|ad6xcjd!-S zH4q)jY#}snRw!tIVKS1dRteVp4v<<_vo0LvS&Jt^%-!<3r$qI(zei5-LWU^=hvI8I zq8yWO^umtpcQEHS*IHqYGwHLZy^^ozC>eAeJ?$z~cy^Ok1@qtsntBdQGOLD?W*@fY z`@+Ruhi|HZyA0Fn5A1t4^UgSd5xzGD;xR2Q%n^(8E+=TuR*qLdw{&bXK#)2&JH+EDvlXA|I)4$ch>}DZ|+(v zbKb$w=KkDmZQ2|1S3eIQ2rHpYw)tyuR(U!nDBF2#hEseprserz1a0Dy%@+C2f2kvN zPBYt#{?!X1*0X1lSKnOPsISI5R1^3Th&hEpVI!WcEaAV$kU`=XgjZ*VEj2yBhAcTd zaHl!_8PyhW|7^g3uKQ{5VPhJqaks$*_TcZYHF`UGYO_wW05?|Eg9lJ!(u;Qy!RgvF z%>f@=m-l3*&QEljG4{*I`#227;uhH{o(cr^xL*qm^Szd`cb=ydU2O-7g5d7qToHAo z>0JoNjgy{vi{bXu3&$3GpDhBAUb^R1{q5Vl$Dtimy?XM|@szj*>hVWbSUpDsT6XWE z)le^~#YvvGUfl9T1dYe{_S@<-Uiws~w&&TL?R_4apSXYcOWO3kUq+y&sARqx+vl05 zMOWsFz3MA{QHsGS^dngEQcJB{FKvXYg8@k~^(6Dp1y z@*ilfeB7{h7}bw*$+|HyZa4!|en|;>gmd@^l?dX4k8x7V+_9s^wS?pH{oHX2YojIq ze^u$nN%C(XOk@c1`wxK*lsfQZDSr#gfARSx;2ZvME{2eSgqFeG@T^qA{wX&aYSvlD z=L_Ru*2o-PkGg-qd9aEmc`*P?hEKkvMiu*{;%_V%a&gscIPl zI7)mOWwh^#y^%B7RSd6WkRd?*3af4giD|f!-cN<)Lw`P-vb$LY9UqB@vuqCnW4~Dq zpWj%GR9n2d;8DAE0G-sbbma<*!rk?ojxMWHFnD;suaiY6hesUh+ z{lR?K_ohJ;3qezVTeIAl;MM<_8<=6C))!Fp)px*|yZ>6~us+sT)OQAd!RIV#a~I{C zhC?rrgzP7i5ybJ^e&tqt!>#+3bX_AklD>DHPTO-s2iZ^CMTu(yr*K(b$vE>(r2xw$ z>|xJ&tG#gp$?JY2REOGdHg;GDEHP1p3%0i z2Dq+{|4buUZwtMnIbw$qlpo0 z$`_f`oyDz9Cs$Rfc`cneElJCBe@YBu%bK+o8z-m3j|(}MP1EU% zbe=~}2by#h$Hw!;wk;KvLS_Rb5vMZSd4y612_3^fhGFekeg_02%*$cHJ8gKn^{FRe zhJ@#{AKq<)keX9FZ<2IBc!^*7xcy}crdWWN`S014_N^(DLsU=K zf!fy{BY5B$a@8&)pK%S76SN7gid^}mYEN3nSWr^zl3;j`Bp2`v&XRay`1VJXf<~EO z3c<|$X?TF){Nb<>ea6)TamI~Q&yV6()WB8k1E?2=FCh8ZOw9kPm;rm>2hiUgv-edP zB^W$|Kun9NI<0{Kgwk0bLfn5{XqdHh9r4wl!~kqn@8@6($n0j*;co7;rA=u*9wN6U zF)p5NP8Oq)BH6flbI>U1Z-&J|DA+yvIeE~zH>gGNbGI9JitW1S5T(}Ik9_B`Z+jU2 z;4LZ+!Z%_tHPr=uq+*`w?ty~HDh>nphl4{VEH>K2Aoroxvbxv&;Vd`F#^aGfuivOO zCLX#w*c!bV77VAZxB0SQ+zVR0bNx!4Z#(g%2PK(27*TSsjcu|q;h}L8`ENDtI*8kW z|6yCuAY!5A8t&u}{ybIp$a@XJ(Q@7BO}0zu{1{eW{5LXjn*MtPK*>`{JyzVBhf$mE zXXm;1BSCPyzUkGoI*Or(kY$k;`}683;7@mqljo45yh;HH?W`rh7=IXciGX6aOOs>T zlO&ott4Zu2U$n7H`|50Yf~F6M_dGT+@;%L6vDiPO$EuRgR4AZs9mhfL&&c(zj6rW` z`FvWn>W(_DIk24}AqpZtsH==BGR9@({bYFF_E!gXyonojq%KONvFp_MTbgk|lduK> zB&{29eBH{}gU|`r-KExGQ^{1cKf`R|(i91lnhi4O{XH>Ly>x0k@B1^A){$?eEy1w@ z{;J8Bc3pxBZlA(hGJNNEA~$yZa4@*26ilK8p_IC>-(<=_Y+(LjwfP^`m|zVbn|ScX zv-RN*PA_b0wi~Fg{J5S+X{4Wfz3}@nE!TWZvE}ow$<@W!^QE8uN=*k7))!kiJC%UK z6o1x?pltjYf1x(VemEa?63+mDq@JUV@Pl4HzeRhSs_<+8K@tv!kYhl;<7|TQ6YSe= zW5F&mpF#bIGDsWnd%2L%saC}A8txwL=1yAPdCRM%(^#S_cnG-&)jDbS>*1;kmB5_3 zP;zYVAkV*YX3JO4_ypsPJ=;lcsM_Bo5P5IXR#|iF@2s9a8=!j@Y-1#G7ke`qboVs% zoRY+|a|fl;R+jWfGc;Torjgp=bMnyrWaF6c%Gp6mUs4*&ASna+fTo^|QWn*Q-2SIC z(z2HtVTT;@mUPW9ST@Gm+D7}^)1H9ogN>GF3SNFLI$ z|23r_L`+aO6D`vQLw6+*MXOrV@3i?iS_QAa=hLC}XxW_M7`YTCw&xI5SK;OD$wf$l3h@nv7C}4iD#bgVc0QHJlkY8vJ^cEB@g1lLi#YK&|hF6l)t<;@RRw3RG76rNfAm3up zGLkJ5w;9p9c8PLeaqyY@UNX>Zv#oQ{ieLEGxg(b*;WH=-CETrrL)! zhn@;44BQ`1@DWi4gr*6I3K*-HsCGxA&%u%{=co?nD_{4%W91TE_y1EwQ15|JwaKv+ znrl>fJd8uEWF#&1I)1vi?j27C(MYrpeN5fY>6yevV!vxniK}+2sES0)RL>k@T9>W; z3MzBjA>Jb0kiJt<&1=7_f<(ZnlP&;!tL6mop47%ES36~rUi>*1s89mT;=eUg_)LWMerpxGd^*;CLh6@j* z*0wV~>Gv<%UuZUMZQ&i-34_mm*6h`uX5B?~$*zOh zN5W5SG{Llv_dnR3VSf3wsBvs(9v>S|Hb2b&zn540IR5@mw@uD|B;|AqB^la?FaA(} z&F$~&X#spVYI8c@revL4NBFmrbT^{g(J_>LcjC(pVoPQfQkr+ey0)0KsHS?5^0ud7 z-gaEL)i_?PGIsmUhw8*~qLP)HOi|HwxUurT+p}b2{!a^l+3hHatDs_5m@#@%~xGJNxLOEeYd!@Oftn9SQw4X+FLcfAhT)ZBG$nFT1{lCtbUGCzc}Ih z+bC0dI_9~jv8kdp)YVw+>hwi&PVj+4JMez~MSf*URq>v>SW&ujDZ$0OHNEOLWs#3K z&>#C3vX{J?a~Yd6%C%|xd(nv8_2=4`s+|W2jEA~tQ?(_SGq8dwt(Y)DU)y$lVQe6w z)Q?SpCE;ja{a&d42lFebYeUqy%<-6~_;8`voMAx*FEmlZ%4 z=;)+q=m}^PXnHh$vCs_hyAvz}f9VN;1FiH#0U71sxH*!^yeen}>x9L0H{Ks&R7OX( zgYA0xmhu(~`9o{tINJnwv;_AFyvj+9K~|Xh(zH6R^uGObMgI054o)UaY5?oko>m6j zf}59J`H(Z)3BsUmbm<6+dm>f0RKe0fpJv50Dp$FX5p(n8!cYRnGvM6xw>o{knCplDMDKJFgym zS7bC%RtQE}fsQKu=_X$kYjZ0RsYSB%17<{Wf9uDIGt$rT(w1F`G6`1g=_d4*@WwM! z5n6vBZ7+HZwjzy1VT_70dg7K{Q%op1WMe^-mA1uyoH?@Qw)dn0#fJ}<*7eM#;cPoNWnaesSJqmibWI}HE;g_{aW_l_80*0F0 z`!0J1$|jHWOZV=no(468dDp*2J=f2hwU+C34lDdTzorS{+>7{yl$FIq;7QR`53}eU zNw2h?6nI|jOo)BV!~;m90g_q$vHZ8RyIA@%WFw^+;{e_1qEVk(#HMy3yP?!u=3wu5 zM7)2NO2%pzn*7sZ%P)DQpU~te$#NW9H<^h$>pt9AWaig1QC>KGuge_nwAE6mT*tC)i>uX(zn!fd8w84jta1r#JEMr zSs#t?z+`@bu`gkIwyPQxCH)-amzp=_X#0-7FFlWVoE8#dY`0%b0}f%FDjM*^e8KvR zo1D+;tkce5>q4s#u`ki0l|#PY{q|JVb42}0t1db7Vef-&RYCmjhmJ`BV4ew)vm=$2 zk_7JdLLH147eod!eG0aM|7ZuDM?1iiWEU|X;<8^7-bX#55)-9PvU?RC)Y=wNndd!B zWdEmPqDbS$4}S5iVH4(m16HG{tADHa#hmE$Cjzqy5Fq$Xm6~F;GF)dvn6X4qt!htQ zAaVnlT$j-=%4;-YFkS6jE`NCwEja$XxtyVgMBsV-T$Uc@S6fZ{h0}siSYdJKC)deY zd>=EZgG7uLHOP=G&wf_Uy6SB*4s1d42vOfcU!|_b=Qm~CxSUYq$025Ct}Cj+7DMl&5lx+xeq+G-3!_`fnjiCjCR_1*p_!t$MpPYvgas8GL^Q65TMr-oPiO zv5nN6=qXxnE*-L>to(EtZPU1u%{yI`UWE2*?p+wSQ29nBQ-^F*ywz0_D7KR_rHVv% zqabWwE!_5ZnIK8`C*J6S=Axw18SH_tWc1;nOW6A|T+#bn*83GdjfkHXO+x{K@rEgI z;%Uti%|j^9!mwXe#$HQ-il-dObQMDy<`ip#O;dHq_Xgkr!Tz64DQg?~lGEc@@P2{` zbmLt472=K8~}0LjiPe13i%G!TRJRL0kE*hmnybbU+D+xoa`7s z{?5gjwu~nGw=T-M?KTB{o4Hja=ju=@+69`{v<`7C49m=zD`v;VaD$h@x+n%Jl%O0t zAW%7S8Ay+(Mf)dsPNafi3dD!B%63@sdyy?tv#VF2?8Eh0i?!AntYi~I27->L#Ouf@ z1r{CXXB2A*7-wh$o757vj7Gxcef}^kXw-01PBj-rn(pS_*Uxd`Oly4bffEemgZZ-SGaHyifG`kcPYSwp>unsm9GSVZbw>ag6g{` zhxG~}LaJnf*XwF&tP(%C`97Lsny~rAejCPXopdgB58xA8S-+7kRT5&0P0ma!E8!+u zkoDhFW8J=smY%+%q}By3m)=jtb8w(n9kO21Me5v=C934!@Ict+*C}ywU}4TgIxOb& z$;GA8!6lS<@O1-KZL8{tOWF4YO1WJO>XpQCl+?{;B64L{V{XGgpqHS1>kYbF9UubQ z;CgrIRh{+}%ERl%Krnqu!lkPnALCgt!Ey!*8;G_6aeFsyw(OPk{Z$5XmA;bI{l)nV z@KVX}7R>$07DHbWOm3YRy3CVU?9Ex(TY%SIQD~6+SA;A z9i$j^*z{)`D?z`M#}qD{MjOHds2gnT{Q0^v%zGy%jH^@#z6Wkv;r2x{%kjWj+nDF8 zuj&jyoIStFP_lLyO*S3PIW1Eihc9Go7c@<@AmKxBEU(85UMYSyT zlf2u1CScZ0{f$?3*`XN(oc69&p|3Ym=WTp56^g7V@w7f~&>*0qIW4y4+OQtmQvI=P z(Ymz$7wf&hQ0jIKHSK$o3Pb824jm*CL=f)#YD>^N?60@V34b-0+LGvTFD{4@jYkv@ z7$GlF5J_s0h5|_)a|&0IyM*xM*B5f17-i;bR z|JynGBr9m#gcoeY)aw^fgP8=cT@iWO9#gHQRP@&jE&EYT*QjDxqi8H^gmUzj%X23f zxF(8YzZ=M}<{K*s<9-E=SV$NMCxR6%-!vLKPTr{My_^PrT1Mhvp7#iNREnWW9k*C9 z1DkG?fdE{8G~8-wGy*iP*5C3^AM%Vbi1@~yP)Yr^=lqjRTe3w*kBvC-#II02N%#pQ z7gjqGy8f*6ogc6JmDYsxEYYyAMa=VhNUAD))e4kH2k_HSx^yhH{gZ(CUdh#Iq5I~$ zf6As>{QORrglxu;I?gskd9wT0W}Px)hbzp$^{3xe0LW%w@2l>D{-u+)Yo7;Vytz(( zLyZ!od&>^>?IL#nru}J-ThosIl9C?Aee&x*r`po_P%}Fit2*t*TDNA7`pH1KUrk#j zbEk8rN~$$D3Fysr-dTAuj!d;~y3)??xjn`3|M%Sj**JmMWWPSj*i-1Op3yjTDyYoIuskj#rtL(V`(mMZne^NWx`akdg z$QUz}G{$-|^M|6slSR^p$(VTIsz28@ZPlT=Y{r)OsdwSi!0%)m*loJmOLPu%Ucmof*5i!!pg> zA>XaV1z3;5mP?q4|8y(kdYT(YU^64C!r9JC%%Vw-!+hYgFJf!@|Y^z z3f?te;wldHx*F=`!b(K-i-k@g{p`^DfR(t*LeA}r=#AL=4_2O`Q}Cjn@$wq`EUfjd zf(f>bdt0z?J~m}`cf~T7^0WhAqLCVc7Wsm9aWL!#o1WVnt$2Z99#p1+e%iSi$WWbk+y(J;K9 zqo`ifmk?NlkJ~RepgKVM0uTiB?%q&{+aYRuX994d?WPm4{_MtwyZ@rys(xT2O4DYQWRsrKZ_rKc45Sop32%q*gnW8%EP9cd1au zR#wq=IA=DXVh9FEs?a8A>o_ymN7kj4n%?axkM^b&RUXwXRQ@iqzbkpbUOdhys(!ov zfQR(}Ej*(chCg`~-@lbao>2nrVtI^UfscN?>OiO(a&GzZbIm9g#wl6v7QWd=#rB#2 zzI7J)t1)7K8A>R+&Q3p*+KhrZ#tAw^TZeSz>7C(}e-x%{PqW`Vw9T26Q{MW3MoE4f(lOF0wz7 zPz?pa(r%p_cfRUQcCW`sCk-g!BOK{#EIGDyC>*D9y=)p^$Cm!f{b89#4}VTp)B_^` z_HYkW0CmZxesKy|9Q8Y_WaQo6PyHKAQESN7?49_ua;==kpUX=zIw1-^p#}J$=sVhj zSwS1?vxn3`ImS^!NQH6C(9gHs&KNj(VXJ)TW$huw=Xmj>lB; zJw-4V)L@!Y)7MNkLUPH~uzmRxUB4?p*+-eCVT*@7mo^HAyBOblC$A50SlYO5|G-4t zU4BU(*_+OU_o{R|6@#2LDMNlSKqJa>)10Bo-+j5oe%;nNE%c*~>t*Jp+0F|NVXE)( z)i{%Jz&QO1GfU>}?xu#}ay|C&uTwV8P*&G}Hyk*_QaMlbGwmOuRFuE1O%45CT< zCG+HSpDc^?he-uX1`Hn@bkdVl6xX;*Vf=Ug3%u0EL}~$%4wvjDl)Cv$Ai=oD0U^9M z6?Kv{KHG2hs;dI&|EQ!R?TsMX$Z30#!15&r?F;4y%XREebj(G?FlUK}tgV|HW>=a- zE2dqG*o}xLA{F(F$tZ`>4~KKz=rF{$TYzZo7*Cr}ZN=ZADe_@g@#HGW3fan3>!kfI zLDMpco_0T76F-K_}2b|;7FD)li#A} zMKY_MqQ`2Gt7h74V!TBPHx&$4>&y^n?4F-6FWp8F*KU`sQreD6ibmL+W{JABm+V#{ z)y}x&K@4gZ?u&8E{7mW9x^;bBhQhD?-7eFqHq#?E!g`Y3DBiwP#1yg6FJg`kFOyVw z>BYLBPz50ioK;jGNG~!q0NYD)mLf*xwoI@J$THA@&%v5+?1Ht#|!Z|3%PJ7v=R=>T#aH} zwRBbNFN;EV+-|3?nYnu-ijXUFSF6@akO+8uXxmMX?OtuKHYhjy{yDFX22ePkvbG)q zLzMC^bl)T9=y9gUYO1v}^neSzAx=exFP=@&jC}>$+T!WjWykibBB7 zG4|R`U+2%~r6&|d;p5p`NVM>dOAL|Lraf<5t~#b_^tv_CT|l~EfvJEV>!n5n;P3di zTmi;hQDQ9t3w3WALL9GR6lz^${kf=3E`-L{u`R=2LTHGd9kgB9%b*Wb&Af@gC4PIKp`1nc3RD{kXydt48q<+`0a z376SZA2uwRPx>e&O})HsCw4@Au+d3BKKU=eXd~>8mQ!1^eaWb#1S^z@c8+Kv9h6mh z<$2FD?Uw|$l_gX7I`lE~%3L1&=D)qCYaLgJCi<{g3CP!LgXH#)vHV-1tld|rE+=fU zK~o$OI5Zl-E`;dIy)4hVDprhtc3&LXZ=PH<@c>JdBgFVWB*V>8&^=>qze2*4QD!eXZ+50j_Rjja)ju#xG@f;{lvo67OJ{ls zIoPIY$FW;}N=^Nc13CBR6L`J{rg2@s>0bTo7KaxW?i+)J!BHk@4ZPIflmAf42Fmxo z_nZgo=46Zu$IlrtQw6d_=ITZp+#OK1Vm{WWUOd*QdY{zVWFo7S2JlDG0X|G$3N8}c zf+McCDDGxiAxECZ3?eCSvZs0TGKR5Vapk+e&5Y$pJUx1@+L!R-6RFKSqiTQ&k%34i z5r#4`FPFkSCb!5xR}3TDD(s@RYNFq+?BD%o_tcu^xNi!$BD1mK98NlAqHLNebHiRa zJhc^IA1*0tutQq4u2fxd^H1^l(N0@Cu73^jDHt^gmlL~rNU~O=AHg|PVIFccyyc3tK$7c_~rm_Lg@93K|s3& z>Zkr?{^H9JolxpptcoaGs!&~C!URf*TH1kp@|$pGA=&$BB@!en3|RWsEXEqY{|&Nt zjSoCyS1p~EWLl#~lZo?F9i|)_JWHLOWoxG8xtP zMP$_Q?c_t7IlX{uDZ@UFyShk4p>JKa!+cz z^qAL9F8uPBVo?mts>|2ANO==6_mZuGTgfK4QW|GEs(3#bW=UU*)3^5oWCJ2bs-BlT zu!~42uWM)e#CH#E+syDXoc55BRx ze{E$7yW}mS!gO&&%#MagVL{2ZWRc8W#eFB`yz+f?dZ~o9rp>J1TeM-@qJem5JX8`( zQcO_q_J#H3ew_eGw`q?iJ>1uCFQEUl2Finbr|lmj_L3e)b7Ek?`~G@l`9%|=!uRQl zImN3CCUE^XiaEMNhO=Mp}l=IE%?gyA<NYh`#sd_2%fnh4@gIBIB&I%eq<%0HpW5)AqUG2OuQ}C92jicqVtEOF#3#ebeM-&Zs(>C`i_`Dxq9EP3V33UW*9p_I zln`V7mTXE|JwUHt`{hTS4IxYKSW&0bekMH)Y4Fwexrv; zz_}jgZD+SLGX%rye))dDfE?m}PDM3a4rekM0Md*;sFV^nW4D<x&(gxaF0@Ux6&d zO8b~54aCi}F!VxzALllpaO&uv`%?#9(pMo)H-?+n-Zpr>+H$_*nOeSM)*P&&_2Fex zK~vDSrlLtSot|9eN6mTAd``riiLcs|nH8^;??146+~ZXBA&XuubrJLtV#wRMDf~_5 zdCK6juK(vx?Qv3}c0-(h&lEZTI`5rsNkr;MqTZrVsiAjDevel8m@3B2CyA%_8XaHm zjp+dmYCu2%&8UtX^Js46MGb@tr<5+ydH7xnG`ieB321>S9b#8%1as2Vi53`O7}d00 zy|Rqt)LiIx8aDz9BMXWV{l40rdllo5=47w&LgP|5yuT$^wUQOK@#}DZiVED7rEAj?j{&%u<%1G`fc@*qAS^F1KLdsTYCg^@*e`g){+gO z^d4bX#wS;P%%r$ct<128;E&`*D8xv9PCCMxXC@8_CHsijc;SjEg8N?P+ucn5pRa@^ z^op4gN?(Du2tP&|mQa;ai*D~AA}KCuu1~L_Z6yMwS$}#hB8x@(oSHbYO?QXUE2lb~ zE-S}7AIZz+H~rhXHh}?nt+P~=>y2}32|74aj_}u65tp^hg<}m&7xdk_X!%7)<-&6R z*(L?GRYH!_6g2tNdFth^mM$pb_zBotP;rYX*C_2BHpX+(*4>Ba(t-G-XpneRttpd5 zuL5_S`~zpAAu}6B9OTJ_kRLPKdHH-dpu5cEGmn&*L{MxlJsOIBnU#;*SJGfxpY)@1 z$FW%CS7O(%#oNX>rG>%^Qv=&!ExN4e)pM2gb4DV&J(pG?PIw2`$*iRsUT2Ehty9((h(~CG^tQQ~%TuAsb03=xf;x{__o1N>t1bfFp85OhXSxlt>({xP zq$yvi_5c{cK;LoklhG(8D)w$%R9^Z>8$I}`;m>^fPaZo1y^qc0Ug<1uMhAqEM*2%q zwfA~EY;H-XONOewW6p%O{(IO^vZpa5WUs6nW2%a=+s8BJcUVOEKe~4_ULMuNJ(JM0 zcNKe~Vsgt)4p@I-Fja8K$=TMFE$6fCzu4~_VZ}pHYDG@npmyui)D=F(R(TMCy4qA7 z?);U$TFq8<(JOWN=G;TW;%C`txA?)3G0n&4g7p%dY@B7(8)XIsw!4 zcH9lm-j;N5B4;_YDit=hQ_%(!%jIOP)RE^M@0lwmsk|1osP(;0wuN$AYU=8ZnC|EI z7uIK{a}*EJfYl;hXDDk!6z5-F)fK~px2-SZe*2V3;XH^k*oBK%7?+JJ1?2W}wMhq) zB?@0V1&7mhJaY4B6cj|~EYG-|Xcsv0S-oYv2Q=USUrTfdUkcyz?QY{XY=^DRqpy9Y zqdGrCV{(yZTxMW=TT0Ha;4nBXSTMzy7+`>BRlfua;S?EpAar^9HifuG-C-WG#}2hEJ?X%~vMN7B>Q85CNKBxCC@|{A zvBy<%fb3-rbq3McOKrJR@84F(*69y0XS?ZvwDJvb+_W+d2kNhkwYfs%$A^4#q;QVl z8ew$%tDUxL6xUXYQy=uTeOIh@*oEN_KK6JI_1%b(m$u*0{?@n1#yXY>3zKujI?A{C)q3(DlTYlz|yjJpzW zasC}tD)k(^k`WmJ4bQL>>t`gzfJB`JY_0Z01ZCnq);^>&T_3-Hn)al`Ngy`Jv%R7>j^8fG@Mb(*)EH>_EZ7GDn*q7Ft5kM zQ$!{~Cmzyz51h-euH&-U{x&}sBWkT|g6ix-Q{mcE5Q9?9Mm3Fl$cRR|DTc3JUQVT6 zJdL%CYZY{RE6z+=a&fUFfYaNL52*jRnc zf`8(Mt_u49?0r|FfV!kfKk5#p32eA@jH`NzcE0N0v4~Ryyb!x5fw#W1RDq8)3eqimj|_l0wf zI&kaL0a$$q`U}}BL3ue9ocqOpv7f3gyy)A&MC5w!-~Bg8axlXewG*WBU)L}Q6|Iu9 zo|b(+q2@8DD#ylWm`8?H*qYAKFLA4=AHx82X?NQ@-83QLp}1^UO(@}C32 zL`4DLQUW~iqg~(noAO5{h}vfBVYtKJ^2jfA|HAasdS(^ zgF|`Rds323JG+9_H7H{>h-ww%EMaq-vJ& zTY`TwrU_BBB`%$s0-{7@`gC;9q>dZ0{kBs5$*wlh;Qqy@knDiK@BWgw8?qtyZKB#i zw-!o@5Jfo+pO+$b#-6aven+w8{gJj7%3UISQ)h#>^ka%O&z ze&K;?45os$M(0q0rxPc0noY@U(!PONKN`l+a)_AzC#CXPq6StzE z`)xym?C=eA`NsK3Rfj)!}2I9>c~b@WnOA*CmM^$XLKxZSo@52!W` z*ry4!22Q)evv{nw$M&Qssp}`?N^Ki=+y{yS$mfkDTuRQSup2X5Z{82yD4WI;m=tD^ z;M`Y4ylqfKHsz{)*Lf<<$cxb<^-p&8k~|6h6$%#7ATJ+j6W&M1-=jF3J~Y3`g>5|F zemmF+r1We3HO$z$jV|sZX^edI7k+JvVo2>1UmAF658-(>0sB7)~s7T;n%D+s5=7EUm+EdUj#kld_eq1lrS z`EIG7^~8kXE;&G$qR0}(Q4E6#i*WOiVLU-G4An%VXO$V5Mm2cGjFGP(sLD)|ktXPm zWUGhylUK!Ko@kUsZ_9a@cs4_}+K^u-^b02abnWS%+S^8!wAI{{GDR&Ab!+>V*+xsJ z4QNDQA91$vj9Cw`ccB0MTaX@ujKQyNxPiWCQzk-+{9`lq`_%UEU74XR!HsovYQj2P z(*4h3@UK${8g7n1${X)em|G-Su6uA8YMw(rzqJ04A-WXA)Dh|Pa#W@kU0k{r6A@c^ zVL-J{&zmg~@U(UU9r9E-B$zG`nhjIJbQ==+Mq;8BkKt^Y?WJ_5is9ItC&*pql7HgBnAM{MLs?Ux7+JO^7h(_NVRu5q3ddm<`SAun({rT?2Jx%N3E&Y&7> z|F5TR^Vogk`bbtb`v1uK4xpyC?fsJwAan$zNDE4_fFQjS5CNrHQRyP>B29Wt0yd;7 zD!qw-UJy`_4hcmNr6awIROvOez-mLxZ1jw%-Y{a%UVLftdC~ z8gx&QV<}FFKufU#Hk^M2my=uaN(D?&oqdQ$0{%lBUq@ZkbQs%>;g2Ql4Lk-l#_20m zfl;;J`&Pp?pdafJ7Qj?5V(ri`=a`E&FWcJCh|93hUM2^2U4Xm#PM26x1vr(-Uo1&l z2o3(=vgFn|PYS~0C2^eHgaJNA z0f*<`ySeAr&|Tw{nW_86H?x;mNpI)rXg%U}^cx#NAKPBgjb~g-qVi?_Qt{L>NuS_0 zt!SHw@|)Fvmw%QY97Fhsy3bu{x;AI&kGR!)}mj--@vkN2wl6PYBBo~w)WKCo2{7P>xflkMSv ziti6$$$2S=-DBUKlPwI8fLe6s{QbPVybyuST-(`{m=i=g!=K8e_mwwIqT>mT@8YNA zkj~)5QAG@ti-(`Y*#}*@2q_!vo&Q(C(PXn($gkT2ammYM*4J*R$dR79LQMjUBT4?jwY@D~YaJc!kMp`X#iC^3`uK7Nb)wl}OvRj98)NS0Z*ET6RWh6q8I9m(>R_aCgp$w1AeG9E| z;Zeu0K9Xwc8>={4gL76EigAS0P(LHK0Z+7>mFn@}`S-9&WQG~3B3EVIunI-k+1sz3 zPHz^U=Q{*nn$hRJ9nJ^fo7Khej)Y65#??ADAXfn)7R4r6Kq8%y#^QA7#~1O)NdDB- zZ$f@O&kzE{leU$!J?FgCStf*(xvY!w@C#CyU&a^9h|ZlIWb3=j-{O6FD?MWFA5%5$a>&+G} z!#=f%{2rY3K{)Hg1{9t0E$QI)@fkDuujAV+UF>jyML{{GN)aqo;KdZrA~!i2W7doS z3H^cynrJ&xA10M~zh@L?6qo+_`mqJ$Ir7_C2Sp+si{-gmyq)d|fW!<7?< z^5aYsqteE_4qDWPQQGB!I_y+;Hws{~oK8XsLF>9Yhtq=AsK z!m$O$a!ijqeO`hLpz73T?6wwg(?2aMGg*)ij51~m&P`qAyOb+7{ZVNHT0(Ho^Sq&g z=hf1lm~f_F-Qt7_SccxFt!&Irj9akkHGGj->56`I>VUzp!Q4AEhF#2K47gd5a3At`dh}_ja!!3Hqp}E|VNIn>hX10$#e9$| ze4=}p>a9yu>Hfwm^7m|B*@7|;28)H6_lEp4VqSdYk-|^&b5i0U;wWLKgh4V-jHM`H z&?PK7v)h(NP(2B2mw>=EA(*XN6MkH(<-6# za~1iDiWLVbj+Id#>?SC(LpXj)hD)nRCzrjNWvBJVx^#&yvS$jC2})98L;7iDL@+0) z=*7?)I4_*pNob&DPNH9;pJ?3K+bAQyGj!`xAS+z`rN-xW9)6%5#r5G3&*Z>)9*)}< z!W#FH+C(Uk1~_+C+#O#$kKbJQgZu_@q^zh^keV%xYp(B+j0DL9+OOwD%aZg*H8FBB z_a8DJs)S(@GSc@In^n(7qEtvmqUDl>qc56l4`pwJ*};=%)$+C}BLMmwrm6Hi4j6>=p)!p9~3t4XhqdfLo_^xVOIio;iSB_|kF|3R~ z8HQCr(B#P22R(JA&Cu;yD}ctkn`#U_9kvQ?4cv5P-Rln+9__WGp`|rE8=vR0I{9>P zEW5uD0Fm{57Z?T2AF!aLJlJx{Q)QHR2m|*gTP!NFthMA!f4pgT&6L{H-1IfJK1LU798(*3}@clNfuESoLC;Mn$$Rj#kb9(#%YL&)TcXEBNE;h z@93do)!{5^>kpLth|e~am}7viSB?fUqME!O_(Ii_ZW)W^LNv|ELjdVKchvn33qpD` zzDCdL?M7Y<)hNpDZa;xuf-#?SrDYlSvS5F@jnnP>aCw=HeIMuCxAntxqnoGV4L`&x z$jJD%4FyDqEw5puNi%6-`$}K8c!O(a0$j(RAG_iD3_wssJ?kGFrR04@$yLylVND6X z?%6%}Vp4~Rs8JQIh`?%@CKh8zZj!-aS5ZZR@M`Cbh|nJe)VH~j%w>6J+48$w5Z}D^ zq47(EGLA1x)Z0U!TF8n-UNZIGj7BTwc!hadJGG+q3>Rl>ef{xU?|N#NUIjlNCLepC zqWwaX;0%uwTO(=|iXq#9fFXb=vu3PjVVR(iR;udN81zmjRgMEuQwHPwRb!SC0%>QU zVLmo5IjM+HHOj74?eE(KBQj@#%rxiIDmv91I4k?9!lf8^DuXl2a+^6gi)WUryz~j; zsi)E73l0Nf_Xb(m<-D?A5Qt#_G}IL^ez)TelqjPISyEn6)U(>CVR;)`UFDl6DiMw& zWseieU=+zWSv9mOGUaqSI`Q>s#cIPD`{{Jo>XI-L!)0ji z`m3pA`N|l_I*+GANN>KItIK%Itxam2>=AAr4M%~H1IHxuM}@#*@EbuY(7`@6#c6rM zV^ohzZGV5DJ4QIl`XrvvmPozPv~PNhhKv)7NEY1*qmBzq6;LXQC!hQ-w%`7=h5Rk3 zVZv&O!&6CS$W-3qLQs5$L9DL;j!yX?rlSQ7r#O_7-?)Ixr}!9WK=c;Cp|wG405e+q zZf2hYJUMx%wc_3IOsal=+mMdTc=@Q`x+)@&y1xHg{Mo*13k;hME;2S%B4J)-QQ3)qkjO}ggR{|XW+1STZGpir&IDB<< z5rfk{(rue+PIJl?vJpqIQp18Oc&K}W=Nd5!!Gws{XVgHB|NJCD%YpaEmjsUynogaK z@XST+iJ?v4S4^2C;fqQwnj!k4`*EL}#CEP$ca$;w3@O1=AvQP;Vm5CWSXGoen06gO zbZcAx_!;lCuc3Y^*QtHt32s>6D?#E|6WY0({y=3iT+fW_6R_VX8ZdlCI zEfhG&;4He+vB^YE-7jOkYp-BP+P|h-#_-^|W0r$kw)Gn%#EsEubF0(%h0m9Hpw_0awYn+O-kmLwOKSvO0iuC)J6Uy2# zd>2;*Bcpz-EjoV_-S-TLv_{n7hbGU%{KW_mKkJhk=n}Pm73+i>*>h8vQ5Y;`LH7Xv zwL0%2a*@IaQ0cmu!{qiHQYyu^s2wkcEizCsfZp!(0|i2 z1>cQ?gjpTAD6raDnQ#v*diDqbGu58#8Oe$&#E?UF7yUDG4J0;TgDzTb-~0|MGU%!(=h)ygf7TnvM1M@DF?PgdT6q3HkK z@8s#TG2VBFS!kk;B8WeyyTq&v9u~T@A&wmx7^!w>Hyo8;>9oQ8;@x4HaTXqC_73{h zj$3}Ngf2s@F2R87xd9hb-?egHtNId=d^%&l5MjDm=60}%GT@!wV+_6^A& zPj<*{)m3m%Vb-RAyO#j*dV`4U_J|u7rAnjJw}$VYJ{QT2oS>UMhj#5kZ2$O#6sPgH z7Z(nbHYlORP3or0KfGUd%goX-24=(IVvJo`A^YtWDCv(MY%pVu43z@3C-jUzw@MTy zR+VsvBH&I!Cd&)*#(3gZ z_1Cgq|M@M=53WFo8?4DQYF2yw$s{rdhKj`aX=N z(Tt=~9nx(6+O$e-OtRJK=Xu!UfUov8cB8Fl3%CzXFvgwrdUzq@ch;}L^-6h;(k_eB zMq1ONj}O>zfZ$FXeoHyrpSU*LaD`S4#d-`k-RteSH*(!`?N}(;KGO%_W+!Tn#x+a8 z+Kz{rM|KLu z^|@keOY+3iL<>UuiyOJMXk*$5GkE3Gfj0xERlexzL&M~(X@(_mQfsUPOuwIE13f?o z9w2laY0+n`WO;un8melwnv5b~>VxF^<{tiCKfLIwtij?sTjEP(zJfkLlS*dE7(j3~D%gZtX~@tTtGK z4^F(_gPS~mqzocTF?>>=Fud}O3gB}bxMyOPvV-Ph_gwQ8L|BAakzs_Py&Qk?I+N$G zvjQtzG#yJ`ci(1~UCBs=Zh1aVV=K}uyN$x3%@Ox^tNquCt)6`2Tcl(d0Ddc?LS3n* zAaAT(IIW6rjQqQI&1bA>5v{EwZ2X%$M?%Tm7_LhXQ&dGV3_ym|cwvIQ!#F8zj8xuQ z$BZoDh2;+f$b+b1^`6!j{_2ar(;qgtEr2maWL~-|_GVg{lSZZs#FM!FW_ zP5ayCJ+ICjmw>EmeF@2NTFriSwd;i+gF=ta#7wKI%P#(oJ=cxynSkNFZ-DWC74CPAvKY#J zbCAoBJmJpqcP2Z<$g@#!5$&RelVFS25KYlW+S$hfR9+B7`4xR#7fStSyP1k>KMZDa z9Ps;y#yuN`(NxX9f=-{np%W-*he1+jZzk|gIS%YG)KG`$PXUn*^H3F3>}mLcB6{p& zI0A0NnvlglYo+4(IY>KZ_xyS?Iy)hL6#A^{O0^!C(>=v6d6FM9A2IL2od#%PSTs9g z^;89&9xKpMJ(l13rsAe{0NMj8iA-SASVjC%W(gh1@Ns+8FkSL82EXfx5fC1@TF{TJ$E{| z1UsPzx~=nwnHl$K%Wz{m+j&OZWxqN7F0H|(r$Ov!w`>$GV#Zw`eaXJfSV7EaaucKB zKw}94)-*{eQcmCro-n!3 zOpvC)G};tTdjvQ7`1XicF^k_EJ^Yfjfe&o%=zZ3KG{X8qrBblZSMxX2kZQPhppld7 zWHTaq`z2HQ5n$fiSQAvY?#tYX1@|$KO7W4Ym-E?fLQjtFEH^nOx9%-piKW2FQ3R@V z;->3yM_QPttv#;v!0KGF*O11QK=zYAKb9an?E=0&+!i>g!$l9fM`rnLKPXAB1D^J` zj>(_&pJ5w;;jXd0WsFQ1`_S-ilix_!*xJ^%r`mTn$A72WyV%I?4XFJ%z_(4G+Q+&w ze7U25b}-lz(D9#p*4$RtP#$%UWv-JNIB4{}o+p<=y>CK#tRW|QHaVb0$Pq!~jVhvr z_clN~okuK&k_aQ+PyMG_UsTA0DQr`X6QyD%nPD*9c@NxYRc7Q!+LhVT-%|zJQ-dQ_ z@4-u|c*+zWGkK?jW_8dnY~$O^j4cY+vJzt_OhGFC+hx(I8lmPDO3q`dp1tA)`7TG% zhPp?-*cETSC48Je!=>`q2>0SLVwH#=<7k?-tX>_Gjmr#qTa*k|$R8GY~{Q!q_EEPWb&G40NTLog1@S1YTZD zIgJX!qMsn#(=DDzHJCi4#!8)h^p>g&dVmDPDJ&(ff*v!ANvV}-(;gZo4*PPcq#z#Q z`EhKc^aNVOD~8=AZIf{!gs27?6pF4evIAZiR#L(C{Ah|0AdEGil0P*9XXX8idw~^R z&XjRpw6ad>-v&^!!%8O4STJ%0lGt&#r*vse4q+D5N5*DWWbE z{T(s)%`gSVD)*Cf%l2K>1?$TZwzTN)4>IvR$jvCVdzH+QiJw*aRb1xy)y9DP-B^CC z&^r2X`gdS!lu`8}z5$jy?@Md^#4*(>DKIipP*yih@zsnF!IyRQ8RP@`$)6d3O$`*d=`FBrR zk>PcX7*CddD0C-T`0a`{JBUsR7hltzpKrkGR8r>h#}<1Jee{F3HJ8==>4(WRiHwTf@`zP#G&zS2G#9kB?CmKV zechxmJND=pRf!3wc3848DDJva#0;nD&kX!8-FkE+JF1+Tem-_cle~(3O2pSVuwF=I+9UnzFEglWQ73~QL<+n6C@rDNn8u1pd91F)B0fUZXhEf3yI7xa7!RpySJsJR=PMw*qFq?k=mMAH%df! zR|qOYXlp9oN*y4ji(_uOS;05SJTa4N_X0jZl#3}SxC0#5zZRUx6f=D1kfBBsb~$0b zLS%4VL#+&X48?keB9u~n!*-{sN7z`;5Rp+^+bjuU=^^K|7(gQmzaccdp_&{55F2?R zF9zsFi)u1^d5-{scz3odH@d9ZTG@{ydDD=b?d;q7FX1G54%Xr6!g~DW!vWBR* z>}G$pT`kI>lQhEKOwMjE(4oM1x1=b;pkL_+k%TZE#2oX99w4f9%g^gzxG3bVneRa{ z-hSO*e2l1ael?#7M8#N#xF-~Ee%IfhM+Kl|;D9X=lvdI21}8DY0-^0fj~%htfZ43u4PT^ zaxlmo0c{0b=*Fu6amK$hE1ntYby6Mc=urwa8)pB~MTa{N;;Dn~q>8HQ${N&dHZhX| zuS5`xDtl;5fWB=Rm?}*te(*EfCkM}uMMc1zrnS+HI7z~TcnM#oI?DPph8$({e(}yM zMF=JrUoAUwP}WuLGh{++fofic^5l#{!F8 z_4-auK>2^aeh3JS3`@r|hOD)FJoRU`b_x@QedC9$Wx3%oE`j z`4q%f-yR}6B_N7xyS*mZQcRc04<)BnAbimoOGri#qO=_SW^e0VvDEac3D%{A*>uD^ zdG0oeSHy&ox)mR<`Uk>!l!anhXwBbz#_}ll!|{{D#ZQZ~K?*Y~YW1{7_#O_OOQAG5 z$iz?r`!B7njAO#TPe21+5!4l_x^2GCB>wQkZSz|)ZfP}Ib!0~Wn9h?AuOj1@^&eCcq-Y*Pdpl-WvKBB_RV%d-g~DnS0Z60JKi4+` zTmssw*XoWci!;$?i)iq!qL|_S^d#Lmb8MQlfC?AwO6om>l!JAnHVUMJ3@0=-(cDRn zz4<7c2DqaO(!y%F#_2Q0^+CK4r-RlVdO1|*6T(S;#1|EqeBY5E`5)|dqsmLW8XH{_ zs$-IuzH}~0z-mP@@K;{wQjgubDVKaO!0_fK9VNq2<%gaiKzy$TyA2WQ)Vpk5Ti~Is zuiP()@*V8to`>d(2{kXjh-;ohVzI0fzq6fVvUfz?k?Q0pR4Ln;8;Ru6NlAWIjV{p{ipEK6OA|WI2*pWAc`?FwFf^wICShP)}S9`jW zg7@&j>_caJ@8A~#&!IE&csik>pHjj*+zmeR zIekF|D!S5_a1?NCz2Kht>t)5H$AwEkueGj$Svqd#2lRih<=~>%H$M5os8LYcSj~PibDAz7(E~|aZ)lw z>+zI+rw($VdbQExLqG~`btG$0V7ywoyK|>thc93AydH*syrwfGh@(p(;We;xOz=Qd z5inws@;d~HuV=yf&zA+fZDH0i6P-+mZ9Oa(L=2Pf9z%{;n4c@eYuFWx-x^4UZvsO* z@C=>EOvATSm~cg)?93AKug5whUd%zZv97?Qv87C=|XFmyD0zy@eZ zj1OYs0iiEk-DiNNVk8Uek5{>nji$~_NWnemir$w7%7}0+v0RyTKMIVY{ zJ&j9NaJxf`eyhDx$Ov}sP;Sa}Z1r0isetV^l;B%<8SLyy8i9Du5+cwp*z1iaSx6NI zy8~cTLjD9Z4?uWg={VMJ$pF!=d|ED&ajQhka_>s<>sX3mdPW#_!$+|&2(2@oKgtQ! zXK<%6-{Q!awQGNv%a#NZ<0OEQ@k$@k3OebO*xC`3NuHZgWR>`WJZ@vCo$oa{`o>~mZM#GFekTY&yHvop(YxqOD z>ejlC;agy&pTt5EP;0j>G^$Hg@xl1PtIPl~IMM9`d+LLsxv2XJLOoIpAXKvEn}uXk z$j&{UGf^^E?L(ZhZB$N!O!c`)Y=?atlAm?p{45p9MFhJh;UKTigkWiFJS7Dyasw$Wjq0pL=(POO5_2uPVP|Nw)qvl2DiUCRKKY+j)=f+MG2S zxP)s27!LSmu@8qDhxlG@&D+0CHH^^E(f~(G$6aAx?XPnG)h8hieLr6(hiudakn4zy z>6|*b$DuH3y7yDU+XhCU(Fq9R5nO|soeY{5;j`CZC~6d}WKaUeD4;swuu5v{Txu-; zR3>LDy5s_UmvdT3KpiK6KbY-EAD?eytEKG(y%Mafb396SDQVkASx#uB(JAr5f@CXT z!0l((zKj_qdPWx4(0Kt2@q#j+dw|U#sO#*Oca4SRc13CU4e*6*wv-Br(IDZqY>q(Q zXAf=Gtv?n~5yCFIn@=dz)d-S_%{L+Uj0_~)gPmJt$RE5&lEsTK66-O`Cy}GC7P*2&9P2KEKO28FB5jDXPGg zg&)+{okZS*mq1dcwCBSxZNHK{cxYkbP@`sr67Mqaq<~S|J?3L?&?tI%@RY7WKkmcH zYk}FG9{z3B7h`tzpDgd@dIKmQ!pVzuUMNFEg35{zn9_q_j*3hM#qrl|4jc_sFg#s; ze!jJ7@#Cuxs38`yhZaNAos7mckIYb=>Xb2dUFu;@QqL*!?M+XbjV~H}3lq48Ll3i^ z>`a7CsEPDkJzKKG?Qb2Ij!E=UxjPgP$<2o(8d-aIbk$H3e0^1#G=K|M?geq0*PV$Q z&8(n-Vepl$#rS~iN{C+r1$oF!)p1eMVFVN8tR3Kd+&ch!c#_5u>(Ybr2Q)Tck_h?? zAVCt<^PD<*u>O%>sYs2QQT|27v;{gpWI@WSwK>_FFx7by@If-IXBGSQ+#wwTTqr& zP(fUPvMdz?)Y8Tz9)@Lgd^`-jq9BzUrfnBM#HEQLeg+nJAR4V8$U1QK-0rTU+0@$ufD#clen z1rW;d1l;2^>FhMzcX+u{G1`n&m@v3I{XH5M9R`69tYsv0Bz4t(oIgD?C3Ea!_818U zILyuG`+j%+yl2bse8h<-AFfE24za9v0bmKe8R=V`QHP&74!%;Toc3uUkZGdI@ zdMsEvSWsADK9A&iwXBU|(dCpkS^hOCQG|fm`A@|%YL-fXJi=vow%^j>n5>E~a2x5! zPJjET+SdGVUp`D(p0BnEV+2T;>yPt%Oal%Oc)nJS1+=v!;BOc$W}Dp!=6q$^mb~eQ zk8%CM*VQNrd!(7cg!EdIfYltm`lgvC=agYufRf>Iy(z1s8NA)!o^PZIM)H|1wVNC! zbZ?oCDg;dKXSu}&3StgCvaT1eN@$$`aYQtNCMKw!qbk7n#ai~-&}It2v98PDOBN#Z zS3ws!>NA3#h&%Hmts;6{ub>ca$9x(q!fRq`&sYY68!+RMP$eF{Xj_K?1%E*4iqW*guB93{I`A z9B#!?O#R=xk1bgpUlM-&_|eP1caN89sgu@Fh{DU(L-}4kUst^!QF}FUcv(V1!lk=L zrr)P0!UtHHIXcQ{tCsl6*9zZ5sYwy{!v&Uh`gUX5z77R2s0jha9vB*QA~x$P>~Y{X zdn^lbB>Cx=fR|@1X6C2T!wobzfDo4GSlipr*bX;D1)7Tt_f|=t)TIm$JA!7?7a3K_ za!V~RQC4}9euy5ZAb%KiK`XIEM zuOWECinw{|s`$&l4jMtP68b*Eb-cDs1(N+z*b(Q+_$o>RQ=#G@T^dN`7>vFHQgQ|e z#sp%`TX5Uv1I3#QGmD}ApsA=sTwH_!(FZKHauWKPz$b-E5s<~_YA!xe_dPt<1|)_? z(^YS@t895hHYq3Nom@|#Bd(dM%xDHWwg;bZzhzkh74_7}2ryHf#i94vxI-3i#hrQ4 z+H_l5*Ylq1m<*SCTGQ0w(2gnN2`b$8BVRftV6>I`_sZNoGe2 z^3mYrMcr3a#&?-r?Y6x_KTr`0!#%j*fSICo_%+dLte%&34)w1zm1KVWgyl`k?B;z# zA)@gE^+-?ySk%dgaHZqUEQUSZ93TjH7qd$yUa5Hgz-a&hM%SSMCOp*+22O9-{Y76p za1Zt9?*~ZXjdmLzbJ15ir&_NaIfgWYhgDm7#%ww{H+N1dMKkIGZ#5}QkX(B z$c6*`ow>dP!56-OWr4RgZAQ+2ielPG=PFF3#6Zc($rqV;e83f;GyH+?#R9Mv_=>is zjT9_p)JVGOV0BC^c!?(|&}h>^rW{+NTy&R?w~@@#%M_dS{XufS71_RgCmypKUkw#? zRnzN%^U%JCG8In-W!~`a2XkqOR??IVz_sJ7jw=HVrAUgp2hR>_kKDTc^B%UyyZ=|< zOrwLLubWk{Tmi*@3m27_e_??5h8^#JyWQ+>Vzu+d%amq1mhy6UGXk%19jsR!&TqDU z+S>Bw%b3&xco&-}>$D3L6w*hjahc6($e-DKs{y<jGptyu3r9!Jzgly=GnQ0>mjslsv0LQ;D_ zJ>uFPlA)JCKaBrf=hvcj^8LbiCjnfL2ix2$D)^a49Z00g^xWkKlHhANHhB-#!IcKH z5s6bVlFy!^(mvRIUB*mxaOt(MBFTjw(o>GKd3&Pocp^Mm3<0nU&gS`lGGAmb5zqmK z)-j)3&#vMsC}}mS(-J|yo6nOy3iQOC%Q!|&!3HG0Lkyfc0Hi}tyfn3}<4O?dH~|b- zdWsGXW8%BBY*CGSo_@lQk{H)lGFf|Wq)`&zYR|RGTjRhb%EfxXz`{2W(g3{rKRSSo za0*F~=E59=bEoU9IXE-E*H>0x_FmIq>Za4RS=lUZIbejf4F|fce9NiH>k5}#57h!V zCMtBa-hyOBFZy5g!sOw2s)L(&g$|>{7}xe?eHLY4m9T)^>(vE1>%7olAO!WIn4R=3 zPk`u^3Lc$f0FfOGq}sg<#l^)zPy7uHYzs##k@@HCWDwgs{bf81u}<1(%&wdR&)KT} z?v*HoF~u{~2jH4lAE7_w_46qnd`~-=+}|%8t92ezl(CzMzp?X%D)qO}wIL`SLC?~j zg6hVOYGiJje;r7Y7b`TBDI$9wn+8pU_*aOOhBuuqDWx!y-*-OMy}<<+CeJ9~{S$qd zCz>Yw20*1-7F3bv?)H29WM$r;jN~-H$IV6+AVwg;&2$97pTA?;{aZSy|}KbJ)&ht^p?FHA-rmLlTlD)h;x(hWa0dow4_U>H2Bc2`GdA zI~0e;r7;u0xV+OdW&9K2aaKd6OibYUEk`DXUv;Ew;B^G_7^i+NzGzY~*SYksX%{OA zeF_h1ZK%6^bJ-0zs}B#?%MySJtR>k#o|Arnc=lZ z64KH_;)A*+&;9cB?vL^>VfK%t6!K3jDqr3ER;Z-#i}$mHK0iMK%%mli1IxaO#i}yL zj%AykoWlUjsv!dU_vK7dIRB3VzvUE~3qRzM!7Y&|u@5;}pI`}t#SY%ISl*tuA_eEy zv4m}foBk8#v{gmZOj@+CT0kD&zd|ch*QrU+hg*bA<@|aV-x?M(BEu3eFO&kr2*Pcn zfQ7%yEC3oIK{W-7-aj&M@C#3JCe?RG*?I{P-aF%;<=)fg8G4>39pAHZcC;fW_mM|| z3Q8K&SjZ25Da`^B%1{HZ`*@>c7=%I__%0A3(XpKY!>F#GH@a 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 From b30b88716e67de93ea1c97d9dfd02a41af5428f3 Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 13 Apr 2022 19:16:36 -0300 Subject: [PATCH 072/308] feat: add very early mod.toml packwiz support Also use it as a on-disk format for storing mod metadata. This will be used later on to make better mod managment. --- launcher/CMakeLists.txt | 8 +++ launcher/minecraft/mod/LocalModUpdateTask.cpp | 32 ++++++++++ launcher/minecraft/mod/LocalModUpdateTask.h | 26 ++++++++ launcher/modplatform/ModIndex.h | 31 ++++++++++ launcher/modplatform/flame/FlameModIndex.cpp | 1 + .../modrinth/ModrinthPackIndex.cpp | 1 + launcher/modplatform/packwiz/Packwiz.cpp | 60 +++++++++++++++++++ launcher/modplatform/packwiz/Packwiz.h | 43 +++++++++++++ 8 files changed, 202 insertions(+) create mode 100644 launcher/minecraft/mod/LocalModUpdateTask.cpp create mode 100644 launcher/minecraft/mod/LocalModUpdateTask.h create mode 100644 launcher/modplatform/packwiz/Packwiz.cpp create mode 100644 launcher/modplatform/packwiz/Packwiz.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 15534c71e..b5c6fe91d 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -331,6 +331,8 @@ set(MINECRAFT_SOURCES minecraft/mod/ModFolderLoadTask.cpp minecraft/mod/LocalModParseTask.h minecraft/mod/LocalModParseTask.cpp + minecraft/mod/LocalModUpdateTask.h + minecraft/mod/LocalModUpdateTask.cpp minecraft/mod/ResourcePackFolderModel.h minecraft/mod/ResourcePackFolderModel.cpp minecraft/mod/TexturePackFolderModel.h @@ -543,6 +545,11 @@ set(MODPACKSCH_SOURCES modplatform/modpacksch/FTBPackManifest.cpp ) +set(PACKWIZ_SOURCES + modplatform/packwiz/Packwiz.h + modplatform/packwiz/Packwiz.cpp +) + set(TECHNIC_SOURCES modplatform/technic/SingleZipPackInstallTask.h modplatform/technic/SingleZipPackInstallTask.cpp @@ -596,6 +603,7 @@ set(LOGIC_SOURCES ${FLAME_SOURCES} ${MODRINTH_SOURCES} ${MODPACKSCH_SOURCES} + ${PACKWIZ_SOURCES} ${TECHNIC_SOURCES} ${ATLAUNCHER_SOURCES} ) diff --git a/launcher/minecraft/mod/LocalModUpdateTask.cpp b/launcher/minecraft/mod/LocalModUpdateTask.cpp new file mode 100644 index 000000000..0f48217bf --- /dev/null +++ b/launcher/minecraft/mod/LocalModUpdateTask.cpp @@ -0,0 +1,32 @@ +#include "LocalModUpdateTask.h" + +#include + +#include "FileSystem.h" +#include "modplatform/packwiz/Packwiz.h" + +LocalModUpdateTask::LocalModUpdateTask(QDir mods_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version) + : m_mod(mod), m_mod_version(mod_version) +{ + // Ensure a '.index' folder exists in the mods folder, and create it if it does not + m_index_dir = { QString("%1/.index").arg(mods_dir.absolutePath()) }; + if (!FS::ensureFolderPathExists(m_index_dir.path())) { + emitFailed(QString("Unable to create index for mod %1!").arg(m_mod.name)); + } +} + +void LocalModUpdateTask::executeTask() +{ + setStatus(tr("Updating index for mod:\n%1").arg(m_mod.name)); + + auto pw_mod = Packwiz::createModFormat(m_index_dir, m_mod, m_mod_version); + Packwiz::updateModIndex(m_index_dir, pw_mod); + + emitSucceeded(); +} + +bool LocalModUpdateTask::abort() +{ + emitAborted(); + return true; +} diff --git a/launcher/minecraft/mod/LocalModUpdateTask.h b/launcher/minecraft/mod/LocalModUpdateTask.h new file mode 100644 index 000000000..866089e9c --- /dev/null +++ b/launcher/minecraft/mod/LocalModUpdateTask.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include "tasks/Task.h" +#include "modplatform/ModIndex.h" + +class LocalModUpdateTask : public Task { + Q_OBJECT + public: + using Ptr = shared_qobject_ptr; + + explicit LocalModUpdateTask(QDir mods_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version); + + bool canAbort() const override { return true; } + bool abort() override; + + protected slots: + //! Entry point for tasks. + void executeTask() override; + + private: + QDir m_index_dir; + ModPlatform::IndexedPack& m_mod; + ModPlatform::IndexedVersion& m_mod_version; +}; diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 7e1cf254e..9c9ba99fe 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -8,6 +8,35 @@ namespace ModPlatform { +enum class Provider{ + MODRINTH, + FLAME +}; + +class ProviderCapabilities { + public: + static QString hashType(Provider p) + { + switch(p){ + case Provider::MODRINTH: + return "sha256"; + case Provider::FLAME: + return "murmur2"; + } + return ""; + } + static QString providerName(Provider p) + { + switch(p){ + case Provider::MODRINTH: + return "modrinth"; + case Provider::FLAME: + return "curseforge"; + } + return ""; + } +}; + struct ModpackAuthor { QString name; QString url; @@ -26,6 +55,7 @@ struct IndexedVersion { struct IndexedPack { QVariant addonId; + Provider provider; QString name; QString description; QList authors; @@ -40,3 +70,4 @@ struct IndexedPack { } // namespace ModPlatform Q_DECLARE_METATYPE(ModPlatform::IndexedPack) +Q_DECLARE_METATYPE(ModPlatform::Provider) diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index ba0824cf5..45f02b71b 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -9,6 +9,7 @@ void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) { pack.addonId = Json::requireInteger(obj, "id"); + pack.provider = ModPlatform::Provider::FLAME; pack.name = Json::requireString(obj, "name"); pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", ""); pack.description = Json::ensureString(obj, "summary", ""); diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index f7fa98641..6c8659dc3 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -28,6 +28,7 @@ static ModrinthAPI api; void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) { pack.addonId = Json::requireString(obj, "project_id"); + pack.provider = ModPlatform::Provider::MODRINTH; pack.name = Json::requireString(obj, "title"); QString slug = Json::ensureString(obj, "slug", ""); diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp new file mode 100644 index 000000000..ff86a8a9d --- /dev/null +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -0,0 +1,60 @@ +#include "Packwiz.h" + +#include "modplatform/ModIndex.h" + +#include +#include +#include + +auto Packwiz::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod +{ + Mod mod; + + mod.name = mod_pack.name; + mod.filename = mod_version.fileName; + + mod.url = mod_version.downloadUrl; + mod.hash_format = ModPlatform::ProviderCapabilities::hashType(mod_pack.provider); + mod.hash = ""; // FIXME + + mod.provider = mod_pack.provider; + mod.file_id = mod_pack.addonId; + mod.project_id = mod_version.fileId; + + return mod; +} + +void Packwiz::updateModIndex(QDir& index_dir, Mod& mod) +{ + // Ensure the corresponding mod's info exists, and create it if not + auto index_file_name = QString("%1.toml").arg(mod.name); + QFile index_file(index_dir.absoluteFilePath(index_file_name)); + + // There's already data on there! + if (index_file.exists()) { index_file.remove(); } + + if (!index_file.open(QIODevice::ReadWrite)) { + qCritical() << "Could not open file " << index_file_name << "!"; + return; + } + + // Put TOML data into the file + QTextStream in_stream(&index_file); + auto addToStream = [&in_stream](QString&& key, QString value) { in_stream << QString("%1 = \"%2\"\n").arg(key, value); }; + + { + addToStream("name", mod.name); + addToStream("filename", mod.filename); + addToStream("side", mod.side); + + in_stream << QString("\n[download]\n"); + addToStream("url", mod.url.toString()); + addToStream("hash-format", mod.hash_format); + addToStream("hash", mod.hash); + + in_stream << QString("\n[update]\n"); + in_stream << QString("[update.%1]\n").arg(ModPlatform::ProviderCapabilities::providerName(mod.provider)); + addToStream("file-id", mod.file_id.toString()); + addToStream("project-id", mod.project_id.toString()); + } +} diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h new file mode 100644 index 000000000..64b95e7ab --- /dev/null +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include + +namespace ModPlatform { +enum class Provider; +class IndexedPack; +class IndexedVersion; +} // namespace ModPlatform + +class QDir; + +class Packwiz { + public: + struct Mod { + QString name; + QString filename; + // FIXME: make side an enum + QString side = "both"; + + // [download] + QUrl url; + // FIXME: make hash-format an enum + QString hash_format; + QString hash; + + // [update] + ModPlatform::Provider provider; + QVariant file_id; + QVariant project_id; + }; + + /* Generates the object representing the information in a mod.toml file via its common representation in the launcher */ + static auto createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod; + + /* Updates the mod index for the provided mod. + * This creates a new index if one does not exist already + * TODO: Ask the user if they want to override, and delete the old mod's files, or keep the old one. + * */ + static void updateModIndex(QDir& index_dir, Mod& mod); +}; From c86c719e1a09be2dc25ffd26278076566672e3b5 Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 13 Apr 2022 19:18:28 -0300 Subject: [PATCH 073/308] feat: add mod index updating to ModDownloadTask This makes ModDownloadTask into a SequentialTask with 2 subtasks: Downloading the mod files and updating the index with the new information. The index updating is done first so that, in the future, we can prompt the user before download if, for instance, we discover there's another version already installed. --- launcher/ModDownloadTask.cpp | 25 +++++++++++----------- launcher/ModDownloadTask.h | 26 +++++++++++------------ launcher/ui/pages/modplatform/ModPage.cpp | 2 +- 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/launcher/ModDownloadTask.cpp b/launcher/ModDownloadTask.cpp index 08a02d299..e5766435a 100644 --- a/launcher/ModDownloadTask.cpp +++ b/launcher/ModDownloadTask.cpp @@ -1,24 +1,28 @@ #include "ModDownloadTask.h" #include "Application.h" +#include "minecraft/mod/LocalModUpdateTask.h" -ModDownloadTask::ModDownloadTask(const QUrl sourceUrl,const QString filename, const std::shared_ptr mods) -: m_sourceUrl(sourceUrl), mods(mods), filename(filename) { -} +ModDownloadTask::ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr mods) + : m_mod(mod), m_mod_version(version), mods(mods) +{ + m_update_task.reset(new LocalModUpdateTask(mods->dir(), m_mod, m_mod_version)); -void ModDownloadTask::executeTask() { - setStatus(tr("Downloading mod:\n%1").arg(m_sourceUrl.toString())); + addTask(m_update_task); m_filesNetJob.reset(new NetJob(tr("Mod download"), APPLICATION->network())); - m_filesNetJob->addNetAction(Net::Download::makeFile(m_sourceUrl, mods->dir().absoluteFilePath(filename))); + m_filesNetJob->setStatus(tr("Downloading mod:\n%1").arg(m_mod_version.downloadUrl)); + + m_filesNetJob->addNetAction(Net::Download::makeFile(m_mod_version.downloadUrl, mods->dir().absoluteFilePath(getFilename()))); connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ModDownloadTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::progress, this, &ModDownloadTask::downloadProgressChanged); connect(m_filesNetJob.get(), &NetJob::failed, this, &ModDownloadTask::downloadFailed); - m_filesNetJob->start(); + + addTask(m_filesNetJob); + } void ModDownloadTask::downloadSucceeded() { - emitSucceeded(); m_filesNetJob.reset(); } @@ -32,8 +36,3 @@ void ModDownloadTask::downloadProgressChanged(qint64 current, qint64 total) { emit progress(current, total); } - -bool ModDownloadTask::abort() { - return m_filesNetJob->abort(); -} - diff --git a/launcher/ModDownloadTask.h b/launcher/ModDownloadTask.h index ddada5a21..d292dfbb7 100644 --- a/launcher/ModDownloadTask.h +++ b/launcher/ModDownloadTask.h @@ -1,28 +1,26 @@ #pragma once + #include "QObjectPtr.h" -#include "tasks/Task.h" +#include "minecraft/mod/LocalModUpdateTask.h" +#include "modplatform/ModIndex.h" +#include "tasks/SequentialTask.h" #include "minecraft/mod/ModFolderModel.h" #include "net/NetJob.h" #include - -class ModDownloadTask : public Task { +class ModDownloadTask : public SequentialTask { Q_OBJECT public: - explicit ModDownloadTask(const QUrl sourceUrl, const QString filename, const std::shared_ptr mods); - const QString& getFilename() const { return filename; } - -public slots: - bool abort() override; -protected: - //! Entry point for tasks. - void executeTask() override; + explicit ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr mods); + const QString& getFilename() const { return m_mod_version.fileName; } private: - QUrl m_sourceUrl; - NetJob::Ptr m_filesNetJob; + ModPlatform::IndexedPack m_mod; + ModPlatform::IndexedVersion m_mod_version; const std::shared_ptr mods; - const QString filename; + + NetJob::Ptr m_filesNetJob; + LocalModUpdateTask::Ptr m_update_task; void downloadProgressChanged(qint64 current, qint64 total); diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index ad36cf2f8..5020d44cd 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -150,7 +150,7 @@ void ModPage::onModSelected() if (dialog->isModSelected(current.name, version.fileName)) { dialog->removeSelectedMod(current.name); } else { - dialog->addSelectedMod(current.name, new ModDownloadTask(version.downloadUrl, version.fileName, dialog->mods)); + dialog->addSelectedMod(current.name, new ModDownloadTask(current, version, dialog->mods)); } updateSelectionButton(); From eaa5ce446765ef4305a1462d68e278b0797966ee Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 13 Apr 2022 19:23:12 -0300 Subject: [PATCH 074/308] feat(ui): adapt SequentialTask to nested SequentialTasks --- launcher/modplatform/packwiz/Packwiz.h | 5 ++--- launcher/tasks/SequentialTask.cpp | 12 +++++++++--- launcher/tasks/SequentialTask.h | 9 +++------ launcher/tasks/Task.h | 1 + 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index 64b95e7ab..9c90f7de8 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -1,13 +1,12 @@ #pragma once +#include "modplatform/ModIndex.h" + #include #include #include namespace ModPlatform { -enum class Provider; -class IndexedPack; -class IndexedVersion; } // namespace ModPlatform class QDir; diff --git a/launcher/tasks/SequentialTask.cpp b/launcher/tasks/SequentialTask.cpp index 1573e476c..2d50c2990 100644 --- a/launcher/tasks/SequentialTask.cpp +++ b/launcher/tasks/SequentialTask.cpp @@ -53,12 +53,18 @@ void SequentialTask::startNext() return; } Task::Ptr next = m_queue[m_currentIndex]; + connect(next.get(), SIGNAL(failed(QString)), this, SLOT(subTaskFailed(QString))); - connect(next.get(), SIGNAL(status(QString)), this, SLOT(subTaskStatus(QString))); - connect(next.get(), SIGNAL(progress(qint64, qint64)), this, SLOT(subTaskProgress(qint64, qint64))); connect(next.get(), SIGNAL(succeeded()), this, SLOT(startNext())); + connect(next.get(), SIGNAL(status(QString)), this, SLOT(subTaskStatus(QString))); + connect(next.get(), SIGNAL(stepStatus(QString)), this, SLOT(subTaskStatus(QString))); + + connect(next.get(), SIGNAL(progress(qint64, qint64)), this, SLOT(subTaskProgress(qint64, qint64))); + setStatus(tr("Executing task %1 out of %2").arg(m_currentIndex + 1).arg(m_queue.size())); + setStepStatus(next->isMultiStep() ? next->getStepStatus() : next->getStatus()); + next->start(); } @@ -68,7 +74,7 @@ void SequentialTask::subTaskFailed(const QString& msg) } void SequentialTask::subTaskStatus(const QString& msg) { - setStepStatus(m_queue[m_currentIndex]->getStatus()); + setStepStatus(msg); } void SequentialTask::subTaskProgress(qint64 current, qint64 total) { diff --git a/launcher/tasks/SequentialTask.h b/launcher/tasks/SequentialTask.h index 5b3c01117..e10cb6f7a 100644 --- a/launcher/tasks/SequentialTask.h +++ b/launcher/tasks/SequentialTask.h @@ -32,13 +32,10 @@ slots: void subTaskStatus(const QString &msg); void subTaskProgress(qint64 current, qint64 total); -signals: - void stepStatus(QString status); +protected: + void setStepStatus(QString status) { m_step_status = status; emit stepStatus(status); }; -private: - void setStepStatus(QString status) { m_step_status = status; }; - -private: +protected: QString m_name; QString m_step_status; diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index f7765c3db..aafaf68c6 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -92,6 +92,7 @@ class Task : public QObject { void aborted(); void failed(QString reason); void status(QString status); + void stepStatus(QString status); public slots: virtual void start(); From 8e4438b375ee904aa8225b569899355372e5987c Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 13 Apr 2022 21:25:08 -0300 Subject: [PATCH 075/308] feat: add parser for current impl of packwiz mod.toml This reads a local mod.toml file and extract information from it. Using C libs in C++ is kind of a pain tho :( --- launcher/modplatform/ModIndex.h | 2 +- launcher/modplatform/packwiz/Packwiz.cpp | 90 ++++++++++++++++++++++++ launcher/modplatform/packwiz/Packwiz.h | 5 ++ 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 9c9ba99fe..c5329772e 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -25,7 +25,7 @@ class ProviderCapabilities { } return ""; } - static QString providerName(Provider p) + static const char* providerName(Provider p) { switch(p){ case Provider::MODRINTH: diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index ff86a8a9d..58bead829 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -1,6 +1,7 @@ #include "Packwiz.h" #include "modplatform/ModIndex.h" +#include "toml.h" #include #include @@ -58,3 +59,92 @@ void Packwiz::updateModIndex(QDir& index_dir, Mod& mod) addToStream("project-id", mod.project_id.toString()); } } + +auto Packwiz::getIndexForMod(QDir& index_dir, QString mod_name) -> Mod +{ + Mod mod; + + auto index_file_name = QString("%1.toml").arg(mod_name); + QFile index_file(index_dir.absoluteFilePath(index_file_name)); + + if (!index_file.exists()) { return mod; } + if (!index_file.open(QIODevice::ReadOnly)) { return mod; } + + toml_table_t* table; + + char errbuf[200]; + table = toml_parse(index_file.readAll().data(), errbuf, sizeof(errbuf)); + + index_file.close(); + + if (!table) { + qCritical() << QString("Could not open file %1").arg(index_file_name); + return mod; + } + + // Helper function for extracting data from the TOML file + auto stringEntry = [&](toml_table_t* parent, const char* entry_name) -> QString { + toml_datum_t var = toml_string_in(parent, entry_name); + if (!var.ok) { + qCritical() << QString("Failed to read property '%1' in mod metadata.").arg(entry_name); + return {}; + } + + QString tmp = var.u.s; + free(var.u.s); + + return tmp; + }; + + { // Basic info + mod.name = stringEntry(table, "name"); + // Basic sanity check + if (mod.name != mod_name) { + qCritical() << QString("Name mismatch in mod metadata:\nExpected:%1\nGot:%2").arg(mod_name, mod.name); + return {}; + } + + mod.filename = stringEntry(table, "filename"); + mod.side = stringEntry(table, "side"); + } + + { // [download] info + toml_table_t* download_table = toml_table_in(table, "download"); + if (!download_table) { + qCritical() << QString("No [download] section found on mod metadata!"); + return {}; + } + + mod.url = stringEntry(download_table, "url"); + mod.hash_format = stringEntry(download_table, "hash-format"); + mod.hash = stringEntry(download_table, "hash"); + } + + { // [update] info + using ProviderCaps = ModPlatform::ProviderCapabilities; + using Provider = ModPlatform::Provider; + + toml_table_t* update_table = toml_table_in(table, "update"); + if (!update_table) { + qCritical() << QString("No [update] section found on mod metadata!"); + return {}; + } + + toml_table_t* mod_provider_table; + if ((mod_provider_table = toml_table_in(update_table, ProviderCaps::providerName(Provider::FLAME)))) { + mod.provider = Provider::FLAME; + } else if ((mod_provider_table = toml_table_in(update_table, ProviderCaps::providerName(Provider::MODRINTH)))) { + mod.provider = Provider::MODRINTH; + } else { + qCritical() << "No mod provider on mod metadata!"; + return {}; + } + + mod.file_id = stringEntry(mod_provider_table, "file-id"); + mod.project_id = stringEntry(mod_provider_table, "project-id"); + } + + toml_free(table); + + return mod; +} diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index 9c90f7de8..08edaab95 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -39,4 +39,9 @@ class Packwiz { * TODO: Ask the user if they want to override, and delete the old mod's files, or keep the old one. * */ static void updateModIndex(QDir& index_dir, Mod& mod); + + /* Gets the metadata for a mod with a particular name. + * If the mod doesn't have a metadata, it simply returns an empty Mod object. + * */ + static auto getIndexForMod(QDir& index_dir, QString mod_name) -> Mod; }; From e93b9560b5137a5ee7acdc34c0f74992aa02aad6 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 14 Apr 2022 22:02:41 -0300 Subject: [PATCH 076/308] feat: add method to delete mod metadata Also moves indexDir setting from LocalModUpdateTask -> ModFolderModel --- launcher/ModDownloadTask.cpp | 2 +- launcher/minecraft/mod/LocalModUpdateTask.cpp | 7 ++- launcher/minecraft/mod/ModFolderModel.h | 7 ++- launcher/modplatform/packwiz/Packwiz.cpp | 44 ++++++++++++++----- launcher/modplatform/packwiz/Packwiz.h | 5 ++- 5 files changed, 48 insertions(+), 17 deletions(-) diff --git a/launcher/ModDownloadTask.cpp b/launcher/ModDownloadTask.cpp index e5766435a..ad1e64e30 100644 --- a/launcher/ModDownloadTask.cpp +++ b/launcher/ModDownloadTask.cpp @@ -5,7 +5,7 @@ ModDownloadTask::ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr mods) : m_mod(mod), m_mod_version(version), mods(mods) { - m_update_task.reset(new LocalModUpdateTask(mods->dir(), m_mod, m_mod_version)); + m_update_task.reset(new LocalModUpdateTask(mods->indexDir(), m_mod, m_mod_version)); addTask(m_update_task); diff --git a/launcher/minecraft/mod/LocalModUpdateTask.cpp b/launcher/minecraft/mod/LocalModUpdateTask.cpp index 0f48217bf..63f5cf9a1 100644 --- a/launcher/minecraft/mod/LocalModUpdateTask.cpp +++ b/launcher/minecraft/mod/LocalModUpdateTask.cpp @@ -5,12 +5,11 @@ #include "FileSystem.h" #include "modplatform/packwiz/Packwiz.h" -LocalModUpdateTask::LocalModUpdateTask(QDir mods_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version) - : m_mod(mod), m_mod_version(mod_version) +LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version) + : m_index_dir(index_dir), m_mod(mod), m_mod_version(mod_version) { // Ensure a '.index' folder exists in the mods folder, and create it if it does not - m_index_dir = { QString("%1/.index").arg(mods_dir.absolutePath()) }; - if (!FS::ensureFolderPathExists(m_index_dir.path())) { + if (!FS::ensureFolderPathExists(index_dir.path())) { emitFailed(QString("Unable to create index for mod %1!").arg(m_mod.name)); } } diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index 62c504dfc..f8ad4ca86 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -108,11 +108,16 @@ public: bool isValid(); - QDir dir() + QDir& dir() { return m_dir; } + QDir indexDir() + { + return { QString("%1/.index").arg(dir().absolutePath()) }; + } + const QList & allMods() { return mods; diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 58bead829..bfadf7cb0 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -7,6 +7,12 @@ #include #include +// Helpers +static inline QString indexFileName(QString const& mod_name) +{ + return QString("%1.toml").arg(mod_name); +} + auto Packwiz::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod { Mod mod; @@ -28,14 +34,13 @@ auto Packwiz::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pac void Packwiz::updateModIndex(QDir& index_dir, Mod& mod) { // Ensure the corresponding mod's info exists, and create it if not - auto index_file_name = QString("%1.toml").arg(mod.name); - QFile index_file(index_dir.absoluteFilePath(index_file_name)); + QFile index_file(index_dir.absoluteFilePath(indexFileName(mod.name))); // There's already data on there! if (index_file.exists()) { index_file.remove(); } if (!index_file.open(QIODevice::ReadWrite)) { - qCritical() << "Could not open file " << index_file_name << "!"; + qCritical() << QString("Could not open file %1!").arg(indexFileName(mod.name)); return; } @@ -60,15 +65,34 @@ void Packwiz::updateModIndex(QDir& index_dir, Mod& mod) } } -auto Packwiz::getIndexForMod(QDir& index_dir, QString mod_name) -> Mod +void Packwiz::deleteModIndex(QDir& index_dir, QString& mod_name) +{ + QFile index_file(index_dir.absoluteFilePath(indexFileName(mod_name))); + + if(!index_file.exists()){ + qWarning() << QString("Tried to delete non-existent mod metadata for %1!").arg(mod_name); + return; + } + + if(!index_file.remove()){ + qWarning() << QString("Failed to remove metadata for mod %1!").arg(mod_name); + } +} + +auto Packwiz::getIndexForMod(QDir& index_dir, QString& mod_name) -> Mod { Mod mod; - auto index_file_name = QString("%1.toml").arg(mod_name); - QFile index_file(index_dir.absoluteFilePath(index_file_name)); + QFile index_file(index_dir.absoluteFilePath(indexFileName(mod_name))); - if (!index_file.exists()) { return mod; } - if (!index_file.open(QIODevice::ReadOnly)) { return mod; } + if (!index_file.exists()) { + qWarning() << QString("Tried to get a non-existent mod metadata for %1").arg(mod_name); + return mod; + } + if (!index_file.open(QIODevice::ReadOnly)) { + qWarning() << QString("Failed to open mod metadata for %1").arg(mod_name); + return mod; + } toml_table_t* table; @@ -78,7 +102,7 @@ auto Packwiz::getIndexForMod(QDir& index_dir, QString mod_name) -> Mod index_file.close(); if (!table) { - qCritical() << QString("Could not open file %1").arg(index_file_name); + qCritical() << QString("Could not open file %1!").arg(indexFileName(mod.name)); return mod; } @@ -136,7 +160,7 @@ auto Packwiz::getIndexForMod(QDir& index_dir, QString mod_name) -> Mod } else if ((mod_provider_table = toml_table_in(update_table, ProviderCaps::providerName(Provider::MODRINTH)))) { mod.provider = Provider::MODRINTH; } else { - qCritical() << "No mod provider on mod metadata!"; + qCritical() << QString("No mod provider on mod metadata!"); return {}; } diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index 08edaab95..541059d08 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -40,8 +40,11 @@ class Packwiz { * */ static void updateModIndex(QDir& index_dir, Mod& mod); + /* Deletes the metadata for the mod with the given name. If the metadata doesn't exist, it does nothing. */ + static void deleteModIndex(QDir& index_dir, QString& mod_name); + /* Gets the metadata for a mod with a particular name. * If the mod doesn't have a metadata, it simply returns an empty Mod object. * */ - static auto getIndexForMod(QDir& index_dir, QString mod_name) -> Mod; + static auto getIndexForMod(QDir& index_dir, QString& mod_name) -> Mod; }; From fcfb2cfc3da9a8f897063db05fdf3aebc41a59ae Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 15 Apr 2022 00:24:57 -0300 Subject: [PATCH 077/308] feat: use mod metadata for getting mod information For now this doesn't mean much, but it will help when we need data exclusive from the metadata, such as addon id and mod provider. Also removes the metadata when the mod is deleted, and make the Mod.h file a little more pleasing to look at :) --- launcher/minecraft/mod/Mod.cpp | 33 ++++++++- launcher/minecraft/mod/Mod.h | 73 +++++++------------- launcher/minecraft/mod/ModFolderLoadTask.cpp | 31 +++++++-- launcher/minecraft/mod/ModFolderLoadTask.h | 4 +- launcher/minecraft/mod/ModFolderModel.cpp | 9 ++- 5 files changed, 90 insertions(+), 60 deletions(-) diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index b6bff29b9..59f4d83bc 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -33,6 +33,30 @@ Mod::Mod(const QFileInfo &file) m_changedDateTime = file.lastModified(); } +Mod::Mod(const QDir& mods_dir, const Packwiz::Mod& metadata) + : m_file(mods_dir.absoluteFilePath(metadata.filename)) + // It is weird, but name is not reliable for comparing with the JAR files name + // FIXME: Maybe use hash when implemented? + , m_mmc_id(metadata.filename) + , m_name(metadata.name) +{ + if(m_file.isDir()){ + m_type = MOD_FOLDER; + } + else{ + if (metadata.filename.endsWith(".zip") || metadata.filename.endsWith(".jar")) + m_type = MOD_ZIPFILE; + else if (metadata.filename.endsWith(".litemod")) + m_type = MOD_LITEMOD; + else + m_type = MOD_SINGLEFILE; + } + + m_from_metadata = true; + m_enabled = true; + m_changedDateTime = m_file.lastModified(); +} + void Mod::repath(const QFileInfo &file) { m_file = file; @@ -101,13 +125,18 @@ bool Mod::enable(bool value) if (!foo.rename(path)) return false; } - repath(QFileInfo(path)); + if(!fromMetadata()) + repath(QFileInfo(path)); + m_enabled = value; return true; } -bool Mod::destroy() +bool Mod::destroy(QDir& index_dir) { + // Delete metadata + Packwiz::deleteModIndex(index_dir, m_name); + m_type = MOD_UNKNOWN; return FS::deletePath(m_file.filePath()); } diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 921faeb15..c9fd5813c 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -14,14 +14,14 @@ */ #pragma once -#include + #include +#include #include #include #include "ModDetails.h" - - +#include "modplatform/packwiz/Packwiz.h" class Mod { @@ -32,65 +32,41 @@ public: 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_LITEMOD, //!< The mod is a litemod + MOD_LITEMOD, //!< The mod is a litemod }; Mod() = default; Mod(const QFileInfo &file); + explicit Mod(const QDir& mods_dir, const Packwiz::Mod& metadata); - QFileInfo filename() const - { - return m_file; - } - QString mmc_id() const - { - return m_mmc_id; - } - ModType type() const - { - return m_type; - } - bool valid() - { - return m_type != MOD_UNKNOWN; - } + QFileInfo filename() const { return m_file; } + QDateTime dateTimeChanged() const { return m_changedDateTime; } + QString mmc_id() const { return m_mmc_id; } + ModType type() const { return m_type; } + bool fromMetadata() const { return m_from_metadata; } + bool enabled() const { return m_enabled; } - QDateTime dateTimeChanged() const - { - return m_changedDateTime; - } + bool valid() const { return m_type != MOD_UNKNOWN; } - bool enabled() const - { - return m_enabled; - } - - const ModDetails &details() const; - - QString name() const; - QString version() const; - QString homeurl() const; + const ModDetails& details() const; + QString name() const; + QString version() const; + QString homeurl() const; QString description() const; QStringList authors() const; bool enable(bool value); // delete all the files of this mod - bool destroy(); + bool destroy(QDir& index_dir); // change the mod's filesystem path (used by mod lists for *MAGIC* purposes) void repath(const QFileInfo &file); - bool shouldResolve() { - return !m_resolving && !m_resolved; - } - bool isResolving() { - return m_resolving; - } - int resolutionTicket() - { - return m_resolutionTicket; - } + bool shouldResolve() const { return !m_resolving && !m_resolved; } + bool isResolving() const { return m_resolving; } + int resolutionTicket() const { return m_resolutionTicket; } + void setResolving(bool resolving, int resolutionTicket) { m_resolving = resolving; m_resolutionTicket = resolutionTicket; @@ -104,12 +80,15 @@ public: protected: QFileInfo m_file; QDateTime m_changedDateTime; + QString m_mmc_id; QString m_name; + ModType m_type = MOD_UNKNOWN; + bool m_from_metadata = false; + std::shared_ptr m_localDetails; + bool m_enabled = true; bool m_resolving = false; bool m_resolved = false; int m_resolutionTicket = 0; - ModType m_type = MOD_UNKNOWN; - std::shared_ptr m_localDetails; }; diff --git a/launcher/minecraft/mod/ModFolderLoadTask.cpp b/launcher/minecraft/mod/ModFolderLoadTask.cpp index 883498771..fd4d60083 100644 --- a/launcher/minecraft/mod/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/ModFolderLoadTask.cpp @@ -1,18 +1,35 @@ #include "ModFolderLoadTask.h" #include -ModFolderLoadTask::ModFolderLoadTask(QDir dir) : - m_dir(dir), m_result(new Result()) +#include "modplatform/packwiz/Packwiz.h" + +ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir) : + m_mods_dir(mods_dir), m_index_dir(index_dir), m_result(new Result()) { } void ModFolderLoadTask::run() { - m_dir.refresh(); - for (auto entry : m_dir.entryInfoList()) - { - Mod m(entry); - m_result->mods[m.mmc_id()] = m; + // Read metadata first + m_index_dir.refresh(); + for(auto entry : m_index_dir.entryList()){ + // QDir::Filter::NoDotAndDotDot seems to exclude all files for some reason... + if(entry == "." || entry == "..") + continue; + + entry.chop(5); // Remove .toml at the end + Mod mod(m_mods_dir, Packwiz::getIndexForMod(m_index_dir, entry)); + m_result->mods[mod.mmc_id()] = mod; } + + // Read JAR files that don't have metadata + m_mods_dir.refresh(); + for (auto entry : m_mods_dir.entryInfoList()) + { + Mod mod(entry); + if(!m_result->mods.contains(mod.mmc_id())) + m_result->mods[mod.mmc_id()] = mod; + } + emit succeeded(); } diff --git a/launcher/minecraft/mod/ModFolderLoadTask.h b/launcher/minecraft/mod/ModFolderLoadTask.h index 8d720e652..c869f0832 100644 --- a/launcher/minecraft/mod/ModFolderLoadTask.h +++ b/launcher/minecraft/mod/ModFolderLoadTask.h @@ -19,11 +19,11 @@ public: } public: - ModFolderLoadTask(QDir dir); + ModFolderLoadTask(QDir& mods_dir, QDir& index_dir); void run(); signals: void succeeded(); private: - QDir m_dir; + QDir& m_mods_dir, m_index_dir; ResultPtr m_result; }; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index f0c53c392..615cfc0ca 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -79,10 +79,14 @@ bool ModFolderModel::update() return true; } - auto task = new ModFolderLoadTask(m_dir); + auto index_dir = indexDir(); + auto task = new ModFolderLoadTask(dir(), index_dir); + m_update = task->result(); + QThreadPool *threadPool = QThreadPool::globalInstance(); connect(task, &ModFolderLoadTask::succeeded, this, &ModFolderModel::finishUpdate); + threadPool->start(task); return true; } @@ -334,7 +338,8 @@ bool ModFolderModel::deleteMods(const QModelIndexList& indexes) for (auto i: indexes) { Mod &m = mods[i.row()]; - m.destroy(); + auto index_dir = indexDir(); + m.destroy(index_dir); } return true; } From 5a34e8fd7c913bc138e1606baf9df2cd1a64baed Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 15 Apr 2022 20:35:17 -0300 Subject: [PATCH 078/308] refactor: move mod tasks to their own subfolder Makes the launcher/minecraft/mod/ folder a little more organized. --- launcher/CMakeLists.txt | 12 ++++++------ launcher/ModDownloadTask.cpp | 3 ++- launcher/ModDownloadTask.h | 13 +++++++------ launcher/minecraft/mod/ModFolderModel.cpp | 14 ++++++++------ launcher/minecraft/mod/ModFolderModel.h | 4 ++-- .../mod/{ => tasks}/LocalModParseTask.cpp | 0 .../minecraft/mod/{ => tasks}/LocalModParseTask.h | 8 +++++--- .../mod/{ => tasks}/LocalModUpdateTask.cpp | 0 .../minecraft/mod/{ => tasks}/LocalModUpdateTask.h | 0 .../mod/{ => tasks}/ModFolderLoadTask.cpp | 0 .../minecraft/mod/{ => tasks}/ModFolderLoadTask.h | 7 ++++--- 11 files changed, 34 insertions(+), 27 deletions(-) rename launcher/minecraft/mod/{ => tasks}/LocalModParseTask.cpp (100%) rename launcher/minecraft/mod/{ => tasks}/LocalModParseTask.h (90%) rename launcher/minecraft/mod/{ => tasks}/LocalModUpdateTask.cpp (100%) rename launcher/minecraft/mod/{ => tasks}/LocalModUpdateTask.h (100%) rename launcher/minecraft/mod/{ => tasks}/ModFolderLoadTask.cpp (100%) rename launcher/minecraft/mod/{ => tasks}/ModFolderLoadTask.h (94%) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index b5c6fe91d..b6df28510 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -327,16 +327,16 @@ set(MINECRAFT_SOURCES minecraft/mod/ModDetails.h minecraft/mod/ModFolderModel.h minecraft/mod/ModFolderModel.cpp - minecraft/mod/ModFolderLoadTask.h - minecraft/mod/ModFolderLoadTask.cpp - minecraft/mod/LocalModParseTask.h - minecraft/mod/LocalModParseTask.cpp - minecraft/mod/LocalModUpdateTask.h - minecraft/mod/LocalModUpdateTask.cpp minecraft/mod/ResourcePackFolderModel.h minecraft/mod/ResourcePackFolderModel.cpp minecraft/mod/TexturePackFolderModel.h minecraft/mod/TexturePackFolderModel.cpp + minecraft/mod/tasks/ModFolderLoadTask.h + minecraft/mod/tasks/ModFolderLoadTask.cpp + minecraft/mod/tasks/LocalModParseTask.h + minecraft/mod/tasks/LocalModParseTask.cpp + minecraft/mod/tasks/LocalModUpdateTask.h + minecraft/mod/tasks/LocalModUpdateTask.cpp # Assets minecraft/AssetsUtils.h diff --git a/launcher/ModDownloadTask.cpp b/launcher/ModDownloadTask.cpp index ad1e64e30..52de9c942 100644 --- a/launcher/ModDownloadTask.cpp +++ b/launcher/ModDownloadTask.cpp @@ -1,6 +1,7 @@ #include "ModDownloadTask.h" + #include "Application.h" -#include "minecraft/mod/LocalModUpdateTask.h" +#include "minecraft/mod/tasks/LocalModUpdateTask.h" ModDownloadTask::ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr mods) : m_mod(mod), m_mod_version(version), mods(mods) diff --git a/launcher/ModDownloadTask.h b/launcher/ModDownloadTask.h index d292dfbb7..5eaee187a 100644 --- a/launcher/ModDownloadTask.h +++ b/launcher/ModDownloadTask.h @@ -1,12 +1,13 @@ #pragma once -#include "QObjectPtr.h" -#include "minecraft/mod/LocalModUpdateTask.h" -#include "modplatform/ModIndex.h" -#include "tasks/SequentialTask.h" -#include "minecraft/mod/ModFolderModel.h" -#include "net/NetJob.h" #include +#include "QObjectPtr.h" +#include "minecraft/mod/ModFolderModel.h" +#include "modplatform/ModIndex.h" +#include "net/NetJob.h" + +#include "tasks/SequentialTask.h" +#include "minecraft/mod/tasks/LocalModUpdateTask.h" class ModDownloadTask : public SequentialTask { Q_OBJECT diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 615cfc0ca..936b68d39 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -14,17 +14,19 @@ */ #include "ModFolderModel.h" + #include +#include +#include #include +#include +#include #include #include -#include -#include -#include -#include "ModFolderLoadTask.h" -#include #include -#include "LocalModParseTask.h" + +#include "minecraft/mod/tasks/LocalModParseTask.h" +#include "minecraft/mod/tasks/ModFolderLoadTask.h" ModFolderModel::ModFolderModel(const QString &dir) : QAbstractListModel(), m_dir(dir) { diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index f8ad4ca86..10a726911 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -24,8 +24,8 @@ #include "Mod.h" -#include "ModFolderLoadTask.h" -#include "LocalModParseTask.h" +#include "minecraft/mod/tasks/ModFolderLoadTask.h" +#include "minecraft/mod/tasks/LocalModParseTask.h" class LegacyInstance; class BaseInstance; diff --git a/launcher/minecraft/mod/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp similarity index 100% rename from launcher/minecraft/mod/LocalModParseTask.cpp rename to launcher/minecraft/mod/tasks/LocalModParseTask.cpp diff --git a/launcher/minecraft/mod/LocalModParseTask.h b/launcher/minecraft/mod/tasks/LocalModParseTask.h similarity index 90% rename from launcher/minecraft/mod/LocalModParseTask.h rename to launcher/minecraft/mod/tasks/LocalModParseTask.h index 0f119ba62..ed92394ce 100644 --- a/launcher/minecraft/mod/LocalModParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.h @@ -1,9 +1,11 @@ #pragma once -#include + #include #include -#include "Mod.h" -#include "ModDetails.h" +#include + +#include "minecraft/mod/Mod.h" +#include "minecraft/mod/ModDetails.h" class LocalModParseTask : public QObject, public QRunnable { diff --git a/launcher/minecraft/mod/LocalModUpdateTask.cpp b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp similarity index 100% rename from launcher/minecraft/mod/LocalModUpdateTask.cpp rename to launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp diff --git a/launcher/minecraft/mod/LocalModUpdateTask.h b/launcher/minecraft/mod/tasks/LocalModUpdateTask.h similarity index 100% rename from launcher/minecraft/mod/LocalModUpdateTask.h rename to launcher/minecraft/mod/tasks/LocalModUpdateTask.h diff --git a/launcher/minecraft/mod/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp similarity index 100% rename from launcher/minecraft/mod/ModFolderLoadTask.cpp rename to launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp diff --git a/launcher/minecraft/mod/ModFolderLoadTask.h b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h similarity index 94% rename from launcher/minecraft/mod/ModFolderLoadTask.h rename to launcher/minecraft/mod/tasks/ModFolderLoadTask.h index c869f0832..bb66022ac 100644 --- a/launcher/minecraft/mod/ModFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h @@ -1,10 +1,11 @@ #pragma once -#include -#include + #include #include -#include "Mod.h" +#include +#include #include +#include "minecraft/mod/Mod.h" class ModFolderLoadTask : public QObject, public QRunnable { From e9fb566c0797865a37e5b59a49163258b3adb328 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 15 Apr 2022 22:07:35 -0300 Subject: [PATCH 079/308] refactor: remove unused mod info and organize some stuff --- launcher/minecraft/mod/Mod.cpp | 87 ++++++++----------- launcher/minecraft/mod/Mod.h | 4 +- launcher/minecraft/mod/ModDetails.h | 15 +++- launcher/minecraft/mod/ModFolderModel.cpp | 10 +-- .../minecraft/mod/tasks/LocalModParseTask.cpp | 22 +---- .../minecraft/mod/tasks/ModFolderLoadTask.cpp | 22 +++-- launcher/ui/widgets/MCModInfoFrame.cpp | 2 +- 7 files changed, 67 insertions(+), 95 deletions(-) diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 59f4d83bc..64c9ffb57 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -13,12 +13,13 @@ * limitations under the License. */ +#include "Mod.h" + #include #include -#include "Mod.h" -#include #include +#include namespace { @@ -26,8 +27,7 @@ ModDetails invalidDetails; } - -Mod::Mod(const QFileInfo &file) +Mod::Mod(const QFileInfo& file) { repath(file); m_changedDateTime = file.lastModified(); @@ -37,13 +37,12 @@ Mod::Mod(const QDir& mods_dir, const Packwiz::Mod& metadata) : m_file(mods_dir.absoluteFilePath(metadata.filename)) // It is weird, but name is not reliable for comparing with the JAR files name // FIXME: Maybe use hash when implemented? - , m_mmc_id(metadata.filename) + , m_internal_id(metadata.filename) , m_name(metadata.name) { - if(m_file.isDir()){ + if (m_file.isDir()) { m_type = MOD_FOLDER; - } - else{ + } else { if (metadata.filename.endsWith(".zip") || metadata.filename.endsWith(".jar")) m_type = MOD_ZIPFILE; else if (metadata.filename.endsWith(".litemod")) @@ -57,43 +56,32 @@ Mod::Mod(const QDir& mods_dir, const Packwiz::Mod& metadata) m_changedDateTime = m_file.lastModified(); } -void Mod::repath(const QFileInfo &file) +void Mod::repath(const QFileInfo& file) { m_file = file; QString name_base = file.fileName(); m_type = Mod::MOD_UNKNOWN; - m_mmc_id = name_base; + m_internal_id = name_base; - if (m_file.isDir()) - { + if (m_file.isDir()) { m_type = MOD_FOLDER; m_name = name_base; - } - else if (m_file.isFile()) - { - if (name_base.endsWith(".disabled")) - { + } else if (m_file.isFile()) { + if (name_base.endsWith(".disabled")) { m_enabled = false; name_base.chop(9); - } - else - { + } else { m_enabled = true; } - if (name_base.endsWith(".zip") || name_base.endsWith(".jar")) - { + if (name_base.endsWith(".zip") || name_base.endsWith(".jar")) { m_type = MOD_ZIPFILE; name_base.chop(4); - } - else if (name_base.endsWith(".litemod")) - { + } else if (name_base.endsWith(".litemod")) { m_type = MOD_LITEMOD; name_base.chop(8); - } - else - { + } else { m_type = MOD_SINGLEFILE; } m_name = name_base; @@ -109,23 +97,22 @@ bool Mod::enable(bool value) return false; QString path = m_file.absoluteFilePath(); - if (value) - { - QFile foo(path); + QFile file(path); + if (value) { if (!path.endsWith(".disabled")) return false; path.chop(9); - if (!foo.rename(path)) + + if (!file.rename(path)) return false; - } - else - { - QFile foo(path); + } else { path += ".disabled"; - if (!foo.rename(path)) + + if (!file.rename(path)) return false; } - if(!fromMetadata()) + + if (!fromMetadata()) repath(QFileInfo(path)); m_enabled = value; @@ -141,29 +128,25 @@ bool Mod::destroy(QDir& index_dir) return FS::deletePath(m_file.filePath()); } - -const ModDetails & Mod::details() const +const ModDetails& Mod::details() const { - if(!m_localDetails) - return invalidDetails; - return *m_localDetails; -} - - -QString Mod::version() const -{ - return details().version; + return m_localDetails ? *m_localDetails : invalidDetails; } QString Mod::name() const { - auto & d = details(); - if(!d.name.isEmpty()) { - return d.name; + auto d_name = details().name; + if (!d_name.isEmpty()) { + return d_name; } return m_name; } +QString Mod::version() const +{ + return details().version; +} + QString Mod::homeurl() const { return details().homeurl; diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index c9fd5813c..46bb1a596 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -41,7 +41,7 @@ public: QFileInfo filename() const { return m_file; } QDateTime dateTimeChanged() const { return m_changedDateTime; } - QString mmc_id() const { return m_mmc_id; } + QString internal_id() const { return m_internal_id; } ModType type() const { return m_type; } bool fromMetadata() const { return m_from_metadata; } bool enabled() const { return m_enabled; } @@ -81,7 +81,7 @@ protected: QFileInfo m_file; QDateTime m_changedDateTime; - QString m_mmc_id; + QString m_internal_id; QString m_name; ModType m_type = MOD_UNKNOWN; bool m_from_metadata = false; diff --git a/launcher/minecraft/mod/ModDetails.h b/launcher/minecraft/mod/ModDetails.h index 6ab4aee7d..d8d4f66fb 100644 --- a/launcher/minecraft/mod/ModDetails.h +++ b/launcher/minecraft/mod/ModDetails.h @@ -5,13 +5,24 @@ struct ModDetails { + /* Mod ID as defined in the ModLoader-specific metadata */ QString mod_id; + + /* Human-readable name */ QString name; + + /* Human-readable mod version */ QString version; + + /* Human-readable minecraft version */ QString mcversion; + + /* URL for mod's home page */ QString homeurl; - QString updateurl; + + /* Human-readable description */ QString description; + + /* List of the author's names */ QStringList authors; - QString credits; }; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 936b68d39..e2e041eb2 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -159,7 +159,7 @@ void ModFolderModel::finishUpdate() modsIndex.clear(); int idx = 0; for(auto & mod: mods) { - modsIndex[mod.mmc_id()] = idx; + modsIndex[mod.internal_id()] = idx; idx++; } } @@ -182,7 +182,7 @@ void ModFolderModel::resolveMod(Mod& m) auto task = new LocalModParseTask(nextResolutionTicket, m.type(), m.filename()); auto result = task->result(); - result->id = m.mmc_id(); + result->id = m.internal_id(); activeTickets.insert(nextResolutionTicket, result); m.setResolving(true, nextResolutionTicket); nextResolutionTicket++; @@ -388,7 +388,7 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const } case Qt::ToolTipRole: - return mods[row].mmc_id(); + return mods[row].internal_id(); case Qt::CheckStateRole: switch (column) @@ -443,11 +443,11 @@ bool ModFolderModel::setModStatus(int row, ModFolderModel::ModStatusAction actio } // preserve the row, but change its ID - auto oldId = mod.mmc_id(); + auto oldId = mod.internal_id(); if(!mod.enable(!mod.enabled())) { return false; } - auto newId = mod.mmc_id(); + auto newId = mod.internal_id(); if(modsIndex.contains(newId)) { // NOTE: this could handle a corner case, where we are overwriting a file, because the same 'mod' exists both enabled and disabled // But is it necessary? diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index a7bec5ae5..3354732b2 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -35,7 +35,6 @@ std::shared_ptr ReadMCModInfo(QByteArray contents) details->name = name; } details->version = firstObj.value("version").toString(); - details->updateurl = firstObj.value("updateUrl").toString(); auto homeurl = firstObj.value("url").toString().trimmed(); if(!homeurl.isEmpty()) { @@ -57,7 +56,6 @@ std::shared_ptr ReadMCModInfo(QByteArray contents) { details->authors.append(author.toString()); } - details->credits = firstObj.value("credits").toString(); return details; }; QJsonParseError jsonError; @@ -168,27 +166,9 @@ std::shared_ptr ReadMCModTOML(QByteArray contents) } if(!authors.isEmpty()) { - // author information is stored as a string now, not a list details->authors.append(authors); } - // is credits even used anywhere? including this for completion/parity with old data version - toml_datum_t creditsDatum = toml_string_in(tomlData, "credits"); - QString credits = ""; - if(creditsDatum.ok) - { - authors = creditsDatum.u.s; - free(creditsDatum.u.s); - } - else - { - creditsDatum = toml_string_in(tomlModsTable0, "credits"); - if(creditsDatum.ok) - { - credits = creditsDatum.u.s; - free(creditsDatum.u.s); - } - } - details->credits = credits; + toml_datum_t homeurlDatum = toml_string_in(tomlData, "displayURL"); QString homeurl = ""; if(homeurlDatum.ok) diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index fd4d60083..bf7b28d63 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -3,32 +3,30 @@ #include "modplatform/packwiz/Packwiz.h" -ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir) : - m_mods_dir(mods_dir), m_index_dir(index_dir), m_result(new Result()) -{ -} +ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir) + : m_mods_dir(mods_dir), m_index_dir(index_dir), m_result(new Result()) +{} void ModFolderLoadTask::run() { // Read metadata first m_index_dir.refresh(); - for(auto entry : m_index_dir.entryList()){ + for (auto entry : m_index_dir.entryList()) { // QDir::Filter::NoDotAndDotDot seems to exclude all files for some reason... - if(entry == "." || entry == "..") + if (entry == "." || entry == "..") continue; - entry.chop(5); // Remove .toml at the end + entry.chop(5); // Remove .toml at the end Mod mod(m_mods_dir, Packwiz::getIndexForMod(m_index_dir, entry)); - m_result->mods[mod.mmc_id()] = mod; + m_result->mods[mod.internal_id()] = mod; } // Read JAR files that don't have metadata m_mods_dir.refresh(); - for (auto entry : m_mods_dir.entryInfoList()) - { + for (auto entry : m_mods_dir.entryInfoList()) { Mod mod(entry); - if(!m_result->mods.contains(mod.mmc_id())) - m_result->mods[mod.mmc_id()] = mod; + if (!m_result->mods.contains(mod.internal_id())) + m_result->mods[mod.internal_id()] = mod; } emit succeeded(); diff --git a/launcher/ui/widgets/MCModInfoFrame.cpp b/launcher/ui/widgets/MCModInfoFrame.cpp index 8c4bd690f..7d78006bd 100644 --- a/launcher/ui/widgets/MCModInfoFrame.cpp +++ b/launcher/ui/widgets/MCModInfoFrame.cpp @@ -32,7 +32,7 @@ void MCModInfoFrame::updateWithMod(Mod &m) QString text = ""; QString name = ""; if (m.name().isEmpty()) - name = m.mmc_id(); + name = m.internal_id(); else name = m.name(); From 092d2f8917271264871d69239ecb8836b34d0994 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 15 Apr 2022 22:37:10 -0300 Subject: [PATCH 080/308] feat: add support for converting builtin -> packwiz mod formats Also adds more documentation. --- launcher/modplatform/packwiz/Packwiz.cpp | 41 ++++++++++++++++++++---- launcher/modplatform/packwiz/Packwiz.h | 36 +++++++++++++-------- 2 files changed, 58 insertions(+), 19 deletions(-) diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index bfadf7cb0..445d64fba 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -1,12 +1,14 @@ #include "Packwiz.h" -#include "modplatform/ModIndex.h" -#include "toml.h" - #include #include #include +#include "toml.h" + +#include "modplatform/ModIndex.h" +#include "minecraft/mod/Mod.h" + // Helpers static inline QString indexFileName(QString const& mod_name) { @@ -31,12 +33,39 @@ auto Packwiz::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pac return mod; } +auto Packwiz::createModFormat(QDir& index_dir, ::Mod& internal_mod) -> Mod +{ + auto mod_name = internal_mod.name(); + + // Try getting metadata if it exists + Mod mod { getIndexForMod(index_dir, mod_name) }; + if(mod.isValid()) + return mod; + + // Manually construct packwiz mod + mod.name = internal_mod.name(); + mod.filename = internal_mod.filename().fileName(); + + // TODO: Have a mechanism for telling the UI subsystem that we want to gather user information + // (i.e. which mod provider we want to use). Maybe an object parameter with a signal for that? + + return mod; +} + void Packwiz::updateModIndex(QDir& index_dir, Mod& mod) { + if(!mod.isValid()){ + qCritical() << QString("Tried to update metadata of an invalid mod!"); + return; + } + // Ensure the corresponding mod's info exists, and create it if not QFile index_file(index_dir.absoluteFilePath(indexFileName(mod.name))); // There's already data on there! + // TODO: We should do more stuff here, as the user is likely trying to + // override a file. In this case, check versions and ask the user what + // they want to do! if (index_file.exists()) { index_file.remove(); } if (!index_file.open(QIODevice::ReadWrite)) { @@ -87,11 +116,11 @@ auto Packwiz::getIndexForMod(QDir& index_dir, QString& mod_name) -> Mod if (!index_file.exists()) { qWarning() << QString("Tried to get a non-existent mod metadata for %1").arg(mod_name); - return mod; + return {}; } if (!index_file.open(QIODevice::ReadOnly)) { qWarning() << QString("Failed to open mod metadata for %1").arg(mod_name); - return mod; + return {}; } toml_table_t* table; @@ -103,7 +132,7 @@ auto Packwiz::getIndexForMod(QDir& index_dir, QString& mod_name) -> Mod if (!table) { qCritical() << QString("Could not open file %1!").arg(indexFileName(mod.name)); - return mod; + return {}; } // Helper function for extracting data from the TOML file diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index 541059d08..457d268a4 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -6,33 +6,43 @@ #include #include -namespace ModPlatform { -} // namespace ModPlatform - class QDir; +// Mod from launcher/minecraft/mod/Mod.h +class Mod; + class Packwiz { public: struct Mod { - QString name; - QString filename; + QString name {}; + QString filename {}; // FIXME: make side an enum - QString side = "both"; + QString side {"both"}; // [download] - QUrl url; + QUrl url {}; // FIXME: make hash-format an enum - QString hash_format; - QString hash; + QString hash_format {}; + QString hash {}; // [update] - ModPlatform::Provider provider; - QVariant file_id; - QVariant project_id; + ModPlatform::Provider provider {}; + QVariant file_id {}; + QVariant project_id {}; + + public: + // This is a heuristic, but should work for now. + auto isValid() const -> bool { return !name.isEmpty(); } }; - /* Generates the object representing the information in a mod.toml file via its common representation in the launcher */ + /* Generates the object representing the information in a mod.toml file via + * its common representation in the launcher, when downloading mods. + * */ static auto createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod; + /* Generates the object representing the information in a mod.toml file via + * its common representation in the launcher. + * */ + static auto createModFormat(QDir& index_dir, ::Mod& internal_mod) -> Mod; /* Updates the mod index for the provided mod. * This creates a new index if one does not exist already From fab4a7a6029beb60bade312ee89e649202d178de Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 16 Apr 2022 13:27:29 -0300 Subject: [PATCH 081/308] refactor: abstract metadata handling and clarify names --- launcher/CMakeLists.txt | 1 + launcher/MMCZip.cpp | 14 +++---- launcher/minecraft/MinecraftInstance.cpp | 10 ++--- launcher/minecraft/mod/MetadataHandler.h | 41 +++++++++++++++++++ launcher/minecraft/mod/Mod.cpp | 6 +-- launcher/minecraft/mod/Mod.h | 7 ++-- launcher/minecraft/mod/ModFolderModel.cpp | 2 +- .../mod/tasks/LocalModUpdateTask.cpp | 6 +-- .../minecraft/mod/tasks/ModFolderLoadTask.cpp | 4 +- launcher/modplatform/packwiz/Packwiz.cpp | 16 +++++--- launcher/modplatform/packwiz/Packwiz.h | 6 ++- 11 files changed, 82 insertions(+), 31 deletions(-) create mode 100644 launcher/minecraft/mod/MetadataHandler.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index b6df28510..03d68e665 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -322,6 +322,7 @@ set(MINECRAFT_SOURCES minecraft/WorldList.h minecraft/WorldList.cpp + minecraft/mod/MetadataHandler.h minecraft/mod/Mod.h minecraft/mod/Mod.cpp minecraft/mod/ModDetails.h diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 8591fcc06..627ceaf10 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -151,23 +151,23 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const continue; if (mod.type() == Mod::MOD_ZIPFILE) { - if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles)) + if (!mergeZipFiles(&zipOut, mod.fileinfo(), addedFiles)) { zipOut.close(); QFile::remove(targetJarPath); - qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; + qCritical() << "Failed to add" << mod.fileinfo().fileName() << "to the jar."; return false; } } else if (mod.type() == Mod::MOD_SINGLEFILE) { // FIXME: buggy - does not work with addedFiles - auto filename = mod.filename(); + auto filename = mod.fileinfo(); if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) { zipOut.close(); QFile::remove(targetJarPath); - qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; + qCritical() << "Failed to add" << mod.fileinfo().fileName() << "to the jar."; return false; } addedFiles.insert(filename.fileName()); @@ -176,7 +176,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const { // untested, but seems to be unused / not possible to reach // FIXME: buggy - does not work with addedFiles - auto filename = mod.filename(); + auto filename = mod.fileinfo(); QString what_to_zip = filename.absoluteFilePath(); QDir dir(what_to_zip); dir.cdUp(); @@ -193,7 +193,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const { zipOut.close(); QFile::remove(targetJarPath); - qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; + qCritical() << "Failed to add" << mod.fileinfo().fileName() << "to the jar."; return false; } qDebug() << "Adding folder " << filename.fileName() << " from " @@ -204,7 +204,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const // Make sure we do not continue launching when something is missing or undefined... zipOut.close(); QFile::remove(targetJarPath); - qCritical() << "Failed to add unknown mod type" << mod.filename().fileName() << "to the jar."; + qCritical() << "Failed to add unknown mod type" << mod.fileinfo().fileName() << "to the jar."; return false; } } diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 61326fac8..2f3390142 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -659,23 +659,23 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr out << QString("%1:").arg(label); auto modList = model.allMods(); std::sort(modList.begin(), modList.end(), [](Mod &a, Mod &b) { - auto aName = a.filename().completeBaseName(); - auto bName = b.filename().completeBaseName(); + auto aName = a.fileinfo().completeBaseName(); + auto bName = b.fileinfo().completeBaseName(); return aName.localeAwareCompare(bName) < 0; }); for(auto & mod: modList) { if(mod.type() == Mod::MOD_FOLDER) { - out << u8" [📁] " + mod.filename().completeBaseName() + " (folder)"; + out << u8" [📁] " + mod.fileinfo().completeBaseName() + " (folder)"; continue; } if(mod.enabled()) { - out << u8" [✔️] " + mod.filename().completeBaseName(); + out << u8" [✔️] " + mod.fileinfo().completeBaseName(); } else { - out << u8" [❌] " + mod.filename().completeBaseName() + " (disabled)"; + out << u8" [❌] " + mod.fileinfo().completeBaseName() + " (disabled)"; } } diff --git a/launcher/minecraft/mod/MetadataHandler.h b/launcher/minecraft/mod/MetadataHandler.h new file mode 100644 index 000000000..26b1f7993 --- /dev/null +++ b/launcher/minecraft/mod/MetadataHandler.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include "modplatform/packwiz/Packwiz.h" + +// launcher/minecraft/mod/Mod.h +class Mod; + +/* Abstraction file for easily changing the way metadata is stored / handled + * Needs to be a class because of -Wunused-function and no C++17 [[maybe_unused]] + * */ +class Metadata { + public: + using ModStruct = Packwiz::V1::Mod; + + static auto create(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct + { + return Packwiz::V1::createModFormat(index_dir, mod_pack, mod_version); + } + + static auto create(QDir& index_dir, Mod& internal_mod) -> ModStruct + { + return Packwiz::V1::createModFormat(index_dir, internal_mod); + } + + static void update(QDir& index_dir, ModStruct& mod) + { + Packwiz::V1::updateModIndex(index_dir, mod); + } + + static void remove(QDir& index_dir, QString& mod_name) + { + Packwiz::V1::deleteModIndex(index_dir, mod_name); + } + + static auto get(QDir& index_dir, QString& mod_name) -> ModStruct + { + return Packwiz::V1::getIndexForMod(index_dir, mod_name); + } +}; diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 64c9ffb57..5b35156d1 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -20,6 +20,7 @@ #include #include +#include "MetadataHandler.h" namespace { @@ -33,7 +34,7 @@ Mod::Mod(const QFileInfo& file) m_changedDateTime = file.lastModified(); } -Mod::Mod(const QDir& mods_dir, const Packwiz::Mod& metadata) +Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata) : m_file(mods_dir.absoluteFilePath(metadata.filename)) // It is weird, but name is not reliable for comparing with the JAR files name // FIXME: Maybe use hash when implemented? @@ -121,8 +122,7 @@ bool Mod::enable(bool value) bool Mod::destroy(QDir& index_dir) { - // Delete metadata - Packwiz::deleteModIndex(index_dir, m_name); + Metadata::remove(index_dir, m_name); m_type = MOD_UNKNOWN; return FS::deletePath(m_file.filePath()); diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 46bb1a596..fef8cbe4d 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -21,7 +21,7 @@ #include #include "ModDetails.h" -#include "modplatform/packwiz/Packwiz.h" +#include "minecraft/mod/MetadataHandler.h" class Mod { @@ -37,9 +37,9 @@ public: Mod() = default; Mod(const QFileInfo &file); - explicit Mod(const QDir& mods_dir, const Packwiz::Mod& metadata); + explicit Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata); - QFileInfo filename() const { return m_file; } + QFileInfo fileinfo() const { return m_file; } QDateTime dateTimeChanged() const { return m_changedDateTime; } QString internal_id() const { return m_internal_id; } ModType type() const { return m_type; } @@ -82,6 +82,7 @@ protected: QDateTime m_changedDateTime; QString m_internal_id; + /* Name as reported via the file name */ QString m_name; ModType m_type = MOD_UNKNOWN; bool m_from_metadata = false; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index e2e041eb2..e034e35e6 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -180,7 +180,7 @@ void ModFolderModel::resolveMod(Mod& m) return; } - auto task = new LocalModParseTask(nextResolutionTicket, m.type(), m.filename()); + auto task = new LocalModParseTask(nextResolutionTicket, m.type(), m.fileinfo()); auto result = task->result(); result->id = m.internal_id(); activeTickets.insert(nextResolutionTicket, result); diff --git a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp index 63f5cf9a1..8b6e8ec7f 100644 --- a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp @@ -3,7 +3,7 @@ #include #include "FileSystem.h" -#include "modplatform/packwiz/Packwiz.h" +#include "minecraft/mod/MetadataHandler.h" LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version) : m_index_dir(index_dir), m_mod(mod), m_mod_version(mod_version) @@ -18,8 +18,8 @@ void LocalModUpdateTask::executeTask() { setStatus(tr("Updating index for mod:\n%1").arg(m_mod.name)); - auto pw_mod = Packwiz::createModFormat(m_index_dir, m_mod, m_mod_version); - Packwiz::updateModIndex(m_index_dir, pw_mod); + auto pw_mod = Metadata::create(m_index_dir, m_mod, m_mod_version); + Metadata::update(m_index_dir, pw_mod); emitSucceeded(); } diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index bf7b28d63..e94bdee90 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -1,7 +1,7 @@ #include "ModFolderLoadTask.h" #include -#include "modplatform/packwiz/Packwiz.h" +#include "minecraft/mod/MetadataHandler.h" ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir) : m_mods_dir(mods_dir), m_index_dir(index_dir), m_result(new Result()) @@ -17,7 +17,7 @@ void ModFolderLoadTask::run() continue; entry.chop(5); // Remove .toml at the end - Mod mod(m_mods_dir, Packwiz::getIndexForMod(m_index_dir, entry)); + Mod mod(m_mods_dir, Metadata::get(m_index_dir, entry)); m_result->mods[mod.internal_id()] = mod; } diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 445d64fba..27339c2db 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -9,13 +9,15 @@ #include "modplatform/ModIndex.h" #include "minecraft/mod/Mod.h" +namespace Packwiz { + // Helpers static inline QString indexFileName(QString const& mod_name) { return QString("%1.toml").arg(mod_name); } -auto Packwiz::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod +auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod { Mod mod; @@ -33,7 +35,7 @@ auto Packwiz::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pac return mod; } -auto Packwiz::createModFormat(QDir& index_dir, ::Mod& internal_mod) -> Mod +auto V1::createModFormat(QDir& index_dir, ::Mod& internal_mod) -> Mod { auto mod_name = internal_mod.name(); @@ -44,7 +46,7 @@ auto Packwiz::createModFormat(QDir& index_dir, ::Mod& internal_mod) -> Mod // Manually construct packwiz mod mod.name = internal_mod.name(); - mod.filename = internal_mod.filename().fileName(); + mod.filename = internal_mod.fileinfo().fileName(); // TODO: Have a mechanism for telling the UI subsystem that we want to gather user information // (i.e. which mod provider we want to use). Maybe an object parameter with a signal for that? @@ -52,7 +54,7 @@ auto Packwiz::createModFormat(QDir& index_dir, ::Mod& internal_mod) -> Mod return mod; } -void Packwiz::updateModIndex(QDir& index_dir, Mod& mod) +void V1::updateModIndex(QDir& index_dir, Mod& mod) { if(!mod.isValid()){ qCritical() << QString("Tried to update metadata of an invalid mod!"); @@ -94,7 +96,7 @@ void Packwiz::updateModIndex(QDir& index_dir, Mod& mod) } } -void Packwiz::deleteModIndex(QDir& index_dir, QString& mod_name) +void V1::deleteModIndex(QDir& index_dir, QString& mod_name) { QFile index_file(index_dir.absoluteFilePath(indexFileName(mod_name))); @@ -108,7 +110,7 @@ void Packwiz::deleteModIndex(QDir& index_dir, QString& mod_name) } } -auto Packwiz::getIndexForMod(QDir& index_dir, QString& mod_name) -> Mod +auto V1::getIndexForMod(QDir& index_dir, QString& mod_name) -> Mod { Mod mod; @@ -201,3 +203,5 @@ auto Packwiz::getIndexForMod(QDir& index_dir, QString& mod_name) -> Mod return mod; } + +} // namespace Packwiz diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index 457d268a4..777a365fc 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -11,7 +11,9 @@ class QDir; // Mod from launcher/minecraft/mod/Mod.h class Mod; -class Packwiz { +namespace Packwiz { + +class V1 { public: struct Mod { QString name {}; @@ -58,3 +60,5 @@ class Packwiz { * */ static auto getIndexForMod(QDir& index_dir, QString& mod_name) -> Mod; }; + +} // namespace Packwiz From 23febc6d94bcc5903a9863ba7b854b5091b0813b Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 17 Apr 2022 09:30:32 -0300 Subject: [PATCH 082/308] feat: cache metadata in ModDetails Allows for more easy access to the metadata by outside entities --- launcher/minecraft/mod/Mod.cpp | 14 ++++++++++++++ launcher/minecraft/mod/Mod.h | 14 ++++++++------ launcher/minecraft/mod/ModDetails.h | 7 +++++++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 5b35156d1..46776239f 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -55,6 +55,8 @@ Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata) m_from_metadata = true; m_enabled = true; m_changedDateTime = m_file.lastModified(); + + m_temp_metadata = std::make_shared(std::move(metadata)); } void Mod::repath(const QFileInfo& file) @@ -161,3 +163,15 @@ QStringList Mod::authors() const { return details().authors; } + +void Mod::finishResolvingWithDetails(std::shared_ptr details) +{ + m_resolving = false; + m_resolved = true; + m_localDetails = details; + + if (fromMetadata() && m_temp_metadata->isValid()) { + m_localDetails->metadata = m_temp_metadata; + m_temp_metadata.reset(); + } +} diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index fef8cbe4d..0d49d94ba 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -18,7 +18,6 @@ #include #include #include -#include #include "ModDetails.h" #include "minecraft/mod/MetadataHandler.h" @@ -55,6 +54,9 @@ public: QString description() const; QStringList authors() const; + const std::shared_ptr metadata() const { return details().metadata; }; + std::shared_ptr metadata() { return m_localDetails->metadata; }; + bool enable(bool value); // delete all the files of this mod @@ -71,11 +73,7 @@ public: m_resolving = resolving; m_resolutionTicket = resolutionTicket; } - void finishResolvingWithDetails(std::shared_ptr details){ - m_resolving = false; - m_resolved = true; - m_localDetails = details; - } + void finishResolvingWithDetails(std::shared_ptr details); protected: QFileInfo m_file; @@ -86,6 +84,10 @@ protected: QString m_name; ModType m_type = MOD_UNKNOWN; bool m_from_metadata = false; + + /* If the mod has metadata, this will be filled in the constructor, and passed to + * the ModDetails when calling finishResolvingWithDetails */ + std::shared_ptr m_temp_metadata; std::shared_ptr m_localDetails; bool m_enabled = true; diff --git a/launcher/minecraft/mod/ModDetails.h b/launcher/minecraft/mod/ModDetails.h index d8d4f66fb..f9973fc2a 100644 --- a/launcher/minecraft/mod/ModDetails.h +++ b/launcher/minecraft/mod/ModDetails.h @@ -1,8 +1,12 @@ #pragma once +#include + #include #include +#include "minecraft/mod/MetadataHandler.h" + struct ModDetails { /* Mod ID as defined in the ModLoader-specific metadata */ @@ -25,4 +29,7 @@ struct ModDetails /* List of the author's names */ QStringList authors; + + /* Metadata information, if any */ + std::shared_ptr metadata; }; From 4439666e67573a6a36af981fdc68410fdf9e4f9f Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 17 Apr 2022 10:19:23 -0300 Subject: [PATCH 083/308] feat: allow disabling mod metadata usage --- launcher/Application.cpp | 3 + launcher/minecraft/mod/Mod.cpp | 6 +- .../mod/tasks/LocalModUpdateTask.cpp | 6 ++ .../minecraft/mod/tasks/ModFolderLoadTask.cpp | 28 +++--- .../minecraft/mod/tasks/ModFolderLoadTask.h | 4 + launcher/ui/pages/global/LauncherPage.cpp | 12 +++ launcher/ui/pages/global/LauncherPage.h | 1 + launcher/ui/pages/global/LauncherPage.ui | 87 ++++++++++++------- 8 files changed, 107 insertions(+), 40 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index ba4096b64..ae4cbcf85 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -643,6 +643,9 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // Minecraft launch method m_settings->registerSetting("MCLaunchMethod", "LauncherPart"); + // Minecraft mods + m_settings->registerSetting("DontUseModMetadata", false); + // Minecraft offline player name m_settings->registerSetting("LastOfflinePlayerName", ""); diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 46776239f..7b5608454 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -124,7 +124,11 @@ bool Mod::enable(bool value) bool Mod::destroy(QDir& index_dir) { - Metadata::remove(index_dir, m_name); + auto n = name(); + // FIXME: This can fail to remove the metadata if the + // "DontUseModMetadata" setting is on, since there could + // be a name mismatch! + Metadata::remove(index_dir, n); m_type = MOD_UNKNOWN; return FS::deletePath(m_file.filePath()); diff --git a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp index 8b6e8ec7f..3c9b76a87 100644 --- a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp @@ -2,6 +2,7 @@ #include +#include "Application.h" #include "FileSystem.h" #include "minecraft/mod/MetadataHandler.h" @@ -18,6 +19,11 @@ void LocalModUpdateTask::executeTask() { setStatus(tr("Updating index for mod:\n%1").arg(m_mod.name)); + if(APPLICATION->settings()->get("DontUseModMetadata").toBool()){ + emitSucceeded(); + return; + } + auto pw_mod = Metadata::create(m_index_dir, m_mod, m_mod_version); Metadata::update(m_index_dir, pw_mod); diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index e94bdee90..5afbb08a9 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -1,6 +1,7 @@ #include "ModFolderLoadTask.h" #include +#include "Application.h" #include "minecraft/mod/MetadataHandler.h" ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir) @@ -9,16 +10,9 @@ ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir) void ModFolderLoadTask::run() { - // Read metadata first - m_index_dir.refresh(); - for (auto entry : m_index_dir.entryList()) { - // QDir::Filter::NoDotAndDotDot seems to exclude all files for some reason... - if (entry == "." || entry == "..") - continue; - - entry.chop(5); // Remove .toml at the end - Mod mod(m_mods_dir, Metadata::get(m_index_dir, entry)); - m_result->mods[mod.internal_id()] = mod; + if (!APPLICATION->settings()->get("DontUseModMetadata").toBool()) { + // Read metadata first + getFromMetadata(); } // Read JAR files that don't have metadata @@ -31,3 +25,17 @@ void ModFolderLoadTask::run() emit succeeded(); } + +void ModFolderLoadTask::getFromMetadata() +{ + m_index_dir.refresh(); + for (auto entry : m_index_dir.entryList()) { + // QDir::Filter::NoDotAndDotDot seems to exclude all files for some reason... + if (entry == "." || entry == "..") + continue; + + entry.chop(5); // Remove .toml at the end + Mod mod(m_mods_dir, Metadata::get(m_index_dir, entry)); + m_result->mods[mod.internal_id()] = mod; + } +} diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h index bb66022ac..ba997874e 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h @@ -24,6 +24,10 @@ public: void run(); signals: void succeeded(); + +private: + void getFromMetadata(); + private: QDir& m_mods_dir, m_index_dir; ResultPtr m_result; diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index af2e2cd1b..8754c0ec9 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -184,6 +184,11 @@ void LauncherPage::on_modsDirBrowseBtn_clicked() } } +void LauncherPage::on_metadataDisableBtn_clicked() +{ + ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked()); +} + void LauncherPage::refreshUpdateChannelList() { // Stop listening for selection changes. It's going to change a lot while we update it and @@ -338,6 +343,9 @@ void LauncherPage::applySettings() s->set("InstSortMode", "Name"); break; } + + // Mods + s->set("DontUseModMetadata", ui->metadataDisableBtn->isChecked()); } void LauncherPage::loadSettings() { @@ -440,6 +448,10 @@ void LauncherPage::loadSettings() { ui->sortByNameBtn->setChecked(true); } + + // Mods + ui->metadataDisableBtn->setChecked(s->get("DontUseModMetadata").toBool()); + ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked()); } void LauncherPage::refreshFontPreview() diff --git a/launcher/ui/pages/global/LauncherPage.h b/launcher/ui/pages/global/LauncherPage.h index bbf5d2fee..f38c922e2 100644 --- a/launcher/ui/pages/global/LauncherPage.h +++ b/launcher/ui/pages/global/LauncherPage.h @@ -88,6 +88,7 @@ slots: void on_instDirBrowseBtn_clicked(); void on_modsDirBrowseBtn_clicked(); void on_iconsDirBrowseBtn_clicked(); + void on_metadataDisableBtn_clicked(); /*! * Updates the list of update channels in the combo box. diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index ae7eb73fe..417bbe059 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -94,19 +94,13 @@ Folders - - + + - I&nstances: - - - instDirTextBox + ... - - - @@ -114,28 +108,15 @@ - - - - &Mods: - - - modsDirTextBox - - - - - - - - + + ... - - + + @@ -147,10 +128,58 @@ - - + + + + + - ... + I&nstances: + + + instDirTextBox + + + + + + + + + + &Mods: + + + modsDirTextBox + + + + + + + + + + Mods + + + + + + Disable using metadata provided by mod providers (like Modrinth or Curseforge) for mods. + + + Disable using metadata for mods? + + + + + + + <html><head/><body><p><span style=" font-weight:600; color:#f5c211;">Warning</span><span style=" color:#f5c211;">: Disabling mod metadata may also disable some upcoming QoL features, such as mod updating!</span></p></body></html> + + + true From d7f6b3699074b268fd554bd1eb9da68f1e533355 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 17 Apr 2022 11:40:41 -0300 Subject: [PATCH 084/308] test+fix: add basic tests and fix issues with it --- launcher/CMakeLists.txt | 6 ++ launcher/minecraft/mod/Mod.cpp | 7 +- launcher/minecraft/mod/Mod.h | 1 - .../minecraft/mod/tasks/ModFolderLoadTask.cpp | 10 ++- launcher/modplatform/ModIndex.h | 2 +- launcher/modplatform/packwiz/Packwiz.cpp | 76 ++++++++++++------- launcher/modplatform/packwiz/Packwiz.h | 11 ++- launcher/modplatform/packwiz/Packwiz_test.cpp | 68 +++++++++++++++++ .../packwiz/testdata/borderless-mining.toml | 13 ++++ .../screenshot-to-clipboard-fabric.toml | 13 ++++ 10 files changed, 168 insertions(+), 39 deletions(-) create mode 100644 launcher/modplatform/packwiz/Packwiz_test.cpp create mode 100644 launcher/modplatform/packwiz/testdata/borderless-mining.toml create mode 100644 launcher/modplatform/packwiz/testdata/screenshot-to-clipboard-fabric.toml diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 03d68e665..6c7b5e437 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -551,6 +551,12 @@ set(PACKWIZ_SOURCES modplatform/packwiz/Packwiz.cpp ) +add_unit_test(Packwiz + SOURCES modplatform/packwiz/Packwiz_test.cpp + DATA modplatform/packwiz/testdata + LIBS Launcher_logic + ) + set(TECHNIC_SOURCES modplatform/technic/SingleZipPackInstallTask.h modplatform/technic/SingleZipPackInstallTask.cpp diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 7b5608454..ef3699e82 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -20,6 +20,8 @@ #include #include + +#include "Application.h" #include "MetadataHandler.h" namespace { @@ -174,8 +176,7 @@ void Mod::finishResolvingWithDetails(std::shared_ptr details) m_resolved = true; m_localDetails = details; - if (fromMetadata() && m_temp_metadata->isValid()) { - m_localDetails->metadata = m_temp_metadata; - m_temp_metadata.reset(); + if (fromMetadata() && m_temp_metadata->isValid() && m_localDetails.get()) { + m_localDetails->metadata.swap(m_temp_metadata); } } diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 0d49d94ba..1e7ed1ed5 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -20,7 +20,6 @@ #include #include "ModDetails.h" -#include "minecraft/mod/MetadataHandler.h" class Mod { diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index 5afbb08a9..03a174612 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -34,8 +34,14 @@ void ModFolderLoadTask::getFromMetadata() if (entry == "." || entry == "..") continue; - entry.chop(5); // Remove .toml at the end - Mod mod(m_mods_dir, Metadata::get(m_index_dir, entry)); + auto metadata = Metadata::get(m_index_dir, entry); + // TODO: Don't simply return. Instead, show to the user that the metadata is there, but + // it's not currently 'installed' (i.e. there's no JAR file yet). + if(!metadata.isValid()){ + return; + } + + Mod mod(m_mods_dir, metadata); m_result->mods[mod.internal_id()] = mod; } } diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index c5329772e..ee623b78b 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -19,7 +19,7 @@ class ProviderCapabilities { { switch(p){ case Provider::MODRINTH: - return "sha256"; + return "sha512"; case Provider::FLAME: return "murmur2"; } diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 27339c2db..8fd74a3e7 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -14,6 +14,8 @@ namespace Packwiz { // Helpers static inline QString indexFileName(QString const& mod_name) { + if(mod_name.endsWith(".toml")) + return mod_name; return QString("%1.toml").arg(mod_name); } @@ -91,8 +93,16 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) in_stream << QString("\n[update]\n"); in_stream << QString("[update.%1]\n").arg(ModPlatform::ProviderCapabilities::providerName(mod.provider)); - addToStream("file-id", mod.file_id.toString()); - addToStream("project-id", mod.project_id.toString()); + switch(mod.provider){ + case(ModPlatform::Provider::FLAME): + in_stream << QString("file-id = %1\n").arg(mod.file_id.toString()); + in_stream << QString("project-id = %1\n").arg(mod.project_id.toString()); + break; + case(ModPlatform::Provider::MODRINTH): + addToStream("mod-id", mod.mod_id().toString()); + addToStream("version", mod.version().toString()); + break; + } } } @@ -110,18 +120,44 @@ void V1::deleteModIndex(QDir& index_dir, QString& mod_name) } } -auto V1::getIndexForMod(QDir& index_dir, QString& mod_name) -> Mod +// Helper functions for extracting data from the TOML file +static auto stringEntry(toml_table_t* parent, const char* entry_name) -> QString +{ + toml_datum_t var = toml_string_in(parent, entry_name); + if (!var.ok) { + qCritical() << QString("Failed to read str property '%1' in mod metadata.").arg(entry_name); + return {}; + } + + QString tmp = var.u.s; + free(var.u.s); + + return tmp; +} + +static auto intEntry(toml_table_t* parent, const char* entry_name) -> int +{ + toml_datum_t var = toml_int_in(parent, entry_name); + if (!var.ok) { + qCritical() << QString("Failed to read int property '%1' in mod metadata.").arg(entry_name); + return {}; + } + + return var.u.i; +} + +auto V1::getIndexForMod(QDir& index_dir, QString& index_file_name) -> Mod { Mod mod; - QFile index_file(index_dir.absoluteFilePath(indexFileName(mod_name))); + QFile index_file(index_dir.absoluteFilePath(indexFileName(index_file_name))); if (!index_file.exists()) { - qWarning() << QString("Tried to get a non-existent mod metadata for %1").arg(mod_name); + qWarning() << QString("Tried to get a non-existent mod metadata for %1").arg(index_file_name); return {}; } if (!index_file.open(QIODevice::ReadOnly)) { - qWarning() << QString("Failed to open mod metadata for %1").arg(mod_name); + qWarning() << QString("Failed to open mod metadata for %1").arg(index_file_name); return {}; } @@ -136,29 +172,9 @@ auto V1::getIndexForMod(QDir& index_dir, QString& mod_name) -> Mod qCritical() << QString("Could not open file %1!").arg(indexFileName(mod.name)); return {}; } - - // Helper function for extracting data from the TOML file - auto stringEntry = [&](toml_table_t* parent, const char* entry_name) -> QString { - toml_datum_t var = toml_string_in(parent, entry_name); - if (!var.ok) { - qCritical() << QString("Failed to read property '%1' in mod metadata.").arg(entry_name); - return {}; - } - - QString tmp = var.u.s; - free(var.u.s); - - return tmp; - }; - + { // Basic info mod.name = stringEntry(table, "name"); - // Basic sanity check - if (mod.name != mod_name) { - qCritical() << QString("Name mismatch in mod metadata:\nExpected:%1\nGot:%2").arg(mod_name, mod.name); - return {}; - } - mod.filename = stringEntry(table, "filename"); mod.side = stringEntry(table, "side"); } @@ -188,15 +204,17 @@ auto V1::getIndexForMod(QDir& index_dir, QString& mod_name) -> Mod toml_table_t* mod_provider_table; if ((mod_provider_table = toml_table_in(update_table, ProviderCaps::providerName(Provider::FLAME)))) { mod.provider = Provider::FLAME; + mod.file_id = intEntry(mod_provider_table, "file-id"); + mod.project_id = intEntry(mod_provider_table, "project-id"); } else if ((mod_provider_table = toml_table_in(update_table, ProviderCaps::providerName(Provider::MODRINTH)))) { mod.provider = Provider::MODRINTH; + mod.mod_id() = stringEntry(mod_provider_table, "mod-id"); + mod.version() = stringEntry(mod_provider_table, "version"); } else { qCritical() << QString("No mod provider on mod metadata!"); return {}; } - mod.file_id = stringEntry(mod_provider_table, "file-id"); - mod.project_id = stringEntry(mod_provider_table, "project-id"); } toml_free(table); diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index 777a365fc..69125dbc2 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -33,8 +33,13 @@ class V1 { QVariant project_id {}; public: - // This is a heuristic, but should work for now. - auto isValid() const -> bool { return !name.isEmpty(); } + // This is a totally heuristic, but should work for now. + auto isValid() const -> bool { return !name.isEmpty() && !project_id.isNull(); } + + // Different providers can use different names for the same thing + // Modrinth-specific + auto mod_id() -> QVariant& { return project_id; } + auto version() -> QVariant& { return file_id; } }; /* Generates the object representing the information in a mod.toml file via @@ -58,7 +63,7 @@ class V1 { /* Gets the metadata for a mod with a particular name. * If the mod doesn't have a metadata, it simply returns an empty Mod object. * */ - static auto getIndexForMod(QDir& index_dir, QString& mod_name) -> Mod; + static auto getIndexForMod(QDir& index_dir, QString& index_file_name) -> Mod; }; } // namespace Packwiz diff --git a/launcher/modplatform/packwiz/Packwiz_test.cpp b/launcher/modplatform/packwiz/Packwiz_test.cpp new file mode 100644 index 000000000..2e61c1679 --- /dev/null +++ b/launcher/modplatform/packwiz/Packwiz_test.cpp @@ -0,0 +1,68 @@ +#include +#include + +#include "TestUtil.h" +#include "Packwiz.h" + +class PackwizTest : public QObject { + Q_OBJECT + + private slots: + // Files taken from https://github.com/packwiz/packwiz-example-pack + void loadFromFile_Modrinth() + { + QString source = QFINDTESTDATA("testdata"); + + QDir index_dir(source); + QString name_mod("borderless-mining.toml"); + QVERIFY(index_dir.entryList().contains(name_mod)); + + auto metadata = Packwiz::V1::getIndexForMod(index_dir, name_mod); + + QVERIFY(metadata.isValid()); + + QCOMPARE(metadata.name, "Borderless Mining"); + QCOMPARE(metadata.filename, "borderless-mining-1.1.1+1.18.jar"); + QCOMPARE(metadata.side, "client"); + + QCOMPARE(metadata.url, QUrl("https://cdn.modrinth.com/data/kYq5qkSL/versions/1.1.1+1.18/borderless-mining-1.1.1+1.18.jar")); + QCOMPARE(metadata.hash_format, "sha512"); + QCOMPARE(metadata.hash, "c8fe6e15ddea32668822dddb26e1851e5f03834be4bcb2eff9c0da7fdc086a9b6cead78e31a44d3bc66335cba11144ee0337c6d5346f1ba63623064499b3188d"); + + QCOMPARE(metadata.provider, ModPlatform::Provider::MODRINTH); + QCOMPARE(metadata.version(), "ug2qKTPR"); + QCOMPARE(metadata.mod_id(), "kYq5qkSL"); + } + + void loadFromFile_Curseforge() + { + QString source = QFINDTESTDATA("testdata"); + + QDir index_dir(source); + QString name_mod("screenshot-to-clipboard-fabric.toml"); + QVERIFY(index_dir.entryList().contains(name_mod)); + + // Try without the .toml at the end + name_mod.chop(5); + + auto metadata = Packwiz::V1::getIndexForMod(index_dir, name_mod); + + QVERIFY(metadata.isValid()); + + QCOMPARE(metadata.name, "Screenshot to Clipboard (Fabric)"); + QCOMPARE(metadata.filename, "screenshot-to-clipboard-1.0.7-fabric.jar"); + QCOMPARE(metadata.side, "both"); + + QCOMPARE(metadata.url, QUrl("https://edge.forgecdn.net/files/3509/43/screenshot-to-clipboard-1.0.7-fabric.jar")); + QCOMPARE(metadata.hash_format, "murmur2"); + QCOMPARE(metadata.hash, "1781245820"); + + QCOMPARE(metadata.provider, ModPlatform::Provider::FLAME); + QCOMPARE(metadata.file_id, 3509043); + QCOMPARE(metadata.project_id, 327154); + } +}; + +QTEST_GUILESS_MAIN(PackwizTest) + +#include "Packwiz_test.moc" diff --git a/launcher/modplatform/packwiz/testdata/borderless-mining.toml b/launcher/modplatform/packwiz/testdata/borderless-mining.toml new file mode 100644 index 000000000..16545fd43 --- /dev/null +++ b/launcher/modplatform/packwiz/testdata/borderless-mining.toml @@ -0,0 +1,13 @@ +name = "Borderless Mining" +filename = "borderless-mining-1.1.1+1.18.jar" +side = "client" + +[download] +url = "https://cdn.modrinth.com/data/kYq5qkSL/versions/1.1.1+1.18/borderless-mining-1.1.1+1.18.jar" +hash-format = "sha512" +hash = "c8fe6e15ddea32668822dddb26e1851e5f03834be4bcb2eff9c0da7fdc086a9b6cead78e31a44d3bc66335cba11144ee0337c6d5346f1ba63623064499b3188d" + +[update] +[update.modrinth] +mod-id = "kYq5qkSL" +version = "ug2qKTPR" diff --git a/launcher/modplatform/packwiz/testdata/screenshot-to-clipboard-fabric.toml b/launcher/modplatform/packwiz/testdata/screenshot-to-clipboard-fabric.toml new file mode 100644 index 000000000..87d70ada5 --- /dev/null +++ b/launcher/modplatform/packwiz/testdata/screenshot-to-clipboard-fabric.toml @@ -0,0 +1,13 @@ +name = "Screenshot to Clipboard (Fabric)" +filename = "screenshot-to-clipboard-1.0.7-fabric.jar" +side = "both" + +[download] +url = "https://edge.forgecdn.net/files/3509/43/screenshot-to-clipboard-1.0.7-fabric.jar" +hash-format = "murmur2" +hash = "1781245820" + +[update] +[update.curseforge] +file-id = 3509043 +project-id = 327154 From ba50765c306d2907e411bc0ed9a10d990cf42fd3 Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 19 Apr 2022 20:19:51 -0300 Subject: [PATCH 085/308] tidy: apply clang-tidy to some files Mostly the ones created in this PR + Mod.h / Mod.cpp / ModDetails.h --- launcher/minecraft/mod/Mod.cpp | 16 ++++---- launcher/minecraft/mod/Mod.h | 40 +++++++++---------- .../mod/tasks/LocalModUpdateTask.cpp | 2 +- .../minecraft/mod/tasks/LocalModUpdateTask.h | 6 +-- launcher/modplatform/packwiz/Packwiz.cpp | 9 +++-- launcher/modplatform/packwiz/Packwiz_test.cpp | 2 +- 6 files changed, 38 insertions(+), 37 deletions(-) diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index ef3699e82..992b91dc7 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -93,7 +93,7 @@ void Mod::repath(const QFileInfo& file) } } -bool Mod::enable(bool value) +auto Mod::enable(bool value) -> bool { if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER) return false; @@ -124,7 +124,7 @@ bool Mod::enable(bool value) return true; } -bool Mod::destroy(QDir& index_dir) +auto Mod::destroy(QDir& index_dir) -> bool { auto n = name(); // FIXME: This can fail to remove the metadata if the @@ -136,12 +136,12 @@ bool Mod::destroy(QDir& index_dir) return FS::deletePath(m_file.filePath()); } -const ModDetails& Mod::details() const +auto Mod::details() const -> const ModDetails& { return m_localDetails ? *m_localDetails : invalidDetails; } -QString Mod::name() const +auto Mod::name() const -> QString { auto d_name = details().name; if (!d_name.isEmpty()) { @@ -150,22 +150,22 @@ QString Mod::name() const return m_name; } -QString Mod::version() const +auto Mod::version() const -> QString { return details().version; } -QString Mod::homeurl() const +auto Mod::homeurl() const -> QString { return details().homeurl; } -QString Mod::description() const +auto Mod::description() const -> QString { return details().description; } -QStringList Mod::authors() const +auto Mod::authors() const -> QStringList { return details().authors; } diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 1e7ed1ed5..3a0ccfa67 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -37,36 +37,36 @@ public: Mod(const QFileInfo &file); explicit Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata); - QFileInfo fileinfo() const { return m_file; } - QDateTime dateTimeChanged() const { return m_changedDateTime; } - QString internal_id() const { return m_internal_id; } - ModType type() const { return m_type; } - bool fromMetadata() const { return m_from_metadata; } - bool enabled() const { return m_enabled; } + auto fileinfo() const -> QFileInfo { return m_file; } + auto dateTimeChanged() const -> QDateTime { return m_changedDateTime; } + auto internal_id() const -> QString { return m_internal_id; } + auto type() const -> ModType { return m_type; } + auto fromMetadata() const -> bool { return m_from_metadata; } + auto enabled() const -> bool { return m_enabled; } - bool valid() const { return m_type != MOD_UNKNOWN; } + auto valid() const -> bool { return m_type != MOD_UNKNOWN; } - const ModDetails& details() const; - QString name() const; - QString version() const; - QString homeurl() const; - QString description() const; - QStringList authors() const; + auto details() const -> const ModDetails&; + auto name() const -> QString; + auto version() const -> QString; + auto homeurl() const -> QString; + auto description() const -> QString; + auto authors() const -> QStringList; - const std::shared_ptr metadata() const { return details().metadata; }; - std::shared_ptr metadata() { return m_localDetails->metadata; }; + auto metadata() const -> const std::shared_ptr { return details().metadata; }; + auto metadata() -> std::shared_ptr { return m_localDetails->metadata; }; - bool enable(bool value); + auto enable(bool value) -> bool; // delete all the files of this mod - bool destroy(QDir& index_dir); + auto destroy(QDir& index_dir) -> bool; // change the mod's filesystem path (used by mod lists for *MAGIC* purposes) void repath(const QFileInfo &file); - bool shouldResolve() const { return !m_resolving && !m_resolved; } - bool isResolving() const { return m_resolving; } - int resolutionTicket() const { return m_resolutionTicket; } + auto shouldResolve() const -> bool { return !m_resolving && !m_resolved; } + auto isResolving() const -> bool { return m_resolving; } + auto resolutionTicket() const -> int { return m_resolutionTicket; } void setResolving(bool resolving, int resolutionTicket) { m_resolving = resolving; diff --git a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp index 3c9b76a87..47207ada0 100644 --- a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp @@ -30,7 +30,7 @@ void LocalModUpdateTask::executeTask() emitSucceeded(); } -bool LocalModUpdateTask::abort() +auto LocalModUpdateTask::abort() -> bool { emitAborted(); return true; diff --git a/launcher/minecraft/mod/tasks/LocalModUpdateTask.h b/launcher/minecraft/mod/tasks/LocalModUpdateTask.h index 866089e9c..15591b21c 100644 --- a/launcher/minecraft/mod/tasks/LocalModUpdateTask.h +++ b/launcher/minecraft/mod/tasks/LocalModUpdateTask.h @@ -2,8 +2,8 @@ #include -#include "tasks/Task.h" #include "modplatform/ModIndex.h" +#include "tasks/Task.h" class LocalModUpdateTask : public Task { Q_OBJECT @@ -12,8 +12,8 @@ class LocalModUpdateTask : public Task { explicit LocalModUpdateTask(QDir mods_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version); - bool canAbort() const override { return true; } - bool abort() override; + auto canAbort() const -> bool override { return true; } + auto abort() -> bool override; protected slots: //! Entry point for tasks. diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 8fd74a3e7..978be4621 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -6,13 +6,13 @@ #include "toml.h" -#include "modplatform/ModIndex.h" #include "minecraft/mod/Mod.h" +#include "modplatform/ModIndex.h" namespace Packwiz { // Helpers -static inline QString indexFileName(QString const& mod_name) +static inline auto indexFileName(QString const& mod_name) -> QString { if(mod_name.endsWith(".toml")) return mod_name; @@ -161,8 +161,9 @@ auto V1::getIndexForMod(QDir& index_dir, QString& index_file_name) -> Mod return {}; } - toml_table_t* table; + toml_table_t* table = nullptr; + // NOLINTNEXTLINE(modernize-avoid-c-arrays) char errbuf[200]; table = toml_parse(index_file.readAll().data(), errbuf, sizeof(errbuf)); @@ -201,7 +202,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString& index_file_name) -> Mod return {}; } - toml_table_t* mod_provider_table; + toml_table_t* mod_provider_table = nullptr; if ((mod_provider_table = toml_table_in(update_table, ProviderCaps::providerName(Provider::FLAME)))) { mod.provider = Provider::FLAME; mod.file_id = intEntry(mod_provider_table, "file-id"); diff --git a/launcher/modplatform/packwiz/Packwiz_test.cpp b/launcher/modplatform/packwiz/Packwiz_test.cpp index 2e61c1679..08de332d7 100644 --- a/launcher/modplatform/packwiz/Packwiz_test.cpp +++ b/launcher/modplatform/packwiz/Packwiz_test.cpp @@ -1,8 +1,8 @@ #include #include -#include "TestUtil.h" #include "Packwiz.h" +#include "TestUtil.h" class PackwizTest : public QObject { Q_OBJECT From a99858c64d275303a9f91912a2732746ef6a3c8a Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 19 Apr 2022 21:10:12 -0300 Subject: [PATCH 086/308] refactor: move code out of ModIndex.h Now it's in ModIndex.cpp --- launcher/CMakeLists.txt | 3 +++ launcher/modplatform/ModIndex.cpp | 24 +++++++++++++++++++++ launcher/modplatform/ModIndex.h | 22 ++----------------- launcher/modplatform/flame/FlameAPI.h | 1 + launcher/modplatform/modrinth/ModrinthAPI.h | 1 + launcher/modplatform/packwiz/Packwiz.cpp | 11 +++++----- 6 files changed, 37 insertions(+), 25 deletions(-) create mode 100644 launcher/modplatform/ModIndex.cpp diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 6c7b5e437..1bab7ecb1 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -500,6 +500,9 @@ set(META_SOURCES ) set(API_SOURCES + modplatform/ModIndex.h + modplatform/ModIndex.cpp + modplatform/ModAPI.h modplatform/flame/FlameAPI.h diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp new file mode 100644 index 000000000..eb8be992f --- /dev/null +++ b/launcher/modplatform/ModIndex.cpp @@ -0,0 +1,24 @@ +#include "modplatform/ModIndex.h" + +namespace ModPlatform{ + +auto ProviderCapabilities::name(Provider p) -> const char* +{ + switch(p){ + case Provider::MODRINTH: + return "modrinth"; + case Provider::FLAME: + return "curseforge"; + } +} +auto ProviderCapabilities::hashType(Provider p) -> QString +{ + switch(p){ + case Provider::MODRINTH: + return "sha512"; + case Provider::FLAME: + return "murmur2"; + } +} + +} // namespace ModPlatform diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index ee623b78b..bb5c7c9dc 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -15,26 +15,8 @@ enum class Provider{ class ProviderCapabilities { public: - static QString hashType(Provider p) - { - switch(p){ - case Provider::MODRINTH: - return "sha512"; - case Provider::FLAME: - return "murmur2"; - } - return ""; - } - static const char* providerName(Provider p) - { - switch(p){ - case Provider::MODRINTH: - return "modrinth"; - case Provider::FLAME: - return "curseforge"; - } - return ""; - } + auto name(Provider) -> const char*; + auto hashType(Provider) -> QString; }; struct ModpackAuthor { diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 8bb33d477..e31cf0a16 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -1,5 +1,6 @@ #pragma once +#include "modplatform/ModIndex.h" #include "modplatform/helpers/NetworkModAPI.h" class FlameAPI : public NetworkModAPI { diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index 79bc5175a..f9d35fcd7 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -20,6 +20,7 @@ #include "BuildConfig.h" #include "modplatform/ModAPI.h" +#include "modplatform/ModIndex.h" #include "modplatform/helpers/NetworkModAPI.h" #include diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 978be4621..872da9b15 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -19,6 +19,8 @@ static inline auto indexFileName(QString const& mod_name) -> QString return QString("%1.toml").arg(mod_name); } +static ModPlatform::ProviderCapabilities ProviderCaps; + auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod { Mod mod; @@ -27,7 +29,7 @@ auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, Mo mod.filename = mod_version.fileName; mod.url = mod_version.downloadUrl; - mod.hash_format = ModPlatform::ProviderCapabilities::hashType(mod_pack.provider); + mod.hash_format = ProviderCaps.hashType(mod_pack.provider); mod.hash = ""; // FIXME mod.provider = mod_pack.provider; @@ -92,7 +94,7 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) addToStream("hash", mod.hash); in_stream << QString("\n[update]\n"); - in_stream << QString("[update.%1]\n").arg(ModPlatform::ProviderCapabilities::providerName(mod.provider)); + in_stream << QString("[update.%1]\n").arg(ProviderCaps.name(mod.provider)); switch(mod.provider){ case(ModPlatform::Provider::FLAME): in_stream << QString("file-id = %1\n").arg(mod.file_id.toString()); @@ -193,7 +195,6 @@ auto V1::getIndexForMod(QDir& index_dir, QString& index_file_name) -> Mod } { // [update] info - using ProviderCaps = ModPlatform::ProviderCapabilities; using Provider = ModPlatform::Provider; toml_table_t* update_table = toml_table_in(table, "update"); @@ -203,11 +204,11 @@ auto V1::getIndexForMod(QDir& index_dir, QString& index_file_name) -> Mod } toml_table_t* mod_provider_table = nullptr; - if ((mod_provider_table = toml_table_in(update_table, ProviderCaps::providerName(Provider::FLAME)))) { + if ((mod_provider_table = toml_table_in(update_table, ProviderCaps.name(Provider::FLAME)))) { mod.provider = Provider::FLAME; mod.file_id = intEntry(mod_provider_table, "file-id"); mod.project_id = intEntry(mod_provider_table, "project-id"); - } else if ((mod_provider_table = toml_table_in(update_table, ProviderCaps::providerName(Provider::MODRINTH)))) { + } else if ((mod_provider_table = toml_table_in(update_table, ProviderCaps.name(Provider::MODRINTH)))) { mod.provider = Provider::MODRINTH; mod.mod_id() = stringEntry(mod_provider_table, "mod-id"); mod.version() = stringEntry(mod_provider_table, "version"); From 96e36f060443cbfa6d58df2adca3c8605851b4a3 Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 20 Apr 2022 18:45:39 -0300 Subject: [PATCH 087/308] refactor: make mod metadata presence (or lack of) easier to find out --- launcher/minecraft/mod/Mod.cpp | 28 +++++++++++++++++-- launcher/minecraft/mod/Mod.h | 6 ++-- launcher/minecraft/mod/ModDetails.h | 9 ++++++ .../minecraft/mod/tasks/ModFolderLoadTask.cpp | 8 +++++- launcher/modplatform/ModIndex.cpp | 2 ++ launcher/modplatform/packwiz/Packwiz.cpp | 9 ++---- 6 files changed, 49 insertions(+), 13 deletions(-) diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 992b91dc7..261ae9d29 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -54,7 +54,6 @@ Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata) m_type = MOD_SINGLEFILE; } - m_from_metadata = true; m_enabled = true; m_changedDateTime = m_file.lastModified(); @@ -117,13 +116,27 @@ auto Mod::enable(bool value) -> bool return false; } - if (!fromMetadata()) + if (status() == ModStatus::NoMetadata) repath(QFileInfo(path)); m_enabled = value; return true; } +void Mod::setStatus(ModStatus status) +{ + if(m_localDetails.get()) + m_localDetails->status = status; +} +void Mod::setMetadata(Metadata::ModStruct* metadata) +{ + if(status() == ModStatus::NoMetadata) + setStatus(ModStatus::Installed); + + if(m_localDetails.get()) + m_localDetails->metadata.reset(metadata); +} + auto Mod::destroy(QDir& index_dir) -> bool { auto n = name(); @@ -170,13 +183,22 @@ auto Mod::authors() const -> QStringList return details().authors; } +auto Mod::status() const -> ModStatus +{ + return details().status; +} + void Mod::finishResolvingWithDetails(std::shared_ptr details) { m_resolving = false; m_resolved = true; m_localDetails = details; - if (fromMetadata() && m_temp_metadata->isValid() && m_localDetails.get()) { + if (status() != ModStatus::NoMetadata + && m_temp_metadata.get() + && m_temp_metadata->isValid() && + m_localDetails.get()) { + m_localDetails->metadata.swap(m_temp_metadata); } } diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 3a0ccfa67..58c7a80fa 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -41,7 +41,6 @@ public: auto dateTimeChanged() const -> QDateTime { return m_changedDateTime; } auto internal_id() const -> QString { return m_internal_id; } auto type() const -> ModType { return m_type; } - auto fromMetadata() const -> bool { return m_from_metadata; } auto enabled() const -> bool { return m_enabled; } auto valid() const -> bool { return m_type != MOD_UNKNOWN; } @@ -52,10 +51,14 @@ public: auto homeurl() const -> QString; auto description() const -> QString; auto authors() const -> QStringList; + auto status() const -> ModStatus; auto metadata() const -> const std::shared_ptr { return details().metadata; }; auto metadata() -> std::shared_ptr { return m_localDetails->metadata; }; + void setStatus(ModStatus status); + void setMetadata(Metadata::ModStruct* metadata); + auto enable(bool value) -> bool; // delete all the files of this mod @@ -82,7 +85,6 @@ protected: /* Name as reported via the file name */ QString m_name; ModType m_type = MOD_UNKNOWN; - bool m_from_metadata = false; /* If the mod has metadata, this will be filled in the constructor, and passed to * the ModDetails when calling finishResolvingWithDetails */ diff --git a/launcher/minecraft/mod/ModDetails.h b/launcher/minecraft/mod/ModDetails.h index f9973fc2a..75ffea324 100644 --- a/launcher/minecraft/mod/ModDetails.h +++ b/launcher/minecraft/mod/ModDetails.h @@ -7,6 +7,12 @@ #include "minecraft/mod/MetadataHandler.h" +enum class ModStatus { + Installed, // Both JAR and Metadata are present + NotInstalled, // Only the Metadata is present + NoMetadata, // Only the JAR is present +}; + struct ModDetails { /* Mod ID as defined in the ModLoader-specific metadata */ @@ -30,6 +36,9 @@ struct ModDetails /* List of the author's names */ QStringList authors; + /* Installation status of the mod */ + ModStatus status; + /* Metadata information, if any */ std::shared_ptr metadata; }; diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index 03a174612..addb0dd89 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -19,8 +19,13 @@ void ModFolderLoadTask::run() m_mods_dir.refresh(); for (auto entry : m_mods_dir.entryInfoList()) { Mod mod(entry); - if (!m_result->mods.contains(mod.internal_id())) + if(m_result->mods.contains(mod.internal_id())){ + m_result->mods[mod.internal_id()].setStatus(ModStatus::Installed); + } + else { m_result->mods[mod.internal_id()] = mod; + m_result->mods[mod.internal_id()].setStatus(ModStatus::NoMetadata); + } } emit succeeded(); @@ -42,6 +47,7 @@ void ModFolderLoadTask::getFromMetadata() } Mod mod(m_mods_dir, metadata); + mod.setStatus(ModStatus::NotInstalled); m_result->mods[mod.internal_id()] = mod; } } diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp index eb8be992f..b3c057fbb 100644 --- a/launcher/modplatform/ModIndex.cpp +++ b/launcher/modplatform/ModIndex.cpp @@ -10,6 +10,7 @@ auto ProviderCapabilities::name(Provider p) -> const char* case Provider::FLAME: return "curseforge"; } + return {}; } auto ProviderCapabilities::hashType(Provider p) -> QString { @@ -19,6 +20,7 @@ auto ProviderCapabilities::hashType(Provider p) -> QString case Provider::FLAME: return "murmur2"; } + return {}; } } // namespace ModPlatform diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 872da9b15..50f87c248 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -48,14 +48,9 @@ auto V1::createModFormat(QDir& index_dir, ::Mod& internal_mod) -> Mod if(mod.isValid()) return mod; - // Manually construct packwiz mod - mod.name = internal_mod.name(); - mod.filename = internal_mod.fileinfo().fileName(); + qWarning() << QString("Tried to create mod metadata with a Mod without metadata!"); - // TODO: Have a mechanism for telling the UI subsystem that we want to gather user information - // (i.e. which mod provider we want to use). Maybe an object parameter with a signal for that? - - return mod; + return {}; } void V1::updateModIndex(QDir& index_dir, Mod& mod) From e17b6804a7424dd5161662c4ef92972f3311675c Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 21 Apr 2022 15:45:20 -0300 Subject: [PATCH 088/308] fix: implement PR suggestions Some stylistic changes, and get hashes from the mod providers when building the metadata. --- launcher/Application.cpp | 2 +- launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp | 11 +++-------- launcher/modplatform/ModIndex.h | 3 ++- launcher/modplatform/flame/FlameModIndex.cpp | 8 ++++++++ launcher/modplatform/modrinth/ModrinthPackIndex.cpp | 4 ++++ launcher/modplatform/packwiz/Packwiz.cpp | 2 +- launcher/ui/pages/global/LauncherPage.cpp | 2 +- 7 files changed, 20 insertions(+), 12 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index ae4cbcf85..99e3d4c5a 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -644,7 +644,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("MCLaunchMethod", "LauncherPart"); // Minecraft mods - m_settings->registerSetting("DontUseModMetadata", false); + m_settings->registerSetting("ModMetadataDisabled", false); // Minecraft offline player name m_settings->registerSetting("LastOfflinePlayerName", ""); diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index addb0dd89..fe807a29b 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -10,7 +10,7 @@ ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir) void ModFolderLoadTask::run() { - if (!APPLICATION->settings()->get("DontUseModMetadata").toBool()) { + if (!APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { // Read metadata first getFromMetadata(); } @@ -34,14 +34,9 @@ void ModFolderLoadTask::run() void ModFolderLoadTask::getFromMetadata() { m_index_dir.refresh(); - for (auto entry : m_index_dir.entryList()) { - // QDir::Filter::NoDotAndDotDot seems to exclude all files for some reason... - if (entry == "." || entry == "..") - continue; - + for (auto entry : m_index_dir.entryList(QDir::Files)) { auto metadata = Metadata::get(m_index_dir, entry); - // TODO: Don't simply return. Instead, show to the user that the metadata is there, but - // it's not currently 'installed' (i.e. there's no JAR file yet). + if(!metadata.isValid()){ return; } diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index bb5c7c9dc..2137f616d 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -8,7 +8,7 @@ namespace ModPlatform { -enum class Provider{ +enum class Provider { MODRINTH, FLAME }; @@ -33,6 +33,7 @@ struct IndexedVersion { QString date; QString fileName; QVector loaders = {}; + QString hash; }; struct IndexedPack { diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 45f02b71b..4b172c13d 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -6,6 +6,8 @@ #include "modplatform/flame/FlameAPI.h" #include "net/NetJob.h" +static ModPlatform::ProviderCapabilities ProviderCaps; + void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) { pack.addonId = Json::requireInteger(obj, "id"); @@ -60,6 +62,12 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, file.downloadUrl = Json::requireString(obj, "downloadUrl"); file.fileName = Json::requireString(obj, "fileName"); + auto hash_list = Json::ensureArray(obj, "hashes"); + if(!hash_list.isEmpty()){ + if(hash_list.contains(ProviderCaps.hashType(ModPlatform::Provider::FLAME))) + file.hash = Json::requireString(hash_list, "value"); + } + unsortedVersions.append(file); } diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 6c8659dc3..8b750740e 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -24,6 +24,7 @@ #include "net/NetJob.h" static ModrinthAPI api; +static ModPlatform::ProviderCapabilities ProviderCaps; void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) { @@ -95,6 +96,9 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, if (parent.contains("url")) { file.downloadUrl = Json::requireString(parent, "url"); file.fileName = Json::requireString(parent, "filename"); + auto hash_list = Json::requireObject(parent, "hashes"); + if(hash_list.contains(ProviderCaps.hashType(ModPlatform::Provider::MODRINTH))) + file.hash = Json::requireString(hash_list, ProviderCaps.hashType(ModPlatform::Provider::MODRINTH)); unsortedVersions.append(file); } diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 50f87c248..70efc6bd1 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -30,7 +30,7 @@ auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, Mo mod.url = mod_version.downloadUrl; mod.hash_format = ProviderCaps.hashType(mod_pack.provider); - mod.hash = ""; // FIXME + mod.hash = mod_version.hash; mod.provider = mod_pack.provider; mod.file_id = mod_pack.addonId; diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 8754c0ec9..faf9272d7 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -345,7 +345,7 @@ void LauncherPage::applySettings() } // Mods - s->set("DontUseModMetadata", ui->metadataDisableBtn->isChecked()); + s->set("ModMetadataDisabled", ui->metadataDisableBtn->isChecked()); } void LauncherPage::loadSettings() { From 67e0214fa5c1ff36d3718c3fb68107bf0dfe7e5d Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 21 Apr 2022 15:47:46 -0300 Subject: [PATCH 089/308] fix: don't try to delete mods multiple times Shows a more helpful message if there's a parsing error when reading the index file. Also fixes a clazy warning with using the `.data()` method in a temporary QByteArray object. --- launcher/minecraft/mod/ModFolderModel.cpp | 3 +++ launcher/modplatform/packwiz/Packwiz.cpp | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index e034e35e6..b2d8f03eb 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -339,6 +339,9 @@ bool ModFolderModel::deleteMods(const QModelIndexList& indexes) for (auto i: indexes) { + if(i.column() != 0) { + continue; + } Mod &m = mods[i.row()]; auto index_dir = indexDir(); m.destroy(index_dir); diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 70efc6bd1..4fe4398af 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -162,12 +162,14 @@ auto V1::getIndexForMod(QDir& index_dir, QString& index_file_name) -> Mod // NOLINTNEXTLINE(modernize-avoid-c-arrays) char errbuf[200]; - table = toml_parse(index_file.readAll().data(), errbuf, sizeof(errbuf)); + auto file_bytearray = index_file.readAll(); + table = toml_parse(file_bytearray.data(), errbuf, sizeof(errbuf)); index_file.close(); if (!table) { - qCritical() << QString("Could not open file %1!").arg(indexFileName(mod.name)); + qWarning() << QString("Could not open file %1!").arg(indexFileName(index_file_name)); + qWarning() << "Reason: " << QString(errbuf); return {}; } From 5c5699bba5ed2a5befb7c3f8d9fbcd679a8698ab Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 22 Apr 2022 13:23:47 -0300 Subject: [PATCH 090/308] refactor: move individual pack version parsing to its own function --- .../modrinth/ModrinthPackIndex.cpp | 95 +++++++++++-------- .../modplatform/modrinth/ModrinthPackIndex.h | 1 + 2 files changed, 55 insertions(+), 41 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 8b750740e..aa7983817 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -59,49 +59,10 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, for (auto versionIter : arr) { auto obj = versionIter.toObject(); - ModPlatform::IndexedVersion file; - file.addonId = Json::requireString(obj, "project_id"); - file.fileId = Json::requireString(obj, "id"); - file.date = Json::requireString(obj, "date_published"); - auto versionArray = Json::requireArray(obj, "game_versions"); - if (versionArray.empty()) { continue; } - for (auto mcVer : versionArray) { - file.mcVersion.append(mcVer.toString()); - } - auto loaders = Json::requireArray(obj, "loaders"); - for (auto loader : loaders) { - file.loaders.append(loader.toString()); - } - file.version = Json::requireString(obj, "name"); - - auto files = Json::requireArray(obj, "files"); - int i = 0; - - // Find correct file (needed in cases where one version may have multiple files) - // Will default to the last one if there's no primary (though I think Modrinth requires that - // at least one file is primary, idk) - // NOTE: files.count() is 1-indexed, so we need to subtract 1 to become 0-indexed - while (i < files.count() - 1){ - auto parent = files[i].toObject(); - auto fileName = Json::requireString(parent, "filename"); - - // Grab the primary file, if available - if(Json::requireBoolean(parent, "primary")) - break; - - i++; - } - - auto parent = files[i].toObject(); - if (parent.contains("url")) { - file.downloadUrl = Json::requireString(parent, "url"); - file.fileName = Json::requireString(parent, "filename"); - auto hash_list = Json::requireObject(parent, "hashes"); - if(hash_list.contains(ProviderCaps.hashType(ModPlatform::Provider::MODRINTH))) - file.hash = Json::requireString(hash_list, ProviderCaps.hashType(ModPlatform::Provider::MODRINTH)); + auto file = loadIndexedPackVersion(obj); + if(file.fileId.isValid()) // Heuristic to check if the returned value is valid unsortedVersions.append(file); - } } auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { // dates are in RFC 3339 format @@ -111,3 +72,55 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, pack.versions = unsortedVersions; pack.versionsLoaded = true; } + +auto Modrinth::loadIndexedPackVersion(QJsonObject &obj) -> ModPlatform::IndexedVersion +{ + ModPlatform::IndexedVersion file; + + file.addonId = Json::requireString(obj, "project_id"); + file.fileId = Json::requireString(obj, "id"); + file.date = Json::requireString(obj, "date_published"); + auto versionArray = Json::requireArray(obj, "game_versions"); + if (versionArray.empty()) { + return {}; + } + for (auto mcVer : versionArray) { + file.mcVersion.append(mcVer.toString()); + } + auto loaders = Json::requireArray(obj, "loaders"); + for (auto loader : loaders) { + file.loaders.append(loader.toString()); + } + file.version = Json::requireString(obj, "name"); + + auto files = Json::requireArray(obj, "files"); + int i = 0; + + // Find correct file (needed in cases where one version may have multiple files) + // Will default to the last one if there's no primary (though I think Modrinth requires that + // at least one file is primary, idk) + // NOTE: files.count() is 1-indexed, so we need to subtract 1 to become 0-indexed + while (i < files.count() - 1) { + auto parent = files[i].toObject(); + auto fileName = Json::requireString(parent, "filename"); + + // Grab the primary file, if available + if (Json::requireBoolean(parent, "primary")) + break; + + i++; + } + + auto parent = files[i].toObject(); + if (parent.contains("url")) { + file.downloadUrl = Json::requireString(parent, "url"); + file.fileName = Json::requireString(parent, "filename"); + auto hash_list = Json::requireObject(parent, "hashes"); + if (hash_list.contains(ProviderCaps.hashType(ModPlatform::Provider::MODRINTH))) + file.hash = Json::requireString(hash_list, ProviderCaps.hashType(ModPlatform::Provider::MODRINTH)); + + return file; + } + + return {}; +} diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h index 7f306f25f..df70278fc 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.h +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h @@ -29,5 +29,6 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr& network, BaseInstance* inst); +auto loadIndexedPackVersion(QJsonObject& obj) -> ModPlatform::IndexedVersion; } // namespace Modrinth From 59d628208b403bfb2442291cbca139cbdfcd325f Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 6 May 2022 12:42:01 -0300 Subject: [PATCH 091/308] feat: allow trying to use multiple hash types --- launcher/modplatform/ModIndex.cpp | 60 +++++++++++++++---- launcher/modplatform/ModIndex.h | 5 +- launcher/modplatform/flame/FlameModIndex.cpp | 10 +++- .../modrinth/ModrinthPackIndex.cpp | 10 +++- launcher/modplatform/packwiz/Packwiz.cpp | 2 +- 5 files changed, 68 insertions(+), 19 deletions(-) diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp index b3c057fbb..f6e134e06 100644 --- a/launcher/modplatform/ModIndex.cpp +++ b/launcher/modplatform/ModIndex.cpp @@ -1,26 +1,60 @@ #include "modplatform/ModIndex.h" -namespace ModPlatform{ +#include + +namespace ModPlatform { auto ProviderCapabilities::name(Provider p) -> const char* { - switch(p){ - case Provider::MODRINTH: - return "modrinth"; - case Provider::FLAME: - return "curseforge"; + switch (p) { + case Provider::MODRINTH: + return "modrinth"; + case Provider::FLAME: + return "curseforge"; } return {}; } -auto ProviderCapabilities::hashType(Provider p) -> QString +auto ProviderCapabilities::readableName(Provider p) -> QString { - switch(p){ - case Provider::MODRINTH: - return "sha512"; - case Provider::FLAME: - return "murmur2"; + switch (p) { + case Provider::MODRINTH: + return "Modrinth"; + case Provider::FLAME: + return "CurseForge"; + } + return {}; +} +auto ProviderCapabilities::hashType(Provider p) -> QStringList +{ + switch (p) { + case Provider::MODRINTH: + return { "sha512", "sha1" }; + case Provider::FLAME: + return { "murmur2" }; + } + return {}; +} +auto ProviderCapabilities::hash(Provider p, QByteArray& data, QString type) -> QByteArray +{ + switch (p) { + case Provider::MODRINTH: { + // NOTE: Data is the result of reading the entire JAR file! + + // If 'type' was specified, we use that + if (!type.isEmpty() && hashType(p).contains(type)) { + if (type == "sha512") + return QCryptographicHash::hash(data, QCryptographicHash::Sha512); + else if (type == "sha1") + return QCryptographicHash::hash(data, QCryptographicHash::Sha1); + } + + return QCryptographicHash::hash(data, QCryptographicHash::Sha512); + } + case Provider::FLAME: + // TODO + break; } return {}; } -} // namespace ModPlatform +} // namespace ModPlatform diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 2137f616d..8ada1fc69 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -16,7 +16,9 @@ enum class Provider { class ProviderCapabilities { public: auto name(Provider) -> const char*; - auto hashType(Provider) -> QString; + auto readableName(Provider) -> QString; + auto hashType(Provider) -> QStringList; + auto hash(Provider, QByteArray&, QString type = "") -> QByteArray; }; struct ModpackAuthor { @@ -33,6 +35,7 @@ struct IndexedVersion { QString date; QString fileName; QVector loaders = {}; + QString hash_type; QString hash; }; diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 4b172c13d..634112758 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -64,8 +64,14 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, auto hash_list = Json::ensureArray(obj, "hashes"); if(!hash_list.isEmpty()){ - if(hash_list.contains(ProviderCaps.hashType(ModPlatform::Provider::FLAME))) - file.hash = Json::requireString(hash_list, "value"); + auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::FLAME); + for(auto& hash_type : hash_types) { + if(hash_list.contains(hash_type)) { + file.hash = Json::requireString(hash_list, "value"); + file.hash_type = hash_type; + break; + } + } } unsortedVersions.append(file); diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index aa7983817..30693a82b 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -116,8 +116,14 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject &obj) -> ModPlatform::IndexedV file.downloadUrl = Json::requireString(parent, "url"); file.fileName = Json::requireString(parent, "filename"); auto hash_list = Json::requireObject(parent, "hashes"); - if (hash_list.contains(ProviderCaps.hashType(ModPlatform::Provider::MODRINTH))) - file.hash = Json::requireString(hash_list, ProviderCaps.hashType(ModPlatform::Provider::MODRINTH)); + auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH); + for (auto& hash_type : hash_types) { + if (hash_list.contains(hash_type)) { + file.hash = Json::requireString(hash_list, hash_type); + file.hash_type = hash_type; + break; + } + } return file; } diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 4fe4398af..cb430c1fb 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -29,7 +29,7 @@ auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, Mo mod.filename = mod_version.fileName; mod.url = mod_version.downloadUrl; - mod.hash_format = ProviderCaps.hashType(mod_pack.provider); + mod.hash_format = mod_version.hash_type; mod.hash = mod_version.hash; mod.provider = mod_pack.provider; From 0985adfd74758891c2e61c2de7f930119cab1386 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 7 May 2022 19:39:00 -0300 Subject: [PATCH 092/308] change: support newest changes with packwiz regarding CF --- launcher/modplatform/ModIndex.cpp | 12 +++++++-- launcher/modplatform/flame/FlameModIndex.cpp | 25 +++++++++++++------ launcher/modplatform/packwiz/Packwiz.cpp | 15 ++++++++--- launcher/modplatform/packwiz/Packwiz.h | 6 ++--- launcher/modplatform/packwiz/Packwiz_test.cpp | 6 ++--- ...-mining.toml => borderless-mining.pw.toml} | 0 ...=> screenshot-to-clipboard-fabric.pw.toml} | 0 7 files changed, 46 insertions(+), 18 deletions(-) rename launcher/modplatform/packwiz/testdata/{borderless-mining.toml => borderless-mining.pw.toml} (100%) rename launcher/modplatform/packwiz/testdata/{screenshot-to-clipboard-fabric.toml => screenshot-to-clipboard-fabric.pw.toml} (100%) diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp index f6e134e06..6027c4f30 100644 --- a/launcher/modplatform/ModIndex.cpp +++ b/launcher/modplatform/ModIndex.cpp @@ -30,7 +30,8 @@ auto ProviderCapabilities::hashType(Provider p) -> QStringList case Provider::MODRINTH: return { "sha512", "sha1" }; case Provider::FLAME: - return { "murmur2" }; + // Try newer formats first, fall back to old format + return { "sha1", "md5", "murmur2" }; } return {}; } @@ -51,7 +52,14 @@ auto ProviderCapabilities::hash(Provider p, QByteArray& data, QString type) -> Q return QCryptographicHash::hash(data, QCryptographicHash::Sha512); } case Provider::FLAME: - // TODO + // If 'type' was specified, we use that + if (!type.isEmpty() && hashType(p).contains(type)) { + if(type == "sha1") + return QCryptographicHash::hash(data, QCryptographicHash::Sha1); + else if (type == "md5") + return QCryptographicHash::hash(data, QCryptographicHash::Md5); + } + break; } return {}; diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 634112758..00dac4117 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -30,6 +30,17 @@ void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) } } +static QString enumToString(int hash_algorithm) +{ + switch(hash_algorithm){ + default: + case 1: + return "sha1"; + case 2: + return "md5"; + } +} + void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr& network, @@ -63,14 +74,14 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, file.fileName = Json::requireString(obj, "fileName"); auto hash_list = Json::ensureArray(obj, "hashes"); - if(!hash_list.isEmpty()){ + for(auto h : hash_list){ + auto hash_entry = Json::ensureObject(h); auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::FLAME); - for(auto& hash_type : hash_types) { - if(hash_list.contains(hash_type)) { - file.hash = Json::requireString(hash_list, "value"); - file.hash_type = hash_type; - break; - } + auto hash_algo = enumToString(Json::ensureInteger(hash_entry, "algo", 1, "algorithm")); + if(hash_types.contains(hash_algo)){ + file.hash = Json::requireString(hash_entry, "value"); + file.hash_type = hash_algo; + break; } } diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index cb430c1fb..1ad6d75b1 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -14,9 +14,9 @@ namespace Packwiz { // Helpers static inline auto indexFileName(QString const& mod_name) -> QString { - if(mod_name.endsWith(".toml")) + if(mod_name.endsWith(".pw.toml")) return mod_name; - return QString("%1.toml").arg(mod_name); + return QString("%1.pw.toml").arg(mod_name); } static ModPlatform::ProviderCapabilities ProviderCaps; @@ -28,7 +28,14 @@ auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, Mo mod.name = mod_pack.name; mod.filename = mod_version.fileName; - mod.url = mod_version.downloadUrl; + if(mod_pack.provider == ModPlatform::Provider::FLAME){ + mod.mode = "metadata:curseforge"; + } + else { + mod.mode = "url"; + mod.url = mod_version.downloadUrl; + } + mod.hash_format = mod_version.hash_type; mod.hash = mod_version.hash; @@ -84,6 +91,7 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) addToStream("side", mod.side); in_stream << QString("\n[download]\n"); + addToStream("mode", mod.mode); addToStream("url", mod.url.toString()); addToStream("hash-format", mod.hash_format); addToStream("hash", mod.hash); @@ -186,6 +194,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString& index_file_name) -> Mod return {}; } + mod.mode = stringEntry(download_table, "mode"); mod.url = stringEntry(download_table, "url"); mod.hash_format = stringEntry(download_table, "hash-format"); mod.hash = stringEntry(download_table, "hash"); diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index 69125dbc2..e66d0030e 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -22,8 +22,8 @@ class V1 { QString side {"both"}; // [download] + QString mode {}; QUrl url {}; - // FIXME: make hash-format an enum QString hash_format {}; QString hash {}; @@ -42,11 +42,11 @@ class V1 { auto version() -> QVariant& { return file_id; } }; - /* Generates the object representing the information in a mod.toml file via + /* Generates the object representing the information in a mod.pw.toml file via * its common representation in the launcher, when downloading mods. * */ static auto createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod; - /* Generates the object representing the information in a mod.toml file via + /* Generates the object representing the information in a mod.pw.toml file via * its common representation in the launcher. * */ static auto createModFormat(QDir& index_dir, ::Mod& internal_mod) -> Mod; diff --git a/launcher/modplatform/packwiz/Packwiz_test.cpp b/launcher/modplatform/packwiz/Packwiz_test.cpp index 08de332d7..9f3c486e8 100644 --- a/launcher/modplatform/packwiz/Packwiz_test.cpp +++ b/launcher/modplatform/packwiz/Packwiz_test.cpp @@ -14,7 +14,7 @@ class PackwizTest : public QObject { QString source = QFINDTESTDATA("testdata"); QDir index_dir(source); - QString name_mod("borderless-mining.toml"); + QString name_mod("borderless-mining.pw.toml"); QVERIFY(index_dir.entryList().contains(name_mod)); auto metadata = Packwiz::V1::getIndexForMod(index_dir, name_mod); @@ -39,10 +39,10 @@ class PackwizTest : public QObject { QString source = QFINDTESTDATA("testdata"); QDir index_dir(source); - QString name_mod("screenshot-to-clipboard-fabric.toml"); + QString name_mod("screenshot-to-clipboard-fabric.pw.toml"); QVERIFY(index_dir.entryList().contains(name_mod)); - // Try without the .toml at the end + // Try without the .pw.toml at the end name_mod.chop(5); auto metadata = Packwiz::V1::getIndexForMod(index_dir, name_mod); diff --git a/launcher/modplatform/packwiz/testdata/borderless-mining.toml b/launcher/modplatform/packwiz/testdata/borderless-mining.pw.toml similarity index 100% rename from launcher/modplatform/packwiz/testdata/borderless-mining.toml rename to launcher/modplatform/packwiz/testdata/borderless-mining.pw.toml diff --git a/launcher/modplatform/packwiz/testdata/screenshot-to-clipboard-fabric.toml b/launcher/modplatform/packwiz/testdata/screenshot-to-clipboard-fabric.pw.toml similarity index 100% rename from launcher/modplatform/packwiz/testdata/screenshot-to-clipboard-fabric.toml rename to launcher/modplatform/packwiz/testdata/screenshot-to-clipboard-fabric.pw.toml From 3a923060ceee142987e585d7ab4d78642f3506da Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 7 May 2022 10:27:01 -0300 Subject: [PATCH 093/308] fix: use correct hash_type when creating metadata also fix: wrong parameter name in LocalModUpdateTask's constructor also fix: correct hash_format in CF --- launcher/minecraft/mod/tasks/LocalModUpdateTask.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/mod/tasks/LocalModUpdateTask.h b/launcher/minecraft/mod/tasks/LocalModUpdateTask.h index 15591b21c..f21c0b06e 100644 --- a/launcher/minecraft/mod/tasks/LocalModUpdateTask.h +++ b/launcher/minecraft/mod/tasks/LocalModUpdateTask.h @@ -10,7 +10,7 @@ class LocalModUpdateTask : public Task { public: using Ptr = shared_qobject_ptr; - explicit LocalModUpdateTask(QDir mods_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version); + explicit LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version); auto canAbort() const -> bool override { return true; } auto abort() -> bool override; From 2fc1b999117ceebc3ebf05d96b0a749e3a0f9e98 Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 10 May 2022 19:57:47 -0300 Subject: [PATCH 094/308] chore: add license headers Prevents a massive inload of Scrumplex ditto's :) I didn't add it to every file modified in this PR because the other changes are pretty minor, and would explode the diff of the PR. I hope that's not a problem O_O --- launcher/ModDownloadTask.cpp | 20 +++++++- launcher/ModDownloadTask.h | 27 +++++++++-- launcher/minecraft/mod/MetadataHandler.h | 18 +++++++ launcher/minecraft/mod/Mod.cpp | 48 +++++++++++++------ launcher/minecraft/mod/Mod.h | 48 +++++++++++++------ launcher/minecraft/mod/ModDetails.h | 35 ++++++++++++++ .../mod/tasks/LocalModUpdateTask.cpp | 20 +++++++- .../minecraft/mod/tasks/LocalModUpdateTask.h | 18 +++++++ .../minecraft/mod/tasks/ModFolderLoadTask.cpp | 36 +++++++++++++- .../minecraft/mod/tasks/ModFolderLoadTask.h | 35 ++++++++++++++ launcher/modplatform/ModIndex.cpp | 18 +++++++ launcher/modplatform/ModIndex.h | 18 +++++++ .../modrinth/ModrinthPackIndex.cpp | 29 +++++------ launcher/modplatform/packwiz/Packwiz.cpp | 18 +++++++ launcher/modplatform/packwiz/Packwiz.h | 18 +++++++ launcher/modplatform/packwiz/Packwiz_test.cpp | 18 +++++++ 16 files changed, 373 insertions(+), 51 deletions(-) diff --git a/launcher/ModDownloadTask.cpp b/launcher/ModDownloadTask.cpp index 52de9c942..301b66372 100644 --- a/launcher/ModDownloadTask.cpp +++ b/launcher/ModDownloadTask.cpp @@ -1,7 +1,25 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* +* 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 . +*/ + #include "ModDownloadTask.h" #include "Application.h" -#include "minecraft/mod/tasks/LocalModUpdateTask.h" +#include "minecraft/mod/ModFolderModel.h" ModDownloadTask::ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr mods) : m_mod(mod), m_mod_version(version), mods(mods) diff --git a/launcher/ModDownloadTask.h b/launcher/ModDownloadTask.h index 5eaee187a..f4438a8d9 100644 --- a/launcher/ModDownloadTask.h +++ b/launcher/ModDownloadTask.h @@ -1,14 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* +* 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 . +*/ + #pragma once -#include -#include "QObjectPtr.h" -#include "minecraft/mod/ModFolderModel.h" -#include "modplatform/ModIndex.h" #include "net/NetJob.h" - #include "tasks/SequentialTask.h" + +#include "modplatform/ModIndex.h" #include "minecraft/mod/tasks/LocalModUpdateTask.h" +class ModFolderModel; + class ModDownloadTask : public SequentialTask { Q_OBJECT public: diff --git a/launcher/minecraft/mod/MetadataHandler.h b/launcher/minecraft/mod/MetadataHandler.h index 26b1f7993..569628187 100644 --- a/launcher/minecraft/mod/MetadataHandler.h +++ b/launcher/minecraft/mod/MetadataHandler.h @@ -1,3 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* +* 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 . +*/ + #pragma once #include diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 261ae9d29..71a32d32f 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -1,17 +1,37 @@ -/* 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. - */ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* +* 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 . +* +* 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 "Mod.h" diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 58c7a80fa..96d471b42 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -1,17 +1,37 @@ -/* 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. - */ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* +* 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 . +* +* 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. +*/ #pragma once diff --git a/launcher/minecraft/mod/ModDetails.h b/launcher/minecraft/mod/ModDetails.h index 75ffea324..3e0a7ab0d 100644 --- a/launcher/minecraft/mod/ModDetails.h +++ b/launcher/minecraft/mod/ModDetails.h @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* +* 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 . +* +* 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. +*/ + #pragma once #include diff --git a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp index 47207ada0..cbe165676 100644 --- a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp @@ -1,6 +1,22 @@ -#include "LocalModUpdateTask.h" +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* +* 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 . +*/ -#include +#include "LocalModUpdateTask.h" #include "Application.h" #include "FileSystem.h" diff --git a/launcher/minecraft/mod/tasks/LocalModUpdateTask.h b/launcher/minecraft/mod/tasks/LocalModUpdateTask.h index f21c0b06e..2db183e09 100644 --- a/launcher/minecraft/mod/tasks/LocalModUpdateTask.h +++ b/launcher/minecraft/mod/tasks/LocalModUpdateTask.h @@ -1,3 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* +* 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 . +*/ + #pragma once #include diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index fe807a29b..62d856f61 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -1,5 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* +* 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 . +* +* 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 "ModFolderLoadTask.h" -#include #include "Application.h" #include "minecraft/mod/MetadataHandler.h" diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h index ba997874e..89a0f84ef 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* +* 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 . +* +* 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. +*/ + #pragma once #include diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp index 6027c4f30..3c4b7887f 100644 --- a/launcher/modplatform/ModIndex.cpp +++ b/launcher/modplatform/ModIndex.cpp @@ -1,3 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* +* 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 . +*/ + #include "modplatform/ModIndex.h" #include diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 8ada1fc69..04dd2dac0 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -1,3 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* +* 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 . +*/ + #pragma once #include diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 30693a82b..fdce71c3d 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -1,19 +1,20 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher - * - * 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 . - */ +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* +* 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 . +*/ #include "ModrinthPackIndex.h" #include "ModrinthAPI.h" diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 1ad6d75b1..91a5f9c65 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -1,3 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* +* 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 . +*/ + #include "Packwiz.h" #include diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index e66d0030e..58b864840 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -1,3 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* +* 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 . +*/ + #pragma once #include "modplatform/ModIndex.h" diff --git a/launcher/modplatform/packwiz/Packwiz_test.cpp b/launcher/modplatform/packwiz/Packwiz_test.cpp index 9f3c486e8..023b990ef 100644 --- a/launcher/modplatform/packwiz/Packwiz_test.cpp +++ b/launcher/modplatform/packwiz/Packwiz_test.cpp @@ -1,3 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* +* 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 . +*/ + #include #include From 42f8ec5b1489c2073adf9d3526080c434dbddd90 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 13 May 2022 11:42:08 -0300 Subject: [PATCH 095/308] fix: do modrinth changes on flame too Also fix a dumb moment --- launcher/modplatform/flame/FlameModIndex.cpp | 75 +++++++++++--------- launcher/modplatform/flame/FlameModIndex.h | 1 + launcher/modplatform/packwiz/Packwiz.cpp | 4 +- 3 files changed, 45 insertions(+), 35 deletions(-) diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 00dac4117..ed6d64c3f 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -52,40 +52,13 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, for (auto versionIter : arr) { auto obj = versionIter.toObject(); + + auto file = loadIndexedPackVersion(obj); + if(!file.addonId.isValid()) + file.addonId = pack.addonId; - auto versionArray = Json::requireArray(obj, "gameVersions"); - if (versionArray.isEmpty()) { - continue; - } - - ModPlatform::IndexedVersion file; - for (auto mcVer : versionArray) { - auto str = mcVer.toString(); - - if (str.contains('.')) - file.mcVersion.append(str); - } - - file.addonId = pack.addonId; - file.fileId = Json::requireInteger(obj, "id"); - file.date = Json::requireString(obj, "fileDate"); - file.version = Json::requireString(obj, "displayName"); - file.downloadUrl = Json::requireString(obj, "downloadUrl"); - file.fileName = Json::requireString(obj, "fileName"); - - auto hash_list = Json::ensureArray(obj, "hashes"); - for(auto h : hash_list){ - auto hash_entry = Json::ensureObject(h); - auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::FLAME); - auto hash_algo = enumToString(Json::ensureInteger(hash_entry, "algo", 1, "algorithm")); - if(hash_types.contains(hash_algo)){ - file.hash = Json::requireString(hash_entry, "value"); - file.hash_type = hash_algo; - break; - } - } - - unsortedVersions.append(file); + if(file.fileId.isValid()) // Heuristic to check if the returned value is valid + unsortedVersions.append(file); } auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { @@ -96,3 +69,39 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, pack.versions = unsortedVersions; pack.versionsLoaded = true; } + +auto FlameMod::loadIndexedPackVersion(QJsonObject& obj) -> ModPlatform::IndexedVersion +{ + auto versionArray = Json::requireArray(obj, "gameVersions"); + if (versionArray.isEmpty()) { + return {}; + } + + ModPlatform::IndexedVersion file; + for (auto mcVer : versionArray) { + auto str = mcVer.toString(); + + if (str.contains('.')) + file.mcVersion.append(str); + } + + file.addonId = Json::requireInteger(obj, "modId"); + file.fileId = Json::requireInteger(obj, "id"); + file.date = Json::requireString(obj, "fileDate"); + file.version = Json::requireString(obj, "displayName"); + file.downloadUrl = Json::requireString(obj, "downloadUrl"); + file.fileName = Json::requireString(obj, "fileName"); + + auto hash_list = Json::ensureArray(obj, "hashes"); + for (auto h : hash_list) { + auto hash_entry = Json::ensureObject(h); + auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::FLAME); + auto hash_algo = enumToString(Json::ensureInteger(hash_entry, "algo", 1, "algorithm")); + if (hash_types.contains(hash_algo)) { + file.hash = Json::requireString(hash_entry, "value"); + file.hash_type = hash_algo; + break; + } + } + return file; +} diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h index d3171d943..2e0f2e864 100644 --- a/launcher/modplatform/flame/FlameModIndex.h +++ b/launcher/modplatform/flame/FlameModIndex.h @@ -16,5 +16,6 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr& network, BaseInstance* inst); +auto loadIndexedPackVersion(QJsonObject& obj) -> ModPlatform::IndexedVersion; } // namespace FlameMod diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 91a5f9c65..ee82f8a0c 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -58,8 +58,8 @@ auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, Mo mod.hash = mod_version.hash; mod.provider = mod_pack.provider; - mod.file_id = mod_pack.addonId; - mod.project_id = mod_version.fileId; + mod.file_id = mod_version.fileId; + mod.project_id = mod_pack.addonId; return mod; } From 5a1de15332bcfbeafff7d0c678d7286ca85cfe18 Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 18 May 2022 05:46:07 -0300 Subject: [PATCH 096/308] fix: use a more robust method of finding metadata indexes Often times, mods can have their name in different forms, changing one letter to caps or the other way (e.g. JourneyMaps -> Journeymaps). This makes it possible to find those as well, which is not perfect by any means, but should suffice for the majority of cases. --- launcher/modplatform/packwiz/Packwiz.cpp | 110 +++++++++++++++-------- launcher/modplatform/packwiz/Packwiz.h | 6 ++ 2 files changed, 80 insertions(+), 36 deletions(-) diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index ee82f8a0c..0782b9f41 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -23,12 +23,37 @@ #include #include "toml.h" +#include "FileSystem.h" #include "minecraft/mod/Mod.h" #include "modplatform/ModIndex.h" namespace Packwiz { +auto getRealIndexName(QDir& index_dir, QString normalized_fname, bool should_find_match) -> QString +{ + QFile index_file(index_dir.absoluteFilePath(normalized_fname)); + + QString real_fname = normalized_fname; + if (!index_file.exists()) { + // Tries to get similar entries + for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) { + if (!QString::compare(normalized_fname, file_name, Qt::CaseInsensitive)) { + real_fname = file_name; + break; + } + } + + if(should_find_match && !QString::compare(normalized_fname, real_fname, Qt::CaseSensitive)){ + qCritical() << "Could not find a match for a valid metadata file!"; + qCritical() << "File: " << normalized_fname; + return {}; + } + } + + return real_fname; +} + // Helpers static inline auto indexFileName(QString const& mod_name) -> QString { @@ -39,6 +64,33 @@ static inline auto indexFileName(QString const& mod_name) -> QString static ModPlatform::ProviderCapabilities ProviderCaps; +// Helper functions for extracting data from the TOML file +auto stringEntry(toml_table_t* parent, const char* entry_name) -> QString +{ + toml_datum_t var = toml_string_in(parent, entry_name); + if (!var.ok) { + qCritical() << QString("Failed to read str property '%1' in mod metadata.").arg(entry_name); + return {}; + } + + QString tmp = var.u.s; + free(var.u.s); + + return tmp; +} + +auto intEntry(toml_table_t* parent, const char* entry_name) -> int +{ + toml_datum_t var = toml_int_in(parent, entry_name); + if (!var.ok) { + qCritical() << QString("Failed to read int property '%1' in mod metadata.").arg(entry_name); + return {}; + } + + return var.u.i; +} + + auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod { Mod mod; @@ -86,7 +138,11 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) } // Ensure the corresponding mod's info exists, and create it if not - QFile index_file(index_dir.absoluteFilePath(indexFileName(mod.name))); + + auto normalized_fname = indexFileName(mod.name); + auto real_fname = getRealIndexName(index_dir, normalized_fname); + + QFile index_file(index_dir.absoluteFilePath(real_fname)); // There's already data on there! // TODO: We should do more stuff here, as the user is likely trying to @@ -127,11 +183,18 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) break; } } + + index_file.close(); } void V1::deleteModIndex(QDir& index_dir, QString& mod_name) { - QFile index_file(index_dir.absoluteFilePath(indexFileName(mod_name))); + auto normalized_fname = indexFileName(mod_name); + auto real_fname = getRealIndexName(index_dir, normalized_fname); + if (real_fname.isEmpty()) + return; + + QFile index_file(index_dir.absoluteFilePath(real_fname)); if(!index_file.exists()){ qWarning() << QString("Tried to delete non-existent mod metadata for %1!").arg(mod_name); @@ -143,42 +206,17 @@ void V1::deleteModIndex(QDir& index_dir, QString& mod_name) } } -// Helper functions for extracting data from the TOML file -static auto stringEntry(toml_table_t* parent, const char* entry_name) -> QString -{ - toml_datum_t var = toml_string_in(parent, entry_name); - if (!var.ok) { - qCritical() << QString("Failed to read str property '%1' in mod metadata.").arg(entry_name); - return {}; - } - - QString tmp = var.u.s; - free(var.u.s); - - return tmp; -} - -static auto intEntry(toml_table_t* parent, const char* entry_name) -> int -{ - toml_datum_t var = toml_int_in(parent, entry_name); - if (!var.ok) { - qCritical() << QString("Failed to read int property '%1' in mod metadata.").arg(entry_name); - return {}; - } - - return var.u.i; -} - auto V1::getIndexForMod(QDir& index_dir, QString& index_file_name) -> Mod { Mod mod; - QFile index_file(index_dir.absoluteFilePath(indexFileName(index_file_name))); - - if (!index_file.exists()) { - qWarning() << QString("Tried to get a non-existent mod metadata for %1").arg(index_file_name); + auto normalized_fname = indexFileName(index_file_name); + auto real_fname = getRealIndexName(index_dir, normalized_fname, true); + if (real_fname.isEmpty()) return {}; - } + + QFile index_file(index_dir.absoluteFilePath(real_fname)); + if (!index_file.open(QIODevice::ReadOnly)) { qWarning() << QString("Failed to open mod metadata for %1").arg(index_file_name); return {}; @@ -198,14 +236,14 @@ auto V1::getIndexForMod(QDir& index_dir, QString& index_file_name) -> Mod qWarning() << "Reason: " << QString(errbuf); return {}; } - - { // Basic info + + { // Basic info mod.name = stringEntry(table, "name"); mod.filename = stringEntry(table, "filename"); mod.side = stringEntry(table, "side"); } - { // [download] info + { // [download] info toml_table_t* download_table = toml_table_in(table, "download"); if (!download_table) { qCritical() << QString("No [download] section found on mod metadata!"); diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index 58b864840..3c99769c0 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -24,6 +24,7 @@ #include #include +struct toml_table_t; class QDir; // Mod from launcher/minecraft/mod/Mod.h @@ -31,6 +32,11 @@ class Mod; namespace Packwiz { +auto getRealIndexName(QDir& index_dir, QString normalized_index_name, bool should_match = false) -> QString; + +auto stringEntry(toml_table_t* parent, const char* entry_name) -> QString; +auto intEntry(toml_table_t* parent, const char* entry_name) -> int; + class V1 { public: struct Mod { From 997bf9144258c89f4c1e5fb23d7f3218790ac5ea Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Mon, 23 May 2022 14:15:49 -0400 Subject: [PATCH 097/308] Add desktop shortcut to Windows installer --- program_info/win_install.nsi | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/program_info/win_install.nsi b/program_info/win_install.nsi index 4ca4de1ad..cb4c8d1d4 100644 --- a/program_info/win_install.nsi +++ b/program_info/win_install.nsi @@ -141,12 +141,18 @@ Section "PolyMC" SectionEnd -Section "Start Menu Shortcuts" SHORTCUTS +Section "Start Menu Shortcut" SM_SHORTCUTS CreateShortcut "$SMPROGRAMS\PolyMC.lnk" "$INSTDIR\polymc.exe" "" "$INSTDIR\polymc.exe" 0 SectionEnd +Section "Desktop Shortcut" DESKTOP_SHORTCUTS + + CreateShortcut "$DESKTOP\PolyMC.lnk" "$INSTDIR\polymc.exe" "" "$INSTDIR\polymc.exe" 0 + +SectionEnd + ;-------------------------------- ; Uninstaller @@ -215,6 +221,7 @@ Section "Uninstall" RMDir /r $INSTDIR\styles Delete "$SMPROGRAMS\PolyMC.lnk" + Delete "$DESKTOP\PolyMC.lnk" RMDir "$INSTDIR" @@ -228,6 +235,7 @@ Function .onInit ${GetParameters} $R0 ${GetOptions} $R0 "/NoShortcuts" $R1 ${IfNot} ${Errors} - !insertmacro UnselectSection ${SHORTCUTS} + !insertmacro UnselectSection ${SM_SHORTCUTS} + !insertmacro UnselectSection ${DESKTOP_SHORTCUTS} ${EndIf} FunctionEnd From f28a0aa666565354e657dec59249aa1fd237cdb0 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Mon, 23 May 2022 19:42:04 +0100 Subject: [PATCH 098/308] ATLauncher: Handle main class depends --- .../atlauncher/ATLPackInstallTask.cpp | 20 ++++++++++++++++--- .../atlauncher/ATLPackManifest.cpp | 8 +++++++- .../modplatform/atlauncher/ATLPackManifest.h | 8 +++++++- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 9b14f3557..e6fd13349 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -414,10 +414,24 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr profile) { - if(m_version.mainClass == QString() && m_version.extraArguments == QString()) { + if (m_version.mainClass.mainClass.isEmpty() && m_version.extraArguments.isEmpty()) { return true; } + auto mainClass = m_version.mainClass.mainClass; + + auto hasMainClassDepends = !m_version.mainClass.depends.isEmpty(); + if (hasMainClassDepends) { + QSet mods; + for (const auto& item : m_version.mods) { + mods.insert(item.name); + } + + if (hasMainClassDepends && !mods.contains(m_version.mainClass.depends)) { + mainClass = ""; + } + } + auto uuid = QUuid::createUuid(); auto id = uuid.toString().remove('{').remove('}'); auto target_id = "org.multimc.atlauncher." + id; @@ -442,8 +456,8 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr< auto f = std::make_shared(); f->name = m_pack + " " + m_version_name; - if(m_version.mainClass != QString() && !mainClasses.contains(m_version.mainClass)) { - f->mainClass = m_version.mainClass; + if (!mainClass.isEmpty() && !mainClasses.contains(mainClass)) { + f->mainClass = mainClass; } // Parse out tweakers diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.cpp b/launcher/modplatform/atlauncher/ATLPackManifest.cpp index d01ec32cf..cec9896b5 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.cpp +++ b/launcher/modplatform/atlauncher/ATLPackManifest.cpp @@ -212,6 +212,12 @@ static void loadVersionMessages(ATLauncher::VersionMessages& m, QJsonObject& obj m.update = Json::ensureString(obj, "update", ""); } +static void loadVersionMainClass(ATLauncher::PackVersionMainClass& m, QJsonObject& obj) +{ + m.mainClass = Json::ensureString(obj, "mainClass", ""); + m.depends = Json::ensureString(obj, "depends", ""); +} + void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj) { v.version = Json::requireString(obj, "version"); @@ -220,7 +226,7 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj) if(obj.contains("mainClass")) { auto main = Json::requireObject(obj, "mainClass"); - v.mainClass = Json::ensureString(main, "mainClass", ""); + loadVersionMainClass(v.mainClass, main); } if(obj.contains("extraArguments")) { diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.h b/launcher/modplatform/atlauncher/ATLPackManifest.h index 23e162e30..bf88d91dc 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.h +++ b/launcher/modplatform/atlauncher/ATLPackManifest.h @@ -150,12 +150,18 @@ struct VersionMessages QString update; }; +struct PackVersionMainClass +{ + QString mainClass; + QString depends; +}; + struct PackVersion { QString version; QString minecraft; bool noConfigs; - QString mainClass; + PackVersionMainClass mainClass; QString extraArguments; VersionLoader loader; From 101ca60b2bb1d3c3047bc5842461c68d05708e39 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Mon, 23 May 2022 20:14:23 +0100 Subject: [PATCH 099/308] ATLauncher: Handle extra arguments depends --- .../atlauncher/ATLPackInstallTask.cpp | 16 +++++++++++++--- .../modplatform/atlauncher/ATLPackManifest.cpp | 8 +++++++- .../modplatform/atlauncher/ATLPackManifest.h | 8 +++++++- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index e6fd13349..b2dda4e47 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -414,14 +414,16 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr profile) { - if (m_version.mainClass.mainClass.isEmpty() && m_version.extraArguments.isEmpty()) { + if (m_version.mainClass.mainClass.isEmpty() && m_version.extraArguments.arguments.isEmpty()) { return true; } auto mainClass = m_version.mainClass.mainClass; + auto extraArguments = m_version.extraArguments.arguments; auto hasMainClassDepends = !m_version.mainClass.depends.isEmpty(); - if (hasMainClassDepends) { + auto hasExtraArgumentsDepends = !m_version.extraArguments.depends.isEmpty(); + if (hasMainClassDepends || hasExtraArgumentsDepends) { QSet mods; for (const auto& item : m_version.mods) { mods.insert(item.name); @@ -430,6 +432,14 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr< if (hasMainClassDepends && !mods.contains(m_version.mainClass.depends)) { mainClass = ""; } + + if (hasExtraArgumentsDepends && !mods.contains(m_version.extraArguments.depends)) { + extraArguments = ""; + } + } + + if (mainClass.isEmpty() && extraArguments.isEmpty()) { + return true; } auto uuid = QUuid::createUuid(); @@ -461,7 +471,7 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr< } // Parse out tweakers - auto args = m_version.extraArguments.split(" "); + auto args = extraArguments.split(" "); QString previous; for(auto arg : args) { if(arg.startsWith("--tweakClass=") || previous == "--tweakClass") { diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.cpp b/launcher/modplatform/atlauncher/ATLPackManifest.cpp index cec9896b5..3af02a091 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.cpp +++ b/launcher/modplatform/atlauncher/ATLPackManifest.cpp @@ -218,6 +218,12 @@ static void loadVersionMainClass(ATLauncher::PackVersionMainClass& m, QJsonObjec m.depends = Json::ensureString(obj, "depends", ""); } +static void loadVersionExtraArguments(ATLauncher::PackVersionExtraArguments& a, QJsonObject& obj) +{ + a.arguments = Json::ensureString(obj, "arguments", ""); + a.depends = Json::ensureString(obj, "depends", ""); +} + void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj) { v.version = Json::requireString(obj, "version"); @@ -231,7 +237,7 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj) if(obj.contains("extraArguments")) { auto arguments = Json::requireObject(obj, "extraArguments"); - v.extraArguments = Json::ensureString(arguments, "arguments", ""); + loadVersionExtraArguments(v.extraArguments, arguments); } if(obj.contains("loader")) { diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.h b/launcher/modplatform/atlauncher/ATLPackManifest.h index bf88d91dc..43510c50d 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.h +++ b/launcher/modplatform/atlauncher/ATLPackManifest.h @@ -156,13 +156,19 @@ struct PackVersionMainClass QString depends; }; +struct PackVersionExtraArguments +{ + QString arguments; + QString depends; +}; + struct PackVersion { QString version; QString minecraft; bool noConfigs; PackVersionMainClass mainClass; - QString extraArguments; + PackVersionExtraArguments extraArguments; VersionLoader loader; QVector libraries; From 4ee5264e24e21d89185d424072dc39cb6b2dd10f Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Mon, 23 May 2022 21:37:09 +0100 Subject: [PATCH 100/308] ATLauncher: Delete files from configs if they conflict with a mod --- .../modplatform/atlauncher/ATLPackInstallTask.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index b2dda4e47..62c7bf6d4 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -781,6 +781,17 @@ bool PackInstallTask::extractMods( for (auto iter = toCopy.begin(); iter != toCopy.end(); iter++) { auto &from = iter.key(); auto &to = iter.value(); + + // If the file already exists, assume the mod is the correct copy - and remove + // the copy from the Configs.zip + QFileInfo fileInfo(to); + if (fileInfo.exists()) { + if (!QFile::remove(to)) { + qWarning() << "Failed to delete" << to; + return false; + } + } + FS::copy fileCopyOperation(from, to); if(!fileCopyOperation()) { qWarning() << "Failed to copy" << from << "to" << to; From fce5c575480c88f81f325f3759889d0cde9a28e0 Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Mon, 23 May 2022 17:27:35 -0400 Subject: [PATCH 101/308] Silence CMake QuaZip not found warnings These are expected most of the time, and thus just noise. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e2635c3fc..fcc2512d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -143,7 +143,7 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5) find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml) if(NOT Launcher_FORCE_BUNDLED_LIBS) - find_package(QuaZip-Qt5 1.3) + find_package(QuaZip-Qt5 1.3 QUIET) endif() if (NOT QuaZip-Qt5_FOUND) set(QUAZIP_QT_MAJOR_VERSION ${QT_VERSION_MAJOR} CACHE STRING "Qt version to use (4, 5 or 6), defaults to ${QT_VERSION_MAJOR}" FORCE) From 0426149580feaca188c7f34b268411ffeb8787b0 Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Tue, 24 May 2022 13:35:01 +0800 Subject: [PATCH 102/308] standard macOS app behavior --- launcher/Application.cpp | 24 ++++++++++++++++++++++++ launcher/Application.h | 7 +++++++ 2 files changed, 31 insertions(+) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index ba4096b64..bcfdc4601 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -871,6 +871,10 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_mcedit.reset(new MCEditTool(m_settings)); } + connect(this, &Application::clickedOnDock, [this]() { + this->showMainWindow(); + }); + connect(this, &Application::aboutToQuit, [this](){ if(m_instances) { @@ -954,6 +958,22 @@ bool Application::createSetupWizard() return false; } +bool Application::event(QEvent* event) { +#ifdef Q_OS_MACOS + if (event->type() == QEvent::ApplicationStateChange) { + auto ev = static_cast(event); + + if (m_prevAppState == Qt::ApplicationActive + && ev->applicationState() == Qt::ApplicationActive) { + qDebug() << "Clicked on dock!"; + emit clickedOnDock(); + } + m_prevAppState = ev->applicationState(); + } +#endif + return QApplication::event(event); +} + void Application::setupWizardFinished(int status) { qDebug() << "Wizard result =" << status; @@ -1284,6 +1304,10 @@ void Application::subRunningInstance() bool Application::shouldExitNow() const { +#ifdef Q_OS_MACOS + return false; +#endif + return m_runningInstances == 0 && m_openWindows == 0; } diff --git a/launcher/Application.h b/launcher/Application.h index 3129b4fb8..d6a5473da 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -94,6 +94,8 @@ public: Application(int &argc, char **argv); virtual ~Application(); + bool event(QEvent* event) override; + std::shared_ptr settings() const { return m_settings; } @@ -180,6 +182,7 @@ signals: void updateAllowedChanged(bool status); void globalSettingsAboutToOpen(); void globalSettingsClosed(); + void clickedOnDock(); public slots: bool launch( @@ -238,6 +241,10 @@ private: QString m_rootPath; Status m_status = Application::StartingUp; +#ifdef Q_OS_MACOS + Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive; +#endif + #if defined Q_OS_WIN32 // used on Windows to attach the standard IO streams bool consoleAttached = false; From 9673dac22b0ff81a54847d5db5438c099a6df587 Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Tue, 24 May 2022 16:18:02 +0800 Subject: [PATCH 103/308] add more `#ifdef`s --- launcher/Application.cpp | 2 ++ launcher/Application.h | 3 +++ 2 files changed, 5 insertions(+) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index bcfdc4601..ff0f21296 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -871,9 +871,11 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_mcedit.reset(new MCEditTool(m_settings)); } +#ifdef Q_OS_MACOS connect(this, &Application::clickedOnDock, [this]() { this->showMainWindow(); }); +#endif connect(this, &Application::aboutToQuit, [this](){ if(m_instances) diff --git a/launcher/Application.h b/launcher/Application.h index d6a5473da..686137ece 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -182,7 +182,10 @@ signals: void updateAllowedChanged(bool status); void globalSettingsAboutToOpen(); void globalSettingsClosed(); + +#ifdef Q_OS_MACOS void clickedOnDock(); +#endif public slots: bool launch( From 4bd30f5e72d585ad6c34ef96d768eb7969ec4901 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 24 May 2022 14:17:44 +0200 Subject: [PATCH 104/308] chore: remove unused GH Workflows --- .github/workflows/backport.yml | 19 ---------- .github/workflows/pr-comment.yml | 61 -------------------------------- 2 files changed, 80 deletions(-) delete mode 100644 .github/workflows/backport.yml delete mode 100644 .github/workflows/pr-comment.yml diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml deleted file mode 100644 index fa287a2ca..000000000 --- a/.github/workflows/backport.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Backport PR to stable -on: - pull_request: - branches: [ develop ] - types: [ closed ] -jobs: - release_pull_request: - if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'backport') - runs-on: ubuntu-latest - steps: - - name: checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Backport PR by cherry-pick-ing - uses: Nathanmalnoury/gh-backport-action@master - with: - pr_branch: 'stable' - github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pr-comment.yml b/.github/workflows/pr-comment.yml deleted file mode 100644 index f0f5b8cc1..000000000 --- a/.github/workflows/pr-comment.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Comment on pull request -on: - workflow_run: - workflows: ['Build Application'] - types: [completed] -jobs: - pr_comment: - if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' - runs-on: ubuntu-latest - steps: - - uses: actions/github-script@v5 - with: - # This snippet is public-domain, taken from - # https://github.com/oprypin/nightly.link/blob/master/.github/workflows/pr-comment.yml - script: | - async function upsertComment(owner, repo, issue_number, purpose, body) { - const {data: comments} = await github.rest.issues.listComments( - {owner, repo, issue_number}); - - const marker = ``; - body = marker + "\n" + body; - - const existing = comments.filter((c) => c.body.includes(marker)); - if (existing.length > 0) { - const last = existing[existing.length - 1]; - core.info(`Updating comment ${last.id}`); - await github.rest.issues.updateComment({ - owner, repo, - body, - comment_id: last.id, - }); - } else { - core.info(`Creating a comment in issue / PR #${issue_number}`); - await github.rest.issues.createComment({issue_number, body, owner, repo}); - } - } - - const {owner, repo} = context.repo; - const run_id = ${{github.event.workflow_run.id}}; - - const pull_requests = ${{ toJSON(github.event.workflow_run.pull_requests) }}; - if (!pull_requests.length) { - return core.error("This workflow doesn't match any pull requests!"); - } - - const artifacts = await github.paginate( - github.rest.actions.listWorkflowRunArtifacts, {owner, repo, run_id}); - if (!artifacts.length) { - return core.error(`No artifacts found`); - } - let body = `Download the artifacts for this pull request:\n`; - for (const art of artifacts) { - body += `\n* [${art.name}.zip](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; - } - - core.info("Review thread message body:", body); - - for (const pr of pull_requests) { - await upsertComment(owner, repo, pr.number, - "nightly-link", body); - } From ca3c6c5e8a5151ea50e51f09938b894e6a610626 Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 24 May 2022 09:38:48 -0300 Subject: [PATCH 105/308] feat: add donate links for modrinth mods --- launcher/modplatform/ModAPI.h | 2 + launcher/modplatform/ModIndex.h | 14 ++++ launcher/modplatform/flame/FlameAPI.h | 2 + .../modplatform/helpers/NetworkModAPI.cpp | 25 +++++++ launcher/modplatform/helpers/NetworkModAPI.h | 2 + launcher/modplatform/modrinth/ModrinthAPI.h | 5 ++ .../modrinth/ModrinthPackIndex.cpp | 21 ++++++ .../modplatform/modrinth/ModrinthPackIndex.h | 1 + launcher/ui/pages/modplatform/ModModel.cpp | 20 ++++++ launcher/ui/pages/modplatform/ModModel.h | 4 ++ launcher/ui/pages/modplatform/ModPage.cpp | 68 +++++++++++++------ launcher/ui/pages/modplatform/ModPage.h | 4 +- .../modplatform/modrinth/ModrinthModModel.cpp | 5 ++ .../modplatform/modrinth/ModrinthModModel.h | 1 + 14 files changed, 151 insertions(+), 23 deletions(-) diff --git a/launcher/modplatform/ModAPI.h b/launcher/modplatform/ModAPI.h index 4230df0bc..24d803853 100644 --- a/launcher/modplatform/ModAPI.h +++ b/launcher/modplatform/ModAPI.h @@ -7,6 +7,7 @@ namespace ModPlatform { class ListModel; +struct IndexedPack; } class ModAPI { @@ -35,6 +36,7 @@ class ModAPI { }; virtual void searchMods(CallerType* caller, SearchArgs&& args) const = 0; + virtual void getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack) = 0; struct VersionSearchArgs { diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 7e1cf254e..6e1a01bc4 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -13,6 +13,12 @@ struct ModpackAuthor { QString url; }; +struct DonationData { + QString id; + QString platform; + QString url; +}; + struct IndexedVersion { QVariant addonId; QVariant fileId; @@ -24,6 +30,10 @@ struct IndexedVersion { QVector loaders = {}; }; +struct ExtraPackData { + QList donate; +}; + struct IndexedPack { QVariant addonId; QString name; @@ -35,6 +45,10 @@ struct IndexedPack { bool versionsLoaded = false; QVector versions; + + // Don't load by default, since some modplatform don't have that info + bool extraDataLoaded = true; + ExtraPackData extraData; }; } // namespace ModPlatform diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 8bb33d477..6ce474c84 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -41,6 +41,8 @@ class FlameAPI : public NetworkModAPI { .arg(gameVersionStr); }; + inline auto getModInfoURL(QString& id) const -> QString override { return {}; }; + inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override { QString gameVersionQuery = args.mcVersions.size() == 1 ? QString("gameVersion=%1&").arg(args.mcVersions.front().toString()) : ""; diff --git a/launcher/modplatform/helpers/NetworkModAPI.cpp b/launcher/modplatform/helpers/NetworkModAPI.cpp index 6829b837c..d7abd10f9 100644 --- a/launcher/modplatform/helpers/NetworkModAPI.cpp +++ b/launcher/modplatform/helpers/NetworkModAPI.cpp @@ -31,6 +31,31 @@ void NetworkModAPI::searchMods(CallerType* caller, SearchArgs&& args) const netJob->start(); } +void NetworkModAPI::getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack) +{ + auto id_str = pack.addonId.toString(); + auto netJob = new NetJob(QString("%1::ModInfo").arg(id_str), APPLICATION->network()); + auto searchUrl = getModInfoURL(id_str); + + auto response = new QByteArray(); + netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); + + QObject::connect(netJob, &NetJob::succeeded, [response, &pack, caller] { + QJsonParseError parse_error{}; + auto doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response for " << pack.name << " at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + + caller->infoRequestFinished(doc, pack); + }); + + netJob->start(); +} + void NetworkModAPI::getVersions(CallerType* caller, VersionSearchArgs&& args) const { auto netJob = new NetJob(QString("%1::ModVersions(%2)").arg(caller->debugName()).arg(args.addonId), APPLICATION->network()); diff --git a/launcher/modplatform/helpers/NetworkModAPI.h b/launcher/modplatform/helpers/NetworkModAPI.h index 000620b2f..87d77ad12 100644 --- a/launcher/modplatform/helpers/NetworkModAPI.h +++ b/launcher/modplatform/helpers/NetworkModAPI.h @@ -5,9 +5,11 @@ class NetworkModAPI : public ModAPI { public: void searchMods(CallerType* caller, SearchArgs&& args) const override; + void getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack) override; void getVersions(CallerType* caller, VersionSearchArgs&& args) const override; protected: virtual auto getModSearchURL(SearchArgs& args) const -> QString = 0; + virtual auto getModInfoURL(QString& id) const -> QString = 0; virtual auto getVersionsURL(VersionSearchArgs& args) const -> QString = 0; }; diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index 79bc5175a..13b62f0c5 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -75,6 +75,11 @@ class ModrinthAPI : public NetworkModAPI { .arg(getGameVersionsArray(args.versions)); }; + inline auto getModInfoURL(QString& id) const -> QString override + { + return BuildConfig.MODRINTH_PROD_URL + "/project/" + id; + }; + inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override { return QString(BuildConfig.MODRINTH_PROD_URL + diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index f7fa98641..32b4cfd4c 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -45,6 +45,27 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) modAuthor.name = Json::requireString(obj, "author"); modAuthor.url = api.getAuthorURL(modAuthor.name); pack.authors.append(modAuthor); + + // Modrinth can have more data than what's provided by the basic search :) + pack.extraDataLoaded = false; +} + +void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& obj) +{ + auto donate_arr = Json::ensureArray(obj, "donation_urls"); + for(auto d : donate_arr){ + auto d_obj = Json::requireObject(d); + + ModPlatform::DonationData donate; + + donate.id = Json::ensureString(d_obj, "id"); + donate.platform = Json::ensureString(d_obj, "platform"); + donate.url = Json::ensureString(d_obj, "url"); + + pack.extraData.donate.append(donate); + } + + pack.extraDataLoaded = true; } void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h index 7f306f25f..b0e3736f2 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.h +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h @@ -25,6 +25,7 @@ namespace Modrinth { void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj); +void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr& network, diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 9dd8f7379..13d9ceea0 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -79,6 +79,11 @@ void ListModel::performPaginatedSearch() this, { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoaders(), getMineVersions() }); } +void ListModel::requestModInfo(ModPlatform::IndexedPack& current) +{ + m_parent->apiProvider()->getModInfo(this, current); +} + void ListModel::refresh() { if (jobPtr) { @@ -225,6 +230,21 @@ void ListModel::searchRequestFailed(QString reason) } } +void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack) +{ + qDebug() << "Loading mod info"; + + try { + auto obj = Json::requireObject(doc); + loadExtraPackInfo(pack, obj); + } catch (const JSONValidationError& e) { + qDebug() << doc; + qWarning() << "Error while reading " << debugName() << " mod info: " << e.cause(); + } + + m_parent->updateUi(); +} + void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId) { auto& current = m_parent->getCurrent(); diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index d460cff22..dd22407cb 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -36,9 +36,11 @@ class ListModel : public QAbstractListModel { void fetchMore(const QModelIndex& parent) override; void refresh(); void searchWithTerm(const QString& term, const int sort, const bool filter_changed); + void requestModInfo(ModPlatform::IndexedPack& current); void requestModVersions(const ModPlatform::IndexedPack& current); virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; + virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) {}; virtual void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) = 0; void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); @@ -49,6 +51,8 @@ class ListModel : public QAbstractListModel { void searchRequestFinished(QJsonDocument& doc); void searchRequestFailed(QString reason); + void infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack); + void versionRequestSucceeded(QJsonDocument doc, QString addonId); protected slots: diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index ad36cf2f8..4a02f5a41 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -94,28 +94,6 @@ void ModPage::onSelectionChanged(QModelIndex first, QModelIndex second) if (!first.isValid()) { return; } current = listModel->data(first, Qt::UserRole).value(); - QString text = ""; - QString name = current.name; - - if (current.websiteUrl.isEmpty()) - text = name; - else - text = "" + name + ""; - - if (!current.authors.empty()) { - auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString { - if (author.url.isEmpty()) { return author.name; } - return QString("%2").arg(author.url, author.name); - }; - QStringList authorStrs; - for (auto& author : current.authors) { - authorStrs.push_back(authorToStr(author)); - } - text += "
" + tr(" by ") + authorStrs.join(", "); - } - text += "

"; - - ui->packDescription->setHtml(text + current.description); if (!current.versionsLoaded) { qDebug() << QString("Loading %1 mod versions").arg(debugName()); @@ -132,6 +110,13 @@ void ModPage::onSelectionChanged(QModelIndex first, QModelIndex second) updateSelectionButton(); } + + if(!current.extraDataLoaded){ + qDebug() << QString("Loading %1 mod info").arg(debugName()); + listModel->requestModInfo(current); + } + + updateUi(); } void ModPage::onVersionSelectionChanged(QString data) @@ -207,3 +192,42 @@ void ModPage::updateSelectionButton() ui->modSelectionButton->setText(tr("Deselect mod for download")); } } + +void ModPage::updateUi() +{ + QString text = ""; + QString name = current.name; + + if (current.websiteUrl.isEmpty()) + text = name; + else + text = "" + name + ""; + + if (!current.authors.empty()) { + auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString { + if (author.url.isEmpty()) { return author.name; } + return QString("%2").arg(author.url, author.name); + }; + QStringList authorStrs; + for (auto& author : current.authors) { + authorStrs.push_back(authorToStr(author)); + } + text += "
" + tr(" by ") + authorStrs.join(", "); + } + + if(!current.extraData.donate.isEmpty()) { + text += "

Donation information:
"; + auto donateToStr = [](ModPlatform::DonationData& donate) -> QString { + return QString("%2").arg(donate.url, donate.platform); + }; + QStringList donates; + for (auto& donate : current.extraData.donate) { + donates.append(donateToStr(donate)); + } + text += donates.join(", "); + } + + text += "

"; + + ui->packDescription->setHtml(text + current.description); +} diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 0e658a8de..9522cc4ca 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -36,10 +36,12 @@ class ModPage : public QWidget, public BasePage { void retranslate() override; + void updateUi(); + auto shouldDisplay() const -> bool override = 0; virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool = 0; - auto apiProvider() const -> const ModAPI* { return api.get(); }; + auto apiProvider() -> ModAPI* { return api.get(); }; auto getFilter() const -> const std::shared_ptr { return m_filter; } auto getCurrent() -> ModPlatform::IndexedPack& { return current; } diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp index 1d9f4d60b..af92e63e9 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp @@ -30,6 +30,11 @@ void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) Modrinth::loadIndexedPack(m, obj); } +void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + Modrinth::loadExtraPackData(m, obj); +} + void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h index ae7b0bddc..386897fdb 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h @@ -31,6 +31,7 @@ class ListModel : public ModPlatform::ListModel { private: void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; From 22e0527502683a625c5963ec8155e07d9ec06d28 Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 24 May 2022 09:46:58 -0300 Subject: [PATCH 106/308] feat: add donate info to modrinth modpacks --- .../modplatform/modrinth/ModrinthPackManifest.cpp | 13 +++++++++++++ .../modplatform/modrinth/ModrinthPackManifest.h | 9 +++++++++ .../ui/pages/modplatform/modrinth/ModrinthPage.cpp | 12 ++++++++++++ 3 files changed, 34 insertions(+) diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp index f1ad39cea..f47942a00 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp @@ -65,6 +65,19 @@ void loadIndexedInfo(Modpack& pack, QJsonObject& obj) pack.extra.sourceUrl = Json::ensureString(obj, "source_url"); pack.extra.wikiUrl = Json::ensureString(obj, "wiki_url"); + auto donate_arr = Json::ensureArray(obj, "donation_urls"); + for(auto d : donate_arr){ + auto d_obj = Json::requireObject(d); + + DonationData donate; + + donate.id = Json::ensureString(d_obj, "id"); + donate.platform = Json::ensureString(d_obj, "platform"); + donate.url = Json::ensureString(d_obj, "url"); + + pack.extra.donate.append(donate); + } + pack.extraInfoLoaded = true; } diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.h b/launcher/modplatform/modrinth/ModrinthPackManifest.h index e5fc9a700..c8ca36605 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.h +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.h @@ -58,12 +58,21 @@ struct File QUrl download; }; +struct DonationData { + QString id; + QString platform; + QString url; +}; + struct ModpackExtra { QString body; QString projectUrl; QString sourceUrl; QString wikiUrl; + + QList donate; + }; struct ModpackVersion { diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index 9bd24b578..f44d05f25 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -224,6 +224,18 @@ void ModrinthPage::updateUI() // TODO: Implement multiple authors with links text += "
" + tr(" by ") + QString("%2").arg(std::get<1>(current.author).toString(), std::get<0>(current.author)); + if(!current.extra.donate.isEmpty()) { + text += "

Donation information:
"; + auto donateToStr = [](Modrinth::DonationData& donate) -> QString { + return QString("%2").arg(donate.url, donate.platform); + }; + QStringList donates; + for (auto& donate : current.extra.donate) { + donates.append(donateToStr(donate)); + } + text += donates.join(", "); + } + text += "
"; HoeDown h; From 5e17d53c7f2e19b6911645d68e0e8a68b6e07d1d Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 24 May 2022 11:11:40 -0300 Subject: [PATCH 107/308] fix: missing tr() and update donate message --- launcher/ui/pages/modplatform/ModPage.cpp | 2 +- launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 4a02f5a41..39e47edcb 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -216,7 +216,7 @@ void ModPage::updateUi() } if(!current.extraData.donate.isEmpty()) { - text += "

Donation information:
"; + text += tr("

Donate information:
"); auto donateToStr = [](ModPlatform::DonationData& donate) -> QString { return QString("%2").arg(donate.url, donate.platform); }; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index f44d05f25..f7c5b2ce8 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -225,7 +225,7 @@ void ModrinthPage::updateUI() text += "
" + tr(" by ") + QString("%2").arg(std::get<1>(current.author).toString(), std::get<0>(current.author)); if(!current.extra.donate.isEmpty()) { - text += "

Donation information:
"; + text += tr("

Donate information:
"); auto donateToStr = [](Modrinth::DonationData& donate) -> QString { return QString("%2").arg(donate.url, donate.platform); }; From 17b30b2ae25a6138f7d0452805998dfd24cd683e Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Tue, 24 May 2022 22:37:00 +0800 Subject: [PATCH 108/308] clean up .clang-format --- .clang-format | 220 +++----------------------------------------------- 1 file changed, 12 insertions(+), 208 deletions(-) diff --git a/.clang-format b/.clang-format index ed96c0303..f90a40604 100644 --- a/.clang-format +++ b/.clang-format @@ -1,211 +1,15 @@ --- Language: Cpp -# BasedOnStyle: Chromium -AccessModifierOffset: -1 -AlignAfterOpenBracket: Align -AlignArrayOfStructures: None -AlignConsecutiveMacros: false # changed -AlignConsecutiveAssignments: false # changed -AlignConsecutiveBitFields: None -AlignConsecutiveDeclarations: None -AlignEscapedNewlines: Left -AlignOperands: Align -AlignTrailingComments: true -AllowAllArgumentsOnNextLine: true -AllowAllParametersOfDeclarationOnNextLine: false -AllowShortEnumsOnASingleLine: true -AllowShortBlocksOnASingleLine: false -AllowShortCaseLabelsOnASingleLine: false -AllowShortFunctionsOnASingleLine: Inline -AllowShortLambdasOnASingleLine: All -AllowShortIfStatementsOnASingleLine: false # changed -AllowShortLoopsOnASingleLine: false -AlwaysBreakAfterDefinitionReturnType: None -AlwaysBreakAfterReturnType: None -AlwaysBreakBeforeMultilineStrings: true -AlwaysBreakTemplateDeclarations: Yes -AttributeMacros: - - __capability -BinPackArguments: true -BinPackParameters: false +BasedOnStyle: Chromium +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AllowShortIfStatementsOnASingleLine: false BraceWrapping: - AfterCaseLabel: false - AfterClass: false - AfterControlStatement: Never - AfterEnum: false - AfterFunction: true # changed - AfterNamespace: false - AfterObjCDeclaration: false - AfterStruct: false - AfterUnion: false - AfterExternBlock: false - BeforeCatch: false - BeforeElse: false - BeforeLambdaBody: false - BeforeWhile: false - IndentBraces: false - SplitEmptyFunction: false # changed - SplitEmptyRecord: false # changed - SplitEmptyNamespace: false # changed -BreakBeforeBinaryOperators: None -BreakBeforeConceptDeclarations: true -BreakBeforeBraces: Custom # changed -BreakBeforeInheritanceComma: false -BreakInheritanceList: BeforeColon -BreakBeforeTernaryOperators: true -BreakConstructorInitializersBeforeComma: true -BreakConstructorInitializers: BeforeComma # changed -BreakAfterJavaFieldAnnotations: false -BreakStringLiterals: true -ColumnLimit: 140 # changed -CommentPragmas: '^ IWYU pragma:' -CompactNamespaces: false -ConstructorInitializerAllOnOneLineOrOnePerLine: true -ConstructorInitializerIndentWidth: 4 -ContinuationIndentWidth: 4 -Cpp11BracedListStyle: false # changed -DeriveLineEnding: true -DerivePointerAlignment: false -DisableFormat: false -EmptyLineAfterAccessModifier: Never -EmptyLineBeforeAccessModifier: LogicalBlock -ExperimentalAutoDetectBinPacking: false -FixNamespaceComments: true -ForEachMacros: - - foreach - - Q_FOREACH - - BOOST_FOREACH -IfMacros: - - KJ_IF_MAYBE -IncludeBlocks: Preserve -IncludeCategories: - - Regex: '^' - Priority: 2 - SortPriority: 0 - CaseSensitive: false - - Regex: '^<.*\.h>' - Priority: 1 - SortPriority: 0 - CaseSensitive: false - - Regex: '^<.*' - Priority: 2 - SortPriority: 0 - CaseSensitive: false - - Regex: '.*' - Priority: 3 - SortPriority: 0 - CaseSensitive: false -IncludeIsMainRegex: '([-_](test|unittest))?$' -IncludeIsMainSourceRegex: '' -IndentAccessModifiers: false -IndentCaseLabels: true -IndentCaseBlocks: false -IndentGotoLabels: true -IndentPPDirectives: None -IndentExternBlock: AfterExternBlock -IndentRequires: false -IndentWidth: 4 # changed -IndentWrappedFunctionNames: false -InsertTrailingCommas: None -JavaScriptQuotes: Leave -JavaScriptWrapImports: true -KeepEmptyLinesAtTheStartOfBlocks: false -LambdaBodyIndentation: Signature -MacroBlockBegin: '' -MacroBlockEnd: '' -MaxEmptyLinesToKeep: 1 -NamespaceIndentation: None -ObjCBinPackProtocolList: Never -ObjCBlockIndentWidth: 2 -ObjCBreakBeforeNestedBlockParam: true -ObjCSpaceAfterProperty: false -ObjCSpaceBeforeProtocolList: true -PenaltyBreakAssignment: 2 -PenaltyBreakBeforeFirstCallParameter: 1 -PenaltyBreakComment: 300 -PenaltyBreakFirstLessLess: 120 -PenaltyBreakString: 1000 -PenaltyBreakTemplateDeclaration: 10 -PenaltyExcessCharacter: 1000000 -PenaltyReturnTypeOnItsOwnLine: 200 -PenaltyIndentedWhitespace: 0 -PointerAlignment: Left -PPIndentWidth: -1 -RawStringFormats: - - Language: Cpp - Delimiters: - - cc - - CC - - cpp - - Cpp - - CPP - - 'c++' - - 'C++' - CanonicalDelimiter: '' - BasedOnStyle: google - - Language: TextProto - Delimiters: - - pb - - PB - - proto - - PROTO - EnclosingFunctions: - - EqualsProto - - EquivToProto - - PARSE_PARTIAL_TEXT_PROTO - - PARSE_TEST_PROTO - - PARSE_TEXT_PROTO - - ParseTextOrDie - - ParseTextProtoOrDie - - ParseTestProto - - ParsePartialTestProto - CanonicalDelimiter: pb - BasedOnStyle: google -ReferenceAlignment: Pointer -ReflowComments: true -ShortNamespaceLines: 1 -SortIncludes: CaseSensitive -SortJavaStaticImport: Before -SortUsingDeclarations: true -SpaceAfterCStyleCast: false -SpaceAfterLogicalNot: false -SpaceAfterTemplateKeyword: true -SpaceBeforeAssignmentOperators: true -SpaceBeforeCaseColon: false -SpaceBeforeCpp11BracedList: false -SpaceBeforeCtorInitializerColon: true -SpaceBeforeInheritanceColon: true -SpaceBeforeParens: ControlStatements -SpaceAroundPointerQualifiers: Default -SpaceBeforeRangeBasedForLoopColon: true -SpaceInEmptyBlock: false -SpaceInEmptyParentheses: false -SpacesBeforeTrailingComments: 2 -SpacesInAngles: Never -SpacesInConditionalStatement: false -SpacesInContainerLiterals: true -SpacesInCStyleCastParentheses: false -SpacesInLineCommentPrefix: - Minimum: 1 - Maximum: -1 -SpacesInParentheses: false -SpacesInSquareBrackets: false -SpaceBeforeSquareBrackets: false -BitFieldColonSpacing: Both -Standard: Auto -StatementAttributeLikeMacros: - - Q_EMIT -StatementMacros: - - Q_UNUSED - - QT_REQUIRE_VERSION -TabWidth: 8 -UseCRLF: false -UseTab: Never -WhitespaceSensitiveMacros: - - STRINGIZE - - PP_STRINGIZE - - BOOST_PP_STRINGIZE - - NS_SWIFT_NAME - - CF_SWIFT_NAME -... - + AfterFunction: true + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBraces: Custom +BreakConstructorInitializers: BeforeComma +ColumnLimit: 140 +Cpp11BracedListStyle: false From d0337da8ea54c272aadfe30bfe0474ae82011109 Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 24 May 2022 11:52:27 -0300 Subject: [PATCH 109/308] feat: add remaining links to modrinth modpacks --- .../modrinth/ModrinthPackManifest.cpp | 14 +++++++ .../modrinth/ModrinthPackManifest.h | 3 ++ .../modplatform/modrinth/ModrinthPage.cpp | 38 ++++++++++++++----- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp index f47942a00..73c8ef848 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp @@ -62,8 +62,22 @@ void loadIndexedInfo(Modpack& pack, QJsonObject& obj) { pack.extra.body = Json::ensureString(obj, "body"); pack.extra.projectUrl = QString("https://modrinth.com/modpack/%1").arg(Json::ensureString(obj, "slug")); + + pack.extra.issuesUrl = Json::ensureString(obj, "issues_url"); + if(pack.extra.issuesUrl.endsWith('/')) + pack.extra.issuesUrl.chop(1); + pack.extra.sourceUrl = Json::ensureString(obj, "source_url"); + if(pack.extra.sourceUrl.endsWith('/')) + pack.extra.sourceUrl.chop(1); + pack.extra.wikiUrl = Json::ensureString(obj, "wiki_url"); + if(pack.extra.wikiUrl.endsWith('/')) + pack.extra.wikiUrl.chop(1); + + pack.extra.discordUrl = Json::ensureString(obj, "discord_url"); + if(pack.extra.discordUrl.endsWith('/')) + pack.extra.discordUrl.chop(1); auto donate_arr = Json::ensureArray(obj, "donation_urls"); for(auto d : donate_arr){ diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.h b/launcher/modplatform/modrinth/ModrinthPackManifest.h index c8ca36605..e95cb589d 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.h +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.h @@ -68,8 +68,11 @@ struct ModpackExtra { QString body; QString projectUrl; + + QString issuesUrl; QString sourceUrl; QString wikiUrl; + QString discordUrl; QList donate; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index f7c5b2ce8..d85006749 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -224,19 +224,37 @@ void ModrinthPage::updateUI() // TODO: Implement multiple authors with links text += "
" + tr(" by ") + QString("%2").arg(std::get<1>(current.author).toString(), std::get<0>(current.author)); - if(!current.extra.donate.isEmpty()) { - text += tr("

Donate information:
"); - auto donateToStr = [](Modrinth::DonationData& donate) -> QString { - return QString("%2").arg(donate.url, donate.platform); - }; - QStringList donates; - for (auto& donate : current.extra.donate) { - donates.append(donateToStr(donate)); + if(current.extraInfoLoaded) { + if (!current.extra.donate.isEmpty()) { + text += "

" + tr("Donate information: "); + auto donateToStr = [](Modrinth::DonationData& donate) -> QString { + return QString("%2").arg(donate.url, donate.platform); + }; + QStringList donates; + for (auto& donate : current.extra.donate) { + donates.append(donateToStr(donate)); + } + text += donates.join(", "); } - text += donates.join(", "); + + if (!current.extra.issuesUrl.isEmpty() + || !current.extra.sourceUrl.isEmpty() + || !current.extra.wikiUrl.isEmpty() + || !current.extra.discordUrl.isEmpty()) { + text += "

" + tr("External links:") + "
"; + } + + if (!current.extra.issuesUrl.isEmpty()) + text += "- " + tr("Issues: %1").arg(current.extra.issuesUrl) + "
"; + if (!current.extra.wikiUrl.isEmpty()) + text += "- " + tr("Wiki: %1").arg(current.extra.wikiUrl) + "
"; + if (!current.extra.sourceUrl.isEmpty()) + text += "- " + tr("Source code: %1").arg(current.extra.sourceUrl) + "
"; + if (!current.extra.discordUrl.isEmpty()) + text += "- " + tr("Discord: %1").arg(current.extra.discordUrl) + "
"; } - text += "
"; + text += "


"; HoeDown h; text += h.process(current.extra.body.toUtf8()); From ae2ef324f297adee33968b50e70d9cf5d8ed72fb Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 24 May 2022 11:58:11 -0300 Subject: [PATCH 110/308] feat: add remaining links to modrinth mods --- launcher/modplatform/ModIndex.h | 5 +++ .../modrinth/ModrinthPackIndex.cpp | 16 ++++++++ launcher/ui/pages/modplatform/ModPage.cpp | 39 ++++++++++++++----- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 6e1a01bc4..4d1d02a54 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -32,6 +32,11 @@ struct IndexedVersion { struct ExtraPackData { QList donate; + + QString issuesUrl; + QString sourceUrl; + QString wikiUrl; + QString discordUrl; }; struct IndexedPack { diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 32b4cfd4c..a9aa3a9d5 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -52,6 +52,22 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& obj) { + pack.extraData.issuesUrl = Json::ensureString(obj, "issues_url"); + if(pack.extraData.issuesUrl.endsWith('/')) + pack.extraData.issuesUrl.chop(1); + + pack.extraData.sourceUrl = Json::ensureString(obj, "source_url"); + if(pack.extraData.sourceUrl.endsWith('/')) + pack.extraData.sourceUrl.chop(1); + + pack.extraData.wikiUrl = Json::ensureString(obj, "wiki_url"); + if(pack.extraData.wikiUrl.endsWith('/')) + pack.extraData.wikiUrl.chop(1); + + pack.extraData.discordUrl = Json::ensureString(obj, "discord_url"); + if(pack.extraData.discordUrl.endsWith('/')) + pack.extraData.discordUrl.chop(1); + auto donate_arr = Json::ensureArray(obj, "donation_urls"); for(auto d : donate_arr){ auto d_obj = Json::requireObject(d); diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 39e47edcb..e0251160b 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -215,19 +215,38 @@ void ModPage::updateUi() text += "
" + tr(" by ") + authorStrs.join(", "); } - if(!current.extraData.donate.isEmpty()) { - text += tr("

Donate information:
"); - auto donateToStr = [](ModPlatform::DonationData& donate) -> QString { - return QString("%2").arg(donate.url, donate.platform); - }; - QStringList donates; - for (auto& donate : current.extraData.donate) { - donates.append(donateToStr(donate)); + + if(current.extraDataLoaded) { + if (!current.extraData.donate.isEmpty()) { + text += "

" + tr("Donate information: "); + auto donateToStr = [](ModPlatform::DonationData& donate) -> QString { + return QString("%2").arg(donate.url, donate.platform); + }; + QStringList donates; + for (auto& donate : current.extraData.donate) { + donates.append(donateToStr(donate)); + } + text += donates.join(", "); } - text += donates.join(", "); + + if (!current.extraData.issuesUrl.isEmpty() + || !current.extraData.sourceUrl.isEmpty() + || !current.extraData.wikiUrl.isEmpty() + || !current.extraData.discordUrl.isEmpty()) { + text += "

" + tr("External links:") + "
"; + } + + if (!current.extraData.issuesUrl.isEmpty()) + text += "- " + tr("Issues: %1").arg(current.extraData.issuesUrl) + "
"; + if (!current.extraData.wikiUrl.isEmpty()) + text += "- " + tr("Wiki: %1").arg(current.extraData.wikiUrl) + "
"; + if (!current.extraData.sourceUrl.isEmpty()) + text += "- " + tr("Source code: %1").arg(current.extraData.sourceUrl) + "
"; + if (!current.extraData.discordUrl.isEmpty()) + text += "- " + tr("Discord: %1").arg(current.extraData.discordUrl) + "
"; } - text += "

"; + text += "
"; ui->packDescription->setHtml(text + current.description); } From c5eb6fe6fb733c62c071473a8d1102c44e133c17 Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 24 May 2022 12:14:08 -0300 Subject: [PATCH 111/308] feat: add links for curseforge mods NOT DOWNLOAD LINKS! (someone would ask it i'm sure :p) --- launcher/modplatform/flame/FlameAPI.h | 5 ++++- launcher/modplatform/flame/FlameModIndex.cpp | 21 ++++++++++++++++++++ launcher/modplatform/flame/FlameModIndex.h | 1 + 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 6ce474c84..3b5c57824 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -41,7 +41,10 @@ class FlameAPI : public NetworkModAPI { .arg(gameVersionStr); }; - inline auto getModInfoURL(QString& id) const -> QString override { return {}; }; + inline auto getModInfoURL(QString& id) const -> QString override + { + return QString("https://api.curseforge.com/v1/mods/%1").arg(id); + }; inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override { diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index ba0824cf5..1a2f2bd4f 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -25,6 +25,27 @@ void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) packAuthor.url = Json::requireString(author, "url"); pack.authors.append(packAuthor); } + + loadExtraPackData(pack, obj); +} + +void FlameMod::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& obj) +{ + auto links_obj = Json::ensureObject(obj, "links"); + + pack.extraData.issuesUrl = Json::ensureString(links_obj, "issuesUrl"); + if(pack.extraData.issuesUrl.endsWith('/')) + pack.extraData.issuesUrl.chop(1); + + pack.extraData.sourceUrl = Json::ensureString(links_obj, "sourceUrl"); + if(pack.extraData.sourceUrl.endsWith('/')) + pack.extraData.sourceUrl.chop(1); + + pack.extraData.wikiUrl = Json::ensureString(links_obj, "wikiUrl"); + if(pack.extraData.wikiUrl.endsWith('/')) + pack.extraData.wikiUrl.chop(1); + + pack.extraDataLoaded = true; } void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h index d3171d943..c631a6f3a 100644 --- a/launcher/modplatform/flame/FlameModIndex.h +++ b/launcher/modplatform/flame/FlameModIndex.h @@ -12,6 +12,7 @@ namespace FlameMod { void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj); +void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr& network, From e64438016040c1a7ad1834a5735d34d6d1ea0cdb Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 24 May 2022 12:27:32 -0300 Subject: [PATCH 112/308] feat: add links to curseforge modpacks --- launcher/modplatform/flame/FlamePackIndex.cpp | 27 +++++++- launcher/modplatform/flame/FlamePackIndex.h | 12 +++- .../ui/pages/modplatform/flame/FlamePage.cpp | 68 ++++++++++++------- .../ui/pages/modplatform/flame/FlamePage.h | 2 + 4 files changed, 84 insertions(+), 25 deletions(-) diff --git a/launcher/modplatform/flame/FlamePackIndex.cpp b/launcher/modplatform/flame/FlamePackIndex.cpp index 6d48a3bf2..43aae02e8 100644 --- a/launcher/modplatform/flame/FlamePackIndex.cpp +++ b/launcher/modplatform/flame/FlamePackIndex.cpp @@ -6,7 +6,6 @@ void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj) { pack.addonId = Json::requireInteger(obj, "id"); pack.name = Json::requireString(obj, "name"); - pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", ""); pack.description = Json::ensureString(obj, "summary", ""); auto logo = Json::requireObject(obj, "logo"); @@ -46,6 +45,32 @@ void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj) if (!found) { throw JSONValidationError(QString("Pack with no good file, skipping: %1").arg(pack.name)); } + + loadIndexedInfo(pack, obj); +} + +void Flame::loadIndexedInfo(IndexedPack& pack, QJsonObject& obj) +{ + auto links_obj = Json::ensureObject(obj, "links"); + + pack.extra.websiteUrl = Json::ensureString(links_obj, "issuesUrl"); + if(pack.extra.websiteUrl.endsWith('/')) + pack.extra.websiteUrl.chop(1); + + pack.extra.issuesUrl = Json::ensureString(links_obj, "issuesUrl"); + if(pack.extra.issuesUrl.endsWith('/')) + pack.extra.issuesUrl.chop(1); + + pack.extra.sourceUrl = Json::ensureString(links_obj, "sourceUrl"); + if(pack.extra.sourceUrl.endsWith('/')) + pack.extra.sourceUrl.chop(1); + + pack.extra.wikiUrl = Json::ensureString(links_obj, "wikiUrl"); + if(pack.extra.wikiUrl.endsWith('/')) + pack.extra.wikiUrl.chop(1); + + pack.extraInfoLoaded = true; + } void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr) diff --git a/launcher/modplatform/flame/FlamePackIndex.h b/launcher/modplatform/flame/FlamePackIndex.h index a8bb15be4..c0781d629 100644 --- a/launcher/modplatform/flame/FlamePackIndex.h +++ b/launcher/modplatform/flame/FlamePackIndex.h @@ -21,6 +21,13 @@ struct IndexedVersion { QString fileName; }; +struct ModpackExtra { + QString websiteUrl; + QString wikiUrl; + QString issuesUrl; + QString sourceUrl; +}; + struct IndexedPack { int addonId; @@ -29,13 +36,16 @@ struct IndexedPack QList authors; QString logoName; QString logoUrl; - QString websiteUrl; bool versionsLoaded = false; QVector versions; + + bool extraInfoLoaded = false; + ModpackExtra extra; }; void loadIndexedPack(IndexedPack & m, QJsonObject & obj); +void loadIndexedInfo(IndexedPack&, QJsonObject&); void loadIndexedPackVersions(IndexedPack & m, QJsonArray & arr); } diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index ec7746217..a238343be 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -119,29 +119,6 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second) } current = listModel->data(first, Qt::UserRole).value(); - QString text = ""; - QString name = current.name; - - if (current.websiteUrl.isEmpty()) - text = name; - else - text = "" + name + ""; - if (!current.authors.empty()) { - auto authorToStr = [](Flame::ModpackAuthor& author) { - if (author.url.isEmpty()) { - return author.name; - } - return QString("%2").arg(author.url, author.name); - }; - QStringList authorStrs; - for (auto& author : current.authors) { - authorStrs.push_back(authorToStr(author)); - } - text += "
" + tr(" by ") + authorStrs.join(", "); - } - text += "

"; - - ui->packDescription->setHtml(text + current.description); if (current.versionsLoaded == false) { qDebug() << "Loading flame modpack versions"; @@ -188,6 +165,8 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second) suggestCurrent(); } + + updateUi(); } void FlamePage::suggestCurrent() @@ -217,3 +196,46 @@ void FlamePage::onVersionSelectionChanged(QString data) selectedVersion = ui->versionSelectionBox->currentData().toString(); suggestCurrent(); } + +void FlamePage::updateUi() +{ + QString text = ""; + QString name = current.name; + + if (current.extra.websiteUrl.isEmpty()) + text = name; + else + text = "" + name + ""; + if (!current.authors.empty()) { + auto authorToStr = [](Flame::ModpackAuthor& author) { + if (author.url.isEmpty()) { + return author.name; + } + return QString("%2").arg(author.url, author.name); + }; + QStringList authorStrs; + for (auto& author : current.authors) { + authorStrs.push_back(authorToStr(author)); + } + text += "
" + tr(" by ") + authorStrs.join(", "); + } + + if(current.extraInfoLoaded) { + if (!current.extra.issuesUrl.isEmpty() + || !current.extra.sourceUrl.isEmpty() + || !current.extra.wikiUrl.isEmpty()) { + text += "

" + tr("External links:") + "
"; + } + + if (!current.extra.issuesUrl.isEmpty()) + text += "- " + tr("Issues: %1").arg(current.extra.issuesUrl) + "
"; + if (!current.extra.wikiUrl.isEmpty()) + text += "- " + tr("Wiki: %1").arg(current.extra.wikiUrl) + "
"; + if (!current.extra.sourceUrl.isEmpty()) + text += "- " + tr("Source code: %1").arg(current.extra.sourceUrl) + "
"; + } + + text += "
"; + + ui->packDescription->setHtml(text + current.description); +} diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.h b/launcher/ui/pages/modplatform/flame/FlamePage.h index baac57c98..8130e4169 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.h +++ b/launcher/ui/pages/modplatform/flame/FlamePage.h @@ -79,6 +79,8 @@ public: virtual bool shouldDisplay() const override; void retranslate() override; + void updateUi(); + void openedImpl() override; bool eventFilter(QObject * watched, QEvent * event) override; From 67c5aa0be9c20e67bb96e917cb881a9d953b9ce4 Mon Sep 17 00:00:00 2001 From: byquanton <32410361+byquanton@users.noreply.github.com> Date: Tue, 24 May 2022 17:44:23 +0200 Subject: [PATCH 113/308] Update org.polymc.PolyMC.metainfo.xml.in Should fix Flatpak/Flathub build --- program_info/org.polymc.PolyMC.metainfo.xml.in | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/program_info/org.polymc.PolyMC.metainfo.xml.in b/program_info/org.polymc.PolyMC.metainfo.xml.in index ff4af1c32..ea665655a 100644 --- a/program_info/org.polymc.PolyMC.metainfo.xml.in +++ b/program_info/org.polymc.PolyMC.metainfo.xml.in @@ -28,23 +28,23 @@ The main PolyMC window - https://polymc.org/img/screenshots/LauncherDark.png + https://polymc.org/img/screenshots/LauncherDark.png Modpack installation - https://polymc.org/img/screenshots/ModpackInstallDark.png + https://polymc.org/img/screenshots/ModpackInstallDark.png Mod installation - https://polymc.org/img/screenshots/ModInstallDark.png + https://polymc.org/img/screenshots/ModInstallDark.png Instance management - https://polymc.org/img/screenshots/PropertiesDark.png + https://polymc.org/img/screenshots/PropertiesDark.png Cat :) - https://polymc.org/img/screenshots/LauncherCatDark.png + https://polymc.org/img/screenshots/LauncherCatDark.png From f8e7fb3d481d41473a6d7102d5c218e4a18bba3d Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 24 May 2022 20:19:31 -0300 Subject: [PATCH 114/308] fix: better handle corner case --- launcher/tasks/SequentialTask.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/launcher/tasks/SequentialTask.cpp b/launcher/tasks/SequentialTask.cpp index e7d585246..ee57cac1b 100644 --- a/launcher/tasks/SequentialTask.cpp +++ b/launcher/tasks/SequentialTask.cpp @@ -34,6 +34,11 @@ void SequentialTask::executeTask() bool SequentialTask::abort() { if(m_currentIndex == -1 || m_currentIndex >= m_queue.size()) { + if(m_currentIndex == -1) { + // Don't call emitAborted() here, we want to bypass the 'is the task running' check + emit aborted(); + emit finished(); + } m_queue.clear(); return true; } From 8a1ba03bcb6d43f9aae0555125447b4d5cd2dc1a Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Wed, 25 May 2022 11:46:15 +0800 Subject: [PATCH 115/308] show default metaserver --- launcher/ui/pages/global/APIPage.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index 6ad243ddc..5d812d079 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -48,6 +48,7 @@ #include "tools/BaseProfiler.h" #include "Application.h" #include "net/PasteUpload.h" +#include "BuildConfig.h" APIPage::APIPage(QWidget *parent) : QWidget(parent), @@ -76,6 +77,8 @@ APIPage::APIPage(QWidget *parent) : ui->baseURLEntry->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->baseURLEntry)); ui->tabWidget->tabBar()->hide(); + ui->metaURL->setPlaceholderText(BuildConfig.META_URL); + loadSettings(); resetBaseURLNote(); From a28fa219d7693bed9e506346aef0fc585dad3af0 Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Wed, 25 May 2022 14:21:09 +0800 Subject: [PATCH 116/308] fix indent width --- .clang-format | 1 + 1 file changed, 1 insertion(+) diff --git a/.clang-format b/.clang-format index f90a40604..51ca0e1c1 100644 --- a/.clang-format +++ b/.clang-format @@ -1,6 +1,7 @@ --- Language: Cpp BasedOnStyle: Chromium +IndentWidth: 4 AlignConsecutiveMacros: false AlignConsecutiveAssignments: false AllowShortIfStatementsOnASingleLine: false From e50ec31351fb226028fef5d746ecc59c9d51fecb Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Wed, 25 May 2022 14:44:47 +0800 Subject: [PATCH 117/308] fix --- launcher/ui/pages/global/APIPage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index 24189c5c5..cf15065bc 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -146,7 +146,7 @@ - (Default) + From 938cae1130464fcedaa776d9f54898dece14f9a7 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 25 May 2022 23:04:49 +0200 Subject: [PATCH 118/308] revert: remove CurseForge workaround for packs too Partial revert. Handles missing download URLs. --- launcher/modplatform/flame/FlamePackIndex.cpp | 12 ++++-------- launcher/modplatform/flame/FlamePackIndex.h | 1 - 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/launcher/modplatform/flame/FlamePackIndex.cpp b/launcher/modplatform/flame/FlamePackIndex.cpp index 6d48a3bf2..bece78434 100644 --- a/launcher/modplatform/flame/FlamePackIndex.cpp +++ b/launcher/modplatform/flame/FlamePackIndex.cpp @@ -65,16 +65,12 @@ void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr) // pick the latest version supported file.mcVersion = versionArray[0].toString(); file.version = Json::requireString(version, "displayName"); - file.fileName = Json::requireString(version, "fileName"); file.downloadUrl = Json::ensureString(version, "downloadUrl"); - if(file.downloadUrl.isEmpty()){ - //FIXME : HACK, MAY NOT WORK FOR LONG - file.downloadUrl = QString("https://media.forgecdn.net/files/%1/%2/%3") - .arg(QString::number(QString::number(file.fileId).leftRef(4).toInt()) - ,QString::number(QString::number(file.fileId).rightRef(3).toInt()) - ,QUrl::toPercentEncoding(file.fileName)); + + // only add if we have a download URL (third party distribution is enabled) + if (!file.downloadUrl.isEmpty()) { + unsortedVersions.append(file); } - unsortedVersions.append(file); } auto orderSortPredicate = [](const IndexedVersion& a, const IndexedVersion& b) -> bool { return a.fileId > b.fileId; }; diff --git a/launcher/modplatform/flame/FlamePackIndex.h b/launcher/modplatform/flame/FlamePackIndex.h index a8bb15be4..7ffa29c3d 100644 --- a/launcher/modplatform/flame/FlamePackIndex.h +++ b/launcher/modplatform/flame/FlamePackIndex.h @@ -18,7 +18,6 @@ struct IndexedVersion { QString version; QString mcVersion; QString downloadUrl; - QString fileName; }; struct IndexedPack From f541ea659c2050b333fc28db582b3848a0fb8976 Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Thu, 26 May 2022 18:16:32 +0800 Subject: [PATCH 119/308] better new icon --- program_info/org.polymc.PolyMC.bigsur.svg | 124 +++++++++++++++++----- program_info/polymc.icns | Bin 331581 -> 518794 bytes 2 files changed, 98 insertions(+), 26 deletions(-) diff --git a/program_info/org.polymc.PolyMC.bigsur.svg b/program_info/org.polymc.PolyMC.bigsur.svg index 8297049be..e9582f5d5 100644 --- a/program_info/org.polymc.PolyMC.bigsur.svg +++ b/program_info/org.polymc.PolyMC.bigsur.svg @@ -5,47 +5,83 @@ fill="none" xmlns="http://www.w3.org/2000/svg" > - + + + + + + + + + + + + + + + + + + + + + + diff --git a/program_info/polymc.icns b/program_info/polymc.icns index 7365e919dec4f8241110641dedd7f849a9a8b95a..231fa22abafe2dc8260e8c17f45574816fa88991 100644 GIT binary patch literal 518794 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

i#o*6SXlxwE$K+x_J_JsIO&E<*{al6zm7 zTMa^U9`P>N-!1^ zXY(SOux~yyS*1-u|jWF)Qc%{Pd*4{s;%>IN)iR{t$!am8Hl6dzMT8Pn*Ki zVB=rdc>CVNz*nGs3a=#8%ja?mNOb4799GIkX+epLh(O+a;_s)^&7 zb?~`E%vmpne*gDmi?{)~wBznaobq5Hdg*BvKb1EN=Oy79kV+fx&J-ZBoc)isQ^o{OB88Hb`aJ$>mgjh-+%@49y~o zX<9p^w$HRW_ygb~o&w_U44YpJa0UIm{w78*omX?0&>fn&w1F8u0f7ytpZO+gg?Zab zxgl1cccysN(A<&v5r=vnKSQUuqp0r1hXTXEJyxIBCoT9c+Y1e7qpmqL#Y#5QYcHc$D35hQfSFnQ#LX2Q$=fvXe@< zzAKCpTow9dR8?uBzLH3|4e03|#a;2dk(gBWfLhLS<9TX;XO`h0y5>;2C?DJrCaFUh z+lv5s1pKKUQ9<;j*fMx=qfPyHoftwyzbQqKrFSwk^UI@7(yGTlG6m>Xm}_vKn>Jwh zp&~p@B71@&!tOi2bpBg(*^Q^pn z2=AU`WjIiB7&tW5H#MLeEwue>tp+dB0M5e?(AhTU=F9t8)#L-KK)*XRU>V*Ugnxdq zW6?Yl?cQ@$5j#W>gl{+LDx2{ z1kuB_yvbXvI;F5w+P@H41&p z!${0Ybu3D^d|{0DGR>mFM&`MhT+@vP9h@DW`ggl{OV{E|o@C$t1LneA-Nx-Chh`dg zzHT1n(9%7!5y^F`gF@hEWV(n~5`zua&3a95>mHN`H*+Ph(RB6Te1HoYf^_&Xj@YvZ=JF&l*PH{@C@&f|=Ul6M>D{ zOMvnFIXAUrbgn`^9=Vul3Hka9qXHH}fx9Amez9Gf1`^^cz6|(3P$JlpNa9dVr|*ET zz5EhpA?kGjFW<^RH%M=M0b~+8txy6d-?k98WZdBFG9}}TFi34(5(d_nxtM7`@!Uln z4Ic)vb`dU9kbY_f_Q2<(e1lVSh-GW-@p4<|f8m*L-Es0U0(XB9{qX;5HORBvqw1Xt zv8b;M8d*75!qk!mVChfNMj^?AxZHJpM@O$Q8F8v`N^Y8gZ zRKQWnUJN}`;h|#fse>u+Cq)ZYPtRpJCrQp8vnvQ*HF5xMOH@?yXXCC zs~J3q$mg^hC|{1oGW4!9Y!Zy(k?%D_+ESdR^))kk+jUGA)fd<|zDMcu)9(EismJb3 z!#=Fc$N1VYhN(O8Yq_&{T;e$1E$U4h~f3Lg`txG8yn^--_ z{=ZUi;>9udW6JTSV^ij4bh)jJt`ouWV_qat1Px+SLgo3cWri>C8t9VT+1Ei0M9Hf; z(v57!kSCesPWNI!>~yIH_M$<+$xjaZ-N6R+!eA@>G8X-mG*y_^dFbl1a8lxW<-kh^ z%GV=yrf=+ZdbUTcgcuuWff#io6(W@_+SAC8OENsBdU~SlHxZ;pSd9uJ zMpVC;$UP!pPNr}!M1LS%e~b9OUdtW|zD@b`@TblFsz7RZquSs!;Gpd|Z6sZ!rJ8%H zAXolQ=uffwuAjbOSIJU16Slv$9mHU5Q~vKoQNnhjgw0@^rYEe*FbfYrHs?Sx;_myR zVm%Q$Ls!KJA`*dax$oPph-Qe_2qojpwasCi0ZvCP7Ysibhpb_*j8`by=K*IBb$S@{e9}IQu%Z{&K~2d)yNs@Rp$@HM zwPXZ7XU*b0soJWl5s~`jqiBB z@lv%u_B)wFBjFoX3#310MCPR`@oJ`|5=bR9l&A9K3jLEBYh%(-OPEliIu%j-=f|Rb z$XJ0N-xL-;6jrRhRc%2nq1Og23TAzO6KztF!*VCEL&Gcl45)rN!>WW-WYufBuLGbt z%c$d<1=6d*o%W>Ga{RSzn9q@qar47DdEMkgJz=@+r$XSF!mQO(UGN{KbE+BGJ{{H` zN5Ep6+#Ps$e9-AGGxNdqxJ9r~6fzs8n}ruhb=Qz6!7B1%?lhx^{`TDjy7O9QPOe8%;0ZNnCAK zzq{nL9%V$`o)YB6g9vrkau5ygs{aJ}D~4Iwn&AB<$f_$~Sm6Tu@kgZOmxd`zQiI?Zn2Jd#TevGRRI`{{Hc`l6Kl zXJA00jrg8AL}PxVxb?w6iwImwr`a+Pd;2Ez{r*$`L=yK>R*-#;_5;f|i^_T1`BIFI zltaxRfK6p(mAL=wX%jVr%0t%8GW_h_&3LxmVHiCPIcYPpdmh=CK{Ymo@+(4+dLTD$7QL1>th5yEG%Dj&3WYW14VBjMUC!7nfJvk zQY9vZjd7s4VtuSUBFeew{~vJ1PrBz7Iy~35hy-3Se|1h^obf&+1Gv863C|5G<3qWr zb!-T9rh4XR`T&ge4&TZTS7%^qi_hQq_xy&499rC#Ud#>+d#p$;IqfgG)3sBNOK{ze zXA`eDT;gF=pDb|n8m-+Kt?Q}c$3BJ5b3I;V4@P*nz={+6IbWFmId68jE8tE`GfFFr zMwUPipA19xJE7~>jZX`^ZQnkAW<~T*GvKYz2M+y%+jgPN2n#*@|a3|?V_bl_^sxOh7s!MO~`c2MOc1JmVy`uvZ9ZlI()oc zYd#zjk2OXqtN$zuqXMoP_x2N~wqEOP6JJcdHQdUs(`4Ad!AosjrsYmbeusmA{>=Q76J+V6MH*W8 zO?%LXZM&c8uw(OxU1mhar2%RWVG8llQf3-hZD25a6Ed>+cD>UN0(l z-@f`$4*esf1dmq4D=v-c0l5_GY$ZE%KV@5zCrb~0%NEo)9V%L!45y5W7qzL%;pc%C z{m+aMzH~}`s;eTJ1*Gw&}^Er(|h(L%~JM*dC2{h9() z`E@p_xdT+H-_C1;E0XS_jLd>TJ=%*!j3tow3r1Umn8RzM?VT6>SYFI{taOktDqipW z^M1FQ$OpBC6mnz8bQ14sF7>&tb4jUtDrBO&7<{fsvZp0HJsw)EXLM7K91J)C`cr57 z&t}Xmm7tUFo%Qy^Gc_UbQ)REj`b3-Qv?zIvCD}!%`PQ;{b+HWt)*%jOiG}@@avP1~FzZXcImgdRqu<`!pJ14? zcGQ5ecsL9zIKRxDhMp`)a9Ix``GqjZfS>E>#@2aXY)+JvdCz1R?M$bV_>AjiOJi+` zp?g%h&FV_K^4~E!U!Oy0`_!j)sxhSVX3V+dGu@2a-kjtB;m10&I1@qIj6_M^TiWBI z#jB3G`tW}NC*5{YdsZnbU}Q4iHAKKUffB|+=pv)6R~Hn8m4S&k227{7+yo&HLRt!U zGOzcb=>*dM=Eig@iyh~F_`|6$`A2WSeibKpcwGjPF~Py5lb75|lR`%Ze!0L*I;WZ? z8q^O)&Q_U4zOLLgVt)CGQu@*>w8$r-ItXf^6f4-8(6ymDb!{>?sO1J z$^`TiOEClFUh#eBhV77F)~)LCL%h%J3TGwGW{?v-zn0nlBO|zegMw=4h|)Kvh}X?{`q8?y9ktXitT_qZNc#J0-56I z-(P`yaAyIi&zH*L-BX9aU*Q}Zxku42s5Gkwl{(@ZWO-QiI=yijTK&I z+gYdURmmaib+Uu-g~XU#&_dp&pS>?q6f^Wz(0o@L>=Z%n6JvI+2wgwtzA90aj7n5( zu9rP=uNeSJf{Fa&V)yC}v+Mj;wMGTW?9!*N(hpBS#K%q_t+4FnEY2IOT45nzSN?zw zu!9v!OWwtnrEpD_@tu?fX*8dl5e-%Uqn+sHIxYX6Z&E0JCnNYSQj1;(F-ByuT5M*( ztEIvusg_X4X`aVeE9XdLzIWUzFXB`-&0^t3AyPS28a*+MWs_}*zg<0*yTetC8ek_K z>f?5LN2JUoc&1E;`u2iNZYvR*gh9!yaNfgR7w{ajVzRp9FOn=YS&ms8`>#8KLN;Sb z;*1grm{@!V`+Ix>Wb}a%{~1`+ z?K+APaLc1QX8!Fzu>}WLVBP8it;e7`q{f=a0ny$g#2Qx6k*wet*8(uD$R3Jm);mIrq7r`{?pg(s?Ip{kU}^ zU3oh>p|g1T_NTR|T5dUO?lc?27!X0T`Ym+es@;TwK7tf#ek7rGFil0~-Kd^27_)3Me!}NmiLECt) zoQFX!^5|r{)_|O?m6%Cp$U|)L&d1aWZs?g4?HQXdczC@9IW~t>E2&p@=}@`WhEfFS zPz*CFLM!(jR@nob8h`9kqY2TPFsGnuUkGI$Rw z{MQU}aBB0hzPf-dcP475ZQxA3cg8+;ube2r@sQ(&#1EG zeJMiP#@pIi-4&&`ycg_VrEA?Zp9FZ5-BFq2q_fiO)2o)bD@}Yrr|KUDfRllbnCyo% znE^H5g6mdM3meXia&nvs7ImwEFZ>jeuYs7F5BKdGkJ){88hm==e~m(TCEQe3c<}w% zV5Fj&i{B$|BX9V2-y3K^=L*lxe>!bJ;7mIG+%IX*1@Y zv&Pe+cqOjR!I@p&DAw&G0a{L%5D&*9(dM-F%XuEx9W$@c9(OrL zv%8aa)L{Z6pG#-v3XGwSPq2FNjzf2AGGRPd55)$EVvR;`kKtiXs(YaXNhBAfQXd1pa${=rRRe>C=(vY^BM5#BFTJ zVB7+vp}?x=5!dg88qqk-Dt;&neQP{5p_f_9eew2;F*45R0C4R^ z3Ge*6(YqOf-DtbAd2_d#v-5MTXT;eZOCAeHVXVm^?XL|bGt@UhT{B(VD<@PEZ%#RT zC-3RM2!a8o2^c+u)1X7u?w^*R?Of8)a>FspWoxqYC*(qJ9*-txnyT~;AJuQsIeX0v z+`E>3RUL)OQc?3~c^Oj^qmqu6|NUsO_qXU&`9sa7k0t2)c}J6H&yUD57v@w=&w;Fa zeEEO-Mf|u^82HHIUJts&n62}T;w)pVcU%Qm0kWS!G(mncJ{-%6i+{W3aJ#SY1PULj zSum*5mS!fiGULnCKaqA8G88;?-R~KD2CjsLs+elO<=&Z^PTkk?;RCd0?v8;?B9!3L0|j-zLZ%n#1RG;$Jd=QoRxRX1 zuuB&bQZnV~6y$iG&3{V9Z@~J;cqTSdFLOpubjPqm*nVQ?)kVjBQvZ6C58Dd;cuJb9 z?29DI!}4s;<(dIXP}OeL^hre9lh0M-*JRm4sICa$fMu1*Zt2qG6kgrw@)Yrlg`Ief z0TLVL+?fa3dbb5jP78Bx|{0wpt~EhMj(&8#((a$u||ov_x#IO~xfQ%aa< zjx6NGP4Lby#%Q6~Ti&<36ue}@8s!$s*K1Kovh`MAApJxf`pTo;Ho(B(VR&-zdf08|EpQe@YU zQ+a9Z?`9__a=1jM4BGC$Z#O1<3i%XQ(MDGK=2=o7&lWMK!dwFdRrFjE14sF;T**Id(+8#l`j} z*>4~#f0E;7;z%6q@|-$&pC7KyJUz;IU-R121L+lv<9zKL(V^}aXpD%&`Tjvr6&aGz zHLo)InWAXm(+DQ^8lSX#G?&tn0?V|=tyWCOlh5;7%c2>bOjmbV2mE#m|DaG8BeU_T z!S+4(-3D(9-*J?h)`0nxv&~7k0u4ABoBTwyWLF;EdWZLZ6UcLQUdpjn=^D-U_vGG?>FN0b!4)2p+3H*}x=EBH;CjGHAzFL91m0K`$M9zRm;4*- ze>%zAYeoPzK%GoxS%l=)%PhlnN-?g+bdW`=-qtu5&$~##rZV*yp}3T`L*m5j#^SoU zr=etk3BG#+f%-xw5MfB($6jyC@Ks1D0;#YL;o0cclTiYQ6~|uMg1YNRdAn8d@;#N< zw;H`nNwHs_){g8MUn)5bNAG_U+-B%owKd)sW-vI@YvUpd4wuX*IdJk^p=r>FMWL67G4_p8(nwcHhwdsomy3X(<2ZY)J!8tz*9H zbu{#NngSEci~9!)o{$yVI))+{xlW_v`y|h@5h#kSc4uD=##y!ybswIPuE1^}f)+Un z)Jgqb^svVk@%MH~K+_I`_2pP*!EV;sw-KoaM?QkPl3mn`r;r$7Y=_oC`-YGE5Bw-FmsYcxQ(gv1BF41%Dz(C2xw$7nEhYf2vG)cd$s)c zsWiokJVnWyE1?5QwxxdkQWm-@slf1pW-X!qnnFSAY_>4Cp-x$Cegq{;e*7HQcAaOD(+(kFX3a01r&&J4hg3D^M)pgydZ@x`h5q`x3^Jn-Mz{tM7we= zpoxb1lO+v>q?T`bi@D@=E?0>AiB+p&gZy5_8#Wwk5H-H6x;GyARpiYnz; zqfQpKs0q5*=F!l~rJbu+^pH)p_b%$1lmGK)PPa8cnz({PiT&Zmqz1=pf@wC+-g7 zw=c%}sM<-Mr)WR@){GvP3^;yFY}}J&f7m3NTP^w_LGjlMky~o2;NEmbfq>?nuIh2` z>ECtcaRYML0f>##72HcvDehPnmuSckgH&b(&PZoPh_spJR0(pFbCM(&b(@u$HS8?o zAzS}^=N3uF+mIa=hu~W8AU%oP?XMD@MI4oUP%}r%En1nx7`wUg@2rWh^w%dGO+^k> zQ^p)#1sDbGY?HOeK#`QuwT|y#NXn4KcjMjPLJb}1m~HYt>u}oLWe8e-#`~Sz->Boh z=bF)mD(#?iWHCz zK!+7s9$hq}cKHr=DQZ|F2_sgqXZ7_)YTn?tKF2OfW7?j#Ym3y34Y+qQPOQU5;|XB^ z6V%el8->TQnoAPr^iBuE$uuWfu1ER^bGe~d6zi9s+vWcTdOHl=kfc-BV7yn);(4v> z%WU~QP&6&Ab$TBh3$m#qLVjJZrxxT7NU= zdSvz!HER2B6!E_;((>y=u10_B6rxjP_}&`QDv*3_yy|3cQGT3YCJY4W6A$owprzt~ zK)sfJmh+5+&7jTn!32LavhjSXR0*?~znKa{uea$u&wX*oAl zVOvFtgqRfmKCftP0QB|C5S9N`-1AQHyJxBRQ>m>%Q09H&oxFeu8iU%D`gi|Y>;In6 zP$S+ssWtC*h0+!;UzH*@%8p-AELIgX*$EqCBqaY>;w+a1cAhirM=f*sc`utBAF>M- z@Ke_)Qveyd1t|&HBgZiKM7q2JV7n^etKv`B%tt-nIqJkrtsdD?Z#~W!l2xtuI~v*{ z`rG&n3SK^LUZvrN$ZgeGi~I~Z(_WEk8z$Ho!dcTewyfj0O%DuiYt4TJcWG;)g_2U6 zmM-9*Hit}+mkr4rJDx@}DvzgU^tzj0SPUkatDA2(O_Z2fHm){tuqVZ=2Hw@!mL1-F z0v7bVRu=u4BQmp=N!ugnx1keHa%GyP8_VDAQ0nl{*UDtltbr>-p*?l;&qdVeyx;%J zXipMp&EYkRQS09DF<+;WZ+@yQk;Vqv@5P)_tc>n1|1x8gJIPnV^F|kzP*y-OP87)L zW2$N3eeIxWEsBsYJ132S__B>iLQm?eremrvBJe&9-G;$yY^SfmX34qy0;mu(FOe^W z%&CJaLEGb@!I(0mTqwK5l||e? zLc~18zu9@(;B!;|E*Ch3^W@$3x6?*zGnP2S{cE&cP&=bm#R35HvIrLlxKMjSZbUkB zc&#gIUQLBlUUVndJU=a7RFjs1fxG4NSV)D4VU3K;s1lVx#$L-Wzn$39E&;;CbL^u` z{A(KTXum#n&ScTwYXusoV`kocdLv4KW5ND^sB}6T{kC6Lo%h~D=9IWbCS94OynT6B zClgSA^cwxUExT;T3nAzcV04*&gdwP)*s12cSy(9Vr}_BsofjwU7O5wl^hTOnjIu9} zM4^vuPW6R?etnHenDbvm$CXZ;zw08|;T8z)_G_bx+LOHa+^oc%3#skplkfAX{ACSg zz88ous<|GdKC8KdKyld^L7Tf=PBF2sg3zIJMKb=`2hPHnGA7Vj->LZg2mPtV;FlMF zj40fqp2vhzJB*koCA`IYByj&b4Api6uB_zixc}Y3-h$=IBxmL;{NAnTsyi$wwVugl zDR>(O=xhBgx>Kf7Ylh*rnMbK^p)en^Cxqvkm5a(4>?qx~y1I<1IvRt`H{l!oR6S`e zvgg&j3onjYx!?Oqdz$Gn{_s})-P1T3<}w5IH$9r}gKQCkExT_(4kE&{LHki~*P2`! z3Ut6UKGn3z=hW8})f&v38PGt=*{wU6ebk|^TX3S3o>_ao>>U2k#6D`sxhs?j4L9MH z_Qq!BFH?iwIlXrD!4|#0Fn=WsC;)7?-e!qpz(32?siH6x%?7p}(mnSEy!CqLCGPZx zsj$GydLpP2fVedl?lKvO?G_JSn%+IV1zm z@GT8qVDd&oc=U?m@QtnWYiBzaq}y7NxWBOJ>$|^ON9!NmQYrh<=~wjHrK{j$^s52e zjJJFe)U%Z}&JUlboC#95n`L276^*l+%ePB*?DA*`CwcM#Sl6%cvsrDwb<4s1P?gvh zbxWtQI4C6M2A<)U`v=aHO-gpRt)i%QE6~&;HQhu-Z)=pAu(Xud1bG1y-gVJA{$?f< z^nm|az*9_4EKO*%kJ8Uq>T*Xn3d!#sPLpR@xc-6R?&*8#DPFaRa~Zi`Gr66wHoP|- z11?G|S|ioatec!P=zvW}oMt%WnG4lk(_Zn$N}{C3L>$#m_~eI-lhf>#nm8x*sL$^& z?JL8KJiUfQ)5y5jc~AE#+dF#Cy7z%ben#GBXS_fz zGTjC-1Utmaai0P|pVaiSy9DcaPS!}C2~*7QD)))qJvU=auQH;5#>AGW)lN{4Xug-boSXji}B%rp0{hyzicakaitq?bJPr?=Kmz za|Kk`mqh~D?yvJv_{e@;xWcoMD=UJHmk3$2J2|-vIO3ZYsOf?*ILntS{i#P%n^^6; zB-jfeFnjkNC>Ue95jG>dF4U(!%VZvSEDqZAq2LX#V4?0efy z`LcIp)K&$;^?=@911x{nuZuX4I>5-GiDs;4tz@n7DVxdsX+8D?{N)66V=E1Glyk=l zzNb!zd~{@NnJ2&)~97KTeG#K@T?< zep4eFVVOvJ);dq)wUvH98%{Z3zORN+4{V-(b#eNEiNU3X1XE#F;Wb6lYl{cT4}gcg zty115&j8k&hMN`ch+YmFw3KyjZ%!F|=d|UdHFoPLj^k!9YiU<;?}>LSp$9b1(Y%Ti z^k)xwvGeA4u(jQCO8sj@bb?KCfrsCD+lIKixVdYy>WIQ=p6ljqO0ZmLl4jIBo^G|u zZ`cXgwbsh0*yqAudtlwF+PjT5C-$=y-7_neas)iH6kkiliEX*m`N~xiXI|frpp{8+In?Rm?pg3*FQT8O) zmIG5B54%n4_t~83tD#|yq}0LFfq#oEmyf&SYF*&?g-2&5j ze^*_9d*+T?;`DFEjojO&RN80Rf0l9a(7f~)O?T^Vz5=5Bmep8Y*yP`&ey_?n_#jx? z>2zmFFyP3*((8g$)`X~=ZILyjY=}4MS1s*jL4619v~zpT|L@j4&w@)AGigs8XCXma zPcu@lDXu{<*K5Wo%QSx{((MTl)hd5$U8=wkHM0#W=aqFE&J<6yhN(EhY1Yg9gKTUy zG7e9NLaZ?Cv<3hn~HdbN<8(xIp4^6*Y!u4ce!^8dEe>$Tq0jq>L=C2 zGw@N6?z^~CR1e~ARy>1=yR+vCwr1Beo8vc0WX$ol+g?h;26o*2M)1AB?azi9rk>cy zPo%Pz7G(-gx>WoiZ_xhhfL7Osz*9!T!z`tXv2z08rYmD-ZR8o_O_66Z>&J8TCy;$j z80hqi!v~z4xq}aKGK)Y%XXswe5t9B#Kk;)teYvNvr@$h$VH;sXuZv3?H67$D zR7U+b=yo;}zRWM?a579-oALUN@6}7|tN>0?;Kgn{LW^jw|LY!j4#IQRix@I+cY{B& zO>oUGdFtD8+=9$(!RLzY2IQw+S|bC>bJ&u9UsL3O6=DTiW=-c?vG=aI47NQwARE8ip!$Ra2{DS*?Ns5Fr7;qclDLj4g-m%Rb*5EpZuK#m*U+{ z#GE?O{z(o1I5rSGq)c0j?L+upkQ6=Vjh83WhS|Nvf3{_f8c|bQn}1}cXrY=7i5=2S z_({1)O+LkRiX;x0Rjs>H*>!r2Ewd}c1tRl0ATR7@t~VINmO`y{l7m}H; z>bCw+TE^zi{vh)gF!(s5f+Bg5`kheCbt&9}iDy>=KjL0%@|HtuYy;=E@f8CuxhWGV zL?ram{bo#F-@ecbqQfgv#z@1d2YFIzP#LgQHst4A7&xLDh6981Ss^qD2&^FIGx#i zFTDj?P67H&#vtMKx}0(OF!Nu9!>@S*33ekj?))n=eb|55^@8PMWlz-$b6x4}99gKT zwAx`NQq#@PA;l?iS)eJ{n5oZ+T@?hu^=|j+QGuokEx2;|r@v|TD46IsI=s*!B8K3I znf;=0#Kcy_Y)VAz%m)u0|Mt>y5@Ua_KAM!h@b@*;)c<4u_rVy-o=N&X zlvKCD>U6ECdrH9h5>}!$1392AKjyRyY|8xGSCG&Y{9@~S*s8vwoQ99cm?!|j*K}(> zsM(CTaG;J1YPQ`laoVf{sRb}!o45Sx^-=~*Ox)P}=9l}N#ngOMl8S*-ZelOe)x0?f zx4!W*$RlIC&y1>Mnqn)zOfb05RZI9}R2UZlkN-o4s!D$@{>`T|35Sx zjR1=$JoRMaOpIPf6P_w%T3aJ!{4ChOj7RUk)wAJY5`~0E{1kcNcNDRA2v$jq@w`-b zuN`*|=|Nu{d##SCI_L*PQLHP$=%ysEo^?g|A9x9ieY~5`PvL$Ch_EWFlM4UV_EO+_ z=B!VxKW=MwIr)Cjql^aCh{k55wkdof#${Qh#5)ljIL>aT&RC-`{I*k!l*QlURs z_W9eVDvKn?U>aG6Vvn1nE4dfNYDGea(j8MK+ZGfGB7^F24w1*F zT1hT0r3c_DKH+O;jclclys7oi1CMguc| z_P+giozK&qH_XBbPd|X&NM(LN^T1g4k1H z5fCST<}kx>B)s-O9_T;}>nrD6g9xtfR+4NgC}!YdE*`^h`sYrD@n*hizv`f<*AR55 zw!|>pkB1->vbLRoA7Dw_&kBB$v4{EuX-r4#^V^cI0}4_CD}0Z5M=~?!QTv|*R~IQU z##gS}df8EXT|hQ77m;bp#Df>i;-TvsHy_{ZV6^iJx*4$EVVP3gVOuow5r`*T`3Y2UO?-MYwWhyiOg{ z4M2W;424xC6l<_Ir&Nlx82U)G+{MddY+urt!P7ct{x|CCM5$rdCbWszxd|HB zy1?Xaqjo=pr@_R<8 zQap(Ejlt(DJIZ+QoBtlEZ#^4dumhuP9_Z!?Y{n1K^ZqlXp3*k&)n$Shuuf%>RQAoBRc>2s4iK&0$=B6F3{A<=u z0I5%o{MD04%S;B^%#5%-+IIp2GSc^yX%YudkCaTCBd048IJo#y)Qoz}?sDpiP7h=@ z`rU(B@k-0-iY8XZwiJ1lB@Ldwcivs4xp%2ZV=n{})UMQ?-zgVBPQ+sPe zI)7b(P}9e?Ls9BW&iNh7*@R_qNYWtqHhapvR2-L@iJi>Zy&i?SbF2_~9posV$mzuw zb;aR-jE;WZ0*<@G$Sc(Y{Uv+dWU2Mer@M4?*h({o3ODl%mZ6H+pUr>y8T_Wz50o3f zj(?3Rt!@+csJj=p^vn>tr5H($N@JVq?L zfLZ(!`WHpeu04RBwB2>P$;AiYD{$7HO9PEPx#a1DI(KTL>$2Ue`KM`4m2P=FgLejG z1l@o>%8*EeV?_#!wbfI|(Kp=<)MTbEYoUHl+a9a5MVoAXrz|yRrO-{S;I7bPsd#BE znWV0$*pE^ix;HUcL3@@m?__6A-o=2f^ZLW;l*Xr+&D9S(nCYe4b(E_qdIO8-TuHd7K4M#@#d!SR-4rGxG!W)G>p;by;g^^S^1^;Q6Tp!|y%l=DPGD@SR!ca!6lzpR zxg&4pc-X*-ynJll7X4r7bpsA_JAE6ls>1(43sD_FAMicETWY%JcN?Bk9f-Q+I+>H9 z1Z){LemaOgGRsT)Z6A~IT|{pCAH^wfq8l@Ab~&N9ZdazI0(f zp`8N7>1OyWc*`)IRCnM^VR{5vUkLJC?$DNC?7E_;y|$~?`YGvEpsz&FDrH*+COV|s zvxK=NYOQhr=CI^8Z@NDKX*~LOynI|Rs?iwKuDluCnRHq`Od-*N(pan`H3X%j##P=6 z?{Lek*~FR%ak0B$?`p~nKq68K86T40rWZAJz)F`--K0N9iIy5Ia4fc%S*>K6H0@q< z<12b@|LD($XV}BOeC=D6n6cGW#VoIemX+57r4H!!6=TE{sU{rs)hJr$sRJgF7I#hV z-%EHPDQBaX-gO~f;r3t8W-W5V7)ayAE{#@a6nHi8^1Z9)+e(QDhrzSX>8s?Ni(rh+ zpxi|HCrU0}oNe^lRJ@Dn3OULZ`64O@>;dQO;sLC7_!tHm-O0X%A}&X4>SMX;(E z4}px<52nO7(ZX^PduyRQ(&t&$uOM^v%pe-(=R2V{{oeO~>~STO*<896`%-Wqwuk%# zONB}U7{jG9ba`>d7#_GQ_yeB%ErnJlolNM!RogvX?WQOm-6i)q)dXInG277U<7>im zbNwn4^7`xwLD#3*6nfUa#twg-em&^i`)^)-HLrU*R!)&W5u%<)LrDX+ z)bPw8C>(V;egDlX9s4ZalVePT|KB=|89wN2fCx%s-h+(w(YWW35pw5u2NT_10=oDu z(8x#9C=_l;>ZtyR!)1Z3WCemflH-i@Hko+R8?PzK`X4_Y8_F5eDwI>yD1SU`sMUSr zQIniB&SygoTMC{S&%cppopoBN39riHs9l`Rcl4cFNtr1XW}}PDB8=Cr4OpmlB(d*C`!-$sGh0M>=Xny=i^h#d-9 z8hFE^5MLci83d zk#2MG!y`{*WvL&xI91!EOF7?2TOWvkSuF%~Lls(>H*3crH7$3t;Q7RxOa#mKKX3eGU4@2~{iOLyxI=ZsRoA zw#IBLnRt?3_}`^!5^zP82+2ROiV)U}ymuxD5Z%^m=IpcW9oKqjb66U)SCAkVxi{AS zGI3@8(Na11jPlV^YS*y8(H&5QrLjv;3(d-5O&uA_=0;woBw2BqgB?RPc=9rzq61pd zpX-+)xV=k5xqk@e*##B^U%lsiCRXsXC$fZ=WiIsKBbJK>fcw<{Gp`4Av(DM?QET`-=1d z5Rjqj{{*ro<*n;qW%9exIy2-VIq7)XbAG^vEJ@MH1iE~Y_^07YHtco5qvFq1ZA>uL ztlE`%Rc@!+S6M!P<=W$Gp14W3=RWn6asSO!Dh z#Agp-2BM*3yhamDF#*GABQ3dBYBJI`>`wDoHc3^Cv{o(nlxVD_Gd+P$x0*C&Px!1J z{B77ciax|k72p%P3S})o-!&H{kzrs(tKJoCD~}NuJGF#C;A83pPBG2L)JZqmzTW=W ze9|l0jp|F9w%It9_8yC1f%TA|#k=zyW4G~#4|uWOfqi*@d4h*udBo2(6k@85B|r^v z&hoekuAVJKYA(|I$m&8@?{rGy#t|Y~ozR;rk-2FswBXWy(I*!$l~9Iph@uvy_oB=6 zqZa&}KVEnT2b?Q8eT@@tnN4fVLb~57;|q3S=LNr?gVp9U)m~A}tZ=X$;MFrM+W!`m zPqfWK-35X5Mx8@i!pH>+5TP-mg?@zGce>!@$Fd}sKM#B!A+ANVrx5>MQQ%A6N%bXx zI7E?g;u<`@k6bq}9mn@M?0de)opn9lChXd?T-%4I@5l2Ddjq!%6XtSNo1XtDk{8NX zTiSfGS^v(Q=Mfxtr@$0z2$~@N66>>c#AogHJ!d;Vah4ZzhMPV#9)?U!uBz*8jF@WP zSJ0#l=~{49^c;=NRyxmkYR~4@;c%xqLH*3UGHBFsS5EAjNEUa`umqT?IvjRmc^dx6 z$+)yLNw8p!Bodd^{n9x)eM^~ayexH;JZ{?mZCfTmb)?t-%aMOz+fqg5_)Ziri{6pl z6O;FnNh&k-t8dpjDMQv*kV`lSG+x2Ivyb7$cFBvpnJvG6Tn*WLo$nRSD{@37T<%oW zQf}Y&V?E!aPgUTM>?z1N!)pn>TI*L7 zsj972p3yW&eAKwpArqa!*Vp<|a3gY+p9e0pTIZ|;n3#W^1(k=B)2oiJQ_wF=sx z0FPLP4&HnIKI06VBxGuL#??|O?KP>%CHG#I#Em*+E4F+9ch$32@U(g@BAd|kX(ZgX?yiA^jT&jwK${G^y`~m zkUyGVQwK}KH%0wceX)jjKt9+GYQBu);*t>bB#vZz86T?r##t@M5gm^ec{g9%GrlId zJPE1ZMNbw^Pq-|ir+x9>eIUlmKgLrvny#pNaYZzpRIrq%7!pI=|yf;V4H*hUJqKDB(i_>_uDAZjRBg9hM zSprYRb{P@{4q=+|{QLcWgLD&;mBHR};CO(9w8@X)y=k4lL)_Bmp z@v?!U*Q#YLq?2O=s6RW5vn~rul5Z~B!Jg24n$_)wW`*LiM8G|Qd(`he_%&i+>j6Iy zH%7?&qPc3BW9Yrm#y!=FPWOk05vzP%!rqbSBq$IGfU(>Nhd_RR)} zu8Kh!*tn&`!cUu-1mw4G7hXrEsxvj^qY=SC;0<8V>cvKcEl8`!#`H#V;-rnDY|T;8 z`L(?zc#A(~^1@5mMog@PUklI3<>CTXHzC%Ak#GTxvv`tz+_EIAqX}CTe{9M2;)xlh zp{l-q<@n#nT1iNOV1rM^M_%kJGmfdD1}2C<%hV$`b_&EcQv(4(WAJ&9^N~hpIQ(izZ*E16eAsJ`!#k4sY1NrKKHXWGTpX8$2nsz(bxB*(I!?rJj*YPGQ=>JIf*~{X({9h;slSGtBt@wL3Q@iEbrQ0^Xmy3IRIPsY z5eo2*0z0~bbjCC+G2HAX^Joakbuu5)yz5mWs~2F~N(Plz3Jf;L&+8o()A%Gilmw@u zNt<874ORG`SyPPxX)V~23!f}kn^1-mjx5)cq?{%`?dg76v22cBZ^byK5h|QJ`%QQO zi)hGMf!KBvL0bU^cs$kt(MegVIGBK&2E>#vvE8b>Dp6E`!!3(TE-aV?qIOY7q`ei|p*T%tUGE0T>iWxmuL^9ZQ)9fPaY*NxHKRZB!DlyttgSci2^yH^eV zB-+3>6V7;_Ju!pqkb`9uP7~>DO;U~YZvU}-IGfIe2x?RJYxH?>|IHs&{Oxf0DGEcKf7}Tta@}7d>Wy zw&&-S1|tB|BTzWZAH0i6Koa7Ba1 zG2)nWd3QAXKZ-I?{SB9dLya<^1V#z{oH-7nbY%h<~NSyURJV` zGx;)kM-R8Dh_x;;+cfh0f#b4*o)y^S!7ZNuv3jnx4=h(9*Kji^-PO(M8KxVN)v{Wb zbuFi+s0pNzvBX0v_a1v^VKJANfa}uov>~&C6y09xAO0(uuG1so17(q?7yakjT1Pk+ zQVWJQIG`;Ks5;bdZKKEwUSe~zLk~@0C;w87t&^~`%V}n$LUP&i%*d+pQ%7o@&pJ)A zxNH9oht|NAm9ef_vxdf$=a{La)rYNMclMRoA&@Qmlrq^3Bq8t%9JjXQGVY*i8^2za zKq~(m12f|d65L|wlDrz*g}$-HPQ`C})x^-y&LwVuL@u(KB=BpX(rSzbzS}4+O9Q^A z+$B_#=XnX;b)l+?`D6}7x?06uGg5W4O2B^akOI>rcaKO;ADm7`VoaN*@Q#$_V>N4rr?zDF{AEE%SG+pjErcEz-7<&TC zt`7Dy54RSm>l_dLM+OiO=p1fCl0Q;hpbiI7%lVA&yDQsajpxRE@hBT0C~&K9Lsq-x z2N!di=}{K(S*I+OPb`-Fr?vG2XSB;1Khns0o{=2rCPAt~x(lpPZ+_eN_PH zth*lj;x}!{tLmAXM(AfPY~vNL*J!$M=fJ_Jxkrj*&;oRty0Ia-lqpOa23l)$U}4pa zxWik*8L~|u@a|P<{~^qST{bP(hP3X#qOqxh2@I|>a^@iPJx z&S^EL{`v`nRmUe1o&4J$aeQEd9opmLj}*Uj3cheaMqda}yttJ#)bjysq%c4cENxS4YBP@~XjP@HUXumR1M6ELtJ^bzJ{IgshwRUz@XNQ2y%SZkMt$*F(OEy_ zW$`YM{@%ST(QK-4vP12)9-O3I=_S|Wddf`6w(03pG!evQqc!kTjefT$D-~{QL{f+X z4ci-(g{>zAF?6hthJ!xK=>U@Z66XG9Hg^X`p)LXZA>wAgVzz{pIlrD}EV7#(6~t8@ z_#Mx(EUyVR^Ef7U|HAY)DiQpn9D?dh?StYBjy9@Y=0A^q#brfcWv=<;&w! zwA^Bl@$!BgASPXY%YesIf0|?LC8oSZ?7@=I97oe-@rJi+Blkv({CPoxq&cy`NywI% z2K)KOk{KaPa)u{*dNjmdDAbj^1?0P@QoKXoNWNibR}e90JL?7FiQQ)z(^~L-5_01+ zRo3I)o5hI`+=SuWO@oR}rcb+{M%Fw7HwC9AI?steIg+G7y9yFoi#!947t3tIPSQb( z>}d$x8sG(^Ji+6*fA-s~_IUu8ZICUs*E_k&Kx5CN$tq;2FFa4$Yt4`YbachM^CC&> zwYmD-J!#Y&0r9;fY1`o#!r!G0t_iB{vGdUT(9=GTf!3%Pj9gq?=sX;2uw5O<7ovCz zqzzGLvD97dkr8s9hs$$a1srBG$Y|cYx6|@brtN(Hod(y*6Rw7FC04Hj(B3QHDW=H- z#t}V7D!Vk|f(7<4O3>24$Y4=z7c}^8-!Jo@vXHHgy!PO70p8LBl zQP`;lcy}uLBX`@&Yk)QN`jNP5QWAM~EpM~P{J}VapW%(C93?t%aq!>5*7LRE!pgV3 z?)Q`5OYYnr9khM2UDcKzm0_zrXW_`yUiu=t{nwKB(di^y!2YDUP z6v0YP!|rnVs6NQGCVk9$Si50DZvVtI zf$y!`0|oX>->lwjf=38OJE=h&!gP&>zSyV+?G-)%O6#l5iRP1VpR=!lnX<$(^m&ZvyeADVZ;Gj@T& z=E`8ipBdUQqkb%<)8<==`^=C&Y>7NyN~6O0Bvlt=E*<|5duJRv;G9*Abq=HD*(^e4 zO{_^i?dg$7DjW9SNvoTcQ0@!3d}4GtzHcK>W_XTmKUTO}WcA$Upp6C)Bu$Oekj)+| z7YiSjji0G~JFHmOuKY&!J>N)CufqQ^_2%(Ve}DWa`IL&N>{+Ig$Tldl&X7v>L?O$R zB?&2vb(SRAO^8WiLPC~7_GKpP*s||r8~Z*ChS~0XfA>D_z5kxK@jl+pywB@Ax0l^O zIQ;9&tdIKAUoXizh{%nW8cQd;j8U!IM(^e>q~yqIBmU^#zKL_f-h=IXNzt)wz0Qx9 z6e^N_^uHe7`vQ!@5bDUzijQWf$5>^t-<14@{-2e|8i*IjDgbU|xK(jHFYb>c^Nr!| zINYk-kHkiLSbNJ?|WnFNC1MnBO5U z-Gk^@KZ^I^`DVU0Ww7XF$S7hi)GR^DZh7wthxU7A?k)G2{-u7G4r2c!?EY;DbWnnqs=N?IbT`Km2#Jdl@ z>FRIi?M=9KINr%`#=3v_v>0kq*3C5)pY>+qjx=z0c&%)71kxr|Uoy~@yHNA;(HRWS z*~B2&+C@=F8@ki@72n(M?0K!`{-NXD6wM38w{VA|h#ES!)tex*jH2Zx`AdwdVgBnG zBIa^fNO;dB0Ys6h@=-vYFBbD*6_j${1xj>wB5}VJt*!=BF&GByS3JFwu|rvJ`Uo=E zs<|LxyOhJiE*RxEaBp1C1g+Ki)x-@~rc`RY*!>bzvlWeL5(K@pRzPU=x_eiej`7P} z4VQ1tEyn$aSvsRK+*(iwrHNtpbzZ@C#XfF=zE@sBW4fhhj;+`6Z`jJ{59sLaF>R)o zV>4y}ju4QfRqfpxwG4506M;azEFLk6%r+u}wieM4glcH;71zFxJ-bm?2FutUoIxG3 zm17acN&TEjdb}=|>|9wDO-7}i+x-T{P3m%O* z7Ey5@q&=*2ww#$Hq2-NUxuFaZkEoAXeL4D@%Xfct)>_QFK_m&*qShB@Jdcm@5~M}T zRx0*Yk8LzoQ+V+yy)(@xwe4Ookdh=%HS)hQ%J^0K%K0n@$a0nV@4VZ7eQNo8li<{p zlkeKJB}R0RPqMS3qbaxY@+&FkVd>YS?tZ(;b>QZoq-%j@H<#E*7m^6OC z{bta>vKpDc|Ko<_oC;`_2JlLDJea*JM{-@G=Qa>8;y1reWKb`Q(AQjx?&ZLYQ%Y)> z6RXKF&*|2fNrrRS`c)h(KXA(m?e`F-G*%GL+d92NNkC_khDWC$FVg}%d}Y&&_ZzL?rwOq=5J?Zwv* znZAn2=v#twbz(_sOp%yg6^1?Rq>{u5ZRiaAu;Ki}oJYJIi9Rxp*Wa6Ujj~zp5*giG z?rxEKW_%9%pN?)_3d*{ceqBUrFY%>MPLk1%@pYpSNjEnmKB@Kh<;s>HR6zUR4FRn9 zvi%G7XT3bB2|pSRimoBbESQ9Ah&A7}mEB`3$-$60>}`~0wwGsYaBbQKk@^t?;Bf<8K>j9I-at95ib2$c-Io{TbOF~GInoHzBn_kr0^0*1?#mxs^=PLgijN~8+(%-w< zLSW<9gmg?wV)nY`t7>)_*z1V+ftL?s{t{K;zSvjAnhN?axwnMkD*nwi?a7C(_D^+`liet61f8WL3B%(5 z+?t2)_PbY~{KIA+cyP1~aF>qzsK4M$uuAHI8|YXG`>6xR@%)SspgAa8tD*;PRQuQS?q);QkHgi|24yrXbk%u0!y1Rt?@x(LD3Z z+c6R-LN^nx5PkN#ZVkjtG+lYraqH-`UYV-HCAr1W_wm6$ckzXkF)Wm}Gg+wy^Vmos zgR7EBk1abH1sR^<`wT9zoVfmipR}Khy5fuA=dz^5>#ZgxKy-T; zXQd!ZW(=hz{OL+drE>Pju3RkUxHf?#V7zI@Bw{8UqPAI(>lwJ2@Fb}}%-fw}Uc;|O z;53GnbhtekxIZe@@K<0W@%Yo`pDMo=N{ieL#5Hrft%!w9L&-zq;2njW-jUa;s^bxg zb9W+&WKIHp<&I?$&x0otCd@HD;FYimAUUI!CXM-e ztJLCR?wBB??8D#7!**OTK&V3T!Wj&aE9v^Lh3F!+)BI9%*KdHl%!(jE%-=z4$ca$7 zqX3gMrWl5%abLUo?w-1|=fTWvX%(D!SRPkFE!A4*`TJj=gI$bQF12}2I`5(U9}w22_5_Zi-w92B$EK}As? zVg>|*k1Ru@2@!z;RSq$f4$Q>)@al&hZK}8`YcKiRtCJV$mY6Z-Bh2I8J+mWp210Ab z=`U#2x984O+eqnt%`yflQ|EL0y{Sv;!b!F>ZuZM&8QMoa+NZxvpJer4`M_!^?z;2X zn-nDdBKMy4*3`tR9o}E*b2_Yo@!tFGN#M+9K}ZzPLb`pZtwQSbXn~SDC5Yo*863{ExeNn)ENP=YTlta{kd~b4(L*wZ3VoD*oAj8^Pzpt z)@H7lCoUqtrd2Q!Sh8SuP+eH};U+U2bQG=5@(r$$9Bu*0KA}5q=1d#! zMI7YYuxVGpsR!4d34akAGM=~{J#i+R%sBKH3w)DBF@cR;b9mU%*nv80^U}PQ0tpmY zwXLY-mcVCi$^COXC&CGSbH(VSJii7%Gff2C8h@$?$|M!w29UkH+SSP|Zpu%(zzBjW zZemd?{llH_^?^7sBHXCDlLjPh^+FRv@>>HMC~u=N6TiC|1$45gELav4(azFHQ)Z|@ zcP}QV>2&5905g)yi{#HOT0rSnj6MWcywzo_-sT4ch8bnhmKc_t*n4ev>>Ut`$=01{ zHuls9nyXxFae&Dw1JG`Q+dTA7@A~>B{&T;0ReUVC6LLE4N?CepC}rL+4i*Cb`%ZZs z!)p<^`Z$qylKq;miO|eM^s3C5W;nqmkyrpEa~k3Ab}0vFSF>bGQMHieh}9o68cczRqRD}jJ;B?YQ%Jv+{jm&?cX04AT>s6vfOty zg)McMi!*Lj!^GvgH>BY~H`a2(d7f9Ka zWTK&LYIaV6uei37OGr@AOnGNfrsJZ+s&1`C^v1XtYUa^>vDMZv{Vj^(bZJW)ubQia zPe(~OXM`A4Z>Y4)%~zC`qT-n&#LH7dtP3Pu6a|k3ic83H!wmo3#u=>%k9+v0?ogl- zsUkfdr%bB8L=*%_r1;){*O{q8OYXBIxJNUQ+UA zmx!S)1aO4a*z^6ol^%KgKkT7~`~6BQ+B`kj3hmMP{zHEjj{t6{hIg(!NNKec@ItQ8 zD5_avrpd44Pv0p*3L+QkuK+i%8Js#PXXi41Bk@W=A=y=2 zWOu;K$K_#3*}dx-^C|?RZN?w1HZNoCTD`#Wcg>EURb+4*Q%}_54Q?xv8obEnpZ#mO z9o4203uC$oA>4jrb!J@esvBtY2H@lVWuMct)n6ZIc5tzs)2(p`B=;L^$E~ZnE5SZ| zLnmt95?MexlquFsYVtbrOX%z;8f~jw&vt1i&Do$E!?5GE#bjb_(M8O+AjQuSacfNV zHsEASz09H`mQ*I^m9PJ=_W@`>f%UT~#F^3@B( zy(6G?j`eg-Ojbrn1h1sBI?`vz0{$yJz@0ySsS;(s^oHEtSv#4m>1p)z_uFxqz~}tE zn=eRR-3mnki`{!RPiuO7rxpv!4};Nhgg5npqb!%J=Aq?8&Ho-<@qy+k#KTy*2!dDy%6=ebM_u{X0YZ8$tBamG*D3X1BKd7$?JSeJR_cY0ldQ_C_w}H-(-m zx92cI?GN+F9!og_dUYPpM$#uNJfpy8D`!HmYVABTs#-43P93q^WruC-IQ-T>_2Ltt zL{*b@p=`ZLT^4qpu8w&4XNSA5DKvcY<3qsCVnM9$=0Rd{#oa>KXs zX?0YB>G28Os27*3fxe7PZ zq}!6qM1J1_6tj>S z^V@>!om)oG67JIu+xLOy%G2L;U$d$squXI@!zZ3?;ir6iv)y{W1?iz*zi>?nwLSGh zbeozAE2+3Hg?}8B8*Sku%MfxBpI%4R?C%+sC5kL+&>pyU0rEX27htFxZu@j(x?OeI20+bP#C7j)?sN88j;0Q9 z(MSetx8vpvoZR25NY!|!C-=tmmRcLCgYzCp7OJ((Ij+Scq1j&i(^rf26?BPbX@*3S zYt#2$&qxWK5+B;E4Se3H&vG;IHhmA{Y0l>4O|CECZhDZbu`ayq0m|8K;{M8{_}G!M z$DV1$#?O0X6GOBsX4Wehb+{48s1~F-g=EzIBocBkzAm@Pd{@6cR`WZEl!M0Y7vO1K zNYTt{CU&oDco*yv=Mkhyrp4jyn_UyR<(}UI~zgMeWrpxi*l) z0p3ZTQ^n;sN>}dvd#yv{8zd%^x`K8=h8G4piw*?g_^7D_n=HncYFw%mA!b05GxG`~7t>OGBQGKEGNy&r3JF#q+QxP1Ld zt;)Ooy94gkCv@jU&KI9p@erMyW-<a)Rh3Mi;$nUi#6fe5Uchb{vt4|~b=iiZceJ%D8Dtt!`14S` zMG#uE(OK&(ZqDsw#0hy+#kZXkYds<^CjnQuZ~9Tke<)KC4Y%dwOm?h(DvwUaO#yLt z9ULcSu`R~&JhI%{!H1i$RaEVUSOl1yH9&+w34gK!AU$32^FHIr&^KnJZ52{W1Z$)& zy-NY7$RrFhb6E!h#zL1!&xvk0>h_8ZdmmqkH97t|09XJ=W%|ay!tMiR1L&T_Dvu_u%!TECQ zW9`Yu_T9Myw<5NL=@%;pryqS{VSRcKo(=pE+Ksom-WQBZl?xf_H36^t!pG|=g z_RGPZ_Zr(zX^~g{L=|60xW_=|Ka(CC+@~gVJUM9>XyN%S;2X=$)?brOZaG0zsS+}~ zr!H{FVIC{tL;~Y<1fQa7ygP1ejVkwqLA5%pgm?g|Ix4ieg8PlnBrl~QXv@Zs;{k)_ zyUV=bqu*?ws=jHLCfWUTW&d;f<--YcW~0ZIKG;qe0Jf5!i@~|EWTjv3K1xaYr7VpFF!wn@->aCd&nmds(v6+YPbiFa00%4C`XXO` zvia}je#(=#m}7D-^CjZ>-26BH3?ELXi{HbpMs(Ks4W*-bYWCW{56nS^xP&bZ&zK=zv;1bRhyf?Kqh^Q|#-qJ*I zJQ-Tt$r_6uS);I7k4OciQ`zK}8$cS@R+*eQ`ldPT zs0#uTd6RBXed7DccM$eZRZ+mfa<;yg)HW}+bF))G#yoI(ue&1&Iq7(^Z?-_`OmM}q z3sfwrV-tQ=QxM0>n#C<~#jdxEE+mFF=jKIElCQ;p`kc80qa~hpMsc-*870|&SHywx zfi(Tni1*p?02zjdx?h%*MEXXgLhM;q3(Ox4ObUBU&pJh4Iy$|pg-<^iR(Ox5VkUyA zp6wiSovc@!rZ*A?CsB}%uG$6J_R?f-WU)Tbwj<0yFb?yIiB=D~MeaL*2wAsJCWGwA@?TO1=r)F+s zz{)QBt_5FzXc#VJ=40V$^li;k!udQ3^86L!n)FZdjsqpR4KT#xR1AgDG>Y~M;oGYu#A> zFtC24ltpQQI(mC4Gl@NL3}M{qg;OZi52#dJT---bz))1N33no-aP1RpCIP+MU~QPLa0K5 z!dgC81aBTyW>Z{J97^kWqT=O1YIH*JzEl8d%HZ;ezK`ZZsH+@)Hq7wm41}|>*}X>H zI+sMt9aB-(FF;x-jYVb!WK=&~oy|-PYOEqZyGsI|z3;DREv`kl93bm>GGsX_BED8> zKAmOUMYKsF#@qBZe`3h3)OP@-fhYpNN2YSD#vEOvQyH&7f0tliKiSo@zw*7Jdc`GT z^J!}?yd|$yI_0GZpnej)c`F@vh}@d0IhsLT{8<}Z==)Kv_?pMSQk3Y_XMXDIsn6uR z(YP-OtL!`qZ^~Dt*#o|@Skz(sA$+Fl5PExt*25)qtJvbp%Z7>hyRwTbnX2#`*@{Lt zT!$QhxX;-UBjlXeTA7^B7Azd=QM|;{wa3_w@+3nu_ORI6^;WbG`Qb)G{JGjPMb8@? zF~vhf{Eyeml{>1@-M;gP7VrBot@D-6_q2wVp6c-mv;|>}9S1e;*GIp3=xxn{+W^SE zuje!^0=4s|UBl`l@|Zw+jp=II@nvq!lD=Sat9FeK~e+QgK% z`KXHkhy-?_aVU=CIT`HvrJ*d6_FH1|e@P3Gc|}r&xJpzkF8LDmT|;Pk^mx zr26ciSFg8(5OYEd`Qj7W`MEliVJG-r^i{v;ma_0Nkr}_tf`D{$B7|B|D zcmP;}tES6EU#l;me*=FL@wdl0Ghm$M*Afix0;-fxig zD+wpa=6U?1y#t&Ga2k^VD+qSh`?+qK|*VfKEtGkc;k+i>UuGw!n`+3 zKASA|N%x*#zh#$bdM?r%_N(W|N!}N+L=WuB8u79NL}D0kK{bD^BF?YR87t-0Rr9SC zl9qyXl>{mDf-u=nL6Uvsn%4<)-docN>bzSw3| z+;DaWPy3BLAoS``H=H+bpU@qZ(Yj_OOv3F6`hzS3SNdEP>dC86-S+Q^F})-YghS;8 z(x6_{!LOh;lo&*9+5mPr>}nN*Hx{#5Z|e1Jz|HnhZP>3yVeldzXfS;^OFhoB=l;Uf zP>3Rr*W7_64E9h$*Y|!~klei$5F`12;a-+iB?T*q(FpyUj7JvU|GMW7m#fTOMcg97@)$!*3XpNO5ncbv zO>6&*z*^8OCjO18%^Sajt>*T=(9J0q{VEw#lTRuV&+frLz4T;?X-oTTw#!+x82HJ= zKjd)BOCoSK)Vwv9ITQEGj50-CW`L{MPSt$1`B(19-Zxf85_|W=uW)OLv5cXQqjdI% zEDyKiv4~iECqmMom8ZaX!0bRzYCxZ$hnD=(vx#FFlM4VQYj!?>$roh2Sjg~gd1m*8 zw2*?RnbFrU-mH|p&?6w+ubUYahuH&rGAi6{)*0S&lpDE{JoKArP}=_#ScC9hYjms=+@d>jG?Xra>?Z$hdzC@IOU+Wb-*7ZojyW;n;5Ac)BD1 z#hq2{yT+ZDP5v5~KGB3?deG{_oU0zCugV6nR6kscsRwL(`2`uNL#*3QbbFoG+k>vQ zSf|)1BEAxNJI~baZ-A9wIlq7Xdwz7{M`hv9m1(99gt2RYSj$Bon>V4%$PO6BFg;&h z5<|IOa(L;M+PT*?O=;9r%u#ADn7q@BndU=>zRK|!g3m))o1py?=o%XHwcBe}=ejn3 z>`T*+IKhJJ9NRE`bi5KLeSzpHQ~DLL)x|Y{r20s&dI zs}f3ZhIHlxqz0C-N<5amgJ5mg4@6%S((KNtZt7@TZ}#*!wjesKlkUfDtIeZDElWvr zeu7A4V63@ehtO_WJWRON%!>Cq0myJT{*8ifFeyz!xE-rVzcTOMGmY!onZ6%qJsZs+ zsY`Zjpr;d~W92Ir_(`}FRc{RDFf`WCk|FOz1}WWFn&dU z8`@#@DR~Iq9G1xg1$VeJI3*fJ8?VUaQ$KR*i$^12cN(*;|9Q=3HZ|%yTz71RhOM=5 zn(JsNgIE4pn|^da12*o3vqu2e1_~}m9Q@GHLN_||o&-j77k14y<`th1oJcBG#KF#U zMt83#UJ#N^jeesTMv(aW=Lpj^oB2MH6@5Ypua!t8viOn$0diKz;pSi%5_8muXC5*S z)tA;6u$&CPBZw$h=(xbU2W{p2f5llR`$wo#_Gy$QX~eEHbB9C#Fn>*-lKO6b%T(B_ zJ^f=fcE~R=UpJ(pH43P~$O#it;L`N8NAbv}ziA0~3PZv&2@*Pi7Jg$xe(@K0!tdBlaT_4x+HhG^*A3R$4xycd+O;jrc1ItzI-ZpZmxeWyIkr?oD)Z@+bV|!fBi}cC0=3j zotbqoF?xFsPnN^)ZWHC0_FF=WEqGTDT~69=@$9?fUq{5W8l zGT^W$J*4{LY*NWW{>{*Qt>pM%mMA7(?CWFWl0@fi8<&BAjz-<^#}?wEFCV#xYx9Q? zuJZjf<~g;-C<(3Qk%)#1jVwO9mrg+E{f`ZaNjV9Q7x>A-Fm#|6$raI%W22ruaI4L0 zSXBgIL0@>=_-~uCDm>Ma@L6->>KaU_2i|YiQ6d1DF#7qZIZOfC9+IMChq{96`dKdJ zAB7h4%-2_pJxdC%XsvxCJEpbi9;`|D6LXPM%dh>hwQUS#*RpaHj@E1yvwHbkkxpQ_ z3^0sU3}ceH523~MhR4}Auu?dgrI-71nF5}Df0Z&%IyK*~SL7&qen!GBK zR>I#=k72zUC)6dhP+@U-T_nk0^O`jBtr+@4ajj8}jDvU3r!l386j$wuJMyS7lc7b* zmlR4Y7o3dXU2}G?0xHsxRJ)nuF6J757KUUMK+tCt>Qs#~nJh7`SA)9wrjzPZ@PyYcjsa?|fCZDvrXP!WEB{DGOVmeZV&T}LS&O3Z#W z_v5{#^715a0op|am5sibUk|o4U0%zj3ehBe0mbXP7M(gczh7u!rzoT4BFBmzGJkDj3XWG)3 z>f^qmHdXJTW;;pv?w>f%@sXFfSwU~R2%T}>x^m)@k{xk~TTdmpcwwCd?rGe{K z&l2@kb)f<;v6YdJ9d7>WGCLj+wAJ7X=iu6gZhT+4u7f+)Q(Q@s5v4=dU`5R008C;* zH8<`v@2|SotYf8gZ^7Oxq%Diwev@MY^&&U?L_3J^)%%UbSic3t%Rz@qg`jritO^S2 zQ#w%A+eqs5L)Zl8{Yu%POr6yM|Ing}Ga?|PwO~I9&YAW+=Lq}CFc*sA4YkB630?@OWw~6FXR4${{;|dGBFd2WJ&M12<#CFMc>5>%}o0k@t z6lD%Obl48+JsQPtK?Bw~HDPx;O5{!McBHek^W70jvbXMA<^0fbXChH&AQN6zu%~J= zAg}+{tu)m-{-KV)Kbk;8mCkrlz5vLh9cd))fUt<{Uhp^OPv5C@>epRY`E)A7uyK>k^ivJsC;@PUCyKKp3rFuAe>m$P zn^+3{dug?6ApB&+wEJJ)(tC~b`c`FMi$5xCmP9B@gU0kooE7Zpmat$gDQ6Bj9h;6> zsl)j|bc})Zz*j6QOL%68Wcu$$ZK9$y&a;$IHJVNU!r8a^P0;CG9{!4Qr4+{)3gxP! zEgCjNiEl=^xN1G-v>SY0Fjny%SKgZI*{9Kpc6rucXI57B4lM?Ew{x;bojZ6Y9@bNu znEy@)C1Gc=Y8<;3ANNYO-KvqZQq!NX-Jnea(-zDb1$@$G^%u{JZ)^0#tc_v+0*;%t zTLvMO^DMu4p=nspRhGjHBfLlYywZeX5|OmDnILjMb~RJ?y7lZtRHS5w)tA_K1!bLu zd8DVH6g>&zbF}l`bXBy5fOc?)>@AjEgp5S7ByO%+?)oCwLaiQXc>$S4j4X1B!lF}z zM>QO)^=vl8i1p+x>_>xBP}$P657O&b8x1&dk(bm|dnIv^65*~Q$2FxHkqhc&s)R)k=1 z8?^&-QdcB+ZQX8PP}fVsUHO`ZAE6^jdKtgJ={-Z`V4X_6wO}iK{o#90brl6e;m+?> z&Il#Y#TxH5Ugf)pp+7P9$cZBa%;ZS<$Ln1$48)=jhQuWi1M`Et{bjw=w+R~C!jwc! z2?4Wpr1j zyPZ4A8;w?3dq`+9HsGctzK1^Dv+c~&|0m&CyB`U}yH1sviN3M|Qlog^EHj!TKx77n zd4ys5wCzCgyIM@dZmyPUmF11eUo2Yxg+^{(Oxfu%5eSzybd9`9#V_k@2Q4$`R9^e) z@4)l&g0WvThJ-HY6ki9r4&7Ts1+RKMbWl^Rvv7HKx5|m8f?a4{RJF~=KEI!wjH_3~ zEfJIlMwPPL#liqf6@+)3l4pD1@7A-VM%L#m=Bk!BVu>xNr$&XNtsZj3QKfiadWk=G zlhHDQw5aw>j0L^du3VPK-L7N0PI6Y*j%`j>t~xM@rz4&^q>xm=V&?)p0D|u@_9YSBT#(+zZ%z)|Rkt!Cnvh^=yz9%mW0_2EFkp_|Mz{k$u17x__-~Nt_lkbqnqa6_acBNBw`<Mfy;#aGrTt{LgpKlS;na_Lp+mF@={-&-DubC>D)X|QBMLPp$#%c6JD zVFB`QGh5#LpJB?SNnG^57H{S@eBvVFL0g zXgMrxD{Y3hR7J4E6U5C~Rr+)iU6~q1*%E=}#+bD9?Sl{fCAIz1nYroJ(~1O=%cffUSmt|Lrlm z-M}nM(*kqy?e6lmZD}M65t2r(zK~`*o||n+Dv7=Jf5jR0Uf)@-Uq|Lxa z862Dk7)T#2y2*?c2g4Y3Nlpi)%a`c$-s34cx@$dfsd7G`nOKlMQfV54Rl5s%B0h6G zaQi>inqBPGe=i8Vn=4ug{c8PB^Y`;Vt|un%_0p4Tzw#1B$;~dV+=td{uW({0+;x5H z0hN!FDYXMnpoDB}M_;m)vQOd~u6@#rWO)KFd9?Pb$L zDvpg_N#8QJUuR zH2v_@j}$U?yJ4+X7XU2Ft$3TnDIQ=~EZzW(4uQRJ!tY@jR175w9J94DUO2<UdRK#V8+-_OuIb6v}Y078G2frN0X9e*@4uS!;K? ziXO@-p}rqwj@XUaMjX}w>yFn7yajc^S3gH@yEJxvS19%fPkp}JhH7b&=gW0Fby|(- z^V|L;cR4RsAnUO?xKaOK{6e*2*YWG$)ks29odi=Z4g)W;rLn{B4nJyjETAl~FYgxI zaSX0tFq4EaL8&2GZzrp~A^)S{c$rN=`T&qN)k~k!$&@u`FjkQVEl8$(-pKAo{#s@2 zr>U2T?vocLd&Um@JSWMBcT5^5atFdpKWx~>$AwsF5i>vg6L`~F`91_*4+JTZ)3xo} z;?E@)8QE?duKS=C)kzY@P@2ERbF02XRzhk5OE@x6;Q0jB`ZLZ4KE%6TX(hqVk$G%Kywb=a zL_6dd>v*NLtCTufW?8VABM$YyOsMAVXm~a%UU->>lA)5ejR9DS1TX(N0U~i zAV3tR1+{}EQ=XGZ2A(usW8`cgI7?b*weWD0yng%~_d_V<5s~zGu*1x!GA`1* zu0LzOpXLp8V+1M@; z4Dae$Y4Y0*d$8iYvDHju$g-fQH8gzgyfkjU<;+4;YrMPV>8J}?ufE$4eLkD_NKZib z^7;2q?qmTYrC%m=8cWS4aDF)dHca=#RjYh~pK-G}@2k(i-#z5KXZY_^jBl8336XMX zqs4(4i~LayQ{!d{NTL;JH92CnckoT;iMK%h0?K!1O6N&g34fEoL(t6?g2{>LvyN zPl0EC>g3E=k6Z~VRCeeFY}mlSez?EYpEumJz0*+Oi2=Yh*Z^egh4#en&BGY@C;4e< z!grur&ty|um2X_!QKL=DEzmP#@F#iqHXkv9?f~bZhUef%I?mK?MR;a1XtE?;?JWGw zH}7nTz4d)t;9ITZL!9lhIskR>38Y9oQr^gYJp`0kjXCp&wNX|w86DD)pn$HF<`?fh zfQee$H2O6*DC<#3AvqF%zC3yPTu$M+V*YQYciPrUe&7nL@XQUnwA;uRDDG+K{@U=l zn62|H>dIMLTE55lPE?=4ba4`bCrdIs zJYO0>EJXmyBIt{6ZNGC7Ee%8;fCcwUOefzIR_#0Aq4p^X>2Jjx3XHI793b&g7KUIh z?z)2Z+UBbq8bqO37KCt|vf4(>cptBnbJ;mVa87hD+>j4YDN{;ZE|oGo=r?Ff-6N<` zS!lWMet$rs3jp2+B%hQ3CnA#J{n#ojmP$XIo}|P~zMd@4ZJ;mYd0Y#`kZVq`iDMWM zE!YL{S^2mUYhx+i6AP`$Y$o5|RH2gDrjDGOquJK`vY8tK=gw7#rloDykUolTRdI4Y zc2<3Mje>peEW{zIRQy3&z#N3k3Rm-+-LF|wwp+VQSFgO{b_cfM_GqqDFN@;b`RaNA zZKqL~yevgE5&%~M9V0Xk6OPAlAJzEcjGecMN29K4Ez6}AZSft40#wRsT3HKedD4xp zet57wgQwRlXM}9V{Y^U@FP_tkS%gjPOZNLZ?;c_fo8xc4X#AJ7pLh2R>)#hGWf5i4 zc>5AJJ$7)No>sEq5^z`oPZ=KhH#k_i_NyR8v9O?yySGH*Q22Sq2gA9+u{YYD^l*;j zA0$KgwDdY($D`Kew-1l$v8#O;<84B4-KT=8Y+$DiW;6(&IbAh}0!i~>Kr3lpMr&6x zo#hWKgX_wnvHKvAX5;OyNfpJMDq}l>&GsN6y&XtxCO!9@2oG*MOI-yHMG(i$sU;Y% ziBeNTot#mkD$o8#t_%3u4By5cE0ZxobN};C+2007^Hucp^mM}0Y4~9>q!_{sp6p4N zE{ri|%sGJZr0pbR;JN#^lKKDJNVdB1j;%&-J}x?nA9;Pnnxpz6TU0XJSnQbje{zia zx5V20^-*xS=juQ(&Hj!0DE4Gv7U{?HNio;6`!$58%|S$Z zB3@6X{N>5-4}JVoPk)?ei8yWsi+A^Dc%X_TO2@aRd8@prqXPT;@B`z}j}s@ke!YGB z7c_9`XZA^<$VY;$KqX@P7PML`$;d{o)}13y&NCbjUbTRXt_#SdaaHK|A;Ulryv8GHY~6U&Sk zzMC`3nN=N>FWoj<1Q(dei;W{eyl`QA;dzZoV($SlZB~`wMhpDXJ3BkOlaOryrcY{Z zmuu6>gO~}WYkv0936xaYktuR&(f2Bw_-%zF(O4(J>%~sT+7Nhx)V1p;O1Do@&amNy z{%oIe!2zBT?{766P9M+c9E=r>s;U0-PtbXAT31#Lf=l@-}g*#gs)f&BemX!X&gmnSkW_?Ai)Mm&+aQ^n@ z7aQ(1nZaWtXEzY()$lSjOTw7c7y+;DT3A{1sVLH?wH9N#s)0db)hUo~A9K0BRpfReVQ z_UjL`f7uhdRufFT`hqGd5-^C5n5CDvMn9e_ifLm=>IIsBX-bVst-=`~ShQH7{NP&xj=)H(*5Y+^V6iF=}&C4BcvVsds% zl}{9J({&KH-W8wk>#zLiL12jwek+ruT}^blh583GN*u6CtGJ>AETV@eCd6hJCOqmLOw*4vl$7dZ z;yuoQAUM?QUu&&kH%=$cH?2$)%sjpuj=LX*mCV*tWauF?JDD;aPq8dt-Z9jNJRMQ@ zn|)Zb^yjdP&5g%b4Me=+?-u3HF}gXBzo2@pQ1&uLi9OH3puy{nyzfOjr1r2|+;o==BE~na^r4 z-Y;B~=r6<@HV-zO&aEsSr%+H(*c_8axQeV@kbSOt&UE1pxmkPbG8@;PDMZBL!dK&Lb(P)LG*AH zXJ9IInq8!KBdom<%SILy6%ogbJp`aN&a033;=;GL?iGHrEffnK3kW(z)$rN1r{}lu zTysZjSoZ|3o^q!P!;csllY9R(1!x%Djog@uc+T3a*&^AIUc%w#KyR}?{DOmT9;%;n zfxib)mhnC&O=e&t%yx|=H_Y45&1HVTS;j|NHnSuTWHD8er)Z z=x5RL^LX4Kv#oOQsXW!QsFzN5n&m>_Ti@6KT*_yycXKHtgES&9-*CFR_)z(jqB3H^ zS{WBi%PtN3i$Yx<$8ainRXWVL6Gs4WFhz5m4Mj2M<4KW?C)O03u@bSemWjs_Zw^8i zj4X&PVmB-+vrr>1A4o6caOVd2M1!DIukBTqzo{H7eF1r?UDH21ou?xHR=xa@eP61} z>CQX48I||7uGS}!6HwHrsl(MVw{S+i@$u0gGu{3d>Y1LFr)l+Tn7knBd;GbjcfWi- zr{d6wTV0Ji>h9sQ{jKqV?}n)HW4`A(xqU%@>ta=I)_r^NF^sa#v@OZ}hC6I?r@_GO zw)pzBNlu67v3{&lU%SduY|Y;cOj{}GO$)ynzaueiWjnT8$bIGzyi<8GlacX)>!{h^ zA8wevzb)&wrS0M2k#-WTzhC0ZiRhfpQBM{hy2P)iodKyOyu1R^#Eb{=3y%5tS9C1X zFIz;*!D@t8{ntUUa9Hqw`$iy|^(}R#@Z6O<$f}Ws=V4;;mj|`DK3-?FtB)`K_(_5# z?n#a=B}j5mxG`+$4?Nzb1Jf>rvXtk5qi9xNU1T&&KXPomwTQnfWdfin4?F=orJ`*c zXx-8droKjb?3f5+y+b9%xJdfL!BCEgq^DUtO=8M$Ok8p{aH% zH%xm(x4$i|UHoRBQArt+|J`(z`^S5`Q}B7cn%nJL{w>#@c@l<3HGhK(%W1k^sk6;| zX!*$UhVA58O*z}C3o9}BS6;_1%k)!jp7e0nP^jPamh%ZJx5}8dsnhFh*VF9AEji8e%Ho@GrZ&-*TTNGK8peO;w$PhyFka; zpGh)5ztYex@wy4xJJ38gA}B#ls(6`V_nOBMdKQ}Mweeka`UN49q$-oGo?lM)4j97I z#jp50isUvtJ(nH#4fr_6>&WFcNmz*zwaqpEh1vWytP*!j_92_WXhr8EJK1HekS6A_ z{@M2~7GQ24H~((_A-qLsYTP51=CAcO_b!)$+?DLFpgV#$=#dGd>u-lkETNC5sASG_ zkAyUoy12_52;TklO+kj1AL$Q}54TNzv)gFVb-f{fU9xv>j`^@{H+g!;?Jgk2<=Y2u z#0t^VgPt;Xq{7q>{=WIngt#zy9Pvy51|wG48%U4Y;@Ic<2dTF*p1(7s~=PMw7heyU$Gx z*~(JvFfdI4y2Sta|Np!MRCxU_DORrn3pU%2NN$M^#l2U?2ls z1Ca$=gRcDh2=KZOyg(p|bTSYHuqXR>E}i`Ux=Id8r}%%~|N9^{t|ABok^}uSvcbr9 z8mQ~-^mNv0_V~oa9$xpPA`6v!Nq+5q$*Y3DZ!h93mjd6twQGCYbNHe{XKYS%4D}s8 zs1qMQkZo9JaKnJ|_KR0nSZ*tjVJTlRK_=E#OqS>p+V0=fUQ{T~VZ|6T%3FYiuP-OBejpme6RuUUBFa2sl$C;aT@c#v78FBG{pK3)IFfOu9`EqmrMhUO=B; z4S$&M^y4F4g@zyb80yp7v>!JGw>s*djL*WB7_~fi5{V6Wj|!OR;@!_~%dSub^!Kd` zk%M` zO7E=bL=R~o{k;l>jdZBr^=@4E{^L-jV>)-EP(r_GdkDn$-XF+}4rgq?exZy3xsDHI zDyc^XPP9|evR41M0uBxAkr3M8Y~HpYe4FeK*>&9{&x!T4DIzZ-m(uW`6dO?p6eNFi z!RYqz*7>gQ3yy2}s7YD+^W76CGVIde0yE=vk075TD%k`7Xvj&P`rYgb86$spvXUwu z1b~M&cUCQK4H2d#Uq=t3L~-c$=Hoz zClN)qA6j=6!R$c^@!YMLFmdbRPT1fK9oH=+KZz?b3!LR^9kL_hA`ZHOX#q>(r2nOT z@`jr-l=ni@h8M3Xku>;p@ae6)A(-|g$ur z>^7#)$>|_RY{BO!F4|{>(_!Xy@7Fn*f32>w61=zfMt1YEb@Dcf&wY@$17Let%Nke1 zY5yM5_1E?9E0$XgSezCo)l2Nr66f6}jL%Mc1nX1=?*6Uh`u7XU!kXBMy5RPriySm9 zZotetEbSQc*5{4lQ0)}ZsFJ65L(B&^JOjjOH~X7xFfUpsI?5^b4dR@N55=b`n*`J5 zU{#kM%l9tn(EX>;W-RvmRk0+&wCeE&U4I#EG89ut-M2GYVLZ02C&MTCjSN11&}32;1{D-!a-O@Z9}_qUJ4d%hO5MG1Vr!One}C zwK#M)3#+(xS1F1W-*#>C3ma0d4m62U}-8OaZ`*wgN z^|Nm{3f)4Ns4JnM_aLv1ZE{k*@*8yqrv-EWeVZ~i-Gc~ZyDgn{=;27gKw~W2@??6e zsCly8GX9mV^|UESj&(wo5g<{EYC^WL`EQGH!>FBG{z;G5s5aJFd(Ipp-UyN9J?AZT zA*wqqBa$9fdYtp>OsviR6Ja=?DI>WUx^zG241j2$9uUVw7`i(p=+)W)PeO3E7<*LB zqhh}_4qlQ~F13@;Al%;}c1Ea7yE!TD)0d1;X5wZv5MN{UwTiTcb5t?rH;@;2=dU}ekpEXm zrPiQfw{oUGEXNQbY>&Uxuja7bCy42`eYO85#=5(U_Hx8Xe^iK==B4D`)p|&NOjMC~ z)ze>)LqLX}B3bch%nKB6GMumpj^LX@YT?`u7wR8Xz5XXd?;P^lTjKDYNrAID#H9))W`e;m_9x>!&1 z!W}4MCJEGHbFL%Sn*OITO*{gD?qrUKK`OkLj13zK!Djxl;Kq)_vg*$b=?&Wud`soE zf29|ZZ|dKtQ%6IYh#pw6ckr`#Lwiqs?JVOfwk^Ypl_xx3rehxtcU=NjO_}Gi`fPorueOpFx!p{~iTt}h0suky)ZMK>zfmqvGS zK%mZ4`0{{pqcnVS{nvCfVF&LvbmQN^27JAfkxZIz&OuDu)0Ip-)nzdXvv*ywwu z1p9>T-b{|{Ngg`Id#?tO9*dekLAO3-i5{2!U(bL6<-KCRs0yds2O+rEi(_ZN=zSK% z$wB34Ya%{wLRA@HXG^Jp#9&2GyeV$u?~!3au&L{FhtUh9XmGtf|M|z?XI`YfLd)<9 z?2Vn16Cmtq7!8@b9#U6)wrvxDrSXSGD&&g&t!4VeLB(IKR4E0bzW3BLxhNX3L=A;~ zP9`FdUWXG=4X5*C$o9<)1F9@~Na!5JOIOV}ylGV)!_spaT_rxE-nIGqd#r zmr=pkF+g>>o#|AK#Dzfw5s1BrWyY*fJ{m%@K45K2!#nedA%=7qjwJIH{(|Y)K z=-<^ey}Y8*3W9^GF=OQ03&wT_(f*_T6;h7VZYdG}XwlT?gphEk;5YBT3F{Lw)Umev zO%4nJzn^7)6k-EHrDTs*a0>Z)00F#aYZtW1Ip>HXfYRW{SnHSAqK}-Azh8getR63! zI6@YMSbwg_+8eyz@iLjgFkIE-6HU4QA71IpmB&X3!qwNO57QP&>M}iE{|;(xPX^G- z6u=EYv`3d*e7dRZK`w5u9#NL3EBE-#8>P`4Yp|Cbm(k&oOVPNWoH66cF5>`#e&t%_#$SL!Oy z-1`2#F&wDWt&2%yX)f;5p0MRwyISI8JN}%Eu1%qd4z2VT9KPZeSU}OA%$M)QcotSU zN0yknxwL{f8Ph{_0X8e72dT9S;jCcY+;6iQ>X3%CEj8VQGQ6@}82(JOlrSSS$sX)sN&MhKJ z>@zk;Uk!on19FzfmgWV?*G5Z>L4^YSj)$njZaz8_*C7QIEAJ)Z+3e{AEvuolucVa5 zYKqr2ola{juxpfR>C221R+Cfb91lI6*v6H#--;6kJ;@9XV^q#Q!QK}{U~Ewj;; zjrW=8B@b`Xhg+rdi!KWzFgM3OyFOegCHyP7Oy-g8^U=M2)I5O?y-{`)A%;xkA`p7Q zUe%L;ww@?0t9ZDPW|cTl+BN_bwe{uE{q=Ca;C6?<^~drYguzVmaY6E2ofk8da+=@x zKVOQ&@v8|CFx+g3(!G?n=MWv&$9fP2!4XJbja_3a1txl@B-$?R{*cO}MWsdp*TBNV(|S z#L7TPUZQZ1RSZ#LG?70MvGT*$$M^?+ME@s;1f!=SR)whpGGJA;GJq)kRyM`v zIKo6bAUp8g{PnLC+9n~ZY8gSb+1L4eHSQInSr2d@3lBcG(8B$44$VDkU*Ur(!U*+0 zF?uqO<~5W)tEO-j^mj@i3?A&QpDvbW77t5D8OK5|`Y}3}Bhxp9Ba9_0j5xZUshQ8erNNjv&kq!v zh~uO0;2+cp>B@1?i|m0J8F=4yd{>IilkZ#6vW})0!9ADdUu2LwJhC@H73%7hA%)a-awhZQqc5pgNwPs1|r!cLM2VVsMh|WGm(li>ykGNOYv2D z#kBm9qlymhKk`Uj;L}fN&EM;gHZ9w(QW~dsAn)59%h}@gU-f)iRoCcxZ#hul^JYG- z>!qznK{|3s*~I$=9eMHSB(BXcY_#KKV0Im7iZN|29?}<0cnz_umjyx)iHom9bT^q@ zz+Ar5R84O=>IJX;2Y0X}SyWBVBkj64YDT)GqwQ4TZYHApocgv8=`f6H0*^8K1z#n1 zS}cqV5X+%IPhQf&`H~@#F7r4M_s?3xE3-34@Om(iA#F?CjYrbZGm^P$f>qF9`jmH^O*Wz_a{*M^)N3B^$*I#nAYZ->W<$7^&Ua=1Js>@+69yT zkb;PTVbxlAm%=Y}H$B}I;BHUPe!=ZFahzU%tf&NJ8L)KX>iN#p`1U$=&R^m8r>BW-eEIcqV$MGV`& z&_LNmeq#M4L`pV$^%d&{$6CU-V+p_40j4N2x=5t+CB(sZ+i;*~&tTCr#;T8<%exWe zc{`UDy{vu?ci{iN5Ee|L{nLIgJUCE7>JLZZa7%c5Hk$!XTSY)t#I=%1iK?g21Jcej zaElW&2u^}5J$ii@Uj12K@VasmWK73bZ=^Z^IhKYsS3g)89f)1ify?)t+=Az7P4?h< ztpsFC4`PhQA)CvVRVy4FNodITF?BcbEe&JDhqupT*8QhDEos0ywiUjw9JjR1c#hVS zrIHp1MQ&PA5+^dph0i$*mn$0o!+pxc^6A=l*`Me|<(&jsNgjnJlu3dpru-^GVMY=b*p6=ZwBwIMa)_Mz;9R`hJpdaU_$$As0q=`mSLcpO_|IX8*CAgu-{#Sqt@SpG7m1uml=F`Y z%DNH!h|vlmXAiAxM=YbcJ9NLN%ysO9SFWQ=?c$G`gyl%K z?HpP-qhKwBC-3I_!hOw>SAoYg0BxpRz_eT48dLjB<>p=1r6U|R7-AP|#KBJ9b9uL3~dZawA zy9QpR?@BWpNt^XA>PH&n@{Folr<@VXvSe>?z7UuI@IVV$oaowE8qz$`KsIU!yLk0S zCx@5@AF}kHnrnZSd4n$wHp2X{r<%B`(`; zz=b%DW9{O71U7`<4ZI)G;d}=(G3zOlck#HfujLzr_;t84=&b#mXOVw{)Pa@ngZsuSPwix`cUU z@EIGFA=#yEdK8xmK6B@W^0OlxlsoOzprSXG=6Xg>?dolJmonWf6Ak!jp{52#->3X1 zZ|hI;SO2M^oS@o>nR(WVd+Lv&;ZRuCG#;PcGyyVl#D;_jjf0r2k& zbsmVX?#0;Wle5|DoXJcT#@|r;ojY+SMY6*lu&zdb)orkC1(vVOhv~Vt*EkAxU#Kh8 zWYi@m(cnnf!s`+KE_z1sh<8Ea2<$BZcP=A!6TdUEAr4bw)5P=CENAOU?;?o5u?0&B zW1+bVjs80SisDs^e!OhYg;+V59eu`|`U}E*6i&#HYQV}ZX5ebi)EiGLkiMUCH`X2( zo)WkVx{#&$g4B@^TGsj86jvPGeECKc1UB;O?n~*ND^0KCmjSsAb{KZXn?fdSACCz`kK)bd3cB{8(IgPEEt8fEFn)35WZNo$(*pt-!h{y$~m6w1nq0%V(k0iHlae#k;*o zqbE>q#FW*E*b?8iLkPzFyL?ub&#+RmzZh$+0lc{+oMS7&b90n+#O5QiENjv*dEp$= zGk<2MN}}9Gibq5`g_L!vsY;`caFJUm#v?<4ix$-rO+K4(8-U-2pgoeCVL;qmycB{H zcZW?q*&l44byJ_rP-83$38eMFe}d4l zZR?C&*Xjr(e}$iOiO9o849S>OC&5z%Pn?1ewKyWvPI82DKVi`+8HcMnUrzC8#6>(G@KH;o?Z~Dk%Q_i-Sp0D&+yfMaTHywJ zej?^|u6Hzh^>slVl2{1-dvI6NcpN_5vL{0R%J|b|Tqg{>ObI zqpUSNz@-6Kwv-tNp;2RCXK@;mw+v>aT=&9SwgMV|o6b)20Qp<7EV0R4hhg3{Ce)ed z;IB+a)9gc)O@ZE96mz;OIceRRr+J^t@n`TFB+SPc+8P*J)TV34_hVsOn+0-mf9ep| zV-TnRIVXIKGbiP=@E`mw^lqZencSjD4QSBEW&{>3d$FmG(3Ph2Qt;ePQy`<+nPth_3(lGP`JGUJem$Uo66A`50x!e5_B8(*6MxI zt4$i_sEOdL8TAjc=Yq(p<%PPYhR+teKydL0BeAVV2K}g+#Rk&GE5;&-zGe!=vS1T4 zBh&}$B0q}v7gWiLnd$!9;7pc&U74N`j>3hn1FBRxsfeMLU|tzpnDEKqbuYZ;d1mm9 zeIc%dzS1&Jl`{nh7mDK?zv| z7BWd*XI{C3ID4R8a`cJgL~4_R0GEE5_2f(48wia&qg!PTf8wMpR8L`9b4OB8LiLUh zGm>YZ)mv>{nIj`)|+<=Rjc|~draZ)kGn|j zOLQ~Y7qq+9ABpT}s|{#&Jhrp7-AK2rzhl?|#%85dtMw#i_3idk z>vg0hpxVCYM1-3BjnFgeN#vg2?N5*}a&L^Nn!`7Mwd$jL&Z&?`&DKg$J~KLcCiu~i zW@AuI&9*i!YYa)C1kpbxGfUS-|WCee2pp8C#L=Pc8kcguo#8jqe$W-rrINHL&1 z85TU5Y_*(1{n^46J`UwodvjlEvx$(7BmFp)Qn7>ylnIJD{oR|sdplbdI*lOH)wtK{VcRp8vOI>X@Y+?~9e4;yMx<)k9o%ep`>e-aI6Ukx=vHadD zweBHmkDOH+22iZY>{=6R7IkDH*~>@*$jxVv=?py~P4XmeEj;mBG%uf7Q!<*7r?JSd zAZo=;Nng|x98BUjUrYT&nCw-aX0O|p^B0ju;%386Jl2@4q_;f>yyrb*bh6U)NSjdt zp_V#eP@?#dxs&pGPgJtkme_e9LRlChFIHuiLD$S^YCxh4`y2EvrSMX1P4|r- z`pdlX$%L^VOl_wO$nxdDoogmhnU6?(Kk6bMGZ5^|Hvv7P-UQIMFA)SFs$GzugG4d< z>Hl`8`^|H00RYnbZA39mnv4IQ0iMYB$$+#5yMSaQz^}BDLFc=1R=l!P@uK3!-~YMU zex`K2eG78)n9k&^c0xvw{75N4_fW9uDNvH;{h6VO`ZW=#XLp9Yda4)BK}%1KdK|FA z|4&Q&=-o~_id66RyLvvpH(#as748`O>C}S;mobmh>sG!iH*PjH2jk6Azu_4i7n~ITRlU#p7@aIn*N~ zm;7OQdHGYmRPLzzy6y+uy0|ACC6h8&rA14$7<~E~O>XY6Q?MHf6mK`xsns8Ew>Rwx zEtbYPDKq=1qW;&#U(1rv?v@1i5820MLkFBW#|M*tTpEAm07}NHIv?hD4qfMO(~QZ_ z`sl!dA>n(8d5IO(T9Z#?BtDnUuGJIX)RzqmCrIK0&cwN)M|?ux4N321bf}RtU85-7 zZSB3|Ou4yM!TN*km(sU>^$SR%KN*HrzGVcdL~Jzwb1d)MtQ+Fi5xzp!pOt@Zp0Z7Q z`n+X>i@c@9HwE%p9!CkdbmfdL>1D1QBrcXblxH(so8BQ-$o{~ym5<@$yiMRcqn~Bo zF39l6mVHoC@;T1pa6j1AjOHvPj(x82?y)6S7#(t6g)P?@9pdCR^+M%&=y30%`6V6r z7egqxA%ELc0b{n%!Ow&=9H7=PnlRL@^Imm-hAZ-dcSwl9s}kxbXfN;3A8uMDV=G=9 zyU7WHzI024Pp;Y(IFmJ5gnU*B2E3=iK4Z@Wws#rIq&gVCZr>fW>+jJu3k_$l&lNq+ z0*i0j(4g)FHy+2#mo8UG%zL8aOSu9CoHmp>Hre=0eqmCmwk6y7PmauB9NomWLtH<` zaBI9r-H4dc^DV7($m)~oKE}!WJ-Gc_@n{eF;>)k^$}&PjfKKHl^^jFk^JWqF7R)>;IWd^y-07!68KuOK}7FC=g{W--CBC+G!;^#Ub$PK3PdIvx7(}Ek*=SH$;T)xJn&)BjOSoe={mXt1*eXM$rAlzUKKT z5QQlX_mYo}noX5wI&+z^??RFiF3eOksxp7wN9{zVEr-Tp@zE41M~R=pZW(Gidg#l- z;{-w4zN%5ctYGVNc}XtQ7IQzAChmJmlbeiJrN-o&FHY>NPDbu*r5SA?@RWI7s(6`H z_76Y-NwYXhP?^~zZYdQxM1+#!m=ckZ87;g3VV1=r(txH$5WW`CUCcUgV0Y02yCw0h| zu-tXD>~GbU z+Up|J^e2=*at<)ulOheyC8ss2MuwIqh)1eQc05LuY^38J#VQMnn!O0pjO`t_`RF1R z#7(CyE&{+X_ap0%e>M(^0f56}f2_7eFj&mKeuo0>)M8icDo$YvdA;D0-EUBN0ce@u zo52{}ndb6-No6{_W9mmjKdqcgOCSHSYEk}mE(MmBZHt+6@ndwe4F$rpg3yBRBYoO- zDlTl8Sy=QRdueVOK@TUfF)DmVal+zeFNeYbKwh*deU?NtUFADPb(JaQn-YIKSiQ{T zvT|-yKW#WNl_{PH;*CCzk_ShKZCbYlyxo`9kKT4anB)#et2Z``hv?(REVe20>ga7inY+>P1UO!CX&@fNHT=`@6(AUscuSz%q5oGlnzfkyJ1KonRKZ(fR(2oOV zD2q!4Mq^iTo^9pfSJsb2LVtf&c`V6eAo@&WbM#;{3%~~rp%n0a+u$=4n(wK$?!7&& zHvleJzBOVAGYfEfdh_&qf|1RN9;=9^*&|;0%G(?fT1zDf^(zL3z_RF@vLsfcy-9bC zY9e!@F@TCnVV`|CgNpYl8$%s&&^zKg)jsy1=z|ST7y$Nk`VmO;>v6r~F+~cdy^g)! z759ef>eH0_rP5K)qBv&CoxRf zc3DsvPAHIkMw{#UpJN35Qz_VorsnYC;K*BqCwSPcgBT!j-vvw^Lq&GicXKA+2=wWZ zS(XPHzPAqFE5%}HqF+D9(5EucgBu_CefqEnHa2;Ue*etARq&~As8iw5UNAB@$uX*E zH9;-WmW6ogMbZv2lE<1T*@|q5uLlWi0|9YhY?(6pDxCd z=~t855&>iQK7ze9DVDaPN${=9r8ChqeY77_gL0f?4X2gDzM6wLW)h^->d|d^0E%HH{zq+4iB}AhW6QKI{Wi^g} zZ@iKLdzEx*Yy$Q_r)0qYvnAv}jJmyV-ln_71r~-ABU2G1e8QJpDl&=nL^p{kOB87E zRc>f@K^aURGkbAv<}2)j(1VFjHGWN#Y7+d?w!d-LsqWqAKaz@`!`!0# zg??(cSH+JXv*Mt~SlDVfM!6YoQUUPsGEqmC$)2+Id>7JSEL(KHQ`;>{Y5}K150=Ja zPO85jsf}6hqx+n=)%iWE?N~DsO6t@%856sRhaL+_Mm^#7F+_jk*o6xj2rvkQ?t86c zgVYv->%#Z=8d;GuhNRs*#Hci>vR5krM=7U5a;^gEpH4`+W=FW%q@~FtSz1n$c(OzB z?Dbkd;i;4+BnSs5amIp$1LKqHNKzjLJRb|P8h zc_R_@M|#m8;|E*{j9Bcq3ebGyy?v-Chuisr{o%JO>AR7MmVD@I9~+2^((I*A8#D1p zd^)E+_tjfP7p@+b8Wta!j}pu$Bd|)nIHtu9FNg(xmf2acdcbgap?kb&os*J1^B<<; zk-LE8r@l2wYzx5*Shp%*`~#QO*+1ne(Mn1lr}L(s*`?C5kXC}?y;jnt;-S9j91-+C zvFTQc6(o0#`)AL(2YmV7L#IpJ9xz|jXG z?HP{TreI|#%-wEMh8{@8L(a@|jtaPJk|t;~w{ss#KGxTq@K0c8e`-86%lK>iK!J*q zFHDW0{`f#+vzv#5sQk;qr&*3IT#dnJ5`$jtCz19qNiM?wMV#0u(pAjhb*e!cks{3Z zm1i#&2J}S!?Eg}Il>fnOed_9gxSAW9s?M~2$$_D_wWq^On;5~3y4sN47jCr}*AE>} z|B*?{`yAda{{B)J`efggv@!4!W0k(;5R}G>`atjf8&f3?RCpi3(hZHO`eANFJ%IqP z>&+2yR@*OjRtN>7b&ke`qT#aPS;wLzk@$96=%^I)o0iFk-$!J1z;8srYHX{1vjthip-S5wWw~xVO#DtY^LMMI; zX$rrUJl&P?wP>?joV&vkYnuiAE`aFUEB)-g`8J|w#eME^|BV%n$edg%%-8ry7)z<* zY8YNusULRcAKb6;^H+D;i3sYQUblRdc!=6GNOd*GdodzjNO>x~j_1;XBc}t<9fi^( z%6|-nn+4ViV7(t)u1roav@*oi-lF=v@n*bKGG`||xJO;Waq;iY;(A&aQ{7xiFC%DF zmp+DvvoQxhh)Z6(1|>7H`1%DK+My(AYtOrRKM5(xrd%kZul21}JCqx>(-sqj<6hNe z_M&W7Cj!B_IZvM#aJbFp6Vy-q>IeHe)%l&@o{9v@vK^H9u|2Igx?b5&pn~?KjYNbT zm|}x&Y}8v^99Jc6V?7$S|9~wwM;UVddfLJ^0AkX?x4ptt)$u4KY9iR6K=D8Wt=W2C zz$t4-R%GTqu>8(#+IS%+^5S!eh0)gpU^p-(zY`gInYYR3=TZ!^iG5^s@6M0+-2TV5 zVPtAPpJV9Rw7p=T=-5$SaQ+pgdnq|1^!44E9go1HnC1l8;eno@G)(+I3(nv~M<2{a z)K&V0z)$_dK=2cxjLFd8`MjA$s-%f}3$rf9#y@Rw^S({D^~86w>>13>E&_l4UU7Mw zcJQ7Hwbuc==`)d;6}@5rZ_kSa)T~urk~XA7YHl0fW1v-6b3&!9u@&a^R`6s|`99=1 z5<*=syl_STlp3_AWMhs{p+$}UIF`+8ggM@Ei@<#dcK?Q*4CUpBewaJ6m{BG*@9CqQ za{f4Kr>)?_+*P_0eQxmkY6BHlpo%7q|5Y{1Hv`~0OK($3HcIH}WPrKlDxF(0j5rt7 zU%K&yo{qi2{QrF>`lcIysYPJ0q+0!UD5XGYXW99h(A%43&n%6)_QL1fa8le=u9i0) zqhj_fjz+}V4<{>#E6yyE_>oybHfO+tJk-XC2~_=IWG}xiUX{scpK~REiL!m}hE4}hzUNGHufuE#9zsqgcC(}>=X$gOKqoYCWnfcG31B%gqF_sjZ zH^3^PZc^eO-pYu!JDLa!4Pof+N$DqGVt>o>vH=j) zEULA6y334RNd2}e>a4lz6x))xJ4jd?Lx(S0I6Hrtxipo}n`U||-opa*Iw42}Hpw$( zQ_MF(dNnW&-Gchl=8{(3>Z7<=4SEnM-#u7oU=lQ@SROjv|R_FXU3uW*v8uq6b4Ev(b+U{94 zUkheuuU~m$QduSupQ!O+NudI8-%g!8q)h~LUD4$?>MEb|j-*ZR_0hrWj$#=N);t5@x#skW2Lpj~#yFc+0=k@Fp6e8VwQcnyz2kFFgqC z)+Idodj3NQdpqliA~RJT`VshtJ^(*UC#iXiGv}<@`48svhbkB~a}c-;W@eWrnR3&W zp6yC)1IY>QyJ$bZ#YzU(pI}gTfDONo0;g&|NTv8=t7q5zY{$!{y)sxkw`hIIWO72( zn!D>B*hE#l@=qst<;S&s5<7>MF;CC?aIeH`oV|}EJMSlP3Y^~zYsisZLfLwGRpIEZecw6fPbMwtn)Fl(XwD+TfM?g|Vb4~*+WYD^bD#CsrOo^SJ8irg>*Qsfw0OoM z?C$d_g;DKLI{8^yw1M;|uV=F4I0Ew03y7c;iY`%p#J^1mI?59cpAbJ~n?yl;WdcKk zZd@5?Q~ns+U>gUdiWtCOEW4e566WMclz@q%uM+(=?T#@To}XYKYD?u z!?qEfW%Pm4sT3@*sv5QWne0Qg4}&8dX9=n_DWCFM_n0<6wFSSW)0Otx1bE*W8aBFC z^!P;p3L^lee$DHQ&(k88I2xjk%A(*j7@^Kw6YavqNlUzKu&`wh7oUkqGpZmSt83HP z0&%C79HnvkD^Co1(h$^(ucd8^*FmpWHHHOfjK`@#Hwu??u2%za6VX=?Oo6!r5l5z1 z*CQ;2I``@wvv!zt1I#Uut3OiLV9vLwBOZ%l%g(>jJdL>T^eQK5<^Vn5Tk?jt&S5eg zcU#FdzlH`uoTXyX7F}wRY^P&$j+Fd5SFg(};%_X1fvUpVp(rv`+<(;tsam@DN6wQKiE1Fw4xe`LjE%xX>3Wj-A|b zY;27<)7TWzpeZ~2Ca%Q=@M%X20*IN0Mdu+Mr1} z7ZOmukgiJn8?|#NCS<`s9ir_lzHb8(|U6(2XjR3wzO#F6MlCy&Ojw( z;))!VfbT(&ByHiDraO8RSJbZ#%{Y#3jDfn_nr0k(cQ?MiX?EMZYBdLXJdzj9Vd4%u zbDKABG6$oh*^{*fD8N$Ejo*P@=DE`n5Df53PmQfmgPG^eTYC2TP7clK6@9Fg=(rOs z?n~l7^I@)0DN3tKPJ}}o4;D$BN>K-jBXxl@?}h@E$7Z~&Mv_f{SX-+O`lK9eAgjuM zf`U@}^!&C@0s#w@35%*1K}<0W-; zPG5q9-%{NJR`N{88k;#u&v5=uQ=ZFXJ}cDYPVDsa)&lpOzPQX|YExkwtm~q%9Nu40 zi5}fk3aN{islVH;W(e-QQEVM)GW+nVJlmCADDEG;v)=2pn8%+##Rv>dq2+ zmJCPhV&)dtR+$(iaU7FkCX=nbfAxq>3(J$d$WG(f1xST1(*UuUFZO!aT4la4(Dx`rrk9kpZ(t=jd~!e z1^piTgpZaJVnD-CS21!F1<F*815SuVArIbRXF+wyXzy@;2Zn?oE#?$G-2n zlX55hbqn6A;AP>EaN9ra`L3mhS04D?b?t5cG2Lu)lD-I(*RxbfwX6_!`&^H}3FH+Z zHhU;K1uStp&4%jG+*nQUC~biKt{6C$exu&3)TXRJfgk&o{`l1GF% zm$W-F9K+fP{=EfDwNYA?cKU6EF*Zw!6Xd6HvHy4pG``n2;M=i-ZW@5jywB#N(|=m@ z{N3TAYZKv9Jsx8?^@>4<0tzBpT10d?<94c7X=RN8c5gd4A~|Mi>Khu6S>!%nu_Vo_ ziv4ehWSU@3n>dxifA6B9gQ|vtN3q+HuFF6 zQ?XHk8EW$B7p%OxMh^b#OCQS9Zz|C>9_qbU*)-%32je|JlR>cop!713?FB*Y6 z@9}?~z-Tn#i&MQ@-3|>j8EC9ca6Q2v+lyufsB8$oo|-Xns!xks{}lcy{GR=^qCBW_ zeO1~;EfwB8_Q8CVNT}doNPn*{ssUUcZ}|~8{Xuned?h@SyITf(juFl>^>6F(A?iX1 z@Z1n;U7L%&QTJo%0qcW%E^{+_HdvN1rE?!#V*a+2RZf0-?X4c7EYW#ON=g81ax6d0 z>aGUNW$0X1{kytfT8TcEUbp)O8F0P@8Mt~PvJUG?p6n3P9-wF<8PFuxQ{3L_{wZK{ z1ttR>Xrz*kRXdz^ccXZ~QsDpZ1+Y%X?lTG5E7?;*kOAl*;BivLHhn8r;rR08gkal9 z%5(d(E81h*m{W8L7-EEc1uvsWk+|DQgpriUzgb3wrF$68nP^2fm>&mID?&ru1!uv))$_5t;f|&$Ep4*Tmsoj&Xl!l&0XYI4X!|kQ! zhJ9onsoDuVZcgk5#SRkUd{v2feJ3gPP0PfkmXBKKOgtnf2u-5y?qL)DypFtM6twSu!qAWlGN=?fj&!3j4B}%tzrFiva_T$WvB@lN%S1?q^0GTI+0l3vEfK9N!S+Z-l{RnJ)7hYqbLLPHNLEXZ_n8v73paU%dv;f zMwGAV&lfcpJkEIQjVZeZ`GdzKQ%W(@4d)wfTQlw7I7bbllfLz~rTMp72uu6Ydv}3Tu(9 ze7*8P$6g~R>+BRnq^#Rb)*tnQ&rG`6M5**9yTB2xl>e&US61Qlyu#+Ra-0Q3C&WD?Wa2)E~Y zY$->;_OWY>m0dOPVeS5g2AUTm^{*6h3aTGmz@;AjdT_D2h0uHIuki#2Cn>Z)KB>dK zqs5UWkL_M@>H-fSNp*vX0<2>m1HJ48s|zQmg+?yNf(1{uFKRrqy&G7xiD3`fQ*=^^ z@#Cw|eKa|*2+kcaHOBCU5Z8GWUIsXbJ2;3qU6GhEk$LXaED_Jq4<}{KueONCtv?1F zL!TDj3I1)>$fFkEkuc=R3?K#qr_KKB|6i*r96CIk)9*GQb95n~YzjGU#cg$i?TKsx zL1LqTo!oy#t5a?MSXv`|#S{!1Z@|UPdD`Jcjj2!6cc~Fe?T9^A8+K<^BA^G`R`_c4 zFx*x)@hd;79uGMkY%o~BRuYn00KUoKBLy^;_OX(n^dVY4@!dx;X;s0749}Q}F`g!w z`8tXdlUtzAo_e=keJMoU zag+av%ehpumkVDNA73v$x)5cNa(viekh(y+v|rhw8#iDRfzocYq4mURJIy-l%Kn}@ zCn^6%`3$!XXvo66(q{K&BPuLBPy&C2#-GD18(NE~#bTG^cA(2mb=23;qr=efdJxf; zgSOJT-P*(yH^0zyoQK%4ILiWWxDuCRuu>XXhk$fNH&x<(l>25MAM3BXE(N<*eZ(A^ zR&}BQgl@VAm0#5N`0ha+CQZzNL;oe<9;Sg~kk0NtEJ+`)jChur+2gK7%wr|l`5YNukz0E{0m<+&aH ziPWh$uPSO-$-B$)JB&kSyVcqg0&_LN#_#TmF|c6k3Hh4}HG#REJd!qr!ePeT&2_+A`7 zz)IR}I%a`YD{`l@t0oT{1RC%WDgOc_l4;rL6)$K_Pf1vu;id+y-H(~FgHBflur6Z& zK9D)rCrvxvZ>8y8`|6A>#-^?v{9d816L#KqPF+=Wm4>PszORXZ^u=801A z3$pF`Zc{^UiOYjiEBiL3n_s4%LYFmH4bqdrDaGXc7=K*RdLdPz5!1D=8eDM`p{>!` zNWKydTEtNZu2i@gu4*qf=s1XS8VDcQ+1#4V)j);edyjSFnHFs}Hl8AhX?^Vswx)6S22 z6)H87{z;-oo-nYgXnV0c93%%_EU-Ny2q!}~a;9j{o)PKs! zSdl!eGoe`6;Y`!7svJ+dCa^nVjRqh@$3pc0y`n&d!FIXLqp{+7MI=jH+;-Py zGc84Y%eSMvLr4_5!WhTjgz^@E6}pNFc(hy*OZ@Gi}$k`+9m zsPyk7WP2bVaS3ycwYlrX(aM*rTFNpnWZEyIf06AZx>xcI42ftiP(}N0`g?Erm9+6`_nvRA#d8a|>r?)z?Q0!WsZ1PK zedIDI-8@LppM9+t+$wG#aX|}+dbaNSU*E^hr-=8-^bh6}w&<_yYVQO@+j?78bk`Kj z4v_7n1ObYh?+qaie9=b!WK{j#t`EO|)cug1qaS)ie3XQ1e^kYsO^{hS3}wQ|sgn8P z*dz5{s}h$17YutAD;xCGU%IV)>3L#C{+?s`j7Qx!IUmOtKR%?HFDymrdO>Sc1=Qp! zbE-y%BL0b&D|Wv)SUHagBk9Vqr1BQg^uvFPq%`YnXV<9H1O(IFjwPdtW!gB=O8nc zh=NLo`Kg1VWZKG>?!QqGZ0POgmLAiUYn;@fv4Xy8i$Y5>ouSL|PEBMxxDzx|2dNHJ zCHnJOeef9i;Ty;BMAd((^{a6L8^5}o@5?w0cF_)HpgP&cp`Caqoum^dApoWYS-Wz@ z!FMc&tfcLhhL(hPB7YmzGJGpo7DAryvX#}0hg{WF`}=C2vYZ;cY{lhc&QtjNhwq`5 zhE(O_Ua}n@xkovOhRk2iyg_|^Hpc~fiGW3T@bIY~%1hHQ?b6``6>h)@WGQRV*NxKU z<}27pL@XyFI9!8gj3k`u!tWtFHA^Rc?rCcU>wEO71&(+jIm+dq=t1YJF791DFkM&oKtZ4@Vvy4TpEy$KQ8;H^32?`{J&4cQ*D zRGSuw8ABfFW`FWJM!o5N&|?5QG7X@3_vL|+ghIFF93$YmZ_S!C$pF47ZS#1-=?Z z#(diSnr5--g>6rs6I*PAV~e)m+(ve`rYJn|7c4#cP+l5?)Q$n8nJ%dgv#lA_kTIM= zr>-#d)dg6SG;H`bT=d{IR3iNFc_RHR0py{z{_{2>rt4%7c>!zU=(`+;e*9YfLn!A$STdEwfYY%yOrNZAUvBZMeDIhH1C6;HxC%>;xx}L84+qzBe_dC&cN|~1 z6$bdd76lqxf2`U)v(_Pmw26;9w}C!tr8JyPHs_Mic#5^(wz*P&vzOorWoQCh@x=>*4#L zK(~4=8Lvy>@6hL8=r>A*Cdj%XI>da|(oBdn04Sjw!R>7KoHia2;PdQ7RuT)LV}2HA zy3YC*KPjkK#}33J(^NG&kD(L+-MY5wYR8LqhPW@n0)nWE;Q}D<+*lt0L94N6ysAgJ zglyed;syT1=2}*1F6%3KzjdVf178LYEnS+}ytjhKI(w53^DSZ(J2AbtRcg%-ZyG&3_tG-kJ#O;wYaez=+#&_IVLJ? zVUNGqnI=&@8*v*-l^IGwlt~9=Cf2#STmQ9b&jpX-r0uUSm_*!uFfeRvH=g=-5mFcyQWk z4nb0zl$!HNW#UvDzuoou1cCA>0H2NI$@5z?1QtP^>HClPp*zZ~RVQD0F0yIbi8ajT zIRG!lim1-9JrHXd3bgz8cmdE+O+Pt*nzd(4U4w1Td$jRl8vjUZj46jQRqeD`-VZyz z_u~K;%y()2;cd>oz{&##gT}`=gAP%FlK^ZYM{qCVVxOCxWRtIg!CJPx*1Bx$b;z55nW`0HiSudTd+Z}leMik&xeZ?(viqj_zDnxKV?<%Hc&3qe$A6gs4| z!7X48;O!s~yTGb^(!vciNK>jEdDHVpMN$^$*{cYk1;hqeP-rVhrO^@16E#;&qgV=l z|Bwr45mu9Q$v6AoC$~hY8-7>jE~4qDc)93!%_U|dq`7>>+HcBPLZFs&;$-h<`K>Il5@J!*(mmOA4Kd(V|25Z?kU{Nwbn~UL1Lgoa~oqV`BLsl5=s9RV}pk zxw_WIl_n!3*xiO~--1AH!}jMrcS}d)>CYCVQ#z)v&(-2)7xI%}B&R)|^>HNR{Nj%p zesVJgWxVFvxmWalyW@?~gZ)DgY|vr3;Y@lP7>t|6EGuNuF`Iu_{U|z+epXx!yM8CL zI>@`x^X<3mOQ63D8AQ=oE_0b^K6_>L$D?TGr6(?7KCYek#lz>Ft%Pw`i2Nb|NOr=? z{}bG5-aQoD9RQsz@sgfej>X;iebL$G!i+ZEtjW$}!+h<0t_wxJkeN>$B&o}I6 zuqt9^xXXb2=}0o@f=z!*tL z`!}0ATB_CSx*@<9Te^s~86iliWl}-hRIi52<4N6W-V_D;g8YB}SN=Xo22VZ+E!WTMHf)_0Na% zEg!$^n-ZQbxrklTY|ydL;%V=vlG**neQIz%(Bou6EI zvvy7>(n>ogL0sSCsal<-=py>iZP_;K!I?qlZAcBs)p5Jw>{MmJ0G)-Lb=$YqE_BN5 zD;2K+-S%YPb!-d^tY!I@7|6rKO@p&l=D}7XrBZyI^JljsqiOeLxS(fw#B>l|CBbmt z_&h(JMXYGbTRV4BXF@3u-(f7W0Bz%~H+XVJ(V?CC{B?+(-w`3y3HZunG*kb@Pbrs& z860fGhsolnkHqT~paJ==`Uftt_0MYhqqGJhj6FxsWmn%w zJJcJ{_UvIAMO!y-XJ4>`rT$eb zxdFG;bfT z({1b)Q&XYgX3vxhrxF=>sO#Q3LFH%ZGNYda2nq#!~$8EO8cH zh|Tkv8TMK71BXvjy@(guB&PaG*OwIz#QrU+nK07KM;5aDA;DUUr?sVr(W2^w4U0$G zI|S`>TFK#AuF|82fL@oNOM=s{Da|e$>Px@#^cOsqNM(ZR*ga& z%b8=M#%piq#DRn`dGD4hz>oJ2>7p_}=v_zP9dKy%%$*Nz=1^ z$jR7MY`5vxSMnKVlP2oD(Sip72G58Ro6);Rhj1L6*?y+Gd2BmU5aR&hY~(;ZC8G8zy~j-by}f?BD^+M;8dX@jbU8t73?{bYxh z6C6oeqa=aoy{cZ$Id2m9%>Q4ao}HcLz)FUz9zs_BevGE6dT`x)}p zn+!WQaW5tz;JK$>IXZ_riB*Xl75-vp&+0-%hh=G;J?XRsNWpS#b7w=WzgjACLhy9; zwX&N6D5mXLfyQ$(?#XHkSZW+6+Y@&i&Ww@28z_JAC^$1HMG>+%puG!eoWUI@MP!d> zJ&_I@>~0hFt5TmHfI?Gwl5SV2B;cDcQF-Aachv)AdT#`<^k>`x_e&$CFhqaEH{)L z>6)|AoysK#5BInEvlD3I(!RS2j|?XSn?^axkf;#ED(de&Z@WFZF<+t0sWpA>s7_fu zC{IlymOf76*&a>WRn@y-qYPkw7gh68z4B3w9P-p?dtZ*`K1C~waOg?%>)>6|F7j+s znFS)WcfQ?^q!5IWhuz2x4koL%;De!B>XmH=g*FxcC9_LujFQd3L z=ml`+cP5o~4lv*++Gl~QzQ!CsizY3fTQtc?`HA5(ITgED$)Ze_qF8bV^&i@oZ>+du zg=)s^1+E%`H{6-??iYhpA?XIznA;>)z}b&)a$LTUYfBei2wrrK|1x}`yH*R+{FB0= zd_kwu9MdRuuGxQK7hO6}`m~3sfe2BGhLAf0K+o0W^)vJxU&S-tN(}(ivQ@}N8@!g>)E+MgF0_mYcgdQs?2QLyO7NJy=#+8G z_!Qb^M4<6<+uOCh*SL^RxoQY*UlaP^or2gpV(fN30~33`=vnVzNf7L@bS?88=N_%a zkfSb(mw~rcRG9CwL^ozMWgj%M2iQju&?`>g_Gv4JIuJXMs;wrVEVdqmJJCf(c8$IA7pLANh zrPq-@m&8XVrZ~M{uaX4#U#rdp5);gw>^_p)KPOctE5OJ;lN=ff|sm<6%e~8q7G~{)MB!G86sYcN?*R-Mp~3zgsdmWmPNA&yqhs+ zto}uWNE}47L<(#Lu1}W1&R@1bPQ#7}!+ZR+m*WCtf6xyev2Zkr7|++$690*!St;c| zPZ&^fv4?vcCUB*Vq4*vZ(o{erM;-57V` z)-QafH~95-%p$?sL0FxH9LuNC2ISs|VWQ%eSun~qSG*AG27Xbmf4v{8NVZ2A zPikUg45);<3RGIa1= zeKayQN9m0%Ri?`$04%N_k+=W-yB<_>jWX7 zhof9Ob-mI(a-90k+VOab-$zsr3N;lnlRi%aLqN3I5<>ECd4hA?Y?^+o5$R$jb zx7Y-2NbWpX!%x&$cul#Qz?sUwUX<@{XTv(8#tz|`U6q@V>n-)7|K`vtYIv_(?}JWt zZv~wQ$@(UzvNPW|?^pV=HYnXG2@WReXO`6vHonTQk{1Vj77z4mI+f|-rS}P9Vc7PY zQv12&%nK-XT$V{TgF~_0PX-G$)7Xc1Ks7>NCV~c$V%W;G!G{siZ7RQlpJ9;TyQ8H`pH{r8ZLU7gd z%SSv}%mD|wp0N@75BzI%K@w1_NODyv+a(RcOTk}mk>cqnQHLS}Wmr!k_EEWl3)h+f zpFllyt2|Wv<)E_}`XCwz$?3=djLTBrYjuxM&CiUrlp<-)AgO7=k zEU(I&6GAW9OUNa1UrH&{ux37&ryfvpDtO&v^^4^pi&V6FH*Y>>fG# zZJnXu_@tTNhNU4@#5Ouk4BHW$KeZV8iEDh#w>2Jpi24^FczdQN`m@g1%I2dDj_%Kq zAHEL--@KjBSDC}4<7~C)WBEz&dEgZ_;$SaA=zf&!k#Tcf93MGMeTm5cx|^a!`I=1g zMGj4tatY8DY9fF2t0Jx<>h;%*L5jl2PTn#;>XSox`>lzE!+j*pw}3X~VXJ}2P%g&^ z9P#c8bQ?$ye|e1`$44PX7SZkgFt2$l{S2=eBHYJ;bs56mAO|*Q)3R*g|caVDJ9J+Q4>FD6~rHS zLkUS5hTX3RAy0g|{v>v#|T5QnvMN@|`hn$D6jim&7_=>_qDZCgdB4=Ie!f-wdLR z)ePk)6|Ya;ns|5|3g0m#b|)NOGG0R|G5ATq=7}`3LQ0t$g)jWH+Rz{djD58$0V^px zd|Y-^PTeDra4t}W5`A&-tW;u~Ucc!ur&R)vjls&mt49NAd zTgq+A*|f1g7Wr=TFYd}k?WYntidt~5uxCE!b|{h3GR@QZqHixlEklQ?ntSE}f3Ddm zs-?_Y3a&{w@yQ?TNdpy~Py0h?xMp5ZM+2-fX!-}Wj`Xhyt_za<(3eL1K)k=-!SB0gF^;s4w!YZQ5(}-6kGf|(QyFT&b`Rp*owa$R zf&9m^pGohK?M!anK+Q~m!7DdyIP5epWGzi^9IT+D=HCA7czhrOv$Y+p)ILa{Y$`}c zA@C3hbJh)W7%;K-yM);JbGXlQ7_A;O{|akL$M*H=qWCtkW3j2`Y8%o#wf>I0Gk+&=s_u769#KT}OGVheX2AN`!*7D7a`(C} z=L5bU+d*?C`VsEaV3A|ca#R6-rrC>+7lB0Eh4yvq&KlVfHExpY@}7LdRE45$5d%@D z0IQm35y)GG`zOe?Cge9Dep2lbe=bLs#Y*ha((TshuyJ_uee78KJtH}9uN zv~yXr+fdl*?c+n3}sYkA&S z_4wDxYlWq{Av}Ytjx*uoH+%m~Il}%NBbqmaKc+A9o|7h*!oRA4kXMkng9m2h4&1qa zm8Q&FXN-GI{;T-{mfvr$-6=Pb;k^%i_O4Lku7ZDvF-$^3w{8H8Z7CZxh>BUu40epY5GJ)L{&W!Qo}|d26nXE}|U3*DEZJfYla{f3M`7uKHEx zRcMT0&(bapJr(mPNbyh+bW%YHuOxIVO z<_i+)&}48q|MXB)6KY$<-zNKw{8UISV&!Ic+qsoAHwX|EaIWb9UicSV}}>|3W;Toyk-!bGHRi8!UB#_Y_=xY8NKfuhQ+)FFvS>1-#-fIHfnxIIy%m z{ATfu!CpFeNJT6&yy^O~O644lLLt%%sMS|RJrKp_6jC#WKC&nUyv>e6BxF&9w9<3E zcfr*JXJOJc5?YoNt_V9#hdHNHrS%s6Fp(D#P)Rm~@Kcx3A#n7Ly8Y)=F%*4nr4fUd zGGCijs|J(T6#-J+9C3)6;((Ta6btpX}b{70D=h z<{n$XHtX_WUk=0N$sFDLgL8e%WlBl?xtWF{;hFL-?o(-K*bH7xJGEQCU z@M1O)fTmICIByO$U5bc07Zs zp>&;?QWr&SXZ=Vpm(xXK=qM*XDk>dxI<&5{t?4U9iaD!KEYxHrN4tGW4QF!Zqj&zL zb1ye;Wro<#u8L;U95@nqc)^m-qn<^#Y1ONZ4l;l3Z@caE(Vv$V@HVQ$qF2U)I6HWOAKS#|McV98Od&ekey`x77Guu%k3d1r@3~wjmF5^Ahe-gmyDu zQM~2hnM2i$js)$HPc!HjpN@SjYoswqKGdIhILS#p9P(o523Sidnk~E!uRt@YE_=v5 zgg^FK0;rz$5xRPh4!&bYBQ9O>&N|3$Q?mTs4`6kflm(JX{t7l8UlIV2Vg%J?&-NTB zIS^V(D%uIOY{PW^Qb&cXeB=iYCxKYWeti~=x9-Bhz_1!7>Gr(~Y&!x`qyL`uRoV|o z-%1hsp2Oz=G`*AM!b-nuZ@X~OT@`F7Zj(}V#i~}tcZL72)Iwj}#^ySK5ju|TIWmP) zIyPB?+=syaJE8?luCOSLCsVGMcXB_*3>FUZ@NXCNCh8+5zn$UU%RvV+m1Iu4L4#{G zz2M5tG3FS+jayqWgH`O3jbhqKkg0)2rVI%IUAm)=3i6BnO1D|DXCIiWSJLUTZMDW?J^!|i*W8OY#_cMAX z=Gl!0X~1(M(_a4nylyZKTbWM`3V%(TRe`<&o@{KpbDs2k_hnvtxSe%}v+d|Dj`eZB z*P26QW&Jx(8`iT7?@6}Qh}!-ztaa>nBV+s_;Y$rALP2m%=K5r@+@>66ns(~ zrw^l^fvlKfkgWMsp=&ui!T3Cn;hNSUXzO4ZGoU4for%#Rx;(&6!dma!?kBJ~4~Re} z(_iATu!n76v;}QRO~>=}WyriUDMn70*c(R`U)fHdNiX>-9@#wSTn6w90+$vIa@r=p zT6OXTv&uNq{YZ)!>fZcjGLhI8D_0sm8XXuT?dxh2?04fzVVwFIPOn@88YT4Qai+0c+q4b4sSQe+a{ zl^FJt2Faf-hePd8|Bb>kD975+;QHOph8@P%eSKBi@k;zTk zrR@L*&`R=&i}S6Daju>i*}wN(^%Q9uZpm?m@VX+mPu=n|BC~ocM;xou4bhAA12SR~ zU@bz@^^$;_yYJ~1?(q(P#IihTT&f4C5xNq})sWXpU|kz-KaGD4hu>~qk2rZx$ve^P zZ2lv7(8_;C1HM6%(E3SE+}~d#wFnZ0=^Dk z%{OzpPyAZU!-YupJ3cv_Cweg$M!LHqxmBhgP?|&BOulMd=h6De_&!Ob+40>CYu@E% zIf#QXw$&lAr7)o<{hSHxRrZ3YN!c*o?OYj7F2KVA@@$_KIQOFb^ z%+d_E#rFisQeG6~%a1WlukWvO^B5dIDdohdjc8NF$57`@#E-N8w3S_{&Pt|x!tj|M zWosgL!0irxul?7EGeM+77+%bJOQ!B}@w_(=OuYF+@NxbTouA1v0B1YPxcjdY)*-NQ zsC^;~L@qDvQeVw&Oah1QkJr2K>wj{^r(E%uZ6*v?UbED?|M9D^ZP&{$;xU7bW)nQT z%Fgz(ViUjrzFPzQ>yy3lw`N1dzZf1zNM|motEkT9ug)!0Z(hMPi!Y?xlG9R8Kb3EV zH?|+jvC_dV`fQ znFA(oIB6WDS9H&G?LCXHHa5BA#_DfZY?(&w@c!(eh9d|~x-^P0fD9p~xBN)jXnD72 z{ZjOZbaV4@A1`|%V>z#nTiaK5#V5H6e)n$r=lH@290K{5UU+`^%E&_P|NP|YLZ$xm zGTt=wt&7ac8Bw#3qE(wp&YX=#lpc5PJ$QVNJ`4^RmDj3aZH96d+uV(bzwg2l&xhUl z8f9#eMd?Ptd{5^L1>uwtHJE4pdJ+@a^xW<&_vYhY$=_6m&&xT(H`L&gN2v+K`{K0C42?(HijIODtfoa)CrWSM6nY;*2_w+WXh(jx`@jdm{1J)T!D#Ju>ve&CnL6iJK33!HB4B#xddHq_^>}yVT8xC&NVNtDW^v zR{rAbDQ7BIeY1&J1jI7%kOZzkF;lfi8^9##fL??dO=H}|p@i=-tBDLp8BIB1Yf=Yi zBJ&8Pz!!gN*soK2H0TzY)&toleKBra$hQrDx7`{D8nJN*T$4ZcEogOtHg0s>P6sI} z?eM+vMB>;0rl~*X6gqi%g<`uEkn=t3UxRzu8c9HCiGt_FS#VN}d1Ea2@S&IU_lcNS zH)NGWn|NEpv_U_6g8!^>e7jNo@yYV}pGy}UY}c%D1@n_tAqX#B|BgFs@GPB`65hn2 zNC#i3OH^SioINjm+w*@ChsI=qf<5X1&KXw2FRoDNHxf6 z18fU+T*Ev5Mmx2Jepa>O398g(CGUE}u6sF8v7?*S>;lyV4aW$<=P#@N2zevG*%fYB zWJh=NWi9?er0kwNYxcI%5j&cd|GZ@ocCD~r3~#?pix19DCF1nCY6WYx)q=8$U0ghcYzR-G>3hyZM?s_` z(8*`)Soik3XIZ4IUU3advmasLoW=Tdef3J*FR+S62=WjPiJ;wr5GTUn{z+Wmh4cr= zGsX{A9Pf33^XdART?G}NuTL!gOVt(Mlin+Gjh5tU{dbAWfFr?sorj+AW^qu-P2bzs zA-nmH7xYMmL_dDCrRQGReF8%daDLi(>QbGT|1!EgOe)x@w<7V%?5$_a$4qGfSI-Il zOdcynm=JQQ2k1Yd-u`RtI4*c9bjbiZGr$;ZeOj}Co!m{O_p5PWl!yor0d9Q^9bwRB zqGPfXi9O^#34tHhAWNDFnFe}Dn?f?uF7CL)eA*>QAdU4)-B7wBqFbv=LxCPA_3UL$ zOiD$!gPCB3qobPOt@Zyx#ov-8QJ zTCUbiJNnECy$7(T01v)^YaCt#+n?%!juJ+N@sut?Dt+qE6^}87NhH(6{?(S_{n0#D z-(sc%qvl@PsQ{>BOGgpV9b{%!;6(GR8_Yv;=aKlbmF|$fWKMI}?A16Upz&#^h^~8h zj(b4aiEy0$f|^U!+^4lfXYrlyPit}@$)6+ky&vUIT>JK;m+|TBjD^yBE+uUto}A%k z9o1B~@0y3D%!3k&N4`tyhMp=4*uakwj_7W z^>XkC)aU?7MN-#}N(Il}f-Z$1iUfY-2`Cq7xO+1$X2#&C~ZSm=LX~WphK6CiY=rGCk0TSwt~9-J@bD9hmOsJ;T1k zxr2tht5sF0Mm2UIm?>%ebc>V_B)J@^f)1ki_{vx!C6u*uXY)>rn97|1eEf7~lY5|x z7D04M#!I9#IAu+cw1yntKQRU^dRZ}+vEFYTTz=9Y{J^@ch|-c*WaN6=`_^B!vVH5# zp9d@nG52ie+3Q9d-zj9VTc>Y-(dVfeKN+t$jFz87ug&|_hpK9be0uAz*s_z5beMtm_P)@NvlW*`__L|yTS)y;YD#jYcHQQ(+43BY zlN3^NV!Ho(hVVp&cQ|G|@?vE~J?Y|xVYbjx?8%gxd+;~An{VJZ-Dw$LQEt)8Kc0tQ zox^au=|uj{8;w36VsCs>JJ1O+qTAKw-?QI*$Rwn}qC6xmA|GU`_u3~rwQyXQ)Z+a| z{Cc|7ruYSSmmR61U{6@c4X>)o&K3ACon(4 z^jk;4rswp7%;dbh4}}gF!~QZCHK)=!jV@UVr<+v9N2R}Rn%)XS+{gCu;42rUsSBs0 z;NQCE;As>zp1ixs<_8=R5H`ceApiTboD?&~x}KXWj0Ikl-=lXu6eMoZZhG=Cyvncu zK7Z&(LJ8blJ>(_P8J`w8xU`+9Hz^)8K4EyQ-JWgX{Z8q;$zSfP5#?)iX~%l$5vIgh zYl%*{#$cj7OW@Lj4*KgLoTLJuUSHKf;mW)jOZgU^49_X!UCsKN@0xP>OL667Y224X_k7u$8qp_)ibOC%^+E`l1?ErJu z-5_C(WdW8CduMJAUP$U#yXUxXnKjv|PkF<&bO_A9XqR{{wk}isvYA&uq+d;p-wFg# zO0^Mywif~}dT(F6bVPz)q9f!#s@eWX@!VTk)*nJFrH@U_Pu8ZmAq&c56{}=Z2AZ>n zWlh=aHmUJF!wf=gSVIVyT-Qd<6K0_CZVsMm%PNnQCqMe;$1pfOKAVFlhMfAw?IX?z zGjGm!!wMd3uUU@obtZBuuAJ{(HFZA-evlQb%oMBSPXa`X3*QJq%EhM9QR@?CEltP3 zLm9Z=2|6=5!|RB}w;z5eK66A#+uk}xZHBx2vJ&T$V}`;66CuTgrkQ|&`J8C4F_@)w z{-ok8vf=gX7ioBZ9*Ih-L;Q)fizUxe6-^~tc>4sI`&7@wv}QRIO|1FzWtYDHR8H^5 z*I`{PZ6jW9w$|)rKWi`M$Xl&B{S5NNUvw4Oc!$!fRD6D%Ex(L#_cgT7YYXJW5^PC{ z0%RGYz6jruwXj|IO&gr+u0P~nR)h%kd-?L~FWCbu0$Bk|hp7GQgnrLWtioW!SY9hL z^7z8fveS9OeSL1X>obL->*tA!KcDAr|L9E2^@H|>(i>S=F?+IB%Osy(z2QzKdRg^v zm@DP`oe8ZCC1uL+osi*$?)y6T66#k6tZDDnCIo|Cy|T@_I~P8Ucjx1`U3_eV7c|1@ zqTbmNv;U2#s?&YLGO^QQaJo=vz}3LT|7!uj z#VFUt%Igvf5v%mqkHv`eK#6#g$2G}s2c3Chh?Fc!l2fFw{{dAk^A~DEhe@JvC-aoO zG@K*db3d2J@ zz4|dxhNt7Wuli1o!Brp@JUtgcjJISHG2)mCk5(2<4l=j@o&Joo_A#9HIu{$gPvt$m zO17k`(1qPw>;c5ALmdkwKtG*2DOwjdj)3)t{e`t(`pKzv?^V^1GLRd5eBn3|EAAQ|j zD#I%l0}^DZ8+>VDIB#BUj%BiQ*-;C>&kqa-W?I6MQoL6K{+0h2Hn#F^77TXPhwcQ# zBUcGJj>RA1#ducWXEitvTOwFeWHS=QW5obtDb+l*%a5Z6{gRk+!Acv})#> z*885Oz5lzcjX{FLNO`)|O5A@3@vBG(MxAGc8F(69uOjT-LKndDA~|DjhQeChp(6E9 zh?+@*yOr9i-uV|Yim0>JVsR0G=Vz|S@%Kuv>wIkY$|$-Q9-fQ1BHT^z+BW07Fi&V5 z(@m!|U>7+OJngLyTLt%HXF&znto8VABtPWH%LSc`vlF+kt-p(|uvk8JI;d-M@wM$C zbQHKzkhpExbuWRDN<*OyU{7Bv><_Zf&ZohzDr5`B`C+=dzHgZ3yXudWTC!o@f2Acv zrB=4s(SR%PmWqgJ{hxKo>7>v9N)669_h*^@X~oy%-N=f|Oezu(lv578S?5`Yqy)(Y z6yA##L#K<6!!MQgMTaBjy$Zf@1U%8RW^t1PwNJ=c$>c{jz2$IYEWP>uV!|gZ8n#<7 zy;Beb<9>E@Bj~y|c!p^Z7r#5Rf2hNrnk)E~Hjg-uq;r}0Wgz?rT^Dni(_sTLfeZsM zR`4-Hqt1i1=QfKp+;5o8qv{OMJh2}SoT0Z<4}p}K8B6vv*AnMLK+F$(XWCy>$0pT&!A+9C&cAfxQLcKP z^EjU0wO)4cIuak~*T;hM>KbmhNV0WGytmUF@c!5?B3)unz-gdz154~GP2p6pq`IlP z3-RMwZy}9ykNe6_uya0SOwmiY($FbppspU-j>+dJdEAkC9=!Wi@WbR8wRa>6ep;Sc zYeCTJbvp>y`ns-ba$xj*b>P^5swZIVKYcUlZJlS__ind`Qa261cC&w!4&k(q%LCFl zQx$`U&QeKO)~QG%$d!&gjoOkQiYsobF?b8-p)nV091qN}J5}$rTIx#E-sNd|WL5cmC6!eMNX->s8?VYbjr@LoRJlx?8%Y04qRKyR*0sCGZEPH7xjE zYsGo_u;VyLayI5vTXO^83I;wMeo@)2V|Tq=bhcZ5JYuJXXe0OZb#P6^sU8e!b=^+s z3%Ra~d1%|+-G((=q)NyL$O?uX(9L{9TZ%%Ph%9(~^LL)RX1p$7bZG+oH!}ZmCqUrs zP-x(%Do`K@C)jWHiaNwR$UQI34)>3@YUXEhueH}X&kLm}mRb~tYFQpqLR<&sej45| zT>Qm~dl&x7g4v8utgk|-QmlMeA>Fow0^= zGN@54ekRwO!1ZJt4&*k=aPia#{Ax%Ms!d$Z_jX3{a7X#D+#R}+d&SwS!8dQfsdtlP zxM13X{*o5hU5<^6Lbdb2`CTFZLP9dZs_wj3p~6CmD4lR%QjTEB$y2@S8BA8-NKo-L zvw;o7qFY6E{4W;RBpx=-lqJxY1^;O;zac(qN8qk3abDGO51d;Y}Aa`@4naDfk$PK%Sz zninxUb1-|p+J4wNRzrA~LR&hy92uVRhb+vO2jA?>=9I*3YC09HHN~aY&npg-Z^u*EbGmek2r_2K>pu)9KtH6q3#Uk6ojA+z1dhpL&t(7H8)3vMOO z&n5ocqeC8s&oC33u<~XN<(Pe_s+dV z$fM@(GGeIuhch^>p_r%nlmoqxyfcSB_`bs@(Hmm>=~R+w{rSuDLW9u5tiepzf(7Bg zZL74}8XU0S39tZullriFc0Y2TSx>OS-GQ$EmJNTFgD{fdj^Cwp0?$3ScdZ|QHkC~+ zTF`T$Xs!Zu2l%=*oeN~ql#&dyU!an)H!P*4Lt=nbQV^Ev4d5ft*)&6OjrshhR1iQ% zu$#Oz(4$k(-l-3!knX$S8Hbu48kA zIV>O-c$l}tH8QCyi#f7uD0@lItNDl^SxftUN><<;OM~(V?`M8iy@qtu@cOqdi=rRx zEQ9=l6DyzQ7k_YY6}BZEEJl(H%bBtRS~#inZukPuzuTqsbd(jX{xz?8&a5N|~^3 zoW3RoJ#R1d(|B~;sO2B?w||F1Uhggx5ARkgr<@kmgfA6V{nUhW>h-58?pYtaAUjw% zFi)rtq{2IWIW^)nW1ph-{Xo3Kg5Z_{i%F9O`S9zdhe_aR_ z;Or&3V6D^udDTzsL}FK55FwsAqUB29>l1q=xasnt--&l78taLe5LwCWy6@+GN&4re z>xSprk#yl&)~jfLnZIyo%68&i3|VlS&YUGN@ut9-I63QNjXc9NcFr5d2O3~%3Zqsk}K4x^m`Xazg>6lOsqZ((|Ohs^Kg3> zR?)V6za~*X0@tOGS0)a3W;o&ZlE3=KPtcWZue*n5;q!5aDZXTgshPX1Q}Lumf{dE# zHgdH!MKyVqm^)QFGnxel8lN&@yIAN#^6KzKJpN*x_|t+Sz0Q`{H|?Gr!xSPzDAc}Z zSlKm_wS3=3$wB2d5A43Q^pz=7ZRVv8wpmvni!;0(Yh_7RO)8)MBm|;?Vjp8Z_}v~s znVIRkn)z^c_W34k^?!j0753hf$yBox2wCppvalhV-^&EX)nMHL{c-5|hsQuqkyNC@ z!-3sW<`r8#k6$7yLSsZx#b#39*4#b1t2)nPng?aq@hD-TU0O4p7*DwNBf5{i>BHt{ zi}rZPeE%>r?z1d@RQ%OOXn?ZLhrA29&va|urmuh29|1dGWP)F6j=gh8b32QzKf`gq zSTl7o7<>!$6z`Uj(OLW9PQIk2ARfMqtm5)H+*M|*yjzA_G3M-$*cUP8OiLf8lRUP1 zJU6n-*zt;nM-4Uh3&be=L!8P8A>iGw10JcC+4X)+89G;N#&_Chv6ZI4A13K<&%JT1 zzRwy)dHso>964L3cA(4Ns`GeRbeQ3Tuatyz(#AW$ujeJ#a`C&_IUux9h*hfAv)?T> z&jP5(F-$Nh;)$}@Bh?mBar@78%&esS=%(AjI56MCVTEfgk|+IG z+GCblaJ?!XQ3_1*)coHWS=E9B>f(n1ve6*6fF4Rs>J?+bXSIz(Oz#5`@x`F}ml~t5 z)1*W-OGCle1fsXcHx0j!9SzWh#CW@bR%Bs;;YLGYL85}A-9LJ}rMXh#UYjxH8&m{n zf~A-VZo@HW$8iG2xG|!9;sQ=dQWkHkUDDQ5XI~`>SWdQp9{OFbyGbT!?Yw(kdO#nm zE8Z#@WB1n8g zV8;31LHXMsLp>_2vrO$LS2)Bb9~HuaJ4C7LiiApFLYk>=s3kg%ch82Ui$N|Zjcw7^ zKOmQrFC}c1fj<&m>sLpz6G;7bgT4An$}wgcwV&ZruoBTKZw-U`ObXwFj7vp|#BIz&<{qZj_;6m6`{0|dj zIN66u$Vq61;AC#V`IEf3DAL#Bl|<;(8GZ30#p6Hi6AKcO?LqDPe@4@~vFIO{jzNWl zwb7yrOnUQg?7cmNt(F0^731H*Vk=Q*ruM2ABzXrc+fl286p}8zrS_A90zjUMNd=D2 zP0n4eDyGX%(%i!qOlKKqe>ZKRV}O78yvLX;QQY1q$!4|+0dv|~B{2t#9`6WNWVwZP zM}v%qbDvxv@Tz(J%{Di${;e$40*eTOQcB80Sx6lT>i0@)_5t%PYVuPDY(8sy|8`e3 zANpz?MZZbk9NZv+FRTI5dm3!QFYV3UA$12RL!x(XmnU}2!iDUr%Xju>E=-bdBQn!$m)Pg9FB~^> zIHhDqd3M;+fxh|)Y8d@RE@GoNp22sZ<1Xask}R))ihhKp{L z>x`r(H(E1I;dIi8Xg;CreX6mFZ8|}S#O*lqq)m-gxwZ~;NAL;fpDdw9mERXgrUFWZ zls^ZsCVs1jY*HPqQ9@>X>-Y_m7q|`FFdaT(iQGZ@4@!~+c=FhGl}F_0q3rF1o5>g+ z<&wA_%(xFUgSW%&8H1zoHaG8;OFn8#1$?(;yT zy8I%}AMR1I{lZhaGp)k}LeANhf{@y6FPUbMM%$oMjz>&x&BqMsHoQ|xg%PF&f%1o( z**s6pzH#LK#_`4#?Cngvz?GBGpoCCZB61}r8?&&W}w3bXB(-KYc8gHV_%Ut{Nt4pskIyp+I6`km$7IevC>aO4%E8q03kh z2?TM3#pDZ5TO*}|x%){D4RvTf9K5D5(|oFYWmGz|Yshqy8F&k;_FHE>3)Um>DAOYl zbaXvt$E$>dZtIzl*z1`R$?HDTjNE?28v0HyXQEZ|_xR@zj*@e5syI&4TT$F2Ioq_f z#TFp)Z?J@45YY$%J@lVKz=dY<5YuUZkH0rF5&EB zvd1OG6)O`JCs~gwT-H{9FOd*^_ndvBtUD>HqU6}YeipRx`woNvX`b!IU6VJ5FY4Xz zx!mrdcVlO6XI%!B9vGFAKOnmEBl$R|JH`x9OP4#~E>@P}f8rsET#eJ%5zE*d#9!|O zv%yeh5EX1;__m4Ex62)pv%lJ_zf&=s(V?B{tJi-`4=@C%8b3=9W+j-mp~M|0K(1a; zBxa|x5WDLUXKlXun?5tJ)&(F7@!G;`B9AfRgHC?7(SRa5LJ+ccMJ9vvh+ajR%Y|#E z7Gj4RkUl{ukl`AK#IR~e;==DssU4B>1-x#e_utL*lsw`k#ZY+tQLTyY3;XT{4-y;h zZXWad{Y=3+K4BzI87-y=u>G{MijE!G#7h{*aA6Mst`aUr*o2UqZ}+AxVN08TBgg8|^Nzlml*qZSOucDadKsnW(_#L~+2ZEb zt>=CXz8>t2CGDyRapvqDJu<%^S*Bj3ZV#ks=g?z84prd|2{vAtsCv$LXb6Ba_3nRP zXH>p6C0(J`v>9bp3SpaupFM{xTHn|}F-FiB@_VXF&Y698fML!mSuXK!ps@le@$BQS zfJwQ$z~eaoI8dA}c1KKZxm_z@SUybI8-W5w<4jSy9! zN{gb4egirO0K-cTyp3M*W{M%{o2!5B&3xS(JO#NWXc-;K!S&J7XRm&hDvn;2?ya<*dc89y2X}uew|FA;yoERaQd~`6HMt>0#s|na(~+u@vkgFZz)7WMqVC z(NyslXz7cy}R+@?guaKh906^@{4HSY@|2z4&6oMT{U) z4!RYTQl*;Xf(|T5FEs)H5Ma0NFuADmh0R$6BhBCvU8!TbYNGbZcuzSlcLWi^~A=zy#WZh zeP)oxA;S{AUPwa}OK*O!{;BwlZ9OyKFB?@6lJHjn`^ltRs?-JEHm+=1;077CPLi`$uBsLqy}^Lmkh)on?Sp}4QMa*Jtkc4&*)qHs9({V-LcaXR24>2 zycG_%ebj8MKM8)D%W(y6Ra^{-M3B(|f?GBjl-Q@KXerSojdm z<(|E>t{c%?Em3VK1NqWsrwBi0E_~WqY@Dt8k**JA#uE44pNU-^+pr<8c#~O8g%g?F z<8Z!3&IitE@t=h!IP(pA{Vtx(cfp#9_RW{{_aXq|v_m2(2wC?BlVvr8gaiM%$sHjr z@^fR8pR~&Pvl4}M=OYcDI>#Ba+eySZ%hHELh#t9LP>XaCa#3Qv+=8<2wJKpEl+@uL znb(e%nxg2{cj=Kwl2wti&`ok8;`2!5jEhnYC02^*Ts*s4ZcwxCxzsfLUTVD)!1%_ zDA*;H&L}^KW#I(x@le(Lt(gJj^%BWvH%ozlIBe6+u1~ z0$KapvE5F^i*sF(JBbf}URaV9B-H~w`ojWCjYHLhEH8rk1ExO0Bh{+n$q47e0~F3z z+GhWZX1t!XmKbMAti9Is-m76Jq<=5*v-_G3@M+s*MJ7;ouK1rM@qSr!!I~6{GC1A} z+W$djvwecV=_I_U_GC)HM@rvuLmRKlgMP_;NV);$+3Gc{a1TVOA4!oN3AzOsj+29+M$cY!l)qq zm*fNDPXBd$oZjsjfyMGaR}yoy_QFa_cGf!nRpf`nY;_1ts3_vuZcYWmLK!kdtGWX; zQ@kcCv&A+UGlVB6dGt5Kg3>01@d=x%~k%CiF%0iYsWSTZ>B~a6nYU^H)_&~U^-&JPI_W8tqMvG1J zRf{H53x3n#I7gfDk$d0BZJJKiA99jG{PdD9D3W#`x>VWEL#l-88yXM_&;QJ=^0s&_ zqGTrEgYWJ8=EgVWDHv+451Kq_^$g7DD$qhNmYe?xu?Y_`n#jPJz{7FCHbfuhgqjJ& zx0@Zc+h$>{zsvi0XGmf`PW+Lkmlm(s6Y$xD%|&d@z@27BEAQe}nRosS{Oa%?1&)H1 zvehlFXym))Y8aqt)U1%j#I@J2E4b_M^wu@>(WnOPF)hZ@YuX<09G&?bAL}AvZIc;x zvm}Nb}cMO!D@}#FFC4TZUX2dximIC z!))JZ)qDqQP`ZS6_%g42*3q*1#GE?W=oDJkU-2sClujI&J7#^BNSyIx6yl0n<+7Mh zuHKL#q@Y%hOQ*&4zI@&|2Syd8N4*b64lU@N%JG#YJH9#cMbOIEN zGrsp$6>p$e%ipIXrn+QwLUpat|0>h{StxF-xts!~Gi9w(oReIBL_*s?KD=Ssa}+%sr0~Ou<;~C)M?)O$QrLjHJf9)d(^FB z-3BY^Q(E4{-ZgQ_eJ9*Evkj1xF{!};k+4FtXm?kAlhd+`3OM?06w~^x@D~58(8?p1 zPl3HG;C1|vL*l~NW}IIC8C6dR&am^i+Bn8^2G#|KouxR z$G?@;k<=^{Ip`*#o_WhS?5D4MY;cN&4Plr*i?m|-dUaTA-_{yR5EX#D+O(glW*F*U zYlI+9i4%pLO6{yMHnXFm&n#i%$v3L$dt`a{hR<8~OW4oOc3Bl9Sy|4WyBf2AFUl$P zPCnob%~UsjE*Fx1B<}ovR?K3o2_sex)Xz{7w-~wiyNtQUYv#3!j(HlSpK$Xbz45;n z9^Gm!cabrltf75cGEJ7{-BtQ|jOOWkb=YxqDRr-@b{v5e))HIwf_iLh^=Ntttd|_O zh5MbKZ0;s{gVKg|8-0W?(^)3;3|i`I06VTD&;7DZjH08$ApYg)ELm@?-x-dFd6yR< zPgZDJuO#Pi*)8zRS+f0|i3R%EM_(WgcnBDTdEQBvrT!POPpjC@pxSLahnrPWiyjKh zs+1FYXZnSVfOtVgR^Rl9(hJs|m^*WZqoewU{yJ#s89Ya;OnLCjZI{orYyO#fhZ{y1 zPFw4ff@VUsPFkqdASIwW|h85Y@-u+=;ioDv6uf>^Pf45RrnmCTWU)!L!x7N=lZGirhCg=M*GbK5s4yH1njPDkoJ|-?(UVcH^;i`)V;l&8|h`V;cwS|!ha@_ zfDP;ij9uvGC#hE*$t(KzrPgik4eHY*TzgTEf`0smVV zL2$JYqe2%0pq&p|uuFN`iMj1Ecp?qwweevHU~w%<-(KhRHBw_v69Pr%{}`p?s7NNV zndrY**IIktJLAoM-Papm1DYHZ0@=TdD1IptoA#-oWH4%}$TBf1)l8|0q)>b5<5{cy zUt8s@rYbL!F_}a6j{0Xc5(OWD%_n823Lq-0Z%Jle_`~}2%5clb%>-f8D&-rZGjcUg z2<%~Oy%pkOh)$Uo{~Smd?G);|cn}Jv#G%9^U_o4w{gpx?=j2{M`x;p{(m~EmJd!%WaT4liZyNmI@>S}(-&jT|8abFT^TP-^*@Jq@YtNSOp^4>&(wPuk-$YgBBw|6;) zzZw!!r$3JeP^cHiY1f?7Cs{a;j&Yl+KXSXd<8D{nE+D#W`v;@(@a&uju)pp=oE2A0 z$J|eQQ_u60;-WZC!HqT$qX?Q;XqZ23(hmv)?a_0GS6$s(Rn(x1T|9*O=nkj?s;kf4uchkqain&Yn+;|X`a_58Lg3VLX0>rdrC9`ACI>( zZIUFXeA)|ESDwGr44Lr>B(tt-aYn7|S{MUMT9Os%brt=+yo*N-`>ev06)`#)1~o}~ z1nqne0%Et)6IVKiR%xlCx3Z^F25WWTx7$DQ=SMIvE<$z3@NB#Rl?G%!+ROUKn1K@{ zL`X?$>3${0lbM?o_Xrd0BfU~|xD0GxsJsF<3=lO67#g7w{naX_IhAioEx^z!910F_ zr0=H<)axmTgjFLoiS<_V=reiojL(v?6dxv1pY7J{p3`SXT|O|xT>Ph%;-0!tOhS}# z_8s$VUf5*eWR$DYBHv?#T26Kt|8@DOBJGb@<-%^wHN|{i|9|}c!P>1We-pcdU*FLH z^)^fHU?&%zNRLkBjn&OQTx@Sy`9|Ud%Zm2Kgc$z%Rd1BF%y3=Vq*0pxCf`mOmWm&jv5J3^}SRY8bjhj#-B=b0|v z3l>%N8YSD+yxsG36*2X!K~%kJDABsbV(1qi{4S~JM9+DwHcpW8jg6a&P~Lq0peLnx z;Lvjl{x%FtQ&1(95ncvwshIrEWWlVL?$i*281tou4-2UFzQ?W=vF!r@F+&5(TYnxp z1yzuiRFz~Fvp^|LB&ZIKmFM@RG0_mBP&kHT!SV78(J-g{fV4O+q&sg{^1=?I8Zx)J zo7zOw-F-q0QRbgswvCYjP>;-XN^EBYjb>PnZ`P`Iy9c7+)8(Bqp2q8q1{4msL5}075tQP(h*g}_h+?^Mz<=+aJQ6CyrJ~}b z!Bs5a6Ef*=r?A4PfF-B$?5pzN#iw-s6GQ2SC3+lO@yb*OeBU;4)Wv3bkE}w%hyNMt zOwA+53qCwr(Nn^5`0}$-HJoo zt2Aro-`m^+^1E8b4Dv4Ckk5CzYUm%C=WR982wKIwBL37mPnOO1_j7RR0{>*$&wRcTu<7n{W_o@(1>pEr%U%P2C4 zoGuErS&iZwm&K*#zcEX!^;qe#FVcJD?PYEp?T5>BnTRF38thK%PrVoIia)f7eN^3J`}O@&Jm$!V}k)ryJ3E-hM4dXXIS@1G$J%0mvP;6+~5KWl@~x%*#Q2@*Ec zK1=#e$qyft9+rK3&qn95V2GAaYtj1xKCHh{ylWd@w?|5~00o5svv=Y?J)UO+SO?$+ zZ=9uHNjSA+s51Z5!+D&UlN_l%LYI~|4xJ+eyo;?aBQK}DH$OPth}P52!BPV4h@Ql1 zhKhxQxr<)r@gCGYiLCgeN}C+C_WcV=KtBCNhFi|}s;u-Gl5df^gyK+U4yrv52gwBE z1OiY~!Jy@=d6?Ee+E_?X`9?}J=WL`V+*`?{>mfMrmP_P(nQ#2)zCrP!LqI}w(RW7d zzARO5B=2mZ{APJ@)hN`j&3T~|*npKm1I(I-(7(eNuFlWgzPU#@>JoUk7>#vWxr$P* zOc^Xxf=Q~6Ip-r!Q<42^^^2{J2JLd*tNsAmKzmZG-0h}CMYT_3rPQGqa^;p-z*=b( zLe#Du!rN0y@Yb(Dz-{T*YlJPIn&Knazclx+n99p96gkadReC=S$Zb#Oq4^fOg#9XU z`I)ukt-ajhRmFi)n_bE4vdzEDSclrySYjN)FHy;JYl8o=o~?eAkSX{6W<UeCI%nM>+~`AvZoy;k|`GnAG+e7&xMEK0q%Oyq4b76(rPDy}*aXNMvXhzO)Y zO|yXIVkR2=Y$dCG!AXMtV7ZGG8>YFN4!e7syWDUfq{EcOsipq5%RA>6Y@%=Z50h&l zob8Lk!9O8g|C$l)$0KdM3+f+r&#mWissFlDXgu)%#j*6#%9p-vdV}Pp(}3N|s(Kdx>0N>v;7mH_Sf0pGXr( z<=}s#qr`(sOrUiQ@cfmex(aRC3uE;WXU)Q=g7z5dfGzVGKMe%2oM!Baim^h zYTS=ibgZZ+FkRq;m7kagQY3bzYl+6@z3HKzu z{6K{s`2n|RfX3=kKw{vl?D&S|#>*Mc^x%f1%4*wSKk|%^sRLPH~r9*+ZJS@>p9RQUE88^;~~$ zA=PF=IgCZ3Gum|EK=5jkfS2fe!=^16w)T$)bofsio{kA#6m;x|llbHLtD=XdU)F2h z(71O}^ze+B5ZUi%*kc3*1?yJ{9|;P2Tut)ct#tB=IUf~2Y@qr$OnYNiX>pnJBQ7+$ zSuUnN#ZP-XgR8^XR#@E~%*O;_H_Hfrr#9YkWiB1>5<;0=HbAs7o*Ea?IH7QFjLRTX zkWr?a7(=E0bKS+sXLS_D3rYBH;v%K?+K4ibL$WbX1r$nF$c9PXv%ijC;UaA;S=Mlt zqB4tY9-j!qIe+(*7QWa9N9UULDS}ePmG)2l0emm$+7wzJg|6*VISHokHAmnoN(!AN?_2c5cTE#QGS{Z1V&)SCv4?3)ljG8ph2+P&7C8f zJ3Qm5(;y5oK7ql^NkK#0G`?z->uJ!Pamw~@y32hO7QZOBh@FWnEeOHs%fAZ{{yv+# z6Ry_t_{I8JHu^;VhmR|-Bur8t2yI{cEnA{2sr26!GIZHQJQA48XmLoDxTh4r^^&4B zfwtyz^mpuU$MD*$u_${_?-6pJOxD=Z(EjO*5bUQ8aZQLT5VPr5#qOMevCmUBk0S@X zgq)_;S=UEGrIjHjG@!rr(FE3Sq~inwR8AiW5rX;ji`8hfDEd^jSjxBwduTV#fUyV- zI@j>L$i_V)(e-MkMAy1GsoRozbcUGUw#!k*%vA?K+O`1^fTQ0??0&pn*z_&rhzW4+Ir`eB0Vi&w!8H-xZ{M5Hvb6&~=$kf10i%bJ6aCERQ9vJ>Jt|9m(ZhssmZ1^tI`>V;f#C~wN8jRW7LjP zj+~yVIPoT5z6I5Y#M}F|`OvnHxLSXlQCgQ=F~70dJA&NyJp<9XT+hn22&`z82OaEd zF)?N%@&-eSAa+`nH6@ApnTZe{-ZXIgDCc1)(Q-VY((0!dKbq~nCsb(dt=e>C;>)M^ zRgbZu7M>9Q)#|SW?eN4D6N#UyNXX>Srw?yA_EL&!?P-&(!M;E*$hbs`t3E*&?oa&2 zTZzlp4N8t#-1ztDC2qG5&)|!10z3Ivcu2lW%;#$3DW|x7SJS-v0FMkl)bExReK{0Y z+b@e=99^o_%S~qSi47s)s+BDh9RHwsz>qg{3;^JDbeIlxnCKA_IT@l~%h-(X(eiG1 zaFO2qHrR^uTH6i0-=P)kFDBs{jkB9f8#xcP7r?_Rk`M^Gb~QC|B+rmqFMul-!PK2w ze%G@_ySeEks24ukEngd`&bFrN16#s?QZ>Pdq8&8OvOmAIo@3@~?a>iZk|20|tSYmD z&1DZ4aJQ4GepIsNDeWcAb>yq&J$d-oB##kpHK*6EZyQK8TwgQ@ReeiU@NrFw_16S{ z>B6!791Iry3LEHIr7kM2ADIt^qd%WsT6rI?6^EzVjHJU|y=v~RGc)gHZ(N{q@+~}` zJL<~cZII99eP0q*$I{_gC3Qq)Dev*Io32>NvU*142q#Ps-j%SqMi#lnXP5K$vnW-? zGa?u`S_CzK4$3?Jy8uSBbgGpA4N)^|*%aZ)cT=R*dztm|aRqngXue?M1an=CIbEL$ zRgBZg=vW9MD@}0fxpE{LW3w4Nug|v#y|i(^)86Fgo)EjRT&2yZV|y`3@zk)9&fyv( zXQQ8%Qg0{K@r}%s3?*iVa8|&jiM2J1V-861dBpATy=jn3hM{Aeq7F@>dOL+a;1}!? z0(;~=m+-unRoP?%R;*iRcWeKp&Q!HWzcg%iT29i41PW)2+*<3mk>b5N}6^e?4N@(9q`6YT~nuf zl=9Bfwr?lGWojbYt4xZlqDqFhGN&4hW;^1Vx4mn?IjBflWSu>mk0GAByLT&dEg{6| z!vT^Cdmq$Fe3@mvTNV_cm5(o^8KY_)kypW`J7VIe*z=Sbym%i`gj|sV4iGI-2S9O%U`y+O{J-I7*R}uC%9V#^}P8GHO?LY=L zX>l$u%2mpuWWuA2#G(nfE?7BXETaxFdx$yf#n9~knrIQ#14`QOe8MRU6rhouV)RjZ z*Yh>s3t9DVLv@0JcXKb0zfc31AvlczHgB-Ti&n2&Te{VEdzR+Fq} zCZb2*5z+xlOAgMbvV-iCvu9{VX-w1VA%$&*#ldd?JJBR9{`QdRML&DM&+G3ZG?KYB zck$i98H?+fp%XAff9eG^K_kS=TEZ2%^0G78vxf4H)Q2e8(tUenCrHw>StotZ0rU zrs}JM|EETn89yemPZQ|0QeyYUg!o+fyX*fRXq>1*kyX=F@nZ#h?-+y6xrNR{D)r%b zfb_>%|4dMrugT-CfTBRU`VAHmF_#Yok^CzH5e8M2MyksR1Y3Zf-Vxkoj~lTGMR%C_ z3oxX=b_|8LOUMQ+-ndy3tJ2x7K3dA{F2`^Z=V_ zb8No6pHWUau<-Y}Q=u)zdx!MRE3z+~15xe0R2H&922^nGBwl6FpC@Pb{ji4IzERHKEh_LU8S-*eIvv5_AOQ!iZ@;=N8aYp{}fX(H2fqehKjL8ScI zD%#XGJCh~exBZB@a8tE%J;|n=!kw#|L)o=-53h%_pK76y_~{wWqLoAty)~0w^YVi-JYcO1`yoHB9QYcNC{g?h-w_uuZulOm!9V_Df|iFBFE3_5-?|q zwCB<(RH!d%29SR-YZLj?wM8d<>ob=CL*7|el_YeITpk`!#JCu^_R1iik$~^6(3VGJ z$Etyt=!!cX@fVx`F((u|l+o%t;BGIwgqsO_ULeXgv(XKb>t6vGL=MYjv=i@J2wKu_ zaCSgRIKy-jo0kOs^`*`xnom7;P)9?D0Zd&4OV3C?x6<|?<|3hiDcMBQwYGRUt#c80 zrki(c-1M|NKL~&L{;?R~T?A?RV#PbWrOwa z-p<2JAgzgl*bKM}&EuPsRRkZHCMDHJ51@S zr}wsN884{Lv#dKsYV%O-{SvCj?oGo#F3&|nZRoPa5?57iq8sD0AEDL$*8ce6Mt7(U zbP%C1Iyi{Aj{+i7#j>3mDsFhNhJb#@cR-~}F%*HwT@3hX=wAq4nJ8D`fL3w86_Ugn zd^vQmYFe?zc;VYC>rLfcg2p;l53u|%6dbs4OnpGvUewHr9Q4k&l~Hv6||H#t=qu5li`@*bYHJw3xnJye}4Gg z=601YF|=N-cj|Z0cAPq#Ce%{RF`1tudnfR_NOi|Ym%pocF%*QYudN3#SlN{SzmXTS z9xrCr+oJ3VsnXBH1CY+z5f8gTUzM*Vz^19I=)r_SuuZppn`Pm2(HemyoY}Tnj3dC| zsO5t02mPQW{EgxAv-UZF=%>DK0Uoiq;J>d@{jYuHok{II$VGpe(dMvJ&<_idIFEL^ z8*+ZuJPfm-5!FIX%D}n|uDM`#t)pY$J8$}WxJU|T~a#A}^1!+d-rxrAW;)mh4R81`K2VV7aI4`R*LN=R)+atEv*=yKy2U)nk7=PfiAubhNr`w8F*U`> zT$uvj#Kzj_RMa9Sn6OSs*!JbIaNlkW-;eKd^B)V!SKcePpcc`q17`U%&|icb@-}RKI0x2EJk%4E zS${5|J(HWUc&-ik4LT>EhVN5j<#G7Twt#N5hsOt_~kDH0k#yG7wxgZR&{Fnj$dT^%D=WKJtsbN7lWNIhN)TP&tG7; z0V%7Ocq_7ju*NZ8{y$MPd>%yACiOdu4r`GH6z$0Yo}9>FHw`=C(60K=6@Nr9%bVj| z5&TTreEQ|i@E^a0iX+sF8N*MtM!WPyJq8CAZn-+TQ`jBViyvOkKTU<<@ITOVkdE?C zy=UWOrWdi4?SX{J2BO(SZSN&Gy<#+n(l`w&Avo#+WznfLgST{L{D4SX1vGHi&{ssYprb7o|f$xpIVQ&J&-{ec#Xh;a_tc>x}D6 zc$w|R;CQzXJ9g@KebdzWP6O4;Sh%)!mS+2%@( z3G#?&x03(=1ApR_cOlsMsh)Kt)6ENAw?8bi{zp_qp3h(ZDhR0%KK1(CwISM_?wh9_ zKxAfg#H|dk%|g|eUUC8Z;vtep*0<$Xh`(^3RrzJtgJo~VPPz$Mf!m2(@>S<+5|rk{ zMZN*RKP+I~Nb_#oW9YoVgEijJ1s@Ms=^r5fbBlz_R%dLnU`7TY101tg20eNRha7Z6 z*U`<7!M*m}51!bNOhdd>fmgB=721R% z&e!`S>AxLcLYf_795Hyrsbau8^K4qPOl;G+NdAsfKBXa_A zk=TzoN9lC!2*4yvYIc}&cRW?Y<@tqPDHVQvFS_8=my34)v+MV~dYAj<_eyAjxLSXV zDoJU1Y#*CPss2`qbMIr$Wo4?&P;SnU=9w_5(v*Ih=tL>Inmm!$(31a|G4?N?)14b= z&5MGjrx5HRsYCzI3!tJAAKPFrNS=+mK5aX?z{iNI(*uAl@~InueG4 zp;gJ)XnE|%_3lH1a*W4Z0|fDBH&_X5LjK&i zL3OFi+;?C<-L=RV#wn=kjD)1eL@qWow0!DXP;E;5*d(ZcDVx!?Zkhh{I}3li>&+_% z>42)s?!O}b=gLnDTir^R_^o<8D##ZzEZ;*KbFn_ecOb2v#72&-x?cg}|A^Y4{ZF8h zzh}b)upjuG9GabV)_$umcm{8|ho@n`uJ%!H_w;i*={4<%cbHAM9)?k}G6hMeSrQf8 z2)hfycBro#){I0T{+AA5gzUxa-eh0Y3g<_%dsr>IBuTEGjoKnhS8K>dqn7ny9g0 z7RVMXnu;ZnrJ_0&>ERSyumfULAK8#*Ll5jRVv1gm0tt+lzm%N~Np{CYx93gS5eGWO zS>j4|{6olM;dPK>;GPufFSnT0uAY-?1a&~H<)tcg{ZjCzOjRyANwc+4>CC%sm`Tow z4EQ^4zu_piA!to!OpMAQbLJ-V=!}j0#PyvG0lh>Ja}m@Ei=bWQLq?(2rx8&{Dg zFjXOZRuQ7rdUiqfw>F<)vRB}|(!IzWEQ3`Pgn>B$r%EgY)@`@^Y{byj%xHUFXi7sL029!byl$p zb;eV}=bRt5lAy_`Q^i$&T)*cciAzpwPH*B>vb8q*3A@V>x+~=8W*o(DK+-){_P~)L zrX$X<4dtJXmOPYAv!4R=9NiBEo&S1;dI+Anytg}x7v+B$^d-=uTG2>FPAwr$|Am;D zz0^X7f{mb}L{{L07gRX-(X)|WiM4!)lM7BWZA^JiFLy)FWhT<)-3UvLG9b(>bZU>&e`O=A89+ z47Ayt5SxSQ6;*5dv`AbULx}>lh{mWDz_myA>rDQRt?>t>C)j9ph+|;A4~RW$_`5gY z%{%5iMYJ!G14IKHh`O^P)a8R3*CAN%q4r5e^@qbuJ@Pb9*pc^4F)_zsqR;Hd!ctGM zK>-Td;*#N{2+dzpc%P-9lMA#sY#E1!Z3blrJt`I4_}!qP)faMw6kwQ}e#^XBUBEV2 zuHm=VRW}|h_e+PO#*l`LvF0@Iji*kr%mv>wHepnrpv7mJfoH;NgHxn34-fiTj;|Y} ziPl_QCFV_qb8h>Io=QE0p*nAg%K%`hUzpLXbmLGYa5Xv02zo>2`mKTh! zBW~sko)~H5eJ$4aQ=M-!7|j6|Q%_CUNBGrT$LRUhkj*09pRdPK<^X%dY8WMd(Lx^_ z1u|xPgtk~?@)ARd5mON}?dBJe|Jniej$PhXcb8DrPB_hsZM2Ek{-k05+J!t613GUx zpB?3$2OKI-ANCm1Xl?x}5?3tX|j6Rtj8EaIG0Yx8W2O8kZAelGqd(=XE28*sJO(It0G2$Z7pDi^w#&EtEFS;YK);kYKA)!a2kIwyHIBYA--2xQMU8CgXzjxmYw zqc*T7P1R|&U+kj*I175Br>x#-)&P0)qBf$Le=x)~6NXWMuXYMG3#80#Y83&W8aRT0 zG@v@DgNh!KT4&gS&Qz!iu$K+jGiI!AvZWKwk$dvA=iB3QQB0a>O^LExStfM)*uPKN z7@E8_dYg7q>)n&>WU0<{#%^RmWz-=@Mubu=z->JVH6U}GSNK{L`q5~1S|_`a{pRBb z`HBcdbppDsqkGfyc-7fcDqs}!;t@iI+P@2cL;cPRfHezc0o@L4?#xw*TrvDR2RmnL zu*5Olm5i_j89S9v$+gz2=4!RFt`(p+U~lu^YEaar8gwjBjW0b1&6tVAjBdifZBuQVI~OEkA1+x2XJ~cb1VY#lSfmcj@vnW|L1{W7fJ2=nLq&3*ha_2e!A?C3{kdaUMN7W6QrL{UAZvDdLfpaPe>W`;CJWD2zff;8 zEI)Zxa5i)G`ix+6VMfyST8`n6r?6sJ$d^5hiicqC^}J7n&Z^KL&LYNY--UmjP5K*v zEYMfVn|W1XxuS0YeV~NF5_yx%2LWnr$mX(Zv%b&%6Y8cXK*6K;eYu&k(B)(#Wh6%( zv{#?Bn*OfrIK~+H`3SF!hZks5H^KpZ{99x-v{5nmbXKOAu=T}Mw)&7-umbBJ$aS7W z3T^a!L?S0xdF8TTO6Ydw9kF{y@Z3Pn?ob(vn~tvEf^gV=Ih73dE4X3diJo;580$O$ z2J|bzGfT99Mo2P`6gg_k4#FYWl^Zb$sp?E3LOjptH!I~kViiA?jf&LCp3@QD*Y6Ru zo8DKt>3N19-iq>OT%(*y%W#o?lR|u2o$ImMFhUHdJE%ikM0C9hshhed%@j;>hYu}R zRh#OXDM?7-RUL0HVSnhEaQCP{vEfd=1%TbpyO2ax=h!bs1rwyBT4*mDWHGe1u6dbh{M8~u%Q*+nG|O=K43x&5zdnay?Ge4|A#tSAoh{h<34#$sqj{rrRuH54LD)+)*wN{ zIP}S#{|3AGn=yEJT)Tr5=--vhK?u|eWUs|FHT${W(5o=7-2bd1;(aCRfZ)tRKqmYU z-r59uV!E!FS=1_!zqhHSQn*1n`hpMzpt`?mstPbZrdYFGqbJoR4N&z(wymkU&uaws zTk|`I(fn=>Wo83QAvYA5bmNrY8u_`}$_SmV5F~>(M_;;)u-}5e#np5XoW42MmP#`Y z-Jv%~kVxc)xN0;HlRcQ`gOf@fN3@Ln-@=}m>q^ORPRcz#}(1kTM-X?Pd;Xk|@ zy1=AcbCRKFd_7>Du9KJ}Mwa^lv{srLHy4LzW|C#m!g$BKIB^WT$M~2vULH%XAsrSQ z7jX7Xe*lv>INGNaMpB|rK-WC0DCbI|<+vhfm>Rq@Xvv0rbK##`=d@|&#RC{jaA{S zUYffH3?se=#ibh?H{K&U~ys~H!>T;%jvsM~ynG^E#FJo=3B z`VhczcTvKjQSk+1fa_F$&fI8R241+u3DvAIUhci-@$X6+9KM2{`@uv^|BxD5kz=nY zqSIP2AK#`C=;{4}ZGr6vD#7O{wDPP0Yai}Xnp@!rd&bv8_t)I~e@P`fu*a#5CM>Z% zS|9jv&1FsGvd{TP<)c%{e_~P8*!w-1`XaxOsvhjyQUe3uLFnRhf?AzbN;`#kJgAv% zt@uQ9svO!{7f1DB?~m*U)c<^v_cjgK7}=Vnva2G*ci-mdZxM^Iwq^p%({*;ISUEmN zvhBPloy3+^lJ|&Rm_AtEGV{ZDccAOxf=}Fl@ErKcZ3NFU3bq46nKz0{l_?i?|j-qR0cI`T* zlyKgu^+S=G2-IfWJ+0|&c6Bsk5;NiV?~|H7dp>*9Syqw3RIF*z7Y5OAVQ6Z4run_S zxb3z;-QZsL@S{eHSb`}SAY3!%F2IBA=a3iFPv>VEKt{aAcH7AbP!n@ zZvWEWFBOlp1lnag`pjmz|F-Lp9+?NuPHSh5sNTK^qoOhhnv)Y#Z9|YfA^Ay7D$lGs zLGqKTcJjyD4|pBTY;F*;m4Y+c02i~ zQO`35t9o+;_XYIJ!N}nga$4zOB`*eSU=@$c{oD4btZpZ>iw<(GevV5#KlA245bq;h zz68YxqIxuryC=Qo+Jf0vC~qtZP+i|i-#p}e%6WfFy{+RK?x>@gj!YxpDi|#H>~vAq zJfvFR@Y^A1>$k7(Fc-U!R6Dp~bL5}>$a%6dge5wSc(wmy`Kez9K@m%;=)9I{*ZT3` zZSold*{MJja)^=Nzjs|f?$AR+y4pOVT=aRoHS_($Pt0y+UphBF$ir4n5?G+-GyX$w z315>K|2xN;za#KwlCX!!N-cZuEPIQ+6Zkn@lTQNN?&MYP0MezH|FSl5dM^JLseGiE zF%B4?0W$q-8plb&9<8#5H^}JIuuIv$}xz>&C`ua~mK^Hj^!>6zw?= zs~Xqd_rqeRP3`*piR#|R8uZdz{k+t|;`CrFM2_Xp@0;r9xjM0p#oUS6B&gUE*lubZ z$@-p|TcUQ`R>iYUSNk5_@2ATuVKLY%3zt0fH2pOas||=QA{u_Gw|Bq-IDge)fSDIx zpleK$`>a(gQAGUZ_5~POV;EPPbT2G4itgaYbGYGUgU@cLzD6t$nB}7Pi59jz+xt%l zIL?re79rORY)NpzeK32_y(|!1X=;yYOjA%EUtvNc8_33wvmJ-8%8PY40!{`F)=SH$ zR25;K{Tcy_;J?%l9oc(uKffF4ZZyttaGm(-c4iGYXF)NOB2ympM`Om(?4P1vU={wn z5qk7VC1@~{n$N#|zpsAE3z6Jp7B?b;8Ua}=-a)?=mSB&ica8>)Qb}ajpbfOv1n@f< zj8AoxfLBDC0Y0Lg92DJOnXJHwCz6aW;w+A+Q1?q_9@BI+#En%+Z0h>Bl?2=0* z#n>)Pg)t;4>26Ism8}&RBPX4VH#2x$Q!Z*e-B#(-acK^wn6+ZhS)Hy|MyPI-TknKA= zavIGhDnODc>oY)*caed?au`KvqE5(N)=~FKG2V9P?r+x8&+urN1(|3{g@K=|>m(J` z=p0)Ou2ssUR&#Avfe6&Y)?-PqM;Fd&Tz!ICi5gQ&L5kEITK;&DUhwzlkV7A_5&6K! z&1F*R7W8K+M}{$jsl;$LDIJf;QpqlIT+Vc1W-_C(8w}qE9#{Vx z8SK$_h2t+>fEdq#CQ|uHcwo~KsLP^s8=_Oe< zu*6aw5D8X0!hx<=U+>cb`puC-di+#LCpR3rTQM5K-cHFhawfP z;by0TB2UGvxw{_!mn5 zk(&3zoXT2z+S3uCHJsY&P@O_00yOd!uWLG6*oKCsaPR-s(0Zl$(po6|R_towm->?Q zq`*IdOs_Gm`SZWd=86k4QrKt5PwfYNq2y(&cO`JG(o=U7%2fD`_roWtv8nMZEY;F- zz1LJnQLD_pUaQ9Er%VDxyrd29iR7sI`H8W)#7{I(^PdEdiePWdHbPN%>9jrrdcGgY&_w&`+H zi`MlvW~P*w^?;{pd(vY&uY&kJ?o~yHFh^!L(rCH|ChI$Lq}FDryV6(eg_8aXxmP8X zVI{XV8amLl_*&=_h1ZvVS=}i@oq3$5F`xRkz0I~rWZSvki)AnZjs}sJL~9cVtA9+X zWiAR8F`UuG<-|2$q$3d=aZZAed!`wP&>#x_zJJjgfT`M!#15n@wVjiE7lIC{X}A2n z$9VZJ$TT&dmv1B3)KlntFrEoxx&WU_R_~wRk_&eaae?#&Yp_@8@N!l_`B3?Abl?KCrDp zr$P}M-Kr2PKyIn=f>4Nb?(|+?&f+I!7Fpr_e6zxg1Yvb@A}aQdkjda0A^ip^sR>0A zzO2KJKfe31m3@5J>DQ?LvN7+-yrO-FR9RAmlQ)ahE+#hfB;d$jQktuiPi67rS)yigTK6`hP+?89k?+N2~_cyrBJVRoYH<(Bkyy)qPpJjM^ z^IVrD9+ar2Tnt8-&OM6@k5pi#{r_!G5pp~Ssi0yl>$#f7fPi3a*v$^ zkySK+tD#HL#jm=v%fPocr3OT zrBbsC<&zI;`kGFQDy`;n>5D#wvki473-4DcH<}{Ztrs^`b~lh;bEgFt+TWK{QP~o^ z?)LQ=k@U0z&32&M!z2S49n#nJoJ()c8Q8!0N_v>;P{(_zBoDOCggeiH{Y=gfL!ezO z(4zkV=s4n6F5oB%>e7%;Ms(tjz@(eBd0+aNY;*!Sv;AA}c?V6$^Z)heY8PE7W@a~D zuR8sDVQd#Q>eLrXa{@Kyl=MPn7p{^5K0Cg1@J5wwHWfNrmCg*Qz7Z z7tWRIJf(Q;#rDzjv!|$IDNS*&9}Bb)_4vK`uj>cgGDdbA5{SnnJS=_IChZv7Fm<`5 zezz(T5_pZE>93UhUZ>zJ4AsvJXp!*;HNlBH3S$p;ukT&$o8upAM4~Goi2Dc0ofFOf zJyNcU@AWNt=iFCh7_Bs7ll75DjC8)X!Rf{8v@3qnZqppprGimTd-Y!VzHI>+_M$-6 zALa4`dNr@@RwHF+V1 z-rG5N&ASZ&#(zgXW1_x+FVj2*Pyy{@W!Nv}LM|ExnVf-I9y1N%SAs-yobQK3o?e^M zJ3pdhDzWTKxtF#flYMGR7Dr)-E;sF+>pTCCu=jFP)1R6@2Ge@en52Stu z+I#mvHD7Egl$s6)c8*7(t=w0TJt$#1oT{{|M$nUo5UHa_qkgyMCtfKL*R3191vJZr zs70ScdjZWR;>KB8qOiBr_gVdG?5ZN!7@uwN5P3`eJkaf&Aro|=I$U8>GGIK2zdT~| z`-{=I_c!EFqW$92Kg|^MWhrP=6^Dwz!I$YR%SZ4=hfsO$@jGk$t+y~~(Ar+HyVP=V)Jlp_GLZ&3KTa4!`rf z|3$WGty22=vzPk?8l>|QbOA$ck(QbC*{ztvvruDwvBM${Hi46Mq~1bndq0iQbPz-- z(1caiy{8f$+d-BeVC3`I_5JPO_=Jrzi1j(8om!CWQ{O(^Ncsphv-$~jGea#ygLlmxMy}9D&0qksNV&d@cy6qB zYc57poKt*H0sqeYdFpey)4@&&FJo>ts~!EF8aLP=GuerRRsLXJ6;m(5V#W%!dltw1 z5Xey3S2lRz)rlPdOfk2wqxt=qg5T_aNCsNjuBJ7=gGDD=rxv;UUUzMax`~>(w5yEE zUlzFRJSGm32~AOtqUGrSRQnS(4Y}7@8x{Ln@W%k8Uq$nv)%wD2zNUX}%|ZqXT6S0+ zH}>d(yJ4(4n@l2+`jf8^ZC~A2A)PM@1emOxsZS&blfx>%yA|O0`wKl?8md-e^yZ1) zY)Ys}9LhR?qfkziJoqekhIQATrhtRVx-)D(4{Ljf^a_h#J9?aUk67`D{)jv4=7m}1AF}?=gF<;& z^&`v4rtq1Hlwr@>IOyLXNyp3m6@LFSJqynpd{rB)X}&|+l(;3qcvz)!kbWZ!u;0z@ zvG{)o;Xc(SYuqYBU=8yv%Gb3` z+fGEUG{#7nf01of`2||rsAZjAjs{yInaK6nWLTTX)~~lMHEKf@yxczqW<$CkeQ7_w zbTCp+YZLEB?)|ELe}dul@xK{^l7y!(uAGftwzMV-jH(Hs!CspHjWWPeu^+r$EX3=)0_kc&{HB`faM=oW4zyQd{Q>%@?2}bqL_{yZu+RjKhwVm6^Rad?p7=MxpOk%^aLrs+$zjOj+&W zHeV&S^#le4Z=qC++3mm*(ACYmUH=ZoOi&pJ23eo2mXcWw^nff>O98OB;yS*}j&I7I8#+D&yW zy+=>1Z5NNjsOCs&!|;{xCXJ)k$XbN3zG z8U&s18T(w6ZC68`(tfXKV}0p3xmWR+(9230Nff*6m?T!O+rb{}5ifhS&LDIPPP32@rWQ>Wc=Q;IMUx zFwSrNaqmgLkq6f>gc-1-g&*Z42(%#^n<*d3dy8Otr;E4CIMFOD3?=}+e`O2LWAE2pS&gS29@IU- z=Pp&e1Dp664u}1XAs(2a9NLiR*IP$4nz*I;U$3CVIKk*&77Q=eKVg$+I}4IxyF7q`e(oG?wue9>-~}%$zqZ z`mS%l?D{hEIi_3Xyi_-3O74Z`=H%YG{*2@QY_GFB+-~Hvacn}|4Os6~J9se%)3!1? zX>P{RzV)~N<=Na?` z`bjGEOgw_l7mS<_+Hd8Dfzq!(aw#w49e^CNa!28_Xo-)y3*0G2m1^)i3ad^0s*;^W zz{-6S8k>%d4Up8vfK#w6Cf!nec_q^yUGojoIB#GhdG6(mG4?>u+-xsu${=JglW7uP z{)#TLMyQesW;zQju~6%ovS}XK-6`#*4Q&~qOLw}?blNukh2{DhT230cty4(%$hD%kCSNi1!Z2 zw(10$)tg$t$fYFLO7niC3D7u0@@v~0(;(}JT+PMuKMW9QfY1@mOtq*%&KK>@*Ec2P zoZ0G^iuW*zsp+x4b6WJta{}cz@e~+=pcLlSIjxg_8;RP1ap<`imsm35k&Iar)}~@( z&ixcqNyHLnJkf0o-}mRoC>dwcp}s0SOCqPy(>j=V_v31atTZ7)J&yR<**M>q8q53w z`pD(BO_m^USR4Oce?KwPk1|P(`?}StLHOaf!P5q}CB)0#>#JG!P(pE{0jfxthLpA5 ze_$Kpc(Sku>{7$I4?|-}S(pyrn+U2^HfLSTA*uydrG<8lv*GiNvG%7ax0-JRv*j`bZsPtV4TEt4 z(nUbTl@lD<{L0l;Gy46Xd0RfXE+O()O~J3y=xNeXLk`J5^NmPJ(!M84`g?h%Xwn{u zs7YjIAKmpz+k85N6l83-<&iqurpd9| ztrMI1rwS-@&k|#!oPlDqAx^Hl89U&4moW{9WOJ2$k~KP&J7_`fW%Bq&O#xJ*+ujSziPJlL4Y)@CTy&Fea3kTma- zF6@;iWvLZ-r~&K?J(oz3)R0Q)i;6XrVAg(!MDg3vS9zs6v2ZT?_gy!i)~B^{A$Qil z?jsQ^kDG|s({x5cEt?)fRB`l{xvapmn{5YbSz5rQbPDVA;x5jC!Q0k&J-PFrY}-Q3 z!mf9;mQ2>EPp>BbD(>neM#v!0tRZV)V$Pkd0NWkxVK5TzGH*}9o8_C74)8?%yy8RS zL%rk&e@r87>ekCySmhg(@%iH)7C0D#6gYVpz08NdQ)>I4=J)!vpi~6^2Q5VP$ax3R zvVA0_c>T16i{x0?HQ&*U041QRPzi`Y%E%l~$&W)cMo(e+-K7eB>|{IWO+95_!X}W4 z`eE@M@Wu19d3>l62T!J;ufTqh0-_x{AGE8ViElb~A~HDxZ7l_QtoCS%QTN?Z(A+%u z)cGw%DZoc;V4b)t3mF~U?@>`l3> zA0ra$*ic(;!nXuwZcHhE5!`2&+O&=}7GPy^MLkuQ8UaP56;r<;yv^Xo>@fpHA!(cL z0`ZB&M3F<8`P_Of&5TL^rYld$YrFqSzjC8ahYB?x)gmX?*A;R+TRPU>ja1s7bgvn~ zX7LTb06z?(wYcn&N#y8zGXLH}1MqpaV zJU-TnMcDtn>P6foAj+GK-S^ZVBmSWrm{f%H8vg#w;rx^|EYoTxFLR*TCe}>W?V1u z1y`Ff;a`3IHZ=|cPDIHv4cha_!u2ymPu)Q^10@{F-VWDP|Z zU=u8dWRnH@8RI~z1!TlzK^AHrSy=hAKF6?RPRUsA1P|s(A>MymWTu$Fs}UR^gYFPC zIYj1`M?lD(J|9o_d-7>xb^+st_z5snAKz1Nh(_lGSxWN(-BaTXbarSsGF$H{Nc)|? zoE*)Y)F_rwP^*49rmxZe;J-E*NwoL245~6{daCe2fmP0Btva+Whq-ZizRV(&!Gw_egc%b8?X+#cNIN^}_1 zgdf^25*a}`;~5Iy_q3u$gI7jA&@1~>JZq*>mtY$!B`DPv(??olEuDEtYN3?|ZbAz^I%m9#YJ%MQnS>r4r!2N_!<5fIBI|9-H12i;EN zB&ea|Ne;9S_mBo_@g*L8j7`zNiszK#H{V|Qkx!0PS|-NNmI}{X`_mD5i&=jcj_03w zUf0*0rXn5LZISas%sfG6->!SeG~>4K?&J5_uIX>go`56!#{A8#Gw7%%IrZbVKjD8+ zQXHvr%|DVqw^JU(CBinjBuyQA{0BITySvMS93RU!i0l_!%C|D6;Nw;U!x^q4uS;{$ z(;xwXbI^tmp*J>LK~12Jgkm9t;+5+XRWn~U<+wQMf?u_%qWBPG8)Ay~*jZj(bX=W) zt*LlOH$`nImDS=6GK@N|9!FlqttE|kM#mdBmN5x7^4AXG-Xmwx^sf(0X3t30Iinqs z88>2$sWZKLhs=N$RcNipWsy@sp9{v`Z4a&w`v6zkG5AlGPlhZGR9vh-q z$=J}}hJP0fY&`5Vn%V*#71%jD;L10HULUn}ahfD^zFV8b#h0EwAW_%w$DeiqGYcc> z=a`-LaBNbvjsvp5Ba{;UE1Q3oir*{Hri7QaU!8Gst5tUJO=`+H!eep7nqW!MO9Qxl zlT_MrCl~Ur=)ba%x-J@sN>1b2q6)iXqf(A{g-mxs!z)+G?tCr}DYs;rI;T)VfysV7 zraIHwiy_lXW?sS|apksZbXe5t)%5O8#n&t8S8R@5Fg?p@9^akjLVD#c(*W+t1RgOJ z?=KQbHJ>iyFs!jBSDF(;fODNGXXRTNq%~$Ei#Py$l0{5E3a~Y017M}S`)1%jed!c1 zCWdMbK!>P&EJU_OCD*_BKOQ>}o!kj{lG9+CCdPj(W4t5Z@{^RLHIw5aicwqzDXGyI zbcr+4&_Ya~WLnM`F(rnm0)LJfMNtNOs<3&7u7eqhPM(?x;|VaZg5}^Es*}Tjm5EeL zFW@C1MStvGf&G#H=b-|uB{-)| zP7FjJO_|H&dG0$CUr=hWHp`_^N?0aeFlKlsN2^GuDfoSTc}vjR@oRt;Mr<5SkP!#< z%QYTLo}c4a55tF!Mtl#Fq- zs(5%Tv%`w-z~VX~^!TT}=rZGTQhwFu4I)V?mFlf|nT2wfoyWLwoTK(Ir_VW2UI9Y| z6&%1ZCHI7dmSSYxxfq}&&Pf(M%{s6POV5XUomt*!>s-!?Jvf6!t6~T9#jbAC`l|2c9`?YF{@xKZ^C*F1 z%u8;sIB_9grR{ZmiL5~3rw(wcfKK!hLfq#z~C^B{4PgL z)XnjETIMD0a%*feaZqFHjsi)&l`=P3ilOCz)ES{mxxBT0Y>%1ILsr4o&MYie1 zcR65LC?UOyxhCEf>nyw#3)VP$ma^i5(NtUV!7R2k^+#{J5L(=p5sJ*X!y^YyG*iRx zfkD*$5M0MzY!i3{U*EQjm%6fZ9oeTFAdPl6fll6;jJ+3`qha4muNx>3#hmbXSE5&N z@e%wA*VOSDaGvZgRD!3?sbsVHF)~NHH5L@}_6O1Uw_HELZt|p*j}-A)Jl5JsMEMH1rsU>%L-hB z4v;pSS8dn^yCynexL#K81!c&JG|776v7v;i4IBOXmb`e!zuE0bhpIR*o*q=Tun4un zLymeHPl`FTe1?%>at6yADo2;4zjCB#JN5^&u2U`)5=W0R2Owu^Rj)$Bzw{(w3UoM0KnZlFOGynk}zChgU)Ouhd7rZ?B31vyhwNxvS<6YvSx zOo$ku_SRrAS}zH8lST^AH};l8(^0+W5`*@C#Ddvg0pVkJhY8SNu8&*O&j&w)iDS{@ zm`b%fj-gjs3=>L}h}zI~lKt9~1nN2^K|}N@E|gcOXR~1cCvOnsB`~e$KWNn*<_=s8 ze6)kiBGB=$%Sm`90yN}<0q^#`KS(mA#KaCxSz~+#jP4RPrQZXOVoWBs@Fd}}y%2my z!324k{d@9G?PL*r&0hW%bmk|yAK*6H!FuF$ir#|J+lg;6#b{pmmQOPKLbRl9vVT7; zy4XO&NcmhstYnWs$@ux(>N0*@e33FYXJwL~NF@6-N7S|STZD{MEN=>iZi_?g8o<2dZ$dlBDwGoE1 z2#Qac+)*&6fhvL-SzCVFMh($8SjS0I&W8v4ebs{yZgJvLAJo-;r?3Ug*I62Q$e1$M z%R~8JYrLnMxcAM0$E^{7veR)g~g&ru>=xuNNQMjge4 zN&Jg91}U4wqOg-Zr7VUQscvuX022P!h6?#TE2ujo+0O$)p^)Ib&+^tJQjA;$PS^i>(w1$LVi} z+TYeJ+M~BRkq#Ny8mHc2V@|f^6VO$W$X**(Qy#+haR&Zc2{`zpa3FVYQTOagIxP@`<7xgoyq%6XO{98rjIGxGZCecdi`ahKqOWU;o z_S5XM23=0{6!LDX#x1pc(aF_y)!mBUn6!);tLGZGWY>pk0QzZ>I|mK-$1OhuT(hK_ z-~e!a_3$Hq^%}~tQ39*Aey6iL%*KVzl~34Jg#agI5MkDth>;x?qE1>TXL74Y@)I)G zXNPTAhx9d3_hR;1C`3pzk1!m)93Q<+8*@`Rqb=eNW~ zlXc-$v~An|LH~qu996mXcJ1xbIqaNeb?R`r4YXYK#!0*Co11QS2K@*T~tF^%tE%ZcJ;jqW~MG z+4qELAP$1wpxHIWSJB6Hdzh`dM0|Bc48)W(kbjq|PyB9d-^qhrCK6tgyTwOLRZ(I*W ztFMBx<0>AEM$zx;0JMm<-n0J9#bGw~-DKpK8p;2{#`07OrH2JJS4&-YOBvhfBL z{nOPU(EB&r(0sq=KQe&4d~iq|%f8@c!c*B5bzs(G!TkO=*(neOT=gxJ9 z6iqV&1%)3DVI%giBo}Q|uk9hAe-;lbaXG0m&i~dVw0CdL3bg5TMR^Ynbh|KP|EPR) z&tE-eUhGgi^TkQybZLGQm1hvB`TT`a7efg)|7Lvn1>G1k$r4cn_xN+PyEHt>a`FKT zFkkZA_ou8lGgsBaw?<*1Cghq^KKFLGB0Yk;VkWNYFhL8*!Pn*G+cOzJMlZ$GJ}qLmmyIml z4%inrxkC$#afMdexj>PYd&_@-@HytDy6a{?x~c9Xud=^My}N&~ zqcsh+ncYGH;=XCGT1t689Tgq8ODwLxd3i>0LbR6pd)e2sMq)Wm`zX8m_H%l+yP`8a z4U=&Hr2r1d@W3W}dsW9DgDZ>Q2K{{ZmF3aSnJ;Hvzs`=Da31%8NX2gn4wy{&ONN{Y zWkX+UJNr8saWZ5zan`bAh4onf;XmDl{@5&apG7NC$lj^v&BrK{ zd(RCg_|*v$G~CA~)SBx)LaxOWRt_!0zxI0a7~eQa%qiEm>^nX_Hor1Z?Yz+WQ<3EX zh%>}c@TJ89UPIAi600r#4`BB@w}$aq{`LB28VyHVkBr1glH%2acZ7VhA)C6O17fWg zMl^PUQAD*C#{}bfR5>{?Ot7e;B?rzIopAg~;hluoSQoIN?KJ2g|2kW9L5SQ~yf^iZ zM;FW>@B)VZK1Wd__aFM>3b%dfI_jM1EdvEcp)FScQFgq@GLPEpAtnUwT6pZ~y|wWs zhCEV@ZT-6QOrqiLX~3dkRUfj9fnxrlXhyLX9BQYM^p3?|8F7KW!!j?$HlolZoo<@6QQhGwylaBKB?X0Gp zRE1J8tJY%en0CbJ+NZ>&h(@cQL2c$%JAl6zu44^x?OK%664p^RmE@5eR$}%*48q4S z&l}gZk2Eu|OL~!#qrs?IT-9S6Trn(-#t7UDCk`)RGwb-Xrx=~kA+MCyKJV)pce!3) z&kEElpFNP22sgxATCHw+Okf63u=$;ZaFx0BeEpJgscuc0FXXKDzR}1)IJB zf>Y- z;+v7Znu%i-4|;Vf8};sMn#uO%wt;O1!eL)uXMWU`{(4E)PFQZV#85ifag1WwI(jc> zF(q483;tK<_D$?dj2`sBLz0GR?R9vvtWcixv;WQT{uf{r8edCtP<%W~IlH z@BdYSsD^leEc{^x54X!t7R3CpB;GOXUAtQq2a%Xa4~vEVf7k7Z{rcdPF5J}Z>Ptfm zSL$xs;qWFP_yjVNzwen%^oHQo=?lB0Wj%PoS?r)~+Q~FCD59x)B#p-29=`GJa zq0oMhANwmk#@&?f(t)ghg^q zxGN3Z8(uFR9f7n;))fzQ<}6mfetZVaeKs)=x_(gv(u(RZbme{boi(?`)Gu_ri>z^> z=oa=!1YS+Uw0PoWR*=-3BtP*{RrEhSLxdbQGjY%PB!CDaRXz%+{l#n{tejlty-1GE zN+cY#pwv{M%KF2AgYsv0Gj_=vjUPe!+tnAut(UWz*af}pI`*y8nZWg0pX#{bij)fV zS9@OqtGA=kjRK(8mI`pqUKh^_<1s#&tKsr3IYroi(936(hgX=_;{YyDu33&%DAs0tPcJiE-~`yXCM~R}#_B?sG!EW9 zVk8gt1-ooiGsx$EZRL}vMxVZ-nbKW}f8zG;Ss+mpM!~YgWXq*z73suoP|GJkOxlF% zHMm=jnsOURlfaoJG|@qRSUv{1V@_##)b&(j*?czYGF8@ci(09g{bgLgln0PEJ&xFu z08O?bH|n>VQQZ%SYx@QZo%batf5W2DC&J1ugVaa04(79yM3lUNt26Q;{4wPTvoA+~ zQ`z3njvBKCXNUyeQpED&toz9cPMo-8-a^5+_E<+_G=!F((K^z+Qd{+cfaD~3ihX?6q&qns^<#FWtm^5y{()7G5|^|GOcDaF-{iM8aI7c@)sB;6rw<0=-K7qD%C@_7VR8q1I8 zX_?t2C!l^1hexL%uhab9ya(DLr0ZMYam5$OhyxYc&eHX`hi$^01+H7!rq+jK;SYco z_pNZ7htP5B1kdAZz&r?ltkJ+3>EgUxg~Yt~@|8J-5x?W4jND_ZyDTDSCuCHKKeSh0 z6PdAF}xL%QMUx?DI- zLn(t131?>mUdfI3WjD+}D1#2ZKLjxC%MLEoo%L|1B>b#DEW8FUHDlnf!PmXlSNBdZ zM7xKKVNZiJn7wDSiZZ1i9?9e?tEbS;Cutln zt)R;Wld3J2I!%hV;hF;D97JKkUPRRCo7=?T>(LpXoR<`O^M(%PJ)N#+*H0b3df_Rq zxQ2UsO>6RzlkGF@8_CXO7QFWIvG~KHf1I0!@AbP>o&L*W8*q5M0&tOz`*?>9#bJ7x zJ@rDPQvctm-`8xB#=MwvP9vLU{JkNQ?5nX=7PcY-KdXolND;mkp|IWkae~N zx0MGrW06v`g@fXMCdloW%7V zTEM||hl}U17={4Y`kr0T3uX@9M%Fm<+tWT0C`>aEDi?YFrgj~~NHku3+MJEZi9&N?Usnloa^&Ed~hn=5W)jqJ(AqEBiPi2R0I zCJX|4!Y*ou3AvtunFvi1`@=k4$fnhNs(21VXmPvq(}DY=lJ(vE6Nx9Ew*FT7G?Sa< zuEVdH+H6NGZatJZG7Q>P$nG6^qoOh%p*VjxqEO~E;CIegCgD7IB4NT5?FC*9n=qW5 zENO>*Hj!L&%Axv@a1oodoef0A5w&yHfKXcq9qLeP^;~)H^@UT^;w7@eRBKHUouK;)fGzO257_a_HM&9#wHlt<_RfuLja&}e)_0Dq-j47nXWaX!52QG2Tj zw$jo={`T7BMVdK!jPV%#q<7!s7?lCnoPF61TJ!F?JJmW;a!|d3M#$89-F|QEn7VkH z<&3lKib;mnv6t4rUuI4-`>%XpwiI{Ted0+Blzx??XSqE!v1WtwQ~I0^EvLWte0Lf+ z`&j@I1vC;yKTO|LfCnpa=@l|&QXJD59)3_2i6x(NCqM{E`gj;;%jEO8I#m;i4P&_` z@Tgv)5U~xitD_`iJ?7-Wq&Xnwc=iz3ix^%DZgdXWAJbvBFCdR2N?Y!i4ma~mCQxSi z!78~&rnDChJ=vg3t=8qRLP96*rHL2ybCwok)iiMl@incSp1`C9yMSuLx{kIO;h^Ja zHKuNGo%m>NWBFnU|7v@lNz*BvaT5pXcrW}g&x%E>5=J?^_FU+T=#b&W?dXX!StR3_OtKMl?3&%9_J(%kS*zEkHDpKt|C)7q4VO4Bb6alO`J6BZ`0W*g)AD@ke2g?< za7#RUp)Z4&j~zht@@Q2hH#^^W+6ji^Rj?CFlIb7rey3e0(9|Ua2U`0I0Ilra(WQ`xJ2^F{ff~?;PQ7m z^tIc3fPgTA4C*r7oCBlRde_ztzLad)fns4zeW0<%$r1;coYDvFB{(nm{_Wk^xWsqv zH;=NH8CODf`#ni>cXg#7_ltrAfz#i~Z(?}N0@j`+@=UT`^EMKkorqqO8Pf>IJ0=qH zfg}zC+`Y~l{#sQ`+EP>vWF=zl=j5B;n~w?%WwGCr^(YkC!X z5$$8HYLyM>&f@ENN^|`O)ku3w6xFg}QYj|*#Y@!LHDn!4!E7FKP``|DPEgWN{!DIu>`*n&d|9B;#|BS>2 zLr-$CxqSelUClG}DiD-g%LA2QlMp#8G~AHPOm!W0K)Z47pUKacdWmhshgxqlWgCZ! z2dWF#WfQLex31~4pO&+6T)3Wi#lL{$BqqE!VB+QYsJK*5Dq}$zZ?HrEtJ&&ds8w?( zU_7MB{2XROwQGo-4Xx}Xm}>0j~s zcdqK|1C4f0mUBAQb^)Y*{hhcC6&EGwhi|Avja$Nt2)k0n>PZbAdp>dPgG7TJWvML3 zHsZV$sv!(BUQ)a>K{P0EPhGpftoH02yUL8T$jMjtPx01DZQPVZ#nE3M(Jebp5SuINyM9sVfxPK`?>hJKF8ca>2OI}wuL<)87jJ52On+a*}k2_onB zy5n~|E`SXHh9Z?4rIw7f4GE;yo{mK)hUM%)vQ)UxhB9-Cl3(t8p>)quyCVo5I?{g4 zmaLZMALC?LEiYxcHO_llLEp+{{~^;-<@W7HDE(n>Sz{^3K#z`-xk%cCnR^uYY{hIa zMzxK5Rz=hCIr}lIO;*_EuH7G9_E(<(#VQ)i7fLr8)nA=25(pNWftk8dm4P#+D~6)J zz5RXA)*NQhDbNWt!}^`Vkey;*9hM?9egau;jld=cO~GQGZqNf^+-aqb4^~@3+|g!( zSLCE$S_8`M$hPi(iWm+6GfRm-$rONi!0Lco9-2Z(gdqjWJRl||_ws_oRa6YVD5eIyqTk^EB1PipOOO9C)2_(LCaD&@;%3!eqvUpjZilT`u>0~bZU>f6 zbi$fI97@wTdiF$Rp3g}JGlSWhJrvIdVcx=*K zik*SUFqM(r(U9%pjp6uG+)&PS5bPO7lzU$@ia6>;Xv<#HCw%Vd{^ zv>7$&(Cy9fB*<}xC5fBm4)rXHRQ5(xu4$`vX@-HRR#;|e_3mFb9_U76IHz3D`C@Tb zsp(ZN^4uFA_m;_Z;%$i)0-tw2l2O2j`D4Zz;+zq7G6K=vaEER@9IAHb|Lq8T zm1#;}r~Fra_!sLE?nj>q-k~kDEO7V?&0DAbH`V_==g(X}DGX^-xCb0gx1ICNm^avO zMpK6w4H9n_*=d4LDIgX}7p9i+e}1Ypd&N`s0X&U-{`mIM^bsU; zWyN75i2eCX-kZ#gR?=P?q59O>o3T7gtKInq@?O?t>dL>2HKeLQ|If9(mA`xA^UQNH z7$gWidxj9a47QDsuD`;y&taik&KDq(Pou8sYw` z_@nTbZk&qI@>$#NJ>2`Bh4oR}40I5)oj7B}lQxnyKfS})pf*OB^hYVkscFVLL10R^ z)Lbs?h6CY541qQN*a^&4HO|A+vw&Q>HmMxsbiC zBn*P38L?m8l@*xz!zoJ^D;9Uk0fcctfiSP8?-tuTjN`*t;lu`J&>%g z<_gESCbzgoThT9XP39x$7|-Mk2`AU2@4uOq6l510+Nue7(V)vzGx0Qj594mi;^0ZH z%jar*kfXjKwBiQJ-f86e${>5$5VOXfYsSVexMdN7waRBV%IUS(5y+?}q$!1H(DgJD zayY&rx5Wsl+Zn6=6G+TPVGr_g)J}xRk17UczjO0!$O^oRtadS*b-}AU9En&rN5`N! z?CBmXz6GjxeBQpy+7~VU-u6QUJa7eX;u|nJN>$?)KPzq^y8dW!tWY0%e2x3vk2d`% zAXmV+1EH={>*M4Ve|c!sevOh-Jt-XEndClIRCc{&Rd4!@Hi37LkWB0hD#VblyQ~Sq zu0u>`thPxaG0Is2h zweb?q83cnNI7;vRSfh>pr}xxlsi!r{AqV#cT&hm#EC`=3I<@L1GC9MbA0cSrg7yKg zoeNgk1w^PHNeT_-V7p9#XCsaY>z zuawayHtV!vMZR;qy(bZ998vJ=NUd4Gw|cXq#zD-K>!kq)OyR!q zM{U2MABrfLH3vtsebqC0R5Er7h`ncLKQV`CHjL+%<l^p zH_IQ=(;2_uHJ=f=f6_SJtPJIpkD!_6n z@{68JcM!+~7t;@pe8>=E+o2aH^(_6)l3N-`ZYdc! z7qi1eCGl$u4Jku}s2r1T7coJ3avNi9mevNs#1FdC2s@Bi#LgWnrAIFV#DaZ>8gwK_ z*?X4S3~UykyZO5Jnc>-Gt5x4B>OY;h%3+i55EieY`l!7;)LXrDKtO%$nV5*rP)Vpk zk+E9wz_4Rm$yVl`!{JFWdGk;23`GP-6k%!$&hD^0XML19q&@eOhGs&Q?9I2Ix(5aA z4L)mYWDA&Jr8%^RB6_2Q@A41MCy74)LvGaoqf^D`(a zv6KAX)?`SC^f6PWa7a)Nzb^uBic z0%HK_(65jg{LP_*oNXdd#s+>q1%}(M1i9-qw6SZFR{us7Nx@xWAPb*~PxS9o655}h zwh1tE|K|UVsb=f1PA4^=A}Ci1n&_$VAF-LnioYa+vDyO9P}QF8*SAM+^o03pwp$2t z15~t?sq^{w8=gyCN`q5Z3?U~2`c3y%c)-VhSUy#L(<(`_`Q^m=_uto#CQKO(ZddxC zyI}z6YF-W+>&&E;e!2HJCF!q|ig+D`1%L09gu1el$-EfQ+LATGbiFYKxTMHN;G3a6 z7PB zqBItw8wrzJbB0{Lq*7n&cK=~X{o667s)F9{e|BB)?FA^0s{w}7iE$<5k)WERU2N&j>Ti=2VcikRHYMi~d7 zyk?_0e@@pTyuwV-cjh3`9MjTd!knI*I7l=`G@84hlPG!C1X`%5>9g=;cdeq0_b)wE zZ|C5d)>^{@%&BmFNW=OXg9A(3GKC&@LO{ZA)Ag%PeLo!nVf|DY1sp76>3dCS^~p}VR1WJ-#sZ$>J_o@KVc{8`T+ zv&QsnkaeY_(>t4ab%USE3iOWuJtm&+$k%QQ%&0N1Os*>+@I)7`_hd!X047e&<~fqLTu^j>%^h3-KdBkj6*&CluR1^G97NlZ zfxvU>i-}+)c*F4`YQrsm^0=9CWBya$^087TxfznD8xMwF$=UW>Xoz7>KlW;>D)ow> zuR48~HKS%Sin+TNeAGjN=Vinp%EN{LC$Vpo!WWTuY-PNT!rVm(gQ^pp*^F}4+H(5BfKdC?qF!5*Pv7Dm}tIhEW-Q(gqhMz%D%Xi^I$1fVn!g$H=aRE$-d zqN=qk;uUBi3AS~UojnJu-^;639V51$wdBB>b6ccSUJC>2CQ)0r(y>Q~?WyYHS>(lE zH9-a5A61L4xeY8wiA;Uwqr92=Ov)XN`;xH6%B}FWY)zWg{~NQ4I*dDl%~l@y-kzoQ za0=ckGW+tneq!OC?9%EF71;GGMT6^3Lv}#y=d6ekQg&>OO!j9pCXV$uUi{hm6HI$q z(nAy0u-KZ77L*t1(Pn-8xtdZ%_v>siMMDJK&o?U-yDHIL-V5+%&-+l#^A!$ynnTOa z?(p!p24W2D2i5P_MZbOIX~~4!0EoV?=QPX$v~p*h!s;S&89-XK@mku+WiE~4z93SI zR<(GVqCsq8qVsri#rACATNUA&#FTe=$cq4oIA*b7C>JlYm5B}R%~g-?FH`{{nc|p2-(J8IG9PP=lQ@pSGs$O+T znt2$>j9+F#KswnGf-T6^;=~&;VhL}!KUkczy3#F1H_Ylf z;j$e>d>Cg&F@2*f#;3~>E9uc${jCL(m`d+T%=wgvcYLu256dpU<1mO~WdDkxST{HI z*0cbIa;^|vYzjG_h?RU-=ED?DG5&Ca%jsEZEs(FFu5Y5XcKUM|cyY=AlGcPE0~Wsh z%|Y0)_T?bnu(b^jCA-w0`m5uz)M`*ve|8r~{ew8f_i9r%9ky;%FkC6&A&N9!eHWTA*h3E8*#Bcjbn#SxkK}d3Jj^SL z^H&q2;kq~JkIg)%yB3aCs@lv`8LFNQ7QU}A!3?6!4Nk_&4sB+q*%ZI=vp4YW$~oA{ z=-U&4;kE07L8n9B2@d}Xk5@hL*QXlHjr-#$)2Nw%PKDXvD>El$nJ+p1?p@Y-dt{ST z4IhWb193Kr*d@6637U}PFXLb(vhkIR+V&ZaF{7GIOpmHA7`}$C<@CPN$u1N5DiKqi zM=TV}>cKs`^mK}0P5o@L$5FTx@X5$8_-NZhJYX)=v?Yfz8~59UJVjZdgDY9stG`-J zm)W!Sjg=BbL!SB+Y%kMS(6n)+_Q8<((M~)D9&7s&pEPLU&Oh!yH_(&n-zVUvDZl)D z;zY*i0)WAsofly86$vLAJbYW8(RCp$I6rE3^bM3JGi5*Y7zp#}VnoHE_rdP;au=%& zy5~IkdX7X7BlMVODV_}bLY`r)As7ujaJAoX_*^NjM+|z`vC5`$@UPJZKTDi(Ak+*p z?$-?bN6{A1bPk9;D5|B~x9Qve+n)F8?wZy;!;Z^F-TKB)HGI)MD79gZHMbJi(g6&` z2it7y2HjbCMM7v3YIhQy-{ki8psLI^$X1H*uLPcsGc^aB;2W+E@8A4c7@hc8QSfVZ zhM^6i@9D$Wa}Xz{jYt!c9hyE&%afNtlckD}F5OZ+_olirjgpE!PVEJgcAL;Myr@vu zY_}oUf-iFuv|R>WL!rNRdCX}`Y4OFrHvWhe$d_W<8KO1n*1lB~gfQJ_c`%2!9sp|XMm-qA zsL*t|o2B>OxHFnrNTK&K%v-&DzhG$Z7>F+9>-R;DT@N$EAKW;~L0VQHyLY(&zK->Vvnm%k7+$)WT3bmM6@JQi-bi0#3NZ2NW z+$f0MwTSd7_3S;=Rf5?3j<;)b~`mboeGt8 zhG4db4|yeJGi96nP97s+y+K5<8tHcv=V_i~Y}q^8ye4kh0@OuJjl=wp0hH?AN)Sc@ zRq)EP!@^%AAeWRK2d$miXp_tzzoNV2+ivkGc?i}N_Ji9O-0ni>5U(F?xFVNF`N*Lw z7L9=3ZOF2m_L%$8*r01CW#8f(w%*KPs;zzlygF@Z{Lu~t*whPWjR39>3;h_5*Lqs@3$N57Zw3hL8i!o33kCE)Q zY2;;T_?|Rlmxu>2e$TK=emA{kEacIa{;>)(e8z@+;xBN^(@PoKa`&P2X*!z+mLDhWIJDo5K^tx@U6)~h|E@82A zW1(KLKAN&$qfDI|Mm~?Y7`jGtLP_lasbt zIvaBG`yBV6NnH(R)B*nywetlwC zoanG)X3@kXNvszse;2qEMpldAf?RXNf`OEj4dt$27NGf;8}dV=i)N z`m{Z)N@HpFgrVA&dw2&Aee<_r(YJYueiGySiPFydqpw4LxON)#b)-Tld%q{9-HMj9OHFZ|cJHnW)pi|5CFtmr}52h0kyK`YYa1&sJ(9@3e zg65mnKL&1EGxOEa{lzQJilkPavmXlF{Js~O0euj`^=(wr{0c}T*Jn4g$58*;)}oAoPz*S*A{n}td9G<&88mXvXv|ytd5{4S99?) zp$TH2E>Akhk4ol!r#ZLN+C2WQBw)khdE%Wl9bf*}n2N|Jb~k@_nw$&>SgZ4fvvKbD zZhl{t(#D?LQCv-u5uy36Lkk&2{^-Q~DlY72p5L`^n8!-#-u!)6gf+9c{Wjak*Mn65 z3uPzFTjw(tWBCpcFXuZ{BIs*F$}A@{Kc(%)w`k}ox;1^n0enuE% zupZkwgE5#~r%ysny<<&EAo32*9IQslkw8BE(;0W*FumI1UZ#%%&A6q!SB zal66O02CPtY;kL-2=dyhwP+rIuA92(HO?Zxs&_uA8$ z-1)8uCD~i|EwX=VyD$*QGmr@n3+OXd8IZ?68x|U>?bFDU?~f-?zDj4@$zOp=kMIxf zGaa!f9_LFmeLS3x(Fn?kzfKF0+j8?Q-fn^gQw-&+%ofQ<$4l60)c*G5v106Em`^hw zyFGlmHE_-KG#8(dwZ5WkI^IVtC?|VnqdByuC}?vjrc_V2}< z0tq~h!kcn}txBhCaR8`|Jd8FZbZ#imyk-cKq%DoW9^#h}U5kE(eCfLtFZ*>ils~a& zJZ#uvG5%ByI8Fc@;|OA`h=NhQlMiR@WD`q#yDzPE4uqeMm~rXmDbZ_K(6uQ2TJ%w2 zt2jbY8Z>r?$WhLkZVnC7lyqQ|)3$1#lRR1oK*i`=4t&Khy@Y3nh{n^`YZ4WuvF;`K z%F%Q@5XQR0XM{@cbn{b`DMoua+fna{Ss%l615TQ>ng<~j3rxLvfpJ*RRi?rWEwoSkyxNFl5D?U~ z*+5c1X6=WLl;zw+RHQ_^#h2K4g&W$73kY`sNm>%b>v;FQ@tR0A9%bhO*CZ7FVS3!KG=%_lU$pRk)Zl$!NPwNN=t#8;I2jVhO4|6(X^+AZrO2o|JiIwzxX>+ z1pye;;gFaFd|+Xar@yp!<~Ck^M~IxLAui6et#7y8t|NT&XYxJ7cz8b6FcZ_gulCAl z+!DDH`*ioo1W%*i+K3#svP8h9w3Oz=aItYgdZJLu>yPk_hWcFO#P`0>_N_Z|b*IJc zYYrlTIH##n6A@PnASH_D?Fzjq0z{&t8OLabSL?1XZcmc|-^%Xgi$M!~IU?*y*UX%rsYs_($_@&d76)Q1EwXctKVord(5kU?v1kL*-c zYRw#<->ZB{RmLnfEvZ=NVP4!%PR7#+C;+u%jNhG4vDipu#k;R$&rnP z^7+bTwpcD}3LB>9SKJVEqVh_G_HT7*TT8{KojUVt1^$kHI94fwF#gOI30pG z8EuE?wkfgK9(1^~_KECI)NR@P13h6vyiuK)s)btrsdxBVX1%gjaos?t?wQwTuCYHoKs#!BUVPOJc1cJ!wiVEse)0%0f|bi*R|lcPf(? zv#KUZUuUa!7{|Q`dJ}jz3`_<{#w?gpHwJM6Y>8|3+bLKQfM)7c!Lr(Ij1Dv4^6nC3 z)dbOrNU$HxcT_uY(xbB8ZFGBDlU?D|+qs^{tr1l3+WR|~Wdwu=f*mNk%acnzB0oD0 zUpNyjzcF0B^v@SPq3i$qBs>7R!aC|W(9Q4N@~(%F6QeN_xT})tBO;8eV014W0rH{W zzB!B5GVRxci}F=(>@YQ2bUWfgMK6LF131P4$f2_xxZM?LL=nqpX+$;PQsyOpgAadB z>v&#<&A8lsBWze)0D* zn8Xd~$k(a)1vXEhM?iQNVYC$?LBRqv-a}<(;{Q?-QLQz9ccs;5t2r%y^?0wvrjUZ zcP}kkcCh^X*l`K-h?}4r!2^ck(`KQgbT*DdG^CFj-DtwJgQ1T)CZ~ha<%@NB^tcO; z?^zC9s#x%EBIKu!R2au#RPTYFip`!3-2O+UdJl7T`W3!+YgIF$U$y^P-a+2Sjl|^r zURrX^S04N*smala>&SB56)TFwO6gh-D1V$xsTp|ci_gNe_a$50uqX~SP-P_^Ji%3a zOe(_n%+V)X%<0cpZDN8?>Q*BR=~|?mFz>)wi1zf}e_@I~wRNMfIneasMN+0F=(yb! z^lo`54_8guuJr_ak#`gJv{{$4@TCsPn&>ht+e;a1l&ugo6mX9TXpH%^tR;57$s+I~ zRn2mE!d8nxghAMYWv_)RS=*{AsU(6p8WezDlh(*xw4?oVGjhc1iWc$XG2j<~&!#51 zHK(`ij{t{}afG`74i%-09I9=-y<&VsA>d&GbY>2`IEoVkr=vt>U;aAuYr$iVt~j{d&s6lzP`!I&J#uzAA%JjxcG7_20`Q%%e#zu^ zGB+$d54~NIFPJn`;th#U{Yz#pvjs%^6hRP46St3{Q_$onaLo4Vc)J-qyT-@LNf!kqry`2;pUK##*p>Eg>|Vicz@ zqlXuwmXxzP^n?8VrJOc&p5yg~jT*Ju_{D8MqKljd(~$MV6x^UY9luzm*m?5iPZa{+ zSS!wui$lW-t*NZAd&7@g?DNTstSfu@ckP49>5L>HbYN<5=DW#CPso2|I37kLkTw9M zPW95Jw13E&(&=l6!)63SK6hkqGjF}3=F`;cM3>16lRaaHKJJqwcnE{af!KvG(vRwQ zaB;yFnuH&p{qQ_#ExaEBqyj)nq;xIY*7$SDg$C9;4>!D!OKL=MLtm<&*$a!lBc?-Y z0+Tp0knjE!+VU&TL~=*gRqnJ1ldfTs?LGOziX`a^AOml-rJoH`@M>>1cMbB%e6!+3 zz7w=?o((Y;ow>0$D2c~kh8atzqJ~@jAq&j7j^AhI*uE2wU%GkW)l9oyfJ%g!x=uPt z1xJo&u<>Y z#h+KKO|szjYjMGzbxNxVHuj7Y8^VQKJ}GptkiMESIRb30pjF+UqMD+B-{P)%POXfpW)k*M!Z)iFfO^`pv~nLmS@B$4heJP>LhpJ-Lm$l% zH<&FvoF$}AUSNL;raUGPpA5E}cvZwjniiCHJ5gS9di-N526I{QWp3fo;N3@+*KgQE zjG@%?oemOBh^T|0_FBNoQq0-iAz2odi};84?pSE>*$lfe?Y%Ko3`FpXfQY4U`22Zk z>_YRI#m1I+7xRCkE@ZlXw;lR?Huv!zex1wb-#@*Z35=9}ozP(@IhVlk;rzQWol{pW z^7wzn&1Ju@Is*%N#G&_a`csT|m`*W)d}*`UjuDIaQ#bAneWi8S+=I&|lpjsFVSIaT zd-qH@_jZ*aS2o~ispwufjxmG6nEFs-4Wmj(%$~%nNgC@{VCl5j9ZG-1U5dLLMX+Wq z*0ZE)advq4;QwRqy~CPXy8qEo6j2cs6_pYT#YzOFL!zS6ML|Vr5l|56y%P`t=_(>9 zO{7VYfb<#^A<~szga82o1PCp??7KPdIp;m^{e7SN$Nk;=+~>K^Ya)BHXYaLVul1QV zYu3!HnH2#CZ@%#e!f7|1oni0}Y8zs2*k zJ37!_T@G7Vz5O+HR`=~44241T#<^{D362nq?}p|r2X+WWN+%+we5|Z4=sejawmd@c z{NxXv*dExZcRiprdO0gTmf_K2D+)#YM&NSQb8yCk$9;=r%{!q>_X7|fja}7mmTc9W zQ{oY$y-cG@kMjE`z7bF6BA( z!^9V&`J2FovJ&95Nh5u|JKi?iJhO+rRwc365)e<26_O~P*SSx&9TUhY5-2{9s(-b0 zu_U{(s0srtnWiYBY~puMc#;e7|${tDD@A}}wF1(SZh!)}yxkP`=rKpN* zeU7S{g-3AQm7-vQ!3H30lf4N() zQ!}W|af&uy#}mk(uJME7i;}z@srOa!=x%mQACa9>vh>KCS`zqnU~s&zuX3@V@U2Wy zVcqVY5^?Hb%k<~kQ+>lOY7Xo$9!B<>cHo3GQK-$Zh2VON!H!w@nW0O<`qjNGtjdD5 z-_IEG#bi!YO~u2dg)-ptDULde$1|eE;nVc$JQ|x_GA2#g= zjM=0n6BmFd{1 z$O9;;d1IdH{gB8+$Z*WC0qg zqUn~fPE1#h2J&!p<@~N)%u|5~&&=ogGGdI}Gw^`2Q$eX0X*11;nKp$-F|y1NtioeG zXy31NIU5ZzNyp>XwPp;nN+CL4sXcPZ_5rJgA__#cw<`xWG_V~r0xM_Zd(<|D1_uZ6 zUdh#Or=8L>s2jx@LR=C-4)-Y$X|FL?PL)5|_TiSB*Sj6z)8HhIi+*CAztZjFKZ}=+ ztV{@2IWmV1lF5M--H`CnZMzCwUAy4DM{~2b3BNEr)B=-aJII|`7M~FgtZYE8tEUGn zOMgs3V*-}*l;Lm!y8OGdqM9vb8HZUm=z%l8;o}jUAWcoal_l5GjmQjL_M2qh@2?SY z+(Y*g)%c%qzmzWFb+6}Qhqpl#7iCkO`+c!-;$i{#&sAP46J@sk*4FqR)7PVA3iI>7 zdhcjV*&&hkGmLWl@~GjHm~2-&>t>lYgUrA(b$GQz>AN_5kMxb#j=ioDWJ!8w9_;iD zLnK`GwL^l{KThmD>5fI-4k3>I_^J^B56Y>b=P*|=08G_ui8iWN8r2S4UyH*@2lfZ9 z$D136zId8@{MPhX$E7-@lMX-jIX~)WYjSgRpRs%(`$gdUo5JPRn*yowYHQzxh{ooD zWHCx~hzb)W>@Ej9JZ0w9HA>(_3thy?W>&S)k~{COU>^nO*(}iyVGbAlDB7z$hVP-^ zQzqrHwk)6Ip2^9{)mK@X2={CQJ?nk~BBpqrE)rvP zNUGS1p@s~6B`G1rT}s+c%&>Cm1+&%JAnQ1^IEWSL#|jF3TRF zO<;NEwT!HUGm~Xv(N`U;O08hthX{QHSR2pY>8Uf zxt3<52WBL-8Xs3Ez2ATP(6r)m^8MY5r}`K=*0%7qZ3H^SKZqHF5%gv`W7rf#dLV~x zt{~bb771-jZG);vp(KL(Gn;RS3|o^Acmt=*w7V)dtB!H&nQRjn{BVlv!fb0KgtX~P zfa>URm$=Rz+qi)&v+0^fsFC}-5T;ctdzc2H#&?bHb~Pt$+hua_#Y(}O6UVqu+!E!k z9@Ff_y5Cf(t8Jt?`}-HFA7zKm&CLZK;))a*($JFfoEUNU&Dif(ywjxy`dT}q_s*Gw z;~)5&9?j2+>lVLE<-NS_gizoL-XpXPv9HOeGvKVlh{M3Rh(b}_$I`Vt%370LKyG+j zQ#DdBIWuavp*Q_pv5|jPk=9;xi0mA z|MIJl8^2$bX6!hYZ;cm2WM@EJ44bqe64=RWP5g3H%9t~fLsTWzuji&XG1_L6Ed08{ zDk_rjC>#9LRa#@P6z>h~>%7+<8uGe$XN;Vi%BYwPtm^}J^BnR00h&rAK~qz3;?Z<8 z^BiQqO0laJOK(z-rN_|Qd~>*Y05aSpZvBpST0ckbAa8PN?zgIdIO!&Sh@>%Zz-z*_ zU?B)h*)n)$xU{WF@LtjV)iF(|x$m<45bI~7YKhm>x3HvP@9l5L6bLc;+e_q{B17)+ zw_+|ljSSxLi2tEBv`u`!Nbx~+b#=CSLj$&vH67hBhG5L&1uLD@hkJY7zTY_ZAZya^ zyZycdxMYI5-y@-gu(WJbxpCr zmlJP=xU>$WY8}ZKGWQ<8PIVYAu^K42E1<8hk0}29+1${`yrDDwqRVl3S@TY=n@;+> z<$iB!@^*`G`y3Xmtf?-ml8AryD86@SNbxsr$h+S8;fIY|nwoX$Z@sDDBsk3Payz34 z&y%hvwVMwQ9gzy0cUiXV)wCj{sS6>-ma^1a&#Z|&H#|`P;!K}G@bBX_Q`zfnVxCfg zHz6$E5YM;}X#YY_0ZxBM5gd4>DW30a(%`495y);q)ULP7QzkQYm-5Zz4<0-yJmTzW zU+7Q)5q{R$BOt3B%y*WScl|!2`RxPs51n2_^o|#oBl(YIW?iVk`(JU_6k;niEUeDE zGHoB7O5@?-$@BB~yD1J2prPu<#sozXUPVmj^XJ9D*(}SYu6o_|m9*zQa4ENmE+e@q zqpLmq+!EM$a~nNu?cKY%p_eJ<7sXy3IC&$ObsVe=kVuK1NQh97o*USdy8fON@?^|u zE9RmLKYwNon0uc`*0{}{IGY$vTs&0t#Gyzra^P7Q1ygg@vE@U-H>n+7*jsiTp|cdp z4{|{p!osPY*-g)GnH-FrABnjr`t|lVmDUd>P)}zedqVFO=-CP6{X91P5JFeof0Hwu zxfWx!Y^E}A-NLk1e-k9i{IqNAC)T4}FsJG<_SbqMr9nV67asvEu@}{!(Y9!9=5$l`c&c~HG zn7*sW)Npx{pPmK8Ly&x0PI}WX^;V~zL;P;;xKE`_@UcGX18;JVsRLLI8%)BD8@iinQ$TXKkgnKUD z@%Hx4AjjV)l?0wd{TR(NNLBg;&Vb z2Nv)5oLUxy?U{-cDa(V!3Cvz&XEuzUaB18-Nk6D+0Y+2qISq47$2v6Z@yu8q36Jw$ zvXEQzk5o~V;N0(xKthM!pUIJGQq;|-YOG;}CW+IMi+Yn zQ!C{-NGSgV_lXO>+@+eazRof)BWaOD~w)Y#B%i2a}5g01PwtI{y~X1;6$vXDTN6i z418uYF6|=WIn0=eQ*iiXoq}IT>D5cz|&-vrV$Bf#KgR z)D&j=*hXj-eVl0VQQY20uuYp=t7L^~jkhE^4pa7f9t62Ky>C@pu?Rc*(^q}JYLvlh zx%TVBc=m_btL9*>xEKY2EnR$?S5X$p%d$UOXvC-dPQ{xWUTtz2wUlsxYu_n2m()`~ z8$)VavO>Ra8$6~bN052AgV=DBd_?QA@X`_5$Jv^WDf^|d#ywRI5xvye0{7jFyVtDq2Q1P=)YV%S_v0DMm-=OqAPf>$%yKs=ip z;4q&l`+iW^*>cr;vdN78mfxpRJ$kKp1lyhxM(A#R+3_0b8fm zfagp;fRyL&m9Y#a|8;T$c*fkt0)}4&1o$`y{RCLg{=PbdA;6~9hoOReJzbyL=2JN# z3@%{p4?@8VK_=njvu7xOZ#So>k4%Dbtl)oT9$5nz4Ccn$XU~HJd_0`(ZLBQrIIc1o zjKA)r3o^ev4-WM8ba8lMjWoR*&J3piO_nam!WBmQdAd2;J$h(na;FCPGxNY2K&R0e zt97OCeH@?KSelubSk3%>CkY6l3DAF3R%Hd-f?1~bo-;#eTmT6y>mOOn-{n=c=+K7` zOik}pfY<&jYZ(Zk3eZ<-s;krdJ#0)(9!@essDEWWqwb<%3tu?9y16~M6GVUZXT}on zjLO3p&rJ7fiRCLK%xi=cD{Xw+8}ZYpgEOa~JIFbymQ zKZo!%tOJx-G7kkh@{3-iG zM!f!KAox%IlfaZ9eLg($V`XuE&c{qIT}>S0ugrf5G&3mleQ`m~nlm*_&|yP zvn41r_;qIFJ+-SU`J50?Ok1J@@1h9MW+OtMJKejjdG(6c82zs-BJhm7o0{%rp{J*R zQ%lL7N(mwV>!w98hlkz~85$DC6CPB}cm|#)g2ZMxp_^P3+H*RD1|~QJ@YDb~0X+2w z2)e2= zy&{7X;{Q=|nG*rGjBel5)zmCar`qV=O>IpLxQc?@1U#0OCR|3CsAhcq$OQ&p5bf9~S& zKadbY7NAgr^wclN%F3NTZu)@_2iJb-8I0%ef{|S)r)hQydmfJehv8^ygww;b^3etF}Y%? z$7&CE+zGxjbLM4;#4xzd)a=!x?Nx_Ut7C*7UA})?_e|Z%ilR)@^Z5gP#Go`1uuL{3 zj1iXtR{}M>am;Si-L#F*WPSf-+))agndU!n&;ax>CRHZ~^Z&13vkBfkGNitZk$3-7 z?u%e3#7F#py(_s%eD5kxu=?Rg86W=#qBO2x!v92WiHi#X84kTdIsN{B$Oa#^=f98o zgE9YQ;J@+lUs(KIDgFzK|H9(G>h`~l$X^)vZ!7#4CxZF^Yg_!S8~~wz1vz9vvs<5*PDZ>^ zaQkWRQYG$XjH^)bjry$@_0fZTfA6qf`AH93$IcW^srl&_Vv@4gp?yGP`Y!e}B+|eH zb~Y$JoFnQr`^nSoW1*pfn*uxZY2jp2EJZ4<-4bqz5NV7rFS-6TNM0xPSKp0$fui`3 zp9q;TLsX=4R7?ZM{T0)UJivk7P3>W4qEa^meMt*_dC;Wu7RVhKzPo}A-Si2saUa&w zNLhxG=VHGGIGbxS4^OT!b#5Rxz6R>&9!IL8pKdOkR_^xoZ10wO$c=p)>@*nXNcb%a zGCS|><=+tVCB9T~RF69KcK|+CG05)s;qgNmL&&{~x;?Ri_@2m%wUUcHF${gnKE4&( z@A?DsnH_@hzu+&NQsWvT1)bChM!L;2y3l>y5 z1?(E6F0Hwf^e5~2zsI)HgFf5@kc=E;E#qwC^w}UIR`d_&IKdTI3=c-hP#v}+oi4UM z_i`w#UL~7D_9r6Edg`+FY?bPd@6RfsO@SBKX$Irg57;$RoE%M0O$`7(a!P$ZaDF|( z0DM7{e4J%5@84e)MZ1a3K{Id|pp+iXpWMVRImri+_L%(bD5Ml~`S$@)a%d1EaFPdF zz3>?3KcaYTc&G)V#cHh<_Hh-(j~sX2?dw=?gOrc21n>S=rD<9_@d2Ugd!cwdLxA4q z!+vTu1n378P6{K|U&XVKXrGp|;tu2Ly?mS>?h;@$z;C>Ile}9!AfXFdD` zQqa{K-KstH>tJjZu{9VC9XB*|V*(a!U)T5+jd@4qV>MqMbK(Yh_b>K|?+t)kWW@MK z2)0!51<9`P1g(Gp!7;-(i9ZEdL8n~4NC*2na|b~tl|gRDH0^^g5is-MWPK#7%k>Jl zL)8@^aZUa!ZZA*(q*gycx_`SlG%o)aJAKyq=R*Nuyf{9++Lm_fH;h$Q9q&WXR~+B8 zU;e2I-A&7EN?2}1DsSFiJiZk3t#D?|AES%3`W&!@y& z{>ciExbT%pv)`EcDqhZm0F6mayuktBaYqI1auZ6%WnZ?~Q*PnQARlBP?=|!$ukI)c znC}s!U^qlfP)At?dkIhp2tt|dKpv&DsZ3$+5!t_si;Zo{a&_L|_#9jH4&s0_YMn82 z@@U_3PZwfHFm$_URi-;iMsrJu%5eof3&VSd$(e|tYLcRv|8fPOPpgNkETLu*3R!b; ze|IE6NvFSHrpIHOgQ;6#9#KYIjNcnez6Dj#2f@`gg3n7ks&I;L@-rayndjZ{^I=s&lYrqZm z`jz;zX5ixf+;#9x6a{?YUK-4O;wT&gv*?LLSBWt12yc=i7Eoy?cmOVxr#55+ft zbM?XX6&FMJ#!9Q4-m`4E4Q4;l?kAlDNmh$$4iGlq?0pg4-z&J0v(l|fQrjpnZdmMT zfl&eBU+)MANwM`Sf6@dgPtFJ_3Fobnrb9VYFi0gMZPvp^kcV~b!%Noz-tN#Xg~oTT z?tSo%a{;R)L2mJY)vxGiBnjA+cQcJ4IQ`9I^!vYwHSx-T%En#&FzUo={4{6L->H7B zHNN~@86Q_K-Lffc&9gsfKd2$tqTrbIw!Qb6lRyX|xWZxzrAn@X__^0~-;w)7$!aEIz|S5v z1`K^82>eo$O#54vfkJ6Y(7BA;NyDww5{pTGEoUuCX{KC3dsnm9z1M|qZdFDaq?T#0 zCnQXe!?x!jS<}Lph4=Qw^sm*`0ptXC58J=>2&kNuo;TH0Bbma2YO75dF@WWQMwd4w z7P7#)Fu6BMfg}b>Vz}-&m7jE?gcu5pl-U9f!x&YIp5KYh3V$m1KiZzWxTYF+0-W~a zWIStuSX{S4XuoayC;L6o{4}m63qg#5ICn(ADsXWvD+nhd97kNOy7ZSy+bW6|FPyU4 zEC?*vs^O22&ZuvM26H#x6;isuEi`P)&6q?R9{v>6q6!8p>szBf2H4i1c@joLvx0s= zIlGP<{{vw@`>U513(=eiS3zpzR(Req6XA`CxRsmUL1q33y|+>&!n`cVLso;I7p$1Ks2XEF3$+~U9Do--G52}Od-*W2@~=i@i}R&YtEn^`H^)nDN3}HP(xm?9 zyr8`88{slW|29WeKz3}eRV;=-L8k&BpoK~RcJBETn4ukeX@J#N%DWyK-hDI~% z!TPj^mNzTP%H!G=UdYq{Wk<0sqB8Wn16SkGbl_JBFbkQ1Qpbo7rV|wZ;#74ff53&H zlaqp2XWn{Dsx4|S7@y(X9kT`pey|8KW0~a0D-UU_I1DET=!P{OrIeNcpI6BdsNe_x z_RBTRYERCtoexGvp;*4SJUBU@P;RtD4X?GmQ=?`uZu^*92$cM)UbO?io~aM+J9DmB z0HMH9#dEK&DA5VXN=Sg!KcRh+Yy1Q!&Q_iH0DQ5pRSmP}os|bfLErk6xLb_(81KzX zTmXMlq&VC=C^A@K`AUYP<0=a8%!-9Z0=*>FZ{0SC>RP*ht6QlO81J+_fQ)fxclYHP z0BJ8-$`CnOngI{xvBDD8m%-H(6tiKGIy=e-DACVOW1i6mY4XFs>BS+^w%r-o|K@da z(<}b(fis#U48Ylnn3RIJ^^6e;LGYfsW2XL#mn996_M`pw-LV_WA;UOyG-sK}K|rA# zz+#>?1%0?>4gLL1yByF2d=YW3UrWMMGdU}(wp_jj`TqKe;*(MQskgW}+ne~sl)U{< zl$7i%lSS?WNJOzUPXHkd=hR}PWB#L1JH9su_q4=cN7<|GrD^VKs)D?H*Noetv|mS6 zOk<0Ztmpyng(tk>f&gPO1HEOA7lQkMQ8Q_##UyeBkX2fq^iP7p`V35lC4Y z0q|lZAMD-CW$L>NM^`j%o4#)=83=wTp$PR zUjTWQcx9=GO$HVGAf%$#?B>IVP)K0SF*qX%!S7=f^Q@6;3xc3P`Yxyk{_xyFpo)lT zx)pbG1Td1&Py3pP*pDn_xOkjKu&_7$wf(Bj_Q;GaP2#(Hjt7ct#4SHqd2((f$;0~h z?a{jRr7vA>OqQ*ZRny#Zro5ZjSZEy<6+*D=yb*4{Z0_qdVnbI0Y?ai21`a{UYU1e7 zz;L=hlTr2ER$I|cRma~r-MsRF6A2}z`MWu2!`ilILTt7ra|!SX3te%&`vin@pLP=t z;-j*_98PO0IsN6zoJKIs**NF~X99>Fr>mW*ebGfR4NR{7!S+5&xXp0qkMn=i90NBE zY~W=Nw=0W#n=W{*Fq``(v+gyF@iw_D!G z_}pj#_GD*81#9~od9mg?{>e^%QWqpXUrL&+)>Q7CFB=%r{Tcr`6@MjZ6g;JCC)nrs zC-tk+)9D+(zy-$}sF|(Vwh@+tLRZpvp0L*WbWuiXb#vdUA|xG7fxDe7p-cgwl~m8! ziW5W?W=tU$Y@r#B>sQD2OmXVr8Hq66k#-e{bogGIl}oZ2AyR3&?L#I=XH;a;tu z$_0!11fj+eqaNyW8o6C_Yem&iyrq0#XxDNC7K5L#8a=OiMz}ptvpp-hlHr%hUZr{e zHr1jA`pQC38e%`CLa!+DgL zqBW#9nST=O*cph~L+y@z5V)0-KO!M17_>YTv^S@k;0eSIa zVLaY&R5L0=a)TD%=~w3>_Ok2!IK~d^UOB}Gh z2gT-;#~(oG&e0Ka#N}_Emlf>2fLyV$ar(D8;2`*hfFtV(8B>psQ!S1$BU>mMB{6Y9 zr;Ne(JTZyj@+r__DqZAmZZ%eHJ-ZdrP0<{*-%-@xHST$eJ$$(?jz6=U-qK#S(!ay# zG7DZ}>j7;0;+$bg^H}AgAo!YYqi(G^Rt*Wfw?FNrZEU@#FvEcLn)KRfNVaDPGiMTI z-aC-oyhsqj%+zI%d3J7)(KTaDDST8m{Ul6W(~L) z(o+!^umSs#)8KIp!Mk@`QeJ8>z&m+bvk}Mln>>YRJVQ>ouxqZ6;2ZlEhoq>b6Z zz00~-le(9JjP#RxZnpY&vYE@b|6c+o-D{9KG?RetYML3bQ}P5AZs z;>6pJC&;_%h1qh<&B5h#fIoZd^2+|K;=iw+rsG^Yll|DzNv;-mZww5QcWpTSLL0&S zV}?+M3+F_#O3#x&6~>qjOB`yoZj%{5N0?Ox1VKl~dyEk9nQV&ps1Y2@H&Bc$D0=oL zC7Rz%g_|Q3tc&e^cW=jTk)tB!PC>v-1}i~iD_jN!5#VIXkMXIHk(iZAcwRd^V^L>? zMYoF^CEi9eD*ObLqYTKHPmFdt8D2D;6gOhM-Fg5$VE^NGKml-lz_=StwQOnh=oG(r zxG?Cmg+|=85gS&}=oZuUJPw5p|oKxIqlOUkVc|!|rY^Ko$TmfxZLg3V{bqWYA^7lb+eE zp~WnH$HYa1-5g1mA~~+M$TBGlSXQf&qp>zZ^dDarJ8ff6{5j$$VYhn(7?* zwg-=)J(L0Wu7?VqBbsH#_1Bvg#w*M{A!{1Xz77P{V0RU~gFq+IF{=+Jh&W~j=WFiD z!TwjH-h+!-vFZhktU|Z8#_U2_f@#KVG#hqO7{DQ=5V0x=&is>F}Mh{dlM=_6}@fvl6j?R>-cMbWrwY}Jaq_d8X z!kH|QmYU$(Vu;ow1_3`|vCgr`F9|$|QI+i~anj0Vip_e2HB$ANq;{uVDvMPAblZ(G z<@PU)ZuK#N+g0U$8?p4D!Mx)q7gO;fc)6&l(HT=)jb^9J9 zH%V@}TaymtC0w~&cZmG5-#rxE_5r~xu!V2DBkf|p%jv3ItUkPHw#+>iXcKWdC14}P z(>f-W(mCPQ<>C4;E)4YA_rNPfX~%5g>_-YUIo~3Z-=ia92$LJ6CwD?DcbhEwn|^+k zhD9~&2B-noExRQ~i!|qYhi~AX8BK-~^>$LPJB*oUMSLg00S$ z=90UY6SX#T_dCobxEV6Xd1a!uhCi*{TV%yPm%HLhSuP+gKf2M=B}!+pBL|crsMGKH&A){am;6 zF9pd}dQYydw4!z0ukLpSI~4I{9fGFNUGc!+Zy}L`b9IYpL$?r}QgsBN9CJ6kEvza` zxms4t(E>dSzk_jcX!+#BQcNmoFCA>jue3L>JQut6QGd@a>kHeu-Lq1z&TUwor)Dz3 ztk2$P!IRc{BKJCbiVxH-RhSdM%VC5ajh=nB-&<-gn-$~$Rp!vT0fPx&LK@m<*&fNP zQ4&7?!|t|Ic6Aos!SaHqW&mG4-cy4sAuFa&DlE+T(n|=-3O* znEjW$DHmtXd5;lGTwhculx86fos_t8=36Ez); z&8cjlvxImZ9@Y$QAD<_zW!MA|dh>+Oe%(MiOkV6hSy3A6jvl>(T_GcuD_l}a-+fqU z*jW5+I5traNbVSJa{DY$`>ThUK}B@M!eOJ=EX6IGCpyQWbrP}zRfDDeK-~v6sRr%a zWe-0Ls1BBK%Ts$Ik_5p*#vfF=bMvFeE z2Yi7-qR;z-Da0%)rcy7LY&c}1mnx8FssCm0Wm)wPE&n$v$yCg>bm!=R?Sr(UQtRC> zr@VF5cxm2ibr8kt<{7^($MsiJYS(L%o5HpROm=0?B_9c~yx7~?kEG&IK-KpM03((% zS#DVosGDLn-t3m(AiQBR6jI5$ngma9HF9nokP;r9$vmGDHJTMWeo$SGr+p&Y;>+B1 zXnW_lBOEwC7ZY+n*0Vyj<5Tm#Bdx{FRioKi`r@Ns@;|AMs&Rxws*f2F>4>k9-ql)z zzG3h;3w@3jKwELK^6A&{LnzxUUyTKK{+rkRfH4biU3>j=Q^U)KH}c*0rBH!LfzFrt zWX`{Cmh*me4ehB`CKd=_X1#$G9NQVkp;RGG_R^0Bf2cKc&AIt_P5Rrq7#=p z2fB?}lQ^PN^;Ecbm@ew)9a!Z8S|lyqD#e^Ch#24RxJQvDGwgq^c=rDIJ&)?x{W$q| z9QmHB$RFB!Mt!pg8ziMx9YRH@UF$jUD(#NLo~FO16m$ZHB(S6npG??{1fOF(W(bv` z7-;BlCa`fS*=V8RA*nR`tb4;))Sjx_({)nHL5TRU#+M1A#ndILHFfoAQgez7I^!V1;3@p36 zU0v|>cq`>{U`yj1(4jn#C?Pfj;HqKfds+qH9no{ze5PDuK#)Yf>hp#%%w_fRBJ0X+Jkyz{j_lv9kgg2o$M?=ZPvJc_uHPRIQ^<6d&X37U{#|XRiq0x z+~l76%Q9WkT?Z3aF813hkWdqV)m0j!2a{NYPBABH+ebZv?hn)<0ZC(IQWlQ0jN_yP zZGu689AeN;ImAMNn(gB)yxsAjwG`vk# z%A)(fwb;!WE$0-Ao+0zJpOZN{z?U*pU)Z}lM>NlLyBHz(4HAt8gFiC$uPpA(m8mo6 zy1V$Lkj7Gk{`{WhZtuBpnAEPv7%)`w&bWegeYs8~A4Ix&Vov|SljuGD^Iv*isU+!` zdmRLgasX~@1&{HZd+{jk;#2fnxt`h{F#3cRoy>@W)zsn+%&>cyP@}94xaOkS8PAcs z6i>jPwT4B_dZPWuoqci4398S-y)lgaM?Yiv4J%nT+z~=x?P@+UVts6HiVK@}3_*(+ zI|yq2p=Bgz7RfU;6*eGW+Qt1)(Ec|X#Q06MAJ=B4uAwwjrpx^USF||}|#Z!!;);>ihv0-@$ zRMg-Ox5}ISFAN4lxF>G73WSd0C^?gw7>0iH!Gv|BrzH1qn zwR;s8mX5?$PipRQYfECuJN>eVOCuPM8GW39W&oE+Dt!;H=UZ?0_b6XWyoOEw`Bp+E z2aB=lP${xW-Eu%xh>yL06xZL@Y?4qs){Ggd7<}sQwnY2|DtL@aMzVeHK*J0A^Ej{A z>)6!n^V()091offTFT=O5D&d|`eJoveKLjWPNB8GrPv zy!L^@Gxu;v*>_k$SmDDP+=e>Nom^-^E&!whiV1Hu{BJugE_1hSkC&hi+X!mF@`e4T z4J)m)Khwjk-^(=)*q>{3n?Ahs-a17oE?!;m=R|?Q+&RW3l};G`x`)5Hw4tRB1R)6& z7J7MhN#t|`;7r+$1agTrXrSiOdhH@Jqf*}`Vo03z-Q@Pv1P^hzn0lhXx@VGdFHp|* zS01X)YhSbkc6g$FXs}{4&mbcR-z7cdz$gg`E-Fi+t5l!DO*6+v-Q` z?&~B)x-dKY_gx+FngO$?gf%G*Qt0~!0bAzMj?Q;*#j(wopHv3!CKD^-oV45H)qdRa z;lP%(SFc2kcN;3T$CpY-ZRmx=w+~<1J^?6nzD+hxTNDE2aWXo`6Y-@GZUKM_q-=CF zA^GBuM&r+S>j5(Hl)usSL{aJHL-rJ4YbZz+jo~n=!cr8J*r%Jye>rAb{W=Zy1s%48 zbY9|K4DYnmL)I!xI2~@WHf$ZwcBrm5(UIX^QYUZ!I68OwqKlVxp;^P5U9EbuJpWbAqb7Z4st4Bix+ z-(bgVe9*5tumA8y6N>vn^Hi(WF_|CasB7kO$gTM*f?*wnBlf-O6tOlFwC^vWlco*y zgrM>^P1pH41EK}O!hR?XX8p6Os~$@nzFy@aYJRJa4>y>!IbeEVDbrcf$SmJj#NyQs{?uvx03(>V54elQ8##XeX)N_!57(_67{CcMJ0e)! zr03Uz>Ol>@nQKu}yrlU^6Bk&|36mO;Ku~J>Z#uW_ZlEH*w)@^8>{q#8Jo59RaRZQ~ zd}v^1nuxPeXD|?5k#_HARNbFfNX%|C1JKp8Z%rrYVASxlu;^7zysK|;a`ti7Fgx~kxW(cdKZFWqz#Y3(0J(Tt+<Lthj=&n`H?6FxDUG|LEHMO*G zoB-+pJDILIqjxWg`sdsk;2a(lPLTam&|FAHZq4voZ8-FJZ%uMj4A|3o7~UMK^T9#f z;e^8lKCq)hH6HgGbHwZwMjO>Wi8Z*uY!s>-?vit~n7>o#|9VLu8@4yUp7t>z_4k6I zf}*qJ4V`H}^F*bfAw_U^X!V_rGuh_8@uG_d32M=&+1d?;U<-MyMKdQxo1pl`9-dH# zMbyMPhrQEFlk_~ji|CSE^*H{js9SGwz2=@Cl;t_5Sz&Y4#kh7YSw1!+`?olQ1kwkTz!JJBZl! z^L47sp#qnw`=-rNHN4t z8s&p2aNu&^`8+eN@r#?aqcT#*z+sqR!q-C~mSqo5UJ>6Kp9Uykl;x_Srl3C2b`vb0 zm|K5gtUzSEQ$e-Mj7()^lAU$zv1QM98vG`K1q$kvw-NYQS#^UAqdnv>>qm7BudLnd z6o0mGsqQy&G}4^LZ%}C$j#_+R z<-BD~GIhbRnqgciQf;Fd&O)sJI7#{$fuiiwZty5mI;$TKz2|gBGjJ|hGTEu17d`&n z)`7M+<%^hR=IDqG`&6J#AkkL5aJIAdSyr||SUCUf`3z2%SYNT*U4b zZg)z6K8TcrGCGfA4S#&LcH5nHvh?(r>w!6tErfu_dlHq~)NDA%-7VyS&C{}mLfF&| z7L{SU@+9bq#f<3DIsH?0=LxSSKJM)ZSm!9ZYKI$DV44S+7&NM=)O-lAM>kMlppj;d zx!zG!e<|a>O+j7qWogjEA{KVm)wNh;o2NoTC>U##G&bD~kPhZM#q1qmww#e+z01ux zk)t4UP(g0`R|k5~ljpekVp~mibn1X6{qE0KnziNUW_A)L09p804s^Wu`Ry<9$8!us z=qI<=Nf4xk}f40+VL4KylFt(^#BR{^n0Hy%U43<|(L9~?9< zmPP~0f@}M-Pfch0*=Qth_65fLLfz_D3>DbVCYpCui3SXq z=^c~e7v9}`A!Jx2x31D|WAEOI4N?@G_EoTPhLY5{iED}THgUgE+?l37>=Bv}lD!k> zaufIaRATwa)Aas@6WNoeW_tGQLs!rBEe{-WtuQd9rv%DYl8vy%*yQ&qDd9mf%hg|O zad#sP<8cSzs8+3j>22uGYil%r4&mb(gXI6lrzGg=V1u1K`?>u%YH&%N`qCKNW=OeT z(UxUB)YxyWAF_c@D8!yJuCM1vce`AdtDGLhK6%>N3JL#ye71mbptrcjGbW{hP+X}{ zd1Pq~nF{U>YXJnp?Cm2e-V$>^qu&*;y!(PBwoL%yDApRX8XP;f5Q^x{fb>JR$IN*| z?_+*1zpaJ;QpL*>2yPH#&?B+A{VSYx4NS~a!r7oz7*O|WSoY@7Cw;BDwHNBwRT_qo zOB;{rH_6i8i1bS2fkuc+(K@VuR6(ocp|b+hRHyr=+;7~ zRh~b3DvLIJnqHxtlWa0~9tX^*Tk*VEifd!zzzMJz&Q~ztJ4rsKxlqt{(_A4*eQRF9 z5}o(wf?&#(@k^V*S_lTU7<9oocdBa$Z=>unzmw~evml&YKYSqYqEmowoS$raZK^5f zX@}_ATanLZ+4tx9WV7D*ABt<}^`4#?i$926@SB#7=u(zP9lk5$mL_{z+0SBn7KCn? zz=ngqbD&=TCXFYjM`NzO_98z0J`NQ+P+GnZO}FXx z8*PcV9<*Ng&G6k*Q`JRozVeHnSMvAdjsB)}`;&FBD-*4t~9q(-ylRe!mt9Wi(xzdtv zR>Gn$d`XMGukcd&W8+M80lM{o2wbZr|Bt(Nltvcj^WUFSre5FJO-PY*TEJ5sB6ifqn@oqMu42yX6RrB0gdmk&!|AW2v zjB2v$+C}X=qJqk!sFa8_5di_|C66K^AVfr(lt`B@RZ1W>K&69#)Tkgxjr5*~^bS%& z4>bWoPeMq&cX;33zCF&_WB=P{jPw1@Ds!znYt6alyyi7MWxX05&1{RGKw~5nVeuc0 zyQ&=;J5|_`d`Hf8do_h7R-0G5@v{TBw4x5dO5eVWQm3>o(|c#aTgJ&Vh~!gm}|S+vf5NHAo*3=Wc(v4uaN z*j5-Bt~Nj_37&tz+axW6reAPL)N(hMk=c6eVt?$pFn&asiCRb2GD3A|z;QBhq8iNezumxB5@}f^G`QgLm2jNqs z<%tYz9rp)5-xB%gk2v9*LoH7{a4sbSIK0rbij!aq5lcWD$l*+~K)nqUjC8Tt_4HP& zPwTRgy)DrR=o=*?_Blx+`D>(#F=wXQQf-BOAnPUTMG0(9b5ZRzBIcyM(1bnL<;ee+ zwhT3RttQ(Q4W}Ehixt{8n{giwJp09VP3ywq5P@BbkLMsTr*U~@=eE6Xr-mZQq_7lo z2Sx^DB4E0WkDHy&H~4$bu3qvdX;=@BR{Pw0)b4^9(hsnyGSGOLUoJj6Q-JSp-7%%z z5nz{i#oTR-4bNKKBXS-M@SBLWTIJg-1D5VW)Ew2k+MF5(;8BR$=JrK&8ay4G4b*+y zjUi7dcu2Wy1~MUZSLYnP?SkWBT}2ZXFW3DO$A_AgLRD*;DJNn;@B3SHnp9$ab*i$$Lq2Y2w-OcuiDi-V252nV1Q_cS~AsqfcxYw9diHK zUeRVqz-CBDz4zhEymWW_S~het%$K2b4J-N&GeShsOhu}SCwG1NSEq~(e=a-b>YFnb z^XP=3Qer~jMX~R2QlUpGm_k9dL&f&|e~Sep)7= zSXH6~xffuG9*r_4tx-eq0W zP_$49cp}!spNyPvNR`r0a&?T=Roe(4eHD;g%&@-8nyBdMCU{DJ z*9|7pD%l}+sr!G7z@RWNa?xqSI8LI7SijR|E38Z9;^P3SlTp(J_DK95)XpfdVr&{M z1ybERN*o4G{Bl(N>6UMDQ6MGPt3 zzOK42Q?A_1Ud=J9?g`j#V0oLYvp@CT4mDNf_6q~e7`{bufyHn2dOnU1PrbA&1bUV1 z*f;Boc0V_ilHrScHxgB43o=em`D(V`4<{#*0|_eXtgic3t@px1-`3p}XnbO(3mpKm zLL?YY#c#4OaKo01TCTJ8J>d4hDvv|aS?g1uB%#k zKqgn4Cd)0QxI07)L(^uSjf8o-e~(rTt1=X>X_v0a710EG>(9JR%u_fxhk zz1Wc&KxmpObtpIQvFDC=lYLQY|FWDY+kCakJF1p#YNfoJEqb78Qc*OZMF^3~`}qfn zj68KJD=u{+0%0`o^?0PRjYBsNZTN)axYc()xw*tX^ zteF*zEfJ2F^t}yj(;m!&N^SLu`&G%5FBdI8vew>O5v`Ch$gk&=w}k#W`G44Cf|4I`9uE5fZD4$y2zfv2@&7kCGagLWnhdMKtZ@NPa=y z&5Xpd`GzNJvL#&)Pi}G`}`x5CYv-KjBnMYRe7Cz%a>~QsxUU}I+kp>y>oav z{7`QxX=zvd=M8K0`d|C-!?o)Xe)M-2FUp^?*`33OZQ^YL$)jdK0o#k;WAH%FL%pu- z-4iu}$I*$&7fYp|ua7^$2!2xxIjIO!Xa^Dw7@}4U;2CgQN_}iH zYKhAdmjD?rFSXj~R;8v((WUulr`@9Ur2D<~2t-JB^TXqc$!Y-(z7K~w0$i7Ca}kqL z&J>BoL|ovaPj_G*vI81eKH}Y3kB9dg;q?14pUqsIXV|V(=!CNq=1p~;Pj;nhfbIo% zW68gzFe~|?H=@QCpY*)M*|yhY?vlwe+n>D86+0x4Y~|Q=nYE! zW#2LfofSou!b*cwQk{zL4_jjP5k;!J`s~_?ILM@)C#@_mAL;F|0$Y_%&hF8t@&!w{}nkzXi(|~ zm>dZipkpM&kwd|dTs8tj4zx1PUkkmn7HxtV2f^0Z#Yz_Q7hBNNKE&q`bAMUmuD;_F zenIox0-ALb3K@P>SJ@?#&GqiPHtsVuGv@c+bIG+yXkHlR>(~r61*QtFEgKUqPsAep z89B{m@?w)3Xxg9kmES7oYr8e`0t+4 z%m#*^_L2FB_rs~z?pMwJ`rfI;uzcuzd86OVw`^ia`!N#u{UGFuy9n|4@H5d;>iMmo zMbE9~K1y|*wIn8i?XQD5JCY2^(I%|x`#Gsx@K_mZWy5PlD3*Up^HCeSF6FO0N~^(Y z`^KrgyboK)B1tUWaFX4Tt=h=8+X>8f({YRA3qe07&0Q)spW!A*5ZbjX1Zf=$zzxsC~pN=vn z2Ku^6$~_lZR@Yn^<4H1$=IrPUZtU{9IjU1V*}v#2?2+HG%a&G~Y-L_R*4_G>LAWHD7i`L>lCq%A@_sASzw;*U z1z!09cHi039|>e4gk@xn(`2IqvXWMB?LSb8+F?LYDg$rM5Fwe@w+o452glgj&ENA@ zWC|{Z0^b7Me6{HYS!$E^hJVOg&5?q%Ap$$R)39)xBy|XN_-a97k7z$qI-_fW(({I` z&|X-aUi>Qlm8q|th-q$^C6@x!xsQ^xNPsjaGJF`@M#pZ$Wip+BnlAV(M>Qf zqSG*@CYI1Hr%dJYwxY@Bc&k(J3DTPS=zZ+&ca62(g0N8QNE=b#ZgI<t zHza)DY4&{zc;KzQ!exQ(+DeaWdFk#ug^H6w#oLJn$Y7f$s1$TEM;ft)8IUC1AfXAN zES`$o3g(y!bZNhX`v^)!lS5r8_9sc|6@Tod8qJ$i8f7>U9`--QaCc>6CB&lgrj71J zY5^Njg{a#gvw>qAT0vj>L@>3AY)C=5l=WXrg}JSZ!<17VhFb0h=#-Zx*f*>8+2%cv zhp8n{PF?g^iiTkx!g&KeDj9pX|9e0Tg$fZ)iay@+uec?6N^L?*1hbcH5_)QnrS!Tp zbcPP^m%%+uef?Y!^3-xKII9SQEc7y#I<7J_%I-`az<~P_GVKfZY4B^MKwP#4Jqp8q zY`fXDl4O9!+i5{AakgqFt9q92vOUkWR`-1)z#NeB{!Bqclca{~}gQ>)e40a9!o8@Fdu^OG78*;goYBl8vcBRySp6WQ1NZO6hp`E zOauLyDn!dI6n)bx`cALVjmL}WQ_rH^^dzjLK?ev8YP2IbsodP6vLc+h69;EdT9GV3 z?D%tQib3#W2SGpAAJSma`G|rw6Qt5JtG{=@cwirKts37CHPs8-OH+4Jb_C*g-Ci%X zAYeIY(ymED%>I+x)-@1Am%I59YppkUI__bQq<`)=K7#-N5u>(JaiZfpRa_oBLKC3a!D-A-lC$JnDTIAS3L!A$v%zAsLxxPBOtcmlgZE1 zWp-?~3Tmahe$=%vRw?oZh=3+-q5I_n$Yk;ZK|2^z|6SLaF$tM%8ZY=5D{}yblgD1R zAoBedBK|R2^7{u3H9c5u`IY{Ou94$6wWQY}aN02Zn&L-i%9-1z3*C;I-oYwO54zcR zIS84I7f(bRt6UD2-klfzAbsBH{p@#RmQA;8?uMe>T_0Fm=Pc2O>rcXqZDdD^{vIUJ zeBo&ZYZUbY)*lo9V1ej$bN-~TuAl$uxv9(Tfd#pmxgF?|nD%h0boPzPie~;jc35QK zWCD)}<~;FxsYcxr?UTdig97$j#CDqu35iC+Gu#r!^lS#~B1i;dOZkOQ0qip7xR1VxbkPyQBqCI&l^oyPjis%JOpU8dM7uGseNwJ+)0py*AyOeq zbF|ljvCNJVi&` zdWb2jhBk*c-J04p66I8%*lOQfs=CavZVkHNqg#`lKY>{olGDsJ+fC>Y*0+sZRKrAIr&>X4{39mxjA`kDVWkyqnsS z^lFbzQ>_J1bd-!IzvoDwmd$C>;Wt&$e@B^ag~h~8hAu0CCYKT*DRx^~*LR}A+WM2W zqoslcWa#IpD%G%pB2WoO5_C1BD9=W7_d7PzmsW`)D6g9Nyx?cJBsuRiUJFx?(?JjkCqJzwTYIgS_~#0LZL1#M=gmO^pm^Z6E2ih0 zgZJ)*Y(^@;PW3i1n_YBZ^9wp*~xtn0AcC|s~6%{IpiTn>% z-9kwOj}5gFkiIMlRJX#T*B_+r6tnm_Er_(wPY}yX zkYN&5JPbSZcjmZnHSG5g0a_WWXf}-OZIH_D69XfG9NBOoxywpng~o-}sz~7K0s;6+ zH*3PV_kI1WGYjNhsBX)%QWA^9)i~3!2Y?y04I7dLzt%sCJ@SKRUapwVcPMrKOS^=N zmvKTbrUA>`M4tzf%Bb7OKEw8S<>fy4!;_?^3E5F6l!Aif&j+}V3k%s+aBw~s-6^Sa z6@W5=8%V<3e`3R}l{ju>>WnR(39-5KJ^Jnp+qPhrJBSKO+K`J&TTEB-&vt6))5R6y z!E9RmvTCs*G3VD&k1fVjQMWcc*dODi*gsovo(LG`UAoh0g1IX$d2O9qwaI=YqW4m# zQ|(xfFpXaV0SXg@xwglH0sNgP5E|#UnMh-lNm&N!52x`LFkx4jK&0MFmKER0}c3Pj{ zEfLFq0PJZ5yjY51UTEEA_<@HjjC;jsIO#KJRW%oa&wc4@!;Cg45{VZC&Z{F@I+M10pP>amj zXUDSos|4>OfL1HsbGQd`lJ4rPYvmR?{<8(83siJpzR&rb$vqnPm094nl+T8P=t>4;B{C)eShvPeCq&MU|hCN@H)ILb;yG| zsEC)pb~&kk`PjL+uKts2a@*6w=x3?rm&{wn=Pj&!yzXKtM-Ct=vJ&mgFn9U5 z+h|hv0u%oG?A9&YzP*65GJMy44W3FR_7P0&@27x6t>qw})g+7}*hntfS8;=w_yfanm%?FX+XbiT<^HL9=Y1l$SzJRh%marp}H%j3tMSPqD*@;lx& zFQWS|8*XdG(h>;i*u-qBoKG^U3n?O+B-<_y0nJu*(%f6G237{TvU@jfiO;Pvlh`CBie`(b`r`x)4j}dnW zNiVgrYihDYg{Sp_<|u)k0E%Shu#amLq~j}7>ea*#E5S2tZ}huZd-r3Z10Rf^fzz1; z;9nWjYV9%pIrDzL!V($G#5mZFWkr%q=weJCCwwU#I*Q?M-B4w%_?nlxip!|_4BcO- zaq57S(;n%RQcXhp?o5|1#%V?_7Y6Kp6Xx|033Cfzh!k=A|7X|#-8SViH0cuqtTs!} zy5&f%4&bjZ*u&dihYzhD(`SFAW8AZY{w4D)Hgiiu81$wM6Pn%B`Cy1g_kth!t(vMX zBV|49nGi>C*OmML+!IkfupvC9Y)9O4b-y)MbE#+Pl-H=Cjo;E^zATY2{Q$1lV9gcL zq4^}03@r?mp(YpTKY^3j=r8{182yv%p!I!{jfnw)4@S-H0D(w}E8AU}eP0Re4DZX0 z^`w1qzCemcMEJ!N&u z{6GJ9L8vo#J|{V4Z2rK#xVQOpqQ1~>qN^#GO+nmThEN*%1_v9 z5i)xsf(Alc9$D9>oB@wMrajIb#I-Q*IQ2hD;c5nCJ*dfY%b2?)Lk2 zupuZ)t=Z&xZumag`wFbZd+xe9{U@(+=(r zB{V&yi)@8xD!|G)6G~2wvswAUQ}mhrlg7rHF=5xZ89BD2Wmkmy6LY*lq|77|c&-Hv zUC!ZEWqSzn*gruUXe0HEC)-8-jQH_Fq@O)I>&ks}9y~c{2Bofq$EucnEbwOx4wq_H zNrNvi*z=22uSw3B|2z4>CJV;?g~(O5BU*1vbw`22f9a{9ov04Tu82D<@rJ?)I4z<{4_0VMRtW8b*mOM= zN8!H2TJ>lZFRf^~lba!rzqijpus_Y%GZmonkkl?o3qmV~eJ#a>L`Y!01gs$%i2$WtLh`gU6z=`T{g-A)=j?h7984QBdTeVPfJJP#E}jq zJWyY@Mp_m!^&tsW)8@nr9@}r{dyN;h=C3`04r3Tg$;JK)Zd_}fRqCyJv6`)>AclyE z>uDD0Oi1$nldLs2S~{XJvC0tEcSoQuCDOcWC~|!5Eh1MhrWt{SY{tO@beux;iAK9@y;4WF7ayG>6S#o zL{FKQRoR#otc{uy_I1q&3ZUTC>nrg~PcrIZ1^Q$4?p5&|79NFhW&2-|nuYfHVV@D} zt#eWLkXC>Db*|}Kqej?YK>fk))gj>ybH*SWW?H6?46Yhg4f``%zp$+(BXC@xoc1_X z#-IJcxf9hNkUC{Af=RnY{_B5=Y&@!B5q?#r%8WsMojZ}msF7uT>+-R#w<6rVwZ6LuWR_& z!rkQepW^+8-4h`1N4B9y(igQ3S&?$)n*>qP9S8Xg{KUhBGm=qJ+7S+S1Ywkya0*5H zA;^qB-ao;(!&DS}{@Y?cl#zA*I9dt>%V>poXRP!^TxYmVvoh2t+_&ps<)iJFci(TeM7z?NkJ(W~W7Vmt&6( zieF3m@*)`!HG_tdw-}R1=A``Vd+*ouKU*1IJq#$l5`R7z!Exy6E7&&p0vV>k8n;JZ z;TtnhEt&3o;u5YX7f|=&v0I%V+R4VXr!`M6Z(3b{_DYQ@bhG*Xl&{k0(`<#N-#>-^ z#ghatiCF%F(Uq5}a@?gM4&C`f$HJfmM5mJ3yz#;n_l%DJ+L#TX?QnUARgt++B&MDy$!kL7=KK+OUy3J`1VuVg#OnJcX` zCL-xem{Q$XZGf3<8(RmtTB=qRmDcn`!l|QoLAga`RfuxOhIJv0nrI_e_puD*+EO7o zT0+y6&JYs4Vx-Or-4uy^C)f1hNtW+;ks;YVgasMAINQWhl6NwmTDQ1LWaYP23gu4b zRXvgkQENzN6huk-#;MN&l3B-Y<3*pBbn0ML&d zD9I~WFW)3199rMhJt4@+QJ%dNqGJzONfA0XSo21ag)#!|>=!#b(TeA``S+sYo%($) zo4xYXRRU0P>Qt1b{o?P3&nRwu&TB3GxOmo11bj57#hEd8J(Qf*#-+ruZvtv7YO1=^ zbyi!p_50n8#2VZ#mG_DuFwv+h$42hG^U(-m14Hb!JQ72z*RNG5eDW-d*cNWAb}n;7 zq{xJg>I7F2vZV#sz5T7bEPaCjncI!P0L~zBhlS9;j$tP{ZUOE;49W+}qgny!fT@EJ zVQOSrGs!1(7N(dU_??5eISn9=v8BGZwVWgtxp^I0053a*}x~N!!FTA`x63S2hZ_6O*S4wyki2R$S^V@PV0sS_hIFmau21&U%xtdv_wCKJ+Qac z-RURQ}OtroqufdR(zkEbj0U6Z=Isn1W=k$Tj^eJ z^{0z~Sll|)Hz8-W^L24)lV+D3MPMAZ)#fC}wsmGp-4E1t&pLT4XFh_a)9_{Qf zInqRrpPF#_>ALHHyMEn{*KQ|EfmbCxmcMgtlA!ev12UEHUea-TURI=0A+_9&?h>YB ztd^du`dlq?LvqVn!_B`R2=Mja0`UF+Pyn!r;0NI1Hq|KfrVZobtFWX0$Yg7`%3l%n zR_Q-*B`vC}5<-V3my;p$2kGlx$#+2{Fn$SdC7gwd2c1)qVs(`6FgH`X;+JbkL@ z`KZjF{eK0sYU*z1%Sjls2|p-zjCNQx76vNdZo%6bGP8j1x#oLk0`Hu)@VNYma|`Gy zI{eyt)Voig7iZZ3N#H^~SY0E3rS z5T7*JoAoIh`0!)*+pN>5!vKY9X&q++#Kf=zqCh(5x@1ZJ)4Mbp?=u(^v)-9Vv|Bot zQi!BW1!IA~9$T6i6t*ecoc~>>@GLda|LHi-RJqd=G%Dedii+n*gN9#4|M;i=w&t-;6m7%dfQ}(a){yi#Att222^b#dOpw~Qby79Qu$1*8L`4! zGLtcCMs@N~p)s^=1BTvYk+#=V zzo`rvjXhGUYN(aprgbRGG+Rur6j|x$tKxKw!KRFK|Ht)Z_k!)YvO&^Ipb#-e7}5bT zAHa@YWwRQfBryK@XKJencb*{ko`RU)N@9POVS?*RTl28br$#-i)6tj@T5G>@;8hnw z_Pke1Xc*cXuPvQ%R}E`3yJ4@&4Ob=0J}jYC$bW7KJ^4|CUQ04%x`j5^P=wv9vP&h) zR+T0LsxnG&TmB32s@p42kGAYktKcG56>4sZey;i{1d}{Y;`3;j>@y`&RJ|Vv1d@=| zb_1HyO(wuo==KKlFUyjAf}T4qrrLVLsfZeqrJT#9PBXJCa|Wf(7Q#zp_V>cxm5dGh ziz+hjPn8B#j53-G`5$u#GK)&aE8_IMM$u!uun_QXQ^IQf`HZj}53_~|SC)-9SHaOZ z)~MwjhOF#7nQX6*HX9&eEYHq#Cxi_N65Y`tVN#`*wjy@xyx zjiB8#+L*p{H=281?v8cR&Wl&M>ep8`5_PDPA5#K+8{N-zNKoY?IL>|@n64BKU~fi+ zy#dE`rv8Wg0ZpKK;CEik>khbLMn2UPmyd%`p05Wt^fhEIoRiN;Qi#OKPP;uE(Exa2 ze=KEJR?U8wS)Wt3H@F$rd7ePtA|qD^@re|4gne~J5WtgM|FXm8&&jIL?=?k83LFY_ z!&WeStKVMCbl*>drV_{ij;%?j{3cr@A?T*Q6eES&F!ckc*v_TMoV_<~dEPx344jDp zfc2)3V&tj`mZDaZwpiw@zSpYwh?m3t2FF9)lO>LNqJ?jgE44W|M7^`C>dr9$b|Pyp^@YwBJ_fi(h5bxD*k2f1%e9!IuKXscd=G{xTpMv2T;oWz~ zE*&5Jt5`KV2{^-TpZi4JPML~ca}$mKWl`+5&p(~Vj5Qu=7Oqv zIaJ!MyS_K{@qQJrEAM4AYm0!>0q-WfgrdC{gM7S>TM|t}j=4HYCy9p&2PpQ8%J9>3 zh6+B~evC&di)+IFY|TP3@cl^mK4gR&xU{{XhF`7$oU)#E-cIy%(D$wMw)(+zKU#I# z)w0LVMh4#VMh{mP`m`7Y(#XINcG9ux@T*ShzeZtplLKGu2^CYF(PS3vy-S*h94ar= zdMX?OoeJ<}y}sA-p!S8n1vst?{C)mU#pC5c?Z(23k>(q+9w5ipC znsd*O39`QjW}v71i!pFU3BmVSj7@rw#|l5Oa=k+`mCw@{c%86wHL~|@8bOMhZJED& zMn0yu?W6X?o&!nO{H3CTvQi!Ih!UqYrA)DlOXxxK_b=7?JW~^Z0}7+m_B;ki++ZSK z$-RD0lOh@Z3wHR$_t?dgN?Nj`WG^@i57h0x(5kw6r6ubIqz|Kb`y9RDYppE)4VUDk zd*z90jg%OV4)sM7;hO%+iSe0`Pp-(gzzlp;-TKBlGZ2*%TXJc=nKcX9Q8d;n7`V7v=SBZ{EGN+#JQU-&QD-!NnJnzU|@aU^C4h7QJg_H;_)A=50j1=}qZEjpgZ5EM*$cYVhgfeYA7gi-=GE#7%qN9n{i%=1iXZJm+}~hZyF+QqKjJT&-Jr|89`MiAdh&2s z0WrxPFt0X%c%{I9O1S3;S_Md!xhwf%V#&P+*#)$U`R4mb$fnccPWzx<8Et1+b^F}p z`@>~Xq@D2)aMRGdb1r538SF?!w~qT=8TX{fTitn7ul}n`8S=!S1$S$jX>p|y|CBus zL+qmLrRtLb6#+?J*+z!*DSkO_npL26yU;d6)=Ik3?q&9crq!tw;<u-%0%wYX^ zhCKGcef!*2cVGOonpKst9gTTOBiz~&{7ZtlpH+N;X>*lr6#F>EcTZ=sY6y;c- @ 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

+6O^39(uG?5^`3sLrM3} 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∋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%0A}eQ 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 literal 331581 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%0D^ka4iDVgpbd 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%0D1kohoXW^QSi1w61HJ{5wh!SF-c}(^S_C#{ z8(q%5mN$%H;0A2z{2`A0szLm!Yn@o7*j3}VI$oBn*tzoDZj(jK%c8HYz(QwmH zofFR$g1E)w^DiQc%t;T+L%?Ef!l#EkaF&AP)*CPQky0mar7II;PsHe?3%~eYD3v{u zKi`}G&YK`Ey1+R2lX#VGO6m7UU!81jtRYxlk1J&5nlK0uk}o|^s%hLBi|GY7s+)}u7WzODO zMm^GWc&PPz6BjDs6a*tp%3a?!2`uC}S4cZLrw@1%t`429mkyD6rc6Fa>96 zCW*)u)&xdSCP6AjEWm$kAMQ0X^uAVEQ#D7m=z@_93c1jUD>=&e=XN|#5iStlMmAxW zkFT|MKRa5Z^0MK%e{2pF{Knd$hMNuiEb_k_&O~xA#K3az9J9;m)h9ay1S(J|;si)C zpMRSctz2f=$eX;yWYLCoZeo&gng2Uk6(UO3#YRIjcCmFl^bPNB(fumGF)f|nc&8VP zOk6p@bx$>PB;(cxZ70P6ONXU7mM{{AotCAs5S54XSMIx_i=@qf*3-X4rU_OhIZxd0 zy8ca_UBKlOn1ZV@h4R^hAX155Sd(6O0o(Efj7S^qUkB4I{~bI0l8j!0T`TZs#bIJw zplpC+ChNeKhdu*`a&Kz4zhiGfJ)a_wL?kDpfUJDLIN5icmn#PVOCNZw& z%|H_^$JKENjc6#uU)^jLpwt-{Ze_aWMQw{L68q{V_K--IRYf z)^?hYy8n@`mt#0o2WSvY$$m5RV#GzFrBj|ibh?+AmQm{n%_^LSiQNfY`HGzU;r^QGjr~5 zMX}>@)r3ukwF0uQMlQ|YsT}_WnrsHn{73fs{hK|Hng=cV588Ixnv_xB1}~J+>0aQ) z5F+P4B~FU5YqPbW4#$g7nw9sl z>`qISKzGD(Ik8py0B#uNeV}h^DS?Q`YA>7BpIv--4HRC)@{`TXbGZubzJXFYytdFy z2-A7*AXXiLo_yxH8`&@K6l3buMBlyYq(9BM4B5dvvgWGgcX`-Vd?QWgu;t5v@vK?h zF}F|p>BFqkUn&$j)!9b&Tw`=OduwkOgHmJU7v-=e;Si=t^2($#ydNKGy*V+V6L|+7 ze_|mg&r*Le9mu`wrv0&(i@Zxn_X66m@mH$JzR+)TEge#}p7A{)hOeH~DVcy) zUSIBQ!yZ}gvH>E0CeF;==}bN@syb|6NuWK-OW8YOn8=tt)!&bO{U!Vr##%Cm(2*+6bMZ}AiMsip>PffgKh=}&j@PG(f2yZg;$EJ# z9l7H|hQYx6@STiT5hOwMU)vL{->6y*C(argCwC~8lu>3pW)nf{@kK(DF|}PnBb*{q zX?NCFo8xdGBIK%t(0aAh5=3@xHHaTRBy?V6 z{qL6d``<XKH)>_JpytQI_P0i!a!~7#Dv~E59=pv>@Sow9SH}rSKrr<)>+cq&nvp~)Au%OVy1=tx^SQ2 zZSt zl_4ybx}+t{3;KR%A24b)^*rcg|8nGO_bWRMpBq=d+BIgN zQ-rU3Oy^>r#c0>erpYE%7=2(Y0l6s*0LO(bZax!u8LnP-a&wXd^4Ipww1rnPhlf%? znMiM|7!B2$oKNbSLuCXlMhU1LIx!yZvmF70w-4^K?AxxY4XT^RVe;6yRz2ZmZ{*ClL4r!2IKI_*X$Km@?$%Q(K)H@5X%AqP#M;i{wH zrU5Cbe8TMf}vyN)VGr6PDKnfbKsFM}+?% zD~465qKz~DXaml+%oVR?QY}g4yV?PZ-6M$IgNq??+0j773*x6j_>IHq?t*bDb3rOI z*}6fv<5V8MT@UflPgnm5BpCMp6G$)wx|yF5%em!I72>d#D<-0Atr4Jn+;`_N!*=b;>Kn=I4g53@4@SIDgpsq>nI zlFMYP?q5Znzi9)9HY?pm88oXT6 z76=z%-y<$fS&P#8Tckxn6FjGy#_rQ%Tmz1ZhsehKK3fL^qRa8>TbZOQwt!3$Nu>d@ zse+yoq77Y@FvVi{R|ZvT^FJMe_Qv2rEq5p`yDV4hSLrMX!mVrce~u}8(|s*uG4}zo zL_;+XX#7i=?Mz;cYit!h$$WEpIV4F*Zf8gzl*7QMHQy#;37^@mavEkKEQb|;?yxl$ zqqWW25pVL1R71x%;rXsCAeunen%Mlat-IoYNg?=}nz^G9^_f*zNIt?}W???w*lS9R zP^&2n;Iou0q>DF0o{TkKP#c-u=H-W@uM6~9&fEI$m^9JsKJ-mwK0eF)#Mi65c~{q2EC-*J|M(sH zI(mu#B>Qk*Y@0joLQ@m__$_S$#bFUbvfS0}or`e1q(!Z}qxb&VhgaRp5_Cl``4m8L z$78ARx6np$l`!Yrm`Fu=Z|HUFn-Q;qYX_{78su34Y>-%4FxILrKbxz#DY5g{>u`Fb zmtmq3;3?YbmGuJZEi*uA1!=*TAjc4YljRO_Kdvq^7B#WG8xM8=nyGRm)!FLjV1y)2 zFQ6Z|e=-J+Mg8=atRYc&yqIaPjGFaqbHqrf>nQivcJ*2Zo z0Nl#_r-CatqwQw?IYUBK?jyyOa(6i+XKa)UNX}?qqL7Kxq}1A`LlAJRmoPaQWLDu5 zzov8fqk=vYSLPAplb9GbGN}ggs2It^gv7yTehIqmAw%DF5e0|8Tno!SS>4H*S^LJ1 zX_Ho#3cD+IX7J@&FJA-sd5&N=UDIfwaiE2|f1HY((d8Jqc#w@6z;kzmmwd#uaF;z1 zP(dLO@Ff6jP2r3|TwX5uhlzLTwAGz0U8&4hj5Lvn{|mvhY&349n&Xm{N2&5)#|8tG zPxvBNLh|V@n=%I}z(Cs#GQJ>GInIZCQvBM^)o(wuDe4_-`p!48>bdBl<=0l7tkThT z@ZsCV>2(J*;+`wmVru;T2%VCHD}omo4l;u7vZLv zm;qYl9CP5cSEib55Pr)0SUtDNvJ-N{l2u<^plwQG(F56JLz8{sC=;X^$D6J`qv>gC zbNJp^LZBE(1dpS(2ccQ4{0>)ReY^jBjW+|0&#S@7D9RY8S}Uhai`}5vwDoEZx77Cs zb3FEKNRo6tLbLp)(@m7uNFJlSx_@ZX$vSSEZW7}bX6J)~E9fb7ZFaPrP{OM7c`df> zH)Ujhyze(Yvzp20Kh128%kq2Ir4Ew)NT()u{duF~;6+kdx#^%X(y)_sR8ZFcn_|d^ zUL9z^MSOB0$RlLJp1qx6sTE~C*hB-ubucY0@=S+hE)`l{?T}psg2RsZc&^sgOGm&C zCTDK{@!@v=eAa_!YCcL$WX4>WnN;w`z)|^b0%3Z1eHa%&ku&aZMAUd{Uj?_>D-~o% z>Z_p~RzTF*jb0E6{r84y3)!X#4l-np;UyTUTR95eYmeI~2h5B`2JY%@ek;wIVJqo% zj6ked&%s= zHpd@COxW?do~I-gjA$S;mTqP2_Kr~;_~@~d`pf-+&z?uN)YvN^w_cfCftpKr0G zE!#W>ZRm=|Hfz-+Ed8sRQJ)jvO^|BWjWQIk$7e%J`34zbVYmx|S7&$H+?( zU{>$+m|nITeI{8o0iE(*=<_ao6W7c8KnvH*BneO5j#9-*vLYVjg^F{p%oi+l=HO;c z=aS#1Iwt}KLPWqBhHezrILx!X=i z0q2~Mm&h(C6>{p{>WY6h1|UIf^x@U$=XqQV2|20BL^{k z?{ds<$?@d^o^7E_ECANNW=o%TTshhP0y#a)T6gpL%Nx@yo2D!#W|?V8e|!ubg&bL; znn^~5h=TT_2HHoz8fc4yo+X@*eao$+bu-fK=!Vjb%n#Ny-B=gvtESHlxD^MqQSELj z^mrnsbdc^ zud8x7DWbeA098LDEowq;LA|?Ebb~_8iubbUTE!FfJ?#9tON*&0iaXzA9PI*z6fY;!| z-iVhqLbd*F?QK18QgA&M-vOUp@f@DD^x@hQKzm0G;+L3Mt9jUeWZD16n^0eAK_{Bv zDi{sP0a55Pyv_&nQDRoUze~q`ToN&e$Z7FjaiOuYbDfgw^i*5qe!-?J_1&l+i{mW_ zRoe6aAnGD_(kb?JTo#G~J^0}fbi+1Yl^Vo zV(EzKyKV!jSho8$+J))G>_SZy2W0PIbkBU-k}XdIZ6v?>?PznmVsEl=PaS3}0F6fp z47FnV4=o7#2|U_s-7G5andL?@J|&3#?W@U{;9NKHAKfZvNImj`V3xOaQT=>e;ZutX zdan;>^1}Q(t}|PSqt?>A+$i{z_U##1a1p>W{})8#1H0o#z~+(WZkx~&)lTGD7q`+d z(O*j?a~U2D?mKoP7VUD{sWO~GnE-5StCo(uM&fTBH=BE0Q<>{2V6#I$JB2qPc5_L5 zwWdHPl*a}i$W4@|IY6m|3}yr*fE`=6Bg3zsmg24m`BxSDf&aZi>;L--o$L6#Y$B_Z zN6B2ylRQ6(273{;TUSZ*6Z2T}YZ!_8N9?7!CaT2dm$!MdY>aK!p=8fa$Moi$)ef5? z9_)RtWS#I3D;K$!auY+IqzULBQ&ps;=?)d;H_*`Qt&)7oGpmPqqhV8$ou!U(ry_;W zofFbRbDIH2*QsOWB_3r6_&s}4r8B86I$#PI5Y$!b8VOsteDGl}^zOju;@?fw?-U7^ zDM9rkTSRD?vUbzQA&d4cQmxy#2CwJoFFb?pCwycAQ&6WZdMz0ox*7|eQ*s3D`*SB4 zs%J7gGnN0Qq=zS8#2_9t=6|FNngXq9(P9T>F2|A-^({D~+Y^STPPj{5$-^ z;75`J1#`C*<6FXYJ<)aKTRgW-R1jI@oDPvA+ZX2i8r?wYXTgz7>g3i*7u+BJyC#*;UFR7uIa^%ddf z&)dv{cloX!G0n7B1&zKtyP&&0w6{2;dwyTK_ZGZeubCi}q$7~bYx~eim zp69`z%G*b8koC}&)bR%i+w5~}zB4L(T{JM1+2FGcY4PcbVM<6Em~-3R(B3etuBoF{ z=~$-9j-co?*`~MZIjVVsw@(zSQlSg0MX&J?=p=FUHeULuK>j$LNw(%1#o+PbYA?wT#dxwC;@9?$m-|LyNz@~; zvm|~$;#37AS7g_XG`|W~BKl=&&&0Y^z3i$t_hjV)5z42l(n};N%U_f zGz2n>{V`PPfCt<@H=A$VRamS+IW@lrb=hpGIVZ!~lNn?;?-?#<<%>MeNgWow~E=uWjjr(*C|EzsWgS?DL+N*%%q=_bXpH!o;RKx|~$9 z?LKlleJqGr{Jt>aJe13#yKO9;8>amweoPS+L2d4Fmz~eGNFlVc0`5D~Gh6rgD|hyz zw;~)+xM=q1{GvD8^H=?(tr=Vi8%QtezGiPoACA4%pn(MLc1Vc|CU`u#QfZP|d-jBA zQoa~mSqZx0y5jZI87gTjX}bfH4Tv`lF%1!Z6S7HE60{zJkiCOP9p@L4s6c~ks&>q` zU|Cmpz!KMUxyfm4Mwp9}7yhMDuT}gO8IL>9$E+wfBn^jRQ~}8x!p#0%y;q8uI_ z9=La*Zu)bmNh08GZ)Ha0s9c03i$Db}#O~|z3%=Et50(-=+QdJtEPHXwm#5aevxZ1` zH?W_w8|7BA9|dEVT}mnoS!4NIZpL5X4em1C{dzo`j1Uh9PQ?4}Z z>A%BAPlkv=Pm(_Cf{vR3F-11_U-)rVRlK@od^go~7q&aZSHSbW3b;)gzBE*4x6|Aa zv|OAWua2)3<=)9MeO0``s8GA{W%hK-1WVd^J^Kv^8C8nfDxnkc4o|st7FJho>q0xS z6{{MQ^z7W4!5>Rif)x7#nlncKU`O-rb}CA`9Lpf0@-RA>url8<8bx=^yogd-J)T;I z{sD{JWO&1#%>mZD2P9jYStQ)O>xz*ld>6qNDV&) zVwQCTs!%2APdmfT_}ID5pVI!zNjSap>O}1j-*th=!wpT=k7G6+srxr3m6u6J9&*?! zq@HvaOQGu99x14K$W6l^WCbR3DI0TkO<=2qc`BrV(QYD3II~VTt1l>ggu(mvckyn+ z8Sc7!Zg8UPRXU=gL37EB0xo~S1?ak_OZ~3L;m}tzUPt*&4~e+u{4aHGk4=%4(e_-N z(_V)j>xnDoc~s6g<*LaO&BP$bhR!ZZAQwW?<5G+X6Ip=E>4gzsBj_#zUGbcYWMDv! z$OOp9%E$^RBmpFuNfnS_-z;it4DtmQI~-#^37K3=e;`6In8?P+Z~_I-f#4}W?? zK#nwAby|U91!|L6TumC*;Pjl9Bf!Q7pCxV!=hrepNwS;ojeZw5iuY{_r#fsx8Jh|;C2Z&t5+Y?5OiFPImo);>j%t@UuGhLJ$744r0Qyd!(c#~b@( z?$h4S;ciilc1cl=^)|~^kBB*=Uw28Wj#N9p#Szz9MK#yEeIH+kDbcPbPL>Yi>DjWX#!)LL(PGvFmX}7HRRL6R)N!G?48yadKa<3%DKr%S`vX29@8tvB=CTMaI zefp8n=+Rz#vbcxU$n6CC{YObDXZqX zA})CR*rkq&GA9aiD%;k*Nw%lP8LxWMLt{z3{4&Hhgx8XD`Q^1~7tX`4EOuV3k)s^y zh*ZQ9Q4Vmydu8~NrbVaJkK`b7;_F&^+RK`U<5UUbNRuyhuirh++O%ZnckiS}&%nmTTQsDzC-rLLV9mSB@gFkvOh>yLZTO-q}|he3JS87k=5M5b9IjLSy|b_=A`3iU>1-y z@1-{cBdiSB`r(Ysad0x-X1={4y1zAe+yyDgd{eGCB2N5ey5r{aAiEof%U5;vKl_0{ z`&BTeLn_fq+Jxc+7-ij|Vg=}bs545fo@*1O=hUgpW5?D~d@2OFv3 z9=D!fl+`QPnGG*FJhgo4bgSn^k=q-4evZ~(F{L_gkv17{DPZ*O2grITJC)O7N-RT! z?tLjr%J!6d1}yyUz3N&o?;QWoiZef^;h~^P} zFu6`2<2(-B*;zq|7__H8tAoJLSyts3m?2?1_<`4s{Rv?;6NVjES74Vs*2o$vEI&He zt0cx7%()3&#Zr%Mmtk#0UEq(+Gcz2`PCA$&6(=MQp{&TBnGvMBWw9}-=bH=6&F$C1 zCrJ>8w!Vd_hqtn%c~N^-W~B7uDvmG~O!BnBp8nkL&r?ArwA2ciUmqnMJ!{*UljQ_u z7cnbYN@aM_kiPs7e1q7?%FlO_HZiCRPSb#pXueB+uA)A>QoKsf?()J({_;!%)k!%) ze!}`^0V8blKZSq9ue6`bIQ0r0c>)Qo_a!#RdN;H&?fifrp*lRGNk_NWD#KNFSbewr zFCG)CXd~iD=c^xcV50yxm6Q~cd`MeU6CWr-&WfJSrdQg7*6w_1n~abhMq3n8*go-k z9>t%G;-b3ic=A>&u&S!wgQcs6qmlXeO@m@Wm)uSIlAvPx5zYZ~xr&`l4-u7)^a}}$ zrSQ&|R6&<0PSn%SH9K=Q-}-J@F?Y#H(;hw`+Iw052a&+F^NC@IZhKE)N>prCpytnM zwynVWw}QC96Ln_>{hR9f+J@osVVWKkY;Y%Ol&*277I|cmCPe6CARMh*H;BxB1n*)WoTD@(qOz=L^RA#fe-=*6`t|wGP%@`tN`eaY$ zV69Wn+ie2aei~dXo5K=35?8so1it9}DCcRR>nG6O-vM0Z-bOfEv3dz4m1yRj=Qx^o zX{;I{mkFDWz`}|`%X@9|N{W{+^e27N3J<$W3Arj6{am)#pCdHy;7!2;!EIV*uCoiR zHia{$CGN+L`&*aH>wbd%Z89y&;8H6 zcUWV1ey?ay&c}`LW7tXjRYQHK=aNS5`>*^cUMYSOpRtTuJUlTDmCcl0kjHmrIM{PP zV}DDk$*XX>5^Jnj*x{u|TQPhT=~a)OrXh(|TYPVqS8Lbl z8)jpcNWl>4MHv0}T-0wdZuatN_MrV!(37#eJ5QYUYp$M@$Y4an2#uGiuHF1_ZC2kq z1F3u8AE3Vh&SsXyR-bC27;~^;26#Q=^}742P8?@*k+u632DeDCz`j-gCsHoMzuwn^ z0Zgntf8^eXfWj#qxou~W9uG6g`p!|&Tj|czIbA<6w-kFKOuZ8wPbl*(g$hxz@SBH? zz7t`oCfI?0kx2$puP@AjIMiKm!?7Z13B{esm@URUwB=I4?d-N7qTHgI}G6x zqfWEr(cZ8Ux#+B6;*igR9bDdlS~)Lt7RfR@I(3XIz5BhS)gDI?>s!%dS5d37Nc9Z= zA$es4Gz{N>SUqOmj}h72A`LRD-%-m>)sJu4ICeO1O4T4JAJSgNjf|}Azn2js+WZCc z+c?@AFvBM*$N5Gn2#E{f1O&xHs1U_pF-uvDQ^k}xvDuMxOy-dQhWb#zA7^ywU88h9 zLO}(O!+mjO7Y3h(C8Z@xXJW4=2eudWx*B zXkb<%qBI4(7%lAOH`r91=k?Z=+k#}c)#_R33~aCOQ{MNUJ1za+Zq9x*=6tWetIt!^ zR-P*Rq6+I}wa8MKH1wtBA*7rnu9W-ckH%?Uu2-AFf=n4C9U>k*DLHdwPkv!E68mC! z8P`>ujd`S!ls$FYb}|yPDBi7|3Wj$YO zHiLtT<_PB_uBV3hAMh}$W-GmON>@;=!H(SNJS=?}PnLO*RMy9+zv(1-7nbd^*3A}wYA??X}z>_6{84Y+z5$VJFdW& z&6^SaO=fJ!Vw`{dkiqDj~0wy9B z=-eddX%NZeupkWQfWomKZb*hJc{i_Get)7_b`<;euijmn=Wn(i^zO^c9NqRrm(#r2 z^|FHC-N5@tMOy-!$?_0fK)(Cbgy$2Xi-hPC;Pi<>6j4={?q3Xf8^?;i6K##mQWrSJ zgO7jHC%vZ4;00|`Km6);f-cpF2t!}K$%Lv! znl?w6*6#}_`>qNq?u#=|Kx3)0kDXW=eKxsjxaC`2p#yYQA{rN{&Ulpnr+<1%NnLU~ zk%H7e4-njXT;MZ0i?L(#L988J%3pC;WaL&Xfl7HP66pi}eU<)ll62Eju?}dLJ6ZiY zQ;6#eI#V&tcD-DkM_!nC?bPW*fM#L4^BQ5P1CT_18$9a*Ci+Jo)~sH zCH15{!A>8Tf$nw+hQS=*T6c9+ID}RK`RUPI=g4T&REf7S zq-%U19B5wjOGJ6rHM5U@YR_&E-O&!ylUU;Drr;Ar>h313zB*%X^`Igk3=AceCgl0l zc&oE26mQR}Cs)?4On=V~S-$K#(ZAFjy#$-VYXf)F7iPZijhNeAX?PB;YYm@Vnv`av zXI?Gn_AW38KUxNd=|dKT400|r+daJ(`mbtG?(PGk=mn*{o=>wB8}3to**&~My>WRb z9c~u%k`%V$PFnQeyTyygOn)0&Sq;k^hbvq?TEOk+#s{t)62~P8pF7f1;;9TB?vjlM zZg296IZZqqO&$1G1EB4>Z>bFGmavhI=C|xAwtABIoU04!qWZeD&xn}c$Eo)&5swP4 zi|EWkJSHz3yDjNx7?JN5o&YP-Y(LO%eqUQiQ;L1`Jn+bh<9*d*4HEXTAS_irh0reV z+b@!=@4>k1Fyv>2baN80d+XLjX@up{bd~@h5hfy+m;-+zRS#u4G$Js{R57WPl zIwKmkSDng+{*p6~aXpz?I-7temz%L#vuph0ZFF++{;Jx!Xvax<3%q%* zHE8vYwS;cg6?*lZqRjjI4R67ddvdM7d)|Z%>{$^ptjC^kmNK{R@a$WOGrs~Kqlok3yF7;8$J$kp6kn$NO9uSjZ0u54dRP<= z1FRr|h>32WQIr1AsMO7eWKwD!=U$X|JSsCbr?q}RW>>G9nCCgo>pdy;70T7kEG5rv zz~C7BkVoXhugAvFWT&qY_c+sh?_?YTOe^*a9G-Jv;u7upOpL-Uk0f!=LamfBnjnu8 zC0SNv-QJ_^{O<_VTNi?AKX#lhq=LM0slZs;i9sm1!rH|+ynbm-2|dF4vIoexkC5UZ zgr;h_XZ0x^`YxG4(T3L~fZ4pBYt)$dS2E|M>sxw|1=YCF8u3^LlZTvDMlMTb7D-nUjXbT~k-FlMD5|WXi zm4Si9yQhEQf#T>il^OPz;A5-LPnI=Ee7Wv#sWqGCH)AWldP;V}^eF7vi}u~LD!jZ$ zYLKtBEE^bO>LA0UZ%6)6ID!WseIT!U|G@pWP9rZ@bhMiM8f=-{Q}qYSy7UefVFnsj zv3i;?=+628vnTDu_`JwcTXDj`FFH^9foEw|cE%Iw**#JX+Aj>Ui{ac`aXi9Eu>WPp z)8>zD{NoKW75U+ZgC-M`f5w0LP6D!S9C=(Z6@aXZ?H09hlSJnX>`BtE6_5 zvO2owN%Ml_lEOsjS`B*3CmMY&R#KhKyJHKJlD)vFnQE*=`6|z|RS|B>I~*qak%JJd zMy-_^b#12BP(_1A59c;;4^X=Ffi*3(gA66d;u+4rbQB~O^k9Q!S|4efk7t+lh-%*D zF=GsHxMjh~t8iP5xW!oJBzmm=mJqaYtwZ8W!f9tEB*yrXaHn+#P8srUYi94e+4Cz3xa0>u3_Bz+ zzjr*eGmdZtP;|IECzxVOL8|bd@tTqWMIr5IC-RN4yUye7S&P|d4=~K@fLSJ2q?x}P z)@`(FormMA-K6Im>H6eN$4e_Guc*_FM3i_xJghaI` z07%ume{vb_hrL5}%}=4M)WzE^Ke0MAYV{6&gLN1&2UdLPX^hgF&pgLb6v$O1w}~_v z$AO*j9i>)VI)}u}q%NC3Bst8Gq$qzFiVOwIN~3v~wek^UZs}%*7i3Y0vK&DUo96sv z1i0VDYEL^e`g~1UXI)`*-HMHr(@DH#HcOc9F~BPzO^bA0rlu#Kwk?YcGukf5?6P?R zuA$tK>Xs3Q$h_I21 z?d%kblF+a0s9$G#tv3^JzzMilih-=gYQgdb~c z$|bVTtPDukX}b%orAUE5)Z1^OUD)c0W{r@O^=wczyZPa}PSdKgmL5tFdJ0%7+wLd3 z6+z{C@9u%97fQX+&*FYg`yA7kTSr3m+z^JwD-3FQ*}GZk;8`Lf#}FIyVGGjsK*6Xf z$>K?lmc4INXh;89BCD7M5M#VJa?uxo$Cudr70O6exrkf6QAMCRXVQm8x8sBg>fDvs zTRM?ZzRd_A9Jz@s(6|^nVgFIR{pbe(wDcE$bV*S)eq)%@7zk1ap}rtyEPl03P(slVId#Hx1B1@8m9&IqjmuQ}+%AoL)TjO6FHJO1 z%gSFhk6}w<4Opn9lm8iz(^p7VP8;{}GO0taSw5-XOj_NJqbHuY7>1xxY1nRFmw)vv zOJ3BXKXXrD5T_5#LMVhZ9@!~cCu|P{N4RfxKT9|fWRat>5==@`6)Sis$YCilspslq zYo&LDTNDe-m#MLM$1ySQlOKBa?#y*p?D7((Lkth7h>ngP$hs~9+Neg7)sE!iD?v1snMQmCy`0o{I;gdrvIlSTaE+98ht2Fjso z0i!WixzW}Ppr-xLi~~$$cx?rlH}})=9UU_wdCS`hFAJ+CF9c;a&I=h_gBc6qAZX$* zzIP25y2lPSrQ^z!N(8oXG(nX^DmBZJVSbq=i&xK|1x${k>y%SNdZTwREEj{di;@(7 z%!NGs>O#aukRnBs&f>%K5PTla~5oU)b<@fU3KHAy?fJtT5E`(AXkvH$istV1EF+%?D`V|yS?JXb`6F% zy^xU7lw2Jk7EKY+F0SO$7c&my?kp5@Tk#-F(Wrb*WQW0O#&T}4GZU88PUBQJI+0SA z)rdzW3IlQ=S#NDdo zy&5(h{VpeFED2i~%UK)RJ4~CR`l2lGklxA4;zBN$-?Mj)x~Sf;zudy{rBw}chqw!r zT?gMim#7hQ^XQ?|(l-@unRU^S$$^X4au$-*OHP^}y=Yjs;gzp(GIuUMpK z$T**!@+9*v%=o)P&-jX}=ndz(_3Pz(Kpw4h>~enZl(bxE1AEQPm%i7qtP2Dg=v~Z$ zO8RmI-v@`Yv_GhtD)%f-XK5p3{4&o(YiZRhtm$-2T5JZCS(2*?OKM8EJIt8 z!|NENn5FKu_;ovn(-j?$3hHc*mqd_PJvYZ_a_4>noy6QGif?%IO26u5j(e24x;s{- zlHQ4Fu_XQ(Nl{RiAuU^Y8=;$8tC9Wf)TeLRHq^MsZi1o{tXzOcrotZVZVA*Zo=@vg zHx(xv*Hy&~aqse$*J8eHw8}6Cm}OX)rP3Ikz2N3?(U%TFQ?&(3QG1v$sHUV{86qnu zQm(KXy=m&Bxjh|#Av4}7kEb!kD_<@)^F&a6yu>N;k;}8fj=YudtOrSnK@dsx{KapY zKijp@qmFD%x0y+B(%((z%U%WRpH9K5l0E^W2tNpmZkxZcJ%^!Vtwb!!NpD)nZ}ql# z+)|d@6=^*%p5qMsl&q+zwOP-}D4T zdY8dlw^;)U&5p7NhMk?<~CHYRh;$8aA288FPJn=Z6vc-AxPf`+4iktXUorq8H(ccugzqE}8w| zrhUNu{cnT^)}WQ)!iMwWOwcS$&o>m}#Lo`v9|7^aI`g@h=TC&$(EHD3kZ$SM)fwWs zp%_&=+Me)MJZag0oD*eyTTtwX_e%LmN1uN0kPWTvKd!SVPp3{Y^|MjQ?oW@FlfhAV zt9ws2rDgoDaQ%IAcCiiED096f1G9(KFZzJpc);`Z^So4m$uv+nyb~REeVN|84=eV$q5(T416Zu(QJ} z=`wKYWM^7W0{G7~>U!m9X-6h%Pj&(d4UTDXvfncz-XVGHS=(ZyTm2+_x$h~#Z z{(BXVlLE!H=Y%O`=@*xja{d^<$?Ldh3l9y4ZIG5zZ4`z2?7wxEYOGzo-MS0WhNAmlts6@@>qQLFr|+DmJn9$@}i%FK*l4Hr8i1#hIPH0BaVA6&^`%)JVc2v4Q_ zV(W=q(iV5>7T8ak^XizOw&&qd0)Oj^vR zZn3&)hAySfM_(veYtWAZj&Zyu{WhH)>;*q$i@jY)ge!_wDosy|Ey%J(h}+Q~!ZRff z;x$13CsYWA#^J>2IY%Y+7WHQ1GDyx-x7oHui06h&x5~1PaoXp>-<|`rzgR;k^AA#Ip$WN73UbcgiZ}=NJYrvzvVaIz@^+XyV zX`aq9oD)z>_UO*)WVDC6s&G=gNFDGlQ}s^ zZDDdo)^IX=Ok!l}z+KF*$GG=!9GXv-WK?B^B+oy6RQEDiz zOZR4nS`ODRW!@t)Q&b8aI`!7dq3B;dzEA^PAJy`6Vp|8G4{gLQKO9X z@-4|zN+!cEuda^w4mXZcKPhZ^Kz>u#OmXSwuWk*LLA!V;cMbqArZ8J=d6!Q8S$A<( zC)8?II*DA~rMX7rDQo_ekA5eLW_&GN-1;MYP9Z992qRB}?hVwIQXm(nu#M*t1&o*b zBFdoxLf$;m(ZSM`q)bCK(l>*QIVxZ=c3n~g7RSq(++qTdrw}>mu9*C?RLm(BEV>^# zvpco&!*?-b-o1skBOH`l0QaaDOK`l&wvieVU%V=0hIBS^&j430)_!QVd?15a_YWjf z$9uuEEUkop{^m)W!~3hxLuyUk5jJKOvo-k-k&6vpNk~sg{jI4DLz&29o=aUq_&R>p zta!T6hG7*A?}igD4}J)4>9FoFtp}c6w!D7(&+h-n2DXjD2q2=Qz)T+{JT`n84{wo^ z=Rmd)xgcAJtB`}g#wc8s!zevL6;{<)`?-``ROA-M2lSo&qKBl|43g&wwoIiik@1fs zie6TQLnXembtHi;XnTb6BO$8=oVr<%KkBz_Xjwi{-;gqU^BR~QvI43rdg1UUv?P|2 zFT#D7LT5HBf|@OGD%tKGbq|j`L0Vzh@uX)4k+TS4Ca#x}33v5(iuvnaTcvuV|E{ek zqIm92;F^=o-`+yGp=f|yt$=;z%eGaoLEWTg7K2}=gJ*o?WSdE(AOG@7VV0(Y?};V*VysE_mks zor{T`Hs*SJ$!mpuOXyl*M*zG^rG#*%f^Hk@s~KgczUysS+v!783n~T$gvoe%i9!mu zjWv{qB$SOad2VA0#RVNM!#)R7S;*HrRGM0jKX$X7v@nf6woH=dzNG*sckM@#2hrH`e{JEoi^MIjc?5$~AUSjtj> z?-=)E`mD1k;EtQk-v)xC>3i-$EW(Ea|4w?-*!H6P(MA{3oZrz}$pDFg&EJLM%WQ3q zUk`f=P5zaLh4eDGivF;bxBT8PP;n|XyG^CIs`?B=H1P9Xw@CBh66$r{3TZK@vZvs7 zK%Zx(ccU2FbQHDd#LC?1QQj7?w+Z&yHxq|En>id zr^XViwt>PFAx2%1#`Vh($-HPg>%F1RiNn9FB)X3V+eZyu=H>@484geD2hKb%gK5Qr zPdx|r!2!Q!w?Qd02sI$FPu}~XLqkAtE03M}Zw_^r8B(qem$8yxmE0|RzdO3CJcRkTF zKsuRR6}LF9gS`zx`2FBGoKP$%_cVZp-mYne>FE4qe{Y@mFr*2;lGRS&o&C*)~A7$g8? zHgN?sc9LcjBm|e1!nDu5CE|W4?)FdI2zb zZ#2o?VkomcNc}8C(ewr^_#KGmK6z$z0Bfp4R=EbJ3l|2}tOD4+=LIe*jc&h>aSDEi zTc|CLU1Vmz6Ykm^#_V{re`4A6Hc6*SNw4x%`K?viOAB zxpbesq%T!4G7X8cHT2Uz=UH9FZme=Podom{9_lGtvGQN z1Xx~hN1@XW?IGSu8<-afP9k0HyH7Y27Fmz`MVI*9xO9cTkXGmIVu`{2t9l-jkNrYp zA>xcas-A+>SB_E7+X_EqLm%7x>qU4QOdasG}Qq8NDx7FF(45;NfNx|8WaeZ`!>3b!x9C`gi=U= zyT7y7l`CZZ&3K)+>p9BlmUY}{n;`t1NrNI}DXRodMDbY8Wwz?Ul#|k1)~NQ`-UQ&L zTrO!jMwep(wXkGq*Cv$AC zXFe;-M9s5#_=xyn_!P|2eBm&ODkw8R`M`#%Cq=#qqu%peX({;v3Uo798qlLITL6u% z&rpe;*MCK-JU<^$f$qiXh=PN5nMFfBOLkok928a8z&cD*I;@^VznDaSxrfL%XXJb* ze0e>DrDv7uFcdEx=7V_@y30cx!7@1uLJL`Ih>pJnmb<^z3QcNu*7@vru)M9#bGMIu zgS$GI3&*8q96a^CmQH_qJ7)Yb^*5MIgm`D!O>ygo!p*(vky)f4wQjXfm%b>~%`Lm@)Mg%%@xGOe~=D)T5t%}!HSr1??_GQ%|6VTyF#;Ec$VXcPW8kr~Y)F^Xcudc>B z3f0k?7^8ai!x-YvWv9{Hd}d$tMQgX+Z>l-1SNgQxKes4E~knGzv)dsfc=_Ux3+_hzmP}<7BP!% zPn(yh-?|ZH@zg%18d;D#ilEnD(GR6tg+Bgqzv!}iz&Uf3&VJE0n#DBA&N+}OsOL=r z{I=~hYtx5JVu|(m>a%|*96BpfO8tdF&jIV<$ze+RGmrW329JX8Y~rk3tIA2mdm@rM zgdW*H7>?6Ctua}A>N1G9EjD?ty=Dh5q>r`a0EHPXC%g9Cl67!tu$5Qu)l)LZg@Fo) zJSxSzfM2sHj@#VA%W$Mu7s$(5wAVH5e2SS+=?`ur90Mzw@KkZosDEG6WfaXaRm=^eR<%&5nKcbVN#YxmFz%3mQ5ipcrz zXS>eS%@bSksID}p0W!udr98C$sh5s848Qk5Tm9vs&;;fzt4xk6P~b35N=L*=kRzOs zj!`DZGvi`n$1_dKWVIgrA0{^_WV64cKFk-Mz_o z)98ZLHgfCGvW*hT@(vIXv=tE?x$e6sa%tt_ufqJWBGEqO+yJF=3MN>*T0A^^ZK?9o zZ&aW#V9{zo6MnK6-pPLz|JOsuy}EW!9&sEz^VCCI2wt-`FnRm&l#z;x0rcW;d_GG0 z{4t_(d1f(Hdru)oqc?BX>wYz<5)DA@cmimxY0kc1wfHnG#ipmGULy=xpl}jE^S1S) z`B)0_W!F9;Lf!o-m6;`@H#yz=PV1Vu9(%@0>-V!Le40+EH#7=qV6bZ3DqAEgvljRS z91#6w?v1$-$9`(Ed=qfz%g$Wwb{90dq9AfBwVp}Twl9{3qsHc~@;;@mKc9tzn4%mE z2r14~K0|Z(zB@^<2x)8ro!$2A>hFg>bo*TxBl~kWGSV*!q`j&W_^8uqFiX*A5d_~S zkFVUMveIr0PC~tAic`haG^t?rM&k@v*y(54pT1dlyeoQe!rr=atJzxla;4tWDJUmC zrnM2EqB$^BFSg%N*eyhKsZ=o79&z4|$#cC7FU0JY=*8w=qv$uAZ^&~rOBx1(3 zVA7(OBLti|9<5R!=K(+FhLR6-$#pLzjgVbgfy#gjsqv%YN8M)8!8R{x!f5w94+(Yl zq@PKlrv~w;Jj~PBT%zx5+Uh(A)<+5=H)-f;1X!!4XpWJFj~QMPBg&q=&BDFdwHFoK zd`OPqbs3GKzX#%}X9F^1%=!y$7+85%{hmZW7mlqPy!f$jBD2tDduG9uY)5#W0<^Zz zbKv8Wp0`MNM5Z^ALR0KJ*pVQ%70aBkUp{T%v<#+w=YCHITT2$+I$^FCu=dMYW7@oI zpmvJcGeIP?_1iUL z4oBL{o0y_)?6zDTTZ<$SlT}Y@<6X*H0KtV*DOz2)?uNe9k3A$`n`?_V-`$hDc|Xdg z?b0*$Sct@9HJdrTd07L$(3(s9X$L6=ub6DA8OG;rMaCO! zzy~<9kfF^8v(S|rs1vTT`bUP_4F)}nRQ1pW?(hBdN}$5{~{3A zyqaltGRNGS{~t-OKzzOG_vRlhMX!A$|0|Pjy82)%-2mKef10+SU|iMaw>zdBw*bI( zH_Q&Ww*tjkZ+DhXn{Ovl29zia77w8!8)(j6|E|EAfF_)J>3MhD|I|& z=M#pLxeEank7>mWZX}?}lp$A9Fv@t){lPe`Qbzi{xa2SLXHG_*-W+Zp24I`JU7Sp? z_(FrHl1t&=#U}=!);LLCvG&?bKG#Mzygs$P$$e|LZtw0_gwo&pmbxBCD@^2g`#FIt z()Q~ILDu-_OO_hH*-A>i^3UBiLhdg@k6oK_Lr3&w;*OZU2tIq~+M@{iZ(w!xE4^B& z`loj_L77#K5aZII!9j-5+i(_hF!_sl=+D}@IKZu?9#W});yRM4ADq5=WWMADQI7`` z$HiZ$lGh%6g5)MR%oFi`nse)lNG<1P1=7EN{ONVLStB~`9?@@mZAs+aaykd>NiR1; zYP2LZm1HfbizZ?9(^oR6&YJN^XWppKhnV;|m=c0MSAA%ra8Tv>aOKU2N?09&%O3dj zbCv@~J7u^j&(RxtwTi8PZj#>M-`v>wZt7@fz4m_ZwZYo_T@#5Ogq|1o%J1-9uN<(` zD1EXOYl92`LTnxJ%yCau?1gf5{|gCw43k99>S>)UxSf$%;>00i4zwIyxfPL)_;5qh z)K_jZV4w_065Rk1DvjyFvnaZW6i{=)X)7`ljA#bf>+ux}Wmkm|-UcH0zWx?Oc^QN} z?Z09yXXOi0S7GnL|HS@EnWASdqc51z_Y_-b=mI_LIPR!{m+G9and5N_)k$b(1(vIR z^s91N04k7DT$S7+zq98CzK_)rS|o+`{ZfI2g%ahn5+5=m4Loq~ENu05xVZA(@D@F? zly`6^RWm@bXv(~f`9eYuV#$@kk|*zp@7@bQERC{d6?mGl73V>mTAf(X(TVS$HoyDQ z^iXN#t@jG>6vG83udk|c52{yJzgc<$+8?FJe9`!KrT9b^+EInE-_4N4dm7{2`qhy4 z;8Qf=2HmE`eM4N12e}3%nfA-B3C$ne=az|+IG{rs{JJi~eV|CNKNIyq7(1k1w1RN+&UZ{#H{_}dj->m!Nx{uUGDo5$lMh#j>?$B< zQeeWPKijUxeL-=A^PbZ%y8;vFexzB@<-;_K>(>5K@4B_$18+QY&_lm?#hHU1F=+#n zx-CQZny%LTRN5!qXTQcvg0CdhT(h@h^KF$oHSsxJ)nc;H7V@yJwsF5_q61lT#x3{g zywj>?mBp{dl2{+bmJhr#m`9I^Aew+=>pNt8e`+TaTqn?5t{TaD3bT%2*!w{LX$=-8+ zvb&wjCj3FCYE`HbX;QAKbZ*7mzotU0QVghDytkBZ{{HnG@dd?ex86{~B{*(AG9_sn zF@3YHTF;f!kQ&SxK%hxay(KQLRtfXqKbz`eu}G|)Y5LjQbc_ra!+z12Pm53y$5p#^ z8TGc&Dk5LVU{qI?2a+lcq83K`Mwrx|K@hjt9No%R0FAh*1fb=;*|9we+n~XzDS)xB z+4`c-=9I-)P)4HoU1s%~H<8MDsHs8%r`>^p&{f1z8Me3R@9SE4Ukbqp2w1fqPRbmF zvU%g)rZ!U`YUHFr>HtJiqj~S`rw$ zJl=@+R#y~qYrOt1&vR}oYcdRLD@RC*1-mo{uYPNb!@nR8AfW1654O4@0Tgp(?t#9} ze%x0q>I5gyIgB39e5m-L#o^{-hb6rn2d0$5x8&u-#C>-&e%erFf<`4Mp>B0y3zXq+ zbh%&5Nl@=M+kfLz>Po)Kf608mjdHQf(qYf&2?RvQ)wG#)@dnK;_2k{O%97Rm*dRMu zP99eIG3qbk$p`-v1#Qr>?oZ)29uc4l%x8|rxg17)N|_w+qp&S>B+ro>XBsG%btfkT zUGst(*Zt+BKe&Ru_}t=U=BA@aJ;oyO(o_#>fNIXJo5dV+r21uSVV5oMdm5UPsP z7j_#r#s4Yr0w>XAV^P&|)Yc1@il4;t#UmL@r?bk>*7J;%&r&ayeSZ!;G655=wuKi) z^u}Aieu8Ge3Vi>qFE5Lk{`J{W&iJhT+b~R1?^++-4JeY#Hw@D-3=8))!tWQJH)uR* z_A&Me{wXZholLqpi`Ks}AInU9ji-TrHOXp_OLv%l^m}g6;r?-0f++7&F^d3~I4+CG z4!|6s?#S**!FT^NGxu&6GjHcZNv8XeudOR|`|^12^`m;*mBKCM3MibTK3d6nR`q>2 zK63|_y_v9ev&NOj?@+m^CU63mC|U8Z9pal*Aeoqd50s<)jFhHWt6}k^Y$`&qrSC|tmKjCc z>9R#pK1=NTV#~2omv2vk@l?$YPIORVp@4&_FX~U-!|MSS{bGCL0?p^t1$$)C`;xX( zvPQ_+BT`+e-R{#HJxsp8jzc)cFV5mQmavIClfnBw8O1!NgnX;3N_kZ)53YHrcJ+YY z(;$@G2(Wa=Fzk9|J=mtA`X8q2^2{8MfF~3mo2G}2-0+ovvW4ntCO$5C(Wd5oSBmT( zSNSc#$K1$Y)V*#LWkhR@jJvYco01OE@XoQXz0E|mwiz)ikO7C+f=^LW2NrZZv*j`a z&&&Tz&L<9OtKB^0elWC_=UBHI+>7=IePnI2+As+E9r;)Aazzv0Q{em2^yGGrOy-I; z@-HkZ(8^J_Ckl-0dZr!O#KEy1Yhy=k&=N+~#(Otf<3wgQuX%M@y8fKe4`oH`nJaK~ zWYH?0CHWwHvfkZX4_3n?yTP7(TvKCfZr5z{@}u!aXF<7Z`}?@J^MdM&epe6qBo)i81|#H#uL>)n<2)F$LEv#n90i_$W=MK!uIg%@3yU{-P_4kU>v;Z=UkvM-d!rw;_L$7whcp!W-+F zG6EQ^_}a(q1` z$rE$T17E0L)RITLtU)+SacZWR4st&>WjpNiaIlxfP$@jua zZqnpbx@G~{RE`cZ;%ZkXVEs`YpeZEY_$t2$kFN(nRHA^c3x1aP&P@0zFpGcrT1ncJ zUn4n;@{rKQc&obO*|sf$*{YIBb!`q^~Dh;;jrIs_mihcoP`}&$wXz7R3OCd zL@+y0dbs27)h6GkdMu)MK6ugoXR3amMo@xvNg*92#Zq({^no=tW>7rzQuLUhcEff{}+n0xOSYGcRro&9KX;_dc;v7SAN0zOatAlw+D?O<)k7_{S z#$NW#&9R2b%q96+GL>=DS4QASaNdKQ^6$aPD9bh>(Q+th&lY zG3e1*UOmp?$G$f{bv2d%cWcE|{@!WBr<=u-<<(dS6;uNJzUKPXRBIk3k&0IT3SjrZ zSGS#a{DUbKvpM2$z8~9cFshS~TJ>$Aab#GVyp>G7@8Dls6-~K_SQfVNTZJc-t)lmX zK592m%tkfSc_lV?oB38N50suD96yR}2p`Wmryd5~*<3hB>nxbko-~Ab3SWiD+s{LS zS*~}8S_7^>+Ky?l@{!Npri$p*iNvYoEoNm* z+U_oKn2t$H{hU%l$h~qt^Ks4-;UUnspEiT0UCq_2`A>?h!u2GauUw%!%xU*??kK?^ z+E^?}WKPT?*Lzc;blwa{gz#WdkOXf81H(vu`G%b%qshSlD52Rm1wpMWf_n zx=y!smphomfm$>6$w0rlbOqz51wG$$jovw-3JP2rAgj_)@%dZB_~ z_bI6Luvs&O_mXQ!q{*_Gaj8W{JJrlgbFY5y73B!pX;@XS4;k2b6m;qFdiQ)_@%E#@ zbJWmsCVY=AImY?-f16k<|3zWv$MOr!J$DQG(v#P4YrcNl%pr&UlO1FyQ$BzUqg~0e zGpGyuUxR`e@GZGG<{N*gA9{Z7kji1@m9lPof0o@0f(JqBhGGZ!!fv;_Dpb3fKGFKW(4^su-e* z-gM+JUeXve7draSfnCwlcM?P%xFDV_X>3WN5*RgB*P+ zN-^Rpz;`*0BJ8lvQvYZx6>+DT`=af9o=R1>fYbDL35*@-wbkMaU|9*CJO0wU?ygd|m__a}tZ&MDxFhSDDw_|NPMB$USg< z65fvEMu6%`uK#xalQ1o>*ESFLUF++s@dqbFj9G9RbN!Mo-|Xv*h_J@)M(J_T)jC!D z!X38&`6Mun8~qpB$kM1K3@VR?cA+38`r*Pie@Qj^kCr&68{hbzNH}>C6TPy`A}&t2 z9L%A)-LiMD)xe5OiOt9v=Cpj z6%uW1wAeanQ;$yGwnGa+kABvO&4+~vL|F=m9+|iXECr}wBn>lMyJgX```Oz;&}7cA zmaBc+`K0I^)q~TZEGhGDkqudqn5}cOFL*(JR7mQ&QEC` zua^&f)={k}+(ULd;PUp zvCuOev?&W*6w7AvVrws_EpmeQ-&;EF?o13arl{S<&rbHnXCYAcSAbidXhDuyMg5&) zkCnfADzVS-%$HdU_{ppg(3vJqv_j)hDXDtFp#7xZRax+7-+w`bzW=z6FJLO9(5$uB z_M!ey5{=!yPVnhLwXn*G+2!)n$xr+tTZuM)n8fhqSg_F>ii52A7-kaF77nqS-5UHj z43QXR4ZK&3c24PMl@AGFX(%M#_TJ2w6PG`^oOK8BL@nOYwht)}B~HoJ)#yL_lv@zz z2K8;v<~m2!id(#fW{0z6@<-+7kJ*OTzUzvhu83NltK9dTqbXK8g^FY9gl)HK8$@Ae ztoj&>uzYujBS6C`g7P8?n zjFe(ur#h+1`zD1l+qG|BFf_5)46Q{5$&EOxO|?JgvofDH?3Pv|w>y(g{)eZx3~0jd z-iL|NDJTs?0g+a^K@kxVP`W4G-8m)*D&1YujE+&#-95Uy8%K@*_}Jt{mzb1vD&DB8c4Wp zo@N}^>_ZKmF@37hW?b)?sEOJ&dD*i{Kw2r7GT{2jbYURA=^#z2C|X|gI^rfT@|Ma$ zmj@iqL(24^L(95O5pacYx+9B~fYtmn$v9|==fSi(I;Coaq#noDeygjG01ts#kpdYR z>-n=8E$zqm63LCA+xvfVuS*^mBumx)$AcX0AC6i)o(%siwZTfiJxECCugq!grv0YS9E!YYZZ?&k*z0G{Q3B3XQwh}F*vl){$=7(?U+La#2B78%mz{PF2ns54W65A_*;?G%$ zuhuMl+RNoyR%t4X!)gB#86tsB(`pC8NAy`(}8LWsHZ z8b~bz>5^Q-LnLGnznQR%fTx?p-==DPz6)%Z6D+Po2nhP2MpG#|JiU+VhP!kmzxQ-V z+FJYVCp8wAjhO<9haEfjg9enDSRXhf6{Q|3M_P7TYK8@vo&S#D)9E+DP^bmI^{xDT z{`hjCYL96*miOFgf@f+UcPD6-vY3}IHRKD#dfJ|Q#P2L29()JSFbH(u)+hWc?7&Jq z;I6?#+zF9tW=*|f!tz13()L7@5k5}#TKC8ej4a(;pLNgstUV8Q7t-UGstB{!-1~vc zMA5i@kDXvOi_25=KEmux@51Ql(o4+F*$aes_8X_vy{SbXif~^6&u4-;Ry)`kM024^ zSG^w-p>P=O-S=1&Tv_e(qf*&5yTrJRuQ_O)udTNyLhxP@h#|LDM1+U)G@H9vIH`;e z%dP@AUjHVLV7lsun!UGpn(+pAwN#tqYADzPpXpXBBfe$*6--^Ci&Ct7d+^lfDtGefX}jdeH!~!uYoBK+Sf}Y} zVI6h@s<(+wk%aZ#4(9?z_WDC{i^Q64zk?B0Ad}zA*0;FE*Up0onu@ulzdP?4BQ==} zdMr!ZR6jbV@R@|Gx;?M8@395J#WY6XGCZLAmGBi4WbY|R#F{(FcuO{dq1I?ma7ALp zFmUP0rpD}R_Lu(JvRq=~ZB`J1>Vtk}}`-MKnoa zKd59(;_5Wu)T#5lx84oH{tDCjRuRC&WBh?BHV|)NVU=1Ko(t;3J$fqQ7Z{=~ zd1?$Y4UnAp>0SJT57>|=G<-UcTTy`yRdJEx;-2kUswkf>NbHsj)JY)$ZsOGYOs4w; z(Tl^_QeB=W5Di!b8}<5E_$ zX5n|h6eZ8QR<1ZA^4xQI!+z>y`kaKtsjiX7hFOMzazwQ*QcQ6BmHz7Bk9#+&e^>K& zF?ZCVh>iOI`z7eL6z0Q3R}sd4yHwzHca6&WB-F|SYMZ(Ck-RUJyfZIbPNy7}{J!g` zu_F0QxY^U&b1Qdb8Rw0X9u>9AmMuE=`7A?Wec)@@L&_Rr5$VKke5g{^U3&F-x5g!&vbJ{6 zZf+w0oAXCU!kgc0g5jq0Z~IRzn-1zO?~I|s3VA-gg=b`RAZBA^@Yeh~& zQ%TnvXS3JIlzYPjfKK~Aelx7AAUv>u$r20`d>hgyDcJNs1i4xkYF=Ejb&nDz(rGptzJJl-dnkRGEItVk|!%o&fyl)fMq3cqjp98BRSNBjr8Sb z^!TW5p8!?r@CD3-snVb(+&w1=y?@thyDjhL6JrSw6aj;@5%<4%94ep3o2}Qyu)A@A zWz~EFpg!yj=YVeUbUDt=h$fK@rYlTZQ~+Cxwm&zf+y7dtGtg|@+7+1*$#*yS)c#Og zVSbGwUbh!DWi>;ZFe=%FL*lo>25uea`>dxY*b+dRx{Nrh>m zA$Kb(l-K?}`lsMB@`0gWP!Wa8K3KzBogSM5cVDpTrju5(KECwOSV6tD>k(g;;i~ib zp7r9>4kxMGh=H>%MPE(9l5G|ad6xcjd!-S zH4q)jY#}snRw!tIVKS1dRteVp4v<<_vo0LvS&Jt^%-!<3r$qI(zei5-LWU^=hvI8I zq8yWO^umtpcQEHS*IHqYGwHLZy^^ozC>eAeJ?$z~cy^Ok1@qtsntBdQGOLD?W*@fY z`@+Ruhi|HZyA0Fn5A1t4^UgSd5xzGD;xR2Q%n^(8E+=TuR*qLdw{&bXK#)2&JH+EDvlXA|I)4$ch>}DZ|+(v zbKb$w=KkDmZQ2|1S3eIQ2rHpYw)tyuR(U!nDBF2#hEseprserz1a0Dy%@+C2f2kvN zPBYt#{?!X1*0X1lSKnOPsISI5R1^3Th&hEpVI!WcEaAV$kU`=XgjZ*VEj2yBhAcTd zaHl!_8PyhW|7^g3uKQ{5VPhJqaks$*_TcZYHF`UGYO_wW05?|Eg9lJ!(u;Qy!RgvF z%>f@=m-l3*&QEljG4{*I`#227;uhH{o(cr^xL*qm^Szd`cb=ydU2O-7g5d7qToHAo z>0JoNjgy{vi{bXu3&$3GpDhBAUb^R1{q5Vl$Dtimy?XM|@szj*>hVWbSUpDsT6XWE z)le^~#YvvGUfl9T1dYe{_S@<-Uiws~w&&TL?R_4apSXYcOWO3kUq+y&sARqx+vl05 zMOWsFz3MA{QHsGS^dngEQcJB{FKvXYg8@k~^(6Dp1y z@*ilfeB7{h7}bw*$+|HyZa4!|en|;>gmd@^l?dX4k8x7V+_9s^wS?pH{oHX2YojIq ze^u$nN%C(XOk@c1`wxK*lsfQZDSr#gfARSx;2ZvME{2eSgqFeG@T^qA{wX&aYSvlD z=L_Ru*2o-PkGg-qd9aEmc`*P?hEKkvMiu*{;%_V%a&gscIPl zI7)mOWwh^#y^%B7RSd6WkRd?*3af4giD|f!-cN<)Lw`P-vb$LY9UqB@vuqCnW4~Dq zpWj%GR9n2d;8DAE0G-sbbma<*!rk?ojxMWHFnD;suaiY6hesUh+ z{lR?K_ohJ;3qezVTeIAl;MM<_8<=6C))!Fp)px*|yZ>6~us+sT)OQAd!RIV#a~I{C zhC?rrgzP7i5ybJ^e&tqt!>#+3bX_AklD>DHPTO-s2iZ^CMTu(yr*K(b$vE>(r2xw$ z>|xJ&tG#gp$?JY2REOGdHg;GDEHP1p3%0i z2Dq+{|4buUZwtMnIbw$qlpo0 z$`_f`oyDz9Cs$Rfc`cneElJCBe@YBu%bK+o8z-m3j|(}MP1EU% zbe=~}2by#h$Hw!;wk;KvLS_Rb5vMZSd4y612_3^fhGFekeg_02%*$cHJ8gKn^{FRe zhJ@#{AKq<)keX9FZ<2IBc!^*7xcy}crdWWN`S014_N^(DLsU=K zf!fy{BY5B$a@8&)pK%S76SN7gid^}mYEN3nSWr^zl3;j`Bp2`v&XRay`1VJXf<~EO z3c<|$X?TF){Nb<>ea6)TamI~Q&yV6()WB8k1E?2=FCh8ZOw9kPm;rm>2hiUgv-edP zB^W$|Kun9NI<0{Kgwk0bLfn5{XqdHh9r4wl!~kqn@8@6($n0j*;co7;rA=u*9wN6U zF)p5NP8Oq)BH6flbI>U1Z-&J|DA+yvIeE~zH>gGNbGI9JitW1S5T(}Ik9_B`Z+jU2 z;4LZ+!Z%_tHPr=uq+*`w?ty~HDh>nphl4{VEH>K2Aoroxvbxv&;Vd`F#^aGfuivOO zCLX#w*c!bV77VAZxB0SQ+zVR0bNx!4Z#(g%2PK(27*TSsjcu|q;h}L8`ENDtI*8kW z|6yCuAY!5A8t&u}{ybIp$a@XJ(Q@7BO}0zu{1{eW{5LXjn*MtPK*>`{JyzVBhf$mE zXXm;1BSCPyzUkGoI*Or(kY$k;`}683;7@mqljo45yh;HH?W`rh7=IXciGX6aOOs>T zlO&ott4Zu2U$n7H`|50Yf~F6M_dGT+@;%L6vDiPO$EuRgR4AZs9mhfL&&c(zj6rW` z`FvWn>W(_DIk24}AqpZtsH==BGR9@({bYFF_E!gXyonojq%KONvFp_MTbgk|lduK> zB&{29eBH{}gU|`r-KExGQ^{1cKf`R|(i91lnhi4O{XH>Ly>x0k@B1^A){$?eEy1w@ z{;J8Bc3pxBZlA(hGJNNEA~$yZa4@*26ilK8p_IC>-(<=_Y+(LjwfP^`m|zVbn|ScX zv-RN*PA_b0wi~Fg{J5S+X{4Wfz3}@nE!TWZvE}ow$<@W!^QE8uN=*k7))!kiJC%UK z6o1x?pltjYf1x(VemEa?63+mDq@JUV@Pl4HzeRhSs_<+8K@tv!kYhl;<7|TQ6YSe= zW5F&mpF#bIGDsWnd%2L%saC}A8txwL=1yAPdCRM%(^#S_cnG-&)jDbS>*1;kmB5_3 zP;zYVAkV*YX3JO4_ypsPJ=;lcsM_Bo5P5IXR#|iF@2s9a8=!j@Y-1#G7ke`qboVs% zoRY+|a|fl;R+jWfGc;Torjgp=bMnyrWaF6c%Gp6mUs4*&ASna+fTo^|QWn*Q-2SIC z(z2HtVTT;@mUPW9ST@Gm+D7}^)1H9ogN>GF3SNFLI$ z|23r_L`+aO6D`vQLw6+*MXOrV@3i?iS_QAa=hLC}XxW_M7`YTCw&xI5SK;OD$wf$l3h@nv7C}4iD#bgVc0QHJlkY8vJ^cEB@g1lLi#YK&|hF6l)t<;@RRw3RG76rNfAm3up zGLkJ5w;9p9c8PLeaqyY@UNX>Zv#oQ{ieLEGxg(b*;WH=-CETrrL)! zhn@;44BQ`1@DWi4gr*6I3K*-HsCGxA&%u%{=co?nD_{4%W91TE_y1EwQ15|JwaKv+ znrl>fJd8uEWF#&1I)1vi?j27C(MYrpeN5fY>6yevV!vxniK}+2sES0)RL>k@T9>W; z3MzBjA>Jb0kiJt<&1=7_f<(ZnlP&;!tL6mop47%ES36~rUi>*1s89mT;=eUg_)LWMerpxGd^*;CLh6@j* z*0wV~>Gv<%UuZUMZQ&i-34_mm*6h`uX5B?~$*zOh zN5W5SG{Llv_dnR3VSf3wsBvs(9v>S|Hb2b&zn540IR5@mw@uD|B;|AqB^la?FaA(} z&F$~&X#spVYI8c@revL4NBFmrbT^{g(J_>LcjC(pVoPQfQkr+ey0)0KsHS?5^0ud7 z-gaEL)i_?PGIsmUhw8*~qLP)HOi|HwxUurT+p}b2{!a^l+3hHatDs_5m@#@%~xGJNxLOEeYd!@Oftn9SQw4X+FLcfAhT)ZBG$nFT1{lCtbUGCzc}Ih z+bC0dI_9~jv8kdp)YVw+>hwi&PVj+4JMez~MSf*URq>v>SW&ujDZ$0OHNEOLWs#3K z&>#C3vX{J?a~Yd6%C%|xd(nv8_2=4`s+|W2jEA~tQ?(_SGq8dwt(Y)DU)y$lVQe6w z)Q?SpCE;ja{a&d42lFebYeUqy%<-6~_;8`voMAx*FEmlZ%4 z=;)+q=m}^PXnHh$vCs_hyAvz}f9VN;1FiH#0U71sxH*!^yeen}>x9L0H{Ks&R7OX( zgYA0xmhu(~`9o{tINJnwv;_AFyvj+9K~|Xh(zH6R^uGObMgI054o)UaY5?oko>m6j zf}59J`H(Z)3BsUmbm<6+dm>f0RKe0fpJv50Dp$FX5p(n8!cYRnGvM6xw>o{knCplDMDKJFgym zS7bC%RtQE}fsQKu=_X$kYjZ0RsYSB%17<{Wf9uDIGt$rT(w1F`G6`1g=_d4*@WwM! z5n6vBZ7+HZwjzy1VT_70dg7K{Q%op1WMe^-mA1uyoH?@Qw)dn0#fJ}<*7eM#;cPoNWnaesSJqmibWI}HE;g_{aW_l_80*0F0 z`!0J1$|jHWOZV=no(468dDp*2J=f2hwU+C34lDdTzorS{+>7{yl$FIq;7QR`53}eU zNw2h?6nI|jOo)BV!~;m90g_q$vHZ8RyIA@%WFw^+;{e_1qEVk(#HMy3yP?!u=3wu5 zM7)2NO2%pzn*7sZ%P)DQpU~te$#NW9H<^h$>pt9AWaig1QC>KGuge_nwAE6mT*tC)i>uX(zn!fd8w84jta1r#JEMr zSs#t?z+`@bu`gkIwyPQxCH)-amzp=_X#0-7FFlWVoE8#dY`0%b0}f%FDjM*^e8KvR zo1D+;tkce5>q4s#u`ki0l|#PY{q|JVb42}0t1db7Vef-&RYCmjhmJ`BV4ew)vm=$2 zk_7JdLLH147eod!eG0aM|7ZuDM?1iiWEU|X;<8^7-bX#55)-9PvU?RC)Y=wNndd!B zWdEmPqDbS$4}S5iVH4(m16HG{tADHa#hmE$Cjzqy5Fq$Xm6~F;GF)dvn6X4qt!htQ zAaVnlT$j-=%4;-YFkS6jE`NCwEja$XxtyVgMBsV-T$Uc@S6fZ{h0}siSYdJKC)deY zd>=EZgG7uLHOP=G&wf_Uy6SB*4s1d42vOfcU!|_b=Qm~CxSUYq$025Ct}Cj+7DMl&5lx+xeq+G-3!_`fnjiCjCR_1*p_!t$MpPYvgas8GL^Q65TMr-oPiO zv5nN6=qXxnE*-L>to(EtZPU1u%{yI`UWE2*?p+wSQ29nBQ-^F*ywz0_D7KR_rHVv% zqabWwE!_5ZnIK8`C*J6S=Axw18SH_tWc1;nOW6A|T+#bn*83GdjfkHXO+x{K@rEgI z;%Uti%|j^9!mwXe#$HQ-il-dObQMDy<`ip#O;dHq_Xgkr!Tz64DQg?~lGEc@@P2{` zbmLt472=K8~}0LjiPe13i%G!TRJRL0kE*hmnybbU+D+xoa`7s z{?5gjwu~nGw=T-M?KTB{o4Hja=ju=@+69`{v<`7C49m=zD`v;VaD$h@x+n%Jl%O0t zAW%7S8Ay+(Mf)dsPNafi3dD!B%63@sdyy?tv#VF2?8Eh0i?!AntYi~I27->L#Ouf@ z1r{CXXB2A*7-wh$o757vj7Gxcef}^kXw-01PBj-rn(pS_*Uxd`Oly4bffEemgZZ-SGaHyifG`kcPYSwp>unsm9GSVZbw>ag6g{` zhxG~}LaJnf*XwF&tP(%C`97Lsny~rAejCPXopdgB58xA8S-+7kRT5&0P0ma!E8!+u zkoDhFW8J=smY%+%q}By3m)=jtb8w(n9kO21Me5v=C934!@Ict+*C}ywU}4TgIxOb& z$;GA8!6lS<@O1-KZL8{tOWF4YO1WJO>XpQCl+?{;B64L{V{XGgpqHS1>kYbF9UubQ z;CgrIRh{+}%ERl%Krnqu!lkPnALCgt!Ey!*8;G_6aeFsyw(OPk{Z$5XmA;bI{l)nV z@KVX}7R>$07DHbWOm3YRy3CVU?9Ex(TY%SIQD~6+SA;A z9i$j^*z{)`D?z`M#}qD{MjOHds2gnT{Q0^v%zGy%jH^@#z6Wkv;r2x{%kjWj+nDF8 zuj&jyoIStFP_lLyO*S3PIW1Eihc9Go7c@<@AmKxBEU(85UMYSyT zlf2u1CScZ0{f$?3*`XN(oc69&p|3Ym=WTp56^g7V@w7f~&>*0qIW4y4+OQtmQvI=P z(Ymz$7wf&hQ0jIKHSK$o3Pb824jm*CL=f)#YD>^N?60@V34b-0+LGvTFD{4@jYkv@ z7$GlF5J_s0h5|_)a|&0IyM*xM*B5f17-i;bR z|JynGBr9m#gcoeY)aw^fgP8=cT@iWO9#gHQRP@&jE&EYT*QjDxqi8H^gmUzj%X23f zxF(8YzZ=M}<{K*s<9-E=SV$NMCxR6%-!vLKPTr{My_^PrT1Mhvp7#iNREnWW9k*C9 z1DkG?fdE{8G~8-wGy*iP*5C3^AM%Vbi1@~yP)Yr^=lqjRTe3w*kBvC-#II02N%#pQ z7gjqGy8f*6ogc6JmDYsxEYYyAMa=VhNUAD))e4kH2k_HSx^yhH{gZ(CUdh#Iq5I~$ zf6As>{QORrglxu;I?gskd9wT0W}Px)hbzp$^{3xe0LW%w@2l>D{-u+)Yo7;Vytz(( zLyZ!od&>^>?IL#nru}J-ThosIl9C?Aee&x*r`po_P%}Fit2*t*TDNA7`pH1KUrk#j zbEk8rN~$$D3Fysr-dTAuj!d;~y3)??xjn`3|M%Sj**JmMWWPSj*i-1Op3yjTDyYoIuskj#rtL(V`(mMZne^NWx`akdg z$QUz}G{$-|^M|6slSR^p$(VTIsz28@ZPlT=Y{r)OsdwSi!0%)m*loJmOLPu%Ucmof*5i!!pg> zA>XaV1z3;5mP?q4|8y(kdYT(YU^64C!r9JC%%Vw-!+hYgFJf!@|Y^z z3f?te;wldHx*F=`!b(K-i-k@g{p`^DfR(t*LeA}r=#AL=4_2O`Q}Cjn@$wq`EUfjd zf(f>bdt0z?J~m}`cf~T7^0WhAqLCVc7Wsm9aWL!#o1WVnt$2Z99#p1+e%iSi$WWbk+y(J;K9 zqo`ifmk?NlkJ~RepgKVM0uTiB?%q&{+aYRuX994d?WPm4{_MtwyZ@rys(xT2O4DYQWRsrKZ_rKc45Sop32%q*gnW8%EP9cd1au zR#wq=IA=DXVh9FEs?a8A>o_ymN7kj4n%?axkM^b&RUXwXRQ@iqzbkpbUOdhys(!ov zfQR(}Ej*(chCg`~-@lbao>2nrVtI^UfscN?>OiO(a&GzZbIm9g#wl6v7QWd=#rB#2 zzI7J)t1)7K8A>R+&Q3p*+KhrZ#tAw^TZeSz>7C(}e-x%{PqW`Vw9T26Q{MW3MoE4f(lOF0wz7 zPz?pa(r%p_cfRUQcCW`sCk-g!BOK{#EIGDyC>*D9y=)p^$Cm!f{b89#4}VTp)B_^` z_HYkW0CmZxesKy|9Q8Y_WaQo6PyHKAQESN7?49_ua;==kpUX=zIw1-^p#}J$=sVhj zSwS1?vxn3`ImS^!NQH6C(9gHs&KNj(VXJ)TW$huw=Xmj>lB; zJw-4V)L@!Y)7MNkLUPH~uzmRxUB4?p*+-eCVT*@7mo^HAyBOblC$A50SlYO5|G-4t zU4BU(*_+OU_o{R|6@#2LDMNlSKqJa>)10Bo-+j5oe%;nNE%c*~>t*Jp+0F|NVXE)( z)i{%Jz&QO1GfU>}?xu#}ay|C&uTwV8P*&G}Hyk*_QaMlbGwmOuRFuE1O%45CT< zCG+HSpDc^?he-uX1`Hn@bkdVl6xX;*Vf=Ug3%u0EL}~$%4wvjDl)Cv$Ai=oD0U^9M z6?Kv{KHG2hs;dI&|EQ!R?TsMX$Z30#!15&r?F;4y%XREebj(G?FlUK}tgV|HW>=a- zE2dqG*o}xLA{F(F$tZ`>4~KKz=rF{$TYzZo7*Cr}ZN=ZADe_@g@#HGW3fan3>!kfI zLDMpco_0T76F-K_}2b|;7FD)li#A} zMKY_MqQ`2Gt7h74V!TBPHx&$4>&y^n?4F-6FWp8F*KU`sQreD6ibmL+W{JABm+V#{ z)y}x&K@4gZ?u&8E{7mW9x^;bBhQhD?-7eFqHq#?E!g`Y3DBiwP#1yg6FJg`kFOyVw z>BYLBPz50ioK;jGNG~!q0NYD)mLf*xwoI@J$THA@&%v5+?1Ht#|!Z|3%PJ7v=R=>T#aH} zwRBbNFN;EV+-|3?nYnu-ijXUFSF6@akO+8uXxmMX?OtuKHYhjy{yDFX22ePkvbG)q zLzMC^bl)T9=y9gUYO1v}^neSzAx=exFP=@&jC}>$+T!WjWykibBB7 zG4|R`U+2%~r6&|d;p5p`NVM>dOAL|Lraf<5t~#b_^tv_CT|l~EfvJEV>!n5n;P3di zTmi;hQDQ9t3w3WALL9GR6lz^${kf=3E`-L{u`R=2LTHGd9kgB9%b*Wb&Af@gC4PIKp`1nc3RD{kXydt48q<+`0a z376SZA2uwRPx>e&O})HsCw4@Au+d3BKKU=eXd~>8mQ!1^eaWb#1S^z@c8+Kv9h6mh z<$2FD?Uw|$l_gX7I`lE~%3L1&=D)qCYaLgJCi<{g3CP!LgXH#)vHV-1tld|rE+=fU zK~o$OI5Zl-E`;dIy)4hVDprhtc3&LXZ=PH<@c>JdBgFVWB*V>8&^=>qze2*4QD!eXZ+50j_Rjja)ju#xG@f;{lvo67OJ{ls zIoPIY$FW;}N=^Nc13CBR6L`J{rg2@s>0bTo7KaxW?i+)J!BHk@4ZPIflmAf42Fmxo z_nZgo=46Zu$IlrtQw6d_=ITZp+#OK1Vm{WWUOd*QdY{zVWFo7S2JlDG0X|G$3N8}c zf+McCDDGxiAxECZ3?eCSvZs0TGKR5Vapk+e&5Y$pJUx1@+L!R-6RFKSqiTQ&k%34i z5r#4`FPFkSCb!5xR}3TDD(s@RYNFq+?BD%o_tcu^xNi!$BD1mK98NlAqHLNebHiRa zJhc^IA1*0tutQq4u2fxd^H1^l(N0@Cu73^jDHt^gmlL~rNU~O=AHg|PVIFccyyc3tK$7c_~rm_Lg@93K|s3& z>Zkr?{^H9JolxpptcoaGs!&~C!URf*TH1kp@|$pGA=&$BB@!en3|RWsEXEqY{|&Nt zjSoCyS1p~EWLl#~lZo?F9i|)_JWHLOWoxG8xtP zMP$_Q?c_t7IlX{uDZ@UFyShk4p>JKa!+cz z^qAL9F8uPBVo?mts>|2ANO==6_mZuGTgfK4QW|GEs(3#bW=UU*)3^5oWCJ2bs-BlT zu!~42uWM)e#CH#E+syDXoc55BRx ze{E$7yW}mS!gO&&%#MagVL{2ZWRc8W#eFB`yz+f?dZ~o9rp>J1TeM-@qJem5JX8`( zQcO_q_J#H3ew_eGw`q?iJ>1uCFQEUl2Finbr|lmj_L3e)b7Ek?`~G@l`9%|=!uRQl zImN3CCUE^XiaEMNhO=Mp}l=IE%?gyA<NYh`#sd_2%fnh4@gIBIB&I%eq<%0HpW5)AqUG2OuQ}C92jicqVtEOF#3#ebeM-&Zs(>C`i_`Dxq9EP3V33UW*9p_I zln`V7mTXE|JwUHt`{hTS4IxYKSW&0bekMH)Y4Fwexrv; zz_}jgZD+SLGX%rye))dDfE?m}PDM3a4rekM0Md*;sFV^nW4D<x&(gxaF0@Ux6&d zO8b~54aCi}F!VxzALllpaO&uv`%?#9(pMo)H-?+n-Zpr>+H$_*nOeSM)*P&&_2Fex zK~vDSrlLtSot|9eN6mTAd``riiLcs|nH8^;??146+~ZXBA&XuubrJLtV#wRMDf~_5 zdCK6juK(vx?Qv3}c0-(h&lEZTI`5rsNkr;MqTZrVsiAjDevel8m@3B2CyA%_8XaHm zjp+dmYCu2%&8UtX^Js46MGb@tr<5+ydH7xnG`ieB321>S9b#8%1as2Vi53`O7}d00 zy|Rqt)LiIx8aDz9BMXWV{l40rdllo5=47w&LgP|5yuT$^wUQOK@#}DZiVED7rEAj?j{&%u<%1G`fc@*qAS^F1KLdsTYCg^@*e`g){+gO z^d4bX#wS;P%%r$ct<128;E&`*D8xv9PCCMxXC@8_CHsijc;SjEg8N?P+ucn5pRa@^ z^op4gN?(Du2tP&|mQa;ai*D~AA}KCuu1~L_Z6yMwS$}#hB8x@(oSHbYO?QXUE2lb~ zE-S}7AIZz+H~rhXHh}?nt+P~=>y2}32|74aj_}u65tp^hg<}m&7xdk_X!%7)<-&6R z*(L?GRYH!_6g2tNdFth^mM$pb_zBotP;rYX*C_2BHpX+(*4>Ba(t-G-XpneRttpd5 zuL5_S`~zpAAu}6B9OTJ_kRLPKdHH-dpu5cEGmn&*L{MxlJsOIBnU#;*SJGfxpY)@1 z$FW%CS7O(%#oNX>rG>%^Qv=&!ExN4e)pM2gb4DV&J(pG?PIw2`$*iRsUT2Ehty9((h(~CG^tQQ~%TuAsb03=xf;x{__o1N>t1bfFp85OhXSxlt>({xP zq$yvi_5c{cK;LoklhG(8D)w$%R9^Z>8$I}`;m>^fPaZo1y^qc0Ug<1uMhAqEM*2%q zwfA~EY;H-XONOewW6p%O{(IO^vZpa5WUs6nW2%a=+s8BJcUVOEKe~4_ULMuNJ(JM0 zcNKe~Vsgt)4p@I-Fja8K$=TMFE$6fCzu4~_VZ}pHYDG@npmyui)D=F(R(TMCy4qA7 z?);U$TFq8<(JOWN=G;TW;%C`txA?)3G0n&4g7p%dY@B7(8)XIsw!4 zcH9lm-j;N5B4;_YDit=hQ_%(!%jIOP)RE^M@0lwmsk|1osP(;0wuN$AYU=8ZnC|EI z7uIK{a}*EJfYl;hXDDk!6z5-F)fK~px2-SZe*2V3;XH^k*oBK%7?+JJ1?2W}wMhq) zB?@0V1&7mhJaY4B6cj|~EYG-|Xcsv0S-oYv2Q=USUrTfdUkcyz?QY{XY=^DRqpy9Y zqdGrCV{(yZTxMW=TT0Ha;4nBXSTMzy7+`>BRlfua;S?EpAar^9HifuG-C-WG#}2hEJ?X%~vMN7B>Q85CNKBxCC@|{A zvBy<%fb3-rbq3McOKrJR@84F(*69y0XS?ZvwDJvb+_W+d2kNhkwYfs%$A^4#q;QVl z8ew$%tDUxL6xUXYQy=uTeOIh@*oEN_KK6JI_1%b(m$u*0{?@n1#yXY>3zKujI?A{C)q3(DlTYlz|yjJpzW zasC}tD)k(^k`WmJ4bQL>>t`gzfJB`JY_0Z01ZCnq);^>&T_3-Hn)al`Ngy`Jv%R7>j^8fG@Mb(*)EH>_EZ7GDn*q7Ft5kM zQ$!{~Cmzyz51h-euH&-U{x&}sBWkT|g6ix-Q{mcE5Q9?9Mm3Fl$cRR|DTc3JUQVT6 zJdL%CYZY{RE6z+=a&fUFfYaNL52*jRnc zf`8(Mt_u49?0r|FfV!kfKk5#p32eA@jH`NzcE0N0v4~Ryyb!x5fw#W1RDq8)3eqimj|_l0wf zI&kaL0a$$q`U}}BL3ue9ocqOpv7f3gyy)A&MC5w!-~Bg8axlXewG*WBU)L}Q6|Iu9 zo|b(+q2@8DD#ylWm`8?H*qYAKFLA4=AHx82X?NQ@-83QLp}1^UO(@}C32 zL`4DLQUW~iqg~(noAO5{h}vfBVYtKJ^2jfA|HAasdS(^ zgF|`Rds323JG+9_H7H{>h-ww%EMaq-vJ& zTY`TwrU_BBB`%$s0-{7@`gC;9q>dZ0{kBs5$*wlh;Qqy@knDiK@BWgw8?qtyZKB#i zw-!o@5Jfo+pO+$b#-6aven+w8{gJj7%3UISQ)h#>^ka%O&z ze&K;?45os$M(0q0rxPc0noY@U(!PONKN`l+a)_AzC#CXPq6StzE z`)xym?C=eA`NsK3Rfj)!}2I9>c~b@WnOA*CmM^$XLKxZSo@52!W` z*ry4!22Q)evv{nw$M&Qssp}`?N^Ki=+y{yS$mfkDTuRQSup2X5Z{82yD4WI;m=tD^ z;M`Y4ylqfKHsz{)*Lf<<$cxb<^-p&8k~|6h6$%#7ATJ+j6W&M1-=jF3J~Y3`g>5|F zemmF+r1We3HO$z$jV|sZX^edI7k+JvVo2>1UmAF658-(>0sB7)~s7T;n%D+s5=7EUm+EdUj#kld_eq1lrS z`EIG7^~8kXE;&G$qR0}(Q4E6#i*WOiVLU-G4An%VXO$V5Mm2cGjFGP(sLD)|ktXPm zWUGhylUK!Ko@kUsZ_9a@cs4_}+K^u-^b02abnWS%+S^8!wAI{{GDR&Ab!+>V*+xsJ z4QNDQA91$vj9Cw`ccB0MTaX@ujKQyNxPiWCQzk-+{9`lq`_%UEU74XR!HsovYQj2P z(*4h3@UK${8g7n1${X)em|G-Su6uA8YMw(rzqJ04A-WXA)Dh|Pa#W@kU0k{r6A@c^ zVL-J{&zmg~@U(UU9r9E-B$zG`nhjIJbQ==+Mq;8BkKt^Y?WJ_5is9ItC&*pql7HgBnAM{MLs?Ux7+JO^7h(_NVRu5q3ddm<`SAun({rT?2Jx%N3E&Y&7> z|F5TR^Vogk`bbtb`v1uK4xpyC?fsJwAan$zNDE4_fFQjS5CNrHQRyP>B29Wt0yd;7 zD!qw-UJy`_4hcmNr6awIROvOez-mLxZ1jw%-Y{a%UVLftdC~ z8gx&QV<}FFKufU#Hk^M2my=uaN(D?&oqdQ$0{%lBUq@ZkbQs%>;g2Ql4Lk-l#_20m zfl;;J`&Pp?pdafJ7Qj?5V(ri`=a`E&FWcJCh|93hUM2^2U4Xm#PM26x1vr(-Uo1&l z2o3(=vgFn|PYS~0C2^eHgaJNA z0f*<`ySeAr&|Tw{nW_86H?x;mNpI)rXg%U}^cx#NAKPBgjb~g-qVi?_Qt{L>NuS_0 zt!SHw@|)Fvmw%QY97Fhsy3bu{x;AI&kGR!)}mj--@vkN2wl6PYBBo~w)WKCo2{7P>xflkMSv ziti6$$$2S=-DBUKlPwI8fLe6s{QbPVybyuST-(`{m=i=g!=K8e_mwwIqT>mT@8YNA zkj~)5QAG@ti-(`Y*#}*@2q_!vo&Q(C(PXn($gkT2ammYM*4J*R$dR79LQMjUBT4?jwY@D~YaJc!kMp`X#iC^3`uK7Nb)wl}OvRj98)NS0Z*ET6RWh6q8I9m(>R_aCgp$w1AeG9E| z;Zeu0K9Xwc8>={4gL76EigAS0P(LHK0Z+7>mFn@}`S-9&WQG~3B3EVIunI-k+1sz3 zPHz^U=Q{*nn$hRJ9nJ^fo7Khej)Y65#??ADAXfn)7R4r6Kq8%y#^QA7#~1O)NdDB- zZ$f@O&kzE{leU$!J?FgCStf*(xvY!w@C#CyU&a^9h|ZlIWb3=j-{O6FD?MWFA5%5$a>&+G} z!#=f%{2rY3K{)Hg1{9t0E$QI)@fkDuujAV+UF>jyML{{GN)aqo;KdZrA~!i2W7doS z3H^cynrJ&xA10M~zh@L?6qo+_`mqJ$Ir7_C2Sp+si{-gmyq)d|fW!<7?< z^5aYsqteE_4qDWPQQGB!I_y+;Hws{~oK8XsLF>9Yhtq=AsK z!m$O$a!ijqeO`hLpz73T?6wwg(?2aMGg*)ij51~m&P`qAyOb+7{ZVNHT0(Ho^Sq&g z=hf1lm~f_F-Qt7_SccxFt!&Irj9akkHGGj->56`I>VUzp!Q4AEhF#2K47gd5a3At`dh}_ja!!3Hqp}E|VNIn>hX10$#e9$| ze4=}p>a9yu>Hfwm^7m|B*@7|;28)H6_lEp4VqSdYk-|^&b5i0U;wWLKgh4V-jHM`H z&?PK7v)h(NP(2B2mw>=EA(*XN6MkH(<-6# za~1iDiWLVbj+Id#>?SC(LpXj)hD)nRCzrjNWvBJVx^#&yvS$jC2})98L;7iDL@+0) z=*7?)I4_*pNob&DPNH9;pJ?3K+bAQyGj!`xAS+z`rN-xW9)6%5#r5G3&*Z>)9*)}< z!W#FH+C(Uk1~_+C+#O#$kKbJQgZu_@q^zh^keV%xYp(B+j0DL9+OOwD%aZg*H8FBB z_a8DJs)S(@GSc@In^n(7qEtvmqUDl>qc56l4`pwJ*};=%)$+C}BLMmwrm6Hi4j6>=p)!p9~3t4XhqdfLo_^xVOIio;iSB_|kF|3R~ z8HQCr(B#P22R(JA&Cu;yD}ctkn`#U_9kvQ?4cv5P-Rln+9__WGp`|rE8=vR0I{9>P zEW5uD0Fm{57Z?T2AF!aLJlJx{Q)QHR2m|*gTP!NFthMA!f4pgT&6L{H-1IfJK1LU798(*3}@clNfuESoLC;Mn$$Rj#kb9(#%YL&)TcXEBNE;h z@93do)!{5^>kpLth|e~am}7viSB?fUqME!O_(Ii_ZW)W^LNv|ELjdVKchvn33qpD` zzDCdL?M7Y<)hNpDZa;xuf-#?SrDYlSvS5F@jnnP>aCw=HeIMuCxAntxqnoGV4L`&x z$jJD%4FyDqEw5puNi%6-`$}K8c!O(a0$j(RAG_iD3_wssJ?kGFrR04@$yLylVND6X z?%6%}Vp4~Rs8JQIh`?%@CKh8zZj!-aS5ZZR@M`Cbh|nJe)VH~j%w>6J+48$w5Z}D^ zq47(EGLA1x)Z0U!TF8n-UNZIGj7BTwc!hadJGG+q3>Rl>ef{xU?|N#NUIjlNCLepC zqWwaX;0%uwTO(=|iXq#9fFXb=vu3PjVVR(iR;udN81zmjRgMEuQwHPwRb!SC0%>QU zVLmo5IjM+HHOj74?eE(KBQj@#%rxiIDmv91I4k?9!lf8^DuXl2a+^6gi)WUryz~j; zsi)E73l0Nf_Xb(m<-D?A5Qt#_G}IL^ez)TelqjPISyEn6)U(>CVR;)`UFDl6DiMw& zWseieU=+zWSv9mOGUaqSI`Q>s#cIPD`{{Jo>XI-L!)0ji z`m3pA`N|l_I*+GANN>KItIK%Itxam2>=AAr4M%~H1IHxuM}@#*@EbuY(7`@6#c6rM zV^ohzZGV5DJ4QIl`XrvvmPozPv~PNhhKv)7NEY1*qmBzq6;LXQC!hQ-w%`7=h5Rk3 zVZv&O!&6CS$W-3qLQs5$L9DL;j!yX?rlSQ7r#O_7-?)Ixr}!9WK=c;Cp|wG405e+q zZf2hYJUMx%wc_3IOsal=+mMdTc=@Q`x+)@&y1xHg{Mo*13k;hME;2S%B4J)-QQ3)qkjO}ggR{|XW+1STZGpir&IDB<< z5rfk{(rue+PIJl?vJpqIQp18Oc&K}W=Nd5!!Gws{XVgHB|NJCD%YpaEmjsUynogaK z@XST+iJ?v4S4^2C;fqQwnj!k4`*EL}#CEP$ca$;w3@O1=AvQP;Vm5CWSXGoen06gO zbZcAx_!;lCuc3Y^*QtHt32s>6D?#E|6WY0({y=3iT+fW_6R_VX8ZdlCI zEfhG&;4He+vB^YE-7jOkYp-BP+P|h-#_-^|W0r$kw)Gn%#EsEubF0(%h0m9Hpw_0awYn+O-kmLwOKSvO0iuC)J6Uy2# zd>2;*Bcpz-EjoV_-S-TLv_{n7hbGU%{KW_mKkJhk=n}Pm73+i>*>h8vQ5Y;`LH7Xv zwL0%2a*@IaQ0cmu!{qiHQYyu^s2wkcEizCsfZp!(0|i2 z1>cQ?gjpTAD6raDnQ#v*diDqbGu58#8Oe$&#E?UF7yUDG4J0;TgDzTb-~0|MGU%!(=h)ygf7TnvM1M@DF?PgdT6q3HkK z@8s#TG2VBFS!kk;B8WeyyTq&v9u~T@A&wmx7^!w>Hyo8;>9oQ8;@x4HaTXqC_73{h zj$3}Ngf2s@F2R87xd9hb-?egHtNId=d^%&l5MjDm=60}%GT@!wV+_6^A& zPj<*{)m3m%Vb-RAyO#j*dV`4U_J|u7rAnjJw}$VYJ{QT2oS>UMhj#5kZ2$O#6sPgH z7Z(nbHYlORP3or0KfGUd%goX-24=(IVvJo`A^YtWDCv(MY%pVu43z@3C-jUzw@MTy zR+VsvBH&I!Cd&)*#(3gZ z_1Cgq|M@M=53WFo8?4DQYF2yw$s{rdhKj`aX=N z(Tt=~9nx(6+O$e-OtRJK=Xu!UfUov8cB8Fl3%CzXFvgwrdUzq@ch;}L^-6h;(k_eB zMq1ONj}O>zfZ$FXeoHyrpSU*LaD`S4#d-`k-RteSH*(!`?N}(;KGO%_W+!Tn#x+a8 z+Kz{rM|KLu z^|@keOY+3iL<>UuiyOJMXk*$5GkE3Gfj0xERlexzL&M~(X@(_mQfsUPOuwIE13f?o z9w2laY0+n`WO;un8melwnv5b~>VxF^<{tiCKfLIwtij?sTjEP(zJfkLlS*dE7(j3~D%gZtX~@tTtGK z4^F(_gPS~mqzocTF?>>=Fud}O3gB}bxMyOPvV-Ph_gwQ8L|BAakzs_Py&Qk?I+N$G zvjQtzG#yJ`ci(1~UCBs=Zh1aVV=K}uyN$x3%@Ox^tNquCt)6`2Tcl(d0Ddc?LS3n* zAaAT(IIW6rjQqQI&1bA>5v{EwZ2X%$M?%Tm7_LhXQ&dGV3_ym|cwvIQ!#F8zj8xuQ z$BZoDh2;+f$b+b1^`6!j{_2ar(;qgtEr2maWL~-|_GVg{lSZZs#FM!FW_ zP5ayCJ+ICjmw>EmeF@2NTFriSwd;i+gF=ta#7wKI%P#(oJ=cxynSkNFZ-DWC74CPAvKY#J zbCAoBJmJpqcP2Z<$g@#!5$&RelVFS25KYlW+S$hfR9+B7`4xR#7fStSyP1k>KMZDa z9Ps;y#yuN`(NxX9f=-{np%W-*he1+jZzk|gIS%YG)KG`$PXUn*^H3F3>}mLcB6{p& zI0A0NnvlglYo+4(IY>KZ_xyS?Iy)hL6#A^{O0^!C(>=v6d6FM9A2IL2od#%PSTs9g z^;89&9xKpMJ(l13rsAe{0NMj8iA-SASVjC%W(gh1@Ns+8FkSL82EXfx5fC1@TF{TJ$E{| z1UsPzx~=nwnHl$K%Wz{m+j&OZWxqN7F0H|(r$Ov!w`>$GV#Zw`eaXJfSV7EaaucKB zKw}94)-*{eQcmCro-n!3 zOpvC)G};tTdjvQ7`1XicF^k_EJ^Yfjfe&o%=zZ3KG{X8qrBblZSMxX2kZQPhppld7 zWHTaq`z2HQ5n$fiSQAvY?#tYX1@|$KO7W4Ym-E?fLQjtFEH^nOx9%-piKW2FQ3R@V z;->3yM_QPttv#;v!0KGF*O11QK=zYAKb9an?E=0&+!i>g!$l9fM`rnLKPXAB1D^J` zj>(_&pJ5w;;jXd0WsFQ1`_S-ilix_!*xJ^%r`mTn$A72WyV%I?4XFJ%z_(4G+Q+&w ze7U25b}-lz(D9#p*4$RtP#$%UWv-JNIB4{}o+p<=y>CK#tRW|QHaVb0$Pq!~jVhvr z_clN~okuK&k_aQ+PyMG_UsTA0DQr`X6QyD%nPD*9c@NxYRc7Q!+LhVT-%|zJQ-dQ_ z@4-u|c*+zWGkK?jW_8dnY~$O^j4cY+vJzt_OhGFC+hx(I8lmPDO3q`dp1tA)`7TG% zhPp?-*cETSC48Je!=>`q2>0SLVwH#=<7k?-tX>_Gjmr#qTa*k|$R8GY~{Q!q_EEPWb&G40NTLog1@S1YTZD zIgJX!qMsn#(=DDzHJCi4#!8)h^p>g&dVmDPDJ&(ff*v!ANvV}-(;gZo4*PPcq#z#Q z`EhKc^aNVOD~8=AZIf{!gs27?6pF4evIAZiR#L(C{Ah|0AdEGil0P*9XXX8idw~^R z&XjRpw6ad>-v&^!!%8O4STJ%0lGt&#r*vse4q+D5N5*DWWbE z{T(s)%`gSVD)*Cf%l2K>1?$TZwzTN)4>IvR$jvCVdzH+QiJw*aRb1xy)y9DP-B^CC z&^r2X`gdS!lu`8}z5$jy?@Md^#4*(>DKIipP*yih@zsnF!IyRQ8RP@`$)6d3O$`*d=`FBrR zk>PcX7*CddD0C-T`0a`{JBUsR7hltzpKrkGR8r>h#}<1Jee{F3HJ8==>4(WRiHwTf@`zP#G&zS2G#9kB?CmKV zechxmJND=pRf!3wc3848DDJva#0;nD&kX!8-FkE+JF1+Tem-_cle~(3O2pSVuwF=I+9UnzFEglWQ73~QL<+n6C@rDNn8u1pd91F)B0fUZXhEf3yI7xa7!RpySJsJR=PMw*qFq?k=mMAH%df! zR|qOYXlp9oN*y4ji(_uOS;05SJTa4N_X0jZl#3}SxC0#5zZRUx6f=D1kfBBsb~$0b zLS%4VL#+&X48?keB9u~n!*-{sN7z`;5Rp+^+bjuU=^^K|7(gQmzaccdp_&{55F2?R zF9zsFi)u1^d5-{scz3odH@d9ZTG@{ydDD=b?d;q7FX1G54%Xr6!g~DW!vWBR* z>}G$pT`kI>lQhEKOwMjE(4oM1x1=b;pkL_+k%TZE#2oX99w4f9%g^gzxG3bVneRa{ z-hSO*e2l1ael?#7M8#N#xF-~Ee%IfhM+Kl|;D9X=lvdI21}8DY0-^0fj~%htfZ43u4PT^ zaxlmo0c{0b=*Fu6amK$hE1ntYby6Mc=urwa8)pB~MTa{N;;Dn~q>8HQ${N&dHZhX| zuS5`xDtl;5fWB=Rm?}*te(*EfCkM}uMMc1zrnS+HI7z~TcnM#oI?DPph8$({e(}yM zMF=JrUoAUwP}WuLGh{++fofic^5l#{!F8 z_4-auK>2^aeh3JS3`@r|hOD)FJoRU`b_x@QedC9$Wx3%oE`j z`4q%f-yR}6B_N7xyS*mZQcRc04<)BnAbimoOGri#qO=_SW^e0VvDEac3D%{A*>uD^ zdG0oeSHy&ox)mR<`Uk>!l!anhXwBbz#_}ll!|{{D#ZQZ~K?*Y~YW1{7_#O_OOQAG5 z$iz?r`!B7njAO#TPe21+5!4l_x^2GCB>wQkZSz|)ZfP}Ib!0~Wn9h?AuOj1@^&eCcq-Y*Pdpl-WvKBB_RV%d-g~DnS0Z60JKi4+` zTmssw*XoWci!;$?i)iq!qL|_S^d#Lmb8MQlfC?AwO6om>l!JAnHVUMJ3@0=-(cDRn zz4<7c2DqaO(!y%F#_2Q0^+CK4r-RlVdO1|*6T(S;#1|EqeBY5E`5)|dqsmLW8XH{_ zs$-IuzH}~0z-mP@@K;{wQjgubDVKaO!0_fK9VNq2<%gaiKzy$TyA2WQ)Vpk5Ti~Is zuiP()@*V8to`>d(2{kXjh-;ohVzI0fzq6fVvUfz?k?Q0pR4Ln;8;Ru6NlAWIjV{p{ipEK6OA|WI2*pWAc`?FwFf^wICShP)}S9`jW zg7@&j>_caJ@8A~#&!IE&csik>pHjj*+zmeR zIekF|D!S5_a1?NCz2Kht>t)5H$AwEkueGj$Svqd#2lRih<=~>%H$M5os8LYcSj~PibDAz7(E~|aZ)lw z>+zI+rw($VdbQExLqG~`btG$0V7ywoyK|>thc93AydH*syrwfGh@(p(;We;xOz=Qd z5inws@;d~HuV=yf&zA+fZDH0i6P-+mZ9Oa(L=2Pf9z%{;n4c@eYuFWx-x^4UZvsO* z@C=>EOvATSm~cg)?93AKug5whUd%zZv97?Qv87C=|XFmyD0zy@eZ zj1OYs0iiEk-DiNNVk8Uek5{>nji$~_NWnemir$w7%7}0+v0RyTKMIVY{ zJ&j9NaJxf`eyhDx$Ov}sP;Sa}Z1r0isetV^l;B%<8SLyy8i9Du5+cwp*z1iaSx6NI zy8~cTLjD9Z4?uWg={VMJ$pF!=d|ED&ajQhka_>s<>sX3mdPW#_!$+|&2(2@oKgtQ! zXK<%6-{Q!awQGNv%a#NZ<0OEQ@k$@k3OebO*xC`3NuHZgWR>`WJZ@vCo$oa{`o>~mZM#GFekTY&yHvop(YxqOD z>ejlC;agy&pTt5EP;0j>G^$Hg@xl1PtIPl~IMM9`d+LLsxv2XJLOoIpAXKvEn}uXk z$j&{UGf^^E?L(ZhZB$N!O!c`)Y=?atlAm?p{45p9MFhJh;UKTigkWiFJS7Dyasw$Wjq0pL=(POO5_2uPVP|Nw)qvl2DiUCRKKY+j)=f+MG2S zxP)s27!LSmu@8qDhxlG@&D+0CHH^^E(f~(G$6aAx?XPnG)h8hieLr6(hiudakn4zy z>6|*b$DuH3y7yDU+XhCU(Fq9R5nO|soeY{5;j`CZC~6d}WKaUeD4;swuu5v{Txu-; zR3>LDy5s_UmvdT3KpiK6KbY-EAD?eytEKG(y%Mafb396SDQVkASx#uB(JAr5f@CXT z!0l((zKj_qdPWx4(0Kt2@q#j+dw|U#sO#*Oca4SRc13CU4e*6*wv-Br(IDZqY>q(Q zXAf=Gtv?n~5yCFIn@=dz)d-S_%{L+Uj0_~)gPmJt$RE5&lEsTK66-O`Cy}GC7P*2&9P2KEKO28FB5jDXPGg zg&)+{okZS*mq1dcwCBSxZNHK{cxYkbP@`sr67Mqaq<~S|J?3L?&?tI%@RY7WKkmcH zYk}FG9{z3B7h`tzpDgd@dIKmQ!pVzuUMNFEg35{zn9_q_j*3hM#qrl|4jc_sFg#s; ze!jJ7@#Cuxs38`yhZaNAos7mckIYb=>Xb2dUFu;@QqL*!?M+XbjV~H}3lq48Ll3i^ z>`a7CsEPDkJzKKG?Qb2Ij!E=UxjPgP$<2o(8d-aIbk$H3e0^1#G=K|M?geq0*PV$Q z&8(n-Vepl$#rS~iN{C+r1$oF!)p1eMVFVN8tR3Kd+&ch!c#_5u>(Ybr2Q)Tck_h?? zAVCt<^PD<*u>O%>sYs2QQT|27v;{gpWI@WSwK>_FFx7by@If-IXBGSQ+#wwTTqr& zP(fUPvMdz?)Y8Tz9)@Lgd^`-jq9BzUrfnBM#HEQLeg+nJAR4V8$U1QK-0rTU+0@$ufD#clen z1rW;d1l;2^>FhMzcX+u{G1`n&m@v3I{XH5M9R`69tYsv0Bz4t(oIgD?C3Ea!_818U zILyuG`+j%+yl2bse8h<-AFfE24za9v0bmKe8R=V`QHP&74!%;Toc3uUkZGdI@ zdMsEvSWsADK9A&iwXBU|(dCpkS^hOCQG|fm`A@|%YL-fXJi=vow%^j>n5>E~a2x5! zPJjET+SdGVUp`D(p0BnEV+2T;>yPt%Oal%Oc)nJS1+=v!;BOc$W}Dp!=6q$^mb~eQ zk8%CM*VQNrd!(7cg!EdIfYltm`lgvC=agYufRf>Iy(z1s8NA)!o^PZIM)H|1wVNC! zbZ?oCDg;dKXSu}&3StgCvaT1eN@$$`aYQtNCMKw!qbk7n#ai~-&}It2v98PDOBN#Z zS3ws!>NA3#h&%Hmts;6{ub>ca$9x(q!fRq`&sYY68!+RMP$eF{Xj_K?1%E*4iqW*guB93{I`A z9B#!?O#R=xk1bgpUlM-&_|eP1caN89sgu@Fh{DU(L-}4kUst^!QF}FUcv(V1!lk=L zrr)P0!UtHHIXcQ{tCsl6*9zZ5sYwy{!v&Uh`gUX5z77R2s0jha9vB*QA~x$P>~Y{X zdn^lbB>Cx=fR|@1X6C2T!wobzfDo4GSlipr*bX;D1)7Tt_f|=t)TIm$JA!7?7a3K_ za!V~RQC4}9euy5ZAb%KiK`XIEM zuOWECinw{|s`$&l4jMtP68b*Eb-cDs1(N+z*b(Q+_$o>RQ=#G@T^dN`7>vFHQgQ|e z#sp%`TX5Uv1I3#QGmD}ApsA=sTwH_!(FZKHauWKPz$b-E5s<~_YA!xe_dPt<1|)_? z(^YS@t895hHYq3Nom@|#Bd(dM%xDHWwg;bZzhzkh74_7}2ryHf#i94vxI-3i#hrQ4 z+H_l5*Ylq1m<*SCTGQ0w(2gnN2`b$8BVRftV6>I`_sZNoGe2 z^3mYrMcr3a#&?-r?Y6x_KTr`0!#%j*fSICo_%+dLte%&34)w1zm1KVWgyl`k?B;z# zA)@gE^+-?ySk%dgaHZqUEQUSZ93TjH7qd$yUa5Hgz-a&hM%SSMCOp*+22O9-{Y76p za1Zt9?*~ZXjdmLzbJ15ir&_NaIfgWYhgDm7#%ww{H+N1dMKkIGZ#5}QkX(B z$c6*`ow>dP!56-OWr4RgZAQ+2ielPG=PFF3#6Zc($rqV;e83f;GyH+?#R9Mv_=>is zjT9_p)JVGOV0BC^c!?(|&}h>^rW{+NTy&R?w~@@#%M_dS{XufS71_RgCmypKUkw#? zRnzN%^U%JCG8In-W!~`a2XkqOR??IVz_sJ7jw=HVrAUgp2hR>_kKDTc^B%UyyZ=|< zOrwLLubWk{Tmi*@3m27_e_??5h8^#JyWQ+>Vzu+d%amq1mhy6UGXk%19jsR!&TqDU z+S>Bw%b3&xco&-}>$D3L6w*hjahc6($e-DKs{y<jGptyu3r9!Jzgly=GnQ0>mjslsv0LQ;D_ zJ>uFPlA)JCKaBrf=hvcj^8LbiCjnfL2ix2$D)^a49Z00g^xWkKlHhANHhB-#!IcKH z5s6bVlFy!^(mvRIUB*mxaOt(MBFTjw(o>GKd3&Pocp^Mm3<0nU&gS`lGGAmb5zqmK z)-j)3&#vMsC}}mS(-J|yo6nOy3iQOC%Q!|&!3HG0Lkyfc0Hi}tyfn3}<4O?dH~|b- zdWsGXW8%BBY*CGSo_@lQk{H)lGFf|Wq)`&zYR|RGTjRhb%EfxXz`{2W(g3{rKRSSo za0*F~=E59=bEoU9IXE-E*H>0x_FmIq>Za4RS=lUZIbejf4F|fce9NiH>k5}#57h!V zCMtBa-hyOBFZy5g!sOw2s)L(&g$|>{7}xe?eHLY4m9T)^>(vE1>%7olAO!WIn4R=3 zPk`u^3Lc$f0FfOGq}sg<#l^)zPy7uHYzs##k@@HCWDwgs{bf81u}<1(%&wdR&)KT} z?v*HoF~u{~2jH4lAE7_w_46qnd`~-=+}|%8t92ezl(CzMzp?X%D)qO}wIL`SLC?~j zg6hVOYGiJje;r7Y7b`TBDI$9wn+8pU_*aOOhBuuqDWx!y-*-OMy}<<+CeJ9~{S$qd zCz>Yw20*1-7F3bv?)H29WM$r;jN~-H$IV6+AVwg;&2$97pTA?;{aZSy|}KbJ)&ht^p?FHA-rmLlTlD)h;x(hWa0dow4_U>H2Bc2`GdA zI~0e;r7;u0xV+OdW&9K2aaKd6OibYUEk`DXUv;Ew;B^G_7^i+NzGzY~*SYksX%{OA zeF_h1ZK%6^bJ-0zs}B#?%MySJtR>k#o|Arnc=lZ z64KH_;)A*+&;9cB?vL^>VfK%t6!K3jDqr3ER;Z-#i}$mHK0iMK%%mli1IxaO#i}yL zj%AykoWlUjsv!dU_vK7dIRB3VzvUE~3qRzM!7Y&|u@5;}pI`}t#SY%ISl*tuA_eEy zv4m}foBk8#v{gmZOj@+CT0kD&zd|ch*QrU+hg*bA<@|aV-x?M(BEu3eFO&kr2*Pcn zfQ7%yEC3oIK{W-7-aj&M@C#3JCe?RG*?I{P-aF%;<=)fg8G4>39pAHZcC;fW_mM|| z3Q8K&SjZ25Da`^B%1{HZ`*@>c7=%I__%0A3(XpKY!>F#GH@a zC}})WD9U^8TnkaYlE>QSD-d~uq{+GG*z{}EZj3kfw+bfcNzDRO|ce zFW&hzpy4tleWjj4b(y3a_*`9kOE^~-cJ^|BZ@juANX`D-GK9N{Owqv9cEO?HzMDFS zozCrR2q>G)GnyKkfLpm^G{z{L<;Kj}|M91cjEo|!HP@5>UgF6^!b}++e7A#1+{wHr zgYNo8N=KX4W@;ETNVUWC(c+e>n&(Vw2ituf`~N;~6bfy8I*`0VT3)e#t5usnBEqtl z8OsL~hlHCgC7cO`O8sv8G<(Gw`^D*#e9(o*eE+;J)G7Gpi;t#Xe6j{aPQ_dMa)HYp zpf+i3+ncc|u0&;cKPS)WYP=4U=fdz5TS@)4?0*W49AfU2UE!4rhSeY2ybU$Xg>}JI zwR{+Fx!*96uKXqWS5p-4fexi`_0L1oMs^nb?A18z(U@+2(Ju)VY22RO%Q?r)40*k4 zG;e~VTM5Zhxo(U*hFNwkn6(J2)f>FOZCjJ&&>eqp9(Eq>f0wsI?HFVa< zWw8S-1!J6$2sjRg_nJ6ClrX@D?1qSPKTs~M9sJf6$L*2hb2V`!&mTR(vIHCCgG}S zp<54=q$r7nK~mTJ{c&?PV%)oo<@M=rWHsfUPR4&;MCUwWZj=0Qe7|OV|AAF z_q!>1!_#8k?FHa8(jba`fNJRC_L{mBn0&j|UmXmOljJ2K$8^!}=Yd22=U$S;0N(pv zuy%JQxh;(pGw{a0ZdfFIgTNY!yWS&&yTJL7D%KeR)vO`$ow48Z1p zN@$>{l$_iaxx2#F`HgkefOgp7ql)5T{|@yGTScIAtjx_OC5pi8L}gcwR!W?4)7a~Z z;hd{YA!pYA?`<{6c=z-h>9C$K;4SINt|`vO&RnI>V>p6(;fI zef$P5`)@4l&$HDMl2TCE0pd*B(tpz$rK?DCEMd)vfHDUjJzu5Vc2Le`N3ZdFt`##; zJ7)MTGyF|UOLeY?RGoDHe6db{#8~joC|M`V$2NB9D}A$zLi<_DO`|9`jl^*E$hq;I z`PC<9?os8pb{wzy?9Xjb{@dCLF&FuvqA+ADPXi^w_gFSk<6bgv?&3F5Wx%6lfV$RQ zZtcsTxx38!SJlUAA^cjD+rf$XiCNF%0BHT^Ri)9H*92^l1D8JaztY1sq2yFzQv+Pa zizU1wDXhL9lYEbk24C%+m2<0K3E8RgEwI9qYNQyoX#cqmx|ywfhyRjnK(xjJ{;&{# zL(erobcuZFa~2;1Bw(mRW%}0`x^(n?9%ia#OXHwH%7`Xl z4w^A@@lQ2VG>`Mm^;?}F7TV5kfF{16wAA;t^U2@ZcA4gC>9P-c(n*_cWj?x0|9m9? z!5CoE#C^6EFdGUOqbtW894!SOuYM9OV;UyEeCnX|JFWL_9;7ze(D}GzUe=TBzM&8@ zr9cWBE9bf{c>f>v64*}1M+B9X>J6(KrltydeNh(_NuAu%`PMT_Vd#~D*b;P834}Npr~f^6Fh8!TsY#Xp zB#8x47o{;G$upYJ5Oq;G$t;N`h8io|cB9vHCo0FyAoxV3n~~H0(l}{pWnDgPlZjn` zlE(k>YrHt|<8uUgi&leJXYsM+gLd$}%yW;y5H0_dg8DuIpm3lr%(ifYEb3V}s&IGJ z{&27(q|}de;GaSr+xehQ{2!~m!4YO=X1akh6U00YV!A_j+OzsHW~m8f-Y0D5z*XFL zV&@6C0@b8QodQL-j6DU@L*AIeU*jqF53BI|RpXgUs@2aQ|Ax)}JVG;M5OMM`x!)JR zZ!xBn@1fH$Q19eB6(_N0GARbMnW!Y$=9lf(>OzINa$H*DV3K9lQKe&CE{lUQp#9HZ z>IH1=p%n?B0=o*be?^^BB>H|^{?sQUEI}qOjWT?{ZdbG7f#5JXQhY^g!ST4|>Gxi4 zt^6E6vZHL@Ht;R9d^^`udSlG!#<5p@u6~2%yeAKM^X{jw;0c{q|B z-*@*^21JL2TfQL1j7W*ocmpO*^#Z*+Pu_ME?s#+a>R}ORDhqMnwnm$-)JVjsm9Zk5hT{q>*Ac8} z2=?zkjyAYe(McB&sk@odz&{^Vk2bN$Zo2tSTH}*^7#w@|Mk!i zPMz~6KIHHhyO>dsl)%GjbViAPC@9l0@0o^wO>KDR?cV89; zO@BCg=ElJ}e1nbspJ#RQBO#JU27EP- z06xcq#tmZwtC;l879Yw-67dg=IR@C%sQ+pBDslSDE%;_n*ne^BjJH`{`Y5DlYkKZgY8*K_J?ITCd{Be!wjCq8X z>W8(NWVc$Ol!O@GpwVWQzQV`1-nxqDQ_V$x#ax{&lAQDwUpDb zi>k7cWC$sD^+W!@4gqaK_t3WjL0R|gP%D#9$%tY4Q5hq4FIA#1SLB^T^{IE~UQi!J z!Av?Ct}Ycxr@05*IsGLjy;W_V>?`#^vhGZhfm@5FO;mS3_d|QSjfY zu7*%nQRz1asgwrOneHD{f@j_q_|?yRtbFw*ZO#5olL_ZdEST%R`uW{-=Li>k;Tv@Q z_I2H2D&wLw$lF}c6Kj8u58@T>Z*s()IHKlgw31BvS7h8H_@)b7QeXZ}EjawNB| z-kOgfi}Pe?W1oX)_}J8Q2LX|E=*t_Y@}t{#zc(d!Rlcc4b!nB zL*oB9gbet`YzKZCOT}C+$5%_eyElM;*{i-MC`SGK0lFzUhUxE~q{nSyP|`%Dso(L_ zwzJ>zjbXyJ0+@rf>yyT?vn-xNikyFHx+5C5+C`qFJW!=Rb2vEU1|#bZnEKhzYl1y* z#Eyc+=5!APlfoB%t;MP2)y(G?wZyZY8i8fw z%lk*U8qZ<3_P37pQx()FN2@h;Up9x45EdtiJ%|T3u+Rlhj z(A~L$RNt}PtKNc0-ajRuAkXf%TrnFWZQq)(Uk^Ju(cesV-krmwuafn-l!u81pr7xGB>eQtqt{gY>+y%%!!8=+-43ybi<6J)Q;yPx{we-Psq3pqGR@Ka zff(*LHwojj4!v@Hm&~sD$Z2h+Un6#{0`qi?#OA7wdy$`lIsU7qK>gEHcc0mK&Y6Pp zA6vj-9RzI%ycPOG5zv>ZhlYp$UwhZV)>PB9LjY+CNL4|Kq9O>`1(6UdO$jO;As`|( z6zL^2v4Wr?q9{cW0YeBUodqrC55_}*Msa#Buq_w3Hx zGjq?JbCyGfugE*S07`mDz8SIzV3!vSMsrXv8*cbrDK5jj! zu$i{%Q^R>jm%S%i^HcV(moB*cccAcck)VHkfnmqc{>r_NAIyw#&_viGrj_jZIE{O{ zEMWF{QHtrP=xvBcpSR8A9oDuo3inDqg?k)6>Eq-6UMb{pNROvs53r26_Mz`}R|-K_ zEmViZ{)s|zC^qg_36yA}{lh*g80y;M_PgyO+;?9OZ4rt6QFA|kPXNMlSq)F;e#oT9 zOK#)e&*6eXF6WQ;whv0o`R8 z1vn`l8x*?@8INqsZCYpe5R2VLjB&ZnA7gm2jmM_d@Ra?D$F|1V6;jah5jsreraN8& zZUENy#p{`|U>ub?hs_Gc;cQP@ra)-fFe9!yXlE!e8&t`RXp zxshqTBrhPyB^T^)@#K7dxW@*Ov3DY#b~^9-sgsRGDtS3(FeA-h@| zL=h`8@>b#~UuFTFk$7Gho9811fc(@Y)pGOA-$kafX%TC$f2xodRY>)T9KCgbhz8on zX6y`gAV*Iat+a_z0_0-5J=FJ`Q`nvl2(1Kkj{)J9PJgFS?^s~zXM1(f>Xll>8s#K| z;Yy~w7(tSG4BjJl!jtpRJxfnBYWom9BS~tdKk!rD!`s_o196_3E02r{aO`sELBH%T zx2a7}8I5f{T<|j7*4`*zPrw+QU=v&%CihiZFZ%W5@uP0r-%F%kB>1ZZDn^VyVeGK4 z7$@G;ci*y7-sW$>-QC9;crjHjAj3HQJ6(D>k@-W0HP@&EUnurbj_7-*H0opcJX*yb zh1e6AIEd%6YW>|4WIwm%wVO{&?erUXiCnF)-{)ixY~tw3d4R3$njg zJ#t`r>Q)`|iQh8Agm255sNE`h)#In$!1SEAZqI^bL(~WMb#E~GRvyLufRh&>7&8%& zalzv{`#*$$v6=V2y&#^OHBIX3MXi)W-0gM%pdTw;^!Ep?EPO!R5`>caIn+ z)~uZOr6bP6udgto7s|7--xZcQ3Sr!={=vD)+r=sQ@4#$VbP1;1g@i0b>?)NPMly8P zSvQ*ssP!h0jz`4&{PtmxGCpqWnOe3&8)NJ5i|ug0Pt9HyKRBMTGj*l|+s>)^SX@6y z_I^1yK>2W2O4&o!O_~QuuM_PXx2I(>`@wDBUOB0kDVYkq)!F?hdOlB?5YV_WG-_Lv z-iM`w$#G9sn!DSZfJ7^QhOudHNTl=cZZZcP7?c2#-{io6>9YFtjUm#pue)WXAX&qY*#{5&Z;=tyB@P> z+GS;STZL!afW({WtiHj^jlun?4ilZ8`u%N~lgTPu#Eq90zp{BoW_d$r#MOn|A$fua zQ(kHrDouL-cKVcSV=||y*si=pEVUt!#j*Ggtp^tcBrgoe+MEmc(sW>uZSJwe^2anX zMrJux%D7mh+lO@C)ZgVzeR%hQ?4WsQj{NoNd7Z7q8Dv5fiSkI?V7_GnKsdLvDe~#n zzh(>qeF2Y*Wc{2hshi0zA#s7rmVIgBvmbMV{kqcah}zWT;(SSjQYq6tGWcEarD%H`*>`k|qPd6#0E=X1NYdptpg+p^XO2QfzPnbXytVDbY7F=E;;X|Lye zfrGSV_#6GI1N-751hnE>Kt$>(6TVwEt@T1&lN=-3;3bm1EaHIx{CF1moSl(W&KVjt z6>4x7G8~hkBzqt8hKTn&<@V+8LgZKplCchqo^JPS^uYW?Tl%WY6+?qUT+sz|5k1-= ze%YFH<@xAKt(~rib55#!l%iZw)8-^5$Lu=OPiF1%>OGX%s2KP;DUwQH=x^M(5%Vd; zod54C$8D9ft@P^X!=b0W#};-oI}Iw+W;r#LsMFmYBio?nu>!Gga%$~BDxphs89Ive zpWpPf-cTDwpnE>XPamSUpUqx_47(J<97zpD39X!!z2)_-`DV)cQ>Aadpoijw?q8eR z(Jvp`8B9_fM6e<~k(~>$A)Su1Gh>e)-Vel_jy2>fT>ISWT8*JlODI2+DnL`7u8Po# zks7KpAxf*Tr`xhRog$}~*X_RW-U&N+HgW@#tUGdj z@X8woUmY|?kgky?Ov_is)MkIWa905GDu$nX*XR3Dd??CJvQS-;a9h2uP<~_N+Ok@w zq~dm?RJK?;C7OUY!Dk=v8(6xt*(f^Z%VWI*q0RFDp>G=yDpAcHt-Zc=(~mdA$MkP& z%%4dcJ;NBb?#V5~yh8|R0BA0Zee=8t>Yyni>7=eN4r31Kzmd;7r^r28pV4FlUtt5| z8YFGYFE+6gb%@NQxn{!{bKUcTQ?T2NS427s-_bEv_4Sa(nmie{5n~dcEEuN*WsATi zqz-C`_~aW3852Ed8Qvx9(ZKiY)AeY{G0@y7J-Gsz)o<3{-D0LBvix)DIjx8sJp4eW zSo3yOh~iM^26nGvMzeYP(4dShMG6Gy^75BY7e?0V?0XeC$tB$=9D`8D^Lx-ve1hV;# z$+5$hn_C73h-@y4N*ZZDi$EV#yax>y8N|6`?fLq(qY^>`FI>8wul7PrB2?I_`Oi-)IHqNdo%fq!2 zJ?NcO{un`aK(;b<>~st6ZYZ~^$IAE1MmzYs_W)M<{dq5 zfwotJ+Y3)0IGGLOI1CT2BGw%jYHnEJ8?~#JMlJqyCoVSmWo4{oPeylstcdJeC>%^- zFkaTC<2RQ_lpSOAQ=T2mxyT3Ov)Cicj}e(4Nrgm=A@c_oQ_LLvz`O~SE~FFZ^PddY z@o9@?i$1WOX4a z`t?N_!aTubg$nsYF*dm9*+xU|Y|NVF5$YF7HmkmWF#EaFG|_r;-@uGFwuE(26LtDS zBe(7lvs(xn1Fl$UixbD?9rxlQ9Rw+I)tXEjPqgSybWtr**$)?vtvJ4g$_@>ul}iX3L*`Yto=p>* z-z{Q+*aR05K=vH=3H|wDTg!pZ8<|2@${8n%ej1N+raBS`NwOyM)_5{4%N~=kOcMM$O5;Y>&NziU7MD_;9t^j)6`4#A>(5JjPh= z^g{_Q0x&w~^-^bFx(+IoZ2pfzEmQ(LBFecDm7u|DeD@%ARLL7BzFL~gh9XpZGep0**q zsH&Lp@Pzrl2wRa6i~cYOSmp4+_3n>n6M+z>VCtF&p_i5|Xfv0?J4R!XD6Gu{PnCO& z${e7})~xB?{bOcOXK?YuZ%%n?vFp{1Z5-oB5BGM>1jxB0RJOm_x^ zkKBQwSC<(T8_QR83$8Ts{ZlsK`+bM4NWh%>C6qH>g(((mUQaO-WNDxXI0yYK%>e`y z!E^jvHG#$J5`90Fdrk#-tNy-;@j=SfnyRAp=@H|1xTs z!!X3eEOU?6ipORD#g8WKQcw(9r4;_{PK?2Df^?SXm=|A-mgr}zr6B`x_9)y?e2Ej* zG-fu9^KMjKLTIqT#TozYx&XcgOzfm6x9y{;2_##X2CgJfNazP~T-7AK6B+ZP@N^zg zF4^;1mXWE%AAbbCN4TP@N{46hN#Mkd*-#zZQs<)#y~X;zPPC+QC%T?G8U6T28BCBF z@PV}GeHB@}N?!&n@}*Z8DKc4jgo+2Z`sBg^5CU+%z*tja zujK`md^fIeA_1EYUA{G($&J*h9nak^0pg&K{&J%AvPl3@Vs(cYDCH?uG?Eec+&323 z8}0y7>R#^=keb0;xMv#(soCsr3){~=d#NC9^QsE^v$_H@uoR_2og}PbbE2j9 z#V;qsDFb5)DATv@o!@y18AZ#I`^E}gZUb2azI#5Umo++YgoPe-^XgBu;Wx{74A-cW zoGSi>>9|cIT}GTY`dpI8AjOalFu;C8a+A;yu_P9Gm}IIv??A|^A8hvAvAa5ON76mA51DaF@g&Z4<>6OXQ1OdZRLoat6w>wi-B z#G~}TVNnhQI{b-aP5k*$lW{I;_FbF%=LfTg8LEqrbHQn%WLcU6iBc)RiH>ZVFw+4r>Y;%SQL!_V5k`ifMT|D)2Qt)#rcUV+#j*T<3> z{eG-zK+Vt8QMuPP10Vh#Et{VR^ak(t*O3`N8kd}$TnPxYg_yqZvCKAHE#-M&1_ zCx_HfXSit{CP~a&Rp~|bH&0A**9o&vUCu8B(=8h5sk>80atky{#rMq!+>!646a?=? zw>Q0&GDb&DS6NabsxTi)C>1T*hc)is62?!mG_J2G#Jr^$J$YQeK$4 zfQuN*4853uup0>Nc1tHr;ji=iD&KAlR%Ulf(^%7c3+Uas_%wXslvjcu%M4&+PCf7F zqXKj*g5uQII=`ycBF=-8WLaLU`oX0r{Sp@axp!8@ZrCbshCW!)(9FG_t*3>Q!iK)S zxw?jRZoG`Mf;-ZUqv^28z)@!*0K}B1oD2GT9C0mXtm+lk+8O0`&L%rebim4{_oJ5t zYSawFGV-`TJ=pVd5@=f9X&!I-F>OTmlR&OoEVewSPfq!#A#70R&8^HLWD(L%gXg zk3LJ9e0%z$@Ua^Z!FFOr1l!__iYBdU*1n*X;R$QXb=V*|<8LGkU+zBWq8!w#AvmakZ5Cpa zad;hcX*L-*U12^~kcLM%_&AnHG65!h}XgdIC}`uIRpawI5TYdGtDg; z!7Cv}Tnk<5ygH{L*j7>kL)sfm5A%l#vi6$?W1GFaeR}%H?q=sgf`698jMS$z$bj7M zgU(xd=4-D@@yR85pLX^|bztZDUQ)muRpNiN=~kjA7?|2Le3!0P?-fIdu3->?F4pmi z5nQi=?8VloCqpooeC50s_x?5Qa8gf@ET~4vb=@nyZp;7dAoB(~4Ha&lOaOv)qP|lXa-S2@gZ&;)ROaXfYgvf?AWrj@Q z53NLd(yLK{Z#iU}>9tqyH5B^hYaJ{pvG&OJe=VYX5D?qqBFIg;TcZvRiGkeZ(gW)= zoBky{j<4mFCiD(Fa^1(89x;5F9?N?Wu*Gi_e;+wp*1IYzgcz}7%nT4^1+{6F?VSAq z{XKZwqS_wopP@WJ%{UHJy!@QWm%NiX$;aT<^)vj@3erNXXadd~MlZs)CYPUa9CLAH z->qoY9ZgEf0=X_BDp<8_F_E~-p#$M#oYyM7@_*QoQY(Si`okMRUOfCe0o0^(d7>1^ zWHgrZsy0c%)IOoFT7x;_*5JL`|oT)Rob2UHn-zCbEbiw%acofo0@Pcw>Ja*#?9G{!6F2Sb- zIHIjJ!hp`@r+K2IiN&><@!0a?ZZ?-ma|lJ|*C?=r8PtqRC+@&arRqzoo-q9OVu|QYTC?8f08 z6;%Y|yY|nXq38p)H{{UV=RFL-?p$Ul?K=NQ$~C6-IU)`&>; zoT}jGHkusUrXo}^Dsc(z!1(~-2y1W1(>4{z0GuMc&GC$RVFp}Bif?~@{7>EM3{;0z z&;I9_&lyG>4Gyf0LUvEQ)j2XrmamFTQqa5fZGl(qas9T+y2TrasPo^9U_b8|@XW~_ z{ppIf_ZvgHTvBK{BBA6H|LSgwE_4`4#jZZg9idIL5?|{tCBBxQ*NOo7IXMH)d|TqP zQ;!yW8C5?g6Uw0A($ZC+&jK#EEmG73{{q;IoCz#MqS8Wq0}SXcRGF_}-yWpnpotplg2h$E-wKr98P5eReqWfSBQ9dMEKlJi8^T*Y! zXx21iI3h}<-72IkR0UD1R?rK*j-WW0sern1 z)o$@0nw6u$(OK2L+lE-}=9=Y8QKXK_u9qG6KLeUQseAT;YjU?MaA=n-jHOnu$=)Q4 zG}sw5c3W-T!7$p;6IGK^Gh?~cYmgQmDVajzin{Ge!_upcVy5rWe?jWrCvf+$Ytg}B zQIblSBk<+JVa1|+916`=cAy3+3uRfUmLg>b2uN8XDy9Q)y4If`>>B0JPID?{4}FC8 zB?Wl8j=(CddMuw8q>;y19YEmV#QPEXH5DbT0X?-BRB3>M)#T;K-eJDz6^K4ngZey< zAE*FDM=iFML5nAuGcH=VNecKl8H|tnu z1UYCG5)6T8zB%QqR$xEP)~XKs?<) zYFgD9DUTOvTB86m16BLmdu@tsUEC{qFZ(?C%* zor1sAWtFp8sX1 zN~Z;6N*5M{y}DuEC;Og_AsY_o zy#sVj1ycu7kga90Wv#P_KlEfKRynAcb+d2pNoQ z2=-=drXkib@f zxHd?fcJ6LSDfp$W@UH?pJh1ro-3>pC733LKUaVjsp+ub$2Yn1;aw)fcv4K?N1yo+39c*HOXqDP%u0gt=b~~8BEbj6sf$(CHQX@b_C&ZcLKNZ<5XE5ub`tXcEOUp9%;|o{uIK_Yt6$ z2CGyq&NA+x6d#@dbwD=IEAVtH3J&kYGFK=FuDln05*w`Go|sI+SaJ8D9PN>d*1scE zkCS|2_eQs15w5M|MPdYHgG15$WN{X`gp5QHG9NX(o%43I_eA489+lh=PRMW10o6cO zU;!<+oQFWWq3drzdh3PCz|z}IO!n-bvFT00Ry8+gTH7G)Cs8m*I0fe`OYW^g8-U!| z;x13RDcXL_4B5#h;szpm6e)9VU=7lD2ypJ^9Lw#!{0}UuZ)Eb6pzv- z%PDv)vY-YatOjR)+LD)@yX#(U?Y4!po+o==u=FZmzk9p=2q;{i$HR3%NM-Nx;awER zt$B)rnGY;Nxb@Ty%rBxwYAaE*tfbyF@)m`;U#X6`Rq_@&#cebcvWgy{KQlMkMnQkI znN*y8(b0e8Ieak~cpIZI(m<#8#sZWjxi0fo)jPkzj?{t9eY(fk8$i4T?LEOGmjc{z z!j>|HcUPC9MssTK6T=h+7w8o=>=>aY99DlHkqM;_bDgs{8 zU9X?UPOE9XZ>HefeaMPr+!hqE0(Pt5m!RaV7ifh}R={FP>?S8CaPGwmnLxJ~o4bI2 za5%?>7vX)*AAzqe++pE*1fFUshOfGSu0(n3U9}%;IrrPylCjD;qvGh9X_##~s(~%n z!v~=!bcLB_GM17aw!p_&6FH&l4~9~>XP0v-SvrbA?~3?=SrdEums22fG{;`W*U0YE zy3A8fuHo*;*Y||?J^|~u^=08>DQKne{H9%!h`KGWmhSr~C<~!}E0kSM790j^*6O|w zHzYs31^-=H@_GBF7KJ#NvE+s6`{3GqSEG5m$63pE<-%cNw^F9uKeVL4CwVSHzjyP) z&86?4dRs;C%&rH&3Y6k5%BfWQ{lI*xQN5iF#=!P@xc+Zm;FoE6Ej+LD`HsKJzhUCP z(Y?waiT>|@jDWCR{Y&ML@xR31zdO7?1t7(@;$+gFHhu+p@Bp4rjM@9E{dXtdK*fd1 z0sE~dRuok6TFcP|i+(duOIPaG{QT|d60h)7K}-5iT=ct13q`Ot?`q@}MgGvrzeiC6 zBC+?k?%(*EqxjXDdNP=ik#l0n0{@QxH^&y&2ZsCY#HY9aJz5zfaC`wJf=2&QCJOd~ z`-0(K%5yva-=n>`8L)l2o$;Ih5EZ|VMCAY&Zq3)-yZ(E$@74mI?y>i)JAd;4zux_C zIsUgCzlG<2+VMZ__!klX2UhPh=O#Cij+LlJemFvUuwCx`Nb!U6d!ygNe`*~+9h7`+b37yC+2UqU zW>%r*lXM5EFB7pfRS#SxvMu-EREm3f_fK>G)RaRrrHjT+yD-vwgNk*fy%Hn$a>BaT%m8;ea!GcV4Z^LP*SJGjSJitjXp zhQi*^+KsO7;m@F4F^zs%m;B@grdn>}u@i{2J*woVIa>}}Slu%-spyHRv1TJ&Xj)oZXl7DT;+_ zd|O4{#J>9aO+5&IRwOEFj{mb^^%YG5bvSJ28LwKE^rm`((dm=iSuL5gXbsV+gt6Q& zM!pYNL$5`KlL~L1Fe+(|xiY~T72H{1VkeY=>1nxb;L=$Gd4KMDZDQl9&&RWbr>uKfDm$6tgf2bIQS-@7<^j}0KUg(f86Wh$@&w7!TfFU^veTd5mj ze)JV2s~dG*F=P8*QaCqkmVEpG!PrdbC0F!8gxvFIo3IMihSG1y4f}DXO3Xd**NjrrR2_@3K#exDtU-e*gn#kqN$k6Zn=Dr9F(=~b4(qn+L5a(e4p{BwV< zi&7GNcyhxjEJ`dp6tkjSQ@vQ4-V!a9uiq}tfcE1(v% z=Db~ogoK39nWA>JJ+nAT)S%M(^&+M@`LA1BTemnVRVDZE_ng$$)?P>((fjz-qeAE5 zN%I?*C)BUweVf00A$}M5VQKDwBauj#oSld0{k7y7)jjp&ht)k54Z1$z4V+Nrr*a`o^8pDfja{QPaz{LXv1xdsJFLHMrbASETt&ziY~yInZ^ zd#A6$B9@o@{DNZn3dB~+uUo%C$x6*P*vF@BjktuQw49CkS#58(@IZU8{oRnYnyZgX zU|{ZAsaR+%f3A#dtU#<_>?%uN7nB=PQV#s&7Ub#@Kh<^@1c Date: Tue, 24 May 2022 21:43:38 +0200 Subject: [PATCH 120/308] fix: prefer stricter modpack formats during import Flame modpacks use "manifest.json" as their only characteristic for identification. Some modpacks might have other files called "manifest.json", which is why we should prefer modpack formats that have a stricter structure. --- launcher/InstanceImportTask.cpp | 52 +++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 4bad72513..514cbcc53 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -135,18 +135,20 @@ void InstanceImportTask::processZipPack() return; } - QStringList blacklist = {"instance.cfg", "manifest.json"}; - QString mmcFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg"); - bool technicFound = QuaZipDir(m_packZip.get()).exists("/bin/modpack.jar") || QuaZipDir(m_packZip.get()).exists("/bin/version.json"); - QString flameFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json"); - QString modrinthFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "modrinth.index.json"); + QuaZipDir packZipDir(m_packZip.get()); + + // https://docs.modrinth.com/docs/modpacks/format_definition/#storage + bool modrinthFound = packZipDir.exists("/modrinth.index.json"); + bool technicFound = packZipDir.exists("/bin/modpack.jar") || packZipDir.exists("/bin/version.json"); QString root; - if(!mmcFound.isNull()) + + // NOTE: Prioritize modpack platforms that aren't searched for recursively. + // Especially Flame has a very common filename for its manifest, which may appear inside overrides for example + if(modrinthFound) { - // process as MultiMC instance/pack - qDebug() << "MultiMC:" << mmcFound; - root = mmcFound; - m_modpackType = ModpackType::MultiMC; + // process as Modrinth pack + qDebug() << "Modrinth:" << modrinthFound; + m_modpackType = ModpackType::Modrinth; } else if (technicFound) { @@ -156,19 +158,25 @@ void InstanceImportTask::processZipPack() extractDir.cd(".minecraft"); m_modpackType = ModpackType::Technic; } - else if(!flameFound.isNull()) + else { - // process as Flame pack - qDebug() << "Flame:" << flameFound; - root = flameFound; - m_modpackType = ModpackType::Flame; - } - else if(!modrinthFound.isNull()) - { - // process as Modrinth pack - qDebug() << "Modrinth:" << modrinthFound; - root = modrinthFound; - m_modpackType = ModpackType::Modrinth; + QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg"); + QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json"); + + if (!mmcRoot.isEmpty()) + { + // process as MultiMC instance/pack + qDebug() << "MultiMC:" << mmcRoot; + root = mmcRoot; + m_modpackType = ModpackType::MultiMC; + } + else if(!flameRoot.isEmpty()) + { + // process as Flame pack + qDebug() << "Flame:" << flameRoot; + root = flameRoot; + m_modpackType = ModpackType::Flame; + } } if(m_modpackType == ModpackType::Unknown) { From 5d3bef32caad17e374559e4718ce73ae2fadbc34 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 27 May 2022 09:15:32 -0300 Subject: [PATCH 121/308] fix: use absolute path when installing icons --- launcher/icons/IconList.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp index c269d10a2..0ddfae556 100644 --- a/launcher/icons/IconList.cpp +++ b/launcher/icons/IconList.cpp @@ -273,7 +273,7 @@ void IconList::installIcons(const QStringList &iconFiles) QFileInfo fileinfo(file); if (!fileinfo.isReadable() || !fileinfo.isFile()) continue; - QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName()); + QString target = FS::PathCombine(getDirectory(), fileinfo.fileName()); QString suffix = fileinfo.suffix(); if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif") @@ -290,7 +290,7 @@ void IconList::installIcon(const QString &file, const QString &name) if(!fileinfo.isReadable() || !fileinfo.isFile()) return; - QString target = FS::PathCombine(m_dir.dirName(), name); + QString target = FS::PathCombine(getDirectory(), name); QFile::copy(file, target); } From 6fb5bb6a5e73d8e967d9bcc142683cdd4ff080ff Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Fri, 27 May 2022 14:50:06 +0200 Subject: [PATCH 122/308] fix: fix mnemonics in APIPage --- launcher/ui/pages/global/APIPage.ui | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index cf15065bc..5c9273916 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -36,13 +36,16 @@ - Pastebin Service + &Pastebin Service - Paste Service Type + Paste Service &Type + + + pasteTypeComboBox @@ -52,7 +55,10 @@ - Base URL + Base &URL + + + baseURLEntry From 283e50e6706074d6a3203e1a4c7b4eede5ffedda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20=C3=87al=C4=B1=C5=9Fkan?= Date: Fri, 27 May 2022 22:23:33 +0300 Subject: [PATCH 123/308] nix: use nixpkgs's quazip --- flake.nix | 5 ++--- packages/nix/polymc/default.nix | 15 +++++++-------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/flake.nix b/flake.nix index e59d6be80..b1e810571 100644 --- a/flake.nix +++ b/flake.nix @@ -5,10 +5,9 @@ nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; flake-compat = { url = "github:edolstra/flake-compat"; flake = false; }; libnbtplusplus = { url = "github:multimc/libnbtplusplus"; flake = false; }; - quazip = { url = "github:stachenov/quazip"; flake = false; }; }; - outputs = { self, nixpkgs, libnbtplusplus, quazip, ... }: + outputs = { self, nixpkgs, libnbtplusplus, ... }: let # Generate a user-friendly version number. version = builtins.substring 0 8 self.lastModifiedDate; @@ -23,7 +22,7 @@ pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system}); in { - packages = forAllSystems (system: { polymc = pkgs.${system}.libsForQt5.callPackage ./packages/nix/polymc { inherit version self quazip libnbtplusplus; }; }); + packages = forAllSystems (system: { polymc = pkgs.${system}.libsForQt5.callPackage ./packages/nix/polymc { inherit version self libnbtplusplus; }; }); defaultPackage = forAllSystems (system: self.packages.${system}.polymc); apps = forAllSystems (system: { polymc = { type = "app"; program = "${self.defaultPackage.${system}}/bin/polymc"; }; }); diff --git a/packages/nix/polymc/default.nix b/packages/nix/polymc/default.nix index e352209a1..d09fe3c7f 100644 --- a/packages/nix/polymc/default.nix +++ b/packages/nix/polymc/default.nix @@ -11,6 +11,7 @@ , xorg , libpulseaudio , qtbase +, quazip , libGL , msaClientID ? "" @@ -18,7 +19,6 @@ , self , version , libnbtplusplus -, quazip }: let @@ -43,8 +43,8 @@ mkDerivation rec { src = lib.cleanSource self; - nativeBuildInputs = [ cmake ninja file makeWrapper ]; - buildInputs = [ qtbase jdk zlib ]; + nativeBuildInputs = [ cmake ninja jdk file makeWrapper ]; + buildInputs = [ qtbase quazip zlib ]; dontWrapQtApps = true; @@ -55,12 +55,11 @@ mkDerivation rec { ''; postUnpack = '' - # Copy submodules inputs - rm -rf source/libraries/{libnbtplusplus,quazip} - mkdir source/libraries/{libnbtplusplus,quazip} + # Copy libnbtplusplus + rm -rf source/libraries/libnbtplusplus + mkdir source/libraries/libnbtplusplus cp -a ${libnbtplusplus}/* source/libraries/libnbtplusplus - cp -a ${quazip}/* source/libraries/quazip - chmod a+r+w source/libraries/{libnbtplusplus,quazip}/* + chmod a+r+w source/libraries/libnbtplusplus/* ''; cmakeFlags = [ From bfd9bd43c935a01d7e7b8b078f479970bb81280b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20=C3=87al=C4=B1=C5=9Fkan?= Date: Fri, 27 May 2022 22:25:25 +0300 Subject: [PATCH 124/308] flake.lock: update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Updated input 'flake-compat': 'github:edolstra/flake-compat/64a525ee38886ab9028e6f61790de0832aa3ef03' (2022-03-25) → 'github:edolstra/flake-compat/b4a34015c698c7793d592d66adbab377907a2be8' (2022-04-19) • Updated input 'nixpkgs': 'github:nixos/nixpkgs/30d3d79b7d3607d56546dd2a6b49e156ba0ec634' (2022-03-25) → 'github:nixos/nixpkgs/41cc1d5d9584103be4108c1815c350e07c807036' (2022-05-23) • Removed input 'quazip' --- flake.lock | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/flake.lock b/flake.lock index e3c490fdb..ccdd51da6 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1648199409, - "narHash": "sha256-JwPKdC2PoVBkG6E+eWw3j6BMR6sL3COpYWfif7RVb8Y=", + "lastModified": 1650374568, + "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", "owner": "edolstra", "repo": "flake-compat", - "rev": "64a525ee38886ab9028e6f61790de0832aa3ef03", + "rev": "b4a34015c698c7793d592d66adbab377907a2be8", "type": "github" }, "original": { @@ -34,11 +34,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1648219316, - "narHash": "sha256-Ctij+dOi0ZZIfX5eMhgwugfvB+WZSrvVNAyAuANOsnQ=", + "lastModified": 1653326962, + "narHash": "sha256-W8feCYqKTsMre4nAEpv5Kx1PVFC+hao/LwqtB2Wci/8=", "owner": "nixos", "repo": "nixpkgs", - "rev": "30d3d79b7d3607d56546dd2a6b49e156ba0ec634", + "rev": "41cc1d5d9584103be4108c1815c350e07c807036", "type": "github" }, "original": { @@ -48,28 +48,11 @@ "type": "github" } }, - "quazip": { - "flake": false, - "locked": { - "lastModified": 1643049383, - "narHash": "sha256-LcJY6yd6GyeL7X5MP4L94diceM1TYespWByliBsjK98=", - "owner": "stachenov", - "repo": "quazip", - "rev": "09ec1d10c6d627f895109b21728dda000cbfa7d1", - "type": "github" - }, - "original": { - "owner": "stachenov", - "repo": "quazip", - "type": "github" - } - }, "root": { "inputs": { "flake-compat": "flake-compat", "libnbtplusplus": "libnbtplusplus", - "nixpkgs": "nixpkgs", - "quazip": "quazip" + "nixpkgs": "nixpkgs" } } }, From 338156500bfca02427d0bd8c9c6402fc1d5b1122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20=C3=87al=C4=B1=C5=9Fkan?= Date: Fri, 27 May 2022 22:31:25 +0300 Subject: [PATCH 125/308] nix: override msa id via cmake flag --- packages/nix/polymc/default.nix | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/nix/polymc/default.nix b/packages/nix/polymc/default.nix index d09fe3c7f..e347db6d6 100644 --- a/packages/nix/polymc/default.nix +++ b/packages/nix/polymc/default.nix @@ -48,12 +48,6 @@ mkDerivation rec { dontWrapQtApps = true; - postPatch = lib.optionalString (msaClientID != "") '' - # add client ID - substituteInPlace CMakeLists.txt \ - --replace '17b47edd-c884-4997-926d-9e7f9a6b4647' '${msaClientID}' - ''; - postUnpack = '' # Copy libnbtplusplus rm -rf source/libraries/libnbtplusplus @@ -65,7 +59,7 @@ mkDerivation rec { cmakeFlags = [ "-GNinja" "-DLauncher_PORTABLE=OFF" - ]; + ] ++ lib.optionals (msaClientID != "") [ "-DLauncher_MSA_CLIENT_ID=${msaClientID}" ]; postInstall = '' # xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128 From 0ffe0b6894ef3621ad0b345b5996509c4c95d919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20=C3=87al=C4=B1=C5=9Fkan?= Date: Fri, 27 May 2022 22:34:44 +0300 Subject: [PATCH 126/308] nix: move files to nix/ --- flake.nix | 2 +- {packages/nix => nix}/NIX.md | 0 {packages/nix/polymc => nix}/default.nix | 0 {packages/nix => nix}/flake-compat.nix | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename {packages/nix => nix}/NIX.md (100%) rename {packages/nix/polymc => nix}/default.nix (100%) rename {packages/nix => nix}/flake-compat.nix (100%) diff --git a/flake.nix b/flake.nix index b1e810571..afc853361 100644 --- a/flake.nix +++ b/flake.nix @@ -22,7 +22,7 @@ pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system}); in { - packages = forAllSystems (system: { polymc = pkgs.${system}.libsForQt5.callPackage ./packages/nix/polymc { inherit version self libnbtplusplus; }; }); + packages = forAllSystems (system: { polymc = pkgs.${system}.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus; }; }); defaultPackage = forAllSystems (system: self.packages.${system}.polymc); apps = forAllSystems (system: { polymc = { type = "app"; program = "${self.defaultPackage.${system}}/bin/polymc"; }; }); diff --git a/packages/nix/NIX.md b/nix/NIX.md similarity index 100% rename from packages/nix/NIX.md rename to nix/NIX.md diff --git a/packages/nix/polymc/default.nix b/nix/default.nix similarity index 100% rename from packages/nix/polymc/default.nix rename to nix/default.nix diff --git a/packages/nix/flake-compat.nix b/nix/flake-compat.nix similarity index 100% rename from packages/nix/flake-compat.nix rename to nix/flake-compat.nix From 48e20cb5f714fbee83889d55505eb99c3f444cda Mon Sep 17 00:00:00 2001 From: Jeremy Lorelli Date: Fri, 27 May 2022 16:41:57 -0700 Subject: [PATCH 127/308] Fix crash when aborting instance import Also turned a loop var into a reference to avoid copies on each iteration --- launcher/InstanceImportTask.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 4bad72513..56081ed14 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -72,7 +72,8 @@ InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent) bool InstanceImportTask::abort() { - m_filesNetJob->abort(); + if (m_filesNetJob) + m_filesNetJob->abort(); m_extractFuture.cancel(); return false; @@ -386,7 +387,7 @@ void InstanceImportTask::processFlame() { auto results = m_modIdResolver->getResults(); m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network()); - for(auto result: results.files) + for(const auto& result: results.files) { QString filename = result.fileName; if(!result.required) From 0ea2135aa54dbfe582e3d91cefbec1d22ffedabc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20=C3=87al=C4=B1=C5=9Fkan?= Date: Fri, 27 May 2022 22:42:23 +0300 Subject: [PATCH 128/308] nix: initial support for qt6 --- flake.nix | 6 +++++- nix/default.nix | 17 ++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/flake.nix b/flake.nix index afc853361..f2247bede 100644 --- a/flake.nix +++ b/flake.nix @@ -22,7 +22,11 @@ pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system}); in { - packages = forAllSystems (system: { polymc = pkgs.${system}.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus; }; }); + packages = forAllSystems (system: { + polymc = pkgs.${system}.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus; }; + polymc-qt6 = pkgs.${system}.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus; }; + }); + defaultPackage = forAllSystems (system: self.packages.${system}.polymc); apps = forAllSystems (system: { polymc = { type = "app"; program = "${self.defaultPackage.${system}}/bin/polymc"; }; }); diff --git a/nix/default.nix b/nix/default.nix index e347db6d6..cce40e638 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -1,5 +1,5 @@ -{ lib -, mkDerivation +{ stdenv +, lib , fetchFromGitHub , cmake , ninja @@ -7,11 +7,10 @@ , jdk , zlib , file -, makeWrapper +, wrapQtAppsHook , xorg , libpulseaudio , qtbase -, quazip , libGL , msaClientID ? "" @@ -37,13 +36,13 @@ let gameLibraryPath = libpath + ":/run/opengl-driver/lib"; in -mkDerivation rec { +stdenv.mkDerivation rec { pname = "polymc"; inherit version; src = lib.cleanSource self; - nativeBuildInputs = [ cmake ninja jdk file makeWrapper ]; + nativeBuildInputs = [ cmake ninja jdk file wrapQtAppsHook ]; buildInputs = [ qtbase quazip zlib ]; dontWrapQtApps = true; @@ -58,13 +57,13 @@ mkDerivation rec { cmakeFlags = [ "-GNinja" - "-DLauncher_PORTABLE=OFF" + "-DENABLE_LTO=on" + "-DLauncher_QT_VERSION_MAJOR=${lib.versions.major qtbase.version}" ] ++ lib.optionals (msaClientID != "") [ "-DLauncher_MSA_CLIENT_ID=${msaClientID}" ]; postInstall = '' # xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128 - wrapProgram $out/bin/polymc \ - "''${qtWrapperArgs[@]}" \ + wrapQtApp $out/bin/polymc \ --set GAME_LIBRARY_PATH ${gameLibraryPath} \ --prefix POLYMC_JAVA_PATHS : ${jdk}/lib/openjdk/bin/java:${jdk8}/lib/openjdk/bin/java \ --prefix PATH : ${lib.makeBinPath [ xorg.xrandr ]} From 123d6c72e4308a0194d57f5a910d063bd84941e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20=C3=87al=C4=B1=C5=9Fkan?= Date: Sat, 28 May 2022 11:11:31 +0300 Subject: [PATCH 129/308] nix: fix nix-build --- default.nix | 2 +- nix/flake-compat.nix | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/default.nix b/default.nix index 5abfc1bd5..146942d59 100644 --- a/default.nix +++ b/default.nix @@ -1 +1 @@ -(import packages/nix/flake-compat.nix).defaultNix +(import nix/flake-compat.nix).defaultNix diff --git a/nix/flake-compat.nix b/nix/flake-compat.nix index bb7ee13e0..8b6cb99ce 100644 --- a/nix/flake-compat.nix +++ b/nix/flake-compat.nix @@ -1,9 +1,9 @@ let - lock = builtins.fromJSON (builtins.readFile ../../flake.lock); + lock = builtins.fromJSON (builtins.readFile ../flake.lock); inherit (lock.nodes.flake-compat.locked) rev narHash; flake-compat = fetchTarball { url = "https://github.com/edolstra/flake-compat/archive/${rev}.tar.gz"; sha256 = narHash; }; in -import flake-compat { src = ../..; } +import flake-compat { src = ../.; } From ab3e2562db52d66f690f08621e220766b0953af7 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 28 May 2022 12:07:01 +0200 Subject: [PATCH 130/308] fix: clarify terms and conditions for API keys --- CMakeLists.txt | 23 ++++++++++++++++------- README.md | 7 +++++-- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fcc2512d8..5c893ceba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,13 +91,6 @@ set(Launcher_META_URL "https://meta.polymc.org/v1/" CACHE STRING "URL to fetch L # Imgur API Client ID set(Launcher_IMGUR_CLIENT_ID "5b97b0713fba4a3" CACHE STRING "Client ID you can get from Imgur when you register an application") -# MSA Client ID -set(Launcher_MSA_CLIENT_ID "549033b2-1532-4d4e-ae77-1bbaa46f9d74" CACHE STRING "Client ID you can get from Microsoft Identity Platform when you register an application") - -# CurseForge API Key -# CHANGE THIS IF YOU FORK THIS PROJECT! -set(Launcher_CURSEFORGE_API_KEY "$2a$10$1Oqr2MX3O4n/ilhFGc597u8tfI3L2Hyr9/rtWDAMRjghSQV2QUuxq" CACHE STRING "CurseForge API Key") - # Bug tracker URL set(Launcher_BUG_TRACKER_URL "https://github.com/PolyMC/PolyMC/issues" CACHE STRING "URL for the bug tracker.") @@ -119,6 +112,22 @@ set(Launcher_SUBREDDIT_URL "https://www.reddit.com/r/PolyMCLauncher/" CACHE STRI set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules") set(Launcher_QT_VERSION_MAJOR "5" CACHE STRING "Major Qt version to build against") +# API Keys +# NOTE: These API keys are here for convenience. If you rebrand this software or intend to break the terms of service +# of these platforms, please change these API keys beforehand. +# Be aware that if you were to use these API keys for malicious purposes they might get revoked, which might cause +# breakage to thousands of users. +# If you don't plan to use these features of this software, you can just remove these values. + +# By using this key in your builds you accept the terms of use laid down in +# https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use +set(Launcher_MSA_CLIENT_ID "549033b2-1532-4d4e-ae77-1bbaa46f9d74" CACHE STRING "Client ID you can get from Microsoft Identity Platform when you register an application") + +# By using this key in your builds you accept the terms and conditions laid down in +# https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions +# NOTE: CurseForge requires you to change this if you make any kind of derivative work. +set(Launcher_CURSEFORGE_API_KEY "$2a$10$1Oqr2MX3O4n/ilhFGc597u8tfI3L2Hyr9/rtWDAMRjghSQV2QUuxq" CACHE STRING "CurseForge API Key") + #### Check the current Git commit and branch include(GetGitRevisionDescription) diff --git a/README.md b/README.md index c493293d0..a08d5dc01 100644 --- a/README.md +++ b/README.md @@ -82,8 +82,11 @@ To modify download information or change packaging information send a pull reque Do whatever you want, we don't care. Just follow the license. If you have any questions about this feel free to ask in an issue. +Be aware that if you build this software without removing the provided API keys in [CMakeLists.txt](CMakeLists.txt) you are accepting the following terms and conditions: + - [Microsoft Identity Platform Terms of Use](https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use) + - [CurseForge 3rd Party API Terms and Conditions](https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions) +If you do not agree with these terms and conditions, then remove the associated API keys from the [CMakeLists.txt](CMakeLists.txt) file. + All launcher code is available under the GPL-3.0-only license. -[Source for the website](https://github.com/PolyMC/polymc.github.io) is hosted under the AGPL-3.0-or-later License. - The logo and related assets are under the CC BY-SA 4.0 license. From 80627b4f8914c821f125893dc3c04380530d6e0b Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 28 May 2022 15:42:54 +0200 Subject: [PATCH 131/308] chore: bump version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fcc2512d8..320984df0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,7 +74,7 @@ set(Launcher_HELP_URL "https://polymc.org/wiki/help-pages/%1" CACHE STRING "URL ######## Set version numbers ######## set(Launcher_VERSION_MAJOR 1) set(Launcher_VERSION_MINOR 3) -set(Launcher_VERSION_HOTFIX 0) +set(Launcher_VERSION_HOTFIX 1) # Build number set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.") From 699ad316f0d90580fa13d570d6c25aff903a470d Mon Sep 17 00:00:00 2001 From: timoreo22 Date: Sat, 28 May 2022 21:53:12 +0200 Subject: [PATCH 132/308] Rework curseforge download (#611) * Use the bulk endpoint on mod resolution for faster download * Search on modrinth for api blocked mods * Display a dialog for manually downloading blocked mods --- launcher/CMakeLists.txt | 5 + launcher/InstanceImportTask.cpp | 191 ++++++++++++----- launcher/InstanceImportTask.h | 6 + .../modplatform/flame/FileResolvingTask.cpp | 126 ++++++++--- .../modplatform/flame/FileResolvingTask.h | 8 +- launcher/modplatform/flame/PackManifest.cpp | 35 +-- launcher/modplatform/flame/PackManifest.h | 10 +- launcher/net/Upload.cpp | 199 ++++++++++++++++++ launcher/net/Upload.h | 31 +++ launcher/ui/dialogs/ScrollMessageBox.cpp | 15 ++ launcher/ui/dialogs/ScrollMessageBox.h | 20 ++ launcher/ui/dialogs/ScrollMessageBox.ui | 84 ++++++++ launcher/ui/pages/modplatform/ImportPage.cpp | 4 +- .../ui/pages/modplatform/flame/FlamePage.cpp | 2 +- 14 files changed, 633 insertions(+), 103 deletions(-) create mode 100644 launcher/net/Upload.cpp create mode 100644 launcher/net/Upload.h create mode 100644 launcher/ui/dialogs/ScrollMessageBox.cpp create mode 100644 launcher/ui/dialogs/ScrollMessageBox.h create mode 100644 launcher/ui/dialogs/ScrollMessageBox.ui diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 15534c71e..b3af12a67 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -128,6 +128,8 @@ set(NET_SOURCES net/PasteUpload.h net/Sink.h net/Validator.h + net/Upload.cpp + net/Upload.h ) # Game launch logic @@ -837,6 +839,8 @@ SET(LAUNCHER_SOURCES ui/dialogs/SkinUploadDialog.h ui/dialogs/ModDownloadDialog.cpp ui/dialogs/ModDownloadDialog.h + ui/dialogs/ScrollMessageBox.cpp + ui/dialogs/ScrollMessageBox.h # GUI - widgets ui/widgets/Common.cpp @@ -940,6 +944,7 @@ qt5_wrap_ui(LAUNCHER_UI ui/dialogs/LoginDialog.ui ui/dialogs/EditAccountDialog.ui ui/dialogs/ReviewMessageBox.ui + ui/dialogs/ScrollMessageBox.ui ) qt5_add_resources(LAUNCHER_RESOURCES diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 4acde16d2..68a497cce 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -60,9 +60,9 @@ #include "net/ChecksumValidator.h" #include "ui/dialogs/CustomMessageBox.h" +#include "ui/dialogs/ScrollMessageBox.h" #include -#include InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent) { @@ -394,61 +394,136 @@ void InstanceImportTask::processFlame() connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, [&]() { auto results = m_modIdResolver->getResults(); - m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network()); - for(const auto& result: results.files) - { - QString filename = result.fileName; - if(!result.required) - { - filename += ".disabled"; - } - - auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); - auto path = FS::PathCombine(m_stagingPath , relpath); - - switch(result.type) - { - case Flame::File::Type::Folder: - { - logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); - // fall-through intentional, we treat these as plain old mods and dump them wherever. - } - case Flame::File::Type::SingleFile: - case Flame::File::Type::Mod: - { - qDebug() << "Will download" << result.url << "to" << path; - auto dl = Net::Download::makeFile(result.url, path); - m_filesNetJob->addNetAction(dl); - break; - } - case Flame::File::Type::Modpack: - logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath)); - break; - case Flame::File::Type::Cmod2: - case Flame::File::Type::Ctoc: - case Flame::File::Type::Unknown: - logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); - break; + //first check for blocked mods + QString text; + auto anyBlocked = false; + for(const auto& result: results.files.values()) { + if (!result.resolved || result.url.isEmpty()) { + text += QString("%1: %2
").arg(result.fileName, result.websiteUrl); + anyBlocked = true; } } - m_modIdResolver.reset(); - connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() - { - m_filesNetJob.reset(); - emitSucceeded(); + if(anyBlocked) { + qWarning() << "Blocked mods found, displaying mod list"; + + auto message_dialog = new ScrollMessageBox(m_parent, + tr("Blocked mods found"), + tr("The following mods were blocked on third party launchers.
" + "You will need to manually download them and add them to the modpack"), + text); + message_dialog->setModal(true); + message_dialog->show(); + connect(message_dialog, &QDialog::rejected, [&]() { + m_modIdResolver.reset(); + emitFailed("Canceled"); + }); + connect(message_dialog, &QDialog::accepted, [&]() { + m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network()); + for (const auto &result: m_modIdResolver->getResults().files) { + QString filename = result.fileName; + if (!result.required) { + filename += ".disabled"; + } + + auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); + auto path = FS::PathCombine(m_stagingPath, relpath); + + switch (result.type) { + case Flame::File::Type::Folder: { + logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); + // fall-through intentional, we treat these as plain old mods and dump them wherever. + } + case Flame::File::Type::SingleFile: + case Flame::File::Type::Mod: { + if (!result.url.isEmpty()) { + qDebug() << "Will download" << result.url << "to" << path; + auto dl = Net::Download::makeFile(result.url, path); + m_filesNetJob->addNetAction(dl); + } + break; + } + case Flame::File::Type::Modpack: + logWarning( + tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg( + relpath)); + break; + case Flame::File::Type::Cmod2: + case Flame::File::Type::Ctoc: + case Flame::File::Type::Unknown: + logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); + break; + } + } + m_modIdResolver.reset(); + connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() { + m_filesNetJob.reset(); + emitSucceeded(); + } + ); + connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) { + m_filesNetJob.reset(); + emitFailed(reason); + }); + connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) { + setProgress(current, total); + }); + setStatus(tr("Downloading mods...")); + m_filesNetJob->start(); + }); + }else{ + //TODO extract to function ? + m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network()); + for (const auto &result: m_modIdResolver->getResults().files) { + QString filename = result.fileName; + if (!result.required) { + filename += ".disabled"; + } + + auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); + auto path = FS::PathCombine(m_stagingPath, relpath); + + switch (result.type) { + case Flame::File::Type::Folder: { + logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); + // fall-through intentional, we treat these as plain old mods and dump them wherever. + } + case Flame::File::Type::SingleFile: + case Flame::File::Type::Mod: { + if (!result.url.isEmpty()) { + qDebug() << "Will download" << result.url << "to" << path; + auto dl = Net::Download::makeFile(result.url, path); + m_filesNetJob->addNetAction(dl); + } + break; + } + case Flame::File::Type::Modpack: + logWarning( + tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg( + relpath)); + break; + case Flame::File::Type::Cmod2: + case Flame::File::Type::Ctoc: + case Flame::File::Type::Unknown: + logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); + break; + } + } + m_modIdResolver.reset(); + connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() { + m_filesNetJob.reset(); + emitSucceeded(); + } + ); + connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) { + m_filesNetJob.reset(); + emitFailed(reason); + }); + connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) { + setProgress(current, total); + }); + setStatus(tr("Downloading mods...")); + m_filesNetJob->start(); } - ); - connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) - { - m_filesNetJob.reset(); - emitFailed(reason); - }); - connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) - { - setProgress(current, total); - }); - setStatus(tr("Downloading mods...")); - m_filesNetJob->start(); } ); connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) @@ -524,11 +599,11 @@ void InstanceImportTask::processModrinth() auto jsonFiles = Json::requireIsArrayOf(obj, "files", "modrinth.index.json"); bool had_optional = false; - for (auto& obj : jsonFiles) { + for (auto& modInfo : jsonFiles) { Modrinth::File file; - file.path = Json::requireString(obj, "path"); + file.path = Json::requireString(modInfo, "path"); - auto env = Json::ensureObject(obj, "env"); + auto env = Json::ensureObject(modInfo, "env"); QString support = Json::ensureString(env, "client", "unsupported"); if (support == "unsupported") { continue; @@ -546,7 +621,7 @@ void InstanceImportTask::processModrinth() file.path += ".disabled"; } - QJsonObject hashes = Json::requireObject(obj, "hashes"); + QJsonObject hashes = Json::requireObject(modInfo, "hashes"); QString hash; QCryptographicHash::Algorithm hashAlgorithm; hash = Json::ensureString(hashes, "sha1"); @@ -566,7 +641,7 @@ void InstanceImportTask::processModrinth() file.hashAlgorithm = hashAlgorithm; // Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode // (as Modrinth seems to incorrectly handle spaces) - file.download = Json::requireString(Json::ensureArray(obj, "downloads").first(), "Download URL for " + file.path); + file.download = Json::requireString(Json::ensureArray(modInfo, "downloads").first(), "Download URL for " + file.path); if (!file.download.isValid() || !Modrinth::validateDownloadUrl(file.download)) { throw JSONValidationError("Download URL for " + file.path + " is not a correctly formatted URL"); } diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h index 5e4d32351..b67d48f3c 100644 --- a/launcher/InstanceImportTask.h +++ b/launcher/InstanceImportTask.h @@ -42,6 +42,7 @@ #include #include "settings/SettingsObject.h" #include "QObjectPtr.h" +#include "modplatform/flame/PackManifest.h" #include @@ -59,6 +60,10 @@ public: bool canAbort() const override { return true; } bool abort() override; + const QVector &getBlockedFiles() const + { + return m_blockedMods; + } protected: //! Entry point for tasks. @@ -87,6 +92,7 @@ private: /* data */ std::unique_ptr m_packZip; QFuture> m_extractFuture; QFutureWatcher> m_extractFutureWatcher; + QVector m_blockedMods; enum class ModpackType{ Unknown, MultiMC, diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index 95924a681..a790ab9c5 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -1,7 +1,9 @@ #include "FileResolvingTask.h" -#include "Json.h" -Flame::FileResolvingTask::FileResolvingTask(shared_qobject_ptr network, Flame::Manifest& toProcess) +#include "Json.h" +#include "net/Upload.h" + +Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptr& network, Flame::Manifest& toProcess) : m_network(network), m_toProcess(toProcess) {} @@ -10,40 +12,116 @@ void Flame::FileResolvingTask::executeTask() setStatus(tr("Resolving mod IDs...")); setProgress(0, m_toProcess.files.size()); m_dljob = new NetJob("Mod id resolver", m_network); - results.resize(m_toProcess.files.size()); - int index = 0; - for (auto& file : m_toProcess.files) { - auto projectIdStr = QString::number(file.projectId); - auto fileIdStr = QString::number(file.fileId); - QString metaurl = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(projectIdStr, fileIdStr); - auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results[index]); - m_dljob->addNetAction(dl); - index++; - } + result.reset(new QByteArray()); + //build json data to send + QJsonObject object; + + object["fileIds"] = QJsonArray::fromVariantList(std::accumulate(m_toProcess.files.begin(), m_toProcess.files.end(), QVariantList(), [](QVariantList& l, const File& s) { + l.push_back(s.fileId); + return l; + })); + QByteArray data = Json::toText(object); + auto dl = Net::Upload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result.get(), data); + m_dljob->addNetAction(dl); connect(m_dljob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::netJobFinished); m_dljob->start(); } void Flame::FileResolvingTask::netJobFinished() { - bool failed = false; int index = 0; - for (auto& bytes : results) { - auto& out = m_toProcess.files[index]; + // job to check modrinth for blocked projects + auto job = new NetJob("Modrinth check", m_network); + blockedProjects = QMap(); + auto doc = Json::requireDocument(*result); + auto array = Json::requireArray(doc.object()["data"]); + for (QJsonValueRef file : array) { + auto fileid = Json::requireInteger(Json::requireObject(file)["id"]); + auto& out = m_toProcess.files[fileid]; try { - failed &= (!out.parseFromBytes(bytes)); + out.parseFromObject(Json::requireObject(file)); } catch (const JSONValidationError& e) { - qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:"; - qCritical() << e.cause(); - qCritical() << "JSON:"; - qCritical() << bytes; - failed = true; + qDebug() << "Blocked mod on curseforge" << out.fileName; + auto hash = out.hash; + if(!hash.isEmpty()) { + auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash); + auto output = new QByteArray(); + auto dl = Net::Download::makeByteArray(QUrl(url), output); + QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() { + out.resolved = true; + }); + + job->addNetAction(dl); + blockedProjects.insert(&out, output); + } } index++; } - if (!failed) { - emitSucceeded(); + connect(job, &NetJob::finished, this, &Flame::FileResolvingTask::modrinthCheckFinished); + + job->start(); +} + +void Flame::FileResolvingTask::modrinthCheckFinished() { + qDebug() << "Finished with blocked mods : " << blockedProjects.size(); + + for (auto it = blockedProjects.keyBegin(); it != blockedProjects.keyEnd(); it++) { + auto &out = *it; + auto bytes = blockedProjects[out]; + if (!out->resolved) { + delete bytes; + continue; + } + QJsonDocument doc = QJsonDocument::fromJson(*bytes); + auto obj = doc.object(); + auto array = Json::requireArray(obj,"files"); + for (auto file: array) { + auto fileObj = Json::requireObject(file); + auto primary = Json::requireBoolean(fileObj,"primary"); + if (primary) { + out->url = Json::requireUrl(fileObj,"url"); + qDebug() << "Found alternative on modrinth " << out->fileName; + break; + } + } + delete bytes; + } + //copy to an output list and filter out projects found on modrinth + auto block = new QList(); + auto it = blockedProjects.keys(); + std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File *f) { + return !f->resolved; + }); + //Display not found mods early + if (!block->empty()) { + //blocked mods found, we need the slug for displaying.... we need another job :D ! + auto slugJob = new NetJob("Slug Job", m_network); + auto slugs = QVector(block->size()); + auto index = 0; + for (auto fileInfo: *block) { + auto projectId = fileInfo->projectId; + slugs[index] = QByteArray(); + auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId); + auto dl = Net::Download::makeByteArray(url, &slugs[index]); + slugJob->addNetAction(dl); + index++; + } + connect(slugJob, &NetJob::succeeded, this, [slugs, this, slugJob, block]() { + slugJob->deleteLater(); + auto index = 0; + for (const auto &slugResult: slugs) { + auto json = QJsonDocument::fromJson(slugResult); + auto base = Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json),"data"),"links"), + "websiteUrl"); + auto mod = block->at(index); + auto link = QString("%1/download/%2").arg(base, QString::number(mod->fileId)); + mod->websiteUrl = link; + index++; + } + emitSucceeded(); + }); + slugJob->start(); } else { - emitFailed(tr("Some mod ID resolving tasks failed.")); + emitSucceeded(); } } diff --git a/launcher/modplatform/flame/FileResolvingTask.h b/launcher/modplatform/flame/FileResolvingTask.h index 5e5adcd73..87981f0a4 100644 --- a/launcher/modplatform/flame/FileResolvingTask.h +++ b/launcher/modplatform/flame/FileResolvingTask.h @@ -10,7 +10,7 @@ class FileResolvingTask : public Task { Q_OBJECT public: - explicit FileResolvingTask(shared_qobject_ptr network, Flame::Manifest &toProcess); + explicit FileResolvingTask(const shared_qobject_ptr& network, Flame::Manifest &toProcess); virtual ~FileResolvingTask() {}; const Flame::Manifest &getResults() const @@ -27,7 +27,11 @@ protected slots: private: /* data */ shared_qobject_ptr m_network; Flame::Manifest m_toProcess; - QVector results; + std::shared_ptr result; NetJob::Ptr m_dljob; + + void modrinthCheckFinished(); + + QMap blockedProjects; }; } diff --git a/launcher/modplatform/flame/PackManifest.cpp b/launcher/modplatform/flame/PackManifest.cpp index e4f90c1a1..12a4b9900 100644 --- a/launcher/modplatform/flame/PackManifest.cpp +++ b/launcher/modplatform/flame/PackManifest.cpp @@ -41,7 +41,7 @@ static void loadManifestV1(Flame::Manifest& m, QJsonObject& manifest) auto obj = Json::requireObject(item); Flame::File file; loadFileV1(file, obj); - m.files.append(file); + m.files.insert(file.fileId,file); } m.overrides = Json::ensureString(manifest, "overrides", "overrides"); } @@ -61,21 +61,9 @@ void Flame::loadManifest(Flame::Manifest& m, const QString& filepath) loadManifestV1(m, obj); } -bool Flame::File::parseFromBytes(const QByteArray& bytes) +bool Flame::File::parseFromObject(const QJsonObject& obj) { - auto doc = Json::requireDocument(bytes); - if (!doc.isObject()) { - throw JSONValidationError(QString("data is not an object? that's not supposed to happen")); - } - auto obj = Json::ensureObject(doc.object(), "data"); - fileName = Json::requireString(obj, "fileName"); - - QString rawUrl = Json::requireString(obj, "downloadUrl"); - url = QUrl(rawUrl, QUrl::TolerantMode); - if (!url.isValid()) { - throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl)); - } // This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience // It is also optional type = File::Type::SingleFile; @@ -87,6 +75,25 @@ bool Flame::File::parseFromBytes(const QByteArray& bytes) // this is probably a mod, dunno what else could modpacks download targetFolder = "mods"; } + // get the hash + hash = QString(); + auto hashes = Json::ensureArray(obj, "hashes"); + for(QJsonValueRef item : hashes) { + auto hobj = Json::requireObject(item); + auto algo = Json::requireInteger(hobj, "algo"); + auto value = Json::requireString(hobj, "value"); + if (algo == 1) { + hash = value; + } + } + + + // may throw, if the project is blocked + QString rawUrl = Json::ensureString(obj, "downloadUrl"); + url = QUrl(rawUrl, QUrl::TolerantMode); + if (!url.isValid()) { + throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl)); + } resolved = true; return true; diff --git a/launcher/modplatform/flame/PackManifest.h b/launcher/modplatform/flame/PackManifest.h index 02f39f0ea..26a48d1c1 100644 --- a/launcher/modplatform/flame/PackManifest.h +++ b/launcher/modplatform/flame/PackManifest.h @@ -2,19 +2,24 @@ #include #include +#include #include +#include namespace Flame { struct File { // NOTE: throws JSONValidationError - bool parseFromBytes(const QByteArray &bytes); + bool parseFromObject(const QJsonObject& object); int projectId = 0; int fileId = 0; // NOTE: the opposite to 'optional'. This is at the time of writing unused. bool required = true; + QString hash; + // NOTE: only set on blocked files ! Empty otherwise. + QString websiteUrl; // our bool resolved = false; @@ -54,7 +59,8 @@ struct Manifest QString name; QString version; QString author; - QVector files; + //File id -> File + QMap files; QString overrides; }; diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp new file mode 100644 index 000000000..bbd273901 --- /dev/null +++ b/launcher/net/Upload.cpp @@ -0,0 +1,199 @@ +// +// Created by timoreo on 20/05/22. +// + +#include "Upload.h" + +#include +#include "ByteArraySink.h" +#include "BuildConfig.h" +#include "Application.h" + +namespace Net { + + void Upload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { + setProgress(bytesReceived, bytesTotal); + } + + void Upload::downloadError(QNetworkReply::NetworkError error) { + if (error == QNetworkReply::OperationCanceledError) { + qCritical() << "Aborted " << m_url.toString(); + m_state = State::AbortedByUser; + } else { + // error happened during download. + qCritical() << "Failed " << m_url.toString() << " with reason " << error; + m_state = State::Failed; + } + } + + void Upload::sslErrors(const QList &errors) { + int i = 1; + for (const auto& error : errors) { + qCritical() << "Upload" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); + auto cert = error.certificate(); + qCritical() << "Certificate in question:\n" << cert.toText(); + i++; + } + } + + bool Upload::handleRedirect() + { + QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl(); + if (!redirect.isValid()) { + if (!m_reply->hasRawHeader("Location")) { + // no redirect -> it's fine to continue + return false; + } + // there is a Location header, but it's not correct. we need to apply some workarounds... + QByteArray redirectBA = m_reply->rawHeader("Location"); + if (redirectBA.size() == 0) { + // empty, yet present redirect header? WTF? + return false; + } + QString redirectStr = QString::fromUtf8(redirectBA); + + if (redirectStr.startsWith("//")) { + /* + * IF the URL begins with //, we need to insert the URL scheme. + * See: https://bugreports.qt.io/browse/QTBUG-41061 + * See: http://tools.ietf.org/html/rfc3986#section-4.2 + */ + redirectStr = m_reply->url().scheme() + ":" + redirectStr; + } else if (redirectStr.startsWith("/")) { + /* + * IF the URL begins with /, we need to process it as a relative URL + */ + auto url = m_reply->url(); + url.setPath(redirectStr, QUrl::TolerantMode); + redirectStr = url.toString(); + } + + /* + * Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues. + * FIXME: report Qt bug for this + */ + redirect = QUrl(redirectStr, QUrl::TolerantMode); + if (!redirect.isValid()) { + qWarning() << "Failed to parse redirect URL:" << redirectStr; + downloadError(QNetworkReply::ProtocolFailure); + return false; + } + qDebug() << "Fixed location header:" << redirect; + } else { + qDebug() << "Location header:" << redirect; + } + + m_url = QUrl(redirect.toString()); + qDebug() << "Following redirect to " << m_url.toString(); + startAction(m_network); + return true; + } + + void Upload::downloadFinished() { + // handle HTTP redirection first + // very unlikely for post requests, still can happen + if (handleRedirect()) { + qDebug() << "Upload redirected:" << m_url.toString(); + return; + } + + // if the download failed before this point ... + if (m_state == State::Succeeded) { + qDebug() << "Upload failed but we are allowed to proceed:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit succeeded(); + return; + } else if (m_state == State::Failed) { + qDebug() << "Upload failed in previous step:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit failed(""); + return; + } else if (m_state == State::AbortedByUser) { + qDebug() << "Upload aborted in previous step:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit aborted(); + return; + } + + // make sure we got all the remaining data, if any + auto data = m_reply->readAll(); + if (data.size()) { + qDebug() << "Writing extra" << data.size() << "bytes"; + m_state = m_sink->write(data); + } + + // otherwise, finalize the whole graph + m_state = m_sink->finalize(*m_reply.get()); + if (m_state != State::Succeeded) { + qDebug() << "Upload failed to finalize:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit failed(""); + return; + } + m_reply.reset(); + qDebug() << "Upload succeeded:" << m_url.toString(); + emit succeeded(); + } + + void Upload::downloadReadyRead() { + if (m_state == State::Running) { + auto data = m_reply->readAll(); + m_state = m_sink->write(data); + } + } + + void Upload::executeTask() { + setStatus(tr("Uploading %1").arg(m_url.toString())); + + if (m_state == State::AbortedByUser) { + qWarning() << "Attempt to start an aborted Upload:" << m_url.toString(); + emit aborted(); + return; + } + QNetworkRequest request(m_url); + m_state = m_sink->init(request); + switch (m_state) { + case State::Succeeded: + emitSucceeded(); + qDebug() << "Upload cache hit " << m_url.toString(); + return; + case State::Running: + qDebug() << "Uploading " << m_url.toString(); + break; + case State::Inactive: + case State::Failed: + emitFailed(""); + return; + case State::AbortedByUser: + emitAborted(); + return; + } + + request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT); + if (request.url().host().contains("api.curseforge.com")) { + request.setRawHeader("x-api-key", APPLICATION->getCurseKey().toUtf8()); + } + //TODO other types of post requests ? + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QNetworkReply* rep = m_network->post(request, m_post_data); + + m_reply.reset(rep); + connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64))); + connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, &QNetworkReply::sslErrors, this, &Upload::sslErrors); + connect(rep, &QNetworkReply::readyRead, this, &Upload::downloadReadyRead); + } + + Upload::Ptr Upload::makeByteArray(QUrl url, QByteArray *output, QByteArray m_post_data) { + auto* up = new Upload(); + up->m_url = std::move(url); + up->m_sink.reset(new ByteArraySink(output)); + up->m_post_data = std::move(m_post_data); + return up; + } +} // Net diff --git a/launcher/net/Upload.h b/launcher/net/Upload.h new file mode 100644 index 000000000..ee784c6e9 --- /dev/null +++ b/launcher/net/Upload.h @@ -0,0 +1,31 @@ +#pragma once + +#include "NetAction.h" +#include "Sink.h" + +namespace Net { + + class Upload : public NetAction { + Q_OBJECT + + public: + static Upload::Ptr makeByteArray(QUrl url, QByteArray *output, QByteArray m_post_data); + + protected slots: + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; + void downloadError(QNetworkReply::NetworkError error) override; + void sslErrors(const QList & errors); + void downloadFinished() override; + void downloadReadyRead() override; + + public slots: + void executeTask() override; + private: + std::unique_ptr m_sink; + QByteArray m_post_data; + + bool handleRedirect(); + }; + +} // Net + diff --git a/launcher/ui/dialogs/ScrollMessageBox.cpp b/launcher/ui/dialogs/ScrollMessageBox.cpp new file mode 100644 index 000000000..afdc4bae1 --- /dev/null +++ b/launcher/ui/dialogs/ScrollMessageBox.cpp @@ -0,0 +1,15 @@ +#include "ScrollMessageBox.h" +#include "ui_ScrollMessageBox.h" + + +ScrollMessageBox::ScrollMessageBox(QWidget *parent, const QString &title, const QString &text, const QString &body) : + QDialog(parent), ui(new Ui::ScrollMessageBox) { + ui->setupUi(this); + this->setWindowTitle(title); + ui->label->setText(text); + ui->textBrowser->setText(body); +} + +ScrollMessageBox::~ScrollMessageBox() { + delete ui; +} diff --git a/launcher/ui/dialogs/ScrollMessageBox.h b/launcher/ui/dialogs/ScrollMessageBox.h new file mode 100644 index 000000000..84aa253a9 --- /dev/null +++ b/launcher/ui/dialogs/ScrollMessageBox.h @@ -0,0 +1,20 @@ +#pragma once + +#include + + +QT_BEGIN_NAMESPACE +namespace Ui { class ScrollMessageBox; } +QT_END_NAMESPACE + +class ScrollMessageBox : public QDialog { +Q_OBJECT + +public: + ScrollMessageBox(QWidget *parent, const QString &title, const QString &text, const QString &body); + + ~ScrollMessageBox() override; + +private: + Ui::ScrollMessageBox *ui; +}; diff --git a/launcher/ui/dialogs/ScrollMessageBox.ui b/launcher/ui/dialogs/ScrollMessageBox.ui new file mode 100644 index 000000000..885fbfd25 --- /dev/null +++ b/launcher/ui/dialogs/ScrollMessageBox.ui @@ -0,0 +1,84 @@ + + + ScrollMessageBox + + + + 0 + 0 + 400 + 455 + + + + ScrollMessageBox + + + + + + + + + Qt::RichText + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + true + + + true + + + + + + + + + buttonBox + accepted() + ScrollMessageBox + accept() + + + 199 + 425 + + + 199 + 227 + + + + + buttonBox + rejected() + ScrollMessageBox + reject() + + + 199 + 425 + + + 199 + 227 + + + + + diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp index c7bc13d88..b3ed1b732 100644 --- a/launcher/ui/pages/modplatform/ImportPage.cpp +++ b/launcher/ui/pages/modplatform/ImportPage.cpp @@ -117,7 +117,7 @@ void ImportPage::updateState() if(fi.exists() && (zip || fi.suffix() == "mrpack")) { QFileInfo fi(url.fileName()); - dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url)); + dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url,this)); dialog->setSuggestedIcon("default"); } } @@ -130,7 +130,7 @@ void ImportPage::updateState() } // hook, line and sinker. QFileInfo fi(url.fileName()); - dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url)); + dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url,this)); dialog->setSuggestedIcon("default"); } } diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index ec7746217..7e90af478 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -201,7 +201,7 @@ void FlamePage::suggestCurrent() return; } - dialog->setSuggestedPack(current.name, new InstanceImportTask(selectedVersion)); + dialog->setSuggestedPack(current.name, new InstanceImportTask(selectedVersion,this)); QString editedLogoName; editedLogoName = "curseforge_" + current.logoName.section(".", 0, 0); listModel->getLogo(current.logoName, current.logoUrl, From f4604bbf797673b089367ec6af42723084b17181 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 28 May 2022 09:19:53 -0300 Subject: [PATCH 133/308] change: update whitelisted hosts in Modrinth modpacks --- launcher/InstanceImportTask.cpp | 11 ++++++++--- .../modplatform/modrinth/ModrinthPackManifest.cpp | 4 ---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 68a497cce..e3f54aebd 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -641,10 +641,15 @@ void InstanceImportTask::processModrinth() file.hashAlgorithm = hashAlgorithm; // Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode // (as Modrinth seems to incorrectly handle spaces) + file.download = Json::requireString(Json::ensureArray(modInfo, "downloads").first(), "Download URL for " + file.path); - if (!file.download.isValid() || !Modrinth::validateDownloadUrl(file.download)) { - throw JSONValidationError("Download URL for " + file.path + " is not a correctly formatted URL"); - } + + if(!file.download.isValid()) + throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path)); + else if(!Modrinth::validateDownloadUrl(file.download)) + throw JSONValidationError( + tr("Download URL for %1 is from a non-whitelisted by Modrinth domain: %2").arg(file.path, file.download.host())); + files.push_back(file); } diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp index f1ad39cea..b1c4fbcd8 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp @@ -98,10 +98,6 @@ auto validateDownloadUrl(QUrl url) -> bool auto domain = url.host(); if(domain == "cdn.modrinth.com") return true; - if(domain == "edge.forgecdn.net") - return true; - if(domain == "media.forgecdn.net") - return true; if(domain == "github.com") return true; if(domain == "raw.githubusercontent.com") From 1698554024d8fb7646a7a725e354a960ee19b568 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 28 May 2022 14:12:00 -0300 Subject: [PATCH 134/308] debug: add non-translated debug logging for 'non-whitelisted url' fails --- launcher/InstanceImportTask.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index e3f54aebd..f166088f9 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -644,11 +644,15 @@ void InstanceImportTask::processModrinth() file.download = Json::requireString(Json::ensureArray(modInfo, "downloads").first(), "Download URL for " + file.path); - if(!file.download.isValid()) + if (!file.download.isValid()) { + 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)); - else if(!Modrinth::validateDownloadUrl(file.download)) + } + else if (!Modrinth::validateDownloadUrl(file.download)) { + qDebug() << QString("Download URL (%1) for %2 is from a non-whitelisted by Modrinth domain").arg(file.download.toString(), file.path); throw JSONValidationError( tr("Download URL for %1 is from a non-whitelisted by Modrinth domain: %2").arg(file.path, file.download.host())); + } files.push_back(file); } From b5e00027d1a16744ae9287b1262e7f6405bd9d5d Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 28 May 2022 14:16:05 -0300 Subject: [PATCH 135/308] change: add 'gitlab.com' to whitelisted Modrinth modpack urls --- launcher/modplatform/modrinth/ModrinthPackManifest.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp index b1c4fbcd8..8b3794809 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp @@ -102,6 +102,8 @@ auto validateDownloadUrl(QUrl url) -> bool return true; if(domain == "raw.githubusercontent.com") return true; + if(domain == "gitlab.com") + return true; return false; } From abd240468e362231ce8cbc23573faea9a0e657f4 Mon Sep 17 00:00:00 2001 From: Lenny McLennington Date: Sat, 28 May 2022 19:54:00 +0100 Subject: [PATCH 136/308] clean up validateDownloadUrl --- .../modrinth/ModrinthPackManifest.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp index 8b3794809..33116231c 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp @@ -42,6 +42,8 @@ #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" +#include + static ModrinthAPI api; namespace Modrinth { @@ -95,17 +97,15 @@ void loadIndexedVersions(Modpack& pack, QJsonDocument& doc) auto validateDownloadUrl(QUrl url) -> bool { - auto domain = url.host(); - if(domain == "cdn.modrinth.com") - return true; - if(domain == "github.com") - return true; - if(domain == "raw.githubusercontent.com") - return true; - if(domain == "gitlab.com") - return true; + static QSet domainWhitelist{ + "cdn.modrinth.com", + "github.com", + "raw.githubusercontent.com", + "gitlab.com" + }; - return false; + auto domain = url.host(); + return domainWhitelist.contains(domain); } auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion From f0ec165d42fb694f8027fb32f8c6d0867f286ced Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 28 May 2022 18:04:16 -0300 Subject: [PATCH 137/308] feat: add warning of non-whitelisted URLs instead of a hard fail Based on people's votes on Discord :^) --- launcher/InstanceImportTask.cpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index f166088f9..0b97430e1 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -585,6 +585,7 @@ void InstanceImportTask::processMultiMC() void InstanceImportTask::processModrinth() { std::vector files; + std::vector non_whitelisted_files; QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion; try { QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); @@ -650,13 +651,29 @@ void InstanceImportTask::processModrinth() } else if (!Modrinth::validateDownloadUrl(file.download)) { qDebug() << QString("Download URL (%1) for %2 is from a non-whitelisted by Modrinth domain").arg(file.download.toString(), file.path); - throw JSONValidationError( - tr("Download URL for %1 is from a non-whitelisted by Modrinth domain: %2").arg(file.path, file.download.host())); + non_whitelisted_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
URL: %2
").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"); for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) { QString name = it.key(); From e7f35e6ca3b90437ef1fb8b02d31b4fbd47b866b Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Sat, 28 May 2022 23:26:47 +0100 Subject: [PATCH 138/308] API: Add settings to support managed packs Managed packs means an installation of a modpack through a modpack provider. Managed packs track their origins (pack platform, name, id), so that in future features can exist around this - such as updating, and reinstalling. --- launcher/BaseInstance.cpp | 49 +++++++++++++++++++++++++++++++++++++++ launcher/BaseInstance.h | 9 +++++++ 2 files changed, 58 insertions(+) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 2fb31d947..c9394d3fd 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (c) 2022 Jamie Mansfield * * 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 @@ -76,6 +77,14 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s 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", ""); } QString BaseInstance::getPreLaunchCommand() @@ -93,6 +102,46 @@ QString BaseInstance::getPostExitCommand() return settings()->get("PostExitCommand").toString(); } +bool BaseInstance::isManagedPack() +{ + return settings()->get("ManagedPack").toBool(); +} + +QString BaseInstance::getManagedPackType() +{ + return settings()->get("ManagedPackType").toString(); +} + +QString BaseInstance::getManagedPackID() +{ + return settings()->get("ManagedPackID").toString(); +} + +QString BaseInstance::getManagedPackName() +{ + return settings()->get("ManagedPackName").toString(); +} + +QString BaseInstance::getManagedPackVersionID() +{ + return settings()->get("ManagedPackVersionID").toString(); +} + +QString BaseInstance::getManagedPackVersionName() +{ + return settings()->get("ManagedPackVersionName").toString(); +} + +void BaseInstance::setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version) +{ + settings()->set("ManagedPack", true); + settings()->set("ManagedPackType", type); + settings()->set("ManagedPackID", id); + settings()->set("ManagedPackName", name); + settings()->set("ManagedPackVersionID", versionId); + settings()->set("ManagedPackVersionName", version); +} + int BaseInstance::getConsoleMaxLines() const { auto lineSetting = settings()->getSetting("ConsoleMaxLines"); diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index c973fcd42..661776146 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (c) 2022 Jamie Mansfield * * 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 @@ -139,6 +140,14 @@ public: QString getPostExitCommand(); QString getWrapperCommand(); + bool isManagedPack(); + QString getManagedPackType(); + QString getManagedPackID(); + QString getManagedPackName(); + QString getManagedPackVersionID(); + QString getManagedPackVersionName(); + void setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version); + /// guess log level from a line of game log virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) { From a98b6663e1fc130b398514fdf3ecb3d4e40b9460 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Sun, 22 May 2022 14:34:11 +0100 Subject: [PATCH 139/308] ATLauncher: Pass the full pack name through to the install task --- .../modplatform/atlauncher/ATLPackInstallTask.cpp | 15 ++++++++------- .../modplatform/atlauncher/ATLPackInstallTask.h | 5 +++-- .../ui/pages/modplatform/atlauncher/AtlPage.cpp | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 62c7bf6d4..c5477addb 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -60,10 +60,11 @@ namespace ATLauncher { static Meta::VersionPtr getComponentVersion(const QString& uid, const QString& version); -PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString pack, QString version) +PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString packName, QString version) { m_support = support; - m_pack = pack; + m_pack_name = packName; + m_pack_safe_name = packName.replace(QRegularExpression("[^A-Za-z0-9]"), ""); m_version_name = version; } @@ -81,7 +82,7 @@ void PackInstallTask::executeTask() qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId(); auto *netJob = new NetJob("ATLauncher::VersionFetch", APPLICATION->network()); auto searchUrl = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json") - .arg(m_pack).arg(m_version_name); + .arg(m_pack_safe_name).arg(m_version_name); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); jobPtr = netJob; jobPtr->start(); @@ -319,7 +320,7 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared auto patchFileName = FS::PathCombine(patchDir, target_id + ".json"); auto f = std::make_shared(); - f->name = m_pack + " " + m_version_name + " (libraries)"; + f->name = m_pack_name + " " + m_version_name + " (libraries)"; const static QMap liteLoaderMap = { { "61179803bcd5fb7790789b790908663d", "1.12-SNAPSHOT" }, @@ -465,7 +466,7 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr< } auto f = std::make_shared(); - f->name = m_pack + " " + m_version_name; + f->name = m_pack_name + " " + m_version_name; if (!mainClass.isEmpty() && !mainClasses.contains(mainClass)) { f->mainClass = mainClass; } @@ -507,9 +508,9 @@ void PackInstallTask::installConfigs() setStatus(tr("Downloading configs...")); jobPtr = new NetJob(tr("Config download"), APPLICATION->network()); - auto path = QString("Configs/%1/%2.zip").arg(m_pack).arg(m_version_name); + auto path = QString("Configs/%1/%2.zip").arg(m_pack_safe_name).arg(m_version_name); auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.zip") - .arg(m_pack).arg(m_version_name); + .arg(m_pack_safe_name).arg(m_version_name); auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", path); entry->setStale(true); diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h index f0af4e3a2..f55873e96 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h @@ -75,7 +75,7 @@ class PackInstallTask : public InstanceTask Q_OBJECT public: - explicit PackInstallTask(UserInteractionSupport *support, QString pack, QString version); + explicit PackInstallTask(UserInteractionSupport *support, QString packName, QString version); virtual ~PackInstallTask(){} bool canAbort() const override { return true; } @@ -117,7 +117,8 @@ private: NetJob::Ptr jobPtr; QByteArray response; - QString m_pack; + QString m_pack_name; + QString m_pack_safe_name; QString m_version_name; PackVersion m_version; diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp index 7bc6fc6b8..8de5211ce 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp @@ -117,7 +117,7 @@ void AtlPage::suggestCurrent() return; } - dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ATLauncher::PackInstallTask(this, selected.safeName, selectedVersion)); + dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ATLauncher::PackInstallTask(this, selected.name, selectedVersion)); auto editedLogoName = selected.safeName; auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(selected.safeName.toLower()); listModel->getLogo(selected.safeName, url, [this, editedLogoName](QString logo) From 411bf3be03ca474f371164c903f95aaa256d81fa Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Sat, 28 May 2022 23:38:21 +0100 Subject: [PATCH 140/308] ATLauncher: Make packs managed when installing --- launcher/modplatform/atlauncher/ATLPackInstallTask.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index c5477addb..d5bdf1d81 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -863,6 +863,7 @@ void PackInstallTask::install() instance.setName(m_instName); instance.setIconKey(m_instIcon); + instance.setManagedPack("atlauncher", m_pack_safe_name, m_pack_name, m_version_name, m_version_name); instanceSettings->resumeSave(); jarmods.clear(); From 96b76c8f5cd54111303ce642998d8fa020d9c0a5 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Sat, 28 May 2022 23:38:55 +0100 Subject: [PATCH 141/308] ModpacksCH: Make packs managed when installing --- .../modpacksch/FTBPackInstallTask.cpp | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index 33df6fa4b..47143c9dc 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -1,18 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only /* - * Copyright 2020-2021 Jamie Mansfield - * Copyright 2020-2021 Petr Mrazek + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 Jamie Mansfield * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 2020-2021 Jamie Mansfield + * Copyright 2020-2021 Petr Mrazek + * + * 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 "FTBPackInstallTask.h" @@ -220,6 +239,7 @@ void PackInstallTask::install() instance.setName(m_instName); instance.setIconKey(m_instIcon); + instance.setManagedPack("modpacksch", QString::number(m_pack.id), m_pack.name, QString::number(m_version.id), m_version.name); instanceSettings->resumeSave(); emitSucceeded(); From febdb85f960f105ac9d85fdafddbe5c0c74673f1 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Mon, 2 May 2022 15:48:12 +0100 Subject: [PATCH 142/308] ModpacksCH: Use ModpacksCH rather than FTB in error messages --- .../modplatform/modpacksch/FTBPackInstallTask.cpp | 2 +- launcher/ui/pages/modplatform/ftb/FtbListModel.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index 47143c9dc..c324ffda1 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -99,7 +99,7 @@ void PackInstallTask::onDownloadSucceeded() QJsonParseError parse_error; QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); if(parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << response; return; } diff --git a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp b/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp index 37244fed7..ad15b6e62 100644 --- a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp +++ b/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp @@ -122,10 +122,10 @@ void ListModel::requestFinished() jobPtr.reset(); remainingPacks.clear(); - QJsonParseError parse_error; + QJsonParseError parse_error {}; QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); if(parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << response; return; } @@ -169,7 +169,7 @@ void ListModel::packRequestFinished() QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); if(parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << response; return; } @@ -184,7 +184,7 @@ void ListModel::packRequestFinished() catch (const JSONValidationError &e) { qDebug() << QString::fromUtf8(response); - qWarning() << "Error while reading pack manifest from FTB: " << e.cause(); + qWarning() << "Error while reading pack manifest from ModpacksCH: " << e.cause(); return; } @@ -192,7 +192,7 @@ void ListModel::packRequestFinished() // ignore those "dud" packs. if (pack.versions.empty()) { - qWarning() << "FTB Pack " << pack.id << " ignored. reason: lacking any versions"; + qWarning() << "ModpacksCH Pack " << pack.id << " ignored. reason: lacking any versions"; } else { @@ -270,7 +270,7 @@ void ListModel::requestLogo(QString logo, QString url) bool stale = entry->isStale(); - NetJob *job = new NetJob(QString("FTB Icon Download %1").arg(logo), APPLICATION->network()); + NetJob *job = new NetJob(QString("ModpacksCH Icon Download %1").arg(logo), APPLICATION->network()); job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); auto fullPath = entry->getFullPath(); From 80da1f1bb96968d8545ddcd6698da75466bd9934 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Sat, 28 May 2022 23:59:00 +0100 Subject: [PATCH 143/308] ATLauncher: Use ATLauncher rather than FTB in error messages --- launcher/modplatform/atlauncher/ATLPackInstallTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index d5bdf1d81..b4936bd8e 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -99,7 +99,7 @@ void PackInstallTask::onDownloadSucceeded() QJsonParseError parse_error {}; QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); if(parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << "Error while parsing JSON response from ATLauncher at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << response; return; } From d4c1d627814ab4719c7baec56941e8cc1038510e Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Sun, 29 May 2022 12:15:20 +0800 Subject: [PATCH 144/308] Update launcher/ui/pages/global/JavaPage.cpp Co-authored-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com> --- launcher/ui/pages/global/JavaPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index 54bfb3cfc..88607e32d 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -95,7 +95,7 @@ void JavaPage::applySettings() // Java Settings s->set("JavaPath", ui->javaPathTextBox->text()); - s->set("JvmArgs", ui->jvmArgsTextBox->toPlainText()); + s->set("JvmArgs", ui->jvmArgsTextBox->toPlainText().replace("\n", " ")); s->set("IgnoreJavaCompatibility", ui->skipCompatibilityCheckbox->isChecked()); s->set("IgnoreJavaWizard", ui->skipJavaWizardCheckbox->isChecked()); JavaCommon::checkJVMArgs(s->get("JvmArgs").toString(), this->parentWidget()); From 577f17c9166ffc5981ab4cfccbba45ed62b38ebe Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Sun, 29 May 2022 09:45:20 +0200 Subject: [PATCH 145/308] remove vista support from the manifest we don't support qt <5.12 anymore anyways --- program_info/polymc.manifest | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/program_info/polymc.manifest b/program_info/polymc.manifest index 2d9eb165b..8ca50acf4 100644 --- a/program_info/polymc.manifest +++ b/program_info/polymc.manifest @@ -16,15 +16,13 @@ Custom Minecraft launcher for managing multiple installs. - - - + From b07c5982e115befaec91a3919dafc9e6ec467f24 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 29 May 2022 12:46:44 +0200 Subject: [PATCH 146/308] fix: set version for Windows binaries --- CMakeLists.txt | 2 ++ launcher/CMakeLists.txt | 2 +- program_info/CMakeLists.txt | 3 +++ program_info/{polymc.manifest => polymc.manifest.in} | 2 +- program_info/{polymc.rc => polymc.rc.in} | 6 +++--- 5 files changed, 10 insertions(+), 5 deletions(-) rename program_info/{polymc.manifest => polymc.manifest.in} (97%) rename program_info/{polymc.rc => polymc.rc.in} (76%) diff --git a/CMakeLists.txt b/CMakeLists.txt index fcc2512d8..31b2f23be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,6 +128,8 @@ message(STATUS "Git commit: ${Launcher_GIT_COMMIT}") message(STATUS "Git refspec: ${Launcher_GIT_REFSPEC}") set(Launcher_RELEASE_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}") +set(Launcher_RELEASE_VERSION_NAME4 "${Launcher_RELEASE_VERSION_NAME}.0") +set(Launcher_RELEASE_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},${Launcher_VERSION_HOTFIX},0") string(TIMESTAMP TODAY "%Y-%m-%d") set(Launcher_RELEASE_TIMESTAMP "${TODAY}") diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index b3af12a67..bbf801854 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -963,7 +963,7 @@ qt5_add_resources(LAUNCHER_RESOURCES ######## Windows resource files ######## if(WIN32) - set(LAUNCHER_RCS ../${Launcher_Branding_WindowsRC}) + set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC}) endif() # Add executable diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index 60549d8d3..2cbef1b61 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -21,3 +21,6 @@ set(Launcher_Portable_File "program_info/portable.txt" PARENT_SCOPE) configure_file(org.polymc.PolyMC.desktop.in org.polymc.PolyMC.desktop) configure_file(org.polymc.PolyMC.metainfo.xml.in org.polymc.PolyMC.metainfo.xml) +configure_file(polymc.rc.in polymc.rc @ONLY) +configure_file(polymc.manifest.in polymc.manifest @ONLY) +configure_file(polymc.ico polymc.ico COPYONLY) diff --git a/program_info/polymc.manifest b/program_info/polymc.manifest.in similarity index 97% rename from program_info/polymc.manifest rename to program_info/polymc.manifest.in index 8ca50acf4..0eefacacd 100644 --- a/program_info/polymc.manifest +++ b/program_info/polymc.manifest.in @@ -1,6 +1,6 @@ - + diff --git a/program_info/polymc.rc b/program_info/polymc.rc.in similarity index 76% rename from program_info/polymc.rc rename to program_info/polymc.rc.in index 011e944b9..0ea9b73a0 100644 --- a/program_info/polymc.rc +++ b/program_info/polymc.rc.in @@ -7,7 +7,7 @@ IDI_ICON1 ICON DISCARDABLE "polymc.ico" 1 RT_MANIFEST "polymc.manifest" VS_VERSION_INFO VERSIONINFO -FILEVERSION 1,0,0,0 +FILEVERSION @Launcher_RELEASE_VERSION_NAME4_COMMA@ FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_APP BEGIN @@ -17,9 +17,9 @@ BEGIN BEGIN VALUE "CompanyName", "MultiMC & PolyMC Contributors" VALUE "FileDescription", "PolyMC" - VALUE "FileVersion", "1.0.0.0" + VALUE "FileVersion", "@Launcher_RELEASE_VERSION_NAME4@" VALUE "ProductName", "PolyMC" - VALUE "ProductVersion", "1" + VALUE "ProductVersion", "@Launcher_RELEASE_VERSION_NAME4@" END END BLOCK "VarFileInfo" From 0b3115997a970b0507665703b9e70da8b3d22423 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 29 May 2022 14:16:13 +0200 Subject: [PATCH 147/308] fix: fix importing Flame/MMC packs --- launcher/InstanceImportTask.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 68a497cce..6df5a491b 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -164,14 +164,14 @@ void InstanceImportTask::processZipPack() QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg"); QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json"); - if (!mmcRoot.isEmpty()) + if (!mmcRoot.isNull()) { // process as MultiMC instance/pack qDebug() << "MultiMC:" << mmcRoot; root = mmcRoot; m_modpackType = ModpackType::MultiMC; } - else if(!flameRoot.isEmpty()) + else if(!flameRoot.isNull()) { // process as Flame pack qDebug() << "Flame:" << flameRoot; From 8e6c592ad9add4f8241c54a64a63ba1cc3750af1 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 29 May 2022 14:28:54 +0200 Subject: [PATCH 148/308] fix: add version to Legacy FTB packs --- launcher/ui/pages/modplatform/legacy_ftb/Page.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp index 27a12cda2..7667d1692 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp @@ -175,7 +175,7 @@ void Page::suggestCurrent() return; } - dialog->setSuggestedPack(selected.name, new PackInstallTask(APPLICATION->network(), selected, selectedVersion)); + dialog->setSuggestedPack(selected.name + " " + selectedVersion, new PackInstallTask(APPLICATION->network(), selected, selectedVersion)); QString editedLogoName; if(selected.logo.toLower().startsWith("ftb")) { From 20832682efc4205f350c27c1d8aa6681925c76e6 Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Sun, 29 May 2022 20:35:57 +0800 Subject: [PATCH 149/308] Update launcher/ui/pages/global/JavaPage.cpp Co-authored-by: Sefa Eyeoglu --- launcher/ui/pages/global/JavaPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index 88607e32d..025771e8b 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -166,7 +166,7 @@ void JavaPage::on_javaTestBtn_clicked() return; } checker.reset(new JavaCommon::TestCheck( - this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->toPlainText(), + this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->toPlainText().replace("\n", " "), ui->minMemSpinBox->value(), ui->maxMemSpinBox->value(), ui->permGenSpinBox->value())); connect(checker.get(), SIGNAL(finished()), SLOT(checkerFinished())); checker->run(); From ee00a5d8eef6c9239d7701554d065c0f5f8767c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20=C3=87al=C4=B1=C5=9Fkan?= Date: Sun, 29 May 2022 17:07:12 +0300 Subject: [PATCH 150/308] nix: make LTO optional --- nix/default.nix | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nix/default.nix b/nix/default.nix index cce40e638..969b455e9 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -11,6 +11,7 @@ , xorg , libpulseaudio , qtbase +, quazip , libGL , msaClientID ? "" @@ -18,6 +19,7 @@ , self , version , libnbtplusplus +, enableLTO ? false }: let @@ -57,9 +59,9 @@ stdenv.mkDerivation rec { cmakeFlags = [ "-GNinja" - "-DENABLE_LTO=on" "-DLauncher_QT_VERSION_MAJOR=${lib.versions.major qtbase.version}" - ] ++ lib.optionals (msaClientID != "") [ "-DLauncher_MSA_CLIENT_ID=${msaClientID}" ]; + ] ++ lib.optionals enableLTO [ "-DENABLE_LTO=on" ] + ++ lib.optionals (msaClientID != "") [ "-DLauncher_MSA_CLIENT_ID=${msaClientID}" ]; postInstall = '' # xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128 From adf1e1982a66959aa594bb0c43eba5428f88999d Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 29 May 2022 16:14:01 +0200 Subject: [PATCH 151/308] fix: remove unnecessary translation (#674) --- launcher/ui/dialogs/ScrollMessageBox.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/ScrollMessageBox.ui b/launcher/ui/dialogs/ScrollMessageBox.ui index 885fbfd25..299d2eccf 100644 --- a/launcher/ui/dialogs/ScrollMessageBox.ui +++ b/launcher/ui/dialogs/ScrollMessageBox.ui @@ -11,7 +11,7 @@ - ScrollMessageBox + ScrollMessageBox From 2746251dcd7ccbe83ca969c6a6b0f6e9c4d9160d Mon Sep 17 00:00:00 2001 From: timoreo Date: Sun, 29 May 2022 18:23:34 +0200 Subject: [PATCH 152/308] Fix modrinth search filters --- launcher/modplatform/ModAPI.h | 2 +- launcher/modplatform/modrinth/ModrinthAPI.h | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/launcher/modplatform/ModAPI.h b/launcher/modplatform/ModAPI.h index 4230df0bc..eb0de3f08 100644 --- a/launcher/modplatform/ModAPI.h +++ b/launcher/modplatform/ModAPI.h @@ -68,7 +68,7 @@ class ModAPI { { QString s; for(auto& ver : mcVersions){ - s += QString("%1,").arg(ver.toString()); + s += QString("\"%1\",").arg(ver.toString()); } s.remove(s.length() - 1, 1); //remove last comma return s; diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index 79bc5175a..6119a4dfb 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -79,11 +79,11 @@ class ModrinthAPI : public NetworkModAPI { { return QString(BuildConfig.MODRINTH_PROD_URL + "/project/%1/version?" - "game_versions=[%2]" + "game_versions=[%2]&" "loaders=[\"%3\"]") - .arg(args.addonId) - .arg(getGameVersionsString(args.mcVersions)) - .arg(getModLoaderStrings(args.loaders).join("\",\"")); + .arg(args.addonId, + getGameVersionsString(args.mcVersions), + getModLoaderStrings(args.loaders).join("\",\"")); }; auto getGameVersionsArray(std::list mcVersions) const -> QString From 8731c86d0deba2f8e624d41137b69abca5b85e8e Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Sun, 29 May 2022 17:37:45 -0400 Subject: [PATCH 153/308] Use CMake for Windows installer branding As a side effect, fixes an issue where the installer wrote the incorrect version to the registry. --- .github/workflows/build.yml | 2 +- CMakeLists.txt | 2 + program_info/CMakeLists.txt | 1 + .../{win_install.nsi => win_install.nsi.in} | 45 ++++++++++--------- 4 files changed, 28 insertions(+), 22 deletions(-) rename program_info/{win_install.nsi => win_install.nsi.in} (81%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6cbd5c21c..db7bd6533 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -206,7 +206,7 @@ jobs: shell: msys2 {0} run: | 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) if: runner.os == 'Linux' && matrix.appimage != true diff --git a/CMakeLists.txt b/CMakeLists.txt index 11d582130..4e9e2e5aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -260,6 +260,8 @@ elseif(WIN32) # install as bundle set(INSTALL_BUNDLE "full") + + configure_file(program_info/win_install.nsi.in program_info/win_install.nsi @ONLY) else() message(FATAL_ERROR "Platform not supported") endif() diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index 2cbef1b61..b2325b6fd 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -14,6 +14,7 @@ set(Launcher_MetaInfo "program_info/org.polymc.PolyMC.metainfo.xml" 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_Branding_ICNS "program_info/polymc.icns" PARENT_SCOPE) +set(Launcher_Branding_ICO "program_info/polymc.ico" PARENT_SCOPE) set(Launcher_Branding_WindowsRC "program_info/polymc.rc" PARENT_SCOPE) set(Launcher_Branding_LogoQRC "program_info/polymc.qrc" PARENT_SCOPE) diff --git a/program_info/win_install.nsi b/program_info/win_install.nsi.in similarity index 81% rename from program_info/win_install.nsi rename to program_info/win_install.nsi.in index cb4c8d1d4..d8b3e88f8 100644 --- a/program_info/win_install.nsi +++ b/program_info/win_install.nsi.in @@ -4,10 +4,13 @@ Unicode true -Name "PolyMC" -InstallDir "$LOCALAPPDATA\Programs\PolyMC" -InstallDirRegKey HKCU "Software\PolyMC" "InstallDir" +Name "@Launcher_Name@" +InstallDir "$LOCALAPPDATA\Programs\@Launcher_Name@" +InstallDirRegKey HKCU "Software\@Launcher_Name@" "InstallDir" RequestExecutionLevel user +OutFile "../@Launcher_Name@-Setup.exe" + +!define MUI_ICON "../@Launcher_Branding_ICO@" ;-------------------------------- @@ -18,7 +21,7 @@ RequestExecutionLevel user !insertmacro MUI_PAGE_COMPONENTS !insertmacro MUI_PAGE_DIRECTORY !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_UNPAGE_CONFIRM @@ -99,15 +102,15 @@ RequestExecutionLevel user ;-------------------------------- ; The stuff to install -Section "PolyMC" +Section "@Launcher_Name@" 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 - File "polymc.exe" + File "@Launcher_APP_BINARY_NAME@.exe" File "qt.conf" File *.dll File /r "iconengines" @@ -117,20 +120,20 @@ Section "PolyMC" File /r "styles" ; Write the installation path into the registry - WriteRegStr HKCU Software\PolyMC "InstallDir" "$INSTDIR" + WriteRegStr HKCU Software\@Launcher_Name@ "InstallDir" "$INSTDIR" ; Write the uninstall keys for Windows ${GetParameters} $R0 ${GetOptions} $R0 "/NoUninstaller" $R1 ${If} ${Errors} - !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\PolyMC" - WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "PolyMC" - WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\polymc.exe" + !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_Name@" + WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "@Launcher_Name@" + WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"' WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S' WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR" - WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "PolyMC Contributors" - WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "${VERSION}" + WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "@Launcher_Name@ Contributors" + WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "@Launcher_RELEASE_VERSION_NAME@" ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 IntFmt $0 "0x%08X" $0 WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0" @@ -143,13 +146,13 @@ SectionEnd Section "Start Menu Shortcut" SM_SHORTCUTS - CreateShortcut "$SMPROGRAMS\PolyMC.lnk" "$INSTDIR\polymc.exe" "" "$INSTDIR\polymc.exe" 0 + CreateShortcut "$SMPROGRAMS\@Launcher_Name@.lnk" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" 0 SectionEnd Section "Desktop Shortcut" DESKTOP_SHORTCUTS - CreateShortcut "$DESKTOP\PolyMC.lnk" "$INSTDIR\polymc.exe" "" "$INSTDIR\polymc.exe" 0 + CreateShortcut "$DESKTOP\@Launcher_Name@.lnk" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" 0 SectionEnd @@ -159,12 +162,12 @@ SectionEnd 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\PolyMC + DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_Name@" + DeleteRegKey HKCU SOFTWARE\@Launcher_Name@ - Delete $INSTDIR\polymc.exe + Delete $INSTDIR\@Launcher_APP_BINARY_NAME@.exe Delete $INSTDIR\uninstall.exe Delete $INSTDIR\portable.txt @@ -220,8 +223,8 @@ Section "Uninstall" RMDir /r $INSTDIR\platforms RMDir /r $INSTDIR\styles - Delete "$SMPROGRAMS\PolyMC.lnk" - Delete "$DESKTOP\PolyMC.lnk" + Delete "$SMPROGRAMS\@Launcher_Name@.lnk" + Delete "$DESKTOP\@Launcher_Name@.lnk" RMDir "$INSTDIR" From 9d8b95107da69cb0202824e6e5d7211b3a7e2830 Mon Sep 17 00:00:00 2001 From: Ilia Date: Mon, 30 May 2022 13:33:07 +0300 Subject: [PATCH 154/308] fix: do not show the "profile select" dialog if the user refused to add an account --- launcher/LaunchController.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 002c08b9c..d36ee3fed 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -105,6 +105,11 @@ void LaunchController::decideAccount() // Open the account manager. APPLICATION->ShowGlobalSettings(m_parentWidget, "accounts"); } + else if (reply == QMessageBox::No) + { + // Do not open "profile select" dialog. + return; + } } m_accountToUse = accounts->defaultAccount(); From 065e38c6aa4ebde6e256b02306758d645daf525e Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Mon, 30 May 2022 12:56:29 -0400 Subject: [PATCH 155/308] Move Windows installer configure to `program_info` --- CMakeLists.txt | 2 -- program_info/CMakeLists.txt | 4 +++- program_info/win_install.nsi.in | 30 +++++++++++++++--------------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e9e2e5aa..11d582130 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -260,8 +260,6 @@ elseif(WIN32) # install as bundle set(INSTALL_BUNDLE "full") - - configure_file(program_info/win_install.nsi.in program_info/win_install.nsi @ONLY) else() message(FATAL_ERROR "Platform not supported") endif() diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index b2325b6fd..1000be23d 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -14,7 +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_SVG "program_info/org.polymc.PolyMC.svg" PARENT_SCOPE) set(Launcher_Branding_ICNS "program_info/polymc.icns" PARENT_SCOPE) -set(Launcher_Branding_ICO "program_info/polymc.ico" 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_LogoQRC "program_info/polymc.qrc" PARENT_SCOPE) @@ -25,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.manifest.in polymc.manifest @ONLY) configure_file(polymc.ico polymc.ico COPYONLY) +configure_file(win_install.nsi.in win_install.nsi @ONLY) diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index d8b3e88f8..596e3b576 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -4,11 +4,11 @@ Unicode true -Name "@Launcher_Name@" -InstallDir "$LOCALAPPDATA\Programs\@Launcher_Name@" -InstallDirRegKey HKCU "Software\@Launcher_Name@" "InstallDir" +Name "@Launcher_CommonName@" +InstallDir "$LOCALAPPDATA\Programs\@Launcher_CommonName@" +InstallDirRegKey HKCU "Software\@Launcher_CommonName@" "InstallDir" RequestExecutionLevel user -OutFile "../@Launcher_Name@-Setup.exe" +OutFile "../@Launcher_CommonName@-Setup.exe" !define MUI_ICON "../@Launcher_Branding_ICO@" @@ -102,7 +102,7 @@ OutFile "../@Launcher_Name@-Setup.exe" ;-------------------------------- ; The stuff to install -Section "@Launcher_Name@" +Section "@Launcher_CommonName@" SectionIn RO @@ -120,19 +120,19 @@ Section "@Launcher_Name@" File /r "styles" ; Write the installation path into the registry - WriteRegStr HKCU Software\@Launcher_Name@ "InstallDir" "$INSTDIR" + WriteRegStr HKCU Software\@Launcher_CommonName@ "InstallDir" "$INSTDIR" ; Write the uninstall keys for Windows ${GetParameters} $R0 ${GetOptions} $R0 "/NoUninstaller" $R1 ${If} ${Errors} - !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_Name@" - WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "@Launcher_Name@" + !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_CommonName@" + WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "@Launcher_CommonName@" WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"' WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S' WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR" - WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "@Launcher_Name@ Contributors" + WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "@Launcher_CommonName@ Contributors" WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "@Launcher_RELEASE_VERSION_NAME@" ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 IntFmt $0 "0x%08X" $0 @@ -146,13 +146,13 @@ SectionEnd Section "Start Menu Shortcut" SM_SHORTCUTS - CreateShortcut "$SMPROGRAMS\@Launcher_Name@.lnk" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" 0 + CreateShortcut "$SMPROGRAMS\@Launcher_CommonName@.lnk" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" 0 SectionEnd Section "Desktop Shortcut" DESKTOP_SHORTCUTS - CreateShortcut "$DESKTOP\@Launcher_Name@.lnk" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" 0 + CreateShortcut "$DESKTOP\@Launcher_CommonName@.lnk" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" 0 SectionEnd @@ -164,8 +164,8 @@ Section "Uninstall" nsExec::Exec /TIMEOUT=2000 'TaskKill /IM @Launcher_APP_BINARY_NAME@.exe /F' - DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_Name@" - DeleteRegKey HKCU SOFTWARE\@Launcher_Name@ + DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_CommonName@" + DeleteRegKey HKCU SOFTWARE\@Launcher_CommonName@ Delete $INSTDIR\@Launcher_APP_BINARY_NAME@.exe Delete $INSTDIR\uninstall.exe @@ -223,8 +223,8 @@ Section "Uninstall" RMDir /r $INSTDIR\platforms RMDir /r $INSTDIR\styles - Delete "$SMPROGRAMS\@Launcher_Name@.lnk" - Delete "$DESKTOP\@Launcher_Name@.lnk" + Delete "$SMPROGRAMS\@Launcher_CommonName@.lnk" + Delete "$DESKTOP\@Launcher_CommonName@.lnk" RMDir "$INSTDIR" From 3585e4764b1bbd6e3e90d322f51ddb9d49a2ceec Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Mon, 30 May 2022 14:01:38 -0400 Subject: [PATCH 156/308] Add Quilt support for Technic modpacks --- .../technic/TechnicPackProcessor.cpp | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/launcher/modplatform/technic/TechnicPackProcessor.cpp b/launcher/modplatform/technic/TechnicPackProcessor.cpp index 782fb9b20..f50e5fb53 100644 --- a/launcher/modplatform/technic/TechnicPackProcessor.cpp +++ b/launcher/modplatform/technic/TechnicPackProcessor.cpp @@ -185,13 +185,22 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1, 1)); } } - else if (libraryName.startsWith("net.minecraftforge:minecraftforge:")) + else { - components->setComponentVersion("net.minecraftforge", libraryName.section(':', 2)); - } - else if (libraryName.startsWith("net.fabricmc:fabric-loader:")) - { - components->setComponentVersion("net.fabricmc.fabric-loader", libraryName.section(':', 2)); + static QSet possibleLoaders{ + "net.minecraftforge:minecraftforge:", + "net.fabricmc:fabric-loader:", + "org.quiltmc:quilt-loader:" + }; + for (const auto& loader : possibleLoaders) + { + if (libraryName.startsWith(loader)) + { + auto loaderComponent = loader.chopped(1).replace(":", "."); + components->setComponentVersion(loaderComponent, libraryName.section(':', 2)); + break; + } + } } } } From 7ac16ed0734168793dba4c09ed2e600cd6c92fee Mon Sep 17 00:00:00 2001 From: Kenneth Chew <79120643+kthchew@users.noreply.github.com> Date: Mon, 30 May 2022 14:40:20 -0400 Subject: [PATCH 157/308] Use `QStringList` instead of `QSet` Co-authored-by: flow --- launcher/modplatform/technic/TechnicPackProcessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/modplatform/technic/TechnicPackProcessor.cpp b/launcher/modplatform/technic/TechnicPackProcessor.cpp index f50e5fb53..471b4a2f4 100644 --- a/launcher/modplatform/technic/TechnicPackProcessor.cpp +++ b/launcher/modplatform/technic/TechnicPackProcessor.cpp @@ -187,7 +187,7 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const } else { - static QSet possibleLoaders{ + static QStringList possibleLoaders{ "net.minecraftforge:minecraftforge:", "net.fabricmc:fabric-loader:", "org.quiltmc:quilt-loader:" From 2999e69f0ff1a8e09d7ef625f73bb3559e181e69 Mon Sep 17 00:00:00 2001 From: LennyMcLennington Date: Mon, 30 May 2022 23:05:29 +0100 Subject: [PATCH 158/308] Change forking policy a bit Ask people forking PolyMC to make it clear that their fork is not endorsed by us. --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a08d5dc01..e4b9ebcd0 100644 --- a/README.md +++ b/README.md @@ -80,12 +80,17 @@ To modify download information or change packaging information send a pull reque ## Forking/Redistributing/Custom builds policy -Do whatever you want, we don't care. Just follow the license. If you have any questions about this feel free to ask in an issue. +We don't care what you do with your fork as long as you do the following as a basic courtesy: +- Follow the terms of the [license](LICENSE) (not just a courtesy, but also a legal responsibility) +- Make it clear that your fork is not PolyMC and is not endorsed by or affiliated with the PolyMC project (https://polymc.org). +- If you are distributing this fork, go through [CMakeLists.txt](CMakeLists.txt) and change PolyMC's API keys to your own or set them to empty strings (`""`) to disable them (this way the program will still compile but the functionality requiring to those keys will be disabled). + +If you have any questions or want any clarification on the above conditions please make an issue and ask us. Be aware that if you build this software without removing the provided API keys in [CMakeLists.txt](CMakeLists.txt) you are accepting the following terms and conditions: - [Microsoft Identity Platform Terms of Use](https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use) - [CurseForge 3rd Party API Terms and Conditions](https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions) -If you do not agree with these terms and conditions, then remove the associated API keys from the [CMakeLists.txt](CMakeLists.txt) file. +If you do not agree with these terms and conditions, then remove the associated API keys from the [CMakeLists.txt](CMakeLists.txt) file by setting them to an empty string (`""`). All launcher code is available under the GPL-3.0-only license. From 8ce8aadd9b7f9da5ef09e1e36e913f12928f3ca9 Mon Sep 17 00:00:00 2001 From: LennyMcLennington Date: Mon, 30 May 2022 23:50:35 +0100 Subject: [PATCH 159/308] fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e4b9ebcd0..bdbe5f8dc 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ To modify download information or change packaging information send a pull reque We don't care what you do with your fork as long as you do the following as a basic courtesy: - Follow the terms of the [license](LICENSE) (not just a courtesy, but also a legal responsibility) - Make it clear that your fork is not PolyMC and is not endorsed by or affiliated with the PolyMC project (https://polymc.org). -- If you are distributing this fork, go through [CMakeLists.txt](CMakeLists.txt) and change PolyMC's API keys to your own or set them to empty strings (`""`) to disable them (this way the program will still compile but the functionality requiring to those keys will be disabled). +- If you are distributing this fork, go through [CMakeLists.txt](CMakeLists.txt) and change PolyMC's API keys to your own or set them to empty strings (`""`) to disable them (this way the program will still compile but the functionality requiring those keys will be disabled). If you have any questions or want any clarification on the above conditions please make an issue and ask us. From 2727df704f19d34e2169b3899c2646917fc4a594 Mon Sep 17 00:00:00 2001 From: LennyMcLennington Date: Mon, 30 May 2022 23:52:12 +0100 Subject: [PATCH 160/308] change the wording a bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bdbe5f8dc..86c5dace3 100644 --- a/README.md +++ b/README.md @@ -80,10 +80,10 @@ To modify download information or change packaging information send a pull reque ## Forking/Redistributing/Custom builds policy -We don't care what you do with your fork as long as you do the following as a basic courtesy: +We don't care what you do with your fork/custom build as long as you do the following as a basic courtesy: - Follow the terms of the [license](LICENSE) (not just a courtesy, but also a legal responsibility) - Make it clear that your fork is not PolyMC and is not endorsed by or affiliated with the PolyMC project (https://polymc.org). -- If you are distributing this fork, go through [CMakeLists.txt](CMakeLists.txt) and change PolyMC's API keys to your own or set them to empty strings (`""`) to disable them (this way the program will still compile but the functionality requiring those keys will be disabled). +- Go through [CMakeLists.txt](CMakeLists.txt) and change PolyMC's API keys to your own or set them to empty strings (`""`) to disable them (this way the program will still compile but the functionality requiring those keys will be disabled). If you have any questions or want any clarification on the above conditions please make an issue and ask us. From 795075f90d411338d5b92723bccb790815202b0d Mon Sep 17 00:00:00 2001 From: LennyMcLennington Date: Mon, 30 May 2022 23:59:48 +0100 Subject: [PATCH 161/308] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 86c5dace3..a5cc154fe 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ If you have any questions or want any clarification on the above conditions plea Be aware that if you build this software without removing the provided API keys in [CMakeLists.txt](CMakeLists.txt) you are accepting the following terms and conditions: - [Microsoft Identity Platform Terms of Use](https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use) - [CurseForge 3rd Party API Terms and Conditions](https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions) + If you do not agree with these terms and conditions, then remove the associated API keys from the [CMakeLists.txt](CMakeLists.txt) file by setting them to an empty string (`""`). All launcher code is available under the GPL-3.0-only license. From 7d21bf15e88517eb3a6e8e4de712a827f87fde39 Mon Sep 17 00:00:00 2001 From: glowiak <52356948+glowiak@users.noreply.github.com> Date: Wed, 1 Jun 2022 15:50:02 +0200 Subject: [PATCH 162/308] Update UpdateController.cpp --- launcher/UpdateController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/UpdateController.cpp b/launcher/UpdateController.cpp index 646f8e573..c27fe7726 100644 --- a/launcher/UpdateController.cpp +++ b/launcher/UpdateController.cpp @@ -93,7 +93,7 @@ void UpdateController::installUpdates() qDebug() << "Installing updates."; #ifdef Q_OS_WIN 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); #elif defined Q_OS_MAC QString finishCmd = QApplication::applicationFilePath(); From 1a004f0c4d624453e4f477d025aa46a8a8d47ce4 Mon Sep 17 00:00:00 2001 From: glowiak <52356948+glowiak@users.noreply.github.com> Date: Wed, 1 Jun 2022 15:50:43 +0200 Subject: [PATCH 163/308] Update MCEditTool.cpp --- launcher/tools/MCEditTool.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/tools/MCEditTool.cpp b/launcher/tools/MCEditTool.cpp index 2c1ec613c..21e1a3b07 100644 --- a/launcher/tools/MCEditTool.cpp +++ b/launcher/tools/MCEditTool.cpp @@ -52,7 +52,7 @@ QString MCEditTool::getProgramPath() #else const QString mceditPath = path(); 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")) { return mceditDir.absoluteFilePath("mcedit.sh"); From 38ff76d2b89c156d6b3cd6f45e76670304820516 Mon Sep 17 00:00:00 2001 From: Technous285 Date: Thu, 2 Jun 2022 02:02:42 +1000 Subject: [PATCH 164/308] Add OpenBSD support Adds OpenBSD support. --- launcher/UpdateController.cpp | 2 +- launcher/tools/MCEditTool.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/UpdateController.cpp b/launcher/UpdateController.cpp index c27fe7726..646f8e573 100644 --- a/launcher/UpdateController.cpp +++ b/launcher/UpdateController.cpp @@ -93,7 +93,7 @@ void UpdateController::installUpdates() qDebug() << "Installing updates."; #ifdef Q_OS_WIN QString finishCmd = QApplication::applicationFilePath(); -#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) +#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined (Q_OS_OPENBSD) QString finishCmd = FS::PathCombine(m_root, BuildConfig.LAUNCHER_NAME); #elif defined Q_OS_MAC QString finishCmd = QApplication::applicationFilePath(); diff --git a/launcher/tools/MCEditTool.cpp b/launcher/tools/MCEditTool.cpp index 21e1a3b07..2c1ec613c 100644 --- a/launcher/tools/MCEditTool.cpp +++ b/launcher/tools/MCEditTool.cpp @@ -52,7 +52,7 @@ QString MCEditTool::getProgramPath() #else const QString mceditPath = path(); QDir mceditDir(mceditPath); -#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) +#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) if (mceditDir.exists("mcedit.sh")) { return mceditDir.absoluteFilePath("mcedit.sh"); From ca21b31696c548a9c03db8932c755030a7d5d116 Mon Sep 17 00:00:00 2001 From: jn64 <23169302+jn64@users.noreply.github.com> Date: Fri, 3 Jun 2022 09:58:57 +0800 Subject: [PATCH 165/308] Add "mc" keyword to desktop file Certain launchers (e.g. GNOME) only search from word boundaries, so typing "mc" doesn't currently match to "PolyMC". --- program_info/org.polymc.PolyMC.desktop.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/program_info/org.polymc.PolyMC.desktop.in b/program_info/org.polymc.PolyMC.desktop.in index 2d9e71038..e6d889092 100644 --- a/program_info/org.polymc.PolyMC.desktop.in +++ b/program_info/org.polymc.PolyMC.desktop.in @@ -8,5 +8,5 @@ Exec=@Launcher_APP_BINARY_NAME@ StartupNotify=true Icon=org.polymc.PolyMC Categories=Game; -Keywords=game;minecraft;launcher; +Keywords=game;minecraft;launcher;mc; StartupWMClass=PolyMC From cf4949b4f5a29757b3dd24cdca3a010f10e6dadb Mon Sep 17 00:00:00 2001 From: TheOPtimal <41379516+TheOPtimal@users.noreply.github.com> Date: Sat, 4 Jun 2022 05:26:46 +0400 Subject: [PATCH 166/308] Prepare for Nix 2.7 (#286) * Prepare for Nix 2.7 * Fix embarassing oopsie --- flake.nix | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/flake.nix b/flake.nix index f2247bede..b378fbb00 100644 --- a/flake.nix +++ b/flake.nix @@ -22,15 +22,17 @@ pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system}); in { - packages = forAllSystems (system: { + packages = forAllSystems (system: rec { polymc = pkgs.${system}.libsForQt5.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"; }; }); - defaultApp = forAllSystems (system: self.apps.${system}.polymc); + apps = forAllSystems (system: rec { polymc = { type = "app"; program = "${self.defaultPackage.${system}}/bin/polymc"; }; default = polymc; }); + defaultApp = forAllSystems (system: self.apps.${system}.default); overlay = final: prev: { polymc = self.defaultPackage.${final.system}; }; }; From 25ab121e42f624352bb4f32faa29e9e455328f09 Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Sat, 4 Jun 2022 15:33:17 +0800 Subject: [PATCH 167/308] feat: custom user-agent --- launcher/Application.cpp | 21 ++++++++++++++++ launcher/Application.h | 2 ++ launcher/net/Download.cpp | 2 +- launcher/net/PasteUpload.cpp | 4 +-- launcher/net/Upload.cpp | 2 +- launcher/screenshots/ImgurAlbumCreation.cpp | 2 +- launcher/screenshots/ImgurUpload.cpp | 3 ++- launcher/ui/pages/global/APIPage.cpp | 4 +++ launcher/ui/pages/global/APIPage.ui | 27 ++++++++++++++++++++- 9 files changed, 60 insertions(+), 7 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index ba4096b64..dd6f8ec65 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -708,6 +708,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // Custom MSA credentials m_settings->registerSetting("MSAClientIDOverride", ""); m_settings->registerSetting("CFKeyOverride", ""); + m_settings->registerSetting("UserAgentOverride", ""); // Init page provider { @@ -1553,3 +1554,23 @@ QString Application::getCurseKey() return BuildConfig.CURSEFORGE_API_KEY; } + +QString Application::getUserAgent() +{ + QString keyOverride = m_settings->get("UserAgentOverride").toString(); + if (!keyOverride.isEmpty()) { + return keyOverride; + } + + return BuildConfig.USER_AGENT; +} + +QString Application::getUserAgentUncached() +{ + QString keyOverride = m_settings->get("UserAgentOverride").toString(); + if (!keyOverride.isEmpty()) { + return keyOverride; + } + + return BuildConfig.USER_AGENT_UNCACHED; +} diff --git a/launcher/Application.h b/launcher/Application.h index 3129b4fb8..f440f4332 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -156,6 +156,8 @@ public: QString getMSAClientID(); QString getCurseKey(); + QString getUserAgent(); + QString getUserAgentUncached(); /// this is the root of the 'installation'. Used for automatic updates const QString &root() { diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index 966d4126e..d93eb0880 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -116,7 +116,7 @@ void Download::executeTask() return; } - request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT); + request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8()); if (request.url().host().contains("api.curseforge.com")) { request.setRawHeader("x-api-key", APPLICATION->getCurseKey().toUtf8()); }; diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index 3855190ab..ead5e1704 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -71,7 +71,7 @@ void PasteUpload::executeTask() QNetworkRequest request{QUrl(m_uploadUrl)}; QNetworkReply *rep{}; - request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); + request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8()); switch (m_pasteType) { case NullPointer: { @@ -91,7 +91,7 @@ void PasteUpload::executeTask() break; } case Hastebin: { - request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); + request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8()); rep = APPLICATION->network()->post(request, m_text); break; } diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index bbd273901..c9942a8dd 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.cpp @@ -173,7 +173,7 @@ namespace Net { return; } - request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT); + request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8()); if (request.url().host().contains("api.curseforge.com")) { request.setRawHeader("x-api-key", APPLICATION->getCurseKey().toUtf8()); } diff --git a/launcher/screenshots/ImgurAlbumCreation.cpp b/launcher/screenshots/ImgurAlbumCreation.cpp index 7afdc5ccf..04e26ea23 100644 --- a/launcher/screenshots/ImgurAlbumCreation.cpp +++ b/launcher/screenshots/ImgurAlbumCreation.cpp @@ -55,7 +55,7 @@ void ImgurAlbumCreation::executeTask() { m_state = State::Running; QNetworkRequest request(m_url); - request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); + request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8()); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); request.setRawHeader("Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toStdString().c_str()); request.setRawHeader("Accept", "application/json"); diff --git a/launcher/screenshots/ImgurUpload.cpp b/launcher/screenshots/ImgurUpload.cpp index fbcfb95f5..9aeb6fb80 100644 --- a/launcher/screenshots/ImgurUpload.cpp +++ b/launcher/screenshots/ImgurUpload.cpp @@ -35,6 +35,7 @@ #include "ImgurUpload.h" #include "BuildConfig.h" +#include "Application.h" #include #include @@ -56,7 +57,7 @@ void ImgurUpload::executeTask() finished = false; m_state = Task::State::Running; QNetworkRequest request(m_url); - request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); + request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8()); request.setRawHeader("Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toStdString().c_str()); request.setRawHeader("Accept", "application/json"); diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index 5d812d079..0c1d7ca26 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -78,6 +78,7 @@ APIPage::APIPage(QWidget *parent) : ui->tabWidget->tabBar()->hide(); ui->metaURL->setPlaceholderText(BuildConfig.META_URL); + ui->userAgentLineEdit->setPlaceholderText(BuildConfig.USER_AGENT); loadSettings(); @@ -139,6 +140,8 @@ void APIPage::loadSettings() ui->metaURL->setText(metaURL); QString curseKey = s->get("CFKeyOverride").toString(); ui->curseKey->setText(curseKey); + QString customUserAgent = s->get("UserAgentOverride").toString(); + ui->userAgentLineEdit->setText(customUserAgent); } void APIPage::applySettings() @@ -167,6 +170,7 @@ void APIPage::applySettings() s->set("MetaURLOverride", metaURL); QString curseKey = ui->curseKey->text(); s->set("CFKeyOverride", curseKey); + s->set("UserAgentOverride", ui->userAgentLineEdit->text()); } bool APIPage::apply() diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index 5c9273916..0981c700f 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -7,7 +7,7 @@ 0 0 800 - 600 + 702 @@ -220,6 +220,31 @@ + + + + + 0 + 0 + + + + User Agent + + + + + + + + + Enter a custom User Agent here. The special string ${launcher_version} will be replaced with the version of the launcher. + + + + + + From 778baa6dbe9a7710b86771262bbe435bdd6ee574 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 4 Jun 2022 11:59:12 +0200 Subject: [PATCH 168/308] fix: always store InstanceType --- launcher/BaseInstance.cpp | 2 +- launcher/InstanceList.cpp | 16 ++++++++++++++-- launcher/minecraft/MinecraftInstance.cpp | 2 ++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 2fb31d947..0240afa8d 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -59,7 +59,7 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s m_settings->registerSetting("lastLaunchTime", 0); m_settings->registerSetting("totalTimePlayed", 0); m_settings->registerSetting("lastTimePlayed", 0); - m_settings->registerSetting("InstanceType", "OneSix"); + m_settings->registerSetting("InstanceType", ""); // Custom Commands auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false); diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 847d897ef..3e3c81f71 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -547,8 +547,20 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id) auto instanceRoot = FS::PathCombine(m_instDir, id); auto instanceSettings = std::make_shared(FS::PathCombine(instanceRoot, "instance.cfg")); 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(); return inst; } diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 61326fac8..9ec4c17a6 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -168,6 +168,8 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO m_settings->registerOverride(globalSettings->getSetting("CloseAfterLaunch"), miscellaneousOverride); m_settings->registerOverride(globalSettings->getSetting("QuitAfterGameStop"), miscellaneousOverride); + m_settings->set("InstanceType", "OneSix"); + m_components.reset(new PackProfile(this)); } From c2a43c6f40d4fd24b5d7e237d13befec17fb3e6b Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 4 Jun 2022 11:01:10 -0300 Subject: [PATCH 169/308] fix: hide .index folder on Windows --- launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp index cbe165676..a3fcd9d9a 100644 --- a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp @@ -22,6 +22,10 @@ #include "FileSystem.h" #include "minecraft/mod/MetadataHandler.h" +#ifdef Q_OS_WIN32 +#include +#endif + LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version) : m_index_dir(index_dir), m_mod(mod), m_mod_version(mod_version) { @@ -29,6 +33,10 @@ LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& if (!FS::ensureFolderPathExists(index_dir.path())) { emitFailed(QString("Unable to create index for mod %1!").arg(m_mod.name)); } + +#ifdef Q_OS_WIN32 + SetFileAttributesA(index_dir.path().toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED); +#endif } void LocalModUpdateTask::executeTask() From 61d36c1723c947674ea7f329338a6b6636c871c3 Mon Sep 17 00:00:00 2001 From: circuit10 Date: Sat, 4 Jun 2022 15:20:49 +0100 Subject: [PATCH 170/308] Clarify the forking policy a bit more --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index a5cc154fe..dbf0c6336 100644 --- a/README.md +++ b/README.md @@ -80,8 +80,7 @@ To modify download information or change packaging information send a pull reque ## Forking/Redistributing/Custom builds policy -We don't care what you do with your fork/custom build as long as you do the following as a basic courtesy: -- Follow the terms of the [license](LICENSE) (not just a courtesy, but also a legal responsibility) +We don't care what you do with your fork/custom build as long as you follow the terms of the [license](LICENSE) (this is a legal responsibility), and if you made code changes rather than just packaging a custom build, please do the following as a basic courtesy: - Make it clear that your fork is not PolyMC and is not endorsed by or affiliated with the PolyMC project (https://polymc.org). - Go through [CMakeLists.txt](CMakeLists.txt) and change PolyMC's API keys to your own or set them to empty strings (`""`) to disable them (this way the program will still compile but the functionality requiring those keys will be disabled). From 5930acc41882222e746044b143aae99bd81c4afa Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Sat, 4 Jun 2022 22:54:05 +0800 Subject: [PATCH 171/308] change UI to scroll let me just say, this does not look right --- launcher/ui/pages/global/APIPage.ui | 462 +++++++++++++++------------- 1 file changed, 240 insertions(+), 222 deletions(-) diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index 0981c700f..eb8825b9a 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -7,7 +7,7 @@ 0 0 800 - 702 + 712 @@ -34,230 +34,248 @@ - - - &Pastebin Service + + + QFrame::NoFrame - - - - - Paste Service &Type - - - pasteTypeComboBox - - - - - - - - - - Base &URL - - - baseURLEntry - - - - - - - - - - true - - - - - - - Note: you probably want to change or clear the Base URL after changing the paste service type. - - - true - - - - + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + false + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + 0 + -73 + 772 + 724 + + + + + + + &Pastebin Service + + + + + + Paste Service &Type + + + pasteTypeComboBox + + + + + + + + + + Base &URL + + + baseURLEntry + + + + + + + + + + true + + + + + + + Note: you probably want to change or clear the Base URL after changing the paste service type. + + + true + + + + + + + + + + &Microsoft Authentication + + + + + + Note: you probably don't need to set this if logging in via Microsoft Authentication already works. + + + Qt::RichText + + + true + + + + + + + (Default) + + + + + + + Enter a custom client ID for Microsoft Authentication here. + + + Qt::RichText + + + true + + + true + + + + + + + + + + Meta&data Server + + + + + + You can set this to a third-party metadata server to use patched libraries or other hacks. + + + Qt::RichText + + + true + + + + + + + + + + + + + + Enter a custom URL for meta here. + + + Qt::RichText + + + true + + + true + + + + + + + + + + true + + + &CurseForge Core API + + + + + + Note: you probably don't need to set this if CurseForge already works. + + + + + + + true + + + (Default) + + + + + + + Enter a custom API Key for CurseForge here. + + + Qt::RichText + + + true + + + true + + + + + + + + + + + 0 + 0 + + + + User Agent + + + + + + + + + Enter a custom User Agent here. The special string ${launcher_version} will be replaced with the version of the launcher. + + + + + + + + - - - - &Microsoft Authentication - - - - - - Note: you probably don't need to set this if logging in via Microsoft Authentication already works. - - - Qt::RichText - - - true - - - - - - - (Default) - - - - - - - Enter a custom client ID for Microsoft Authentication here. - - - Qt::RichText - - - true - - - true - - - - - - - - - - Meta&data Server - - - - - - You can set this to a third-party metadata server to use patched libraries or other hacks. - - - Qt::RichText - - - true - - - - - - - - - - - - - - Enter a custom URL for meta here. - - - Qt::RichText - - - true - - - true - - - - - - - - - - true - - - &CurseForge Core API - - - - - - Note: you probably don't need to set this if CurseForge already works. - - - - - - - true - - - (Default) - - - - - - - Enter a custom API Key for CurseForge here. - - - Qt::RichText - - - true - - - true - - - - - - - - - - - 0 - 0 - - - - User Agent - - - - - - - - - Enter a custom User Agent here. The special string ${launcher_version} will be replaced with the version of the launcher. - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - From 4cecba8787d3c4e9e8d1a0234a1850747b501a2e Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Sat, 4 Jun 2022 22:59:57 +0800 Subject: [PATCH 172/308] make $LAUNCHER_VER actually work --- launcher/Application.cpp | 12 ++++++------ launcher/ui/pages/global/APIPage.ui | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index dd6f8ec65..7143b7670 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -1557,9 +1557,9 @@ QString Application::getCurseKey() QString Application::getUserAgent() { - QString keyOverride = m_settings->get("UserAgentOverride").toString(); - if (!keyOverride.isEmpty()) { - return keyOverride; + QString uaOverride = m_settings->get("UserAgentOverride").toString(); + if (!uaOverride.isEmpty()) { + return uaOverride.replace("$LAUNCHER_VER", BuildConfig.printableVersionString()); } return BuildConfig.USER_AGENT; @@ -1567,9 +1567,9 @@ QString Application::getUserAgent() QString Application::getUserAgentUncached() { - QString keyOverride = m_settings->get("UserAgentOverride").toString(); - if (!keyOverride.isEmpty()) { - return keyOverride; + QString uaOverride = m_settings->get("UserAgentOverride").toString(); + if (!uaOverride.isEmpty()) { + return uaOverride.replace("$LAUNCHER_VER", BuildConfig.printableVersionString()); } return BuildConfig.USER_AGENT_UNCACHED; diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index eb8825b9a..9524424eb 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -265,7 +265,7 @@ - Enter a custom User Agent here. The special string ${launcher_version} will be replaced with the version of the launcher. + Enter a custom User Agent here. The special string $LAUNCHER_VER will be replaced with the version of the launcher. From 91b85f99190621baf4da28b6c9050becb5767041 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Sat, 4 Jun 2022 17:09:11 +0200 Subject: [PATCH 173/308] Revert "Merge pull request #315 from txtsd/display_scaling" This reverts commit fcf728f3b5f4923cc05edfeb45f8340f420669cf. --- launcher/main.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/launcher/main.cpp b/launcher/main.cpp index 85c5fdeee..3d25b4ffe 100644 --- a/launcher/main.cpp +++ b/launcher/main.cpp @@ -27,10 +27,6 @@ int main(int argc, char *argv[]) QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) - QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); -#endif - // initialize Qt Application app(argc, argv); From fcd56dddc2de6c7f5056f3ceb8166f4a2705dae7 Mon Sep 17 00:00:00 2001 From: RaptaG <77157639+RaptaG@users.noreply.github.com> Date: Sat, 4 Jun 2022 22:08:35 +0300 Subject: [PATCH 174/308] Capitalization fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a5cc154fe..3dbc19c18 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ If there are any issues with the space or you are using a client that does not s [![Support](https://img.shields.io/matrix/polymc-support:matrix.org?label=PolyMC%20Support)](https://matrix.to/#/#polymc-support:matrix.org) [![Voice](https://img.shields.io/matrix/polymc-voice:matrix.org?label=PolyMC%20Voice)](https://matrix.to/#/#polymc-voice:matrix.org) -we also have a subreddit you can post your issues and suggestions on: +We also have a subreddit you can post your issues and suggestions on: [r/PolyMCLauncher](https://www.reddit.com/r/PolyMCLauncher/) From 7a3acc324979704e69a815bfe307aa054d4db8a3 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 4 Jun 2022 22:04:30 +0200 Subject: [PATCH 175/308] refactor(ui): use tabs for APIPage --- launcher/ui/pages/global/APIPage.cpp | 1 - launcher/ui/pages/global/APIPage.ui | 504 ++++++++++++++------------- 2 files changed, 263 insertions(+), 242 deletions(-) diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index 0c1d7ca26..b889e6f7c 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -75,7 +75,6 @@ APIPage::APIPage(QWidget *parent) : // This function needs to be called even when the ComboBox's index is still in its default state. updateBaseURLPlaceholder(ui->pasteTypeComboBox->currentIndex()); ui->baseURLEntry->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->baseURLEntry)); - ui->tabWidget->tabBar()->hide(); ui->metaURL->setPlaceholderText(BuildConfig.META_URL); ui->userAgentLineEdit->setPlaceholderText(BuildConfig.USER_AGENT); diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index 9524424eb..5327771c5 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -7,7 +7,7 @@ 0 0 800 - 712 + 600 @@ -30,252 +30,274 @@ - Tab 1 + Services - - - QFrame::NoFrame + + + &Pastebin Service - - QFrame::Plain - - - Qt::ScrollBarAlwaysOff - - - false - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - 0 - -73 - 772 - 724 - - - - - - - &Pastebin Service - - - - - - Paste Service &Type - - - pasteTypeComboBox - - - - - - - - - - Base &URL - - - baseURLEntry - - - - - - - - - - true - - - - - - - Note: you probably want to change or clear the Base URL after changing the paste service type. - - - true - - - - - - - - - - &Microsoft Authentication - - - - - - Note: you probably don't need to set this if logging in via Microsoft Authentication already works. - - - Qt::RichText - - - true - - - - - - - (Default) - - - - - - - Enter a custom client ID for Microsoft Authentication here. - - - Qt::RichText - - - true - - - true - - - - - - - - - - Meta&data Server - - - - - - You can set this to a third-party metadata server to use patched libraries or other hacks. - - - Qt::RichText - - - true - - - - - - - - - - - - - - Enter a custom URL for meta here. - - - Qt::RichText - - - true - - - true - - - - - - - - - - true - - - &CurseForge Core API - - - - - - Note: you probably don't need to set this if CurseForge already works. - - - - - - - true - - - (Default) - - - - - - - Enter a custom API Key for CurseForge here. - - - Qt::RichText - - - true - - - true - - - - - - - - - - - 0 - 0 - - - - User Agent - - - - - - - - - Enter a custom User Agent here. The special string $LAUNCHER_VER will be replaced with the version of the launcher. - - - - - - - - + + + + + Paste Service &Type + + + pasteTypeComboBox + + + + + + + + + + Base &URL + + + baseURLEntry + + + + + + + + + + true + + + + + + + Note: you probably want to change or clear the Base URL after changing the paste service type. + + + true + + + + + + + + Meta&data Server + + + + + + You can set this to a third-party metadata server to use patched libraries or other hacks. + + + Qt::RichText + + + true + + + + + + + + + + + + + + Enter a custom URL for meta here. + + + Qt::RichText + + + true + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + API Keys + + + + + + &Microsoft Authentication + + + + + + Note: you probably don't need to set this if logging in via Microsoft Authentication already works. + + + Qt::RichText + + + true + + + + + + + (Default) + + + + + + + Enter a custom client ID for Microsoft Authentication here. + + + Qt::RichText + + + true + + + true + + + + + + + + + + true + + + &CurseForge Core API + + + + + + Note: you probably don't need to set this if CurseForge already works. + + + + + + + Enter a custom API Key for CurseForge here. + + + Qt::RichText + + + true + + + true + + + + + + + true + + + (Default) + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Miscellaneous + + + + + + + 0 + 0 + + + + User Agent + + + + + + + + + Enter a custom User Agent here. The special string $LAUNCHER_VER will be replaced with the version of the launcher. + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + From cd49406bfec2d019cd9533f7a020107e551e7d61 Mon Sep 17 00:00:00 2001 From: icelimetea Date: Sun, 5 Jun 2022 01:18:59 +0100 Subject: [PATCH 176/308] Fix launching process for some legacy Forge versions --- .../launcher/net/minecraft/Launcher.java | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/libraries/launcher/net/minecraft/Launcher.java b/libraries/launcher/net/minecraft/Launcher.java index 265fa66ac..0b4d1c5c0 100644 --- a/libraries/launcher/net/minecraft/Launcher.java +++ b/libraries/launcher/net/minecraft/Launcher.java @@ -28,11 +28,15 @@ public final class Launcher extends Applet implements AppletStub { private final Map params = new TreeMap<>(); - private final Applet wrappedApplet; + private Applet wrappedApplet; private boolean active = false; public Launcher(Applet applet) { + this(applet, null); + } + + public Launcher(Applet applet, URL documentBase) { this.setLayout(new BorderLayout()); this.add(applet, "Center"); @@ -40,8 +44,25 @@ public final class Launcher extends Applet implements AppletStub { this.wrappedApplet = applet; } - 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); } From dd6d8e000238bdf9fad76cbebb787bf70546201d Mon Sep 17 00:00:00 2001 From: icelimetea Date: Sun, 5 Jun 2022 02:43:14 +0100 Subject: [PATCH 177/308] Make Launcher class to look more like original --- .../launcher/net/minecraft/Launcher.java | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/libraries/launcher/net/minecraft/Launcher.java b/libraries/launcher/net/minecraft/Launcher.java index 0b4d1c5c0..6bf671bec 100644 --- a/libraries/launcher/net/minecraft/Launcher.java +++ b/libraries/launcher/net/minecraft/Launcher.java @@ -24,12 +24,18 @@ import java.net.URL; import java.util.Map; 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 { private final Map params = new TreeMap<>(); private Applet wrappedApplet; + private URL documentBase; + private boolean active = false; public Launcher(Applet applet) { @@ -42,6 +48,20 @@ public final class Launcher extends Applet implements AppletStub { this.add(applet, "Center"); 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 replace(Applet applet) { @@ -75,7 +95,7 @@ public final class Launcher extends Applet implements AppletStub { try { return super.getParameter(name); - } catch (Exception ignore) {} + } catch (Exception ignored) {} return null; } @@ -129,25 +149,13 @@ public final class Launcher extends Applet implements AppletStub { try { return new URL("http://www.minecraft.net/game/"); } catch (MalformedURLException e) { - e.printStackTrace(); + throw new RuntimeException(e); } - - return null; } @Override public URL getDocumentBase() { - try { - // 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; + return documentBase; } @Override From 757fa1410cb6d065b2c26092b47dbe61f8c6d480 Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Sun, 5 Jun 2022 23:52:21 +0800 Subject: [PATCH 178/308] Update launcher/Application.cpp Co-authored-by: Sefa Eyeoglu --- launcher/Application.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 7143b7670..542b4d142 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -1569,6 +1569,7 @@ QString Application::getUserAgentUncached() { QString uaOverride = m_settings->get("UserAgentOverride").toString(); if (!uaOverride.isEmpty()) { + uaOverride += " (Uncached)"; return uaOverride.replace("$LAUNCHER_VER", BuildConfig.printableVersionString()); } From 6587e399923318ac7130d565fc4fb6b9ace6892b Mon Sep 17 00:00:00 2001 From: Zetvue <87939327+Zetvue@users.noreply.github.com> Date: Sun, 5 Jun 2022 18:05:09 -0400 Subject: [PATCH 179/308] Resize PolyMC logo --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3dbc19c18..0ed1f8832 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ -

- PolyMC logo - PolyMC logo +

+PolyMC logo +PolyMC logo

-
PolyMC is a custom launcher for Minecraft that focuses on predictability, long term stability and simplicity. From 843c860d98dab5a438374ab136b28d409184ec81 Mon Sep 17 00:00:00 2001 From: OldWorldOrdr Date: Mon, 6 Jun 2022 13:52:50 -0400 Subject: [PATCH 180/308] Update bug_report.yml --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index b387f46a2..f07698c50 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -27,7 +27,7 @@ body: attributes: label: Version of PolyMC description: The version of PolyMC used in the bug report. - placeholder: PolyMC 1.2.2 + placeholder: PolyMC 1.3.1 validations: required: true - type: textarea From 1c60e9b4fcaeae505232aa6287d76a2567d6ea1d Mon Sep 17 00:00:00 2001 From: MrMelon Date: Mon, 6 Jun 2022 18:12:50 +0100 Subject: [PATCH 181/308] Add initial sorting function --- launcher/icons/IconList.cpp | 28 ++++++++++++++++++++++++++++ launcher/icons/IconList.h | 1 + 2 files changed, 29 insertions(+) diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp index 0ddfae556..e0debcb09 100644 --- a/launcher/icons/IconList.cpp +++ b/launcher/icons/IconList.cpp @@ -56,6 +56,32 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren emit iconUpdated({}); } +void IconList::sortIconList() +{ + qDebug() << "Sorting icon list..."; + + QVector newIcons = QVector(); + QVectorIterator iconIter(icons); + +iconLoop: + while(iconIter.hasNext()) + { + MMCIcon a = iconIter.next(); + for(int i=0;i Date: Mon, 6 Jun 2022 22:18:19 +0100 Subject: [PATCH 182/308] Simplify sorting logic to a single std::sort call --- launcher/icons/IconList.cpp | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp index e0debcb09..522b39a79 100644 --- a/launcher/icons/IconList.cpp +++ b/launcher/icons/IconList.cpp @@ -59,26 +59,9 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren void IconList::sortIconList() { qDebug() << "Sorting icon list..."; - - QVector newIcons = QVector(); - QVectorIterator iconIter(icons); - -iconLoop: - while(iconIter.hasNext()) - { - MMCIcon a = iconIter.next(); - for(int i=0;i Date: Mon, 6 Jun 2022 22:13:10 -0400 Subject: [PATCH 183/308] nix: add package argument for extra jdks --- nix/default.nix | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nix/default.nix b/nix/default.nix index 969b455e9..d6aa370c5 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -14,6 +14,7 @@ , quazip , libGL , msaClientID ? "" +, extraJDKs ? [ ] # flake , self @@ -36,6 +37,8 @@ let # This variable will be passed to Minecraft by PolyMC gameLibraryPath = libpath + ":/run/opengl-driver/lib"; + + javaPaths = lib.makeSearchPath "bin/java" ([ jdk jdk8 ] ++ extraJDKs); in 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 wrapQtApp $out/bin/polymc \ --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 ]} ''; From cd0d8a76c5aa3db14f4947fc16125569cab64c65 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 6 Jun 2022 21:00:10 +0200 Subject: [PATCH 184/308] chore: add sponsors to README --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 3dbc19c18..1e4e5caff 100644 --- a/README.md +++ b/README.md @@ -96,3 +96,16 @@ If you do not agree with these terms and conditions, then remove the associated All launcher code is available under the GPL-3.0-only license. The logo and related assets are under the CC BY-SA 4.0 license. + +## Sponsors +Thank you to all our generous backers over at Open Collective! Support PolyMC by [becoming a backer](https://opencollective.com/polymc). + +[![OpenCollective Backers](https://opencollective.com/polymc/backers.svg?width=890&limit=1000)](https://opencollective.com/polymc#backers) + +Also, thanks to JetBrains for providing us a few licenses for all their products, as part of their [Open Source program](https://www.jetbrains.com/opensource/). + +[![JetBrains](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://www.jetbrains.com/opensource/) + +Additionally, thanks to the awesome people over at [MacStadium](https://www.macstadium.com/), for providing M1-Macs for development purposes! + +Powered by MacStadium From 1d9797660b70f6bd4026403a738c1d08fd3bba5d Mon Sep 17 00:00:00 2001 From: MrMelon Date: Tue, 7 Jun 2022 15:27:57 +0100 Subject: [PATCH 185/308] QString::locateAwareCompare() is better for human-like sorting --- launcher/icons/IconList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp index 522b39a79..d426aa804 100644 --- a/launcher/icons/IconList.cpp +++ b/launcher/icons/IconList.cpp @@ -60,7 +60,7 @@ void IconList::sortIconList() { qDebug() << "Sorting icon list..."; std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) { - return a.m_key.compare(b.m_key) < 0; + return a.m_key.localeAwareCompare(b.m_key) < 0; }); reindex(); } From 7f8aa2099c1872c471b3e7fcc3fca9e369167b42 Mon Sep 17 00:00:00 2001 From: Zetvue <87939327+Zetvue@users.noreply.github.com> Date: Tue, 7 Jun 2022 18:58:02 -0400 Subject: [PATCH 186/308] Make it responsive --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0ed1f8832..fee0011e2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-PolyMC logo -PolyMC logo +PolyMC logo +PolyMC logo

PolyMC is a custom launcher for Minecraft that focuses on predictability, long term stability and simplicity. From 6ee5ee496ed6eb2a62efc6d76b94d2613ef054c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20=C3=87al=C4=B1=C5=9Fkan?= Date: Wed, 8 Jun 2022 14:32:08 +0300 Subject: [PATCH 187/308] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:nixos/nixpkgs/41cc1d5d9584103be4108c1815c350e07c807036' (2022-05-23) → 'github:nixos/nixpkgs/43ecbe7840d155fa933ee8a500fb00dbbc651fc8' (2022-06-08) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index ccdd51da6..120b11c5c 100644 --- a/flake.lock +++ b/flake.lock @@ -34,11 +34,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1653326962, - "narHash": "sha256-W8feCYqKTsMre4nAEpv5Kx1PVFC+hao/LwqtB2Wci/8=", + "lastModified": 1654665288, + "narHash": "sha256-7blJpfoZEu7GKb84uh3io/5eSJNdaagXD9d15P9iQMs=", "owner": "nixos", "repo": "nixpkgs", - "rev": "41cc1d5d9584103be4108c1815c350e07c807036", + "rev": "43ecbe7840d155fa933ee8a500fb00dbbc651fc8", "type": "github" }, "original": { From 0c8ca1b3c0a64a2aae8b751cef64ae1071e046e0 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 8 Jun 2022 21:04:27 +0200 Subject: [PATCH 188/308] fix: remove debug CXX flags --- CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 11d582130..a0ae0a4f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 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}") +set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}") option(ENABLE_LTO "Enable Link Time Optimization" off) From f2ed4a92e278d70c8399bbe4a2ff8df2b333cae3 Mon Sep 17 00:00:00 2001 From: Zetvue <87939327+Zetvue@users.noreply.github.com> Date: Wed, 8 Jun 2022 15:55:28 -0400 Subject: [PATCH 189/308] Update README.md Co-authored-by: Tatsuya Noda --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fee0011e2..e3aa9b4df 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-PolyMC logo -PolyMC logo +PolyMC logo +PolyMC logo

PolyMC is a custom launcher for Minecraft that focuses on predictability, long term stability and simplicity. From 1b1f728c589a51db77eb711b4840307b897e3e67 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 9 Jun 2022 18:46:19 -0300 Subject: [PATCH 190/308] fix: allow opening external links in technic modpack page --- launcher/ui/pages/modplatform/technic/TechnicPage.ui | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.ui b/launcher/ui/pages/modplatform/technic/TechnicPage.ui index ca6a9b7ee..15bf645fb 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.ui +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.ui @@ -60,7 +60,11 @@ - + + + true + +
From 46e403b20b8f14269aebc163f2bc481d3dea43c5 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 9 Jun 2022 19:53:29 -0300 Subject: [PATCH 191/308] fix: properly parse mrpacks without the 'env' field It's optional, so some files may not have it (like most of FO). --- launcher/InstanceImportTask.cpp | 35 +++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 09c2a333f..73f05d442 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -582,6 +582,7 @@ void InstanceImportTask::processMultiMC() emitSucceeded(); } +// https://docs.modrinth.com/docs/modpacks/format_definition/ void InstanceImportTask::processModrinth() { std::vector files; @@ -600,26 +601,30 @@ void InstanceImportTask::processModrinth() auto jsonFiles = Json::requireIsArrayOf(obj, "files", "modrinth.index.json"); bool had_optional = false; - for (auto& modInfo : jsonFiles) { + for (auto modInfo : jsonFiles) { Modrinth::File file; file.path = Json::requireString(modInfo, "path"); auto env = Json::ensureObject(modInfo, "env"); - QString support = Json::ensureString(env, "client", "unsupported"); - if (support == "unsupported") { - continue; - } else if (support == "optional") { - // TODO: Make a review dialog for choosing which ones the user wants! - if (!had_optional) { - had_optional = true; - auto info = CustomMessageBox::selectable( - 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(); - } + // 'env' field is optional + if (!env.isEmpty()) { + QString support = Json::ensureString(env, "client", "unsupported"); + if (support == "unsupported") { + continue; + } else if (support == "optional") { + // TODO: Make a review dialog for choosing which ones the user wants! + if (!had_optional) { + had_optional = true; + auto info = CustomMessageBox::selectable( + 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")) - file.path += ".disabled"; + if (file.path.endsWith(".jar")) + file.path += ".disabled"; + } } QJsonObject hashes = Json::requireObject(modInfo, "hashes"); From 1b878030aaba832ab416786c5f0dbc69da0e2166 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 9 Jun 2022 19:54:50 -0300 Subject: [PATCH 192/308] fix: enable using more than one download url in mrpacks Kinda, it's ugly and hackish, since we don't have the facilities to do this properly (yet!) --- launcher/InstanceImportTask.cpp | 52 ++++++++++++++----- .../modrinth/ModrinthPackManifest.h | 7 ++- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 73f05d442..74991e36f 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -645,18 +645,32 @@ void InstanceImportTask::processModrinth() } file.hash = QByteArray::fromHex(hash.toLatin1()); file.hashAlgorithm = hashAlgorithm; + // Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode // (as Modrinth seems to incorrectly handle spaces) + + auto download_arr = Json::ensureArray(modInfo, "downloads"); + for(auto download : download_arr) { + qWarning() << download.toString(); + bool is_last = download.toString() == download_arr.last().toString(); - file.download = Json::requireString(Json::ensureArray(modInfo, "downloads").first(), "Download URL for " + file.path); + auto download_url = QUrl(download.toString()); - if (!file.download.isValid()) { - 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)); - } - else if (!Modrinth::validateDownloadUrl(file.download)) { - qDebug() << QString("Download URL (%1) for %2 is from a non-whitelisted by Modrinth domain").arg(file.download.toString(), file.path); - non_whitelisted_files.push_back(file); + if (!download_url.isValid()) { + qDebug() << QString("Download URL (%1) for %2 is not a correctly formatted URL") + .arg(download_url.toString(), file.path); + if(is_last && file.downloads.isEmpty()) + throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path)); + } + else { + if (!Modrinth::validateDownloadUrl(download_url)) { + qDebug() << QString("Download URL (%1) for %2 is from a non-whitelisted by Modrinth domain").arg(download_url.toString(), file.path); + if(is_last && file.downloads.isEmpty()) + non_whitelisted_files.push_back(file); + } + + file.downloads.push_back(download_url); + } } files.push_back(file); @@ -665,7 +679,10 @@ void InstanceImportTask::processModrinth() if (!non_whitelisted_files.empty()) { QString text; for (const auto& file : non_whitelisted_files) { - text += tr("Filepath: %1
URL: %2
").arg(file.path, file.download.toString()); + text += tr("Filepath: %1
").arg(file.path); + for(auto d : file.downloads) + text += tr("URL:") + QString("%2").arg(d.toString()); + text += "
"; } auto message_dialog = new ScrollMessageBox(m_parent, tr("Non-whitelisted mods found"), @@ -740,13 +757,24 @@ void InstanceImportTask::processModrinth() instance.saveNow(); 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); - qDebug() << "Will download" << file.download << "to" << path; - auto dl = Net::Download::makeFile(file.download, path); + qDebug() << "Will try to download" << file.downloads.front() << "to" << path; + auto dl = Net::Download::makeFile(file.downloads.front(), path); dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); m_filesNetJob->addNetAction(dl); + + if (file.downloads.size() > 1) { + // 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, [&]() { diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.h b/launcher/modplatform/modrinth/ModrinthPackManifest.h index e5fc9a700..b2083f57b 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.h +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.h @@ -40,6 +40,7 @@ #include #include +#include #include #include #include @@ -48,14 +49,12 @@ class MinecraftInstance; namespace Modrinth { -struct File -{ +struct File { QString path; QCryptographicHash::Algorithm hashAlgorithm; QByteArray hash; - // TODO: should this support multiple download URLs, like the JSON does? - QUrl download; + QQueue downloads; }; struct ModpackExtra { From b3c8f9d508b9110c13b3abf0f54d3f4927292559 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 9 Jun 2022 19:57:51 -0300 Subject: [PATCH 193/308] revert: don't check modrinth whitelisted hosts people didn't seem to like it, and its not required --- launcher/InstanceImportTask.cpp | 27 ------------------- .../modrinth/ModrinthPackManifest.cpp | 16 ----------- 2 files changed, 43 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 74991e36f..1ccf7ffc0 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -586,7 +586,6 @@ void InstanceImportTask::processMultiMC() void InstanceImportTask::processModrinth() { std::vector files; - std::vector non_whitelisted_files; QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion; try { QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); @@ -663,12 +662,6 @@ void InstanceImportTask::processModrinth() throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path)); } else { - if (!Modrinth::validateDownloadUrl(download_url)) { - qDebug() << QString("Download URL (%1) for %2 is from a non-whitelisted by Modrinth domain").arg(download_url.toString(), file.path); - if(is_last && file.downloads.isEmpty()) - non_whitelisted_files.push_back(file); - } - file.downloads.push_back(download_url); } } @@ -676,26 +669,6 @@ void InstanceImportTask::processModrinth() files.push_back(file); } - if (!non_whitelisted_files.empty()) { - QString text; - for (const auto& file : non_whitelisted_files) { - text += tr("Filepath: %1
").arg(file.path); - for(auto d : file.downloads) - text += tr("URL:") + QString("%2").arg(d.toString()); - text += "
"; - } - - 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"); for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) { QString name = it.key(); diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp index 33116231c..cc12f62fd 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp @@ -95,19 +95,6 @@ void loadIndexedVersions(Modpack& pack, QJsonDocument& doc) pack.versionsLoaded = true; } -auto validateDownloadUrl(QUrl url) -> bool -{ - static QSet domainWhitelist{ - "cdn.modrinth.com", - "github.com", - "raw.githubusercontent.com", - "gitlab.com" - }; - - auto domain = url.host(); - return domainWhitelist.contains(domain); -} - auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion { ModpackVersion file; @@ -137,9 +124,6 @@ auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion auto url = Json::requireString(parent, "url"); - if(!validateDownloadUrl(url)) - continue; - file.download_url = url; if(is_primary) break; From 4a261cac1a71b0817ed9693da3b16796ada8f348 Mon Sep 17 00:00:00 2001 From: Vance <40771709+vancez@users.noreply.github.com> Date: Fri, 10 Jun 2022 10:25:13 +0800 Subject: [PATCH 194/308] fix: update toolbar when instance state changes --- launcher/ui/MainWindow.cpp | 11 +++++++++++ launcher/ui/MainWindow.h | 2 ++ 2 files changed, 13 insertions(+) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 7e152b96b..c5a4cafe3 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -2101,6 +2101,9 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & selectionBad(); return; } + if (m_selectedInstance) { + disconnect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::on_InstanceState_changed); + } QString id = current.data(InstanceList::InstanceIDRole).toString(); m_selectedInstance = APPLICATION->instances()->getInstanceById(id); if (m_selectedInstance) @@ -2127,6 +2130,8 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & updateToolsMenu(); APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id()); + + connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::on_InstanceState_changed); } else { @@ -2216,3 +2221,9 @@ void MainWindow::updateStatusCenter() m_statusCenter->setText(tr("Total playtime: %1").arg(Time::prettifyDuration(timePlayed))); } } + +void MainWindow::on_InstanceState_changed(bool running) +{ + auto current = view->selectionModel()->currentIndex(); + instanceChanged(current, current); +} diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 2032acbaf..bd16246b1 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -192,6 +192,8 @@ private slots: void keyReleaseEvent(QKeyEvent *event) override; #endif + void on_InstanceState_changed(bool running); + private: void retranslateUi(); From 529fb07b4200b5dada2a8eec2953b29fc535ec7d Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Fri, 10 Jun 2022 15:18:47 +0800 Subject: [PATCH 195/308] I changed my mind --- launcher/Application.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index ff0f21296..29088c396 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -1306,10 +1306,6 @@ void Application::subRunningInstance() bool Application::shouldExitNow() const { -#ifdef Q_OS_MACOS - return false; -#endif - return m_runningInstances == 0 && m_openWindows == 0; } From fa5b1d99786567a6a183513df6fdf63d1e667bc5 Mon Sep 17 00:00:00 2001 From: Vance <40771709+vancez@users.noreply.github.com> Date: Fri, 10 Jun 2022 15:48:18 +0800 Subject: [PATCH 196/308] change slot name --- launcher/ui/MainWindow.cpp | 6 +++--- launcher/ui/MainWindow.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index c5a4cafe3..0a5f20007 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -2102,7 +2102,7 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & return; } if (m_selectedInstance) { - disconnect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::on_InstanceState_changed); + disconnect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance); } QString id = current.data(InstanceList::InstanceIDRole).toString(); m_selectedInstance = APPLICATION->instances()->getInstanceById(id); @@ -2131,7 +2131,7 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id()); - connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::on_InstanceState_changed); + connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance); } else { @@ -2222,7 +2222,7 @@ void MainWindow::updateStatusCenter() } } -void MainWindow::on_InstanceState_changed(bool running) +void MainWindow::refreshCurrentInstance(bool running) { auto current = view->selectionModel()->currentIndex(); instanceChanged(current, current); diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index bd16246b1..61a75c450 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -192,7 +192,7 @@ private slots: void keyReleaseEvent(QKeyEvent *event) override; #endif - void on_InstanceState_changed(bool running); + void refreshCurrentInstance(bool running); private: void retranslateUi(); From 8a2e8ad953d33965a2f50ae28fa68701f7461bf8 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 11 Jun 2022 10:48:56 +0200 Subject: [PATCH 197/308] feat: track real CPU architecture for instances --- launcher/Application.cpp | 1 + launcher/launch/steps/CheckJava.cpp | 16 +++++++++++----- launcher/launch/steps/CheckJava.h | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 99e3d4c5a..bd7a24bdb 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -624,6 +624,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("JavaPath", ""); m_settings->registerSetting("JavaTimestamp", 0); m_settings->registerSetting("JavaArchitecture", ""); + m_settings->registerSetting("JavaRealArchitecture", ""); m_settings->registerSetting("JavaVersion", ""); m_settings->registerSetting("JavaVendor", ""); m_settings->registerSetting("LastHostname", ""); diff --git a/launcher/launch/steps/CheckJava.cpp b/launcher/launch/steps/CheckJava.cpp index 3226fae72..ef5db2c9b 100644 --- a/launcher/launch/steps/CheckJava.cpp +++ b/launcher/launch/steps/CheckJava.cpp @@ -75,11 +75,14 @@ void CheckJava::executeTask() qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch(); auto storedUnixTime = settings->get("JavaTimestamp").toLongLong(); auto storedArchitecture = settings->get("JavaArchitecture").toString(); + auto storedRealArchitecture = settings->get("JavaRealArchitecture").toString(); auto storedVersion = settings->get("JavaVersion").toString(); auto storedVendor = settings->get("JavaVendor").toString(); m_javaUnixTime = javaUnixTime; // if timestamps are not the same, or something is missing, check! - if (javaUnixTime != storedUnixTime || storedVersion.size() == 0 || storedArchitecture.size() == 0 || storedVendor.size() == 0) + if (javaUnixTime != storedUnixTime || storedVersion.size() == 0 + || storedArchitecture.size() == 0 || storedRealArchitecture.size() == 0 + || storedVendor.size() == 0) { m_JavaChecker = new JavaChecker(); emit logLine(QString("Checking Java version..."), MessageLevel::Launcher); @@ -92,8 +95,9 @@ void CheckJava::executeTask() { auto verString = instance->settings()->get("JavaVersion").toString(); auto archString = instance->settings()->get("JavaArchitecture").toString(); + auto realArchString = settings->get("JavaRealArchitecture").toString(); auto vendorString = instance->settings()->get("JavaVendor").toString(); - printJavaInfo(verString, archString, vendorString); + printJavaInfo(verString, archString, realArchString, vendorString); } emitSucceeded(); } @@ -124,10 +128,11 @@ void CheckJava::checkJavaFinished(JavaCheckResult result) case JavaCheckResult::Validity::Valid: { auto instance = m_parent->instance(); - printJavaInfo(result.javaVersion.toString(), result.realPlatform, result.javaVendor); + printJavaInfo(result.javaVersion.toString(), result.mojangPlatform, result.realPlatform, result.javaVendor); printSystemInfo(true, result.is_64bit); instance->settings()->set("JavaVersion", result.javaVersion.toString()); instance->settings()->set("JavaArchitecture", result.mojangPlatform); + instance->settings()->set("JavaRealArchitecture", result.realPlatform); instance->settings()->set("JavaVendor", result.javaVendor); instance->settings()->set("JavaTimestamp", m_javaUnixTime); emitSucceeded(); @@ -136,9 +141,10 @@ void CheckJava::checkJavaFinished(JavaCheckResult result) } } -void CheckJava::printJavaInfo(const QString& version, const QString& architecture, const QString & vendor) +void CheckJava::printJavaInfo(const QString& version, const QString& architecture, const QString& realArchitecture, const QString & vendor) { - emit logLine(QString("Java is version %1, using %2 architecture, from %3.\n\n").arg(version, architecture, vendor), MessageLevel::Launcher); + emit logLine(QString("Java is version %1, using %2 (%3) architecture, from %4.\n\n") + .arg(version, architecture, realArchitecture, vendor), MessageLevel::Launcher); } void CheckJava::printSystemInfo(bool javaIsKnown, bool javaIs64bit) diff --git a/launcher/launch/steps/CheckJava.h b/launcher/launch/steps/CheckJava.h index 68cd618b3..d084b1321 100644 --- a/launcher/launch/steps/CheckJava.h +++ b/launcher/launch/steps/CheckJava.h @@ -35,7 +35,7 @@ private slots: void checkJavaFinished(JavaCheckResult result); private: - void printJavaInfo(const QString & version, const QString & architecture, const QString & vendor); + void printJavaInfo(const QString & version, const QString & architecture, const QString & realArchitecture, const QString & vendor); void printSystemInfo(bool javaIsKnown, bool javaIs64bit); private: From 2ea20a8b29808308ce4b23b223457b3ebfb55174 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 11 Jun 2022 07:12:59 -0300 Subject: [PATCH 198/308] fix: allow discovering mrpacks in languages without dot --- launcher/ui/pages/modplatform/ImportPage.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp index b3ed1b732..2ad7881d4 100644 --- a/launcher/ui/pages/modplatform/ImportPage.cpp +++ b/launcher/ui/pages/modplatform/ImportPage.cpp @@ -114,7 +114,7 @@ void ImportPage::updateState() // Allow non-latin people to use ZIP files! auto zip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip"); - if(fi.exists() && (zip || fi.suffix() == "mrpack")) + if(fi.exists() && (zip || fi.suffix() == "mrpack" || fi.fileName().endsWith("mrpack"))) { QFileInfo fi(url.fileName()); dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url,this)); @@ -149,7 +149,7 @@ void ImportPage::setUrl(const QString& url) void ImportPage::on_modpackBtn_clicked() { auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString(); - filter += ";;" + tr("Modrinth pack (*.mrpack)"); + filter += ";;" + tr("Modrinth pack (*.mrpack *mrpack)"); const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), filter); if (url.isValid()) { From 81daffe68e5f91e94a9850ee98fc6ad45d911e5b Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 11 Jun 2022 13:57:40 +0200 Subject: [PATCH 199/308] fix: remove file filter from translation --- launcher/ui/pages/modplatform/ImportPage.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp index 2ad7881d4..0b8577b19 100644 --- a/launcher/ui/pages/modplatform/ImportPage.cpp +++ b/launcher/ui/pages/modplatform/ImportPage.cpp @@ -110,11 +110,13 @@ void ImportPage::updateState() { // FIXME: actually do some validation of what's inside here... this is fake AF QFileInfo fi(input); - // mrpack is a modrinth pack // Allow non-latin people to use ZIP files! - auto zip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip"); - if(fi.exists() && (zip || fi.suffix() == "mrpack" || fi.fileName().endsWith("mrpack"))) + bool isZip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip"); + // mrpack is a modrinth pack + bool isMRPack = fi.suffix() == "mrpack"; + + if(fi.exists() && (isZip || isMRPack)) { QFileInfo fi(url.fileName()); dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url,this)); @@ -149,7 +151,8 @@ void ImportPage::setUrl(const QString& url) void ImportPage::on_modpackBtn_clicked() { auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString(); - filter += ";;" + tr("Modrinth pack (*.mrpack *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); if (url.isValid()) { From 54144154f9761726edda4adf811e86b883f9603b Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 11 Jun 2022 13:43:09 -0300 Subject: [PATCH 200/308] fix: apply client overrides in mrpacks another oopsie x.x --- launcher/FileSystem.cpp | 43 +++++++++++++++++++++++++++++++++ launcher/FileSystem.h | 4 +++ launcher/InstanceImportTask.cpp | 18 +++++++++++--- 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 6de20de6f..3837d75f5 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -454,4 +454,47 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na return false; #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; +} + } diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 8f6e8b489..bc942ab39 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -124,4 +124,8 @@ QString getDesktopDir(); // call it *name* and assign it the icon *icon* // return true if operation succeeded 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); } diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 1ccf7ffc0..3dcd92c8d 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -696,16 +696,26 @@ void InstanceImportTask::processModrinth() emitFailed(tr("Could not understand pack index:\n") + e.cause()); return; } + + auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft"); - QString overridePath = FS::PathCombine(m_stagingPath, "overrides"); - if (QFile::exists(overridePath)) { - QString mcPath = FS::PathCombine(m_stagingPath, ".minecraft"); - if (!QFile::rename(overridePath, mcPath)) { + auto override_path = FS::PathCombine(m_stagingPath, "overrides"); + if (QFile::exists(override_path)) { + if (!QFile::rename(override_path, mcPath)) { emitFailed(tr("Could not rename the overrides folder:\n") + "overrides"); 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"); auto instanceSettings = std::make_shared(configPath); MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); From 29e5a213a5be2d3716018b64241ac030ca2b0af5 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 11 Jun 2022 14:19:51 -0300 Subject: [PATCH 201/308] fix: dequeue first added file in mrpack import Co-authored-by: Sefa Eyeoglu --- launcher/InstanceImportTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 3dcd92c8d..1498db6f7 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -744,7 +744,7 @@ void InstanceImportTask::processModrinth() { auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path); qDebug() << "Will try to download" << file.downloads.front() << "to" << path; - auto dl = Net::Download::makeFile(file.downloads.front(), path); + auto dl = Net::Download::makeFile(file.downloads.dequeue(), path); dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); m_filesNetJob->addNetAction(dl); From 37160f973f1d2fed450f40f38a55b8445787c4bd Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 11 Jun 2022 14:31:50 -0300 Subject: [PATCH 202/308] fix: account for the dequeued url when checking the number of urls Co-authored-by: Sefa Eyeoglu --- launcher/InstanceImportTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 1498db6f7..d5684805a 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -748,7 +748,7 @@ void InstanceImportTask::processModrinth() dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); m_filesNetJob->addNetAction(dl); - if (file.downloads.size() > 1) { + 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]{ From 8683d529fc8182adc394a851bb9d1c4c68bf959e Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 11 Jun 2022 19:35:40 +0200 Subject: [PATCH 203/308] chore: bump version --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 11d582130..0fed9f183 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,8 +73,8 @@ set(Launcher_HELP_URL "https://polymc.org/wiki/help-pages/%1" CACHE STRING "URL ######## Set version numbers ######## set(Launcher_VERSION_MAJOR 1) -set(Launcher_VERSION_MINOR 3) -set(Launcher_VERSION_HOTFIX 1) +set(Launcher_VERSION_MINOR 4) +set(Launcher_VERSION_HOTFIX 0) # Build number set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.") From 8a0aa5a0c852c3a8043d24831be30ccc89aa32d0 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 11 Jun 2022 23:06:42 +0200 Subject: [PATCH 204/308] fix: avoid re-registering InstanceType --- launcher/BaseInstance.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 0240afa8d..f02205e91 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -59,7 +59,11 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s m_settings->registerSetting("lastLaunchTime", 0); m_settings->registerSetting("totalTimePlayed", 0); m_settings->registerSetting("lastTimePlayed", 0); - m_settings->registerSetting("InstanceType", ""); + + // 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); From 13b03e7e503dacdb7a3251a9804c520aae641db0 Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Sun, 12 Jun 2022 11:44:04 +0800 Subject: [PATCH 205/308] Update Application.cpp --- launcher/Application.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 29088c396..dfa756d41 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -967,7 +967,6 @@ bool Application::event(QEvent* event) { if (m_prevAppState == Qt::ApplicationActive && ev->applicationState() == Qt::ApplicationActive) { - qDebug() << "Clicked on dock!"; emit clickedOnDock(); } m_prevAppState = ev->applicationState(); From e843b8e1884f9d0e5b94963d92df4e990fcf8e45 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 4 Jun 2022 08:56:03 -0300 Subject: [PATCH 206/308] fix(test): fix packwiz test --- launcher/modplatform/packwiz/Packwiz_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/modplatform/packwiz/Packwiz_test.cpp b/launcher/modplatform/packwiz/Packwiz_test.cpp index 023b990ef..f7a52e4a8 100644 --- a/launcher/modplatform/packwiz/Packwiz_test.cpp +++ b/launcher/modplatform/packwiz/Packwiz_test.cpp @@ -61,7 +61,7 @@ class PackwizTest : public QObject { QVERIFY(index_dir.entryList().contains(name_mod)); // Try without the .pw.toml at the end - name_mod.chop(5); + name_mod.chop(8); auto metadata = Packwiz::V1::getIndexForMod(index_dir, name_mod); From 8856c8cd62fe3f45faf1020e70fa3dc503eb3453 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 4 Jun 2022 15:30:34 +0200 Subject: [PATCH 207/308] refactor(test): fix loading mod metadata setting --- launcher/ModDownloadTask.cpp | 8 +++++--- launcher/ModDownloadTask.h | 2 +- launcher/minecraft/MinecraftInstance.cpp | 6 ++++-- launcher/minecraft/mod/Mod.cpp | 2 +- launcher/minecraft/mod/ModFolderModel.cpp | 4 ++-- launcher/minecraft/mod/ModFolderModel.h | 3 ++- launcher/minecraft/mod/ModFolderModel_test.cpp | 4 ++-- launcher/minecraft/mod/ResourcePackFolderModel.cpp | 2 +- launcher/minecraft/mod/TexturePackFolderModel.cpp | 2 +- launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp | 5 ----- launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp | 6 +++--- launcher/minecraft/mod/tasks/ModFolderLoadTask.h | 3 ++- launcher/ui/pages/global/LauncherPage.cpp | 2 +- launcher/ui/pages/modplatform/ModPage.cpp | 4 +++- 14 files changed, 28 insertions(+), 25 deletions(-) diff --git a/launcher/ModDownloadTask.cpp b/launcher/ModDownloadTask.cpp index 301b66372..a54baec45 100644 --- a/launcher/ModDownloadTask.cpp +++ b/launcher/ModDownloadTask.cpp @@ -21,12 +21,14 @@ #include "Application.h" #include "minecraft/mod/ModFolderModel.h" -ModDownloadTask::ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr mods) +ModDownloadTask::ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr mods, bool is_indexed) : m_mod(mod), m_mod_version(version), mods(mods) { - m_update_task.reset(new LocalModUpdateTask(mods->indexDir(), m_mod, m_mod_version)); + if (is_indexed) { + m_update_task.reset(new LocalModUpdateTask(mods->indexDir(), m_mod, m_mod_version)); - addTask(m_update_task); + addTask(m_update_task); + } m_filesNetJob.reset(new NetJob(tr("Mod download"), APPLICATION->network())); m_filesNetJob->setStatus(tr("Downloading mod:\n%1").arg(m_mod_version.downloadUrl)); diff --git a/launcher/ModDownloadTask.h b/launcher/ModDownloadTask.h index f4438a8d9..06a8a6de8 100644 --- a/launcher/ModDownloadTask.h +++ b/launcher/ModDownloadTask.h @@ -29,7 +29,7 @@ class ModFolderModel; class ModDownloadTask : public SequentialTask { Q_OBJECT public: - explicit ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr mods); + explicit ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr mods, bool is_indexed); const QString& getFilename() const { return m_mod_version.fileName; } private: diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index e99d30fe2..7e72601ff 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -1015,7 +1015,8 @@ std::shared_ptr MinecraftInstance::loaderModList() const { if (!m_loader_mod_list) { - m_loader_mod_list.reset(new ModFolderModel(modsRoot())); + bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_loader_mod_list.reset(new ModFolderModel(modsRoot(), is_indexed)); m_loader_mod_list->disableInteraction(isRunning()); connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction); } @@ -1026,7 +1027,8 @@ std::shared_ptr MinecraftInstance::coreModList() const { if (!m_core_mod_list) { - m_core_mod_list.reset(new ModFolderModel(coreModsDir())); + bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_core_mod_list.reset(new ModFolderModel(coreModsDir(), is_indexed)); m_core_mod_list->disableInteraction(isRunning()); connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction); } diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 71a32d32f..ff8552307 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -161,7 +161,7 @@ auto Mod::destroy(QDir& index_dir) -> bool { auto n = name(); // FIXME: This can fail to remove the metadata if the - // "DontUseModMetadata" setting is on, since there could + // "ModMetadataDisabled" setting is on, since there could // be a name mismatch! Metadata::remove(index_dir, n); diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index b2d8f03eb..bb52bbe4d 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -28,7 +28,7 @@ #include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/ModFolderLoadTask.h" -ModFolderModel::ModFolderModel(const QString &dir) : QAbstractListModel(), m_dir(dir) +ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : QAbstractListModel(), m_dir(dir), m_is_indexed(is_indexed) { FS::ensureFolderPathExists(m_dir.absolutePath()); m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); @@ -82,7 +82,7 @@ bool ModFolderModel::update() } auto index_dir = indexDir(); - auto task = new ModFolderLoadTask(dir(), index_dir); + auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed); m_update = task->result(); diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index 10a726911..efec2f3f7 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -52,7 +52,7 @@ public: Enable, Toggle }; - ModFolderModel(const QString &dir); + ModFolderModel(const QString &dir, bool is_indexed); virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; @@ -146,6 +146,7 @@ protected: bool scheduled_update = false; bool interaction_disabled = false; QDir m_dir; + bool m_is_indexed; QMap modsIndex; QMap activeTickets; int nextResolutionTicket = 0; diff --git a/launcher/minecraft/mod/ModFolderModel_test.cpp b/launcher/minecraft/mod/ModFolderModel_test.cpp index 76f16ed52..429d82b32 100644 --- a/launcher/minecraft/mod/ModFolderModel_test.cpp +++ b/launcher/minecraft/mod/ModFolderModel_test.cpp @@ -32,7 +32,7 @@ slots: { QString folder = source; QTemporaryDir tempDir; - ModFolderModel m(tempDir.path()); + ModFolderModel m(tempDir.path(), true); m.installMod(folder); verify(tempDir.path()); } @@ -41,7 +41,7 @@ slots: { QString folder = source + '/'; QTemporaryDir tempDir; - ModFolderModel m(tempDir.path()); + ModFolderModel m(tempDir.path(), true); m.installMod(folder); verify(tempDir.path()); } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index f3d7f5668..35d179ee0 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -1,6 +1,6 @@ #include "ResourcePackFolderModel.h" -ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ModFolderModel(dir) { +ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ModFolderModel(dir, false) { } QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const { diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index d5956da10..96fea33ed 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -1,6 +1,6 @@ #include "TexturePackFolderModel.h" -TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ModFolderModel(dir) { +TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ModFolderModel(dir, false) { } QVariant TexturePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const { diff --git a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp index cbe165676..b81700033 100644 --- a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp @@ -35,11 +35,6 @@ void LocalModUpdateTask::executeTask() { setStatus(tr("Updating index for mod:\n%1").arg(m_mod.name)); - if(APPLICATION->settings()->get("DontUseModMetadata").toBool()){ - emitSucceeded(); - return; - } - auto pw_mod = Metadata::create(m_index_dir, m_mod, m_mod_version); Metadata::update(m_index_dir, pw_mod); diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index 62d856f61..285225a07 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -38,13 +38,13 @@ #include "Application.h" #include "minecraft/mod/MetadataHandler.h" -ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir) - : m_mods_dir(mods_dir), m_index_dir(index_dir), m_result(new Result()) +ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed) + : m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_result(new Result()) {} void ModFolderLoadTask::run() { - if (!APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { + if (m_is_indexed) { // Read metadata first getFromMetadata(); } diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h index 89a0f84ef..bc162f436 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h @@ -55,7 +55,7 @@ public: } public: - ModFolderLoadTask(QDir& mods_dir, QDir& index_dir); + ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed); void run(); signals: void succeeded(); @@ -65,5 +65,6 @@ private: private: QDir& m_mods_dir, m_index_dir; + bool m_is_indexed; ResultPtr m_result; }; diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index faf9272d7..4be24979d 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -450,7 +450,7 @@ void LauncherPage::loadSettings() } // Mods - ui->metadataDisableBtn->setChecked(s->get("DontUseModMetadata").toBool()); + ui->metadataDisableBtn->setChecked(s->get("ModMetadataDisabled").toBool()); ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked()); } diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 5020d44cd..e43a087c1 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -1,4 +1,5 @@ #include "ModPage.h" +#include "Application.h" #include "ui_ModPage.h" #include @@ -150,7 +151,8 @@ void ModPage::onModSelected() if (dialog->isModSelected(current.name, version.fileName)) { dialog->removeSelectedMod(current.name); } else { - dialog->addSelectedMod(current.name, new ModDownloadTask(current, version, dialog->mods)); + bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + dialog->addSelectedMod(current.name, new ModDownloadTask(current, version, dialog->mods, is_indexed)); } updateSelectionButton(); From 32217a774f9902d3d523e7b7985bbe22060d0451 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 12 Jun 2022 12:39:04 +0200 Subject: [PATCH 208/308] fix(tests): wait until ModFolderModel has updated --- launcher/minecraft/mod/ModFolderModel_test.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/launcher/minecraft/mod/ModFolderModel_test.cpp b/launcher/minecraft/mod/ModFolderModel_test.cpp index 429d82b32..21e905f48 100644 --- a/launcher/minecraft/mod/ModFolderModel_test.cpp +++ b/launcher/minecraft/mod/ModFolderModel_test.cpp @@ -32,8 +32,11 @@ slots: { QString folder = source; QTemporaryDir tempDir; + QEventLoop loop; ModFolderModel m(tempDir.path(), true); + connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit); m.installMod(folder); + loop.exec(); verify(tempDir.path()); } @@ -41,8 +44,11 @@ slots: { QString folder = source + '/'; QTemporaryDir tempDir; + QEventLoop loop; ModFolderModel m(tempDir.path(), true); + connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit); m.installMod(folder); + loop.exec(); verify(tempDir.path()); } } From 2ff0aa09e35eb6910ef0a030ea41f84a1ed95782 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 12 Jun 2022 12:22:48 +0200 Subject: [PATCH 209/308] fix: remove updater if it is not used --- launcher/Application.cpp | 6 +++ launcher/Application.h | 3 ++ launcher/CMakeLists.txt | 38 ++++++++++--------- .../minecraft/launch/LauncherPartLaunch.cpp | 1 + launcher/net/PasteUpload.cpp | 2 + launcher/ui/GuiUtil.cpp | 1 + launcher/ui/MainWindow.cpp | 10 +++++ launcher/ui/MainWindow.h | 8 ++++ launcher/ui/pages/global/LauncherPage.cpp | 9 +++-- launcher/ui/pages/global/LauncherPage.h | 5 ++- launcher/ui/pages/global/LauncherPage.ui | 3 ++ launcher/ui/pages/instance/LogPage.cpp | 1 + launcher/ui/pages/instance/ModFolderPage.h | 1 + launcher/ui/pages/instance/ScreenshotsPage.h | 1 + launcher/ui/pages/instance/ServersPage.cpp | 1 + launcher/ui/pages/instance/WorldListPage.cpp | 1 + .../modplatform/legacy_ftb/ListModel.cpp | 2 + .../modplatform/modrinth/ModrinthModel.h | 1 + 18 files changed, 70 insertions(+), 24 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 4e0393c07..ab3110a3a 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -154,6 +154,7 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QSt fflush(stderr); } +#ifdef LAUNCHER_WITH_UPDATER QString getIdealPlatform(QString currentPlatform) { auto info = Sys::getKernelInfo(); switch(info.kernelType) { @@ -192,6 +193,7 @@ QString getIdealPlatform(QString currentPlatform) { } return currentPlatform; } +#endif } @@ -754,6 +756,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) qDebug() << "<> Translations loaded."; } +#ifdef LAUNCHER_WITH_UPDATER // initialize the updater if(BuildConfig.UPDATER_ENABLED) { @@ -763,6 +766,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_updateChecker.reset(new UpdateChecker(m_network, channelUrl, BuildConfig.VERSION_CHANNEL, BuildConfig.VERSION_BUILD)); qDebug() << "<> Updater started."; } +#endif // Instance icons { @@ -1408,7 +1412,9 @@ MainWindow* Application::showMainWindow(bool minimized) } m_mainWindow->checkInstancePathForProblems(); +#ifdef LAUNCHER_WITH_UPDATER connect(this, &Application::updateAllowedChanged, m_mainWindow, &MainWindow::updatesAllowedChanged); +#endif connect(m_mainWindow, &MainWindow::isClosing, this, &Application::on_windowClose); m_openWindows++; } diff --git a/launcher/Application.h b/launcher/Application.h index e08e354a3..090071606 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -42,7 +42,10 @@ #include #include #include + +#ifdef LAUNCHER_WITH_UPDATER #include +#endif #include diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 5397a988c..b8db803b0 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -156,27 +156,29 @@ set(LAUNCH_SOURCES launch/LogModel.h ) -# Old update system -set(UPDATE_SOURCES - updater/GoUpdate.h - updater/GoUpdate.cpp - updater/UpdateChecker.h - updater/UpdateChecker.cpp - updater/DownloadTask.h - updater/DownloadTask.cpp -) - -add_unit_test(UpdateChecker - SOURCES updater/UpdateChecker_test.cpp - LIBS Launcher_logic - DATA updater/testdata +if (Launcher_UPDATER_BASE) + set(Launcher_APP_BINARY_DEFS "-DLAUNCHER_WITH_UPDATER ${Launcher_APP_BINARY_DEFS}") + # Old update system + set(UPDATE_SOURCES + updater/GoUpdate.h + updater/GoUpdate.cpp + updater/UpdateChecker.h + updater/UpdateChecker.cpp + updater/DownloadTask.h + updater/DownloadTask.cpp ) -add_unit_test(DownloadTask - SOURCES updater/DownloadTask_test.cpp - LIBS Launcher_logic - DATA updater/testdata + add_unit_test(UpdateChecker + SOURCES updater/UpdateChecker_test.cpp + LIBS Launcher_logic + DATA updater/testdata ) + add_unit_test(DownloadTask + SOURCES updater/DownloadTask_test.cpp + LIBS Launcher_logic + DATA updater/testdata + ) +endif() # Backend for the news bar... there's usually no news. set(NEWS_SOURCES diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index d7010355a..d6fc11e70 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -16,6 +16,7 @@ #include "LauncherPartLaunch.h" #include +#include #include "launch/LaunchTask.h" #include "minecraft/MinecraftInstance.h" diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index ead5e1704..2f200a995 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -43,6 +43,8 @@ #include #include #include +#include +#include std::array PasteUpload::PasteTypes = { {{"0x0.st", "https://0x0.st", ""}, diff --git a/launcher/ui/GuiUtil.cpp b/launcher/ui/GuiUtil.cpp index 320f1502a..62f369510 100644 --- a/launcher/ui/GuiUtil.cpp +++ b/launcher/ui/GuiUtil.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/CustomMessageBox.h" diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 0a5f20007..a6168e7ae 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1010,6 +1010,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow } +#ifdef LAUNCHER_WITH_UPDATER if(BuildConfig.UPDATER_ENABLED) { bool updatesAllowed = APPLICATION->updatesAreAllowed(); @@ -1028,6 +1029,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow updater->checkForUpdate(APPLICATION->settings()->get("UpdateChannel").toString(), false); } } +#endif setSelectedInstanceById(APPLICATION->settings()->get("SelectedInstance").toString()); @@ -1337,6 +1339,7 @@ void MainWindow::repopulateAccountsMenu() ui->profileMenu->addAction(ui->actionManageAccounts); } +#ifdef LAUNCHER_WITH_UPDATER void MainWindow::updatesAllowedChanged(bool allowed) { if(!BuildConfig.UPDATER_ENABLED) @@ -1345,6 +1348,7 @@ void MainWindow::updatesAllowedChanged(bool allowed) } ui->actionCheckUpdate->setEnabled(allowed); } +#endif /* * Assumes the sender is a QAction @@ -1450,6 +1454,7 @@ void MainWindow::updateNewsLabel() } } +#ifdef LAUNCHER_WITH_UPDATER void MainWindow::updateAvailable(GoUpdate::Status status) { if(!APPLICATION->updatesAreAllowed()) @@ -1475,6 +1480,7 @@ void MainWindow::updateNotAvailable() UpdateDialog dlg(false, this); dlg.exec(); } +#endif QList stringToIntList(const QString &string) { @@ -1496,6 +1502,7 @@ QString intListToString(const QList &list) return slist.join(','); } +#ifdef LAUNCHER_WITH_UPDATER void MainWindow::downloadUpdates(GoUpdate::Status status) { if(!APPLICATION->updatesAreAllowed()) @@ -1529,6 +1536,7 @@ void MainWindow::downloadUpdates(GoUpdate::Status status) CustomMessageBox::selectable(this, tr("Error"), updateTask.failReason(), QMessageBox::Warning)->show(); } } +#endif void MainWindow::onCatToggled(bool state) { @@ -1841,6 +1849,7 @@ void MainWindow::on_actionConfig_Folder_triggered() } } +#ifdef LAUNCHER_WITH_UPDATER void MainWindow::checkForUpdates() { if(BuildConfig.UPDATER_ENABLED) @@ -1853,6 +1862,7 @@ void MainWindow::checkForUpdates() qWarning() << "Updater not set up. Cannot check for updates."; } } +#endif void MainWindow::on_actionSettings_triggered() { diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 61a75c450..abd4db920 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -54,7 +54,9 @@ public: void checkInstancePathForProblems(); +#ifdef LAUNCHER_WITH_UPDATER void updatesAllowedChanged(bool allowed); +#endif void droppedURLs(QList urls); signals: @@ -100,7 +102,9 @@ private slots: void on_actionViewCentralModsFolder_triggered(); +#ifdef LAUNCHER_WITH_UPDATER void checkForUpdates(); +#endif void on_actionSettings_triggered(); @@ -167,9 +171,11 @@ private slots: void startTask(Task *task); +#ifdef LAUNCHER_WITH_UPDATER void updateAvailable(GoUpdate::Status status); void updateNotAvailable(); +#endif void defaultAccountChanged(); @@ -179,10 +185,12 @@ private slots: void updateNewsLabel(); +#ifdef LAUNCHER_WITH_UPDATER /*! * Runs the DownloadTask and installs updates. */ void downloadUpdates(GoUpdate::Status status); +#endif void konamiTriggered(); diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 4be24979d..edbf609ff 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -78,6 +78,7 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch m_languageModel = APPLICATION->translations(); loadSettings(); +#ifdef LAUNCHER_WITH_UPDATER if(BuildConfig.UPDATER_ENABLED) { QObject::connect(APPLICATION->updateChecker().get(), &UpdateChecker::channelListLoaded, this, &LauncherPage::refreshUpdateChannelList); @@ -90,11 +91,9 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch { APPLICATION->updateChecker()->updateChanList(false); } + ui->updateSettingsBox->setHidden(false); } - else - { - ui->updateSettingsBox->setHidden(true); - } +#endif connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview())); connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview())); } @@ -189,6 +188,7 @@ void LauncherPage::on_metadataDisableBtn_clicked() ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked()); } +#ifdef LAUNCHER_WITH_UPDATER void LauncherPage::refreshUpdateChannelList() { // Stop listening for selection changes. It's going to change a lot while we update it and @@ -260,6 +260,7 @@ void LauncherPage::refreshUpdateChannelDesc() m_currentUpdateChannel = selected.id; } } +#endif void LauncherPage::applySettings() { diff --git a/launcher/ui/pages/global/LauncherPage.h b/launcher/ui/pages/global/LauncherPage.h index f38c922e2..ccfd7e9e3 100644 --- a/launcher/ui/pages/global/LauncherPage.h +++ b/launcher/ui/pages/global/LauncherPage.h @@ -90,6 +90,7 @@ slots: void on_iconsDirBrowseBtn_clicked(); void on_metadataDisableBtn_clicked(); +#ifdef LAUNCHER_WITH_UPDATER /*! * Updates the list of update channels in the combo box. */ @@ -100,13 +101,13 @@ slots: */ void refreshUpdateChannelDesc(); + void updateChannelSelectionChanged(int index); +#endif /*! * Updates the font preview */ void refreshFontPreview(); - void updateChannelSelectionChanged(int index); - private: Ui::LauncherPage *ui; diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 417bbe059..ceb68c5b3 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -50,6 +50,9 @@ Update Settings + + false + diff --git a/launcher/ui/pages/instance/LogPage.cpp b/launcher/ui/pages/instance/LogPage.cpp index 30a8735fc..51303501a 100644 --- a/launcher/ui/pages/instance/LogPage.cpp +++ b/launcher/ui/pages/instance/LogPage.cpp @@ -39,6 +39,7 @@ #include "Application.h" #include +#include #include #include diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 72e2d4042..2dd44e850 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -41,6 +41,7 @@ #include "ui/pages/BasePage.h" #include +#include class ModFolderModel; namespace Ui diff --git a/launcher/ui/pages/instance/ScreenshotsPage.h b/launcher/ui/pages/instance/ScreenshotsPage.h index c22706af9..c34c97551 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.h +++ b/launcher/ui/pages/instance/ScreenshotsPage.h @@ -35,6 +35,7 @@ #pragma once +#include #include #include "ui/pages/BasePage.h" diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 2af6164c4..d6303cdd7 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -47,6 +47,7 @@ #include #include +#include static const int COLUMN_COUNT = 2; // 3 , TBD: latency and other nice things. diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index 76725539c..731dd85f0 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include "tools/MCEditTool.h" #include "FileSystem.h" diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp index 63b944c46..0d8e1dcf6 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp @@ -11,6 +11,8 @@ #include +#include + namespace LegacyFTB { FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent) diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h index 14aa67473..1b4d8da46 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h @@ -39,6 +39,7 @@ #include "modplatform/modrinth/ModrinthPackManifest.h" #include "ui/pages/modplatform/modrinth/ModrinthPage.h" +#include "net/NetJob.h" class ModPage; class Version; From a4ef0940ed76d646db1b1be1224da2baab4be9e2 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 12 Jun 2022 13:50:58 +0200 Subject: [PATCH 210/308] chore: add license headers --- launcher/ModDownloadTask.cpp | 1 + launcher/ModDownloadTask.h | 1 + .../minecraft/launch/LauncherPartLaunch.cpp | 40 +++++++++++---- launcher/minecraft/mod/Mod.cpp | 1 + launcher/minecraft/mod/ModFolderModel.cpp | 49 +++++++++++++------ launcher/minecraft/mod/ModFolderModel.h | 49 +++++++++++++------ .../minecraft/mod/ModFolderModel_test.cpp | 34 +++++++++++++ .../minecraft/mod/ResourcePackFolderModel.cpp | 35 +++++++++++++ .../minecraft/mod/TexturePackFolderModel.cpp | 35 +++++++++++++ .../mod/tasks/LocalModUpdateTask.cpp | 1 + .../minecraft/mod/tasks/ModFolderLoadTask.cpp | 1 + .../minecraft/mod/tasks/ModFolderLoadTask.h | 1 + launcher/modplatform/packwiz/Packwiz_test.cpp | 31 ++++++------ launcher/net/PasteUpload.cpp | 1 + launcher/ui/GuiUtil.cpp | 1 + launcher/ui/MainWindow.cpp | 47 +++++++++++++----- launcher/ui/MainWindow.h | 44 +++++++++++++---- launcher/ui/pages/global/LanguagePage.cpp | 1 + launcher/ui/pages/global/LanguagePage.h | 1 + launcher/ui/pages/instance/LogPage.cpp | 1 + launcher/ui/pages/instance/ModFolderPage.cpp | 1 + .../ui/pages/instance/ScreenshotsPage.cpp | 1 + launcher/ui/pages/instance/ServersPage.cpp | 1 + launcher/ui/pages/instance/WorldListPage.cpp | 1 + launcher/ui/pages/modplatform/ModPage.cpp | 35 +++++++++++++ .../modplatform/legacy_ftb/ListModel.cpp | 35 +++++++++++++ .../modplatform/modrinth/ModrinthModel.cpp | 1 + 27 files changed, 374 insertions(+), 76 deletions(-) diff --git a/launcher/ModDownloadTask.cpp b/launcher/ModDownloadTask.cpp index a54baec45..41856fb53 100644 --- a/launcher/ModDownloadTask.cpp +++ b/launcher/ModDownloadTask.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 flowln +* Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ModDownloadTask.h b/launcher/ModDownloadTask.h index 06a8a6de8..b3c259096 100644 --- a/launcher/ModDownloadTask.h +++ b/launcher/ModDownloadTask.h @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 flowln +* Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index d6fc11e70..427bc32be 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 "LauncherPartLaunch.h" diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index ff8552307..a85aecfb1 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 flowln +* Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index bb52bbe4d..ded2d3a2a 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -1,17 +1,38 @@ -/* 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. - */ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* Copyright (C) 2022 Sefa Eyeoglu +* +* 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 . +* +* 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 "ModFolderModel.h" diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index efec2f3f7..fcedae964 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -1,17 +1,38 @@ -/* 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. - */ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* Copyright (C) 2022 Sefa Eyeoglu +* +* 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 . +* +* 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. +*/ #pragma once diff --git a/launcher/minecraft/mod/ModFolderModel_test.cpp b/launcher/minecraft/mod/ModFolderModel_test.cpp index 21e905f48..34a3b83a4 100644 --- a/launcher/minecraft/mod/ModFolderModel_test.cpp +++ b/launcher/minecraft/mod/ModFolderModel_test.cpp @@ -1,3 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (C) 2022 Sefa Eyeoglu +* +* 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 . +* +* 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 #include diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index 35d179ee0..fb1f1d296 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (C) 2022 Sefa Eyeoglu +* +* 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 . +* +* 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 "ResourcePackFolderModel.h" ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ModFolderModel(dir, false) { diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index 96fea33ed..644dfd777 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (C) 2022 Sefa Eyeoglu +* +* 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 . +* +* 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 "TexturePackFolderModel.h" TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ModFolderModel(dir, false) { diff --git a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp index b81700033..018bc6e30 100644 --- a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 flowln +* Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index 285225a07..af67d3058 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 flowln +* Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h index bc162f436..088f873e2 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 flowln +* Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/modplatform/packwiz/Packwiz_test.cpp b/launcher/modplatform/packwiz/Packwiz_test.cpp index f7a52e4a8..3d47f9f7b 100644 --- a/launcher/modplatform/packwiz/Packwiz_test.cpp +++ b/launcher/modplatform/packwiz/Packwiz_test.cpp @@ -1,20 +1,21 @@ // SPDX-License-Identifier: GPL-3.0-only /* -* PolyMC - Minecraft Launcher -* Copyright (c) 2022 flowln -* -* 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 . -*/ + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + */ #include #include diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index 2f200a995..7438e1a16 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -3,6 +3,7 @@ * PolyMC - Minecraft Launcher * Copyright (C) 2022 Lenny McLennington * Copyright (C) 2022 Swirl + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/GuiUtil.cpp b/launcher/ui/GuiUtil.cpp index 62f369510..b1ea5ee96 100644 --- a/launcher/ui/GuiUtil.cpp +++ b/launcher/ui/GuiUtil.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (C) 2022 Lenny McLennington + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index a6168e7ae..210442df3 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1,21 +1,42 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * Authors: Andrew Okin - * Peterix - * Orochimarufan + * 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. * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . * - * 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. + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Authors: Andrew Okin + * Peterix + * Orochimarufan + * + * 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 "Application.h" #include "BuildConfig.h" diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index abd4db920..6c64756f6 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -1,16 +1,40 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 + * + * Authors: Andrew Okin + * Peterix + * Orochimarufan + * + * 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 diff --git a/launcher/ui/pages/global/LanguagePage.cpp b/launcher/ui/pages/global/LanguagePage.cpp index 485d7fd47..fcd174bdb 100644 --- a/launcher/ui/pages/global/LanguagePage.cpp +++ b/launcher/ui/pages/global/LanguagePage.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/pages/global/LanguagePage.h b/launcher/ui/pages/global/LanguagePage.h index 9b3211703..2fd4ab0de 100644 --- a/launcher/ui/pages/global/LanguagePage.h +++ b/launcher/ui/pages/global/LanguagePage.h @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/pages/instance/LogPage.cpp b/launcher/ui/pages/instance/LogPage.cpp index 51303501a..a6c98c088 100644 --- a/launcher/ui/pages/instance/LogPage.cpp +++ b/launcher/ui/pages/instance/LogPage.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index b0cd405f0..d929a0ea5 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index 2cf17b32f..51163e281 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index d6303cdd7..c3bde6129 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index 731dd85f0..ff30dd82d 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index e43a087c1..85e1f752b 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "ModPage.h" #include "Application.h" #include "ui_ModPage.h" diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp index 0d8e1dcf6..06e9db4f9 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "ListModel.h" #include "Application.h" diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 7cacf37ad..07d1687ce 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 flowln + * Copyright (C) 2022 Sefa Eyeoglu * * 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 From 40ccd1a46910012f80285f7b6982a5919e2a9dcf Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 4 Jun 2022 23:11:25 -0300 Subject: [PATCH 211/308] fix: handling of incomplete mods (i.e. mods without ModDetails that may have metadata) --- launcher/minecraft/mod/Mod.cpp | 44 ++++++++++++++++++++++++---------- launcher/minecraft/mod/Mod.h | 8 +++++-- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 71a32d32f..39c7efd89 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -58,8 +58,6 @@ Mod::Mod(const QFileInfo& file) Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata) : m_file(mods_dir.absoluteFilePath(metadata.filename)) - // It is weird, but name is not reliable for comparing with the JAR files name - // FIXME: Maybe use hash when implemented? , m_internal_id(metadata.filename) , m_name(metadata.name) { @@ -131,7 +129,7 @@ auto Mod::enable(bool value) -> bool return false; } else { path += ".disabled"; - + if (!file.rename(path)) return false; } @@ -145,16 +143,22 @@ auto Mod::enable(bool value) -> bool void Mod::setStatus(ModStatus status) { - if(m_localDetails.get()) + if (m_localDetails) { m_localDetails->status = status; + } else { + m_temp_status = status; + } } void Mod::setMetadata(Metadata::ModStruct* metadata) { - if(status() == ModStatus::NoMetadata) + if (status() == ModStatus::NoMetadata) setStatus(ModStatus::Installed); - if(m_localDetails.get()) + if (m_localDetails) { m_localDetails->metadata.reset(metadata); + } else { + m_temp_metadata.reset(metadata); + } } auto Mod::destroy(QDir& index_dir) -> bool @@ -205,20 +209,36 @@ auto Mod::authors() const -> QStringList auto Mod::status() const -> ModStatus { + if (!m_localDetails) + return m_temp_status; return details().status; } +auto Mod::metadata() -> std::shared_ptr +{ + if (m_localDetails) + return m_localDetails->metadata; + return m_temp_metadata; +} + +auto Mod::metadata() const -> const std::shared_ptr +{ + if (m_localDetails) + return m_localDetails->metadata; + return m_temp_metadata; +} + void Mod::finishResolvingWithDetails(std::shared_ptr details) { m_resolving = false; m_resolved = true; m_localDetails = details; - if (status() != ModStatus::NoMetadata - && m_temp_metadata.get() - && m_temp_metadata->isValid() && - m_localDetails.get()) { - - m_localDetails->metadata.swap(m_temp_metadata); + if (m_localDetails && m_temp_metadata && m_temp_metadata->isValid()) { + m_localDetails->metadata = m_temp_metadata; + if (status() == ModStatus::NoMetadata) + setStatus(ModStatus::Installed); } + + setStatus(m_temp_status); } diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 96d471b42..5f9c46848 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -73,8 +73,8 @@ public: auto authors() const -> QStringList; auto status() const -> ModStatus; - auto metadata() const -> const std::shared_ptr { return details().metadata; }; - auto metadata() -> std::shared_ptr { return m_localDetails->metadata; }; + auto metadata() -> std::shared_ptr; + auto metadata() const -> const std::shared_ptr; void setStatus(ModStatus status); void setMetadata(Metadata::ModStruct* metadata); @@ -109,6 +109,10 @@ protected: /* If the mod has metadata, this will be filled in the constructor, and passed to * the ModDetails when calling finishResolvingWithDetails */ std::shared_ptr m_temp_metadata; + + /* Set the mod status while it doesn't have local details just yet */ + ModStatus m_temp_status = ModStatus::NotInstalled; + std::shared_ptr m_localDetails; bool m_enabled = true; From 9f1f37e78023d66ce01481c05fa73db9eba0882a Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 7 Jun 2022 22:32:13 -0300 Subject: [PATCH 212/308] fix: correctly handle disabled mods with metadata im stupid --- .../minecraft/mod/tasks/ModFolderLoadTask.cpp | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index 62d856f61..bde32b3e5 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -53,12 +53,31 @@ void ModFolderLoadTask::run() m_mods_dir.refresh(); for (auto entry : m_mods_dir.entryInfoList()) { Mod mod(entry); - if(m_result->mods.contains(mod.internal_id())){ - m_result->mods[mod.internal_id()].setStatus(ModStatus::Installed); + + if (mod.enabled()) { + if (m_result->mods.contains(mod.internal_id())) { + m_result->mods[mod.internal_id()].setStatus(ModStatus::Installed); + } + else { + m_result->mods[mod.internal_id()] = mod; + m_result->mods[mod.internal_id()].setStatus(ModStatus::NoMetadata); + } } - else { - m_result->mods[mod.internal_id()] = mod; - m_result->mods[mod.internal_id()].setStatus(ModStatus::NoMetadata); + else { + QString chopped_id = mod.internal_id().chopped(9); + if (m_result->mods.contains(chopped_id)) { + m_result->mods[mod.internal_id()] = mod; + + auto metadata = m_result->mods[chopped_id].metadata(); + mod.setMetadata(new Metadata::ModStruct(*metadata)); + + m_result->mods[mod.internal_id()].setStatus(ModStatus::Installed); + m_result->mods.remove(chopped_id); + } + else { + m_result->mods[mod.internal_id()] = mod; + m_result->mods[mod.internal_id()].setStatus(ModStatus::NoMetadata); + } } } From 4448418b63715bc64acbb19bd75bedf725cb4165 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 12 Jun 2022 09:44:03 -0300 Subject: [PATCH 213/308] fix: segfault when the same mod is present enabled and disabled at once This maintains the previous behaviour --- launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index bde32b3e5..80242fef3 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -69,10 +69,12 @@ void ModFolderLoadTask::run() m_result->mods[mod.internal_id()] = mod; auto metadata = m_result->mods[chopped_id].metadata(); - mod.setMetadata(new Metadata::ModStruct(*metadata)); + if (metadata) { + mod.setMetadata(new Metadata::ModStruct(*metadata)); - m_result->mods[mod.internal_id()].setStatus(ModStatus::Installed); - m_result->mods.remove(chopped_id); + m_result->mods[mod.internal_id()].setStatus(ModStatus::Installed); + m_result->mods.remove(chopped_id); + } } else { m_result->mods[mod.internal_id()] = mod; From 2ce4ce90640edb23c22667eb4a6dc95f514e7fdd Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 12 Jun 2022 14:51:24 +0200 Subject: [PATCH 214/308] fix(installer): add version info to installer --- program_info/win_install.nsi.in | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index 596e3b576..cdd68d004 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -101,6 +101,13 @@ OutFile "../@Launcher_CommonName@-Setup.exe" ;-------------------------------- +; Version info +VIProductVersion "@Launcher_RELEASE_VERSION_NAME@" +VIFileVersion "@Launcher_RELEASE_VERSION_NAME@" +VIAddVersionKey "FileVersion" "@Launcher_RELEASE_VERSION_NAME@" + +;-------------------------------- + ; The stuff to install Section "@Launcher_CommonName@" From 278219b1d8bbd9b468f329fa8097fa243e155358 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 12 Jun 2022 16:24:05 +0200 Subject: [PATCH 215/308] fix(installer): use Windows version number format --- program_info/win_install.nsi.in | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index cdd68d004..e5687de7b 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -102,9 +102,9 @@ OutFile "../@Launcher_CommonName@-Setup.exe" ;-------------------------------- ; Version info -VIProductVersion "@Launcher_RELEASE_VERSION_NAME@" -VIFileVersion "@Launcher_RELEASE_VERSION_NAME@" -VIAddVersionKey "FileVersion" "@Launcher_RELEASE_VERSION_NAME@" +VIProductVersion "@Launcher_RELEASE_VERSION_NAME4@" +VIFileVersion "@Launcher_RELEASE_VERSION_NAME4@" +VIAddVersionKey "FileVersion" "@Launcher_RELEASE_VERSION_NAME4@" ;-------------------------------- @@ -140,7 +140,7 @@ Section "@Launcher_CommonName@" WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S' WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR" WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "@Launcher_CommonName@ Contributors" - WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "@Launcher_RELEASE_VERSION_NAME@" + WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "@Launcher_RELEASE_VERSION_NAME4@" ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 IntFmt $0 "0x%08X" $0 WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0" From 63b69c0b0c817614c4c5e57f4f8e467916e90c08 Mon Sep 17 00:00:00 2001 From: icelimetea Date: Sun, 12 Jun 2022 16:29:39 +0100 Subject: [PATCH 216/308] Change formatting of JavaCheck --- libraries/javacheck/JavaCheck.java | 35 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/libraries/javacheck/JavaCheck.java b/libraries/javacheck/JavaCheck.java index 560abbc07..4bf43a544 100644 --- a/libraries/javacheck/JavaCheck.java +++ b/libraries/javacheck/JavaCheck.java @@ -1,24 +1,25 @@ -import java.lang.Integer; +public final class JavaCheck { -public class JavaCheck -{ - private static final String[] keys = {"os.arch", "java.version", "java.vendor"}; - public static void main (String [] args) - { - int ret = 0; - for(String key : keys) - { + private static final String[] CHECKED_PROPERTIES = new String[] { + "os.arch", + "java.version", + "java.vendor" + }; + + public static void main(String[] args) { + int returnCode = 0; + + for (String key : CHECKED_PROPERTIES) { String property = System.getProperty(key); - if(property != null) - { + + if (property != null) { System.out.println(key + "=" + property); - } - else - { - ret = 1; + } else { + returnCode = 1; } } - - System.exit(ret); + + System.exit(returnCode); } + } From 8e43190984593c6a4e38174d10b497c560b7dc30 Mon Sep 17 00:00:00 2001 From: icelimetea Date: Sun, 12 Jun 2022 17:46:40 +0100 Subject: [PATCH 217/308] Compile JavaCheck for Java 7 --- libraries/javacheck/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/javacheck/CMakeLists.txt b/libraries/javacheck/CMakeLists.txt index 735de4434..fd545d2bc 100644 --- a/libraries/javacheck/CMakeLists.txt +++ b/libraries/javacheck/CMakeLists.txt @@ -4,7 +4,7 @@ find_package(Java 1.7 REQUIRED COMPONENTS Development) include(UseJava) set(CMAKE_JAVA_JAR_ENTRY_POINT JavaCheck) -set(CMAKE_JAVA_COMPILE_FLAGS -target 8 -source 8 -Xlint:deprecation -Xlint:unchecked) +set(CMAKE_JAVA_COMPILE_FLAGS -target 7 -source 7 -Xlint:deprecation -Xlint:unchecked) set(SRC JavaCheck.java From c04e38d01135a42c00b500cbb2b113d6824c37de Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Sun, 12 Jun 2022 19:13:19 +0200 Subject: [PATCH 218/308] update macos runner to macos 12 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index db7bd6533..d8fc1ff2b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,7 @@ jobs: name: "Windows-x86_64" msystem: mingw64 - - os: macos-11 + - os: macos-12 macosx_deployment_target: 10.13 runs-on: ${{ matrix.os }} From 9f039cef72d32dd2cfeee1038323e5c30af4957a Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Sun, 12 Jun 2022 14:21:56 -0400 Subject: [PATCH 219/308] Set correct installer properties and uninstall registry keys --- program_info/CMakeLists.txt | 3 ++- program_info/win_install.nsi.in | 11 +++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index 1000be23d..8d8353226 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -1,6 +1,7 @@ set(Launcher_CommonName "PolyMC") -set(Launcher_Copyright "PolyMC Contributors\\n© 2012-2021 MultiMC Contributors" PARENT_SCOPE) +set(Launcher_Copyright "PolyMC Contributors\\n© 2012-2021 MultiMC Contributors") +set(Launcher_Copyright "${Launcher_Copyright}" PARENT_SCOPE) set(Launcher_Domain "polymc.org" PARENT_SCOPE) set(Launcher_Name "${Launcher_CommonName}" PARENT_SCOPE) set(Launcher_DisplayName "${Launcher_CommonName}" PARENT_SCOPE) diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index e5687de7b..e8290108f 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -104,7 +104,11 @@ OutFile "../@Launcher_CommonName@-Setup.exe" ; Version info VIProductVersion "@Launcher_RELEASE_VERSION_NAME4@" VIFileVersion "@Launcher_RELEASE_VERSION_NAME4@" -VIAddVersionKey "FileVersion" "@Launcher_RELEASE_VERSION_NAME4@" +VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductName" "@Launcher_CommonName@" +VIAddVersionKey /LANG=${LANG_ENGLISH} "FileDescription" "@Launcher_CommonName@ Installer" +VIAddVersionKey /LANG=${LANG_ENGLISH} "LegalCopyright" "@Launcher_Copyright@" +VIAddVersionKey /LANG=${LANG_ENGLISH} "FileVersion" "@Launcher_RELEASE_VERSION_NAME4@" +VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductVersion" "@Launcher_RELEASE_VERSION_NAME4@" ;-------------------------------- @@ -140,7 +144,10 @@ Section "@Launcher_CommonName@" WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S' WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR" WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "@Launcher_CommonName@ Contributors" - WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "@Launcher_RELEASE_VERSION_NAME4@" + WriteRegStr HKCU "${UNINST_KEY}" "Version" "@Launcher_RELEASE_VERSION_NAME4@" + WriteRegStr HKCU "${UNINST_KEY}" "DisplayVersion" "@Launcher_RELEASE_VERSION_NAME@" + WriteRegStr HKCU "${UNINST_KEY}" "VersionMajor" "@Launcher_VERSION_MAJOR@" + WriteRegStr HKCU "${UNINST_KEY}" "VersionMinor" "@Launcher_VERSION_MINOR@" ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 IntFmt $0 "0x%08X" $0 WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0" From 4be9e6a0bc0a4ac6b47ead7008b8a6a811c63b4d Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 13 Jun 2022 22:03:12 +0200 Subject: [PATCH 220/308] refactor: make is_indexed false by default Co-authored-by: flow --- launcher/minecraft/mod/ModFolderModel.h | 2 +- launcher/minecraft/mod/ResourcePackFolderModel.cpp | 2 +- launcher/minecraft/mod/TexturePackFolderModel.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index fcedae964..24b4d358f 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -73,7 +73,7 @@ public: Enable, Toggle }; - ModFolderModel(const QString &dir, bool is_indexed); + ModFolderModel(const QString &dir, bool is_indexed = false); virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index fb1f1d296..276804ed1 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -35,7 +35,7 @@ #include "ResourcePackFolderModel.h" -ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ModFolderModel(dir, false) { +ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ModFolderModel(dir) { } QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const { diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index 644dfd777..e3a22219a 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -35,7 +35,7 @@ #include "TexturePackFolderModel.h" -TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ModFolderModel(dir, false) { +TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ModFolderModel(dir) { } QVariant TexturePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const { From d394235ee0040b061504ae50daaea10d3c80500c Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 11 Mar 2022 18:03:21 -0300 Subject: [PATCH 221/308] refactor: Create a more clear hierarchy for some instance pages Previously, the Shaders, Texture packs and Resource packs tabs had as parent the ModFolderPage, making it so that making changes only to the Mods page would require checking the id of the page for the correct one. This was hackish and error-prone. Now, those pages all inherit from a single class, ExternalResourcesPage, that handles the basic behaviour of all of them, while allowing for individual modification in code. This is still not a clear separation, since internally, all those resources are derived from Mods, so for now there's still some awkward common code :/ --- launcher/CMakeLists.txt | 4 +- launcher/InstancePageProvider.h | 4 +- .../pages/instance/ExternalResourcesPage.cpp | 297 ++++++++++++++ .../ui/pages/instance/ExternalResourcesPage.h | 73 ++++ ...FolderPage.ui => ExternalResourcesPage.ui} | 38 +- launcher/ui/pages/instance/ModFolderPage.cpp | 377 ++---------------- launcher/ui/pages/instance/ModFolderPage.h | 112 +----- launcher/ui/pages/instance/ResourcePackPage.h | 20 +- launcher/ui/pages/instance/ShaderPackPage.h | 16 +- launcher/ui/pages/instance/TexturePackPage.h | 18 +- 10 files changed, 486 insertions(+), 473 deletions(-) create mode 100644 launcher/ui/pages/instance/ExternalResourcesPage.cpp create mode 100644 launcher/ui/pages/instance/ExternalResourcesPage.h rename launcher/ui/pages/instance/{ModFolderPage.ui => ExternalResourcesPage.ui} (83%) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index b8db803b0..e8e2ebd98 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -717,6 +717,8 @@ SET(LAUNCHER_SOURCES ui/pages/BasePageProvider.h # GUI - instance pages + ui/pages/instance/ExternalResourcesPage.cpp + ui/pages/instance/ExternalResourcesPage.h ui/pages/instance/GameOptionsPage.cpp ui/pages/instance/GameOptionsPage.h ui/pages/instance/VersionPage.cpp @@ -924,7 +926,7 @@ qt5_wrap_ui(LAUNCHER_UI ui/pages/global/ProxyPage.ui ui/pages/global/MinecraftPage.ui ui/pages/global/ExternalToolsPage.ui - ui/pages/instance/ModFolderPage.ui + ui/pages/instance/ExternalResourcesPage.ui ui/pages/instance/NotesPage.ui ui/pages/instance/LogPage.ui ui/pages/instance/ServersPage.ui diff --git a/launcher/InstancePageProvider.h b/launcher/InstancePageProvider.h index 357157d00..78fb70165 100644 --- a/launcher/InstancePageProvider.h +++ b/launcher/InstancePageProvider.h @@ -33,10 +33,10 @@ public: values.append(new LogPage(inst)); std::shared_ptr onesix = std::dynamic_pointer_cast(inst); values.append(new VersionPage(onesix.get())); - auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList(), "mods", "loadermods", tr("Mods"), "Loader-mods"); + auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList()); modsPage->setFilter("%1 (*.zip *.jar *.litemod)"); values.append(modsPage); - values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList(), "coremods", "coremods", tr("Core mods"), "Core-mods")); + values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList())); values.append(new ResourcePackPage(onesix.get())); values.append(new TexturePackPage(onesix.get())); values.append(new ShaderPackPage(onesix.get())); diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp new file mode 100644 index 000000000..0b1dc4f32 --- /dev/null +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -0,0 +1,297 @@ +#include "ExternalResourcesPage.h" +#include "ui_ExternalResourcesPage.h" + +#include "DesktopServices.h" +#include "Version.h" +#include "minecraft/mod/ModFolderModel.h" +#include "ui/GuiUtil.h" + +#include +#include + +namespace { +// FIXME: wasteful +void RemoveThePrefix(QString& string) +{ + QRegularExpression regex(QStringLiteral("^(([Tt][Hh][eE])|([Tt][eE][Hh])) +")); + string.remove(regex); + string = string.trimmed(); +} +} // namespace + +class SortProxy : public QSortFilterProxyModel { + public: + explicit SortProxy(QObject* parent = nullptr) : QSortFilterProxyModel(parent) {} + + protected: + bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override + { + ModFolderModel* model = qobject_cast(sourceModel()); + if (!model) + return false; + + const auto& mod = model->at(source_row); + + if (mod.name().contains(filterRegExp())) + return true; + if (mod.description().contains(filterRegExp())) + return true; + + for (auto& author : mod.authors()) { + if (author.contains(filterRegExp())) { + return true; + } + } + + return false; + } + + bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override + { + ModFolderModel* model = qobject_cast(sourceModel()); + if (!model || !source_left.isValid() || !source_right.isValid() || source_left.column() != source_right.column()) { + return QSortFilterProxyModel::lessThan(source_left, source_right); + } + + // we are now guaranteed to have two valid indexes in the same column... we love the provided invariants unconditionally and + // proceed. + + auto column = (ModFolderModel::Columns) source_left.column(); + bool invert = false; + switch (column) { + // GH-2550 - sort by enabled/disabled + case ModFolderModel::ActiveColumn: { + auto dataL = source_left.data(Qt::CheckStateRole).toBool(); + auto dataR = source_right.data(Qt::CheckStateRole).toBool(); + if (dataL != dataR) + return dataL > dataR; + + // fallthrough + invert = sortOrder() == Qt::DescendingOrder; + } + // GH-2722 - sort mod names in a way that discards "The" prefixes + case ModFolderModel::NameColumn: { + auto dataL = model->data(model->index(source_left.row(), ModFolderModel::NameColumn)).toString(); + RemoveThePrefix(dataL); + auto dataR = model->data(model->index(source_right.row(), ModFolderModel::NameColumn)).toString(); + RemoveThePrefix(dataR); + + auto less = dataL.compare(dataR, sortCaseSensitivity()); + if (less != 0) + return invert ? (less > 0) : (less < 0); + + // fallthrough + invert = sortOrder() == Qt::DescendingOrder; + } + // GH-2762 - sort versions by parsing them as versions + case ModFolderModel::VersionColumn: { + auto dataL = Version(model->data(model->index(source_left.row(), ModFolderModel::VersionColumn)).toString()); + auto dataR = Version(model->data(model->index(source_right.row(), ModFolderModel::VersionColumn)).toString()); + return invert ? (dataL > dataR) : (dataL < dataR); + } + default: { + return QSortFilterProxyModel::lessThan(source_left, source_right); + } + } + } +}; + +ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared_ptr model, QWidget* parent) + : QMainWindow(parent), m_instance(instance), ui(new Ui::ExternalResourcesPage), m_model(model) +{ + ui->setupUi(this); + + runningStateChanged(m_instance && m_instance->isRunning()); + + ui->actionsToolbar->insertSpacer(ui->actionViewConfigs); + + m_filterModel = new SortProxy(this); + m_filterModel->setDynamicSortFilter(true); + m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive); + m_filterModel->setSourceModel(m_model.get()); + m_filterModel->setFilterKeyColumn(-1); + ui->treeView->setModel(m_filterModel); + + ui->treeView->installEventFilter(this); + ui->treeView->sortByColumn(1, Qt::AscendingOrder); + ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu); + + // The default function names by Qt are pretty ugly, so let's just connect the actions manually, + // to make it easier to read :) + connect(ui->actionAddItem, &QAction::triggered, this, &ExternalResourcesPage::addItem); + connect(ui->actionRemoveItem, &QAction::triggered, this, &ExternalResourcesPage::removeItem); + connect(ui->actionEnableItem, &QAction::triggered, this, &ExternalResourcesPage::enableItem); + connect(ui->actionDisableItem, &QAction::triggered, this, &ExternalResourcesPage::disableItem); + connect(ui->actionViewConfigs, &QAction::triggered, this, &ExternalResourcesPage::viewConfigs); + connect(ui->actionViewFolder, &QAction::triggered, this, &ExternalResourcesPage::viewFolder); + + connect(ui->treeView, &ModListView::customContextMenuRequested, this, &ExternalResourcesPage::ShowContextMenu); + connect(ui->treeView, &ModListView::activated, this, &ExternalResourcesPage::itemActivated); + + auto selection_model = ui->treeView->selectionModel(); + connect(selection_model, &QItemSelectionModel::currentChanged, this, &ExternalResourcesPage::current); + connect(ui->filterEdit, &QLineEdit::textChanged, this, &ExternalResourcesPage::filterTextChanged); + connect(m_instance, &BaseInstance::runningStatusChanged, this, &ExternalResourcesPage::runningStateChanged); +} + +ExternalResourcesPage::~ExternalResourcesPage() +{ + m_model->stopWatching(); + delete ui; +} + +void ExternalResourcesPage::itemActivated(const QModelIndex&) +{ + if (!m_controlsEnabled) + return; + + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); + m_model->setModStatus(selection.indexes(), ModFolderModel::Toggle); +} + +QMenu* ExternalResourcesPage::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction(ui->actionsToolbar->toggleViewAction()); + return filteredMenu; +} + +void ExternalResourcesPage::ShowContextMenu(const QPoint& pos) +{ + auto menu = ui->actionsToolbar->createContextMenu(this, tr("Context menu")); + menu->exec(ui->treeView->mapToGlobal(pos)); + delete menu; +} + +void ExternalResourcesPage::openedImpl() +{ + m_model->startWatching(); +} + +void ExternalResourcesPage::closedImpl() +{ + m_model->stopWatching(); +} + +void ExternalResourcesPage::retranslate() +{ + ui->retranslateUi(this); +} + +void ExternalResourcesPage::filterTextChanged(const QString& newContents) +{ + m_viewFilter = newContents; + m_filterModel->setFilterFixedString(m_viewFilter); +} + +void ExternalResourcesPage::runningStateChanged(bool running) +{ + if (m_controlsEnabled == !running) + return; + + m_controlsEnabled = !running; + ui->actionAddItem->setEnabled(m_controlsEnabled); + ui->actionDisableItem->setEnabled(m_controlsEnabled); + ui->actionEnableItem->setEnabled(m_controlsEnabled); + ui->actionRemoveItem->setEnabled(m_controlsEnabled); +} + +bool ExternalResourcesPage::shouldDisplay() const +{ + return true; +} + +bool ExternalResourcesPage::listFilter(QKeyEvent* keyEvent) +{ + switch (keyEvent->key()) { + case Qt::Key_Delete: + removeItem(); + return true; + case Qt::Key_Plus: + addItem(); + return true; + default: + break; + } + return QWidget::eventFilter(ui->treeView, keyEvent); +} + +bool ExternalResourcesPage::eventFilter(QObject* obj, QEvent* ev) +{ + if (ev->type() != QEvent::KeyPress) + return QWidget::eventFilter(obj, ev); + + QKeyEvent* keyEvent = static_cast(ev); + if (obj == ui->treeView) + return listFilter(keyEvent); + + return QWidget::eventFilter(obj, ev); +} + +void ExternalResourcesPage::addItem() +{ + if (!m_controlsEnabled) + return; + + + auto list = GuiUtil::BrowseForFiles( + helpPage(), tr("Select %1", "Select whatever type of files the page contains. Example: 'Loader Mods'").arg(displayName()), + m_fileSelectionFilter.arg(displayName()), APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget()); + + if (!list.isEmpty()) { + for (auto filename : list) { + m_model->installMod(filename); + } + } +} + +void ExternalResourcesPage::removeItem() +{ + if (!m_controlsEnabled) + return; + + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); + m_model->deleteMods(selection.indexes()); +} + +void ExternalResourcesPage::enableItem() +{ + if (!m_controlsEnabled) + return; + + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); + m_model->setModStatus(selection.indexes(), ModFolderModel::Enable); +} + +void ExternalResourcesPage::disableItem() +{ + if (!m_controlsEnabled) + return; + + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); + m_model->setModStatus(selection.indexes(), ModFolderModel::Disable); +} + +void ExternalResourcesPage::viewConfigs() +{ + DesktopServices::openDirectory(m_instance->instanceConfigFolder(), true); +} + +void ExternalResourcesPage::viewFolder() +{ + DesktopServices::openDirectory(m_model->dir().absolutePath(), true); +} + +void ExternalResourcesPage::current(const QModelIndex& current, const QModelIndex& previous) +{ + if (!current.isValid()) { + ui->frame->clear(); + return; + } + + auto sourceCurrent = m_filterModel->mapToSource(current); + int row = sourceCurrent.row(); + Mod& m = m_model->operator[](row); + ui->frame->updateWithMod(m); +} diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.h b/launcher/ui/pages/instance/ExternalResourcesPage.h new file mode 100644 index 000000000..412371398 --- /dev/null +++ b/launcher/ui/pages/instance/ExternalResourcesPage.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include + +#include "Application.h" +#include "minecraft/MinecraftInstance.h" +#include "ui/pages/BasePage.h" + +class ModFolderModel; + +namespace Ui { +class ExternalResourcesPage; +} + +/* This page is used as a base for pages in which the user can manage external resources + * related to the game, such as mods, shaders or resource packs. */ +class ExternalResourcesPage : public QMainWindow, public BasePage { + Q_OBJECT + + public: + // FIXME: Switch to different model (or change the name of this one) + explicit ExternalResourcesPage(BaseInstance* instance, std::shared_ptr model, QWidget* parent = nullptr); + virtual ~ExternalResourcesPage(); + + virtual QString displayName() const override = 0; + virtual QIcon icon() const override = 0; + virtual QString id() const override = 0; + virtual QString helpPage() const override = 0; + + virtual bool shouldDisplay() const override = 0; + + void openedImpl() override; + void closedImpl() override; + + void retranslate() override; + + protected: + bool eventFilter(QObject* obj, QEvent* ev) override; + bool listFilter(QKeyEvent* ev); + QMenu* createPopupMenu() override; + + public slots: + void current(const QModelIndex& current, const QModelIndex& previous); + + protected slots: + void itemActivated(const QModelIndex& index); + void filterTextChanged(const QString& newContents); + void runningStateChanged(bool running); + + virtual void addItem(); + virtual void removeItem(); + + virtual void enableItem(); + virtual void disableItem(); + + virtual void viewFolder(); + virtual void viewConfigs(); + + void ShowContextMenu(const QPoint& pos); + + protected: + BaseInstance* m_instance = nullptr; + + Ui::ExternalResourcesPage* ui = nullptr; + std::shared_ptr m_model; + QSortFilterProxyModel* m_filterModel = nullptr; + + QString m_fileSelectionFilter; + QString m_viewFilter; + + bool m_controlsEnabled = true; +}; diff --git a/launcher/ui/pages/instance/ModFolderPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui similarity index 83% rename from launcher/ui/pages/instance/ModFolderPage.ui rename to launcher/ui/pages/instance/ExternalResourcesPage.ui index ab59b0df2..3982b6ee1 100644 --- a/launcher/ui/pages/instance/ModFolderPage.ui +++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui @@ -1,7 +1,7 @@ - ModFolderPage - + ExternalResourcesPage + 0 @@ -53,7 +53,7 @@ - + 0 @@ -83,15 +83,15 @@ false - + - - - - - + + + + + - + &Add @@ -99,31 +99,31 @@ Add - + &Remove - Remove selected mods + Remove selected item - + &Enable - Enable selected mods + Enable selected item - + &Disable - Disable selected mods + Disable selected item - + View &Configs @@ -131,7 +131,7 @@ Open the 'config' folder in the system file manager. - + View &Folder @@ -156,7 +156,7 @@ - modTreeView + treeView filterEdit diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index d929a0ea5..be32ad0a6 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -35,368 +35,95 @@ */ #include "ModFolderPage.h" -#include "ui_ModFolderPage.h" +#include "ui_ExternalResourcesPage.h" -#include +#include #include #include -#include #include +#include #include #include "Application.h" +#include "ui/GuiUtil.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ModDownloadDialog.h" -#include "ui/GuiUtil.h" #include "DesktopServices.h" -#include "minecraft/mod/ModFolderModel.h" -#include "minecraft/mod/Mod.h" -#include "minecraft/VersionFilterData.h" #include "minecraft/PackProfile.h" +#include "minecraft/VersionFilterData.h" +#include "minecraft/mod/Mod.h" +#include "minecraft/mod/ModFolderModel.h" #include "modplatform/ModAPI.h" #include "Version.h" -#include "ui/dialogs/ProgressDialog.h" #include "tasks/SequentialTask.h" +#include "ui/dialogs/ProgressDialog.h" -namespace { - // FIXME: wasteful - void RemoveThePrefix(QString & string) { - QRegularExpression regex(QStringLiteral("^(([Tt][Hh][eE])|([Tt][eE][Hh])) +")); - string.remove(regex); - string = string.trimmed(); - } -} - -class ModSortProxy : public QSortFilterProxyModel +ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent) + : ExternalResourcesPage(inst, mods, parent) { -public: - explicit ModSortProxy(QObject *parent = 0) : QSortFilterProxyModel(parent) - { - } - -protected: - bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const override { - ModFolderModel *model = qobject_cast(sourceModel()); - if(!model) { - return false; - } - const auto &mod = model->at(source_row); - if(mod.name().contains(filterRegExp())) { - return true; - } - if(mod.description().contains(filterRegExp())) { - return true; - } - for(auto & author: mod.authors()) { - if (author.contains(filterRegExp())) { - return true; - } - } - return false; - } - - bool lessThan(const QModelIndex & source_left, const QModelIndex & source_right) const override - { - ModFolderModel *model = qobject_cast(sourceModel()); - if( - !model || - !source_left.isValid() || - !source_right.isValid() || - source_left.column() != source_right.column() - ) { - return QSortFilterProxyModel::lessThan(source_left, source_right); - } - - // we are now guaranteed to have two valid indexes in the same column... we love the provided invariants unconditionally and proceed. - - auto column = (ModFolderModel::Columns) source_left.column(); - bool invert = false; - switch(column) { - // GH-2550 - sort by enabled/disabled - case ModFolderModel::ActiveColumn: { - auto dataL = source_left.data(Qt::CheckStateRole).toBool(); - auto dataR = source_right.data(Qt::CheckStateRole).toBool(); - if(dataL != dataR) { - return dataL > dataR; - } - // fallthrough - invert = sortOrder() == Qt::DescendingOrder; - } - // GH-2722 - sort mod names in a way that discards "The" prefixes - case ModFolderModel::NameColumn: { - auto dataL = model->data(model->index(source_left.row(), ModFolderModel::NameColumn)).toString(); - RemoveThePrefix(dataL); - auto dataR = model->data(model->index(source_right.row(), ModFolderModel::NameColumn)).toString(); - RemoveThePrefix(dataR); - - auto less = dataL.compare(dataR, sortCaseSensitivity()); - if(less != 0) { - return invert ? (less > 0) : (less < 0); - } - // fallthrough - invert = sortOrder() == Qt::DescendingOrder; - } - // GH-2762 - sort versions by parsing them as versions - case ModFolderModel::VersionColumn: { - auto dataL = Version(model->data(model->index(source_left.row(), ModFolderModel::VersionColumn)).toString()); - auto dataR = Version(model->data(model->index(source_right.row(), ModFolderModel::VersionColumn)).toString()); - return invert ? (dataL > dataR) : (dataL < dataR); - } - default: { - return QSortFilterProxyModel::lessThan(source_left, source_right); - } - } - } -}; - -ModFolderPage::ModFolderPage( - BaseInstance *inst, - std::shared_ptr mods, - QString id, - QString iconName, - QString displayName, - QString helpPage, - QWidget *parent -) : - QMainWindow(parent), - ui(new Ui::ModFolderPage) -{ - ui->setupUi(this); - // This is structured like that so that these changes - // do not affect the Resouce pack and Shader pack tabs - if(id == "mods") { + // do not affect the Resource pack and Shader pack tabs + { auto act = new QAction(tr("Download mods"), this); act->setToolTip(tr("Download mods from online mod platforms")); - ui->actionsToolbar->insertActionBefore(ui->actionAdd, act); - connect(act, &QAction::triggered, this, &ModFolderPage::on_actionInstall_mods_triggered); + ui->actionsToolbar->insertActionBefore(ui->actionAddItem, act); + connect(act, &QAction::triggered, this, &ModFolderPage::installMods); - ui->actionAdd->setText(tr("Add .jar")); - ui->actionAdd->setToolTip(tr("Add mods via local file")); + ui->actionAddItem->setText(tr("Add .jar")); + ui->actionAddItem->setToolTip(tr("Add mods via local file")); } - - ui->actionsToolbar->insertSpacer(ui->actionView_configs); - - m_inst = inst; - on_RunningState_changed(m_inst && m_inst->isRunning()); - m_mods = mods; - m_id = id; - m_displayName = displayName; - m_iconName = iconName; - m_helpName = helpPage; - m_fileSelectionFilter = "%1 (*.zip *.jar)"; - m_filterModel = new ModSortProxy(this); - m_filterModel->setDynamicSortFilter(true); - m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); - m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive); - m_filterModel->setSourceModel(m_mods.get()); - m_filterModel->setFilterKeyColumn(-1); - ui->modTreeView->setModel(m_filterModel); - ui->modTreeView->installEventFilter(this); - ui->modTreeView->sortByColumn(1, Qt::AscendingOrder); - ui->modTreeView->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->modTreeView, &ModListView::customContextMenuRequested, this, &ModFolderPage::ShowContextMenu); - connect(ui->modTreeView, &ModListView::activated, this, &ModFolderPage::modItemActivated); - - auto smodel = ui->modTreeView->selectionModel(); - connect(smodel, &QItemSelectionModel::currentChanged, this, &ModFolderPage::modCurrent); - connect(ui->filterEdit, &QLineEdit::textChanged, this, &ModFolderPage::on_filterTextChanged); - connect(m_inst, &BaseInstance::runningStatusChanged, this, &ModFolderPage::on_RunningState_changed); } -void ModFolderPage::modItemActivated(const QModelIndex&) -{ - if(!m_controlsEnabled) { - return; - } - auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); - m_mods->setModStatus(selection.indexes(), ModFolderModel::Toggle); -} - -QMenu * ModFolderPage::createPopupMenu() -{ - QMenu* filteredMenu = QMainWindow::createPopupMenu(); - filteredMenu->removeAction(ui->actionsToolbar->toggleViewAction() ); - return filteredMenu; -} - -void ModFolderPage::ShowContextMenu(const QPoint& pos) -{ - auto menu = ui->actionsToolbar->createContextMenu(this, tr("Context menu")); - menu->exec(ui->modTreeView->mapToGlobal(pos)); - delete menu; -} - -void ModFolderPage::openedImpl() -{ - m_mods->startWatching(); -} - -void ModFolderPage::closedImpl() -{ - m_mods->stopWatching(); -} - -void ModFolderPage::on_filterTextChanged(const QString& newContents) -{ - m_viewFilter = newContents; - m_filterModel->setFilterFixedString(m_viewFilter); -} - - -CoreModFolderPage::CoreModFolderPage(BaseInstance *inst, std::shared_ptr mods, - QString id, QString iconName, QString displayName, - QString helpPage, QWidget *parent) - : ModFolderPage(inst, mods, id, iconName, displayName, helpPage, parent) -{ -} - -ModFolderPage::~ModFolderPage() -{ - m_mods->stopWatching(); - delete ui; -} - -void ModFolderPage::on_RunningState_changed(bool running) -{ - if(m_controlsEnabled == !running) { - return; - } - m_controlsEnabled = !running; - ui->actionsToolbar->setEnabled(m_controlsEnabled); -} +CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent) + : ModFolderPage(inst, mods, parent) +{} bool ModFolderPage::shouldDisplay() const { return true; } -void ModFolderPage::retranslate() -{ - ui->retranslateUi(this); -} - bool CoreModFolderPage::shouldDisplay() const { - if (ModFolderPage::shouldDisplay()) - { - auto inst = dynamic_cast(m_inst); + if (ModFolderPage::shouldDisplay()) { + auto inst = dynamic_cast(m_instance); if (!inst) return true; + auto version = inst->getPackProfile(); + if (!version) return true; - if(!version->getComponent("net.minecraftforge")) - { + if (!version->getComponent("net.minecraftforge")) return false; - } - if(!version->getComponent("net.minecraft")) - { + if (!version->getComponent("net.minecraft")) return false; - } - if(version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate) - { + if (version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate) return true; - } + } return false; } -bool ModFolderPage::modListFilter(QKeyEvent *keyEvent) +void ModFolderPage::installMods() { - switch (keyEvent->key()) - { - case Qt::Key_Delete: - on_actionRemove_triggered(); - return true; - case Qt::Key_Plus: - on_actionAdd_triggered(); - return true; - default: - break; - } - return QWidget::eventFilter(ui->modTreeView, keyEvent); -} - -bool ModFolderPage::eventFilter(QObject *obj, QEvent *ev) -{ - if (ev->type() != QEvent::KeyPress) - { - return QWidget::eventFilter(obj, ev); - } - QKeyEvent *keyEvent = static_cast(ev); - if (obj == ui->modTreeView) - return modListFilter(keyEvent); - return QWidget::eventFilter(obj, ev); -} - -void ModFolderPage::on_actionAdd_triggered() -{ - if(!m_controlsEnabled) { + if (!m_controlsEnabled) return; - } - auto list = GuiUtil::BrowseForFiles( - m_helpName, - tr("Select %1", - "Select whatever type of files the page contains. Example: 'Loader Mods'") - .arg(m_displayName), - m_fileSelectionFilter.arg(m_displayName), APPLICATION->settings()->get("CentralModsDir").toString(), - this->parentWidget()); - if (!list.empty()) - { - for (auto filename : list) - { - m_mods->installMod(filename); - } - } -} - -void ModFolderPage::on_actionEnable_triggered() -{ - if(!m_controlsEnabled) { - return; - } - auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); - m_mods->setModStatus(selection.indexes(), ModFolderModel::Enable); -} - -void ModFolderPage::on_actionDisable_triggered() -{ - if(!m_controlsEnabled) { - return; - } - auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); - m_mods->setModStatus(selection.indexes(), ModFolderModel::Disable); -} - -void ModFolderPage::on_actionRemove_triggered() -{ - if(!m_controlsEnabled) { - return; - } - auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); - m_mods->deleteMods(selection.indexes()); -} - -void ModFolderPage::on_actionInstall_mods_triggered() -{ - if(!m_controlsEnabled) { - return; - } - if(m_inst->typeName() != "Minecraft"){ - return; //this is a null instance or a legacy instance - } - auto profile = ((MinecraftInstance *)m_inst)->getPackProfile(); + if (m_instance->typeName() != "Minecraft") + return; // this is a null instance or a legacy instance + + auto profile = static_cast(m_instance)->getPackProfile(); if (profile->getModLoaders() == ModAPI::Unspecified) { - QMessageBox::critical(this,tr("Error"),tr("Please install a mod loader first!")); + QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); return; } - ModDownloadDialog mdownload(m_mods, this, m_inst); + + ModDownloadDialog mdownload(m_model, this, m_instance); if (mdownload.exec()) { SequentialTask* tasks = new SequentialTask(this); connect(tasks, &Task::failed, [this, tasks](QString reason) { @@ -409,40 +136,20 @@ void ModFolderPage::on_actionInstall_mods_triggered() }); connect(tasks, &Task::succeeded, [this, tasks]() { QStringList warnings = tasks->warnings(); - if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); } + if (warnings.count()) + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + tasks->deleteLater(); }); - for (auto task : mdownload.getTasks()) { + for (auto& task : mdownload.getTasks()) { tasks->addTask(task); } ProgressDialog loadDialog(this); loadDialog.setSkipButton(true, tr("Abort")); loadDialog.execWithTask(tasks); - m_mods->update(); + + m_model->update(); } } - -void ModFolderPage::on_actionView_configs_triggered() -{ - DesktopServices::openDirectory(m_inst->instanceConfigFolder(), true); -} - -void ModFolderPage::on_actionView_Folder_triggered() -{ - DesktopServices::openDirectory(m_mods->dir().absolutePath(), true); -} - -void ModFolderPage::modCurrent(const QModelIndex ¤t, const QModelIndex &previous) -{ - if (!current.isValid()) - { - ui->frame->clear(); - return; - } - auto sourceCurrent = m_filterModel->mapToSource(current); - int row = sourceCurrent.row(); - Mod &m = m_mods->operator[](row); - ui->frame->updateWithMod(m); -} diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 2dd44e850..1a9ed7dbf 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -35,109 +35,31 @@ #pragma once -#include +#include "ExternalResourcesPage.h" -#include "minecraft/MinecraftInstance.h" -#include "ui/pages/BasePage.h" - -#include -#include - -class ModFolderModel; -namespace Ui -{ -class ModFolderPage; -} - -class ModFolderPage : public QMainWindow, public BasePage -{ +class ModFolderPage : public ExternalResourcesPage { Q_OBJECT -public: - explicit ModFolderPage( - BaseInstance *inst, - std::shared_ptr mods, - QString id, - QString iconName, - QString displayName, - QString helpPage = "", - QWidget *parent = 0 - ); - virtual ~ModFolderPage(); + public: + explicit ModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent = nullptr); + virtual ~ModFolderPage() = default; - void setFilter(const QString & filter) - { - m_fileSelectionFilter = filter; - } + void setFilter(const QString& filter) { m_fileSelectionFilter = filter; } + + virtual QString displayName() const override { return tr("Mods"); } + virtual QIcon icon() const override { return APPLICATION->getThemedIcon("loadermods"); } + virtual QString id() const override { return "mods"; } + virtual QString helpPage() const override { return "Loader-mods"; } - virtual QString displayName() const override - { - return m_displayName; - } - virtual QIcon icon() const override - { - return APPLICATION->getThemedIcon(m_iconName); - } - virtual QString id() const override - { - return m_id; - } - virtual QString helpPage() const override - { - return m_helpName; - } virtual bool shouldDisplay() const override; - void retranslate() override; - virtual void openedImpl() override; - virtual void closedImpl() override; -protected: - bool eventFilter(QObject *obj, QEvent *ev) override; - bool modListFilter(QKeyEvent *ev); - QMenu * createPopupMenu() override; - -protected: - BaseInstance *m_inst = nullptr; - -protected: - Ui::ModFolderPage *ui = nullptr; - std::shared_ptr m_mods; - QSortFilterProxyModel *m_filterModel = nullptr; - QString m_iconName; - QString m_id; - QString m_displayName; - QString m_helpName; - QString m_fileSelectionFilter; - QString m_viewFilter; - bool m_controlsEnabled = true; - -public -slots: - void modCurrent(const QModelIndex ¤t, const QModelIndex &previous); - -private -slots: - void modItemActivated(const QModelIndex &index); - void on_filterTextChanged(const QString & newContents); - void on_RunningState_changed(bool running); - void on_actionAdd_triggered(); - void on_actionRemove_triggered(); - void on_actionEnable_triggered(); - void on_actionDisable_triggered(); - void on_actionInstall_mods_triggered(); - void on_actionView_Folder_triggered(); - void on_actionView_configs_triggered(); - void ShowContextMenu(const QPoint &pos); + private slots: + void installMods(); }; -class CoreModFolderPage : public ModFolderPage -{ -public: - explicit CoreModFolderPage(BaseInstance *inst, std::shared_ptr mods, QString id, - QString iconName, QString displayName, QString helpPage = "", - QWidget *parent = 0); - virtual ~CoreModFolderPage() - { - } +class CoreModFolderPage : public ModFolderPage { + public: + explicit CoreModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent = 0); + virtual ~CoreModFolderPage() = default; virtual bool shouldDisplay() const; }; diff --git a/launcher/ui/pages/instance/ResourcePackPage.h b/launcher/ui/pages/instance/ResourcePackPage.h index 8054926cf..a6c9fdd34 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.h +++ b/launcher/ui/pages/instance/ResourcePackPage.h @@ -35,24 +35,28 @@ #pragma once -#include "ModFolderPage.h" -#include "ui_ModFolderPage.h" +#include "ExternalResourcesPage.h" +#include "ui_ExternalResourcesPage.h" -class ResourcePackPage : public ModFolderPage +class ResourcePackPage : public ExternalResourcesPage { Q_OBJECT public: explicit ResourcePackPage(MinecraftInstance *instance, QWidget *parent = 0) - : ModFolderPage(instance, instance->resourcePackList(), "resourcepacks", - "resourcepacks", tr("Resource packs"), "Resource-packs", parent) + : ExternalResourcesPage(instance, instance->resourcePackList(), parent) { - ui->actionView_configs->setVisible(false); + ui->actionViewConfigs->setVisible(false); } virtual ~ResourcePackPage() {} + QString displayName() const override { return tr("Resource packs"); } + QIcon icon() const override { return APPLICATION->getThemedIcon("resourcepacks"); } + QString id() const override { return "resourcepacks"; } + QString helpPage() const override { return "Resource-packs"; } + virtual bool shouldDisplay() const override { - return !m_inst->traits().contains("no-texturepacks") && - !m_inst->traits().contains("texturepacks"); + return !m_instance->traits().contains("no-texturepacks") && + !m_instance->traits().contains("texturepacks"); } }; diff --git a/launcher/ui/pages/instance/ShaderPackPage.h b/launcher/ui/pages/instance/ShaderPackPage.h index 7d4f50742..2cc056c83 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.h +++ b/launcher/ui/pages/instance/ShaderPackPage.h @@ -35,21 +35,25 @@ #pragma once -#include "ModFolderPage.h" -#include "ui_ModFolderPage.h" +#include "ExternalResourcesPage.h" +#include "ui_ExternalResourcesPage.h" -class ShaderPackPage : public ModFolderPage +class ShaderPackPage : public ExternalResourcesPage { Q_OBJECT public: explicit ShaderPackPage(MinecraftInstance *instance, QWidget *parent = 0) - : ModFolderPage(instance, instance->shaderPackList(), "shaderpacks", - "shaderpacks", tr("Shader packs"), "Resource-packs", parent) + : ExternalResourcesPage(instance, instance->shaderPackList(), parent) { - ui->actionView_configs->setVisible(false); + ui->actionViewConfigs->setVisible(false); } virtual ~ShaderPackPage() {} + QString displayName() const override { return tr("Shader packs"); } + QIcon icon() const override { return APPLICATION->getThemedIcon("shaderpacks"); } + QString id() const override { return "shaderpacks"; } + QString helpPage() const override { return "Resource-packs"; } + virtual bool shouldDisplay() const override { return true; diff --git a/launcher/ui/pages/instance/TexturePackPage.h b/launcher/ui/pages/instance/TexturePackPage.h index e8cefe6e3..f550a5bcf 100644 --- a/launcher/ui/pages/instance/TexturePackPage.h +++ b/launcher/ui/pages/instance/TexturePackPage.h @@ -35,23 +35,27 @@ #pragma once -#include "ModFolderPage.h" -#include "ui_ModFolderPage.h" +#include "ExternalResourcesPage.h" +#include "ui_ExternalResourcesPage.h" -class TexturePackPage : public ModFolderPage +class TexturePackPage : public ExternalResourcesPage { Q_OBJECT public: explicit TexturePackPage(MinecraftInstance *instance, QWidget *parent = 0) - : ModFolderPage(instance, instance->texturePackList(), "texturepacks", "resourcepacks", - tr("Texture packs"), "Texture-packs", parent) + : ExternalResourcesPage(instance, instance->texturePackList(), parent) { - ui->actionView_configs->setVisible(false); + ui->actionViewConfigs->setVisible(false); } virtual ~TexturePackPage() {} + QString displayName() const override { return tr("Texture packs"); } + QIcon icon() const override { return APPLICATION->getThemedIcon("resourcepacks"); } + QString id() const override { return "texturepacks"; } + QString helpPage() const override { return "Texture-packs"; } + virtual bool shouldDisplay() const override { - return m_inst->traits().contains("texturepacks"); + return m_instance->traits().contains("texturepacks"); } }; From e25cdd9d122b5f7adbbdf7c8b9989ae49337db9e Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 30 May 2022 16:36:30 +0200 Subject: [PATCH 222/308] refector: move download action to ExternalResourcesPage --- .../ui/pages/instance/ExternalResourcesPage.ui | 11 +++++++++++ launcher/ui/pages/instance/ModFolderPage.cpp | 14 ++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui index 3982b6ee1..17bf455a1 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.ui +++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui @@ -136,6 +136,17 @@ View &Folder + + + false + + + &Download + + + Download a new resource + + diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index be32ad0a6..8fd0f86ee 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -69,13 +69,15 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr // This is structured like that so that these changes // do not affect the Resource pack and Shader pack tabs { - auto act = new QAction(tr("Download mods"), this); - act->setToolTip(tr("Download mods from online mod platforms")); - ui->actionsToolbar->insertActionBefore(ui->actionAddItem, act); - connect(act, &QAction::triggered, this, &ModFolderPage::installMods); + ui->actionDownloadItem->setText(tr("Download mods")); + ui->actionDownloadItem->setToolTip(tr("Download mods from online mod platforms")); + ui->actionDownloadItem->setEnabled(true); + ui->actionAddItem->setText(tr("Add file")); + ui->actionAddItem->setToolTip(tr("Add a locally downloaded file")); - ui->actionAddItem->setText(tr("Add .jar")); - ui->actionAddItem->setToolTip(tr("Add mods via local file")); + ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); + + connect(ui->actionDownloadItem, &QAction::triggered, this, &ModFolderPage::installMods); } } From ba939c92ec2e47f609c52b0d824c051fda25e38a Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 4 Jun 2022 14:25:01 +0200 Subject: [PATCH 223/308] feat(actions): test before packaging --- .github/workflows/build.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index db7bd6533..79cc2129d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -167,6 +167,21 @@ jobs: run: | cmake --build ${{ env.BUILD_DIR }} + ## + # TEST + ## + + - name: Test + if: runner.os != 'Windows' + run: | + ctest --test-dir build --output-on-failure + + - name: Test (Windows) + if: runner.os == 'Windows' + shell: msys2 {0} + run: | + ctest --test-dir build --output-on-failure + ## # PACKAGE BUILDS ## From effe46db86e1317da5aad66687966b3e11398b6c Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 12 Jun 2022 01:46:30 +0200 Subject: [PATCH 224/308] refactor: move away from UnitTest.cmake --- CMakeLists.txt | 15 ++- cmake/UnitTest.cmake | 50 ---------- cmake/UnitTest/TestUtil.h | 28 ------ cmake/UnitTest/generate_test_data.cmake | 23 ----- cmake/UnitTest/test.manifest | 27 ------ cmake/UnitTest/test.rc | 28 ------ launcher/CMakeLists.txt | 95 ++++++------------- launcher/FileSystem.h | 5 - launcher/FileSystem_test.cpp | 44 +-------- launcher/GZip_test.cpp | 1 - launcher/java/JavaVersion_test.cpp | 1 - launcher/meta/Index_test.cpp | 1 - launcher/minecraft/GradleSpecifier_test.cpp | 1 - launcher/minecraft/Library_test.cpp | 44 ++++----- .../minecraft/MojangVersionFormat_test.cpp | 8 +- launcher/minecraft/ParseUtils_test.cpp | 2 - .../minecraft/mod/ModFolderModel_test.cpp | 3 +- .../assets/minecraft/textures/blah.txt | 1 + .../mod/testdata/test_folder/pack.mcmeta | 6 ++ .../mod/testdata/test_folder/pack.nfo | 1 + launcher/modplatform/packwiz/Packwiz_test.cpp | 1 - launcher/mojang/PackageManifest_test.cpp | 1 - launcher/settings/INIFile_test.cpp | 1 - launcher/tasks/Task_test.cpp | 1 - launcher/updater/DownloadTask_test.cpp | 32 ++++--- launcher/updater/UpdateChecker_test.cpp | 19 ++-- libraries/systeminfo/CMakeLists.txt | 6 +- libraries/systeminfo/src/sys_test.cpp | 1 - 28 files changed, 106 insertions(+), 340 deletions(-) delete mode 100644 cmake/UnitTest.cmake delete mode 100644 cmake/UnitTest/TestUtil.h delete mode 100644 cmake/UnitTest/generate_test_data.cmake delete mode 100644 cmake/UnitTest/test.manifest delete mode 100644 cmake/UnitTest/test.rc create mode 100644 launcher/minecraft/mod/testdata/test_folder/assets/minecraft/textures/blah.txt create mode 100644 launcher/minecraft/mod/testdata/test_folder/pack.mcmeta create mode 100644 launcher/minecraft/mod/testdata/test_folder/pack.nfo diff --git a/CMakeLists.txt b/CMakeLists.txt index b09e7fd22..96abe22e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,14 +6,12 @@ if(WIN32) endif() project(Launcher) -include(CTest) string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BUILD_DIR}" IS_IN_SOURCE_BUILD) if(IS_IN_SOURCE_BUILD) message(FATAL_ERROR "You are building the Launcher in-source. Please separate the build tree from the source tree.") endif() - ##################################### Set CMake options ##################################### set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) @@ -63,6 +61,17 @@ if(ENABLE_LTO) endif() endif() +option(BUILD_TESTING "Build the testing tree." ON) + +find_package(ECM REQUIRED NO_MODULE) +set(CMAKE_MODULE_PATH "${ECM_MODULE_PATH};${CMAKE_MODULE_PATH}") +if (BUILD_TESTING) + include(CTest) + include(ECMAddTests) + + enable_testing() +endif() + ##################################### Set Application options ##################################### ######## Set URLs ######## @@ -102,8 +111,6 @@ set(Launcher_MATRIX_URL "https://matrix.to/#/#polymc:matrix.org" CACHE STRING "U # Discord URL set(Launcher_DISCORD_URL "https://discord.gg/Z52pwxWCHP" CACHE STRING "URL for the Discord guild.") - - # Subreddit URL set(Launcher_SUBREDDIT_URL "https://www.reddit.com/r/PolyMCLauncher/" CACHE STRING "URL for the subreddit.") diff --git a/cmake/UnitTest.cmake b/cmake/UnitTest.cmake deleted file mode 100644 index 7d7bd4ad4..000000000 --- a/cmake/UnitTest.cmake +++ /dev/null @@ -1,50 +0,0 @@ -find_package(Qt5Test REQUIRED) - -set(TEST_RESOURCE_PATH ${CMAKE_CURRENT_LIST_DIR}) - -message(${TEST_RESOURCE_PATH}) - -function(add_unit_test name) - if(BUILD_TESTING) - set(options "") - set(oneValueArgs DATA) - set(multiValueArgs SOURCES LIBS) - - cmake_parse_arguments(OPT "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) - - if(WIN32) - add_executable(${name}_test ${OPT_SOURCES} ${TEST_RESOURCE_PATH}/UnitTest/test.rc) - else() - add_executable(${name}_test ${OPT_SOURCES}) - endif() - - if(NOT "${OPT_DATA}" STREQUAL "") - set(TEST_DATA_PATH "${CMAKE_CURRENT_BINARY_DIR}/data") - set(TEST_DATA_PATH_SRC "${CMAKE_CURRENT_SOURCE_DIR}/${OPT_DATA}") - message("From ${TEST_DATA_PATH_SRC} to ${TEST_DATA_PATH}") - string(REGEX REPLACE "[/\\:]" "_" DATA_TARGET_NAME "${TEST_DATA_PATH_SRC}") - if(UNIX) - # on unix we get the third / from the filename - set(TEST_DATA_URL "file://${TEST_DATA_PATH}") - else() - # we don't on windows, so we have to add it ourselves - set(TEST_DATA_URL "file:///${TEST_DATA_PATH}") - endif() - if(NOT TARGET "${DATA_TARGET_NAME}") - add_custom_target(${DATA_TARGET_NAME}) - add_dependencies(${name}_test ${DATA_TARGET_NAME}) - add_custom_command( - TARGET ${DATA_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} "-DTEST_DATA_URL=${TEST_DATA_URL}" -DSOURCE=${TEST_DATA_PATH_SRC} -DDESTINATION=${TEST_DATA_PATH} -P ${TEST_RESOURCE_PATH}/UnitTest/generate_test_data.cmake - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - ) - endif() - endif() - - target_link_libraries(${name}_test Qt5::Test ${OPT_LIBS}) - - target_include_directories(${name}_test PRIVATE "${TEST_RESOURCE_PATH}/UnitTest/") - - add_test(NAME ${name} COMMAND ${name}_test WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - endif() -endfunction() diff --git a/cmake/UnitTest/TestUtil.h b/cmake/UnitTest/TestUtil.h deleted file mode 100644 index ebe3c662d..000000000 --- a/cmake/UnitTest/TestUtil.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#define expandstr(s) expandstr2(s) -#define expandstr2(s) #s - -class TestsInternal -{ -public: - static QByteArray readFile(const QString &fileName) - { - QFile f(fileName); - f.open(QFile::ReadOnly); - return f.readAll(); - } - static QString readFileUtf8(const QString &fileName) - { - return QString::fromUtf8(readFile(fileName)); - } -}; - -#define GET_TEST_FILE(file) TestsInternal::readFile(QFINDTESTDATA(file)) -#define GET_TEST_FILE_UTF8(file) TestsInternal::readFileUtf8(QFINDTESTDATA(file)) - diff --git a/cmake/UnitTest/generate_test_data.cmake b/cmake/UnitTest/generate_test_data.cmake deleted file mode 100644 index d0bd4ab12..000000000 --- a/cmake/UnitTest/generate_test_data.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# Copy files from source directory to destination directory, substituting any -# variables. Create destination directory if it does not exist. - -function(configure_files srcDir destDir) - make_directory(${destDir}) - - file(GLOB templateFiles RELATIVE ${srcDir} ${srcDir}/*) - foreach(templateFile ${templateFiles}) - set(srcTemplatePath ${srcDir}/${templateFile}) - if(NOT IS_DIRECTORY ${srcTemplatePath}) - configure_file( - ${srcTemplatePath} - ${destDir}/${templateFile} - @ONLY - NEWLINE_STYLE LF - ) - else() - configure_files("${srcTemplatePath}" "${destDir}/${templateFile}") - endif() - endforeach() -endfunction() - -configure_files(${SOURCE} ${DESTINATION}) \ No newline at end of file diff --git a/cmake/UnitTest/test.manifest b/cmake/UnitTest/test.manifest deleted file mode 100644 index dc5f9d8fe..000000000 --- a/cmake/UnitTest/test.manifest +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - Custom Minecraft launcher for managing multiple installs. - - - - - - - - - - - diff --git a/cmake/UnitTest/test.rc b/cmake/UnitTest/test.rc deleted file mode 100644 index 6c0f06416..000000000 --- a/cmake/UnitTest/test.rc +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#include - -1 RT_MANIFEST "test.manifest" - -VS_VERSION_INFO VERSIONINFO -FILEVERSION 1,0,0,0 -FILEOS VOS_NT_WINDOWS32 -FILETYPE VFT_APP -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "000004b0" - BEGIN - VALUE "CompanyName", "MultiMC & PolyMC Contributors" - VALUE "FileDescription", "Testcase" - VALUE "FileVersion", "1.0.0.0" - VALUE "ProductName", "Launcher Testcase" - VALUE "ProductVersion", "5" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x0000, 0x04b0 // Unicode - END -END diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index b8db803b0..e768ffaa0 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -4,8 +4,6 @@ project(application) ######## Sources and headers ######## -include (UnitTest) - set(CORE_SOURCES # LOGIC - Base classes and infrastructure BaseInstaller.h @@ -90,16 +88,11 @@ set(CORE_SOURCES MMCTime.cpp ) -add_unit_test(FileSystem - SOURCES FileSystem_test.cpp - LIBS Launcher_logic - DATA testdata - ) +ecm_add_test(FileSystem_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test + TEST_NAME FileSystem) # TODO: needs testdata -add_unit_test(GZip - SOURCES GZip_test.cpp - LIBS Launcher_logic - ) +ecm_add_test(GZip_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test + TEST_NAME GZip) set(PATHMATCHER_SOURCES # Path matchers @@ -168,16 +161,10 @@ if (Launcher_UPDATER_BASE) updater/DownloadTask.cpp ) - add_unit_test(UpdateChecker - SOURCES updater/UpdateChecker_test.cpp - LIBS Launcher_logic - DATA updater/testdata - ) - add_unit_test(DownloadTask - SOURCES updater/DownloadTask_test.cpp - LIBS Launcher_logic - DATA updater/testdata - ) + ecm_add_test(updater/UpdateChecker_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test + TEST_NAME UpdateChecker) + ecm_add_test(updater/DownloadTask_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test + TEST_NAME DownloadTask) endif() # Backend for the news bar... there's usually no news. @@ -359,10 +346,8 @@ set(MINECRAFT_SOURCES mojang/PackageManifest.cpp minecraft/Agent.h) -add_unit_test(GradleSpecifier - SOURCES minecraft/GradleSpecifier_test.cpp - LIBS Launcher_logic - ) +ecm_add_test(minecraft/GradleSpecifier_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test + TEST_NAME GradleSpecifier) if(BUILD_TESTING) add_executable(PackageManifest @@ -382,28 +367,20 @@ if(BUILD_TESTING) ) endif() -add_unit_test(MojangVersionFormat - SOURCES minecraft/MojangVersionFormat_test.cpp - LIBS Launcher_logic - DATA minecraft/testdata - ) +# TODO: needs minecraft/testdata +ecm_add_test(minecraft/MojangVersionFormat_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test + TEST_NAME MojangVersionFormat) -add_unit_test(Library - SOURCES minecraft/Library_test.cpp - LIBS Launcher_logic - ) +ecm_add_test(minecraft/Library_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test + TEST_NAME Library) # FIXME: shares data with FileSystem test -add_unit_test(ModFolderModel - SOURCES minecraft/mod/ModFolderModel_test.cpp - DATA testdata - LIBS Launcher_logic - ) +# TODO: needs testdata +ecm_add_test(minecraft/mod/ModFolderModel_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test + TEST_NAME ModFolderModel) -add_unit_test(ParseUtils - SOURCES minecraft/ParseUtils_test.cpp - LIBS Launcher_logic - ) +ecm_add_test(minecraft/ParseUtils_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test + TEST_NAME ParseUtils) # the screenshots feature set(SCREENSHOTS_SOURCES @@ -422,10 +399,8 @@ set(TASKS_SOURCES tasks/SequentialTask.cpp ) -add_unit_test(Task - SOURCES tasks/Task_test.cpp - LIBS Launcher_logic - ) +ecm_add_test(tasks/Task_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test + TEST_NAME Task) set(SETTINGS_SOURCES # Settings @@ -443,10 +418,8 @@ set(SETTINGS_SOURCES settings/SettingsObject.h ) -add_unit_test(INIFile - SOURCES settings/INIFile_test.cpp - LIBS Launcher_logic - ) +ecm_add_test(settings/INIFile_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test + TEST_NAME INIFile) set(JAVA_SOURCES java/JavaChecker.h @@ -463,10 +436,8 @@ set(JAVA_SOURCES java/JavaVersion.cpp ) -add_unit_test(JavaVersion - SOURCES java/JavaVersion_test.cpp - LIBS Launcher_logic - ) +ecm_add_test(java/JavaVersion_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test + TEST_NAME JavaVersion) set(TRANSLATIONS_SOURCES translations/TranslationsModel.h @@ -558,11 +529,9 @@ set(PACKWIZ_SOURCES modplatform/packwiz/Packwiz.cpp ) -add_unit_test(Packwiz - SOURCES modplatform/packwiz/Packwiz_test.cpp - DATA modplatform/packwiz/testdata - LIBS Launcher_logic - ) +# TODO: needs modplatform/packwiz/testdata +ecm_add_test(modplatform/packwiz/Packwiz_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test + TEST_NAME Packwiz) set(TECHNIC_SOURCES modplatform/technic/SingleZipPackInstallTask.h @@ -586,10 +555,8 @@ set(ATLAUNCHER_SOURCES modplatform/atlauncher/ATLShareCode.h ) -add_unit_test(Index - SOURCES meta/Index_test.cpp - LIBS Launcher_logic - ) +ecm_add_test(meta/Index_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test + TEST_NAME Index) ################################ COMPILE ################################ diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index bc942ab39..31c7af700 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -120,11 +120,6 @@ bool checkProblemticPathJava(QDir folder); // Get the Directory representing the User's Desktop QString getDesktopDir(); -// Create a shortcut at *location*, pointing to *dest* called with the arguments *args* -// call it *name* and assign it the icon *icon* -// return true if operation succeeded -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); diff --git a/launcher/FileSystem_test.cpp b/launcher/FileSystem_test.cpp index 90ddc4993..99ae92691 100644 --- a/launcher/FileSystem_test.cpp +++ b/launcher/FileSystem_test.cpp @@ -1,7 +1,6 @@ #include #include #include -#include "TestUtil.h" #include "FileSystem.h" @@ -81,7 +80,7 @@ slots: void test_copy() { - QString folder = QFINDTESTDATA("data/test_folder"); + QString folder = QFINDTESTDATA("testdata/test_folder"); auto f = [&folder]() { QTemporaryDir tempDir; @@ -116,47 +115,6 @@ slots: { QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); } - -// this is only valid on linux -// FIXME: implement on windows, OSX, then test. -#if defined(Q_OS_LINUX) - void test_createShortcut_data() - { - QTest::addColumn("location"); - QTest::addColumn("dest"); - QTest::addColumn("args"); - QTest::addColumn("name"); - QTest::addColumn("iconLocation"); - QTest::addColumn("result"); - - QTest::newRow("unix") << QDir::currentPath() - << "asdfDest" - << (QStringList() << "arg1" << "arg2") - << "asdf" - << QString() - #if defined(Q_OS_LINUX) - << GET_TEST_FILE("data/FileSystem-test_createShortcut-unix") - #elif defined(Q_OS_WIN) - << QByteArray() - #endif - ; - } - - void test_createShortcut() - { - QFETCH(QString, location); - QFETCH(QString, dest); - QFETCH(QStringList, args); - QFETCH(QString, name); - QFETCH(QString, iconLocation); - QFETCH(QByteArray, result); - - QVERIFY(FS::createShortCut(location, dest, args, name, iconLocation)); - QCOMPARE(QString::fromLocal8Bit(TestsInternal::readFile(location + QDir::separator() + name + ".desktop")), QString::fromLocal8Bit(result)); - - //QDir().remove(location); - } -#endif }; QTEST_GUILESS_MAIN(FileSystemTest) diff --git a/launcher/GZip_test.cpp b/launcher/GZip_test.cpp index 3f4d181ca..73859fbc9 100644 --- a/launcher/GZip_test.cpp +++ b/launcher/GZip_test.cpp @@ -1,5 +1,4 @@ #include -#include "TestUtil.h" #include "GZip.h" #include diff --git a/launcher/java/JavaVersion_test.cpp b/launcher/java/JavaVersion_test.cpp index 10ae13a74..545947ef0 100644 --- a/launcher/java/JavaVersion_test.cpp +++ b/launcher/java/JavaVersion_test.cpp @@ -1,5 +1,4 @@ #include -#include "TestUtil.h" #include "java/JavaVersion.h" diff --git a/launcher/meta/Index_test.cpp b/launcher/meta/Index_test.cpp index 5d3ddc509..261858c44 100644 --- a/launcher/meta/Index_test.cpp +++ b/launcher/meta/Index_test.cpp @@ -1,5 +1,4 @@ #include -#include "TestUtil.h" #include "meta/Index.h" #include "meta/VersionList.h" diff --git a/launcher/minecraft/GradleSpecifier_test.cpp b/launcher/minecraft/GradleSpecifier_test.cpp index 0900c9d8e..a062dfacb 100644 --- a/launcher/minecraft/GradleSpecifier_test.cpp +++ b/launcher/minecraft/GradleSpecifier_test.cpp @@ -1,5 +1,4 @@ #include -#include "TestUtil.h" #include "minecraft/GradleSpecifier.h" diff --git a/launcher/minecraft/Library_test.cpp b/launcher/minecraft/Library_test.cpp index 47531ad6f..834dd5583 100644 --- a/launcher/minecraft/Library_test.cpp +++ b/launcher/minecraft/Library_test.cpp @@ -1,5 +1,4 @@ #include -#include "TestUtil.h" #include "minecraft/MojangVersionFormat.h" #include "minecraft/OneSixVersionFormat.h" @@ -11,15 +10,14 @@ class LibraryTest : public QObject { Q_OBJECT private: - LibraryPtr readMojangJson(const char *file) + LibraryPtr readMojangJson(const QString path) { - auto path = QFINDTESTDATA(file); QFile jsonFile(path); jsonFile.open(QIODevice::ReadOnly); auto data = jsonFile.readAll(); jsonFile.close(); ProblemContainer problems; - return MojangVersionFormat::libraryFromJson(problems, QJsonDocument::fromJson(data).object(), file); + return MojangVersionFormat::libraryFromJson(problems, QJsonDocument::fromJson(data).object(), path); } // get absolute path to expected storage, assuming default cache prefix QStringList getStorage(QString relative) @@ -32,7 +30,7 @@ slots: { cache.reset(new HttpMetaCache()); cache->addBase("libraries", QDir("libraries").absolutePath()); - dataDir = QDir("data").absolutePath(); + dataDir = QDir(QFINDTESTDATA("testdata")).absolutePath(); } void test_legacy() { @@ -74,14 +72,14 @@ slots: QCOMPARE(test.isNative(), false); QStringList failedFiles; test.setHint("local"); - auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString("data")); + auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QFINDTESTDATA("testdata")); QCOMPARE(downloads.size(), 0); qDebug() << failedFiles; QCOMPARE(failedFiles.size(), 0); QStringList jar, native, native32, native64; - test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString("data")); - QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()}); + test.getApplicableFiles(currentSystem, jar, native, native32, native64, QFINDTESTDATA("testdata")); + QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/codecwav-20101023.jar")).absoluteFilePath()}); QCOMPARE(native, {}); QCOMPARE(native32, {}); QCOMPARE(native64, {}); @@ -167,20 +165,20 @@ slots: test.setRepositoryURL("file://foo/bar"); { QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString("data")); + test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QFINDTESTDATA("testdata")); QCOMPARE(jar, {}); QCOMPARE(native, {}); - QCOMPARE(native32, {QFileInfo("data/testname-testversion-linux-32.jar").absoluteFilePath()}); - QCOMPARE(native64, {QFileInfo("data/testname-testversion-linux-64.jar").absoluteFilePath()}); + QCOMPARE(native32, {QFileInfo(QFINDTESTDATA("testdata/testname-testversion-linux-32.jar")).absoluteFilePath()}); + QCOMPARE(native64, {QFileInfo(QFINDTESTDATA("testdata") + "/testname-testversion-linux-64.jar").absoluteFilePath()}); QStringList failedFiles; - auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString("data")); + auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QFINDTESTDATA("testdata")); QCOMPARE(dls.size(), 0); - QCOMPARE(failedFiles, {"data/testname-testversion-linux-64.jar"}); + QCOMPARE(failedFiles, {QFileInfo(QFINDTESTDATA("testdata") + "/testname-testversion-linux-64.jar").absoluteFilePath()}); } } void test_onenine() { - auto test = readMojangJson("data/lib-simple.json"); + auto test = readMojangJson(QFINDTESTDATA("testdata/lib-simple.json")); { QStringList jar, native, native32, native64; test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); @@ -199,41 +197,41 @@ slots: test->setHint("local"); { QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data")); - QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()}); + test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QFINDTESTDATA("testdata")); + QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/codecwav-20101023.jar")).absoluteFilePath()}); QCOMPARE(native, {}); QCOMPARE(native32, {}); QCOMPARE(native64, {}); } { QStringList failedFiles; - auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data")); + auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QFINDTESTDATA("testdata")); QCOMPARE(dls.size(), 0); QCOMPARE(failedFiles, {}); } } void test_onenine_local_override() { - auto test = readMojangJson("data/lib-simple.json"); + auto test = readMojangJson(QFINDTESTDATA("testdata/lib-simple.json")); test->setHint("local"); { QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data")); - QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()}); + test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QFINDTESTDATA("testdata")); + QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/codecwav-20101023.jar")).absoluteFilePath()}); QCOMPARE(native, {}); QCOMPARE(native32, {}); QCOMPARE(native64, {}); } { QStringList failedFiles; - auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data")); + auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QFINDTESTDATA("testdata")); QCOMPARE(dls.size(), 0); QCOMPARE(failedFiles, {}); } } void test_onenine_native() { - auto test = readMojangJson("data/lib-native.json"); + auto test = readMojangJson(QFINDTESTDATA("testdata/lib-native.json")); QStringList jar, native, native32, native64; test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); QCOMPARE(jar, QStringList()); @@ -248,7 +246,7 @@ slots: } void test_onenine_native_arch() { - auto test = readMojangJson("data/lib-native-arch.json"); + auto test = readMojangJson(QFINDTESTDATA("testdata/lib-native-arch.json")); QStringList jar, native, native32, native64; test->getApplicableFiles(Os_Windows, jar, native, native32, native64, QString()); QCOMPARE(jar, {}); diff --git a/launcher/minecraft/MojangVersionFormat_test.cpp b/launcher/minecraft/MojangVersionFormat_test.cpp index 9d0953406..71df784b7 100644 --- a/launcher/minecraft/MojangVersionFormat_test.cpp +++ b/launcher/minecraft/MojangVersionFormat_test.cpp @@ -1,6 +1,5 @@ #include #include -#include "TestUtil.h" #include "minecraft/MojangVersionFormat.h" @@ -8,9 +7,8 @@ class MojangVersionFormatTest : public QObject { Q_OBJECT - static QJsonDocument readJson(const char *file) + static QJsonDocument readJson(const QString path) { - auto path = QFINDTESTDATA(file); QFile jsonFile(path); jsonFile.open(QIODevice::ReadOnly); auto data = jsonFile.readAll(); @@ -31,7 +29,7 @@ private slots: void test_Through_Simple() { - QJsonDocument doc = readJson("data/1.9-simple.json"); + QJsonDocument doc = readJson(QFINDTESTDATA("testdata/1.9-simple.json")); auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9-simple.json"); auto doc2 = MojangVersionFormat::versionFileToJson(vfile); writeJson("1.9-simple-passthorugh.json", doc2); @@ -41,7 +39,7 @@ slots: void test_Through() { - QJsonDocument doc = readJson("data/1.9.json"); + QJsonDocument doc = readJson(QFINDTESTDATA("testdata/1.9.json")); auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9.json"); auto doc2 = MojangVersionFormat::versionFileToJson(vfile); writeJson("1.9-passthorugh.json", doc2); diff --git a/launcher/minecraft/ParseUtils_test.cpp b/launcher/minecraft/ParseUtils_test.cpp index fcc137e5d..7721a46db 100644 --- a/launcher/minecraft/ParseUtils_test.cpp +++ b/launcher/minecraft/ParseUtils_test.cpp @@ -1,5 +1,4 @@ #include -#include "TestUtil.h" #include "minecraft/ParseUtils.h" @@ -42,4 +41,3 @@ slots: QTEST_GUILESS_MAIN(ParseUtilsTest) #include "ParseUtils_test.moc" - diff --git a/launcher/minecraft/mod/ModFolderModel_test.cpp b/launcher/minecraft/mod/ModFolderModel_test.cpp index 34a3b83a4..b4d37ce53 100644 --- a/launcher/minecraft/mod/ModFolderModel_test.cpp +++ b/launcher/minecraft/mod/ModFolderModel_test.cpp @@ -35,7 +35,6 @@ #include #include -#include "TestUtil.h" #include "FileSystem.h" #include "minecraft/mod/ModFolderModel.h" @@ -50,7 +49,7 @@ slots: void test_1178() { // source - QString source = QFINDTESTDATA("data/test_folder"); + QString source = QFINDTESTDATA("testdata/test_folder"); // sanity check QVERIFY(!source.endsWith('/')); diff --git a/launcher/minecraft/mod/testdata/test_folder/assets/minecraft/textures/blah.txt b/launcher/minecraft/mod/testdata/test_folder/assets/minecraft/textures/blah.txt new file mode 100644 index 000000000..8d1c8b69c --- /dev/null +++ b/launcher/minecraft/mod/testdata/test_folder/assets/minecraft/textures/blah.txt @@ -0,0 +1 @@ + diff --git a/launcher/minecraft/mod/testdata/test_folder/pack.mcmeta b/launcher/minecraft/mod/testdata/test_folder/pack.mcmeta new file mode 100644 index 000000000..67ee04348 --- /dev/null +++ b/launcher/minecraft/mod/testdata/test_folder/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "pack_format": 1, + "description": "Some resource pack maybe" + } +} diff --git a/launcher/minecraft/mod/testdata/test_folder/pack.nfo b/launcher/minecraft/mod/testdata/test_folder/pack.nfo new file mode 100644 index 000000000..8d1c8b69c --- /dev/null +++ b/launcher/minecraft/mod/testdata/test_folder/pack.nfo @@ -0,0 +1 @@ + diff --git a/launcher/modplatform/packwiz/Packwiz_test.cpp b/launcher/modplatform/packwiz/Packwiz_test.cpp index 3d47f9f7b..d62511482 100644 --- a/launcher/modplatform/packwiz/Packwiz_test.cpp +++ b/launcher/modplatform/packwiz/Packwiz_test.cpp @@ -21,7 +21,6 @@ #include #include "Packwiz.h" -#include "TestUtil.h" class PackwizTest : public QObject { Q_OBJECT diff --git a/launcher/mojang/PackageManifest_test.cpp b/launcher/mojang/PackageManifest_test.cpp index d4c55c5aa..e8da4266c 100644 --- a/launcher/mojang/PackageManifest_test.cpp +++ b/launcher/mojang/PackageManifest_test.cpp @@ -1,6 +1,5 @@ #include #include -#include "TestUtil.h" #include "mojang/PackageManifest.h" diff --git a/launcher/settings/INIFile_test.cpp b/launcher/settings/INIFile_test.cpp index 08c2155eb..d23f9fdfa 100644 --- a/launcher/settings/INIFile_test.cpp +++ b/launcher/settings/INIFile_test.cpp @@ -1,5 +1,4 @@ #include -#include "TestUtil.h" #include "settings/INIFile.h" diff --git a/launcher/tasks/Task_test.cpp b/launcher/tasks/Task_test.cpp index 9b6cc2e55..ef153a6ac 100644 --- a/launcher/tasks/Task_test.cpp +++ b/launcher/tasks/Task_test.cpp @@ -1,5 +1,4 @@ #include -#include "TestUtil.h" #include "Task.h" diff --git a/launcher/updater/DownloadTask_test.cpp b/launcher/updater/DownloadTask_test.cpp index 8e823a63c..deba26321 100644 --- a/launcher/updater/DownloadTask_test.cpp +++ b/launcher/updater/DownloadTask_test.cpp @@ -1,8 +1,6 @@ #include #include -#include "TestUtil.h" - #include "updater/GoUpdate.h" #include "updater/DownloadTask.h" #include "updater/UpdateChecker.h" @@ -71,13 +69,23 @@ slots: void test_parseVersionInfo_data() { + QFile f1(QFINDTESTDATA("testdata/1.json")); + f1.open(QFile::ReadOnly); + QByteArray data1 = f1.readAll(); + f1.close(); + + QFile f2(QFINDTESTDATA("testdata/2.json")); + f2.open(QFile::ReadOnly); + QByteArray data2 = f2.readAll(); + f2.close(); + QTest::addColumn("data"); QTest::addColumn("list"); QTest::addColumn("error"); QTest::addColumn("ret"); QTest::newRow("one") - << GET_TEST_FILE("data/1.json") + << data1 << (VersionFileList() << VersionFileEntry{"fileOne", 493, @@ -93,7 +101,7 @@ slots: "f12df554b21e320be6471d7154130e70"}) << QString() << true; QTest::newRow("two") - << GET_TEST_FILE("data/2.json") + << data2 << (VersionFileList() << VersionFileEntry{"fileOne", 493, @@ -133,42 +141,42 @@ slots: QTest::newRow("test 1") << tempFolder << (VersionFileList() << VersionFileEntry{ - "data/fileOne", 493, + QFINDTESTDATA("testdata/fileOne"), 493, FileSourceList() << FileSource( "http", "http://host/path/fileOne-1"), "9eb84090956c484e32cb6c08455a667b"} << VersionFileEntry{ - "data/fileTwo", 644, + QFINDTESTDATA("testdata/fileTwo"), 644, FileSourceList() << FileSource( "http", "http://host/path/fileTwo-1"), "38f94f54fa3eb72b0ea836538c10b043"} << VersionFileEntry{ - "data/fileThree", 420, + QFINDTESTDATA("testdata/fileThree"), 420, FileSourceList() << FileSource( "http", "http://host/path/fileThree-1"), "f12df554b21e320be6471d7154130e70"}) << (VersionFileList() << VersionFileEntry{ - "data/fileOne", 493, + QFINDTESTDATA("testdata/fileOne"), 493, FileSourceList() << FileSource("http", "http://host/path/fileOne-2"), "42915a71277c9016668cce7b82c6b577"} << VersionFileEntry{ - "data/fileTwo", 644, + QFINDTESTDATA("testdata/fileTwo"), 644, FileSourceList() << FileSource("http", "http://host/path/fileTwo-2"), "38f94f54fa3eb72b0ea836538c10b043"}) << (OperationList() - << Operation::DeleteOp("data/fileThree") + << Operation::DeleteOp(QFINDTESTDATA("testdata/fileThree")) << Operation::CopyOp( FS::PathCombine(tempFolder, - QString("data/fileOne").replace("/", "_")), - "data/fileOne", 493)); + QFINDTESTDATA("data/fileOne").replace("/", "_")), + QFINDTESTDATA("data/fileOne"), 493)); } void test_processFileLists() { diff --git a/launcher/updater/UpdateChecker_test.cpp b/launcher/updater/UpdateChecker_test.cpp index ec55a40e8..70e3381f5 100644 --- a/launcher/updater/UpdateChecker_test.cpp +++ b/launcher/updater/UpdateChecker_test.cpp @@ -1,7 +1,6 @@ #include #include -#include "TestUtil.h" #include "updater/UpdateChecker.h" Q_DECLARE_METATYPE(UpdateChecker::ChannelListEntry) @@ -50,36 +49,36 @@ slots: QTest::newRow("garbage") << QString() - << findTestDataUrl("data/garbageChannels.json") + << findTestDataUrl("testdata/garbageChannels.json") << false << false << QList(); QTest::newRow("errors") << QString() - << findTestDataUrl("data/errorChannels.json") + << findTestDataUrl("testdata/errorChannels.json") << false << true << QList(); QTest::newRow("no channels") << QString() - << findTestDataUrl("data/noChannels.json") + << findTestDataUrl("testdata/noChannels.json") << false << true << QList(); QTest::newRow("one channel") << QString("develop") - << findTestDataUrl("data/oneChannel.json") + << findTestDataUrl("testdata/oneChannel.json") << true << true << (QList() << UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", "http://example.org/stuff"}); QTest::newRow("several channels") << QString("develop") - << findTestDataUrl("data/channels.json") + << findTestDataUrl("testdata/channels.json") << true << true << (QList() - << UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", findTestDataUrl("data")} - << UpdateChecker::ChannelListEntry{"stable", "Stable", "It's stable at least", findTestDataUrl("data")} + << UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", findTestDataUrl("testdata")} + << UpdateChecker::ChannelListEntry{"stable", "Stable", "It's stable at least", findTestDataUrl("testdata")} << UpdateChecker::ChannelListEntry{"42", "The Channel", "This is the channel that is going to answer all of your questions", "https://dent.me/tea"}); } void tst_ChannelListParsing() @@ -117,7 +116,7 @@ slots: void tst_UpdateChecking() { QString channel = "develop"; - QString channelUrl = findTestDataUrl("data/channels.json"); + QString channelUrl = findTestDataUrl("testdata/channels.json"); int currentBuild = 2; shared_qobject_ptr nam = new QNetworkAccessManager(); @@ -132,7 +131,7 @@ slots: QVERIFY(channelListLoadedSpy.wait()); qDebug() << "CWD:" << QDir::current().absolutePath(); - checker.m_channels[0].url = findTestDataUrl("data/"); + checker.m_channels[0].url = findTestDataUrl("testdata/"); checker.checkForUpdate(channel, false); QVERIFY(updateAvailableSpy.wait()); diff --git a/libraries/systeminfo/CMakeLists.txt b/libraries/systeminfo/CMakeLists.txt index 548a589c1..d68904f87 100644 --- a/libraries/systeminfo/CMakeLists.txt +++ b/libraries/systeminfo/CMakeLists.txt @@ -22,8 +22,4 @@ add_library(systeminfo STATIC ${systeminfo_SOURCES}) target_link_libraries(systeminfo Qt5::Core Qt5::Gui Qt5::Network) target_include_directories(systeminfo PUBLIC include) -include (UnitTest) -add_unit_test(sys - SOURCES src/sys_test.cpp - LIBS systeminfo -) +ecm_add_test(src/sys_test.cpp LINK_LIBRARIES systeminfo Qt5::Test TEST_NAME sys) diff --git a/libraries/systeminfo/src/sys_test.cpp b/libraries/systeminfo/src/sys_test.cpp index 315050d29..9a5f9dfa2 100644 --- a/libraries/systeminfo/src/sys_test.cpp +++ b/libraries/systeminfo/src/sys_test.cpp @@ -1,5 +1,4 @@ #include -#include "TestUtil.h" #include From 3fbbaddeceadd1d154f2aa5450974b228512fe60 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 12 Jun 2022 13:36:55 +0200 Subject: [PATCH 225/308] fix(actions): install extra-cmake-modules --- .github/workflows/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 79cc2129d..b544a8e4c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -61,6 +61,7 @@ jobs: pacboy: >- toolchain:p cmake:p + extra-cmake-modules:p ninja:p qt5:p ccache:p @@ -107,7 +108,7 @@ jobs: if: runner.os == 'macOS' run: | brew update - brew install qt@5 ninja + brew install qt@5 ninja extra-cmake-modules - name: Update Qt (AppImage) if: runner.os == 'Linux' && matrix.appimage == true @@ -121,7 +122,7 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get -y update - sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 ninja-build qt5-image-formats-plugins + sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 ninja-build qt5-image-formats-plugins extra-cmake-modules - name: Prepare AppImage (Linux) if: runner.os == 'Linux' && matrix.appimage == true From 3d0740f80e8d2d1bd0f83c914b53d074d73e7c97 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 14 Jun 2022 21:42:44 +0200 Subject: [PATCH 226/308] fix(cmake): allow disabling tests --- CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 96abe22e4..5c5144f68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,10 +65,9 @@ option(BUILD_TESTING "Build the testing tree." ON) find_package(ECM REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH "${ECM_MODULE_PATH};${CMAKE_MODULE_PATH}") +include(CTest) +include(ECMAddTests) if (BUILD_TESTING) - include(CTest) - include(ECMAddTests) - enable_testing() endif() From 8e3efec40fed65daa48bfd5290b552928e76d6f1 Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Wed, 15 Jun 2022 00:41:52 -0400 Subject: [PATCH 227/308] Unselect shortcut installation by default if PolyMC is already installed This is the same behavior as the `/NoShortcuts` switch. --- program_info/win_install.nsi.in | 1 + 1 file changed, 1 insertion(+) diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index e5687de7b..98a87880a 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -245,6 +245,7 @@ Function .onInit ${GetParameters} $R0 ${GetOptions} $R0 "/NoShortcuts" $R1 ${IfNot} ${Errors} +${OrIf} ${FileExists} "$InstDir\@Launcher_APP_BINARY_NAME@.exe" !insertmacro UnselectSection ${SM_SHORTCUTS} !insertmacro UnselectSection ${DESKTOP_SHORTCUTS} ${EndIf} From 251942323e53913112bf807cedf54c98ddc5a2a4 Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Wed, 15 Jun 2022 00:46:34 -0400 Subject: [PATCH 228/308] Deselect desktop shortcut by default Follows the guidelines at https://docs.microsoft.com/en-us/windows/win32/uxguide/winenv-desktop --- program_info/win_install.nsi.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index 98a87880a..1997d6b6a 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -157,7 +157,7 @@ Section "Start Menu Shortcut" SM_SHORTCUTS SectionEnd -Section "Desktop Shortcut" DESKTOP_SHORTCUTS +Section /o "Desktop Shortcut" DESKTOP_SHORTCUTS CreateShortcut "$DESKTOP\@Launcher_CommonName@.lnk" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" 0 From 1f6cef6f8a678be49e091a7f11123fbfb1ef749a Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Wed, 15 Jun 2022 09:11:23 +0200 Subject: [PATCH 229/308] fix https://github.com/PolyMC/PolyMC/issues/798 --- launcher/modplatform/flame/FlamePackIndex.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/modplatform/flame/FlamePackIndex.cpp b/launcher/modplatform/flame/FlamePackIndex.cpp index ba1622d17..ad48b7b6a 100644 --- a/launcher/modplatform/flame/FlamePackIndex.cpp +++ b/launcher/modplatform/flame/FlamePackIndex.cpp @@ -53,7 +53,7 @@ void Flame::loadIndexedInfo(IndexedPack& pack, QJsonObject& obj) { auto links_obj = Json::ensureObject(obj, "links"); - pack.extra.websiteUrl = Json::ensureString(links_obj, "issuesUrl"); + pack.extra.websiteUrl = Json::ensureString(links_obj, "websiteUrl"); if(pack.extra.websiteUrl.endsWith('/')) pack.extra.websiteUrl.chop(1); From 0ba02f0830c2c15df345aa8889bbb6e7945d2a93 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 15 Jun 2022 10:05:35 +0200 Subject: [PATCH 230/308] refactor: rename NewLaunch package --- libraries/launcher/CMakeLists.txt | 20 +++++++++---------- .../org/{multimc => polymc}/EntryPoint.java | 6 +++--- .../org/{multimc => polymc}/Launcher.java | 2 +- .../{multimc => polymc}/LauncherFactory.java | 6 +++--- .../applet/LegacyFrame.java | 2 +- .../exception/ParameterNotFoundException.java | 2 +- .../exception/ParseException.java | 2 +- .../impl/OneSixLauncher.java | 10 +++++----- .../{multimc => polymc}/utils/Parameters.java | 4 ++-- .../org/{multimc => polymc}/utils/Utils.java | 2 +- 10 files changed, 28 insertions(+), 28 deletions(-) rename libraries/launcher/org/{multimc => polymc}/EntryPoint.java (97%) rename libraries/launcher/org/{multimc => polymc}/Launcher.java (97%) rename libraries/launcher/org/{multimc => polymc}/LauncherFactory.java (94%) rename libraries/launcher/org/{multimc => polymc}/applet/LegacyFrame.java (99%) rename libraries/launcher/org/{multimc => polymc}/exception/ParameterNotFoundException.java (96%) rename libraries/launcher/org/{multimc => polymc}/exception/ParseException.java (96%) rename libraries/launcher/org/{multimc => polymc}/impl/OneSixLauncher.java (97%) rename libraries/launcher/org/{multimc => polymc}/utils/Parameters.java (95%) rename libraries/launcher/org/{multimc => polymc}/utils/Utils.java (98%) diff --git a/libraries/launcher/CMakeLists.txt b/libraries/launcher/CMakeLists.txt index 2c859499d..c4dfa5b70 100644 --- a/libraries/launcher/CMakeLists.txt +++ b/libraries/launcher/CMakeLists.txt @@ -3,19 +3,19 @@ project(launcher Java) find_package(Java 1.7 REQUIRED COMPONENTS Development) include(UseJava) -set(CMAKE_JAVA_JAR_ENTRY_POINT org.multimc.EntryPoint) +set(CMAKE_JAVA_JAR_ENTRY_POINT org.polymc.EntryPoint) set(CMAKE_JAVA_COMPILE_FLAGS -target 7 -source 7 -Xlint:deprecation -Xlint:unchecked) set(SRC - org/multimc/EntryPoint.java - org/multimc/Launcher.java - org/multimc/LauncherFactory.java - org/multimc/impl/OneSixLauncher.java - org/multimc/applet/LegacyFrame.java - org/multimc/exception/ParameterNotFoundException.java - org/multimc/exception/ParseException.java - org/multimc/utils/Parameters.java - org/multimc/utils/Utils.java + org/polymc/EntryPoint.java + org/polymc/Launcher.java + org/polymc/LauncherFactory.java + org/polymc/impl/OneSixLauncher.java + org/polymc/applet/LegacyFrame.java + org/polymc/exception/ParameterNotFoundException.java + org/polymc/exception/ParseException.java + org/polymc/utils/Parameters.java + org/polymc/utils/Utils.java net/minecraft/Launcher.java ) add_jar(NewLaunch ${SRC}) diff --git a/libraries/launcher/org/multimc/EntryPoint.java b/libraries/launcher/org/polymc/EntryPoint.java similarity index 97% rename from libraries/launcher/org/multimc/EntryPoint.java rename to libraries/launcher/org/polymc/EntryPoint.java index c0500bbec..abb44596f 100644 --- a/libraries/launcher/org/multimc/EntryPoint.java +++ b/libraries/launcher/org/polymc/EntryPoint.java @@ -33,10 +33,10 @@ * limitations under the License. */ -package org.multimc; +package org.polymc; -import org.multimc.exception.ParseException; -import org.multimc.utils.Parameters; +import org.polymc.exception.ParseException; +import org.polymc.utils.Parameters; import java.io.BufferedReader; import java.io.IOException; diff --git a/libraries/launcher/org/multimc/Launcher.java b/libraries/launcher/org/polymc/Launcher.java similarity index 97% rename from libraries/launcher/org/multimc/Launcher.java rename to libraries/launcher/org/polymc/Launcher.java index bc0b525eb..5bff123e7 100644 --- a/libraries/launcher/org/multimc/Launcher.java +++ b/libraries/launcher/org/polymc/Launcher.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.multimc; +package org.polymc; public interface Launcher { diff --git a/libraries/launcher/org/multimc/LauncherFactory.java b/libraries/launcher/org/polymc/LauncherFactory.java similarity index 94% rename from libraries/launcher/org/multimc/LauncherFactory.java rename to libraries/launcher/org/polymc/LauncherFactory.java index a2af8581a..c8c63f80f 100644 --- a/libraries/launcher/org/multimc/LauncherFactory.java +++ b/libraries/launcher/org/polymc/LauncherFactory.java @@ -16,10 +16,10 @@ * along with this program. If not, see . */ -package org.multimc; +package org.polymc; -import org.multimc.impl.OneSixLauncher; -import org.multimc.utils.Parameters; +import org.polymc.impl.OneSixLauncher; +import org.polymc.utils.Parameters; import java.util.HashMap; import java.util.Map; diff --git a/libraries/launcher/org/multimc/applet/LegacyFrame.java b/libraries/launcher/org/polymc/applet/LegacyFrame.java similarity index 99% rename from libraries/launcher/org/multimc/applet/LegacyFrame.java rename to libraries/launcher/org/polymc/applet/LegacyFrame.java index caec079c3..2cdd17d7c 100644 --- a/libraries/launcher/org/multimc/applet/LegacyFrame.java +++ b/libraries/launcher/org/polymc/applet/LegacyFrame.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.multimc.applet; +package org.polymc.applet; import net.minecraft.Launcher; diff --git a/libraries/launcher/org/multimc/exception/ParameterNotFoundException.java b/libraries/launcher/org/polymc/exception/ParameterNotFoundException.java similarity index 96% rename from libraries/launcher/org/multimc/exception/ParameterNotFoundException.java rename to libraries/launcher/org/polymc/exception/ParameterNotFoundException.java index 9edbb8261..2044814e9 100644 --- a/libraries/launcher/org/multimc/exception/ParameterNotFoundException.java +++ b/libraries/launcher/org/polymc/exception/ParameterNotFoundException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.multimc.exception; +package org.polymc.exception; public final class ParameterNotFoundException extends IllegalArgumentException { diff --git a/libraries/launcher/org/multimc/exception/ParseException.java b/libraries/launcher/org/polymc/exception/ParseException.java similarity index 96% rename from libraries/launcher/org/multimc/exception/ParseException.java rename to libraries/launcher/org/polymc/exception/ParseException.java index 848b395de..2f2f82944 100644 --- a/libraries/launcher/org/multimc/exception/ParseException.java +++ b/libraries/launcher/org/polymc/exception/ParseException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.multimc.exception; +package org.polymc.exception; public final class ParseException extends IllegalArgumentException { diff --git a/libraries/launcher/org/multimc/impl/OneSixLauncher.java b/libraries/launcher/org/polymc/impl/OneSixLauncher.java similarity index 97% rename from libraries/launcher/org/multimc/impl/OneSixLauncher.java rename to libraries/launcher/org/polymc/impl/OneSixLauncher.java index b981e4ff4..362ff8d63 100644 --- a/libraries/launcher/org/multimc/impl/OneSixLauncher.java +++ b/libraries/launcher/org/polymc/impl/OneSixLauncher.java @@ -13,12 +13,12 @@ * limitations under the License. */ -package org.multimc.impl; +package org.polymc.impl; -import org.multimc.Launcher; -import org.multimc.applet.LegacyFrame; -import org.multimc.utils.Parameters; -import org.multimc.utils.Utils; +import org.polymc.Launcher; +import org.polymc.applet.LegacyFrame; +import org.polymc.utils.Parameters; +import org.polymc.utils.Utils; import java.applet.Applet; import java.io.File; diff --git a/libraries/launcher/org/multimc/utils/Parameters.java b/libraries/launcher/org/polymc/utils/Parameters.java similarity index 95% rename from libraries/launcher/org/multimc/utils/Parameters.java rename to libraries/launcher/org/polymc/utils/Parameters.java index 7be790c29..864d3cd22 100644 --- a/libraries/launcher/org/multimc/utils/Parameters.java +++ b/libraries/launcher/org/polymc/utils/Parameters.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.multimc.utils; +package org.polymc.utils; -import org.multimc.exception.ParameterNotFoundException; +import org.polymc.exception.ParameterNotFoundException; import java.util.ArrayList; import java.util.HashMap; diff --git a/libraries/launcher/org/multimc/utils/Utils.java b/libraries/launcher/org/polymc/utils/Utils.java similarity index 98% rename from libraries/launcher/org/multimc/utils/Utils.java rename to libraries/launcher/org/polymc/utils/Utils.java index 416eff26b..12d6e1aac 100644 --- a/libraries/launcher/org/multimc/utils/Utils.java +++ b/libraries/launcher/org/polymc/utils/Utils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.multimc.utils; +package org.polymc.utils; import java.io.File; import java.lang.reflect.Field; From 8b9ac636573eb62f8a6bbf5ae39f61155f36d268 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 15 Jun 2022 10:15:35 +0200 Subject: [PATCH 231/308] chore: update COPYING.md --- COPYING.md | 81 +++++++++++++++++++++++++++----------- libraries/README.md | 2 +- libraries/launcher/LICENSE | 1 + 3 files changed, 61 insertions(+), 23 deletions(-) create mode 120000 libraries/launcher/LICENSE diff --git a/COPYING.md b/COPYING.md index 273a5b3af..058560168 100644 --- a/COPYING.md +++ b/COPYING.md @@ -1,33 +1,36 @@ # PolyMC - Copyright (C) 2012-2021 MultiMC Contributors - Copyright (C) 2021-2022 PolyMC Contributors + PolyMC - Minecraft Launcher + Copyright (C) 2021-2022 PolyMC Contributors - 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . -# Launcher (https://github.com/MultiMC/Launcher) - Copyright 2012-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 + This file incorporates work covered by the following copyright and + permission notice: - http://www.apache.org/licenses/LICENSE-2.0 + Copyright 2013-2021 MultiMC Contributors - 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. + 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. # MinGW runtime (Windows) @@ -213,6 +216,40 @@ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# launcher (`libraries/launcher`) + + PolyMC - Minecraft Launcher + Copyright (C) 2021-2022 PolyMC Contributors + + 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 . + + 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. + # lionshead Code has been taken from https://github.com/natefoo/lionshead and loosely diff --git a/libraries/README.md b/libraries/README.md index 7e7e740d1..63209d816 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -125,7 +125,7 @@ cp /home/peterix/minecraft/FTB/versions/1.7.10/1.7.10.jar launcher onesix ``` -Available under the Apache 2.0 license. +Available under `GPL-3.0-only`, sublicensed from its original `Apache-2.0` codebase ## libnbtplusplus libnbt++ is a free C++ library for Minecraft's file format Named Binary Tag (NBT). It can read and write compressed and uncompressed NBT files and provides a code interface for working with NBT data. diff --git a/libraries/launcher/LICENSE b/libraries/launcher/LICENSE new file mode 120000 index 000000000..30cff7403 --- /dev/null +++ b/libraries/launcher/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file From 08fc3ea2e0f9adf58b139a0bd8aaed604f241342 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 15 Jun 2022 10:39:56 +0200 Subject: [PATCH 232/308] fix: add classpath exception to launcher library Signed-off-by: icelimetea --- COPYING.md | 17 +++++++++++++++++ libraries/README.md | 2 +- libraries/launcher/org/polymc/EntryPoint.java | 17 +++++++++++++++++ .../launcher/org/polymc/LauncherFactory.java | 17 +++++++++++++++++ 4 files changed, 52 insertions(+), 1 deletion(-) diff --git a/COPYING.md b/COPYING.md index 058560168..191ea7853 100644 --- a/COPYING.md +++ b/COPYING.md @@ -230,6 +230,23 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + Linking this library statically or dynamically with other modules is + making a combined work based on this library. Thus, the terms and + conditions of the GNU General Public License cover the whole + combination. + + As a special exception, the copyright holders of this library give + you permission to link this library with independent modules to + produce an executable, regardless of the license terms of these + independent modules, and to copy and distribute the resulting + executable under terms of your choice, provided that you also meet, + for each linked independent module, the terms and conditions of the + license of that module. An independent module is a module which is + not derived from or based on this library. If you modify this + library, you may extend this exception to your version of the + library, but you are not obliged to do so. If you do not wish to do + so, delete this exception statement from your version. + You should have received a copy of the GNU General Public License along with this program. If not, see . diff --git a/libraries/README.md b/libraries/README.md index 63209d816..49879a267 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -125,7 +125,7 @@ cp /home/peterix/minecraft/FTB/versions/1.7.10/1.7.10.jar launcher onesix ``` -Available under `GPL-3.0-only`, sublicensed from its original `Apache-2.0` codebase +Available under `GPL-3.0-only` (with classpath exception), sublicensed from its original `Apache-2.0` codebase ## libnbtplusplus libnbt++ is a free C++ library for Minecraft's file format Named Binary Tag (NBT). It can read and write compressed and uncompressed NBT files and provides a code interface for working with NBT data. diff --git a/libraries/launcher/org/polymc/EntryPoint.java b/libraries/launcher/org/polymc/EntryPoint.java index abb44596f..20f418ebc 100644 --- a/libraries/launcher/org/polymc/EntryPoint.java +++ b/libraries/launcher/org/polymc/EntryPoint.java @@ -12,6 +12,23 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * + * Linking this library statically or dynamically with other modules is + * making a combined work based on this library. Thus, the terms and + * conditions of the GNU General Public License cover the whole + * combination. + * + * As a special exception, the copyright holders of this library give + * you permission to link this library with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also meet, + * for each linked independent module, the terms and conditions of the + * license of that module. An independent module is a module which is + * not derived from or based on this library. If you modify this + * library, you may extend this exception to your version of the + * library, but you are not obliged to do so. If you do not wish to do + * so, delete this exception statement from your version. + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * diff --git a/libraries/launcher/org/polymc/LauncherFactory.java b/libraries/launcher/org/polymc/LauncherFactory.java index c8c63f80f..868629297 100644 --- a/libraries/launcher/org/polymc/LauncherFactory.java +++ b/libraries/launcher/org/polymc/LauncherFactory.java @@ -12,6 +12,23 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * + * Linking this library statically or dynamically with other modules is + * making a combined work based on this library. Thus, the terms and + * conditions of the GNU General Public License cover the whole + * combination. + * + * As a special exception, the copyright holders of this library give + * you permission to link this library with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also meet, + * for each linked independent module, the terms and conditions of the + * license of that module. An independent module is a module which is + * not derived from or based on this library. If you modify this + * library, you may extend this exception to your version of the + * library, but you are not obliged to do so. If you do not wish to do + * so, delete this exception statement from your version. + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ From 4c6ac9e4fe26085d7f3532194d516d79d691d8a2 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Fri, 17 Jun 2022 15:56:19 +0200 Subject: [PATCH 233/308] chore: add LGTM config --- lgtm.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 lgtm.yml diff --git a/lgtm.yml b/lgtm.yml new file mode 100644 index 000000000..39cd3036b --- /dev/null +++ b/lgtm.yml @@ -0,0 +1,2 @@ +queries: + - exclude: "cpp/fixme-comment" # We like to use FIXME From 9ec260619b48447e398445aecd6651d319b8217e Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Fri, 17 Jun 2022 16:34:32 +0200 Subject: [PATCH 234/308] fix: fix warnings reported by LGTM.com --- launcher/minecraft/auth/AccountTask.cpp | 2 ++ launcher/modplatform/flame/FlameAPI.h | 2 +- launcher/translations/POTranslator.cpp | 5 +++++ launcher/translations/POTranslator.h | 1 + libraries/LocalPeer/src/LocalPeer.cpp | 8 ++++---- libraries/classparser/src/annotations.cpp | 4 ++-- libraries/classparser/src/classfile.h | 4 ++-- libraries/classparser/src/constants.h | 5 +++-- 8 files changed, 20 insertions(+), 11 deletions(-) diff --git a/launcher/minecraft/auth/AccountTask.cpp b/launcher/minecraft/auth/AccountTask.cpp index 49b6e46fc..4118c3c5d 100644 --- a/launcher/minecraft/auth/AccountTask.cpp +++ b/launcher/minecraft/auth/AccountTask.cpp @@ -79,6 +79,8 @@ QString AccountTask::getStateMessage() const bool AccountTask::changeState(AccountTaskState newState, QString reason) { m_taskState = newState; + // FIXME: virtual method invoked in constructor. + // We want that behavior, but maybe make it less weird? setStatus(getStateMessage()); switch(newState) { case AccountTaskState::STATE_CREATED: { diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 424153d28..aea76ff19 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -59,7 +59,7 @@ class FlameAPI : public NetworkModAPI { }; public: - static auto getMappedModLoader(const ModLoaderTypes loaders) -> const int + static auto getMappedModLoader(const ModLoaderTypes loaders) -> int { // https://docs.curseforge.com/?http#tocS_ModLoaderType if (loaders & Forge) diff --git a/launcher/translations/POTranslator.cpp b/launcher/translations/POTranslator.cpp index 1ffcb9a40..c77ae45d3 100644 --- a/launcher/translations/POTranslator.cpp +++ b/launcher/translations/POTranslator.cpp @@ -329,6 +329,11 @@ POTranslator::POTranslator(const QString& filename, QObject* parent) : QTranslat d->reload(); } +POTranslator::~POTranslator() +{ + delete d; +} + QString POTranslator::translate(const char* context, const char* sourceText, const char* disambiguation, int n) const { if(disambiguation) diff --git a/launcher/translations/POTranslator.h b/launcher/translations/POTranslator.h index 6d5185601..1634018cc 100644 --- a/launcher/translations/POTranslator.h +++ b/launcher/translations/POTranslator.h @@ -9,6 +9,7 @@ class POTranslator : public QTranslator Q_OBJECT public: explicit POTranslator(const QString& filename, QObject * parent = nullptr); + virtual ~POTranslator(); QString translate(const char * context, const char * sourceText, const char * disambiguation, int n) const override; bool isEmpty() const override; private: diff --git a/libraries/LocalPeer/src/LocalPeer.cpp b/libraries/LocalPeer/src/LocalPeer.cpp index cb218466f..2c996ae78 100644 --- a/libraries/LocalPeer/src/LocalPeer.cpp +++ b/libraries/LocalPeer/src/LocalPeer.cpp @@ -162,15 +162,15 @@ bool LocalPeer::sendMessage(const QByteArray &message, int timeout) QLocalSocket socket; bool connOk = false; - for(int i = 0; i < 2; i++) { + int tries = 2; + for(int i = 0; i < tries; i++) { // Try twice, in case the other instance is just starting up socket.connectToServer(socketName); connOk = socket.waitForConnected(timeout/2); - if (connOk || i) + if (!connOk && i < (tries - 1)) { - break; + std::this_thread::sleep_for(std::chrono::milliseconds(250)); } - std::this_thread::sleep_for(std::chrono::milliseconds(250)); } if (!connOk) { diff --git a/libraries/classparser/src/annotations.cpp b/libraries/classparser/src/annotations.cpp index 18a9e880a..89b201bc8 100644 --- a/libraries/classparser/src/annotations.cpp +++ b/libraries/classparser/src/annotations.cpp @@ -79,7 +79,7 @@ element_value *element_value::readElementValue(util::membuffer &input, } return new element_value_array(ARRAY, vals, pool); default: - throw new java::classfile_exception(); + throw java::classfile_exception(); } } -} \ No newline at end of file +} diff --git a/libraries/classparser/src/classfile.h b/libraries/classparser/src/classfile.h index 1616a828e..d629dde1f 100644 --- a/libraries/classparser/src/classfile.h +++ b/libraries/classparser/src/classfile.h @@ -17,7 +17,7 @@ public: is_synthetic = false; read_be(magic); if (magic != 0xCAFEBABE) - throw new classfile_exception(); + throw classfile_exception(); read_be(minor_version); read_be(major_version); constants.load(*this); @@ -153,4 +153,4 @@ public: // FIXME: doesn't free up memory on delete java::annotation_table visible_class_annotations; }; -} \ No newline at end of file +} diff --git a/libraries/classparser/src/constants.h b/libraries/classparser/src/constants.h index 3b6c3b7ae..47b325b93 100644 --- a/libraries/classparser/src/constants.h +++ b/libraries/classparser/src/constants.h @@ -1,5 +1,6 @@ #pragma once #include "errors.h" +#include "membuffer.h" #include namespace java @@ -90,7 +91,7 @@ public: break; default: // invalid constant type! - throw new classfile_exception(); + throw classfile_exception(); } } constant(int) @@ -210,7 +211,7 @@ public: { if (constant_index == 0 || constant_index > constants.size()) { - throw new classfile_exception(); + throw classfile_exception(); } return constants[constant_index - 1]; } From 4b6ddfb89b1bcff163500b274bfa2bef98f33e71 Mon Sep 17 00:00:00 2001 From: jn64 <23169302+jn64@users.noreply.github.com> Date: Sat, 18 Jun 2022 20:00:28 +0800 Subject: [PATCH 235/308] Add version to Qt applicationDisplayName --- launcher/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index ab3110a3a..4e8b5b0fe 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -226,7 +226,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) setOrganizationName(BuildConfig.LAUNCHER_NAME); setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN); setApplicationName(BuildConfig.LAUNCHER_NAME); - setApplicationDisplayName(BuildConfig.LAUNCHER_DISPLAYNAME); + setApplicationDisplayName(QString("%1 %2").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString())); setApplicationVersion(BuildConfig.printableVersionString()); setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME); startTime = QDateTime::currentDateTime(); From 6d1b166ad7378b88688abd05820680e416dea223 Mon Sep 17 00:00:00 2001 From: jn64 <23169302+jn64@users.noreply.github.com> Date: Sat, 18 Jun 2022 22:19:23 +0800 Subject: [PATCH 236/308] Make labels selectable User can copy version/build info easily. --- launcher/ui/dialogs/AboutDialog.ui | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/launcher/ui/dialogs/AboutDialog.ui b/launcher/ui/dialogs/AboutDialog.ui index 70c5009d5..591a22fb9 100644 --- a/launcher/ui/dialogs/AboutDialog.ui +++ b/launcher/ui/dialogs/AboutDialog.ui @@ -92,6 +92,9 @@ Qt::AlignCenter + + Qt::TextSelectableByMouse + @@ -166,6 +169,9 @@ Qt::AlignCenter + + Qt::TextSelectableByMouse + @@ -176,6 +182,9 @@ Qt::AlignCenter + + Qt::TextSelectableByMouse + @@ -186,6 +195,9 @@ Qt::AlignCenter + + Qt::TextSelectableByMouse + From 0afa2e92d535cfdd41759e7563891ef843b7b9cd Mon Sep 17 00:00:00 2001 From: jn64 <23169302+jn64@users.noreply.github.com> Date: Sat, 18 Jun 2022 22:20:38 +0800 Subject: [PATCH 237/308] Make GitHub link focusable by keyboard --- launcher/ui/dialogs/AboutDialog.ui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/ui/dialogs/AboutDialog.ui b/launcher/ui/dialogs/AboutDialog.ui index 591a22fb9..ff9dc7ce8 100644 --- a/launcher/ui/dialogs/AboutDialog.ui +++ b/launcher/ui/dialogs/AboutDialog.ui @@ -136,6 +136,9 @@ Qt::AlignCenter + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse + From 7f62de3854e7e1ed7a4609def3efa300986322ff Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 19 Jun 2022 22:03:02 -0300 Subject: [PATCH 238/308] fix: don't create unnecessary folders when extracting ZIPs --- launcher/MMCZip.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 627ceaf10..d7ad4428f 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -305,7 +305,7 @@ nonstd::optional MMCZip::extractSubDir(QuaZip *zip, const QString & QString path; if(name.contains('/') && !name.endsWith('/')){ path = name.section('/', 0, -2) + "/"; - FS::ensureFolderPathExists(path); + FS::ensureFolderPathExists(FS::PathCombine(target, path)); name = name.split('/').last(); } From 5335540c333c392019e89bd8fba9ff56250cecdd Mon Sep 17 00:00:00 2001 From: jn64 <23169302+jn64@users.noreply.github.com> Date: Sat, 18 Jun 2022 20:00:54 +0800 Subject: [PATCH 239/308] Rename main window --- launcher/ui/MainWindow.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 210442df3..8c70cd495 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -822,7 +822,7 @@ public: } MainWindow->resize(800, 600); MainWindow->setWindowIcon(APPLICATION->getThemedIcon("logo")); - MainWindow->setWindowTitle(BuildConfig.LAUNCHER_DISPLAYNAME); + MainWindow->setWindowTitle(APPLICATION->applicationDisplayName()); #ifndef QT_NO_ACCESSIBILITY MainWindow->setAccessibleName(BuildConfig.LAUNCHER_NAME); #endif @@ -857,8 +857,6 @@ public: void retranslateUi(MainWindow *MainWindow) { - QString winTitle = tr("%1 - Version %2", "Launcher - Version X").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString()); - MainWindow->setWindowTitle(winTitle); // all the actions for(auto * item: all_actions) { From fa6829a6a12c1f48ebdf5bbb29b18e26b1b3b518 Mon Sep 17 00:00:00 2001 From: jn64 <23169302+jn64@users.noreply.github.com> Date: Mon, 20 Jun 2022 14:28:38 +0800 Subject: [PATCH 240/308] Set beam cursor on selectable labels --- launcher/ui/dialogs/AboutDialog.ui | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/launcher/ui/dialogs/AboutDialog.ui b/launcher/ui/dialogs/AboutDialog.ui index ff9dc7ce8..6323992b9 100644 --- a/launcher/ui/dialogs/AboutDialog.ui +++ b/launcher/ui/dialogs/AboutDialog.ui @@ -89,6 +89,9 @@ + + IBeamCursor + Qt::AlignCenter @@ -166,6 +169,9 @@ + + IBeamCursor + Platform: @@ -179,6 +185,9 @@ + + IBeamCursor + Build Number: @@ -192,6 +201,9 @@ + + IBeamCursor + Channel: From 5558d7eef8d838990b8e5a4af48261b08f99be9b Mon Sep 17 00:00:00 2001 From: OldWorldOrdr Date: Mon, 20 Jun 2022 11:03:22 -0400 Subject: [PATCH 241/308] Update bug_report.yml --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f07698c50..bac73932e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -27,7 +27,7 @@ body: attributes: label: Version of PolyMC description: The version of PolyMC used in the bug report. - placeholder: PolyMC 1.3.1 + placeholder: PolyMC 1.3.2 validations: required: true - type: textarea From a135c06bcfb70da4a74d1ba671f8dff04e199dc5 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 19 Jun 2022 23:01:31 -0300 Subject: [PATCH 242/308] fix: scale mod icons to the right size --- launcher/ui/pages/modplatform/ModModel.cpp | 6 +++++- launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 98eec31c7..4917b8908 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -53,7 +53,11 @@ auto ListModel::data(const QModelIndex& index, int role) const -> QVariant } case Qt::DecorationRole: { if (m_logoMap.contains(pack.logoName)) { - return (m_logoMap.value(pack.logoName)); + auto icon = m_logoMap.value(pack.logoName); + // FIXME: This doesn't really belong here, but Qt doesn't offer a good way right now ;( + auto icon_scaled = QIcon(icon.pixmap(48, 48).scaledToWidth(48)); + + return icon_scaled; } QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); // un-const-ify this diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 07d1687ce..a0050e50d 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -87,6 +87,7 @@ auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVarian } else if (role == Qt::DecorationRole) { if (m_logoMap.contains(pack.iconName)) { auto icon = m_logoMap.value(pack.iconName); + // FIXME: This doesn't really belong here, but Qt doesn't offer a good way right now ;( auto icon_scaled = QIcon(icon.pixmap(48, 48).scaledToWidth(48)); return icon_scaled; From d4e544c62caaf18c797cafc5900ce019f2d71536 Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Sun, 19 Jun 2022 14:33:34 -0400 Subject: [PATCH 243/308] Separate the kill and launch instance actions --- launcher/ui/MainWindow.cpp | 86 ++++++++++++++++---------------------- launcher/ui/MainWindow.h | 2 + 2 files changed, 38 insertions(+), 50 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 210442df3..ebd86228b 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -224,6 +224,7 @@ public: TranslatedAction actionMoreNews; TranslatedAction actionManageAccounts; TranslatedAction actionLaunchInstance; + TranslatedAction actionKillInstance; TranslatedAction actionRenameInstance; TranslatedAction actionChangeInstGroup; TranslatedAction actionChangeInstIcon; @@ -282,27 +283,6 @@ public: TranslatedToolbar instanceToolBar; TranslatedToolbar newsToolBar; QVector all_toolbars; - bool m_kill = false; - - void updateLaunchAction() - { - if(m_kill) - { - actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Kill")); - actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Kill the running instance")); - } - else - { - actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Launch")); - actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance.")); - } - actionLaunchInstance.retranslate(); - } - void setLaunchAction(bool kill) - { - m_kill = kill; - updateLaunchAction(); - } void createMainToolbarActions(QMainWindow *MainWindow) { @@ -506,6 +486,7 @@ public: fileMenu->addAction(actionAddInstance); fileMenu->addAction(actionLaunchInstance); fileMenu->addAction(actionLaunchInstanceOffline); + fileMenu->addAction(actionKillInstance); fileMenu->addAction(actionCloseWindow); fileMenu->addSeparator(); fileMenu->addAction(actionEditInstance); @@ -580,10 +561,9 @@ public: } // "Instance actions" are actions that require an instance to be selected (i.e. "new instance" is not here) + // Actions that also require other conditions (e.g. a running instance) won't be changed. void setInstanceActionsEnabled(bool enabled) { - actionLaunchInstance->setEnabled(enabled); - actionLaunchInstanceOffline->setEnabled(enabled); actionEditInstance->setEnabled(enabled); actionEditInstNotes->setEnabled(enabled); actionMods->setEnabled(enabled); @@ -670,6 +650,14 @@ public: actionLaunchInstanceOffline.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance in offline mode.")); all_actions.append(&actionLaunchInstanceOffline); + actionKillInstance = TranslatedAction(MainWindow); + actionKillInstance->setObjectName(QStringLiteral("actionKillInstance")); + actionKillInstance->setDisabled(true); + actionKillInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Kill")); + actionKillInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Kill the running instance")); + actionKillInstance->setShortcut(QKeySequence(tr("Ctrl+K"))); + all_actions.append(&actionKillInstance); + actionEditInstance = TranslatedAction(MainWindow); actionEditInstance->setObjectName(QStringLiteral("actionEditInstance")); actionEditInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Edit Inst&ance...")); @@ -785,6 +773,7 @@ public: instanceToolBar->addAction(actionLaunchInstance); instanceToolBar->addAction(actionLaunchInstanceOffline); + instanceToolBar->addAction(actionKillInstance); instanceToolBar->addSeparator(); @@ -1184,14 +1173,10 @@ void MainWindow::updateToolsMenu() QToolButton *launchButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance)); QToolButton *launchOfflineButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceOffline)); - if(m_selectedInstance && m_selectedInstance->isRunning()) - { - ui->actionLaunchInstance->setMenu(nullptr); - ui->actionLaunchInstanceOffline->setMenu(nullptr); - launchButton->setPopupMode(QToolButton::InstantPopup); - launchOfflineButton->setPopupMode(QToolButton::InstantPopup); - return; - } + bool currentInstanceRunning = m_selectedInstance && m_selectedInstance->isRunning(); + + ui->actionLaunchInstance->setDisabled(!m_selectedInstance || currentInstanceRunning); + ui->actionLaunchInstanceOffline->setDisabled(!m_selectedInstance || currentInstanceRunning); QMenu *launchMenu = ui->actionLaunchInstance->menu(); QMenu *launchOfflineMenu = ui->actionLaunchInstanceOffline->menu(); @@ -1219,6 +1204,9 @@ void MainWindow::updateToolsMenu() normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O"))); if (m_selectedInstance) { + normalLaunch->setEnabled(m_selectedInstance->canLaunch()); + normalLaunchOffline->setEnabled(m_selectedInstance->canLaunch()); + connect(normalLaunch, &QAction::triggered, [this]() { APPLICATION->launch(m_selectedInstance, true); }); @@ -1249,6 +1237,9 @@ void MainWindow::updateToolsMenu() } else if (m_selectedInstance) { + profilerAction->setEnabled(m_selectedInstance->canLaunch()); + profilerOfflineAction->setEnabled(m_selectedInstance->canLaunch()); + connect(profilerAction, &QAction::triggered, [this, profiler]() { APPLICATION->launch(m_selectedInstance, true, profiler.get()); @@ -2081,15 +2072,7 @@ void MainWindow::instanceActivated(QModelIndex index) void MainWindow::on_actionLaunchInstance_triggered() { - if (!m_selectedInstance) - { - return; - } - if(m_selectedInstance->isRunning()) - { - APPLICATION->kill(m_selectedInstance); - } - else + if(m_selectedInstance && !m_selectedInstance->isRunning()) { APPLICATION->launch(m_selectedInstance); } @@ -2108,6 +2091,14 @@ void MainWindow::on_actionLaunchInstanceOffline_triggered() } } +void MainWindow::on_actionKillInstance_triggered() +{ + if(m_selectedInstance && m_selectedInstance->isRunning()) + { + APPLICATION->kill(m_selectedInstance); + } +} + void MainWindow::taskEnd() { QObject *sender = QObject::sender(); @@ -2141,17 +2132,9 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & { ui->instanceToolBar->setEnabled(true); ui->setInstanceActionsEnabled(true); - if(m_selectedInstance->isRunning()) - { - ui->actionLaunchInstance->setEnabled(true); - ui->setLaunchAction(true); - } - else - { - ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch()); - ui->setLaunchAction(false); - } + ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch()); ui->actionLaunchInstanceOffline->setEnabled(m_selectedInstance->canLaunch()); + ui->actionKillInstance->setEnabled(m_selectedInstance->isRunning()); ui->actionExportInstance->setEnabled(m_selectedInstance->canExport()); ui->renameButton->setText(m_selectedInstance->name()); m_statusLeft->setText(m_selectedInstance->getStatusbarDescription()); @@ -2168,6 +2151,9 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & { ui->instanceToolBar->setEnabled(false); ui->setInstanceActionsEnabled(false); + ui->actionLaunchInstance->setEnabled(false); + ui->actionLaunchInstanceOffline->setEnabled(false); + ui->actionKillInstance->setEnabled(false); APPLICATION->settings()->set("SelectedInstance", QString()); selectionBad(); return; diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 6c64756f6..4615975e2 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -148,6 +148,8 @@ private slots: void on_actionLaunchInstanceOffline_triggered(); + void on_actionKillInstance_triggered(); + void on_actionDeleteInstance_triggered(); void deleteGroup(); From 5c05cf220619b9203fa0282b683c8fff3d73dcbf Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Sun, 19 Jun 2022 15:00:51 -0400 Subject: [PATCH 244/308] Disable launch actions in menu bar when last instance is deleted This caused a crash when the action was selected! --- launcher/ui/MainWindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index ebd86228b..cceeb2888 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -2183,6 +2183,7 @@ void MainWindow::selectionBad() statusBar()->clearMessage(); ui->instanceToolBar->setEnabled(false); ui->setInstanceActionsEnabled(false); + updateToolsMenu(); ui->renameButton->setText(tr("Rename Instance")); updateInstanceToolIcon("grass"); From c31fce362153a0a3b89d1edd0360208ecec228a1 Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Wed, 22 Jun 2022 00:19:20 -0400 Subject: [PATCH 245/308] Workaround Qt bug to fix menu bar separators on macOS --- launcher/ui/MainWindow.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 210442df3..9f9cbb904 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -503,6 +503,8 @@ public: menuBar->setVisible(APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool()); fileMenu = menuBar->addMenu(tr("&File")); + // Workaround for QTBUG-94802 (https://bugreports.qt.io/browse/QTBUG-94802); also present for other menus + fileMenu->setSeparatorsCollapsible(false); fileMenu->addAction(actionAddInstance); fileMenu->addAction(actionLaunchInstance); fileMenu->addAction(actionLaunchInstanceOffline); @@ -526,15 +528,18 @@ public: fileMenu->addAction(actionSettings); viewMenu = menuBar->addMenu(tr("&View")); + viewMenu->setSeparatorsCollapsible(false); viewMenu->addAction(actionCAT); viewMenu->addSeparator(); menuBar->addMenu(foldersMenu); profileMenu = menuBar->addMenu(tr("&Profiles")); + profileMenu->setSeparatorsCollapsible(false); profileMenu->addAction(actionManageAccounts); helpMenu = menuBar->addMenu(tr("&Help")); + helpMenu->setSeparatorsCollapsible(false); helpMenu->addAction(actionAbout); helpMenu->addAction(actionOpenWiki); helpMenu->addAction(actionNewsMenuBar); From 04e822acfbe70f110122ea0a2c4b4c65050e902d Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 22 Jun 2022 20:47:47 +0200 Subject: [PATCH 246/308] fix: remove old reference to launchermeta --- launcher/minecraft/MojangDownloadInfo.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/MojangDownloadInfo.h b/launcher/minecraft/MojangDownloadInfo.h index 88f87287d..13e27e15e 100644 --- a/launcher/minecraft/MojangDownloadInfo.h +++ b/launcher/minecraft/MojangDownloadInfo.h @@ -65,7 +65,7 @@ struct MojangAssetIndexInfo : public MojangDownloadInfo // https://www.theregister.co.uk/2017/02/28/aws_is_awol_as_s3_goes_haywire/ if(id == "legacy") { - url = "https://launchermeta.mojang.com/mc/assets/legacy/c0fd82e8ce9fbc93119e40d96d5a4e62cfa3f729/legacy.json"; + url = "https://piston-meta.mojang.com/mc/assets/legacy/c0fd82e8ce9fbc93119e40d96d5a4e62cfa3f729/legacy.json"; } // HACK else From 5da87d190464421b4dc50810aaf9619f1ef29d5a Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 22 Jun 2022 19:56:24 -0300 Subject: [PATCH 247/308] fix: add missing connections to the abort signal Beginning with efa3fbff39bf0dabebdf1c6330090ee320895a4d, we separated the failing and the aborting signals, as they can mean different things in certain contexts. Still, some places are not yet changed to reflect this modification. This can cause aborting of progress dialogs to not work, instead making the application hang in an unusable satte. This goes through some places where it's not hooked up yet, fixing their behaviour in those kinds of situation. --- launcher/minecraft/ComponentUpdateTask.cpp | 8 ++++++++ launcher/minecraft/MinecraftLoadAndCheck.cpp | 1 + launcher/minecraft/MinecraftUpdate.cpp | 2 ++ launcher/minecraft/MinecraftUpdate.h | 2 ++ launcher/minecraft/PackProfile.cpp | 1 + launcher/minecraft/auth/MinecraftAccount.cpp | 4 ++++ launcher/minecraft/auth/steps/YggdrasilStep.cpp | 1 + launcher/minecraft/update/AssetUpdateTask.cpp | 2 ++ launcher/minecraft/update/FMLLibrariesTask.cpp | 1 + launcher/minecraft/update/LibrariesTask.cpp | 1 + 10 files changed, 23 insertions(+) diff --git a/launcher/minecraft/ComponentUpdateTask.cpp b/launcher/minecraft/ComponentUpdateTask.cpp index ff7ed0af3..6db21622e 100644 --- a/launcher/minecraft/ComponentUpdateTask.cpp +++ b/launcher/minecraft/ComponentUpdateTask.cpp @@ -197,6 +197,10 @@ void ComponentUpdateTask::loadComponents() { remoteLoadFailed(taskIndex, error); }); + connect(indexLoadTask.get(), &Task::aborted, [=]() + { + remoteLoadFailed(taskIndex, tr("Aborted")); + }); taskIndex++; } } @@ -243,6 +247,10 @@ void ComponentUpdateTask::loadComponents() { remoteLoadFailed(taskIndex, error); }); + connect(loadTask.get(), &Task::aborted, [=]() + { + remoteLoadFailed(taskIndex, tr("Aborted")); + }); RemoteLoadStatus status; status.type = loadType; status.PackProfileIndex = componentIndex; diff --git a/launcher/minecraft/MinecraftLoadAndCheck.cpp b/launcher/minecraft/MinecraftLoadAndCheck.cpp index 79b0c4840..d72bc7bed 100644 --- a/launcher/minecraft/MinecraftLoadAndCheck.cpp +++ b/launcher/minecraft/MinecraftLoadAndCheck.cpp @@ -20,6 +20,7 @@ void MinecraftLoadAndCheck::executeTask() } connect(m_task.get(), &Task::succeeded, this, &MinecraftLoadAndCheck::subtaskSucceeded); connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed); + connect(m_task.get(), &Task::aborted, this, [this]{ subtaskFailed(tr("Aborted")); }); connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress); connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus); } diff --git a/launcher/minecraft/MinecraftUpdate.cpp b/launcher/minecraft/MinecraftUpdate.cpp index 32e9cbb61..0ce0c3471 100644 --- a/launcher/minecraft/MinecraftUpdate.cpp +++ b/launcher/minecraft/MinecraftUpdate.cpp @@ -98,6 +98,7 @@ void MinecraftUpdate::next() auto task = m_tasks[m_currentTask - 1]; disconnect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded); disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed); + disconnect(task.get(), &Task::aborted, this, &Task::abort); disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); } @@ -115,6 +116,7 @@ void MinecraftUpdate::next() } connect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded); connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed); + connect(task.get(), &Task::aborted, this, &Task::abort); connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); // if the task is already running, do not start it again diff --git a/launcher/minecraft/MinecraftUpdate.h b/launcher/minecraft/MinecraftUpdate.h index 9ebef6569..acf2eb86b 100644 --- a/launcher/minecraft/MinecraftUpdate.h +++ b/launcher/minecraft/MinecraftUpdate.h @@ -27,6 +27,8 @@ class MinecraftVersion; class MinecraftInstance; +// FIXME: This looks very similar to a SequentialTask. Maybe we can reduce code duplications? :^) + class MinecraftUpdate : public Task { Q_OBJECT diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index 125048f05..01d42b00e 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -346,6 +346,7 @@ void PackProfile::resolve(Net::Mode netmode) d->m_updateTask.reset(updateTask); connect(updateTask, &ComponentUpdateTask::succeeded, this, &PackProfile::updateSucceeded); connect(updateTask, &ComponentUpdateTask::failed, this, &PackProfile::updateFailed); + connect(updateTask, &ComponentUpdateTask::aborted, this, [this]{ updateFailed(tr("Aborted")); }); d->m_updateTask->start(); } diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index ec86fa5c4..9c8eb70b2 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -135,6 +135,7 @@ shared_qobject_ptr MinecraftAccount::login(QString password) { m_currentTask.reset(new MojangLogin(&data, password)); connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); + connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); }); emit activityChanged(true); return m_currentTask; } @@ -145,6 +146,7 @@ shared_qobject_ptr MinecraftAccount::loginMSA() { m_currentTask.reset(new MSAInteractive(&data)); connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); + connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); }); emit activityChanged(true); return m_currentTask; } @@ -155,6 +157,7 @@ shared_qobject_ptr MinecraftAccount::loginOffline() { m_currentTask.reset(new OfflineLogin(&data)); connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); + connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); }); emit activityChanged(true); return m_currentTask; } @@ -176,6 +179,7 @@ shared_qobject_ptr MinecraftAccount::refresh() { connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); + connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); }); emit activityChanged(true); return m_currentTask; } diff --git a/launcher/minecraft/auth/steps/YggdrasilStep.cpp b/launcher/minecraft/auth/steps/YggdrasilStep.cpp index 4c6b1624b..e1d331726 100644 --- a/launcher/minecraft/auth/steps/YggdrasilStep.cpp +++ b/launcher/minecraft/auth/steps/YggdrasilStep.cpp @@ -9,6 +9,7 @@ YggdrasilStep::YggdrasilStep(AccountData* data, QString password) : AuthStep(dat connect(m_yggdrasil, &Task::failed, this, &YggdrasilStep::onAuthFailed); connect(m_yggdrasil, &Task::succeeded, this, &YggdrasilStep::onAuthSucceeded); + connect(m_yggdrasil, &Task::aborted, this, &YggdrasilStep::onAuthFailed); } YggdrasilStep::~YggdrasilStep() noexcept = default; diff --git a/launcher/minecraft/update/AssetUpdateTask.cpp b/launcher/minecraft/update/AssetUpdateTask.cpp index c4bddb08b..dd2466654 100644 --- a/launcher/minecraft/update/AssetUpdateTask.cpp +++ b/launcher/minecraft/update/AssetUpdateTask.cpp @@ -43,6 +43,7 @@ void AssetUpdateTask::executeTask() connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::assetIndexFinished); connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed); + connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); qDebug() << m_inst->name() << ": Starting asset index download"; @@ -80,6 +81,7 @@ void AssetUpdateTask::assetIndexFinished() downloadJob = job; connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::emitSucceeded); connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed); + connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); downloadJob->start(); return; diff --git a/launcher/minecraft/update/FMLLibrariesTask.cpp b/launcher/minecraft/update/FMLLibrariesTask.cpp index 58141991f..b6238ce91 100644 --- a/launcher/minecraft/update/FMLLibrariesTask.cpp +++ b/launcher/minecraft/update/FMLLibrariesTask.cpp @@ -72,6 +72,7 @@ void FMLLibrariesTask::executeTask() connect(dljob, &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished); connect(dljob, &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed); + connect(dljob, &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); connect(dljob, &NetJob::progress, this, &FMLLibrariesTask::progress); downloadJob.reset(dljob); downloadJob->start(); diff --git a/launcher/minecraft/update/LibrariesTask.cpp b/launcher/minecraft/update/LibrariesTask.cpp index 26679110b..aa2bf4074 100644 --- a/launcher/minecraft/update/LibrariesTask.cpp +++ b/launcher/minecraft/update/LibrariesTask.cpp @@ -68,6 +68,7 @@ void LibrariesTask::executeTask() connect(downloadJob.get(), &NetJob::succeeded, this, &LibrariesTask::emitSucceeded); connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed); + connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress); downloadJob->start(); } From bdfcd0b99e7b67b3adb94f969ddfec98f2a1a601 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:49:22 +0200 Subject: [PATCH 248/308] chore(flame): reword warning --- launcher/ui/pages/modplatform/flame/FlamePage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.ui b/launcher/ui/pages/modplatform/flame/FlamePage.ui index 9fab97737..2dbfe0109 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.ui +++ b/launcher/ui/pages/modplatform/flame/FlamePage.ui @@ -19,7 +19,7 @@ - Note: CurseForge's API is very unreliable. CurseForge and some mod authors have disallowed downloading mods in third-party applications like PolyMC. As such, you may need to manually download some mods to be able to install a modpack. + Note: CurseForge now provides a setting for creators that allows to block access to third-party tools like PolyMC. As such, you may need to manually download some mods to be able to install a modpack. Qt::AlignCenter From 0fe438406737524197a4d5ef50821514688ed254 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Thu, 23 Jun 2022 17:58:54 +0200 Subject: [PATCH 249/308] Update launcher/ui/pages/modplatform/flame/FlamePage.ui Co-authored-by: Sefa Eyeoglu --- launcher/ui/pages/modplatform/flame/FlamePage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.ui b/launcher/ui/pages/modplatform/flame/FlamePage.ui index 2dbfe0109..aab16421f 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.ui +++ b/launcher/ui/pages/modplatform/flame/FlamePage.ui @@ -19,7 +19,7 @@ - Note: CurseForge now provides a setting for creators that allows to block access to third-party tools like PolyMC. As such, you may need to manually download some mods to be able to install a modpack. + Note: CurseForge allows creators to block access to third-party tools like PolyMC. As such, you may need to manually download some mods to be able to install a modpack. Qt::AlignCenter From 04e8780dd088e500fb2d22564b4bb83b1640c14a Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Fri, 24 Jun 2022 10:47:02 +0200 Subject: [PATCH 250/308] fix(modrinth): fix sorting --- .../ui/pages/modplatform/modrinth/ModrinthModel.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 07d1687ce..96118284d 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -160,15 +160,15 @@ static auto sortFromIndex(int index) -> QString { switch(index){ default: - case 1: + case 0: return "relevance"; - case 2: + case 1: return "downloads"; - case 3: + case 2: return "follows"; - case 4: + case 3: return "newest"; - case 5: + case 4: return "updated"; } From 4e319254dd654bd12af5c89292905f35ef25fa20 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 25 Jun 2022 20:14:27 -0300 Subject: [PATCH 251/308] fix: use right name for the content of a News entry --- launcher/news/NewsEntry.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/news/NewsEntry.cpp b/launcher/news/NewsEntry.cpp index 137703d16..cfe07e864 100644 --- a/launcher/news/NewsEntry.cpp +++ b/launcher/news/NewsEntry.cpp @@ -54,7 +54,7 @@ inline QString childValue(const QDomElement& element, const QString& childName, bool NewsEntry::fromXmlElement(const QDomElement& element, NewsEntry* entry, QString* errorMsg) { QString title = childValue(element, "title", tr("Untitled")); - QString content = childValue(element, "description", tr("No content.")); + QString content = childValue(element, "content", tr("No content.")); QString link = childValue(element, "id"); entry->title = title; From 455e4de6f32d8b46e5798409e19cd27af4a8e083 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 25 Jun 2022 20:15:16 -0300 Subject: [PATCH 252/308] feat: add news reader dialog Makes it easier to read about new blog posts! Yay :D --- launcher/CMakeLists.txt | 3 + launcher/ui/MainWindow.cpp | 18 ++--- launcher/ui/dialogs/NewsDialog.cpp | 49 +++++++++++++ launcher/ui/dialogs/NewsDialog.h | 30 ++++++++ launcher/ui/dialogs/NewsDialog.ui | 113 +++++++++++++++++++++++++++++ 5 files changed, 203 insertions(+), 10 deletions(-) create mode 100644 launcher/ui/dialogs/NewsDialog.cpp create mode 100644 launcher/ui/dialogs/NewsDialog.h create mode 100644 launcher/ui/dialogs/NewsDialog.ui diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index b8db803b0..ce1b9e916 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -845,6 +845,8 @@ SET(LAUNCHER_SOURCES ui/dialogs/NewComponentDialog.h ui/dialogs/NewInstanceDialog.cpp ui/dialogs/NewInstanceDialog.h + ui/dialogs/NewsDialog.cpp + ui/dialogs/NewsDialog.h ui/pagedialog/PageDialog.cpp ui/pagedialog/PageDialog.h ui/dialogs/ProgressDialog.cpp @@ -954,6 +956,7 @@ qt5_wrap_ui(LAUNCHER_UI ui/dialogs/NewInstanceDialog.ui ui/dialogs/UpdateDialog.ui ui/dialogs/NewComponentDialog.ui + ui/dialogs/NewsDialog.ui ui/dialogs/ProfileSelectDialog.ui ui/dialogs/SkinUploadDialog.ui ui/dialogs/ExportInstanceDialog.ui diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 210442df3..e60588400 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -95,6 +95,7 @@ #include "ui/instanceview/InstanceDelegate.h" #include "ui/widgets/LabeledToolButton.h" #include "ui/dialogs/NewInstanceDialog.h" +#include "ui/dialogs/NewsDialog.h" #include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/AboutDialog.h" #include "ui/dialogs/VersionSelectDialog.h" @@ -1952,20 +1953,17 @@ void MainWindow::on_actionOpenWiki_triggered() void MainWindow::on_actionMoreNews_triggered() { - DesktopServices::openUrl(QUrl(BuildConfig.NEWS_OPEN_URL)); + auto entries = m_newsChecker->getNewsEntries(); + NewsDialog news_dialog(entries, this); + news_dialog.exec(); } void MainWindow::newsButtonClicked() { - QList entries = m_newsChecker->getNewsEntries(); - if (entries.count() > 0) - { - DesktopServices::openUrl(QUrl(entries[0]->link)); - } - else - { - DesktopServices::openUrl(QUrl(BuildConfig.NEWS_OPEN_URL)); - } + auto entries = m_newsChecker->getNewsEntries(); + NewsDialog news_dialog(entries, this); + news_dialog.toggleArticleList(); + news_dialog.exec(); } void MainWindow::on_actionAbout_triggered() diff --git a/launcher/ui/dialogs/NewsDialog.cpp b/launcher/ui/dialogs/NewsDialog.cpp new file mode 100644 index 000000000..df6204648 --- /dev/null +++ b/launcher/ui/dialogs/NewsDialog.cpp @@ -0,0 +1,49 @@ +#include "NewsDialog.h" +#include "ui_NewsDialog.h" + +NewsDialog::NewsDialog(QList entries, QWidget* parent) : QDialog(parent), ui(new Ui::NewsDialog()) +{ + ui->setupUi(this); + + for (auto entry : entries) { + ui->articleListWidget->addItem(entry->title); + m_entries.insert(entry->title, entry); + } + + connect(ui->articleListWidget, &QListWidget::currentTextChanged, this, &NewsDialog::selectedArticleChanged); + connect(ui->toggleListButton, &QPushButton::clicked, this, &NewsDialog::toggleArticleList); + + m_article_list_hidden = ui->articleListWidget->isHidden(); + + auto first_item = ui->articleListWidget->item(0); + ui->articleListWidget->setItemSelected(first_item, true); + + auto article_entry = m_entries.constFind(first_item->text()).value(); + ui->articleTitleLabel->setText(QString("%2").arg(article_entry->link, first_item->text())); + ui->currentArticleContentBrowser->setText(article_entry->content); +} + +NewsDialog::~NewsDialog() +{ + delete ui; +} + +void NewsDialog::selectedArticleChanged(const QString& new_title) +{ + auto const& article_entry = m_entries.constFind(new_title).value(); + + ui->articleTitleLabel->setText(QString("%2").arg(article_entry->link, new_title)); + ui->currentArticleContentBrowser->setText(article_entry->content); +} + +void NewsDialog::toggleArticleList() +{ + m_article_list_hidden = !m_article_list_hidden; + + ui->articleListWidget->setHidden(m_article_list_hidden); + + if (m_article_list_hidden) + ui->toggleListButton->setText(tr("Show article list")); + else + ui->toggleListButton->setText(tr("Hide article list")); +} diff --git a/launcher/ui/dialogs/NewsDialog.h b/launcher/ui/dialogs/NewsDialog.h new file mode 100644 index 000000000..add6b8dd7 --- /dev/null +++ b/launcher/ui/dialogs/NewsDialog.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include "news/NewsEntry.h" + +namespace Ui { +class NewsDialog; +} + +class NewsDialog : public QDialog { + Q_OBJECT + + public: + NewsDialog(QList entries, QWidget* parent = nullptr); + ~NewsDialog(); + + public slots: + void toggleArticleList(); + + private slots: + void selectedArticleChanged(const QString& new_title); + + private: + Ui::NewsDialog* ui; + + QHash m_entries; + bool m_article_list_hidden = false; +}; diff --git a/launcher/ui/dialogs/NewsDialog.ui b/launcher/ui/dialogs/NewsDialog.ui new file mode 100644 index 000000000..5f0405c33 --- /dev/null +++ b/launcher/ui/dialogs/NewsDialog.ui @@ -0,0 +1,113 @@ + + + NewsDialog + + + + 0 + 0 + 800 + 500 + + + + News + + + true + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + Placeholder + + + Qt::AlignCenter + + + true + + + + + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + true + + + true + + + + + + + + + + + + + + 10 + 0 + + + + Close + + + + + + + Hide article list + + + + + + + + + + + closeButton + pressed() + NewsDialog + accept() + + + 199 + 277 + + + 199 + 149 + + + + + From 9ef38171e246dcc88878f438d06bf8d7f72aec51 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 26 Jun 2022 08:10:52 -0300 Subject: [PATCH 253/308] fix: use `clicked` instead of `pressed` signal for button clicks Co-authored-by: Sefa Eyeoglu --- launcher/ui/dialogs/NewsDialog.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/NewsDialog.ui b/launcher/ui/dialogs/NewsDialog.ui index 5f0405c33..2aaa08f1a 100644 --- a/launcher/ui/dialogs/NewsDialog.ui +++ b/launcher/ui/dialogs/NewsDialog.ui @@ -95,7 +95,7 @@ closeButton - pressed() + clicked() NewsDialog accept() From 2bba64fe3a0509251c6a197dba4eb503ea0f20a7 Mon Sep 17 00:00:00 2001 From: Russell Banks Date: Tue, 28 Jun 2022 12:11:52 +0100 Subject: [PATCH 254/308] Create winget.yml --- .github/workflows/winget.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/workflows/winget.yml diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml new file mode 100644 index 000000000..b8ecce133 --- /dev/null +++ b/.github/workflows/winget.yml @@ -0,0 +1,14 @@ +name: Publish to WinGet +on: + release: + types: [released] + +jobs: + publish: + runs-on: windows-latest + steps: + - uses: vedantmgoyal2009/winget-releaser@latest + with: + identifier: PolyMC.PolyMC + installers-regex: '\.exe$' + token: ${{ secrets.WINGET_TOKEN }} From 68d6ce60a9e74758743d2a43d9e4604fd06e6511 Mon Sep 17 00:00:00 2001 From: Gingeh <39150378+Gingeh@users.noreply.github.com> Date: Wed, 29 Jun 2022 18:42:01 +1000 Subject: [PATCH 255/308] Don't show account name for offline accounts --- launcher/minecraft/auth/AccountData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp index dd9c3f8f9..f6468ba75 100644 --- a/launcher/minecraft/auth/AccountData.cpp +++ b/launcher/minecraft/auth/AccountData.cpp @@ -473,7 +473,7 @@ QString AccountData::accountDisplayString() const { return userName(); } case AccountType::Offline: { - return userName(); + return ""; } case AccountType::MSA: { if(xboxApiToken.extra.contains("gtg")) { From f685139d89bb10fd8ec9872b710d118530b97976 Mon Sep 17 00:00:00 2001 From: Gingeh <39150378+Gingeh@users.noreply.github.com> Date: Wed, 29 Jun 2022 18:43:29 +1000 Subject: [PATCH 256/308] Move the profile name column to the left --- launcher/minecraft/auth/AccountList.cpp | 16 ++++++++-------- launcher/minecraft/auth/AccountList.h | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index 3422df7c8..394a94d75 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -282,6 +282,10 @@ QVariant AccountList::data(const QModelIndex &index, int role) const case Qt::DisplayRole: switch (index.column()) { + case ProfileNameColumn: { + return account->profileName(); + } + case NameColumn: return account->accountDisplayString(); @@ -320,10 +324,6 @@ QVariant AccountList::data(const QModelIndex &index, int role) const } } - case ProfileNameColumn: { - return account->profileName(); - } - case MigrationColumn: { if(account->isMSA() || account->isOffline()) { return tr("N/A", "Can Migrate?"); @@ -365,6 +365,8 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r case Qt::DisplayRole: switch (section) { + case ProfileNameColumn: + return tr("Profile"); case NameColumn: return tr("Account"); case TypeColumn: @@ -373,8 +375,6 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r return tr("Status"); case MigrationColumn: return tr("Can Migrate?"); - case ProfileNameColumn: - return tr("Profile"); default: return QVariant(); } @@ -382,6 +382,8 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r case Qt::ToolTipRole: switch (section) { + case ProfileNameColumn: + return tr("Name of the Minecraft profile associated with the account."); case NameColumn: return tr("User name of the account."); case TypeColumn: @@ -390,8 +392,6 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r return tr("Current status of the account."); case MigrationColumn: return tr("Can this account migrate to Microsoft account?"); - case ProfileNameColumn: - return tr("Name of the Minecraft profile associated with the account."); default: return QVariant(); } diff --git a/launcher/minecraft/auth/AccountList.h b/launcher/minecraft/auth/AccountList.h index baaf74149..8136a92ed 100644 --- a/launcher/minecraft/auth/AccountList.h +++ b/launcher/minecraft/auth/AccountList.h @@ -58,8 +58,8 @@ public: enum VListColumns { // TODO: Add icon column. - NameColumn = 0, - ProfileNameColumn, + ProfileNameColumn = 0, + NameColumn, MigrationColumn, TypeColumn, StatusColumn, From b606a2e040b96b6d80fae012a4eef43307a04771 Mon Sep 17 00:00:00 2001 From: Gingeh <39150378+Gingeh@users.noreply.github.com> Date: Wed, 29 Jun 2022 18:45:36 +1000 Subject: [PATCH 257/308] Make the profile and account name columns use all available space --- launcher/ui/pages/global/AccountListPage.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index 6e1e21836..b97fe35ea 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -73,9 +73,11 @@ AccountListPage::AccountListPage(QWidget *parent) m_accounts = APPLICATION->accounts(); ui->listView->setModel(m_accounts.get()); - ui->listView->header()->setSectionResizeMode(0, QHeaderView::Stretch); - ui->listView->header()->setSectionResizeMode(1, QHeaderView::Stretch); - ui->listView->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents); + ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::ProfileNameColumn, QHeaderView::Stretch); + ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::NameColumn, QHeaderView::Stretch); + ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::MigrationColumn, QHeaderView::ResizeToContents); + ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::TypeColumn, QHeaderView::ResizeToContents); + ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::StatusColumn, QHeaderView::ResizeToContents); ui->listView->setSelectionMode(QAbstractItemView::SingleSelection); // Expand the account column From d6f4ff26b548f9b340297c15df33680cf45359ad Mon Sep 17 00:00:00 2001 From: Gingeh <39150378+Gingeh@users.noreply.github.com> Date: Wed, 29 Jun 2022 18:46:39 +1000 Subject: [PATCH 258/308] Disable skin buttons for offline accounts --- launcher/ui/pages/global/AccountListPage.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index b97fe35ea..5447a07b3 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -255,18 +255,20 @@ void AccountListPage::updateButtonStates() { // If there is no selection, disable buttons that require something selected. QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); - bool hasSelection = selection.size() > 0; + bool hasSelection = !selection.empty(); bool accountIsReady = false; + bool accountIsOnline; if (hasSelection) { QModelIndex selected = selection.first(); MinecraftAccountPtr account = selected.data(AccountList::PointerRole).value(); accountIsReady = !account->isActive(); + accountIsOnline = !account->isOffline(); } ui->actionRemove->setEnabled(accountIsReady); ui->actionSetDefault->setEnabled(accountIsReady); - ui->actionUploadSkin->setEnabled(accountIsReady); - ui->actionDeleteSkin->setEnabled(accountIsReady); + ui->actionUploadSkin->setEnabled(accountIsReady && accountIsOnline); + ui->actionDeleteSkin->setEnabled(accountIsReady && accountIsOnline); ui->actionRefresh->setEnabled(accountIsReady); if(m_accounts->defaultAccount().get() == nullptr) { From 63589d2ba97bc3fa5b4fc2fdd30a32a034aa9b50 Mon Sep 17 00:00:00 2001 From: Gingeh <39150378+Gingeh@users.noreply.github.com> Date: Wed, 29 Jun 2022 18:49:06 +1000 Subject: [PATCH 259/308] Rename profile column to username and update the tooltip --- launcher/minecraft/auth/AccountList.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index 394a94d75..e05aa526c 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -366,7 +366,7 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r switch (section) { case ProfileNameColumn: - return tr("Profile"); + return tr("Username"); case NameColumn: return tr("Account"); case TypeColumn: @@ -383,7 +383,7 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r switch (section) { case ProfileNameColumn: - return tr("Name of the Minecraft profile associated with the account."); + return tr("Minecraft username associated with the account."); case NameColumn: return tr("User name of the account."); case TypeColumn: @@ -391,7 +391,7 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r case StatusColumn: return tr("Current status of the account."); case MigrationColumn: - return tr("Can this account migrate to Microsoft account?"); + return tr("Can this account migrate to a Microsoft account?"); default: return QVariant(); } From 84bd5ace6cca2c9cd810426bf7cff12941694624 Mon Sep 17 00:00:00 2001 From: Gingeh <39150378+Gingeh@users.noreply.github.com> Date: Wed, 29 Jun 2022 19:58:41 +1000 Subject: [PATCH 260/308] Move account checkboxes to the profile column (oops) --- launcher/minecraft/auth/AccountList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index e05aa526c..526cdced0 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -349,7 +349,7 @@ QVariant AccountList::data(const QModelIndex &index, int role) const case Qt::CheckStateRole: switch (index.column()) { - case NameColumn: + case ProfileNameColumn: return account == m_defaultAccount ? Qt::Checked : Qt::Unchecked; } From d639da741ba8aadb6a831d90435fc21c14f9ebb2 Mon Sep 17 00:00:00 2001 From: Gingeh <39150378+Gingeh@users.noreply.github.com> Date: Thu, 30 Jun 2022 08:49:40 +1000 Subject: [PATCH 261/308] add tr() to offline account name Co-authored-by: flow --- launcher/minecraft/auth/AccountData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp index f6468ba75..602eadb91 100644 --- a/launcher/minecraft/auth/AccountData.cpp +++ b/launcher/minecraft/auth/AccountData.cpp @@ -473,7 +473,7 @@ QString AccountData::accountDisplayString() const { return userName(); } case AccountType::Offline: { - return ""; + return tr(""); } case AccountType::MSA: { if(xboxApiToken.extra.contains("gtg")) { From 91b5f0228d77d2b64d615824f09b4f82c8d687b5 Mon Sep 17 00:00:00 2001 From: Gingeh <39150378+Gingeh@users.noreply.github.com> Date: Thu, 30 Jun 2022 08:49:40 +1000 Subject: [PATCH 262/308] add tr() to offline account name Co-authored-by: flow --- launcher/minecraft/auth/AccountData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp index f6468ba75..3c7b193c3 100644 --- a/launcher/minecraft/auth/AccountData.cpp +++ b/launcher/minecraft/auth/AccountData.cpp @@ -473,7 +473,7 @@ QString AccountData::accountDisplayString() const { return userName(); } case AccountType::Offline: { - return ""; + return QObject::tr(""); } case AccountType::MSA: { if(xboxApiToken.extra.contains("gtg")) { From bef79df6bba3c04737529e2631a88d27d6b37471 Mon Sep 17 00:00:00 2001 From: Gingeh <39150378+Gingeh@users.noreply.github.com> Date: Thu, 30 Jun 2022 09:08:30 +1000 Subject: [PATCH 263/308] Disable the refresh button for offline accounts --- launcher/ui/pages/global/AccountListPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index 5447a07b3..f90ba863b 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -269,7 +269,7 @@ void AccountListPage::updateButtonStates() ui->actionSetDefault->setEnabled(accountIsReady); ui->actionUploadSkin->setEnabled(accountIsReady && accountIsOnline); ui->actionDeleteSkin->setEnabled(accountIsReady && accountIsOnline); - ui->actionRefresh->setEnabled(accountIsReady); + ui->actionRefresh->setEnabled(accountIsReady && accountIsOnline); if(m_accounts->defaultAccount().get() == nullptr) { ui->actionNoDefault->setEnabled(false); From 5f951e8f213e3636f4e64190fe1a720067963108 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 30 Jun 2022 06:44:53 -0300 Subject: [PATCH 264/308] change: better regex for removing 'The' when sorting mods Teh :| Co-authored-by: timoreo22 --- launcher/ui/pages/instance/ExternalResourcesPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp index 0b1dc4f32..02eeae3d0 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -13,7 +13,7 @@ namespace { // FIXME: wasteful void RemoveThePrefix(QString& string) { - QRegularExpression regex(QStringLiteral("^(([Tt][Hh][eE])|([Tt][eE][Hh])) +")); + QRegularExpression regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption); string.remove(regex); string = string.trimmed(); } From b5d2570fe2a5d2a4ce49524a4a022a517f27f428 Mon Sep 17 00:00:00 2001 From: Gingeh <39150378+Gingeh@users.noreply.github.com> Date: Thu, 30 Jun 2022 22:17:15 +1000 Subject: [PATCH 265/308] Change Online status to Ready --- launcher/minecraft/auth/AccountList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index 526cdced0..2b851e18a 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -304,7 +304,7 @@ QVariant AccountList::data(const QModelIndex &index, int role) const return tr("Offline", "Account status"); } case AccountState::Online: { - return tr("Online", "Account status"); + return tr("Ready", "Account status"); } case AccountState::Working: { return tr("Working", "Account status"); From 3039cb5c0267f51ab3627a87e4304821240a5449 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 30 Jun 2022 16:33:34 +0200 Subject: [PATCH 266/308] chore: add DCO requirement Signed-off-by: Sefa Eyeoglu --- CONTRIBUTING.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 13 +---------- 2 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..216549c69 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,62 @@ +# Contributions Guidelines + +## Code formatting + +Try to follow the existing formatting. +If there is no existing formatting, you may use `clang-format` with our included `.clang-format` configuration. + +In general, in order of importance: +- Make sure your IDE is not messing up line endings or whitespace and avoid using linters. +- Prefer readability over dogma. +- Keep to the existing formatting. +- Indent with 4 space unless it's in a submodule. +- Keep lists (of arguments, parameters, initializers...) as lists, not paragraphs. It should either read from top to bottom, or left to right. Not both. + +## Signing your work + +In an effort to ensure that the code you contribute is actually compatible with the licenses in this codebase, we require you to sign-off all your contributions. + +This can be done by appending `-s` to your `git commit` call, or by manually appending the following text to your commit message: + +``` + + +Signed-off-by: Author name +``` + +By signing off your work, you agree to the terms below: + + Developer's Certificate of Origin 1.1 + + By making a contribution to this project, I certify that: + + (a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + + (b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + + (c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + + (d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. + +These terms will be enforced once you create a pull request, and you will be informed automatically if any of your commits aren't signed-off by you. + +As a bonus, you can also [cryptographically sign your commits][gh-signing-commits] and enable [vigilant mode][gh-vigilant-mode] on GitHub. + + + +[gh-signing-commits]: https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits +[gh-vigilant-mode]: https://docs.github.com/en/authentication/managing-commit-signature-verification/displaying-verification-statuses-for-all-of-your-commits diff --git a/README.md b/README.md index c1fabc448..b9c23fec0 100644 --- a/README.md +++ b/README.md @@ -58,23 +58,12 @@ If you want to contribute to PolyMC you might find it useful to join our Discord If you want to build PolyMC yourself, check [Build Instructions](https://polymc.org/wiki/development/build-instructions/) for build instructions. -## Code formatting - -Just follow the existing formatting. - -In general, in order of importance: - -- Make sure your IDE is not messing up line endings or whitespace and avoid using linters. -- Prefer readability over dogma. -- Keep to the existing formatting. -- Indent with 4 space unless it's in a submodule. -- Keep lists (of arguments, parameters, initializers...) as lists, not paragraphs. It should either read from top to bottom, or left to right. Not both. - ## Translations The translation effort for PolyMC is hosted on [Weblate](https://hosted.weblate.org/projects/polymc/polymc/) and information about translating PolyMC is available at https://github.com/PolyMC/Translations ## Download information + To modify download information or change packaging information send a pull request or issue to the website [Here](https://github.com/PolyMC/polymc.github.io/blob/master/src/download.md) ## Forking/Redistributing/Custom builds policy From 06bf7b0f317cc6c0ff939644e23eba00fa7708cc Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Wed, 29 Jun 2022 00:34:13 -0400 Subject: [PATCH 267/308] Fix Minecraft version not appearing in status bar --- launcher/minecraft/MinecraftInstance.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 7e72601ff..46fa977d5 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -826,8 +826,16 @@ QString MinecraftInstance::getStatusbarDescription() traits.append(tr("broken")); } + QString mcVersion = m_components->getComponentVersion("net.minecraft"); + if (mcVersion.isEmpty()) + { + // Load component info if needed + m_components->reload(Net::Mode::Offline); + mcVersion = m_components->getComponentVersion("net.minecraft"); + } + QString description; - description.append(tr("Minecraft %1 (%2)").arg(m_components->getComponentVersion("net.minecraft")).arg(typeName())); + description.append(tr("Minecraft %1 (%2)").arg(mcVersion).arg(typeName())); if(m_settings->get("ShowGameTime").toBool()) { if (lastTimePlayed() > 0) { From 79840f0fca9fd7b04b331a3fbf151ad0e11c7817 Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Wed, 29 Jun 2022 00:34:44 -0400 Subject: [PATCH 268/308] Remove redundant type name from status bar The type name is always "Minecraft", so it showed "Minecraft X.X.X (Minecraft)" --- launcher/minecraft/MinecraftInstance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 46fa977d5..e0113a098 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -835,7 +835,7 @@ QString MinecraftInstance::getStatusbarDescription() } QString description; - description.append(tr("Minecraft %1 (%2)").arg(mcVersion).arg(typeName())); + description.append(tr("Minecraft %1").arg(mcVersion)); if(m_settings->get("ShowGameTime").toBool()) { if (lastTimePlayed() > 0) { From 8e80b4bfc119fc4b5649e232167d7908c34f2c15 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 12 Jun 2022 13:07:11 -0300 Subject: [PATCH 269/308] feat: add ConcurrentTask This tasks (or rather, meta-task) has the ability to run several other sub tasks concurrently. Signed-off-by: flow --- launcher/CMakeLists.txt | 2 + launcher/tasks/ConcurrentTask.cpp | 144 ++++++++++++++++++++++++++++++ launcher/tasks/ConcurrentTask.h | 58 ++++++++++++ 3 files changed, 204 insertions(+) create mode 100644 launcher/tasks/ConcurrentTask.cpp create mode 100644 launcher/tasks/ConcurrentTask.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index e8e2ebd98..42936a645 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -418,6 +418,8 @@ set(TASKS_SOURCES # Tasks tasks/Task.h tasks/Task.cpp + tasks/ConcurrentTask.h + tasks/ConcurrentTask.cpp tasks/SequentialTask.h tasks/SequentialTask.cpp ) diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp new file mode 100644 index 000000000..b88cfb131 --- /dev/null +++ b/launcher/tasks/ConcurrentTask.cpp @@ -0,0 +1,144 @@ +#include "ConcurrentTask.h" + +#include + +ConcurrentTask::ConcurrentTask(QObject* parent, QString task_name, int max_concurrent) + : Task(parent), m_name(task_name), m_total_max_size(max_concurrent) +{} + +ConcurrentTask::~ConcurrentTask() +{ + for (auto task : m_queue) { + if (task) + task->deleteLater(); + } +} + +auto ConcurrentTask::getStepProgress() const -> qint64 +{ + return m_stepProgress; +} + +auto ConcurrentTask::getStepTotalProgress() const -> qint64 +{ + return m_stepTotalProgress; +} + +void ConcurrentTask::addTask(Task::Ptr task) +{ + if (!isRunning()) + m_queue.append(task); + else + qWarning() << "Tried to add a task to a running concurrent task!"; +} + +void ConcurrentTask::executeTask() +{ + m_total_size = m_queue.size(); + + for (int i = 0; i < m_total_max_size; i++) + startNext(); +} + +bool ConcurrentTask::abort() +{ + if (m_doing.isEmpty()) { + // Don't call emitAborted() here, we want to bypass the 'is the task running' check + emit aborted(); + emit finished(); + + m_aborted = true; + return true; + } + + m_queue.clear(); + + m_aborted = true; + for (auto task : m_doing) + m_aborted &= task->abort(); + + if (m_aborted) + emitAborted(); + + return m_aborted; +} + +void ConcurrentTask::startNext() +{ + if (m_aborted || m_doing.count() > m_total_max_size) + return; + + if (m_queue.isEmpty() && m_doing.isEmpty()) { + emitSucceeded(); + return; + } + + if (m_queue.isEmpty()) + return; + + Task::Ptr next = m_queue.dequeue(); + + connect(next.get(), &Task::succeeded, this, [this, next] { subTaskSucceeded(next); }); + connect(next.get(), &Task::failed, this, [this, next](QString msg) { subTaskFailed(next, msg); }); + + connect(next.get(), &Task::status, this, &ConcurrentTask::subTaskStatus); + connect(next.get(), &Task::stepStatus, this, &ConcurrentTask::subTaskStatus); + + connect(next.get(), &Task::progress, this, &ConcurrentTask::subTaskProgress); + + m_doing.insert(next.get(), next); + + setStepStatus(next->isMultiStep() ? next->getStepStatus() : next->getStatus()); + updateState(); + + next->start(); +} + +void ConcurrentTask::subTaskSucceeded(Task::Ptr task) +{ + m_done.insert(task.get(), task); + m_doing.remove(task.get()); + + disconnect(task.get(), 0, this, 0); + + updateState(); + + startNext(); +} + +void ConcurrentTask::subTaskFailed(Task::Ptr task, const QString& msg) +{ + m_done.insert(task.get(), task); + m_failed.insert(task.get(), task); + + m_doing.remove(task.get()); + + disconnect(task.get(), 0, this, 0); + + updateState(); + + startNext(); +} + +void ConcurrentTask::subTaskStatus(const QString& msg) +{ + setStepStatus(msg); +} + +void ConcurrentTask::subTaskProgress(qint64 current, qint64 total) +{ + if (total == 0) { + setProgress(0, 100); + return; + } + + m_stepProgress = current; + m_stepTotalProgress = total; +} + +void ConcurrentTask::updateState() +{ + setProgress(m_done.count(), m_total_size); + setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)") + .arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(m_total_size))); +} diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h new file mode 100644 index 000000000..5898899d1 --- /dev/null +++ b/launcher/tasks/ConcurrentTask.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include + +#include "tasks/Task.h" + +class ConcurrentTask : public Task { + Q_OBJECT +public: + explicit ConcurrentTask(QObject* parent = nullptr, QString task_name = "", int max_concurrent = 6); + virtual ~ConcurrentTask(); + + inline auto isMultiStep() const -> bool override { return m_queue.size() > 1; }; + auto getStepProgress() const -> qint64 override; + auto getStepTotalProgress() const -> qint64 override; + + inline auto getStepStatus() const -> QString override { return m_step_status; } + + void addTask(Task::Ptr task); + +public slots: + bool abort() override; + +protected +slots: + void executeTask() override; + + virtual void startNext(); + + void subTaskSucceeded(Task::Ptr); + void subTaskFailed(Task::Ptr, const QString &msg); + void subTaskStatus(const QString &msg); + void subTaskProgress(qint64 current, qint64 total); + +protected: + void setStepStatus(QString status) { m_step_status = status; emit stepStatus(status); }; + + virtual void updateState(); + +protected: + QString m_name; + QString m_step_status; + + QQueue m_queue; + + QHash m_doing; + QHash m_done; + QHash m_failed; + + int m_total_max_size; + int m_total_size; + + qint64 m_stepProgress = 0; + qint64 m_stepTotalProgress = 100; + + bool m_aborted = false; +}; From 02201631e72992a520595a2dfc64084dc9274788 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 12 Jun 2022 13:51:19 -0300 Subject: [PATCH 270/308] feat: use ConcurrentTask for mod downloads Way faster :) Signed-off-by: flow --- launcher/ui/pages/instance/ModFolderPage.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 8fd0f86ee..4432ccc8c 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -60,7 +60,7 @@ #include "modplatform/ModAPI.h" #include "Version.h" -#include "tasks/SequentialTask.h" +#include "tasks/ConcurrentTask.h" #include "ui/dialogs/ProgressDialog.h" ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent) @@ -127,7 +127,7 @@ void ModFolderPage::installMods() ModDownloadDialog mdownload(m_model, this, m_instance); if (mdownload.exec()) { - SequentialTask* tasks = new SequentialTask(this); + ConcurrentTask* tasks = new ConcurrentTask(this); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); From 8cec4b60a601902dc6c6eff23c3a54b48630c312 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Fri, 1 Jul 2022 19:50:41 +0200 Subject: [PATCH 271/308] fix: update NewLaunch package name --- launcher/minecraft/launch/LauncherPartLaunch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index 427bc32be..fe8a1b1bd 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -142,7 +142,7 @@ void LauncherPartLaunch::executeTask() #else args << classPath.join(':'); #endif - args << "org.multimc.EntryPoint"; + args << "org.polymc.EntryPoint"; qDebug() << args.join(' '); From b40619bcbd530c6fc0f8420c3272b6cc902201b0 Mon Sep 17 00:00:00 2001 From: Ivan Puntiy Date: Sat, 2 Jul 2022 18:05:33 +0300 Subject: [PATCH 272/308] don't censor offline access token --- launcher/minecraft/MinecraftInstance.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 7e72601ff..e8d2928dc 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -747,7 +747,9 @@ QMap MinecraftInstance::createCensorFilterFromSession(AuthSess { addToFilter(sessionRef.session, tr("")); } - addToFilter(sessionRef.access_token, tr("")); + if (sessionRef.access_token != "offline") { + addToFilter(sessionRef.access_token, tr("")); + } if(sessionRef.client_token.size()) { addToFilter(sessionRef.client_token, tr("")); } From 471d6d603100e3668322d6da28ed1e1beee95456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charlotte=20=F0=9F=A6=9D=20Delenk?= Date: Sun, 3 Jul 2022 10:28:41 +0100 Subject: [PATCH 273/308] fix: Add extra-cmake-modules to the nix build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Charlotte 🦝 Delenk --- nix/default.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nix/default.nix b/nix/default.nix index d6aa370c5..94b74354d 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -15,6 +15,7 @@ , libGL , msaClientID ? "" , extraJDKs ? [ ] +, extra-cmake-modules # flake , self @@ -47,7 +48,7 @@ stdenv.mkDerivation rec { src = lib.cleanSource self; - nativeBuildInputs = [ cmake ninja jdk file wrapQtAppsHook ]; + nativeBuildInputs = [ cmake extra-cmake-modules ninja jdk file wrapQtAppsHook ]; buildInputs = [ qtbase quazip zlib ]; dontWrapQtApps = true; From 278d2169da20f53f71d75925afea44ecbc23fdcf Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 3 Jul 2022 14:32:01 +0200 Subject: [PATCH 274/308] fix: initialize accountIsOnline to fix build CMAKE_BUILD_TYPE=Release makes the build fail otherwise. Signed-off-by: Sefa Eyeoglu --- launcher/ui/pages/global/AccountListPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index f90ba863b..a608771e3 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -257,7 +257,7 @@ void AccountListPage::updateButtonStates() QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); bool hasSelection = !selection.empty(); bool accountIsReady = false; - bool accountIsOnline; + bool accountIsOnline = false; if (hasSelection) { QModelIndex selected = selection.first(); From 3c40355d7c61a961d5037e49fc598113dae30dc2 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 3 Jul 2022 17:27:49 +0200 Subject: [PATCH 275/308] chore(DCO): allow remediation Signed-off-by: Sefa Eyeoglu --- .github/dco.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/dco.yml diff --git a/.github/dco.yml b/.github/dco.yml new file mode 100644 index 000000000..7993b95cc --- /dev/null +++ b/.github/dco.yml @@ -0,0 +1,2 @@ +allowRemediationCommits: + individual: true From 474d77ac574c24918759413c2a77dc657e1e8581 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 14 Jun 2022 22:00:24 +0200 Subject: [PATCH 276/308] feat: resolve JARs dynamically Signed-off-by: Sefa Eyeoglu --- CMakeLists.txt | 3 --- launcher/Application.cpp | 21 ++++++++++++------- launcher/Application.h | 7 +++++-- launcher/JavaCommon.cpp | 13 ++++++++++++ launcher/JavaCommon.h | 10 +++++---- launcher/java/JavaChecker.cpp | 8 ++++++- launcher/java/JavaUtils.cpp | 6 ++++++ launcher/java/JavaUtils.h | 2 ++ launcher/launch/steps/CheckJava.cpp | 9 ++++++++ .../minecraft/launch/LauncherPartLaunch.cpp | 11 +++++++++- launcher/ui/pages/global/JavaPage.cpp | 5 +++++ .../pages/instance/InstanceSettingsPage.cpp | 6 ++++++ launcher/ui/widgets/JavaSettingsWidget.cpp | 5 +++++ 13 files changed, 87 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c5144f68..8979d7b37 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -238,9 +238,6 @@ elseif(UNIX) # Set RPATH SET(Launcher_BINARY_RPATH "$ORIGIN/") - # jars path is determined on runtime, relative to "Application root path", generally /usr or the root of the portable bundle - set(Launcher_APP_BINARY_DEFS "-DLAUNCHER_JARS_LOCATION=${JARS_DEST_DIR}") - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_Desktop} DESTINATION ${LAUNCHER_DESKTOP_DEST_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MetaInfo} DESTINATION ${LAUNCHER_METAINFO_DEST_DIR}) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION ${LAUNCHER_ICON_DEST_DIR}) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index bafb928ba..cb5af00a3 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -334,10 +334,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // on macOS, touch the root to force Finder to reload the .app metadata (and fix any icon change issues) FS::updateTimestamp(m_rootPath); #endif - -#ifdef LAUNCHER_JARS_LOCATION - m_jarsPath = TOSTRING(LAUNCHER_JARS_LOCATION); -#endif } QString adjustedBy; @@ -1557,13 +1553,22 @@ shared_qobject_ptr Application::metadataIndex() return m_metadataIndex; } -QString Application::getJarsPath() +QString Application::getJarPath(QString jarFile) { - if(m_jarsPath.isEmpty()) + QStringList potentialPaths = { +#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) + FS::PathCombine(m_rootPath, "share/jars"), +#endif + FS::PathCombine(m_rootPath, "jars"), + FS::PathCombine(applicationDirPath(), "jars") + }; + for(QString p : potentialPaths) { - return FS::PathCombine(QCoreApplication::applicationDirPath(), "jars"); + QString jarPath = FS::PathCombine(p, jarFile); + if (QFileInfo(jarPath).isFile()) + return jarPath; } - return FS::PathCombine(m_rootPath, m_jarsPath); + return {}; } QString Application::getMSAClientID() diff --git a/launcher/Application.h b/launcher/Application.h index 090071606..18461ad88 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -157,7 +157,11 @@ public: shared_qobject_ptr metadataIndex(); - QString getJarsPath(); + /*! + * Finds and returns the full path to a jar file. + * Returns a null-string if it could not be found. + */ + QString getJarPath(QString jarFile); QString getMSAClientID(); QString getCurseKey(); @@ -241,7 +245,6 @@ private: std::shared_ptr m_globalSettingsProvider; std::map> m_themes; std::unique_ptr m_mcedit; - QString m_jarsPath; QSet m_features; QMap> m_profilers; diff --git a/launcher/JavaCommon.cpp b/launcher/JavaCommon.cpp index 17278d864..ae6cd247c 100644 --- a/launcher/JavaCommon.cpp +++ b/launcher/JavaCommon.cpp @@ -1,4 +1,5 @@ #include "JavaCommon.h" +#include "java/JavaUtils.h" #include "ui/dialogs/CustomMessageBox.h" #include @@ -65,6 +66,13 @@ void JavaCommon::javaBinaryWasBad(QWidget *parent, JavaCheckResult result) CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show(); } +void JavaCommon::javaCheckNotFound(QWidget *parent) +{ + QString text; + text += QObject::tr("Java checker library could not be found. Please check your installation"); + CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show(); +} + void JavaCommon::TestCheck::run() { if (!JavaCommon::checkJVMArgs(m_args, m_parent)) @@ -72,6 +80,11 @@ void JavaCommon::TestCheck::run() emit finished(); return; } + if (JavaUtils::getJavaCheckPath().isEmpty()) { + javaCheckNotFound(m_parent); + emit finished(); + return; + } checker.reset(new JavaChecker()); connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this, SLOT(checkFinished(JavaCheckResult))); diff --git a/launcher/JavaCommon.h b/launcher/JavaCommon.h index ca98145c3..59cb7a67d 100644 --- a/launcher/JavaCommon.h +++ b/launcher/JavaCommon.h @@ -10,12 +10,14 @@ namespace JavaCommon { bool checkJVMArgs(QString args, QWidget *parent); - // Show a dialog saying that the Java binary was not usable - void javaBinaryWasBad(QWidget *parent, JavaCheckResult result); - // Show a dialog saying that the Java binary was not usable because of bad options - void javaArgsWereBad(QWidget *parent, JavaCheckResult result); // Show a dialog saying that the Java binary was usable void javaWasOk(QWidget *parent, JavaCheckResult result); + // Show a dialog saying that the Java binary was not usable because of bad options + void javaArgsWereBad(QWidget *parent, JavaCheckResult result); + // Show a dialog saying that the Java binary was not usable + void javaBinaryWasBad(QWidget *parent, JavaCheckResult result); + // Show a dialog if we couldn't find Java Checker + void javaCheckNotFound(QWidget *parent); class TestCheck : public QObject { diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 946599c51..15b222605 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -16,7 +16,13 @@ JavaChecker::JavaChecker(QObject *parent) : QObject(parent) void JavaChecker::performCheck() { - QString checkerJar = FS::PathCombine(APPLICATION->getJarsPath(), "JavaCheck.jar"); + QString checkerJar = JavaUtils::getJavaCheckPath(); + + if (checkerJar.isEmpty()) + { + qDebug() << "Java checker library could not be found. Please check your installation."; + return; + } QStringList args; diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index 65a8b1db4..24a1556e4 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -24,6 +24,7 @@ #include "java/JavaUtils.h" #include "java/JavaInstallList.h" #include "FileSystem.h" +#include "Application.h" #define IBUS "@im=ibus" @@ -437,3 +438,8 @@ QList JavaUtils::FindJavaPaths() return addJavasFromEnv(javas); } #endif + +QString JavaUtils::getJavaCheckPath() +{ + return APPLICATION->getJarPath("JavaCheck.jar"); +} diff --git a/launcher/java/JavaUtils.h b/launcher/java/JavaUtils.h index 3152d143c..26d8003bb 100644 --- a/launcher/java/JavaUtils.h +++ b/launcher/java/JavaUtils.h @@ -39,4 +39,6 @@ public: #ifdef Q_OS_WIN QList FindJavaFromRegistryKey(DWORD keyType, QString keyName, QString keyJavaDir, QString subkeySuffix = ""); #endif + + static QString getJavaCheckPath(); }; diff --git a/launcher/launch/steps/CheckJava.cpp b/launcher/launch/steps/CheckJava.cpp index ef5db2c9b..db56b652c 100644 --- a/launcher/launch/steps/CheckJava.cpp +++ b/launcher/launch/steps/CheckJava.cpp @@ -34,6 +34,7 @@ */ #include "CheckJava.h" +#include "java/JavaUtils.h" #include #include #include @@ -71,6 +72,14 @@ void CheckJava::executeTask() emit logLine("Java path is:\n" + m_javaPath + "\n\n", MessageLevel::Launcher); } + if (JavaUtils::getJavaCheckPath().isEmpty()) + { + const char *reason = QT_TR_NOOP("Java checker library could not be found. Please check your installation."); + emit logLine(tr(reason), MessageLevel::Fatal); + emitFailed(tr(reason)); + return; + } + QFileInfo javaInfo(realJavaPath); qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch(); auto storedUnixTime = settings->get("JavaTimestamp").toLongLong(); diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index fe8a1b1bd..3ed5e9570 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -92,6 +92,15 @@ bool fitsInLocal8bit(const QString & string) void LauncherPartLaunch::executeTask() { + QString jarPath = APPLICATION->getJarPath("NewLaunch.jar"); + if (jarPath.isEmpty()) + { + const char *reason = QT_TR_NOOP("Launcher library could not be found. Please check your installation."); + emit logLine(tr(reason), MessageLevel::Fatal); + emitFailed(tr(reason)); + return; + } + auto instance = m_parent->instance(); std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); @@ -108,7 +117,7 @@ void LauncherPartLaunch::executeTask() m_process.setDetachable(true); auto classPath = minecraftInstance->getClassPath(); - classPath.prepend(FS::PathCombine(APPLICATION->getJarsPath(), "NewLaunch.jar")); + classPath.prepend(jarPath); auto natPath = minecraftInstance->getNativePath(); #ifdef Q_OS_WIN diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index 025771e8b..2cee15bf1 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -127,6 +127,11 @@ void JavaPage::loadSettings() void JavaPage::on_javaDetectBtn_clicked() { + if (JavaUtils::getJavaCheckPath().isEmpty()) { + JavaCommon::javaCheckNotFound(this); + return; + } + JavaInstallPtr java; VersionSelectDialog vselect(APPLICATION->javalist().get(), tr("Select a Java version"), this, true); diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index b45628432..5c0369b55 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -50,6 +50,7 @@ #include "Application.h" #include "java/JavaInstallList.h" +#include "java/JavaUtils.h" #include "FileSystem.h" @@ -336,6 +337,11 @@ void InstanceSettingsPage::loadSettings() void InstanceSettingsPage::on_javaDetectBtn_clicked() { + if (JavaUtils::getJavaCheckPath().isEmpty()) { + JavaCommon::javaCheckNotFound(this); + return; + } + JavaInstallPtr java; VersionSelectDialog vselect(APPLICATION->javalist().get(), tr("Select a Java version"), this, true); diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index 340518b1c..f07659098 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -11,6 +11,7 @@ #include +#include "JavaCommon.h" #include "java/JavaInstall.h" #include "java/JavaUtils.h" #include "FileSystem.h" @@ -133,6 +134,10 @@ void JavaSettingsWidget::initialize() void JavaSettingsWidget::refresh() { + if (JavaUtils::getJavaCheckPath().isEmpty()) { + JavaCommon::javaCheckNotFound(this); + return; + } m_versionWidget->loadList(); } From 4232b1cedbae070ef27bd56d3fb3fad165e9836e Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 23 Jun 2022 07:19:41 -0300 Subject: [PATCH 277/308] fix: don't use uniform sizes in Modrinth modpack viewer Apparently, when Qt sees an icon with the height smaller than the rest, with this option set, it will change the height of all other items to be that one, causing #828. While we do lose some performance changing this option, the issue is gone, so :| Signed-off-by: flow --- launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui | 3 --- 1 file changed, 3 deletions(-) diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui index ae9556edf..6a34701d9 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui @@ -63,9 +63,6 @@ 48 - - true - From 4bfc445cf8f2a9b0e1d704e43f5a81f841159f79 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 23 Jun 2022 07:58:27 -0300 Subject: [PATCH 278/308] fix: add progress indicator on Flame mod resolution dialog This code is super :pofat: omg Signed-off-by: flow --- launcher/InstanceImportTask.cpp | 15 +++++++-------- launcher/modplatform/flame/FileResolvingTask.cpp | 4 +++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index d5684805a..6b0f08d1e 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -412,12 +412,8 @@ void InstanceImportTask::processFlame() "You will need to manually download them and add them to the modpack"), text); message_dialog->setModal(true); - message_dialog->show(); - connect(message_dialog, &QDialog::rejected, [&]() { - m_modIdResolver.reset(); - emitFailed("Canceled"); - }); - connect(message_dialog, &QDialog::accepted, [&]() { + + if (message_dialog->exec()) { m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network()); for (const auto &result: m_modIdResolver->getResults().files) { QString filename = result.fileName; @@ -469,8 +465,11 @@ void InstanceImportTask::processFlame() }); setStatus(tr("Downloading mods...")); m_filesNetJob->start(); - }); - }else{ + } else { + m_modIdResolver.reset(); + emitFailed("Canceled"); + } + } else { //TODO extract to function ? m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network()); for (const auto &result: m_modIdResolver->getResults().files) { diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index a790ab9c5..c1f56658f 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -10,7 +10,7 @@ Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptr Date: Fri, 24 Jun 2022 09:02:58 -0300 Subject: [PATCH 279/308] feat+fix: cache versions and extra info in Modrinth packs When you change a copy thinking you're changing the original data smh Signed-off-by: flow --- .../modplatform/modrinth/ModrinthModel.cpp | 11 ++++++++ .../modplatform/modrinth/ModrinthModel.h | 1 + .../modplatform/modrinth/ModrinthPage.cpp | 25 ++++++++++++++----- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 39b935a6d..5018faa22 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -104,6 +104,17 @@ auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVarian return {}; } +bool ModpackListModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + int pos = index.row(); + if (pos >= modpacks.size() || pos < 0 || !index.isValid()) + return false; + + modpacks[pos] = value.value(); + + return true; +} + void ModpackListModel::performPaginatedSearch() { // TODO: Move to standalone API diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h index 1b4d8da46..249d6483a 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h @@ -64,6 +64,7 @@ class ModpackListModel : public QAbstractListModel { /* Retrieve information from the model at a given index with the given role */ auto data(const QModelIndex& index, int role) const -> QVariant override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; inline void setActiveJob(NetJob::Ptr ptr) { jobPtr = ptr; } diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index d85006749..713b98ab5 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -101,18 +101,18 @@ bool ModrinthPage::eventFilter(QObject* watched, QEvent* event) return QObject::eventFilter(watched, event); } -void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) +void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev) { ui->versionSelectionBox->clear(); - if (!first.isValid()) { + if (!curr.isValid()) { if (isOpened) { dialog->setSuggestedPack(); } return; } - current = m_model->data(first, Qt::UserRole).value(); + current = m_model->data(curr, Qt::UserRole).value(); auto name = current.name; if (!current.extraInfoLoaded) { @@ -125,7 +125,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) netJob->addNetAction(Net::Download::makeByteArray(QString("%1/project/%2").arg(BuildConfig.MODRINTH_PROD_URL, id), response)); - QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id] { + QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id, curr] { if (id != current.id) { return; // wrong request? } @@ -149,6 +149,13 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) } updateUI(); + + QVariant current_updated; + current_updated.setValue(current); + + if (!m_model->setData(curr, current_updated, Qt::UserRole)) + qWarning() << "Failed to cache extra info for the current pack!"; + suggestCurrent(); }); QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { @@ -170,7 +177,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) netJob->addNetAction( Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response)); - QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id] { + QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id, curr] { if (id != current.id) { return; // wrong request? } @@ -195,6 +202,12 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) ui->versionSelectionBox->addItem(version.version, QVariant(version.id)); } + QVariant current_updated; + current_updated.setValue(current); + + if (!m_model->setData(curr, current_updated, Qt::UserRole)) + qWarning() << "Failed to cache versions for the current pack!"; + suggestCurrent(); }); QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { @@ -224,7 +237,7 @@ void ModrinthPage::updateUI() // TODO: Implement multiple authors with links text += "
" + tr(" by ") + QString("%2").arg(std::get<1>(current.author).toString(), std::get<0>(current.author)); - if(current.extraInfoLoaded) { + if (current.extraInfoLoaded) { if (!current.extra.donate.isEmpty()) { text += "

" + tr("Donate information: "); auto donateToStr = [](Modrinth::DonationData& donate) -> QString { From 64d123f5243ee666d4e0c582452ec804e4fbe74b Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 24 Jun 2022 09:11:11 -0300 Subject: [PATCH 280/308] fix: use better naming for Modrinth pack versions Signed-off-by: flow --- .../ui/pages/modplatform/modrinth/ModrinthPage.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index 713b98ab5..df29c0c33 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -199,7 +199,10 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev) } for (auto version : current.versions) { - ui->versionSelectionBox->addItem(version.version, QVariant(version.id)); + if (!version.name.contains(version.version)) + ui->versionSelectionBox->addItem(QString("%1 — %2").arg(version.name, version.version), QVariant(version.id)); + else + ui->versionSelectionBox->addItem(version.name, QVariant(version.id)); } QVariant current_updated; @@ -218,7 +221,10 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev) } else { for (auto version : current.versions) { - ui->versionSelectionBox->addItem(QString("%1 - %2").arg(version.name, version.version), QVariant(version.id)); + if (!version.name.contains(version.version)) + ui->versionSelectionBox->addItem(QString("%1 - %2").arg(version.name, version.version), QVariant(version.id)); + else + ui->versionSelectionBox->addItem(version.name, QVariant(version.id)); } suggestCurrent(); From 64776d6bacfd33317306d08c5ce35b7827714c04 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 24 Jun 2022 09:16:41 -0300 Subject: [PATCH 281/308] feat+fix: cache Flame modpack versions Signed-off-by: flow --- launcher/ui/pages/modplatform/flame/FlameModel.cpp | 11 +++++++++++ launcher/ui/pages/modplatform/flame/FlameModel.h | 1 + launcher/ui/pages/modplatform/flame/FlamePage.cpp | 14 ++++++++++---- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index f97536e8a..f1e8a8351 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -57,6 +57,17 @@ QVariant ListModel::data(const QModelIndex& index, int role) const return QVariant(); } +bool ListModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + int pos = index.row(); + if (pos >= modpacks.size() || pos < 0 || !index.isValid()) + return false; + + modpacks[pos] = value.value(); + + return true; +} + void ListModel::logoLoaded(QString logo, QIcon out) { m_loadingLogos.removeAll(logo); diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.h b/launcher/ui/pages/modplatform/flame/FlameModel.h index 536f6adde..cab666cc3 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.h +++ b/launcher/ui/pages/modplatform/flame/FlameModel.h @@ -34,6 +34,7 @@ public: int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; Qt::ItemFlags flags(const QModelIndex &index) const override; bool canFetchMore(const QModelIndex & parent) const override; void fetchMore(const QModelIndex & parent) override; diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index b65ace6bb..e2a485ddc 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -107,18 +107,18 @@ void FlamePage::triggerSearch() listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex()); } -void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second) +void FlamePage::onSelectionChanged(QModelIndex curr, QModelIndex prev) { ui->versionSelectionBox->clear(); - if (!first.isValid()) { + if (!curr.isValid()) { if (isOpened) { dialog->setSuggestedPack(); } return; } - current = listModel->data(first, Qt::UserRole).value(); + current = listModel->data(curr, Qt::UserRole).value(); if (current.versionsLoaded == false) { qDebug() << "Loading flame modpack versions"; @@ -127,7 +127,7 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second) int addonId = current.addonId; netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response)); - QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId] { + QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId, curr] { if (addonId != current.addonId) { return; // wrong request } @@ -151,6 +151,12 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second) ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl)); } + QVariant current_updated; + current_updated.setValue(current); + + if (!listModel->setData(curr, current_updated, Qt::UserRole)) + qWarning() << "Failed to cache versions for the current pack!"; + suggestCurrent(); }); QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { From 145da82cd8ca6856975eca175fdad74f6d6a0659 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 24 Jun 2022 09:26:35 -0300 Subject: [PATCH 282/308] fix: show invalid version even when there's none Having a blank instead of _anything_ is bad UX. Instead, even when there's not a valid version (most likely disabled redistribution), we show a message in the UI, to differentiate from the loading state. Signed-off-by: flow --- launcher/ui/pages/modplatform/flame/FlamePage.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index e2a485ddc..7d2ba2e2f 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -157,6 +157,10 @@ void FlamePage::onSelectionChanged(QModelIndex curr, QModelIndex prev) if (!listModel->setData(curr, current_updated, Qt::UserRole)) qWarning() << "Failed to cache versions for the current pack!"; + // TODO: Check whether it's a connection issue or the project disabled 3rd-party distribution. + if (current.versionsLoaded && ui->versionSelectionBox->count() < 1) { + ui->versionSelectionBox->addItem(tr("No version is available!"), -1); + } suggestCurrent(); }); QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { @@ -172,6 +176,11 @@ void FlamePage::onSelectionChanged(QModelIndex curr, QModelIndex prev) suggestCurrent(); } + // TODO: Check whether it's a connection issue or the project disabled 3rd-party distribution. + if (current.versionsLoaded && ui->versionSelectionBox->count() < 1) { + ui->versionSelectionBox->addItem(tr("No version is available!"), -1); + } + updateUi(); } @@ -181,7 +190,7 @@ void FlamePage::suggestCurrent() return; } - if (selectedVersion.isEmpty()) { + if (selectedVersion.isEmpty() || selectedVersion == "-1") { dialog->setSuggestedPack(); return; } From e5f6dc1b14a03b078b69be1c4c3c5819092604c3 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 24 Jun 2022 20:09:44 -0300 Subject: [PATCH 283/308] fix: aborts when using a Qt build with assertions enabled Preventing undefined behaviour hooray! :D Signed-off-by: flow --- launcher/minecraft/mod/ModFolderModel.cpp | 14 +++++++++----- launcher/ui/pages/modplatform/ModModel.cpp | 4 ++++ launcher/ui/pages/modplatform/flame/FlameModel.cpp | 5 +++++ .../pages/modplatform/modrinth/ModrinthModel.cpp | 4 ++++ .../ui/pages/modplatform/technic/TechnicModel.cpp | 5 +++++ 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index ded2d3a2a..bc2362a91 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -167,12 +167,16 @@ void ModFolderModel::finishUpdate() { QSet added = newSet; added.subtract(currentSet); - beginInsertRows(QModelIndex(), mods.size(), mods.size() + added.size() - 1); - for(auto & addedMod: added) { - mods.append(newMods[addedMod]); - resolveMod(mods.last()); + + // When you have a Qt build with assertions turned on, proceeding here will abort the application + if (added.size() > 0) { + beginInsertRows(QModelIndex(), mods.size(), mods.size() + added.size() - 1); + for (auto& addedMod : added) { + mods.append(newMods[addedMod]); + resolveMod(mods.last()); + } + endInsertRows(); } - endInsertRows(); } // update index diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 4917b8908..94b1f0993 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -219,6 +219,10 @@ void ListModel::searchRequestFinished(QJsonDocument& doc) searchState = CanPossiblyFetchMore; } + // When you have a Qt build with assertions turned on, proceeding here will abort the application + if (newList.size() == 0) + return; + beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1); modpacks.append(newList); endInsertRows(); diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index f1e8a8351..b98046813 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -221,6 +221,11 @@ void Flame::ListModel::searchRequestFinished() nextSearchOffset += 25; searchState = CanPossiblyFetchMore; } + + // When you have a Qt build with assertions turned on, proceeding here will abort the application + if (newList.size() == 0) + return; + beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1); modpacks.append(newList); endInsertRows(); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 5018faa22..3633d5752 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -290,6 +290,10 @@ void ModpackListModel::searchRequestFinished(QJsonDocument& doc_all) searchState = CanPossiblyFetchMore; } + // When you have a Qt build with assertions turned on, proceeding here will abort the application + if (newList.size() == 0) + return; + beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1); modpacks.append(newList); endInsertRows(); diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp index 9c9d1e751..742f4f2a3 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp @@ -217,6 +217,11 @@ void Technic::ListModel::searchRequestFinished() return; } searchState = Finished; + + // When you have a Qt build with assertions turned on, proceeding here will abort the application + if (newList.size() == 0) + return; + beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1); modpacks.append(newList); endInsertRows(); From cad581388f32dc3523c81f614e898716685f35c3 Mon Sep 17 00:00:00 2001 From: Jan200101 Date: Wed, 29 Jun 2022 22:37:25 +0200 Subject: [PATCH 284/308] Add Performance related settings Integrates support for Feral Gamemode, discrete GPU support for Mesa and the proprietary Nvidia driver and MangoHud support Signed-off-by: Jan200101 --- CMakeLists.txt | 1 + launcher/Application.cpp | 5 + launcher/BaseInstance.h | 1 + launcher/CMakeLists.txt | 7 + launcher/NullInstance.h | 4 + launcher/minecraft/MinecraftInstance.cpp | 36 ++ launcher/minecraft/MinecraftInstance.h | 1 + .../minecraft/launch/DirectJavaLaunch.cpp | 22 +- .../minecraft/launch/LauncherPartLaunch.cpp | 17 +- launcher/ui/pages/global/MinecraftPage.cpp | 13 + launcher/ui/pages/global/MinecraftPage.ui | 60 ++- .../pages/instance/InstanceSettingsPage.cpp | 26 ++ .../ui/pages/instance/InstanceSettingsPage.ui | 68 ++++ libraries/README.md | 8 + libraries/gamemode/CMakeLists.txt | 7 + libraries/gamemode/include/gamemode_client.h | 365 ++++++++++++++++++ 16 files changed, 628 insertions(+), 13 deletions(-) create mode 100644 libraries/gamemode/CMakeLists.txt create mode 100644 libraries/gamemode/include/gamemode_client.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b09e7fd22..dd6918d8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -293,6 +293,7 @@ add_subdirectory(libraries/classparser) # class parser library add_subdirectory(libraries/optional-bare) add_subdirectory(libraries/tomlc99) # toml parser add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much +add_subdirectory(libraries/gamemode) ############################### Built Artifacts ############################### diff --git a/launcher/Application.cpp b/launcher/Application.cpp index bafb928ba..757d852f8 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -638,6 +638,11 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("UseNativeOpenAL", false); m_settings->registerSetting("UseNativeGLFW", false); + // Peformance related options + m_settings->registerSetting("EnableFeralGamemode", false); + m_settings->registerSetting("EnableMangoHud", false); + m_settings->registerSetting("UseDiscreteGpu", false); + // Game time m_settings->registerSetting("ShowGameTime", true); m_settings->registerSetting("ShowGlobalGameTime", true); diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 661776146..2a94dcc64 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -188,6 +188,7 @@ public: * Create envrironment variables for running the instance */ virtual QProcessEnvironment createEnvironment() = 0; + virtual QProcessEnvironment createLaunchEnvironment() = 0; /*! * Returns a matcher that can maps relative paths within the instance to whether they are 'log files' diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index b8db803b0..f837ba7c5 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -998,6 +998,13 @@ target_link_libraries(Launcher_logic BuildConfig Katabasis ) + +if (UNIX AND NOT CYGWIN AND NOT APPLE) + target_link_libraries(Launcher_logic + gamemode + ) +endif() + target_link_libraries(Launcher_logic Qt5::Core Qt5::Xml diff --git a/launcher/NullInstance.h b/launcher/NullInstance.h index ed4214336..9b0a9331d 100644 --- a/launcher/NullInstance.h +++ b/launcher/NullInstance.h @@ -39,6 +39,10 @@ public: { return QProcessEnvironment(); } + QProcessEnvironment createLaunchEnvironment() override + { + return QProcessEnvironment(); + } QMap getVariables() const override { return QMap(); diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 7e72601ff..10f04e5bf 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -154,6 +154,12 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO m_settings->registerOverride(globalSettings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride); m_settings->registerOverride(globalSettings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride); + // Peformance related options + auto performanceOverride = m_settings->registerSetting("OverridePerformance", false); + m_settings->registerOverride(globalSettings->getSetting("EnableFeralGamemode"), performanceOverride); + m_settings->registerOverride(globalSettings->getSetting("EnableMangoHud"), performanceOverride); + m_settings->registerOverride(globalSettings->getSetting("UseDiscreteGpu"), performanceOverride); + // Game time auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false); m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride); @@ -435,6 +441,36 @@ QProcessEnvironment MinecraftInstance::createEnvironment() return env; } +QProcessEnvironment MinecraftInstance::createLaunchEnvironment() +{ + // prepare the process environment + QProcessEnvironment env = createEnvironment(); + +#ifdef Q_OS_LINUX + if (settings()->get("EnableMangoHud").toBool()) + { + auto preload = env.value("LD_PRELOAD", "") + ":libMangoHud_dlsym.so:libMangoHud.so"; + auto lib_path = env.value("LD_LIBRARY_PATH", "") + ":/usr/local/$LIB/mangohud/:/usr/$LIB/mangohud/"; + + env.insert("LD_PRELOAD", preload); + env.insert("LD_LIBRARY_PATH", lib_path); + env.insert("MANGOHUD", "1"); + } + + if (settings()->get("UseDiscreteGpu").toBool()) + { + // Open Source Drivers + env.insert("DRI_PRIME", "1"); + // Proprietary Nvidia Drivers + env.insert("__NV_PRIME_RENDER_OFFLOAD", "1"); + env.insert("__VK_LAYER_NV_optimus", "NVIDIA_only"); + env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia"); + } +#endif + + return env; +} + static QString replaceTokensIn(QString text, QMap with) { QString result; diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index fda58aa7a..05450d410 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -91,6 +91,7 @@ public: /// create an environment for launching processes QProcessEnvironment createEnvironment() override; + QProcessEnvironment createLaunchEnvironment() override; /// guess log level from a line of minecraft log MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) override; diff --git a/launcher/minecraft/launch/DirectJavaLaunch.cpp b/launcher/minecraft/launch/DirectJavaLaunch.cpp index 742170fa0..152485b3a 100644 --- a/launcher/minecraft/launch/DirectJavaLaunch.cpp +++ b/launcher/minecraft/launch/DirectJavaLaunch.cpp @@ -12,13 +12,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - #include "DirectJavaLaunch.h" + +#include + #include #include #include #include -#include + +#ifdef Q_OS_LINUX +#include "gamemode_client.h" +#endif DirectJavaLaunch::DirectJavaLaunch(LaunchTask *parent) : LaunchStep(parent) { @@ -50,7 +55,7 @@ void DirectJavaLaunch::executeTask() auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString()); - m_process.setProcessEnvironment(instance->createEnvironment()); + m_process.setProcessEnvironment(instance->createLaunchEnvironment()); // make detachable - this will keep the process running even if the object is destroyed m_process.setDetachable(true); @@ -79,6 +84,17 @@ void DirectJavaLaunch::executeTask() { m_process.start(javaPath, args); } + +#ifdef Q_OS_LINUX + if (instance->settings()->get("EnableFeralGamemode").toBool()) + { + auto pid = m_process.processId(); + if (pid) + { + gamemode_request_start_for(pid); + } + } +#endif } void DirectJavaLaunch::on_state(LoggedProcess::State state) diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index 427bc32be..e37c64fa0 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -44,6 +44,10 @@ #include "Commandline.h" #include "Application.h" +#ifdef Q_OS_LINUX +#include "gamemode_client.h" +#endif + LauncherPartLaunch::LauncherPartLaunch(LaunchTask *parent) : LaunchStep(parent) { auto instance = parent->instance(); @@ -102,7 +106,7 @@ void LauncherPartLaunch::executeTask() auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString()); - m_process.setProcessEnvironment(instance->createEnvironment()); + m_process.setProcessEnvironment(instance->createLaunchEnvironment()); // make detachable - this will keep the process running even if the object is destroyed m_process.setDetachable(true); @@ -167,6 +171,17 @@ void LauncherPartLaunch::executeTask() { m_process.start(javaPath, args); } + +#ifdef Q_OS_LINUX + if (instance->settings()->get("EnableFeralGamemode").toBool()) + { + auto pid = m_process.processId(); + if (pid) + { + gamemode_request_start_for(pid); + } + } +#endif } void LauncherPartLaunch::on_state(LoggedProcess::State state) diff --git a/launcher/ui/pages/global/MinecraftPage.cpp b/launcher/ui/pages/global/MinecraftPage.cpp index f49f5a920..e3ac7e7cd 100644 --- a/launcher/ui/pages/global/MinecraftPage.cpp +++ b/launcher/ui/pages/global/MinecraftPage.cpp @@ -87,6 +87,11 @@ void MinecraftPage::applySettings() s->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked()); s->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked()); + // Peformance related options + s->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked()); + s->set("EnableMangoHud", ui->enableMangoHud->isChecked()); + s->set("UseDiscreteGpu", ui->useDiscreteGpuCheck->isChecked()); + // Game time s->set("ShowGameTime", ui->showGameTime->isChecked()); s->set("ShowGlobalGameTime", ui->showGlobalGameTime->isChecked()); @@ -109,6 +114,14 @@ void MinecraftPage::loadSettings() ui->useNativeOpenALCheck->setChecked(s->get("UseNativeOpenAL").toBool()); ui->useNativeGLFWCheck->setChecked(s->get("UseNativeGLFW").toBool()); + ui->enableFeralGamemodeCheck->setChecked(s->get("EnableFeralGamemode").toBool()); + ui->enableMangoHud->setChecked(s->get("EnableMangoHud").toBool()); + ui->useDiscreteGpuCheck->setChecked(s->get("UseDiscreteGpu").toBool()); + +#if !defined(Q_OS_LINUX) + ui->perfomanceGroupBox->setVisible(false); +#endif + ui->showGameTime->setChecked(s->get("ShowGameTime").toBool()); ui->showGlobalGameTime->setChecked(s->get("ShowGlobalGameTime").toBool()); ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool()); diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui index 353390bd4..640f436de 100644 --- a/launcher/ui/pages/global/MinecraftPage.ui +++ b/launcher/ui/pages/global/MinecraftPage.ui @@ -134,6 +134,45 @@
+ + + + Performance + + + + + + <html><head/><body><p>Enable Feral Interactive's GameMode, to potentially improve gaming performance.</p></body></html> + + + Enable Feral GameMode + + + + + + + <html><head/><body><p>Enable MangoHud's advanced performance overlay.</p></body></html> + + + Enable MangoHud + + + + + + + <html><head/><body><p>Use the discrete GPU instead of the primary GPU.</p></body></html> + + + Use discrete GPU + + + + + + @@ -181,15 +220,15 @@ - - - <html><head/><body><p>The launcher will automatically quit after the game exits or crashes.</p></body></html> - - - &Quit the launcher after game window closes - - - + + + <html><head/><body><p>The launcher will automatically quit after the game exits or crashes.</p></body></html> + + + &Quit the launcher after game window closes + + + @@ -218,6 +257,9 @@ windowHeightSpinBox useNativeGLFWCheck useNativeOpenALCheck + enableFeralGamemodeCheck + enableMangoHud + useDiscreteGpuCheck diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index b45628432..459447c81 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -232,6 +232,22 @@ void InstanceSettingsPage::applySettings() m_settings->reset("UseNativeGLFW"); } + // Performance + bool performance = ui->perfomanceGroupBox->isChecked(); + m_settings->set("OverridePerformance", performance); + if(performance) + { + m_settings->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked()); + m_settings->set("EnableMangoHud", ui->enableMangoHud->isChecked()); + m_settings->set("UseDiscreteGpu", ui->useDiscreteGpuCheck->isChecked()); + } + else + { + m_settings->reset("EnableFeralGamemode"); + m_settings->reset("EnableMangoHud"); + m_settings->reset("UseDiscreteGpu"); + } + // Game time bool gameTime = ui->gameTimeGroupBox->isChecked(); m_settings->set("OverrideGameTime", gameTime); @@ -325,6 +341,16 @@ void InstanceSettingsPage::loadSettings() ui->useNativeGLFWCheck->setChecked(m_settings->get("UseNativeGLFW").toBool()); ui->useNativeOpenALCheck->setChecked(m_settings->get("UseNativeOpenAL").toBool()); + // Performance + ui->perfomanceGroupBox->setChecked(m_settings->get("OverridePerformance").toBool()); + ui->enableFeralGamemodeCheck->setChecked(m_settings->get("EnableFeralGamemode").toBool()); + ui->enableMangoHud->setChecked(m_settings->get("EnableMangoHud").toBool()); + ui->useDiscreteGpuCheck->setChecked(m_settings->get("UseDiscreteGpu").toBool()); + + #if !defined(Q_OS_LINUX) + ui->perfomanceGroupBox->setVisible(false); + #endif + // Miscellanous ui->gameTimeGroupBox->setChecked(m_settings->get("OverrideGameTime").toBool()); ui->showGameTime->setChecked(m_settings->get("ShowGameTime").toBool()); diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui index cb66b3ce5..8b3c33702 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.ui +++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui @@ -455,6 +455,74 @@ + + + Performance + + + + + + true + + + Performance + + + true + + + false + + + + + + <html><head/><body><p>Enable Feral Interactive's GameMode, to potentially improve gaming performance.</p></body></html> + + + Enable Feral GameMode + + + + + + + <html><head/><body><p>Enable MangoHud's advanced performance overlay.</p></body></html> + + + Enable MangoHud + + + + + + + <html><head/><body><p>Use the discrete GPU instead of the primary GPU.</p></body></html> + + + Use discrete GPU + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + Miscellaneous diff --git a/libraries/README.md b/libraries/README.md index 7e7e740d1..bdaef7a64 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -179,3 +179,11 @@ Licenced under the MIT licence. Tiny implementation of LZMA2 de/compression. This format is only used by Forge to save bandwidth. Public domain. + +## gamemode + +performance optimisation daemon + +Upstream https://github.com/FeralInteractive/gamemode + +BSD licensed diff --git a/libraries/gamemode/CMakeLists.txt b/libraries/gamemode/CMakeLists.txt new file mode 100644 index 000000000..9e07f34ac --- /dev/null +++ b/libraries/gamemode/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.9.4) +project(gamemode + VERSION 1.6.1) + +add_library(gamemode) +target_include_directories(gamemode PUBLIC include) +target_link_libraries(gamemode PUBLIC ${CMAKE_DL_LIBS}) diff --git a/libraries/gamemode/include/gamemode_client.h b/libraries/gamemode/include/gamemode_client.h new file mode 100644 index 000000000..b6f7afd4c --- /dev/null +++ b/libraries/gamemode/include/gamemode_client.h @@ -0,0 +1,365 @@ +/* + +Copyright (c) 2017-2019, Feral Interactive +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Feral Interactive nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + */ +#ifndef CLIENT_GAMEMODE_H +#define CLIENT_GAMEMODE_H +/* + * GameMode supports the following client functions + * Requests are refcounted in the daemon + * + * int gamemode_request_start() - Request gamemode starts + * 0 if the request was sent successfully + * -1 if the request failed + * + * int gamemode_request_end() - Request gamemode ends + * 0 if the request was sent successfully + * -1 if the request failed + * + * GAMEMODE_AUTO can be defined to make the above two functions apply during static init and + * destruction, as appropriate. In this configuration, errors will be printed to stderr + * + * int gamemode_query_status() - Query the current status of gamemode + * 0 if gamemode is inactive + * 1 if gamemode is active + * 2 if gamemode is active and this client is registered + * -1 if the query failed + * + * int gamemode_request_start_for(pid_t pid) - Request gamemode starts for another process + * 0 if the request was sent successfully + * -1 if the request failed + * -2 if the request was rejected + * + * int gamemode_request_end_for(pid_t pid) - Request gamemode ends for another process + * 0 if the request was sent successfully + * -1 if the request failed + * -2 if the request was rejected + * + * int gamemode_query_status_for(pid_t pid) - Query status of gamemode for another process + * 0 if gamemode is inactive + * 1 if gamemode is active + * 2 if gamemode is active and this client is registered + * -1 if the query failed + * + * const char* gamemode_error_string() - Get an error string + * returns a string describing any of the above errors + * + * Note: All the above requests can be blocking - dbus requests can and will block while the daemon + * handles the request. It is not recommended to make these calls in performance critical code + */ + +#include +#include + +#include +#include + +#include + +static char internal_gamemode_client_error_string[512] = { 0 }; + +/** + * Load libgamemode dynamically to dislodge us from most dependencies. + * This allows clients to link and/or use this regardless of runtime. + * See SDL2 for an example of the reasoning behind this in terms of + * dynamic versioning as well. + */ +static volatile int internal_libgamemode_loaded = 1; + +/* Typedefs for the functions to load */ +typedef int (*api_call_return_int)(void); +typedef const char *(*api_call_return_cstring)(void); +typedef int (*api_call_pid_return_int)(pid_t); + +/* Storage for functors */ +static api_call_return_int REAL_internal_gamemode_request_start = NULL; +static api_call_return_int REAL_internal_gamemode_request_end = NULL; +static api_call_return_int REAL_internal_gamemode_query_status = NULL; +static api_call_return_cstring REAL_internal_gamemode_error_string = NULL; +static api_call_pid_return_int REAL_internal_gamemode_request_start_for = NULL; +static api_call_pid_return_int REAL_internal_gamemode_request_end_for = NULL; +static api_call_pid_return_int REAL_internal_gamemode_query_status_for = NULL; + +/** + * Internal helper to perform the symbol binding safely. + * + * Returns 0 on success and -1 on failure + */ +__attribute__((always_inline)) static inline int internal_bind_libgamemode_symbol( + void *handle, const char *name, void **out_func, size_t func_size, bool required) +{ + void *symbol_lookup = NULL; + char *dl_error = NULL; + + /* Safely look up the symbol */ + symbol_lookup = dlsym(handle, name); + dl_error = dlerror(); + if (required && (dl_error || !symbol_lookup)) { + snprintf(internal_gamemode_client_error_string, + sizeof(internal_gamemode_client_error_string), + "dlsym failed - %s", + dl_error); + return -1; + } + + /* Have the symbol correctly, copy it to make it usable */ + memcpy(out_func, &symbol_lookup, func_size); + return 0; +} + +/** + * Loads libgamemode and needed functions + * + * Returns 0 on success and -1 on failure + */ +__attribute__((always_inline)) static inline int internal_load_libgamemode(void) +{ + /* We start at 1, 0 is a success and -1 is a fail */ + if (internal_libgamemode_loaded != 1) { + return internal_libgamemode_loaded; + } + + /* Anonymous struct type to define our bindings */ + struct binding { + const char *name; + void **functor; + size_t func_size; + bool required; + } bindings[] = { + { "real_gamemode_request_start", + (void **)&REAL_internal_gamemode_request_start, + sizeof(REAL_internal_gamemode_request_start), + true }, + { "real_gamemode_request_end", + (void **)&REAL_internal_gamemode_request_end, + sizeof(REAL_internal_gamemode_request_end), + true }, + { "real_gamemode_query_status", + (void **)&REAL_internal_gamemode_query_status, + sizeof(REAL_internal_gamemode_query_status), + false }, + { "real_gamemode_error_string", + (void **)&REAL_internal_gamemode_error_string, + sizeof(REAL_internal_gamemode_error_string), + true }, + { "real_gamemode_request_start_for", + (void **)&REAL_internal_gamemode_request_start_for, + sizeof(REAL_internal_gamemode_request_start_for), + false }, + { "real_gamemode_request_end_for", + (void **)&REAL_internal_gamemode_request_end_for, + sizeof(REAL_internal_gamemode_request_end_for), + false }, + { "real_gamemode_query_status_for", + (void **)&REAL_internal_gamemode_query_status_for, + sizeof(REAL_internal_gamemode_query_status_for), + false }, + }; + + void *libgamemode = NULL; + + /* Try and load libgamemode */ + libgamemode = dlopen("libgamemode.so.0", RTLD_NOW); + if (!libgamemode) { + /* Attempt to load unversioned library for compatibility with older + * versions (as of writing, there are no ABI changes between the two - + * this may need to change if ever ABI-breaking changes are made) */ + libgamemode = dlopen("libgamemode.so", RTLD_NOW); + if (!libgamemode) { + snprintf(internal_gamemode_client_error_string, + sizeof(internal_gamemode_client_error_string), + "dlopen failed - %s", + dlerror()); + internal_libgamemode_loaded = -1; + return -1; + } + } + + /* Attempt to bind all symbols */ + for (size_t i = 0; i < sizeof(bindings) / sizeof(bindings[0]); i++) { + struct binding *binder = &bindings[i]; + + if (internal_bind_libgamemode_symbol(libgamemode, + binder->name, + binder->functor, + binder->func_size, + binder->required)) { + internal_libgamemode_loaded = -1; + return -1; + }; + } + + /* Success */ + internal_libgamemode_loaded = 0; + return 0; +} + +/** + * Redirect to the real libgamemode + */ +__attribute__((always_inline)) static inline const char *gamemode_error_string(void) +{ + /* If we fail to load the system gamemode, or we have an error string already, return our error + * string instead of diverting to the system version */ + if (internal_load_libgamemode() < 0 || internal_gamemode_client_error_string[0] != '\0') { + return internal_gamemode_client_error_string; + } + + return REAL_internal_gamemode_error_string(); +} + +/** + * Redirect to the real libgamemode + * Allow automatically requesting game mode + * Also prints errors as they happen. + */ +#ifdef GAMEMODE_AUTO +__attribute__((constructor)) +#else +__attribute__((always_inline)) static inline +#endif +int gamemode_request_start(void) +{ + /* Need to load gamemode */ + if (internal_load_libgamemode() < 0) { +#ifdef GAMEMODE_AUTO + fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); +#endif + return -1; + } + + if (REAL_internal_gamemode_request_start() < 0) { +#ifdef GAMEMODE_AUTO + fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); +#endif + return -1; + } + + return 0; +} + +/* Redirect to the real libgamemode */ +#ifdef GAMEMODE_AUTO +__attribute__((destructor)) +#else +__attribute__((always_inline)) static inline +#endif +int gamemode_request_end(void) +{ + /* Need to load gamemode */ + if (internal_load_libgamemode() < 0) { +#ifdef GAMEMODE_AUTO + fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); +#endif + return -1; + } + + if (REAL_internal_gamemode_request_end() < 0) { +#ifdef GAMEMODE_AUTO + fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); +#endif + return -1; + } + + return 0; +} + +/* Redirect to the real libgamemode */ +__attribute__((always_inline)) static inline int gamemode_query_status(void) +{ + /* Need to load gamemode */ + if (internal_load_libgamemode() < 0) { + return -1; + } + + if (REAL_internal_gamemode_query_status == NULL) { + snprintf(internal_gamemode_client_error_string, + sizeof(internal_gamemode_client_error_string), + "gamemode_query_status missing (older host?)"); + return -1; + } + + return REAL_internal_gamemode_query_status(); +} + +/* Redirect to the real libgamemode */ +__attribute__((always_inline)) static inline int gamemode_request_start_for(pid_t pid) +{ + /* Need to load gamemode */ + if (internal_load_libgamemode() < 0) { + return -1; + } + + if (REAL_internal_gamemode_request_start_for == NULL) { + snprintf(internal_gamemode_client_error_string, + sizeof(internal_gamemode_client_error_string), + "gamemode_request_start_for missing (older host?)"); + return -1; + } + + return REAL_internal_gamemode_request_start_for(pid); +} + +/* Redirect to the real libgamemode */ +__attribute__((always_inline)) static inline int gamemode_request_end_for(pid_t pid) +{ + /* Need to load gamemode */ + if (internal_load_libgamemode() < 0) { + return -1; + } + + if (REAL_internal_gamemode_request_end_for == NULL) { + snprintf(internal_gamemode_client_error_string, + sizeof(internal_gamemode_client_error_string), + "gamemode_request_end_for missing (older host?)"); + return -1; + } + + return REAL_internal_gamemode_request_end_for(pid); +} + +/* Redirect to the real libgamemode */ +__attribute__((always_inline)) static inline int gamemode_query_status_for(pid_t pid) +{ + /* Need to load gamemode */ + if (internal_load_libgamemode() < 0) { + return -1; + } + + if (REAL_internal_gamemode_query_status_for == NULL) { + snprintf(internal_gamemode_client_error_string, + sizeof(internal_gamemode_client_error_string), + "gamemode_query_status_for missing (older host?)"); + return -1; + } + + return REAL_internal_gamemode_query_status_for(pid); +} + +#endif // CLIENT_GAMEMODE_H From 00df092a99214db0a4c2329e0a07af7b9a70df14 Mon Sep 17 00:00:00 2001 From: txtsd Date: Wed, 6 Jul 2022 09:26:00 +0530 Subject: [PATCH 285/308] chore(readme): Reword and place entry in alphabetical order Signed-off-by: txtsd --- libraries/README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/libraries/README.md b/libraries/README.md index bdaef7a64..511fad157 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -9,6 +9,14 @@ This library has served as a base for some (much more full-featured and advanced Copyright belongs to Petr Mrázek, unless explicitly stated otherwise in the source files. Available under the Apache 2.0 license. +## gamemode + +A performance optimization daemon. + +See [github repo](https://github.com/FeralInteractive/gamemode). + +BSD licensed + ## hoedown Hoedown is a revived fork of Sundown, the Markdown parser based on the original code of the Upskirt library by Natacha Porté. @@ -180,10 +188,3 @@ Tiny implementation of LZMA2 de/compression. This format is only used by Forge t Public domain. -## gamemode - -performance optimisation daemon - -Upstream https://github.com/FeralInteractive/gamemode - -BSD licensed From e210a4b2444e7e818cf2959027b719c928e2ecca Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 6 Jul 2022 18:12:16 +0200 Subject: [PATCH 286/308] Revert "fix: remove updater if it is not used" This reverts commit 2ff0aa09e35eb6910ef0a030ea41f84a1ed95782. Signed-off-by: Sefa Eyeoglu --- launcher/Application.cpp | 6 ---- launcher/Application.h | 3 -- launcher/CMakeLists.txt | 29 +++++++++---------- .../minecraft/launch/LauncherPartLaunch.cpp | 1 - launcher/net/PasteUpload.cpp | 2 -- launcher/ui/GuiUtil.cpp | 1 - launcher/ui/MainWindow.cpp | 10 ------- launcher/ui/MainWindow.h | 8 ----- launcher/ui/pages/global/LauncherPage.cpp | 9 +++--- launcher/ui/pages/global/LauncherPage.h | 5 ++-- launcher/ui/pages/global/LauncherPage.ui | 3 -- launcher/ui/pages/instance/LogPage.cpp | 1 - launcher/ui/pages/instance/ScreenshotsPage.h | 1 - launcher/ui/pages/instance/ServersPage.cpp | 1 - launcher/ui/pages/instance/WorldListPage.cpp | 1 - .../modplatform/legacy_ftb/ListModel.cpp | 2 -- .../modplatform/modrinth/ModrinthModel.h | 1 - 17 files changed, 19 insertions(+), 65 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 0ef641ba0..c6e04a85a 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -154,7 +154,6 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QSt fflush(stderr); } -#ifdef LAUNCHER_WITH_UPDATER QString getIdealPlatform(QString currentPlatform) { auto info = Sys::getKernelInfo(); switch(info.kernelType) { @@ -193,7 +192,6 @@ QString getIdealPlatform(QString currentPlatform) { } return currentPlatform; } -#endif } @@ -758,7 +756,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) qDebug() << "<> Translations loaded."; } -#ifdef LAUNCHER_WITH_UPDATER // initialize the updater if(BuildConfig.UPDATER_ENABLED) { @@ -768,7 +765,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_updateChecker.reset(new UpdateChecker(m_network, channelUrl, BuildConfig.VERSION_CHANNEL, BuildConfig.VERSION_BUILD)); qDebug() << "<> Updater started."; } -#endif // Instance icons { @@ -1414,9 +1410,7 @@ MainWindow* Application::showMainWindow(bool minimized) } m_mainWindow->checkInstancePathForProblems(); -#ifdef LAUNCHER_WITH_UPDATER connect(this, &Application::updateAllowedChanged, m_mainWindow, &MainWindow::updatesAllowedChanged); -#endif connect(m_mainWindow, &MainWindow::isClosing, this, &Application::on_windowClose); m_openWindows++; } diff --git a/launcher/Application.h b/launcher/Application.h index 18461ad88..e3b4fced3 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -42,10 +42,7 @@ #include #include #include - -#ifdef LAUNCHER_WITH_UPDATER #include -#endif #include diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index eb5a68cd1..2d53776da 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -149,23 +149,20 @@ set(LAUNCH_SOURCES launch/LogModel.h ) -if (Launcher_UPDATER_BASE) - set(Launcher_APP_BINARY_DEFS "-DLAUNCHER_WITH_UPDATER ${Launcher_APP_BINARY_DEFS}") - # Old update system - set(UPDATE_SOURCES - updater/GoUpdate.h - updater/GoUpdate.cpp - updater/UpdateChecker.h - updater/UpdateChecker.cpp - updater/DownloadTask.h - updater/DownloadTask.cpp - ) +# Old update system +set(UPDATE_SOURCES + updater/GoUpdate.h + updater/GoUpdate.cpp + updater/UpdateChecker.h + updater/UpdateChecker.cpp + updater/DownloadTask.h + updater/DownloadTask.cpp +) - ecm_add_test(updater/UpdateChecker_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test - TEST_NAME UpdateChecker) - ecm_add_test(updater/DownloadTask_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test - TEST_NAME DownloadTask) -endif() +ecm_add_test(updater/UpdateChecker_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test + TEST_NAME UpdateChecker) +ecm_add_test(updater/DownloadTask_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test + TEST_NAME DownloadTask) # Backend for the news bar... there's usually no news. set(NEWS_SOURCES diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index 9965ef890..63e4d90f4 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -36,7 +36,6 @@ #include "LauncherPartLaunch.h" #include -#include #include "launch/LaunchTask.h" #include "minecraft/MinecraftInstance.h" diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index 7438e1a16..835e4cd12 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -44,8 +44,6 @@ #include #include #include -#include -#include std::array PasteUpload::PasteTypes = { {{"0x0.st", "https://0x0.st", ""}, diff --git a/launcher/ui/GuiUtil.cpp b/launcher/ui/GuiUtil.cpp index b1ea5ee96..5a62e4d06 100644 --- a/launcher/ui/GuiUtil.cpp +++ b/launcher/ui/GuiUtil.cpp @@ -39,7 +39,6 @@ #include #include #include -#include #include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/CustomMessageBox.h" diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index f68cf61ae..aeff80732 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1024,7 +1024,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow } -#ifdef LAUNCHER_WITH_UPDATER if(BuildConfig.UPDATER_ENABLED) { bool updatesAllowed = APPLICATION->updatesAreAllowed(); @@ -1043,7 +1042,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow updater->checkForUpdate(APPLICATION->settings()->get("UpdateChannel").toString(), false); } } -#endif setSelectedInstanceById(APPLICATION->settings()->get("SelectedInstance").toString()); @@ -1355,7 +1353,6 @@ void MainWindow::repopulateAccountsMenu() ui->profileMenu->addAction(ui->actionManageAccounts); } -#ifdef LAUNCHER_WITH_UPDATER void MainWindow::updatesAllowedChanged(bool allowed) { if(!BuildConfig.UPDATER_ENABLED) @@ -1364,7 +1361,6 @@ void MainWindow::updatesAllowedChanged(bool allowed) } ui->actionCheckUpdate->setEnabled(allowed); } -#endif /* * Assumes the sender is a QAction @@ -1470,7 +1466,6 @@ void MainWindow::updateNewsLabel() } } -#ifdef LAUNCHER_WITH_UPDATER void MainWindow::updateAvailable(GoUpdate::Status status) { if(!APPLICATION->updatesAreAllowed()) @@ -1496,7 +1491,6 @@ void MainWindow::updateNotAvailable() UpdateDialog dlg(false, this); dlg.exec(); } -#endif QList stringToIntList(const QString &string) { @@ -1518,7 +1512,6 @@ QString intListToString(const QList &list) return slist.join(','); } -#ifdef LAUNCHER_WITH_UPDATER void MainWindow::downloadUpdates(GoUpdate::Status status) { if(!APPLICATION->updatesAreAllowed()) @@ -1552,7 +1545,6 @@ void MainWindow::downloadUpdates(GoUpdate::Status status) CustomMessageBox::selectable(this, tr("Error"), updateTask.failReason(), QMessageBox::Warning)->show(); } } -#endif void MainWindow::onCatToggled(bool state) { @@ -1865,7 +1857,6 @@ void MainWindow::on_actionConfig_Folder_triggered() } } -#ifdef LAUNCHER_WITH_UPDATER void MainWindow::checkForUpdates() { if(BuildConfig.UPDATER_ENABLED) @@ -1878,7 +1869,6 @@ void MainWindow::checkForUpdates() qWarning() << "Updater not set up. Cannot check for updates."; } } -#endif void MainWindow::on_actionSettings_triggered() { diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 4615975e2..0ca8ec7b3 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -78,9 +78,7 @@ public: void checkInstancePathForProblems(); -#ifdef LAUNCHER_WITH_UPDATER void updatesAllowedChanged(bool allowed); -#endif void droppedURLs(QList urls); signals: @@ -126,9 +124,7 @@ private slots: void on_actionViewCentralModsFolder_triggered(); -#ifdef LAUNCHER_WITH_UPDATER void checkForUpdates(); -#endif void on_actionSettings_triggered(); @@ -197,11 +193,9 @@ private slots: void startTask(Task *task); -#ifdef LAUNCHER_WITH_UPDATER void updateAvailable(GoUpdate::Status status); void updateNotAvailable(); -#endif void defaultAccountChanged(); @@ -211,12 +205,10 @@ private slots: void updateNewsLabel(); -#ifdef LAUNCHER_WITH_UPDATER /*! * Runs the DownloadTask and installs updates. */ void downloadUpdates(GoUpdate::Status status); -#endif void konamiTriggered(); diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index edbf609ff..4be24979d 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -78,7 +78,6 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch m_languageModel = APPLICATION->translations(); loadSettings(); -#ifdef LAUNCHER_WITH_UPDATER if(BuildConfig.UPDATER_ENABLED) { QObject::connect(APPLICATION->updateChecker().get(), &UpdateChecker::channelListLoaded, this, &LauncherPage::refreshUpdateChannelList); @@ -91,9 +90,11 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch { APPLICATION->updateChecker()->updateChanList(false); } - ui->updateSettingsBox->setHidden(false); } -#endif + else + { + ui->updateSettingsBox->setHidden(true); + } connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview())); connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview())); } @@ -188,7 +189,6 @@ void LauncherPage::on_metadataDisableBtn_clicked() ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked()); } -#ifdef LAUNCHER_WITH_UPDATER void LauncherPage::refreshUpdateChannelList() { // Stop listening for selection changes. It's going to change a lot while we update it and @@ -260,7 +260,6 @@ void LauncherPage::refreshUpdateChannelDesc() m_currentUpdateChannel = selected.id; } } -#endif void LauncherPage::applySettings() { diff --git a/launcher/ui/pages/global/LauncherPage.h b/launcher/ui/pages/global/LauncherPage.h index ccfd7e9e3..f38c922e2 100644 --- a/launcher/ui/pages/global/LauncherPage.h +++ b/launcher/ui/pages/global/LauncherPage.h @@ -90,7 +90,6 @@ slots: void on_iconsDirBrowseBtn_clicked(); void on_metadataDisableBtn_clicked(); -#ifdef LAUNCHER_WITH_UPDATER /*! * Updates the list of update channels in the combo box. */ @@ -101,13 +100,13 @@ slots: */ void refreshUpdateChannelDesc(); - void updateChannelSelectionChanged(int index); -#endif /*! * Updates the font preview */ void refreshFontPreview(); + void updateChannelSelectionChanged(int index); + private: Ui::LauncherPage *ui; diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index ceb68c5b3..417bbe059 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -50,9 +50,6 @@ Update Settings - - false - diff --git a/launcher/ui/pages/instance/LogPage.cpp b/launcher/ui/pages/instance/LogPage.cpp index a6c98c088..8fefb44c7 100644 --- a/launcher/ui/pages/instance/LogPage.cpp +++ b/launcher/ui/pages/instance/LogPage.cpp @@ -40,7 +40,6 @@ #include "Application.h" #include -#include #include #include diff --git a/launcher/ui/pages/instance/ScreenshotsPage.h b/launcher/ui/pages/instance/ScreenshotsPage.h index c34c97551..c22706af9 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.h +++ b/launcher/ui/pages/instance/ScreenshotsPage.h @@ -35,7 +35,6 @@ #pragma once -#include #include #include "ui/pages/BasePage.h" diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index c3bde6129..3971d422e 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -48,7 +48,6 @@ #include #include -#include static const int COLUMN_COUNT = 2; // 3 , TBD: latency and other nice things. diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index ff30dd82d..647b04a75 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -47,7 +47,6 @@ #include #include #include -#include #include "tools/MCEditTool.h" #include "FileSystem.h" diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp index 06e9db4f9..c13b15549 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp @@ -46,8 +46,6 @@ #include -#include - namespace LegacyFTB { FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent) diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h index 1b4d8da46..14aa67473 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h @@ -39,7 +39,6 @@ #include "modplatform/modrinth/ModrinthPackManifest.h" #include "ui/pages/modplatform/modrinth/ModrinthPage.h" -#include "net/NetJob.h" class ModPage; class Version; From ffa756ccee8c63471cd8425ab4f4ffcad8875b79 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 6 Jul 2022 18:12:56 +0200 Subject: [PATCH 287/308] fix: remove tests for updater Signed-off-by: Sefa Eyeoglu --- launcher/CMakeLists.txt | 5 - launcher/updater/DownloadTask_test.cpp | 204 ------------------ launcher/updater/UpdateChecker_test.cpp | 148 ------------- launcher/updater/testdata/1.json | 43 ---- launcher/updater/testdata/2.json | 31 --- launcher/updater/testdata/channels.json | 23 -- launcher/updater/testdata/errorChannels.json | 23 -- launcher/updater/testdata/fileOneA | 1 - launcher/updater/testdata/fileOneB | 3 - launcher/updater/testdata/fileThree | 1 - launcher/updater/testdata/fileTwo | 1 - .../updater/testdata/garbageChannels.json | 22 -- launcher/updater/testdata/index.json | 9 - launcher/updater/testdata/noChannels.json | 5 - launcher/updater/testdata/oneChannel.json | 11 - ...t_DownloadTask-test_writeInstallScript.xml | 17 -- 16 files changed, 547 deletions(-) delete mode 100644 launcher/updater/DownloadTask_test.cpp delete mode 100644 launcher/updater/UpdateChecker_test.cpp delete mode 100644 launcher/updater/testdata/1.json delete mode 100644 launcher/updater/testdata/2.json delete mode 100644 launcher/updater/testdata/channels.json delete mode 100644 launcher/updater/testdata/errorChannels.json delete mode 100644 launcher/updater/testdata/fileOneA delete mode 100644 launcher/updater/testdata/fileOneB delete mode 100644 launcher/updater/testdata/fileThree delete mode 100644 launcher/updater/testdata/fileTwo delete mode 100644 launcher/updater/testdata/garbageChannels.json delete mode 100644 launcher/updater/testdata/index.json delete mode 100644 launcher/updater/testdata/noChannels.json delete mode 100644 launcher/updater/testdata/oneChannel.json delete mode 100644 launcher/updater/testdata/tst_DownloadTask-test_writeInstallScript.xml diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 2d53776da..ec8e84c93 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -159,11 +159,6 @@ set(UPDATE_SOURCES updater/DownloadTask.cpp ) -ecm_add_test(updater/UpdateChecker_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test - TEST_NAME UpdateChecker) -ecm_add_test(updater/DownloadTask_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test - TEST_NAME DownloadTask) - # Backend for the news bar... there's usually no news. set(NEWS_SOURCES # News System diff --git a/launcher/updater/DownloadTask_test.cpp b/launcher/updater/DownloadTask_test.cpp deleted file mode 100644 index deba26321..000000000 --- a/launcher/updater/DownloadTask_test.cpp +++ /dev/null @@ -1,204 +0,0 @@ -#include -#include - -#include "updater/GoUpdate.h" -#include "updater/DownloadTask.h" -#include "updater/UpdateChecker.h" -#include - -using namespace GoUpdate; - -FileSourceList encodeBaseFile(const char *suffix) -{ - auto base = QDir::currentPath(); - QUrl localFile = QUrl::fromLocalFile(base + suffix); - QString localUrlString = localFile.toString(QUrl::FullyEncoded); - auto item = FileSource("http", localUrlString); - return FileSourceList({item}); -} - -Q_DECLARE_METATYPE(VersionFileList) -Q_DECLARE_METATYPE(Operation) - -QDebug operator<<(QDebug dbg, const FileSource &f) -{ - dbg.nospace() << "FileSource(type=" << f.type << " url=" << f.url - << " comp=" << f.compressionType << ")"; - return dbg.maybeSpace(); -} - -QDebug operator<<(QDebug dbg, const VersionFileEntry &v) -{ - dbg.nospace() << "VersionFileEntry(path=" << v.path << " mode=" << v.mode - << " md5=" << v.md5 << " sources=" << v.sources << ")"; - return dbg.maybeSpace(); -} - -QDebug operator<<(QDebug dbg, const Operation::Type &t) -{ - switch (t) - { - case Operation::OP_REPLACE: - dbg << "OP_COPY"; - break; - case Operation::OP_DELETE: - dbg << "OP_DELETE"; - break; - } - return dbg.maybeSpace(); -} - -QDebug operator<<(QDebug dbg, const Operation &u) -{ - dbg.nospace() << "Operation(type=" << u.type << " file=" << u.source - << " dest=" << u.destination << " mode=" << u.destinationMode << ")"; - return dbg.maybeSpace(); -} - -class DownloadTaskTest : public QObject -{ - Q_OBJECT -private -slots: - void initTestCase() - { - } - void cleanupTestCase() - { - } - - void test_parseVersionInfo_data() - { - QFile f1(QFINDTESTDATA("testdata/1.json")); - f1.open(QFile::ReadOnly); - QByteArray data1 = f1.readAll(); - f1.close(); - - QFile f2(QFINDTESTDATA("testdata/2.json")); - f2.open(QFile::ReadOnly); - QByteArray data2 = f2.readAll(); - f2.close(); - - QTest::addColumn("data"); - QTest::addColumn("list"); - QTest::addColumn("error"); - QTest::addColumn("ret"); - - QTest::newRow("one") - << data1 - << (VersionFileList() - << VersionFileEntry{"fileOne", - 493, - encodeBaseFile("/data/fileOneA"), - "9eb84090956c484e32cb6c08455a667b"} - << VersionFileEntry{"fileTwo", - 644, - encodeBaseFile("/data/fileTwo"), - "38f94f54fa3eb72b0ea836538c10b043"} - << VersionFileEntry{"fileThree", - 750, - encodeBaseFile("/data/fileThree"), - "f12df554b21e320be6471d7154130e70"}) - << QString() << true; - QTest::newRow("two") - << data2 - << (VersionFileList() - << VersionFileEntry{"fileOne", - 493, - encodeBaseFile("/data/fileOneB"), - "42915a71277c9016668cce7b82c6b577"} - << VersionFileEntry{"fileTwo", - 644, - encodeBaseFile("/data/fileTwo"), - "38f94f54fa3eb72b0ea836538c10b043"}) - << QString() << true; - } - void test_parseVersionInfo() - { - QFETCH(QByteArray, data); - QFETCH(VersionFileList, list); - QFETCH(QString, error); - QFETCH(bool, ret); - - VersionFileList outList; - QString outError; - bool outRet = parseVersionInfo(data, outList, outError); - QCOMPARE(outRet, ret); - QCOMPARE(outList, list); - QCOMPARE(outError, error); - } - - void test_processFileLists_data() - { - QTest::addColumn("tempFolder"); - QTest::addColumn("currentVersion"); - QTest::addColumn("newVersion"); - QTest::addColumn("expectedOperations"); - - QTemporaryDir tempFolderObj; - QString tempFolder = tempFolderObj.path(); - // update fileOne, keep fileTwo, remove fileThree - QTest::newRow("test 1") - << tempFolder << (VersionFileList() - << VersionFileEntry{ - QFINDTESTDATA("testdata/fileOne"), 493, - FileSourceList() - << FileSource( - "http", "http://host/path/fileOne-1"), - "9eb84090956c484e32cb6c08455a667b"} - << VersionFileEntry{ - QFINDTESTDATA("testdata/fileTwo"), 644, - FileSourceList() - << FileSource( - "http", "http://host/path/fileTwo-1"), - "38f94f54fa3eb72b0ea836538c10b043"} - << VersionFileEntry{ - QFINDTESTDATA("testdata/fileThree"), 420, - FileSourceList() - << FileSource( - "http", "http://host/path/fileThree-1"), - "f12df554b21e320be6471d7154130e70"}) - << (VersionFileList() - << VersionFileEntry{ - QFINDTESTDATA("testdata/fileOne"), 493, - FileSourceList() - << FileSource("http", - "http://host/path/fileOne-2"), - "42915a71277c9016668cce7b82c6b577"} - << VersionFileEntry{ - QFINDTESTDATA("testdata/fileTwo"), 644, - FileSourceList() - << FileSource("http", - "http://host/path/fileTwo-2"), - "38f94f54fa3eb72b0ea836538c10b043"}) - << (OperationList() - << Operation::DeleteOp(QFINDTESTDATA("testdata/fileThree")) - << Operation::CopyOp( - FS::PathCombine(tempFolder, - QFINDTESTDATA("data/fileOne").replace("/", "_")), - QFINDTESTDATA("data/fileOne"), 493)); - } - void test_processFileLists() - { - QFETCH(QString, tempFolder); - QFETCH(VersionFileList, currentVersion); - QFETCH(VersionFileList, newVersion); - QFETCH(OperationList, expectedOperations); - - OperationList operations; - - shared_qobject_ptr network = new QNetworkAccessManager(); - processFileLists(currentVersion, newVersion, QDir::currentPath(), tempFolder, new NetJob("Dummy", network), operations); - qDebug() << (operations == expectedOperations); - qDebug() << operations; - qDebug() << expectedOperations; - QCOMPARE(operations, expectedOperations); - } -}; - -extern "C" -{ - QTEST_GUILESS_MAIN(DownloadTaskTest) -} - -#include "DownloadTask_test.moc" diff --git a/launcher/updater/UpdateChecker_test.cpp b/launcher/updater/UpdateChecker_test.cpp deleted file mode 100644 index 70e3381f5..000000000 --- a/launcher/updater/UpdateChecker_test.cpp +++ /dev/null @@ -1,148 +0,0 @@ -#include -#include - -#include "updater/UpdateChecker.h" - -Q_DECLARE_METATYPE(UpdateChecker::ChannelListEntry) - -bool operator==(const UpdateChecker::ChannelListEntry &e1, const UpdateChecker::ChannelListEntry &e2) -{ - qDebug() << e1.url << "vs" << e2.url; - return e1.id == e2.id && - e1.name == e2.name && - e1.description == e2.description && - e1.url == e2.url; -} - -QDebug operator<<(QDebug dbg, const UpdateChecker::ChannelListEntry &c) -{ - dbg.nospace() << "ChannelListEntry(id=" << c.id << " name=" << c.name << " description=" << c.description << " url=" << c.url << ")"; - return dbg.maybeSpace(); -} - -QString findTestDataUrl(const char *file) -{ - return QUrl::fromLocalFile(QFINDTESTDATA(file)).toString(); -} - -class UpdateCheckerTest : public QObject -{ - Q_OBJECT -private -slots: - void initTestCase() - { - - } - void cleanupTestCase() - { - - } - - void tst_ChannelListParsing_data() - { - QTest::addColumn("channel"); - QTest::addColumn("channelUrl"); - QTest::addColumn("hasChannels"); - QTest::addColumn("valid"); - QTest::addColumn >("result"); - - QTest::newRow("garbage") - << QString() - << findTestDataUrl("testdata/garbageChannels.json") - << false - << false - << QList(); - QTest::newRow("errors") - << QString() - << findTestDataUrl("testdata/errorChannels.json") - << false - << true - << QList(); - QTest::newRow("no channels") - << QString() - << findTestDataUrl("testdata/noChannels.json") - << false - << true - << QList(); - QTest::newRow("one channel") - << QString("develop") - << findTestDataUrl("testdata/oneChannel.json") - << true - << true - << (QList() << UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", "http://example.org/stuff"}); - QTest::newRow("several channels") - << QString("develop") - << findTestDataUrl("testdata/channels.json") - << true - << true - << (QList() - << UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", findTestDataUrl("testdata")} - << UpdateChecker::ChannelListEntry{"stable", "Stable", "It's stable at least", findTestDataUrl("testdata")} - << UpdateChecker::ChannelListEntry{"42", "The Channel", "This is the channel that is going to answer all of your questions", "https://dent.me/tea"}); - } - void tst_ChannelListParsing() - { - - QFETCH(QString, channel); - QFETCH(QString, channelUrl); - QFETCH(bool, hasChannels); - QFETCH(bool, valid); - QFETCH(QList, result); - - shared_qobject_ptr nam = new QNetworkAccessManager(); - UpdateChecker checker(nam, channelUrl, channel, 0); - - QSignalSpy channelListLoadedSpy(&checker, SIGNAL(channelListLoaded())); - QVERIFY(channelListLoadedSpy.isValid()); - - checker.updateChanList(false); - - if (valid) - { - QVERIFY(channelListLoadedSpy.wait()); - QCOMPARE(channelListLoadedSpy.size(), 1); - } - else - { - channelListLoadedSpy.wait(); - QCOMPARE(channelListLoadedSpy.size(), 0); - } - - QCOMPARE(checker.hasChannels(), hasChannels); - QCOMPARE(checker.getChannelList(), result); - } - - void tst_UpdateChecking() - { - QString channel = "develop"; - QString channelUrl = findTestDataUrl("testdata/channels.json"); - int currentBuild = 2; - - shared_qobject_ptr nam = new QNetworkAccessManager(); - UpdateChecker checker(nam, channelUrl, channel, currentBuild); - - QSignalSpy updateAvailableSpy(&checker, SIGNAL(updateAvailable(GoUpdate::Status))); - QVERIFY(updateAvailableSpy.isValid()); - QSignalSpy channelListLoadedSpy(&checker, SIGNAL(channelListLoaded())); - QVERIFY(channelListLoadedSpy.isValid()); - - checker.updateChanList(false); - QVERIFY(channelListLoadedSpy.wait()); - - qDebug() << "CWD:" << QDir::current().absolutePath(); - checker.m_channels[0].url = findTestDataUrl("testdata/"); - checker.checkForUpdate(channel, false); - - QVERIFY(updateAvailableSpy.wait()); - - auto status = updateAvailableSpy.first().first().value(); - QCOMPARE(checker.m_channels[0].url, status.newRepoUrl); - QCOMPARE(3, status.newVersionId); - QCOMPARE(currentBuild, status.currentVersionId); - } -}; - -QTEST_GUILESS_MAIN(UpdateCheckerTest) - -#include "UpdateChecker_test.moc" diff --git a/launcher/updater/testdata/1.json b/launcher/updater/testdata/1.json deleted file mode 100644 index 7af7e52d0..000000000 --- a/launcher/updater/testdata/1.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "ApiVersion": 0, - "Id": 1, - "Name": "1.0.1", - "Files": [ - { - "Path": "fileOne", - "Sources": [ - { - "SourceType": "http", - "Url": "@TEST_DATA_URL@/fileOneA" - } - ], - "Executable": true, - "Perms": 493, - "MD5": "9eb84090956c484e32cb6c08455a667b" - }, - { - "Path": "fileTwo", - "Sources": [ - { - "SourceType": "http", - "Url": "@TEST_DATA_URL@/fileTwo" - } - ], - "Executable": false, - "Perms": 644, - "MD5": "38f94f54fa3eb72b0ea836538c10b043" - }, - { - "Path": "fileThree", - "Sources": [ - { - "SourceType": "http", - "Url": "@TEST_DATA_URL@/fileThree" - } - ], - "Executable": false, - "Perms": "750", - "MD5": "f12df554b21e320be6471d7154130e70" - } - ] -} diff --git a/launcher/updater/testdata/2.json b/launcher/updater/testdata/2.json deleted file mode 100644 index 96d430d52..000000000 --- a/launcher/updater/testdata/2.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "ApiVersion": 0, - "Id": 1, - "Name": "1.0.1", - "Files": [ - { - "Path": "fileOne", - "Sources": [ - { - "SourceType": "http", - "Url": "@TEST_DATA_URL@/fileOneB" - } - ], - "Executable": true, - "Perms": 493, - "MD5": "42915a71277c9016668cce7b82c6b577" - }, - { - "Path": "fileTwo", - "Sources": [ - { - "SourceType": "http", - "Url": "@TEST_DATA_URL@/fileTwo" - } - ], - "Executable": false, - "Perms": 644, - "MD5": "38f94f54fa3eb72b0ea836538c10b043" - } - ] -} diff --git a/launcher/updater/testdata/channels.json b/launcher/updater/testdata/channels.json deleted file mode 100644 index 5c6e42cbd..000000000 --- a/launcher/updater/testdata/channels.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "format_version": 0, - "channels": [ - { - "id": "develop", - "name": "Develop", - "description": "The channel called \"develop\"", - "url": "@TEST_DATA_URL@" - }, - { - "id": "stable", - "name": "Stable", - "description": "It's stable at least", - "url": "@TEST_DATA_URL@" - }, - { - "id": "42", - "name": "The Channel", - "description": "This is the channel that is going to answer all of your questions", - "url": "https://dent.me/tea" - } - ] -} diff --git a/launcher/updater/testdata/errorChannels.json b/launcher/updater/testdata/errorChannels.json deleted file mode 100644 index a2cb2165a..000000000 --- a/launcher/updater/testdata/errorChannels.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "format_version": 0, - "channels": [ - { - "id": "", - "name": "Develop", - "description": "The channel called \"develop\"", - "url": "http://example.org/stuff" - }, - { - "id": "stable", - "name": "", - "description": "It's stable at least", - "url": "ftp://username@host/path/to/stuff" - }, - { - "id": "42", - "name": "The Channel", - "description": "This is the channel that is going to answer all of your questions", - "url": "" - } - ] -} diff --git a/launcher/updater/testdata/fileOneA b/launcher/updater/testdata/fileOneA deleted file mode 100644 index f2e41136e..000000000 --- a/launcher/updater/testdata/fileOneA +++ /dev/null @@ -1 +0,0 @@ -stuff diff --git a/launcher/updater/testdata/fileOneB b/launcher/updater/testdata/fileOneB deleted file mode 100644 index f9aba922a..000000000 --- a/launcher/updater/testdata/fileOneB +++ /dev/null @@ -1,3 +0,0 @@ -stuff - -more stuff that came in the new version diff --git a/launcher/updater/testdata/fileThree b/launcher/updater/testdata/fileThree deleted file mode 100644 index 6353ff161..000000000 --- a/launcher/updater/testdata/fileThree +++ /dev/null @@ -1 +0,0 @@ -this is yet another file diff --git a/launcher/updater/testdata/fileTwo b/launcher/updater/testdata/fileTwo deleted file mode 100644 index aad9a93ad..000000000 --- a/launcher/updater/testdata/fileTwo +++ /dev/null @@ -1 +0,0 @@ -some other stuff diff --git a/launcher/updater/testdata/garbageChannels.json b/launcher/updater/testdata/garbageChannels.json deleted file mode 100644 index 344374514..000000000 --- a/launcher/updater/testdata/garbageChannels.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "format_version": 0, - "channels": [ - { - "id": "develop", - "name": "Develop", - "description": "The channel called \"develop\"", -aa "url": "http://example.org/stuff" - }, -a "id": "stable", - "name": "Stable", - "description": "It's stable at least", - "url": "ftp://username@host/path/to/stuff" - }, - { - "id": "42"f - "name": "The Channel", - "description": "This is the channel that is going to answer all of your questions", - "url": "https://dent.me/tea" - } - ] -} diff --git a/launcher/updater/testdata/index.json b/launcher/updater/testdata/index.json deleted file mode 100644 index 867bdcfba..000000000 --- a/launcher/updater/testdata/index.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "ApiVersion": 0, - "Versions": [ - { "Id": 0, "Name": "1.0.0" }, - { "Id": 1, "Name": "1.0.1" }, - { "Id": 2, "Name": "1.0.2" }, - { "Id": 3, "Name": "1.0.3" } - ] -} diff --git a/launcher/updater/testdata/noChannels.json b/launcher/updater/testdata/noChannels.json deleted file mode 100644 index 769889822..000000000 --- a/launcher/updater/testdata/noChannels.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "format_version": 0, - "channels": [ - ] -} diff --git a/launcher/updater/testdata/oneChannel.json b/launcher/updater/testdata/oneChannel.json deleted file mode 100644 index cc8ed2557..000000000 --- a/launcher/updater/testdata/oneChannel.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "format_version": 0, - "channels": [ - { - "id": "develop", - "name": "Develop", - "description": "The channel called \"develop\"", - "url": "http://example.org/stuff" - } - ] -} diff --git a/launcher/updater/testdata/tst_DownloadTask-test_writeInstallScript.xml b/launcher/updater/testdata/tst_DownloadTask-test_writeInstallScript.xml deleted file mode 100644 index 38ecc809c..000000000 --- a/launcher/updater/testdata/tst_DownloadTask-test_writeInstallScript.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - sourceOne - destOne - 0777 - - - PolyMC.exe - P/o/l/y/M/C/e/x/e - 0644 - - - - toDelete.abc - - From 301b811310ce03454deb5167a1d05ddbd82a8007 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 7 Jul 2022 09:56:28 +0200 Subject: [PATCH 288/308] fix: make loader components not important Signed-off-by: Sefa Eyeoglu --- launcher/InstanceImportTask.cpp | 6 +++--- launcher/modplatform/atlauncher/ATLPackInstallTask.cpp | 4 ++-- launcher/modplatform/modpacksch/FTBPackInstallTask.cpp | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index d5684805a..cbac4b379 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -723,11 +723,11 @@ void InstanceImportTask::processModrinth() components->buildingFromScratch(); components->setComponentVersion("net.minecraft", minecraftVersion, true); if (!fabricVersion.isEmpty()) - components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion, true); + components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion); if (!quiltVersion.isEmpty()) - components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion, true); + components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion); if (!forgeVersion.isEmpty()) - components->setComponentVersion("net.minecraftforge", forgeVersion, true); + components->setComponentVersion("net.minecraftforge", forgeVersion); if (m_instIcon != "default") { instance.setIconKey(m_instIcon); diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index b4936bd8e..b8e0f4b07 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -830,14 +830,14 @@ void PackInstallTask::install() auto version = getVersionForLoader("net.minecraftforge"); if(version == Q_NULLPTR) return; - components->setComponentVersion("net.minecraftforge", version, true); + components->setComponentVersion("net.minecraftforge", version); } else if(m_version.loader.type == QString("fabric")) { auto version = getVersionForLoader("net.fabricmc.fabric-loader"); if(version == Q_NULLPTR) return; - components->setComponentVersion("net.fabricmc.fabric-loader", version, true); + components->setComponentVersion("net.fabricmc.fabric-loader", version); } else if(m_version.loader.type != QString()) { diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index c324ffda1..cac432cdd 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -216,10 +216,10 @@ void PackInstallTask::install() if(target.type != "modloader") continue; if(target.name == "forge") { - components->setComponentVersion("net.minecraftforge", target.version, true); + components->setComponentVersion("net.minecraftforge", target.version); } else if(target.name == "fabric") { - components->setComponentVersion("net.fabricmc.fabric-loader", target.version, true); + components->setComponentVersion("net.fabricmc.fabric-loader", target.version); } } From e11706d99d8cfa38a72cc4bde29e8374c05e203a Mon Sep 17 00:00:00 2001 From: Gytis Ivaskevicius Date: Thu, 7 Jul 2022 19:25:14 +0300 Subject: [PATCH 289/308] Cleanup flake.nix Signed-off-by: Gytis Ivaskevicius --- flake.nix | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/flake.nix b/flake.nix index b378fbb00..c2fdffda1 100644 --- a/flake.nix +++ b/flake.nix @@ -9,31 +9,29 @@ outputs = { self, nixpkgs, libnbtplusplus, ... }: let - # Generate a user-friendly version number. + # User-friendly version number. version = builtins.substring 0 8 self.lastModifiedDate; - # System types to support (qtbase is currently broken for "aarch64-darwin") + # Supported systems (qtbase is currently broken for "aarch64-darwin") supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" ]; # Helper function to generate an attrset '{ x86_64-linux = f "x86_64-linux"; ... }'. forAllSystems = nixpkgs.lib.genAttrs supportedSystems; - # Nixpkgs instantiated for supported system types. + # Nixpkgs instantiated for supported systems. pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system}); + + packagesFn = pkgs: rec { + polymc = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus; }; + polymc-qt6 = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus; }; + }; in { - packages = forAllSystems (system: rec { - polymc = pkgs.${system}.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus; }; - polymc-qt6 = pkgs.${system}.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus; }; - - default = polymc; - }); + packages = forAllSystems (system: + let packages = packagesFn pkgs.${system}; in + packages // { default = packages.polymc; } + ); - defaultPackage = forAllSystems (system: self.packages.${system}.default); - - apps = forAllSystems (system: rec { polymc = { type = "app"; program = "${self.defaultPackage.${system}}/bin/polymc"; }; default = polymc; }); - defaultApp = forAllSystems (system: self.apps.${system}.default); - - overlay = final: prev: { polymc = self.defaultPackage.${final.system}; }; + overlay = final: packagesFn; }; } From 9e19b73ce6c27cef72462eee717449337bdf2f92 Mon Sep 17 00:00:00 2001 From: jopejoe1 Date: Thu, 7 Jul 2022 23:18:13 +0200 Subject: [PATCH 290/308] Updated FTB Classic layout Signed-off-by: jopejoe1 --- launcher/ui/pages/modplatform/legacy_ftb/Page.ui | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui index 15e5d4325..f4231d8d6 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui @@ -25,7 +25,7 @@ - 250 + 16777215 16777215 @@ -51,7 +51,7 @@ - 250 + 16777215 16777215 @@ -71,7 +71,7 @@ - 250 + 16777215 16777215 From 4ab0e70a9af2992f917b7900dbc99352b1134652 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Fri, 8 Jul 2022 17:27:15 +0200 Subject: [PATCH 291/308] fix(technic): map loader libraries to components properly Signed-off-by: Sefa Eyeoglu --- .../modplatform/technic/TechnicPackProcessor.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/launcher/modplatform/technic/TechnicPackProcessor.cpp b/launcher/modplatform/technic/TechnicPackProcessor.cpp index 471b4a2f4..95feb4b28 100644 --- a/launcher/modplatform/technic/TechnicPackProcessor.cpp +++ b/launcher/modplatform/technic/TechnicPackProcessor.cpp @@ -187,17 +187,17 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const } else { - static QStringList possibleLoaders{ - "net.minecraftforge:minecraftforge:", - "net.fabricmc:fabric-loader:", - "org.quiltmc:quilt-loader:" + // -> + static QMap loaderMap { + {"net.minecraftforge:minecraftforge:", "net.minecraftforge"}, + {"net.fabricmc:fabric-loader:", "net.fabricmc.fabric-loader"}, + {"org.quiltmc:quilt-loader:", "org.quiltmc.quilt-loader"} }; - for (const auto& loader : possibleLoaders) + for (const auto& loader : loaderMap.keys()) { if (libraryName.startsWith(loader)) { - auto loaderComponent = loader.chopped(1).replace(":", "."); - components->setComponentVersion(loaderComponent, libraryName.section(':', 2)); + components->setComponentVersion(loaderMap.value(loader), libraryName.section(':', 2)); break; } } From 984692dc629ca3712d482b174a67557dd9e635a8 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 2 May 2022 19:10:45 +0200 Subject: [PATCH 292/308] refactor: fix deprecation up to Qt 5.15 Signed-off-by: Sefa Eyeoglu --- CMakeLists.txt | 1 - launcher/Application.cpp | 6 +- launcher/ApplicationMessage.cpp | 9 +- launcher/BaseVersionList.cpp | 2 +- launcher/CMakeLists.txt | 2 +- launcher/FileSystem.h | 4 +- launcher/InstanceList.cpp | 8 +- launcher/Json.cpp | 16 +- launcher/Json.h | 2 - launcher/LoggedProcess.cpp | 13 - launcher/LoggedProcess.h | 1 - launcher/VersionProxyModel.cpp | 5 +- launcher/icons/IconList.cpp | 8 + launcher/icons/MMCIcon.cpp | 4 +- launcher/java/JavaChecker.cpp | 9 + launcher/java/JavaInstallList.cpp | 2 +- launcher/launch/LaunchTask.cpp | 17 + launcher/launch/LaunchTask.h | 1 + launcher/launch/steps/PostLaunchCommand.cpp | 10 + launcher/launch/steps/PreLaunchCommand.cpp | 9 + launcher/minecraft/MinecraftInstance.cpp | 4 + launcher/minecraft/OneSixVersionFormat.cpp | 2 +- launcher/minecraft/PackProfile.cpp | 4 + launcher/minecraft/ProfileUtils.cpp | 18 - launcher/minecraft/ProfileUtils.h | 3 - launcher/minecraft/WorldList.cpp | 6 +- launcher/minecraft/mod/ModFolderModel.cpp | 8 + launcher/modplatform/flame/PackManifest.h | 2 +- .../legacy_ftb/PrivatePackManager.cpp | 8 +- launcher/net/NetJob.cpp | 5 + launcher/translations/TranslationsModel.cpp | 6 +- launcher/ui/MainWindow.cpp | 50 +- launcher/ui/dialogs/CopyInstanceDialog.cpp | 6 + launcher/ui/dialogs/ExportInstanceDialog.cpp | 4 + launcher/ui/dialogs/NewComponentDialog.cpp | 1 - launcher/ui/dialogs/NewInstanceDialog.cpp | 6 + launcher/ui/instanceview/InstanceDelegate.cpp | 6 +- launcher/ui/pages/instance/LogPage.cpp | 2 +- .../ui/pages/instance/ScreenshotsPage.cpp | 2 +- launcher/ui/pages/instance/ServersPage.cpp | 8 + .../modplatform/legacy_ftb/ListModel.cpp | 2 +- .../ui/pages/modplatform/legacy_ftb/Page.cpp | 2 +- launcher/ui/widgets/LabeledToolButton.cpp | 4 +- launcher/ui/widgets/LogView.cpp | 2 +- launcher/ui/widgets/PageContainer.cpp | 2 +- launcher/ui/widgets/VersionListView.cpp | 2 +- libraries/LocalPeer/src/LocalPeer.cpp | 3 +- libraries/iconfix/CMakeLists.txt | 20 - libraries/iconfix/internal/qhexstring_p.h | 100 --- libraries/iconfix/internal/qiconloader.cpp | 688 ------------------ libraries/iconfix/internal/qiconloader_p.h | 219 ------ libraries/iconfix/xdgicon.cpp | 152 ---- libraries/iconfix/xdgicon.h | 48 -- libraries/systeminfo/src/distroutils.cpp | 27 +- 54 files changed, 199 insertions(+), 1352 deletions(-) delete mode 100644 libraries/iconfix/CMakeLists.txt delete mode 100644 libraries/iconfix/internal/qhexstring_p.h delete mode 100644 libraries/iconfix/internal/qiconloader.cpp delete mode 100644 libraries/iconfix/internal/qiconloader_p.h delete mode 100644 libraries/iconfix/xdgicon.cpp delete mode 100644 libraries/iconfix/xdgicon.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 958e6ef6a..134d2d126 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -290,7 +290,6 @@ else() message(STATUS "Using system QuaZip") endif() add_subdirectory(libraries/rainbow) # Qt extension for colors -add_subdirectory(libraries/iconfix) # fork of Qt's QIcon loader add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions add_subdirectory(libraries/classparser) # class parser library add_subdirectory(libraries/optional-bare) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index c6e04a85a..07658c5d2 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -84,6 +84,7 @@ #include #include #include +#include #include "InstanceList.h" @@ -99,7 +100,6 @@ #include "tools/JVisualVM.h" #include "tools/MCEditTool.h" -#include #include "settings/INISettingsObject.h" #include "settings/Setting.h" @@ -1182,7 +1182,7 @@ void Application::setApplicationTheme(const QString& name, bool initial) void Application::setIconTheme(const QString& name) { - XdgIcon::setThemeName(name); + QIcon::setThemeName(name); } QIcon Application::getThemedIcon(const QString& name) @@ -1190,7 +1190,7 @@ QIcon Application::getThemedIcon(const QString& name) if(name == "logo") { return QIcon(":/org.polymc.PolyMC.svg"); } - return XdgIcon::fromTheme(name); + return QIcon::fromTheme(name); } bool Application::openJsonEditor(const QString &filename) diff --git a/launcher/ApplicationMessage.cpp b/launcher/ApplicationMessage.cpp index e22bf13c8..9426b5a7b 100644 --- a/launcher/ApplicationMessage.cpp +++ b/launcher/ApplicationMessage.cpp @@ -2,10 +2,11 @@ #include #include +#include "Json.h" void ApplicationMessage::parse(const QByteArray & input) { - auto doc = QJsonDocument::fromBinaryData(input); - auto root = doc.object(); + auto doc = Json::requireDocument(input, "ApplicationMessage"); + auto root = Json::requireObject(doc, "ApplicationMessage"); command = root.value("command").toString(); args.clear(); @@ -25,7 +26,5 @@ QByteArray ApplicationMessage::serialize() { } root.insert("args", outArgs); - QJsonDocument out; - out.setObject(root); - return out.toBinaryData(); + return Json::toText(root); } diff --git a/launcher/BaseVersionList.cpp b/launcher/BaseVersionList.cpp index aa9cb6cf1..506844093 100644 --- a/launcher/BaseVersionList.cpp +++ b/launcher/BaseVersionList.cpp @@ -51,7 +51,7 @@ QVariant BaseVersionList::data(const QModelIndex &index, int role) const switch (role) { case VersionPointerRole: - return qVariantFromValue(version); + return QVariant::fromValue(version); case VersionRole: return version->name(); diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index ec8e84c93..b90f8cd5b 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -963,6 +963,7 @@ target_link_libraries(Launcher_logic tomlc99 BuildConfig Katabasis + Qt5::Widgets ) if (UNIX AND NOT CYGWIN AND NOT APPLE) @@ -979,7 +980,6 @@ target_link_libraries(Launcher_logic Qt5::Gui ) target_link_libraries(Launcher_logic - Launcher_iconfix QuaZip::QuaZip hoedown LocalPeer diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 31c7af700..93dfa98b7 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -49,8 +49,8 @@ class copy public: copy(const QString & src, const QString & dst) { - m_src = src; - m_dst = dst; + m_src.setPath(src); + m_dst.setPath(dst); } copy & followSymlinks(const bool follow) { diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 3e3c81f71..f6714614c 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -136,7 +136,7 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const { case InstancePointerRole: { - QVariant v = qVariantFromValue((void *)pdata); + QVariant v = QVariant::fromValue((void *)pdata); return v; } case InstanceIDRole: @@ -252,7 +252,7 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name) QStringList InstanceList::getGroups() { - return m_groupNameCache.toList(); + return m_groupNameCache.values(); } void InstanceList::deleteGroup(const QString& name) @@ -353,7 +353,11 @@ QList< InstanceId > InstanceList::discoverInstances() out.append(id); qDebug() << "Found instance ID" << id; } +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + instanceSet = QSet(out.begin(), out.end()); +#else instanceSet = out.toSet(); +#endif m_instancesProbed = true; return out; } diff --git a/launcher/Json.cpp b/launcher/Json.cpp index 37ada1aa2..04b15091d 100644 --- a/launcher/Json.cpp +++ b/launcher/Json.cpp @@ -22,14 +22,6 @@ void write(const QJsonArray &array, const QString &filename) write(QJsonDocument(array), filename); } -QByteArray toBinary(const QJsonObject &obj) -{ - return QJsonDocument(obj).toBinaryData(); -} -QByteArray toBinary(const QJsonArray &array) -{ - return QJsonDocument(array).toBinaryData(); -} QByteArray toText(const QJsonObject &obj) { return QJsonDocument(obj).toJson(QJsonDocument::Compact); @@ -48,12 +40,8 @@ QJsonDocument requireDocument(const QByteArray &data, const QString &what) { if (isBinaryJson(data)) { - QJsonDocument doc = QJsonDocument::fromBinaryData(data); - if (doc.isNull()) - { - throw JsonException(what + ": Invalid JSON (binary JSON detected)"); - } - return doc; + // FIXME: Is this needed? + throw JsonException(what + ": Invalid JSON. Binary JSON unsupported"); } else { diff --git a/launcher/Json.h b/launcher/Json.h index f2e68f0c2..06a45a728 100644 --- a/launcher/Json.h +++ b/launcher/Json.h @@ -29,8 +29,6 @@ void write(const QJsonObject &object, const QString &filename); /// @throw FileSystemException void write(const QJsonArray &array, const QString &filename); -QByteArray toBinary(const QJsonObject &obj); -QByteArray toBinary(const QJsonArray &array); QByteArray toText(const QJsonObject &obj); QByteArray toText(const QJsonArray &array); diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp index 2479f4ff4..05d2fd746 100644 --- a/launcher/LoggedProcess.cpp +++ b/launcher/LoggedProcess.cpp @@ -157,19 +157,6 @@ void LoggedProcess::on_stateChange(QProcess::ProcessState state) } } -#if defined Q_OS_WIN32 -#include -#endif - -qint64 LoggedProcess::processId() const -{ -#ifdef Q_OS_WIN - return pid() ? pid()->dwProcessId : 0; -#else - return pid(); -#endif -} - void LoggedProcess::setDetachable(bool detachable) { m_is_detachable = detachable; diff --git a/launcher/LoggedProcess.h b/launcher/LoggedProcess.h index e52b8a7ba..03ded98c7 100644 --- a/launcher/LoggedProcess.h +++ b/launcher/LoggedProcess.h @@ -43,7 +43,6 @@ public: State state() const; int exitCode() const; - qint64 processId() const; void setDetachable(bool detachable); diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp index b9a87c9c2..684547f8f 100644 --- a/launcher/VersionProxyModel.cpp +++ b/launcher/VersionProxyModel.cpp @@ -208,7 +208,8 @@ QVariant VersionProxyModel::data(const QModelIndex &index, int role) const { return APPLICATION->getThemedIcon("bug"); } - auto pixmap = QPixmapCache::find("placeholder"); + QPixmap pixmap; + QPixmapCache::find("placeholder", &pixmap); if(!pixmap) { QPixmap px(16,16); @@ -216,7 +217,7 @@ QVariant VersionProxyModel::data(const QModelIndex &index, int role) const QPixmapCache::insert("placeholder", px); return px; } - return *pixmap; + return pixmap; } } default: diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp index d426aa804..fe7c34eaf 100644 --- a/launcher/icons/IconList.cpp +++ b/launcher/icons/IconList.cpp @@ -86,7 +86,11 @@ void IconList::directoryChanged(const QString &path) QString &foo = (*it); foo = m_dir.filePath(foo); } +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + QSet new_set(new_list.begin(), new_list.end()); +#else auto new_set = new_list.toSet(); +#endif QList current_list; for (auto &it : icons) { @@ -94,7 +98,11 @@ void IconList::directoryChanged(const QString &path) continue; current_list.push_back(it.m_images[IconType::FileBased].filename); } +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + QSet current_set(current_list.begin(), current_list.end()); +#else QSet current_set = current_list.toSet(); +#endif QSet to_remove = current_set; to_remove -= new_set; diff --git a/launcher/icons/MMCIcon.cpp b/launcher/icons/MMCIcon.cpp index f0b82ec90..29e3939b6 100644 --- a/launcher/icons/MMCIcon.cpp +++ b/launcher/icons/MMCIcon.cpp @@ -15,7 +15,7 @@ #include "MMCIcon.h" #include -#include +#include IconType operator--(IconType &t, int) { @@ -63,7 +63,7 @@ QIcon MMCIcon::icon() const if(!icon.isNull()) return icon; // FIXME: inject this. - return XdgIcon::fromTheme(m_images[m_current_type].key); + return QIcon::fromTheme(m_images[m_current_type].key); } void MMCIcon::remove(IconType rm_type) diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 15b222605..c38462885 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -105,7 +105,12 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) bool success = true; QMap results; + +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + QStringList lines = m_stdout.split("\n", Qt::SkipEmptyParts); +#else QStringList lines = m_stdout.split("\n", QString::SkipEmptyParts); +#endif for(QString line : lines) { line = line.trimmed(); @@ -114,7 +119,11 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) continue; } +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + auto parts = line.split('=', Qt::SkipEmptyParts); +#else auto parts = line.split('=', QString::SkipEmptyParts); +#endif if(parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty()) { continue; diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp index 9b7450953..c32d89e11 100644 --- a/launcher/java/JavaInstallList.cpp +++ b/launcher/java/JavaInstallList.cpp @@ -81,7 +81,7 @@ QVariant JavaInstallList::data(const QModelIndex &index, int role) const switch (role) { case VersionPointerRole: - return qVariantFromValue(m_vlist[index.row()]); + return QVariant::fromValue(m_vlist[index.row()]); case VersionIdRole: return version->descriptor(); case VersionRole: diff --git a/launcher/launch/LaunchTask.cpp b/launcher/launch/LaunchTask.cpp index d5442a2bd..3aa950520 100644 --- a/launcher/launch/LaunchTask.cpp +++ b/launcher/launch/LaunchTask.cpp @@ -282,6 +282,23 @@ void LaunchTask::emitFailed(QString reason) Task::emitFailed(reason); } +void LaunchTask::substituteVariables(const QStringList &args) const +{ + auto variables = m_instance->getVariables(); + auto envVariables = QProcessEnvironment::systemEnvironment(); + + for (auto arg : args) { + for (auto key : variables) + { + arg.replace("$" + key, variables.value(key)); + } + for (auto env : envVariables.keys()) + { + arg.replace("$" + env, envVariables.value(env)); + } + } +} + QString LaunchTask::substituteVariables(const QString &cmd) const { QString out = cmd; diff --git a/launcher/launch/LaunchTask.h b/launcher/launch/LaunchTask.h index a1e891ac6..6ab0a3930 100644 --- a/launcher/launch/LaunchTask.h +++ b/launcher/launch/LaunchTask.h @@ -85,6 +85,7 @@ public: /* methods */ shared_qobject_ptr getLogModel(); public: + void substituteVariables(const QStringList &args) const; QString substituteVariables(const QString &cmd) const; QString censorPrivateInfo(QString in); diff --git a/launcher/launch/steps/PostLaunchCommand.cpp b/launcher/launch/steps/PostLaunchCommand.cpp index 143eb4412..9aece9753 100644 --- a/launcher/launch/steps/PostLaunchCommand.cpp +++ b/launcher/launch/steps/PostLaunchCommand.cpp @@ -27,9 +27,19 @@ PostLaunchCommand::PostLaunchCommand(LaunchTask *parent) : LaunchStep(parent) void PostLaunchCommand::executeTask() { + //FIXME: where to put this? +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + auto args = QProcess::splitCommand(m_command); + m_parent->substituteVariables(args); + + emit logLine(tr("Running Post-Launch command: %1").arg(args.join(' ')), MessageLevel::Launcher); + const QString program = args.takeFirst(); + m_process.start(program, args); +#else QString postlaunch_cmd = m_parent->substituteVariables(m_command); emit logLine(tr("Running Post-Launch command: %1").arg(postlaunch_cmd), MessageLevel::Launcher); m_process.start(postlaunch_cmd); +#endif } void PostLaunchCommand::on_state(LoggedProcess::State state) diff --git a/launcher/launch/steps/PreLaunchCommand.cpp b/launcher/launch/steps/PreLaunchCommand.cpp index 1a0889c88..d3660b30f 100644 --- a/launcher/launch/steps/PreLaunchCommand.cpp +++ b/launcher/launch/steps/PreLaunchCommand.cpp @@ -28,9 +28,18 @@ PreLaunchCommand::PreLaunchCommand(LaunchTask *parent) : LaunchStep(parent) void PreLaunchCommand::executeTask() { //FIXME: where to put this? +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + auto args = QProcess::splitCommand(m_command); + m_parent->substituteVariables(args); + + emit logLine(tr("Running Pre-Launch command: %1").arg(args.join(' ')), MessageLevel::Launcher); + const QString program = args.takeFirst(); + m_process.start(program, args); +#else QString prelaunch_cmd = m_parent->substituteVariables(m_command); emit logLine(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd), MessageLevel::Launcher); m_process.start(prelaunch_cmd); +#endif } void PreLaunchCommand::on_state(LoggedProcess::State state) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 889c6dde8..445a1bf09 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -540,7 +540,11 @@ QStringList MinecraftInstance::processMinecraftArgs( token_mapping["assets_root"] = absAssetsDir; token_mapping["assets_index_name"] = assets->id; +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + QStringList parts = args_pattern.split(' ', Qt::SkipEmptyParts); +#else QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); +#endif for (int i = 0; i < parts.length(); i++) { parts[i] = replaceTokensIn(parts[i], token_mapping); diff --git a/launcher/minecraft/OneSixVersionFormat.cpp b/launcher/minecraft/OneSixVersionFormat.cpp index 879f18c1d..1983b7bbb 100644 --- a/launcher/minecraft/OneSixVersionFormat.cpp +++ b/launcher/minecraft/OneSixVersionFormat.cpp @@ -296,7 +296,7 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch } writeString(root, "appletClass", patch->appletClass); writeStringList(root, "+tweakers", patch->addTweakers); - writeStringList(root, "+traits", patch->traits.toList()); + writeStringList(root, "+traits", patch->traits.values()); if (!patch->libraries.isEmpty()) { QJsonArray array; diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index 01d42b00e..f0f236251 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -688,7 +688,11 @@ void PackProfile::move(const int index, const MoveDirection direction) return; } beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap); +#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) + d->components.swapItemsAt(index, theirIndex); +#else d->components.swap(index, theirIndex); +#endif endMoveRows(); invalidateLaunchProfile(); scheduleSave(); diff --git a/launcher/minecraft/ProfileUtils.cpp b/launcher/minecraft/ProfileUtils.cpp index 8ca24cc8c..28299c8f0 100644 --- a/launcher/minecraft/ProfileUtils.cpp +++ b/launcher/minecraft/ProfileUtils.cpp @@ -141,24 +141,6 @@ bool saveJsonFile(const QJsonDocument doc, const QString & filename) return true; } -VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo) -{ - QFile file(fileInfo.absoluteFilePath()); - if (!file.open(QFile::ReadOnly)) - { - auto errorStr = QObject::tr("Unable to open the version file %1: %2.").arg(fileInfo.fileName(), file.errorString()); - return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr); - } - QJsonDocument doc = QJsonDocument::fromBinaryData(file.readAll()); - file.close(); - if (doc.isNull()) - { - file.remove(); - throw JSONValidationError(QObject::tr("Unable to process the version file %1.").arg(fileInfo.fileName())); - } - return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), false); -} - void removeLwjglFromPatch(VersionFilePtr patch) { auto filter = [](QList& libs) diff --git a/launcher/minecraft/ProfileUtils.h b/launcher/minecraft/ProfileUtils.h index 351c36cb4..8b80c488f 100644 --- a/launcher/minecraft/ProfileUtils.h +++ b/launcher/minecraft/ProfileUtils.h @@ -19,9 +19,6 @@ VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder) /// Save a JSON file (in any format) bool saveJsonFile(const QJsonDocument doc, const QString & filename); -/// Parse a version file in binary JSON format -VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo); - /// Remove LWJGL from a patch file. This is applied to all Mojang-like profile files. void removeLwjglFromPatch(VersionFilePtr patch); diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index 955609bf7..75d0877e6 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -195,7 +195,7 @@ QVariant WorldList::data(const QModelIndex &index, int role) const switch (column) { case SizeColumn: - return qVariantFromValue(world.bytes()); + return QVariant::fromValue(world.bytes()); default: return data(index, Qt::DisplayRole); @@ -215,7 +215,7 @@ QVariant WorldList::data(const QModelIndex &index, int role) const } case SeedRole: { - return qVariantFromValue(world.seed()); + return QVariant::fromValue(world.seed()); } case NameRole: { @@ -227,7 +227,7 @@ QVariant WorldList::data(const QModelIndex &index, int role) const } case SizeRole: { - return qVariantFromValue(world.bytes()); + return QVariant::fromValue(world.bytes()); } case IconFileRole: { diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index bc2362a91..0545352ba 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -116,9 +116,17 @@ bool ModFolderModel::update() void ModFolderModel::finishUpdate() { +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + auto currentList = modsIndex.keys(); + QSet currentSet(currentList.begin(), currentList.end()); + auto & newMods = m_update->mods; + auto newList = newMods.keys(); + QSet newSet(newList.begin(), newList.end()); +#else QSet currentSet = modsIndex.keys().toSet(); auto & newMods = m_update->mods; QSet newSet = newMods.keys().toSet(); +#endif // see if the kept mods changed in some way { diff --git a/launcher/modplatform/flame/PackManifest.h b/launcher/modplatform/flame/PackManifest.h index 26a48d1c1..51fe8888d 100644 --- a/launcher/modplatform/flame/PackManifest.h +++ b/launcher/modplatform/flame/PackManifest.h @@ -25,7 +25,7 @@ struct File bool resolved = false; QString fileName; QUrl url; - QString targetFolder = QLatin1Literal("mods"); + QString targetFolder = QStringLiteral("mods"); enum class Type { Unknown, diff --git a/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp b/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp index 501e6003e..824798c0d 100644 --- a/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp +++ b/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp @@ -10,7 +10,13 @@ void PrivatePackManager::load() { try { +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + auto foo = QString::fromUtf8(FS::read(m_filename)).split('\n', Qt::SkipEmptyParts); + currentPacks = QSet(foo.begin(), foo.end()); +#else currentPacks = QString::fromUtf8(FS::read(m_filename)).split('\n', QString::SkipEmptyParts).toSet(); +#endif + dirty = false; } catch(...) @@ -28,7 +34,7 @@ void PrivatePackManager::save() const } try { - QStringList list = currentPacks.toList(); + QStringList list = currentPacks.values(); FS::write(m_filename, list.join('\n').toUtf8()); dirty = false; } diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index df899178f..349273697 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -97,7 +97,12 @@ auto NetJob::abort() -> bool bool fullyAborted = true; // fail all downloads on the queue +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + QSet todoSet(m_todo.begin(), m_todo.end()); + m_failed.unite(todoSet); +#else m_failed.unite(m_todo.toSet()); +#endif m_todo.clear(); // abort active downloads diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 53722d690..bf5a6d432 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -52,7 +52,7 @@ #include "Application.h" -const static QLatin1Literal defaultLangCode("en_US"); +const static QLatin1String defaultLangCode("en_US"); enum class FileType { @@ -431,9 +431,7 @@ QVariant TranslationsModel::data(const QModelIndex& index, int role) const } case Column::Completeness: { - QString text; - text.sprintf("%3.1f %%", lang.percentTranslated()); - return text; + return QString("%1%").arg(lang.percentTranslated(), 3, 'f', 1); } } } diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index aeff80732..18e06349b 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -42,31 +42,31 @@ #include "MainWindow.h" -#include -#include -#include -#include +#include +#include +#include +#include -#include +#include +#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -1494,7 +1494,11 @@ void MainWindow::updateNotAvailable() QList stringToIntList(const QString &string) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + QStringList split = string.split(',', Qt::SkipEmptyParts); +#else QStringList split = string.split(',', QString::SkipEmptyParts); +#endif QList out; for (int i = 0; i < split.size(); ++i) { diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index e5113981c..8136502b4 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -39,8 +39,14 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey)); ui->instNameTextBox->setText(original->name()); ui->instNameTextBox->setFocus(); +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + auto groupList = APPLICATION->instances()->getGroups(); + QSet groups(groupList.begin(), groupList.end()); + groupList = QStringList(groups.values()); +#else auto groups = APPLICATION->instances()->getGroups().toSet(); auto groupList = QStringList(groups.toList()); +#endif groupList.sort(Qt::CaseInsensitive); groupList.removeOne(""); groupList.push_front(""); diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp index 8631edf63..9f32dd8e4 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.cpp +++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp @@ -488,7 +488,11 @@ void ExportInstanceDialog::loadPackIgnore() } auto data = ignoreFile.readAll(); auto string = QString::fromUtf8(data); +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + proxyModel->setBlockedPaths(string.split('\n', Qt::SkipEmptyParts)); +#else proxyModel->setBlockedPaths(string.split('\n', QString::SkipEmptyParts)); +#endif } void ExportInstanceDialog::savePackIgnore() diff --git a/launcher/ui/dialogs/NewComponentDialog.cpp b/launcher/ui/dialogs/NewComponentDialog.cpp index 1bbafb0ce..cd043e1ba 100644 --- a/launcher/ui/dialogs/NewComponentDialog.cpp +++ b/launcher/ui/dialogs/NewComponentDialog.cpp @@ -46,7 +46,6 @@ NewComponentDialog::NewComponentDialog(const QString & initialName, const QStrin connect(ui->nameTextBox, &QLineEdit::textChanged, this, &NewComponentDialog::updateDialogState); connect(ui->uidTextBox, &QLineEdit::textChanged, this, &NewComponentDialog::updateDialogState); - auto groups = APPLICATION->instances()->getGroups().toSet(); ui->nameTextBox->setFocus(); originalPlaceholderText = ui->uidTextBox->placeholderText(); diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index 05ea091de..c7bcfe6e9 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -54,8 +54,14 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString InstIconKey = "default"; ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey)); +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + auto groupList = APPLICATION->instances()->getGroups(); + auto groups = QSet(groupList.begin(), groupList.end()); + groupList = groups.values(); +#else auto groups = APPLICATION->instances()->getGroups().toSet(); auto groupList = QStringList(groups.toList()); +#endif groupList.sort(Qt::CaseInsensitive); groupList.removeOne(""); groupList.push_front(initialGroup); diff --git a/launcher/ui/instanceview/InstanceDelegate.cpp b/launcher/ui/instanceview/InstanceDelegate.cpp index b446e39db..037b7b5e5 100644 --- a/launcher/ui/instanceview/InstanceDelegate.cpp +++ b/launcher/ui/instanceview/InstanceDelegate.cpp @@ -24,7 +24,7 @@ #include "InstanceView.h" #include "BaseInstance.h" #include "InstanceList.h" -#include +#include #include // Origin: Qt @@ -61,7 +61,7 @@ void drawSelectionRect(QPainter *painter, const QStyleOptionViewItem &option, painter->fillRect(rect, option.palette.brush(QPalette::Highlight)); else { - QColor backgroundColor = option.palette.color(QPalette::Background); + QColor backgroundColor = option.palette.color(QPalette::Window); backgroundColor.setAlpha(160); painter->fillRect(rect, QBrush(backgroundColor)); } @@ -142,7 +142,7 @@ void drawBadges(QPainter *painter, const QStyleOptionViewItem &option, BaseInsta return; } // FIXME: inject this. - auto icon = XdgIcon::fromTheme(it.next()); + auto icon = QIcon::fromTheme(it.next()); // opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state); const QPixmap pixmap; // itemSide diff --git a/launcher/ui/pages/instance/LogPage.cpp b/launcher/ui/pages/instance/LogPage.cpp index 8fefb44c7..3d9fb025f 100644 --- a/launcher/ui/pages/instance/LogPage.cpp +++ b/launcher/ui/pages/instance/LogPage.cpp @@ -63,7 +63,7 @@ public: { case Qt::FontRole: return m_font; - case Qt::TextColorRole: + case Qt::ForegroundRole: { MessageLevel::Enum level = (MessageLevel::Enum) QIdentityProxyModel::data(index, LogModel::LevelRole).toInt(); return m_colors->getFront(level); diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index 51163e281..75eb5a3fa 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -270,7 +270,7 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget *parent) ui->listView->setViewMode(QListView::IconMode); ui->listView->setResizeMode(QListView::Adjust); ui->listView->installEventFilter(this); - ui->listView->setEditTriggers(0); + ui->listView->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->listView->setItemDelegate(new CenteredEditingDelegate(this)); ui->listView->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->listView, &QListView::customContextMenuRequested, this, &ScreenshotsPage::ShowContextMenu); diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 3971d422e..b9583d867 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -288,7 +288,11 @@ public: return false; } beginMoveRows(QModelIndex(), row, row, QModelIndex(), row - 1); +#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) + m_servers.swapItemsAt(row-1, row); +#else m_servers.swap(row-1, row); +#endif endMoveRows(); scheduleSave(); return true; @@ -306,7 +310,11 @@ public: return false; } beginMoveRows(QModelIndex(), row, row, QModelIndex(), row + 2); +#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) + m_servers.swapItemsAt(row+1, row); +#else m_servers.swap(row+1, row); +#endif endMoveRows(); scheduleSave(); return true; diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp index c13b15549..2d135e597 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp @@ -168,7 +168,7 @@ QVariant ListModel::data(const QModelIndex &index, int role) const ((ListModel *)this)->requestLogo(pack.logo); return icon; } - else if(role == Qt::TextColorRole) + else if(role == Qt::ForegroundRole) { if(pack.broken) { diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp index 7667d1692..0b180bf36 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp @@ -151,7 +151,7 @@ void Page::openedImpl() ftbFetchTask->fetch(); ftbPrivatePacks->load(); - ftbFetchTask->fetchPrivate(ftbPrivatePacks->getCurrentPackCodes().toList()); + ftbFetchTask->fetchPrivate(ftbPrivatePacks->getCurrentPackCodes().values()); initialized = true; } suggestCurrent(); diff --git a/launcher/ui/widgets/LabeledToolButton.cpp b/launcher/ui/widgets/LabeledToolButton.cpp index ab2d3278a..3866b43fc 100644 --- a/launcher/ui/widgets/LabeledToolButton.cpp +++ b/launcher/ui/widgets/LabeledToolButton.cpp @@ -80,9 +80,7 @@ QSize LabeledToolButton::sizeHint() const if (popupMode() == MenuButtonPopup) w += style()->pixelMetric(QStyle::PM_MenuButtonIndicator, &opt, this); - QSize rawSize = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(w, h), this); - QSize sizeHint = rawSize.expandedTo(QApplication::globalStrut()); - return sizeHint; + return style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(w, h), this); } diff --git a/launcher/ui/widgets/LogView.cpp b/launcher/ui/widgets/LogView.cpp index 26a2a527e..3bb5c69af 100644 --- a/launcher/ui/widgets/LogView.cpp +++ b/launcher/ui/widgets/LogView.cpp @@ -102,7 +102,7 @@ void LogView::rowsInserted(const QModelIndex& parent, int first, int last) { format.setFont(font.value()); } - auto fg = m_model->data(idx, Qt::TextColorRole); + auto fg = m_model->data(idx, Qt::ForegroundRole); if(fg.isValid()) { format.setForeground(fg.value()); diff --git a/launcher/ui/widgets/PageContainer.cpp b/launcher/ui/widgets/PageContainer.cpp index 2af7d731c..ed8df4604 100644 --- a/launcher/ui/widgets/PageContainer.cpp +++ b/launcher/ui/widgets/PageContainer.cpp @@ -171,7 +171,7 @@ void PageContainer::createUI() headerHLayout->addSpacerItem(new QSpacerItem(rightMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); headerHLayout->setContentsMargins(0, 6, 0, 0); - m_pageStack->setMargin(0); + m_pageStack->setContentsMargins(0, 0, 0, 0); m_pageStack->addWidget(new QWidget(this)); m_layout = new QGridLayout; diff --git a/launcher/ui/widgets/VersionListView.cpp b/launcher/ui/widgets/VersionListView.cpp index aba0b1a10..ec5d57b00 100644 --- a/launcher/ui/widgets/VersionListView.cpp +++ b/launcher/ui/widgets/VersionListView.cpp @@ -136,7 +136,7 @@ void VersionListView::paintInfoLabel(QPaintEvent *event) const auto innerBounds = bounds; innerBounds.adjust(10, 10, -10, -10); - QColor background = QApplication::palette().color(QPalette::Foreground); + QColor background = QApplication::palette().color(QPalette::WindowText); QColor foreground = QApplication::palette().color(QPalette::Base); foreground.setAlpha(190); painter.setFont(font); diff --git a/libraries/LocalPeer/src/LocalPeer.cpp b/libraries/LocalPeer/src/LocalPeer.cpp index 2c996ae78..3c3d8b4ce 100644 --- a/libraries/LocalPeer/src/LocalPeer.cpp +++ b/libraries/LocalPeer/src/LocalPeer.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include "LockedFile.h" #if defined(Q_OS_WIN) @@ -72,7 +73,7 @@ ApplicationId ApplicationId::fromTraditionalApp() protoId = protoId.toLower(); #endif auto prefix = protoId.section(QLatin1Char('/'), -1); - prefix.remove(QRegExp("[^a-zA-Z]")); + prefix.remove(QRegularExpression("[^a-zA-Z]")); prefix.truncate(6); QByteArray idc = protoId.toUtf8(); quint16 idNum = qChecksum(idc.constData(), idc.size()); diff --git a/libraries/iconfix/CMakeLists.txt b/libraries/iconfix/CMakeLists.txt deleted file mode 100644 index 97a591297..000000000 --- a/libraries/iconfix/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -cmake_minimum_required(VERSION 3.9.4) -project(iconfix) - -find_package(Qt5Core REQUIRED QUIET) -find_package(Qt5Widgets REQUIRED QUIET) - -set(ICONFIX_SOURCES -xdgicon.h -xdgicon.cpp -internal/qhexstring_p.h -internal/qiconloader.cpp -internal/qiconloader_p.h -) - -add_library(Launcher_iconfix STATIC ${ICONFIX_SOURCES}) -target_include_directories(Launcher_iconfix PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} "${CMAKE_CURRENT_BINARY_DIR}" ) - -target_link_libraries(Launcher_iconfix Qt5::Core Qt5::Widgets) - -generate_export_header(Launcher_iconfix) diff --git a/libraries/iconfix/internal/qhexstring_p.h b/libraries/iconfix/internal/qhexstring_p.h deleted file mode 100644 index c81904e5f..000000000 --- a/libraries/iconfix/internal/qhexstring_p.h +++ /dev/null @@ -1,100 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the QtGui module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include -#include -#include -#include -#include - -#pragma once - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -// internal helper. Converts an integer value to an unique string token -template struct HexString -{ - inline HexString(const T t) : val(t) - { - } - - inline void write(QChar *&dest) const - { - const ushort hexChars[] = {'0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - const char *c = reinterpret_cast(&val); - for (uint i = 0; i < sizeof(T); ++i) - { - *dest++ = hexChars[*c & 0xf]; - *dest++ = hexChars[(*c & 0xf0) >> 4]; - ++c; - } - } - const T val; -}; - -// specialization to enable fast concatenating of our string tokens to a string -template struct QConcatenable> -{ - typedef HexString type; - enum - { - ExactSize = true - }; - static int size(const HexString &) - { - return sizeof(T) * 2; - } - static inline void appendTo(const HexString &str, QChar *&out) - { - str.write(out); - } - typedef QString ConvertTo; -}; diff --git a/libraries/iconfix/internal/qiconloader.cpp b/libraries/iconfix/internal/qiconloader.cpp deleted file mode 100644 index 0d8466f07..000000000 --- a/libraries/iconfix/internal/qiconloader.cpp +++ /dev/null @@ -1,688 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the QtGui module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL21$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 or version 3 as published by the Free -** Software Foundation and appearing in the file LICENSE.LGPLv21 and -** LICENSE.LGPLv3 included in the packaging of this file. Please review the -** following information to ensure the GNU Lesser General Public License -** requirements will be met: https://www.gnu.org/licenses/lgpl.html and -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ -#include "qiconloader_p.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "qhexstring_p.h" - -namespace QtXdg -{ - -Q_GLOBAL_STATIC(QIconLoader, iconLoaderInstance) - -/* Theme to use in last resort, if the theme does not have the icon, neither the parents */ - -static QString fallbackTheme() -{ - return QString("hicolor"); -} - -QIconLoader::QIconLoader() : m_themeKey(1), m_supportsSvg(false), m_initialized(false) -{ -} - -// We lazily initialize the loader to make static icons -// work. Though we do not officially support this. - -static inline QString systemThemeName() -{ - return QIcon::themeName(); -} - -static inline QStringList systemIconSearchPaths() -{ - auto paths = QIcon::themeSearchPaths(); - paths.push_front(":/icons"); - return paths; -} - -void QIconLoader::ensureInitialized() -{ - if (!m_initialized) - { - m_initialized = true; - - Q_ASSERT(qApp); - - m_systemTheme = QIcon::themeName(); - - if (m_systemTheme.isEmpty()) - m_systemTheme = fallbackTheme(); - m_supportsSvg = true; - } -} - -QIconLoader *QIconLoader::instance() -{ - iconLoaderInstance()->ensureInitialized(); - return iconLoaderInstance(); -} - -// Queries the system theme and invalidates existing -// icons if the theme has changed. -void QIconLoader::updateSystemTheme() -{ - // Only change if this is not explicitly set by the user - if (m_userTheme.isEmpty()) - { - QString theme = systemThemeName(); - if (theme.isEmpty()) - theme = fallbackTheme(); - if (theme != m_systemTheme) - { - m_systemTheme = theme; - invalidateKey(); - } - } -} - -void QIconLoader::setThemeName(const QString &themeName) -{ - m_userTheme = themeName; - invalidateKey(); -} - -void QIconLoader::setThemeSearchPath(const QStringList &searchPaths) -{ - m_iconDirs = searchPaths; - themeList.clear(); - invalidateKey(); -} - -QStringList QIconLoader::themeSearchPaths() const -{ - if (m_iconDirs.isEmpty()) - { - m_iconDirs = systemIconSearchPaths(); - } - return m_iconDirs; -} - -QIconTheme::QIconTheme(const QString &themeName) : m_valid(false) -{ - QFile themeIndex; - - QStringList iconDirs = systemIconSearchPaths(); - for (int i = 0; i < iconDirs.size(); ++i) - { - QDir iconDir(iconDirs[i]); - QString themeDir = iconDir.path() + QLatin1Char('/') + themeName; - themeIndex.setFileName(themeDir + QLatin1String("/index.theme")); - if (themeIndex.exists()) - { - m_contentDir = themeDir; - m_valid = true; - - foreach (QString path, iconDirs) - { - if (QFileInfo(path).isDir()) - m_contentDirs.append(path + QLatin1Char('/') + themeName); - } - - break; - } - } - - // if there is no index file, abscond. - if (!themeIndex.exists()) - return; - - // otherwise continue reading index file - const QSettings indexReader(themeIndex.fileName(), QSettings::IniFormat); - QStringListIterator keyIterator(indexReader.allKeys()); - while (keyIterator.hasNext()) - { - const QString key = keyIterator.next(); - if (!key.endsWith(QLatin1String("/Size"))) - continue; - - // Note the QSettings ini-format does not accept - // slashes in key names, hence we have to cheat - int size = indexReader.value(key).toInt(); - if (!size) - continue; - - QString directoryKey = key.left(key.size() - 5); - QIconDirInfo dirInfo(directoryKey); - dirInfo.size = size; - QString type = - indexReader.value(directoryKey + QLatin1String("/Type")).toString(); - - if (type == QLatin1String("Fixed")) - dirInfo.type = QIconDirInfo::Fixed; - else if (type == QLatin1String("Scalable")) - dirInfo.type = QIconDirInfo::Scalable; - else - dirInfo.type = QIconDirInfo::Threshold; - - dirInfo.threshold = - indexReader.value(directoryKey + QLatin1String("/Threshold"), 2) - .toInt(); - - dirInfo.minSize = - indexReader.value(directoryKey + QLatin1String("/MinSize"), size) - .toInt(); - - dirInfo.maxSize = - indexReader.value(directoryKey + QLatin1String("/MaxSize"), size) - .toInt(); - m_keyList.append(dirInfo); - } - - // Parent themes provide fallbacks for missing icons - m_parents = indexReader.value(QLatin1String("Icon Theme/Inherits")).toStringList(); - m_parents.removeAll(QString()); - - // Ensure a default platform fallback for all themes - if (m_parents.isEmpty()) - { - const QString fallback = fallbackTheme(); - if (!fallback.isEmpty()) - m_parents.append(fallback); - } - - // Ensure that all themes fall back to hicolor - if (!m_parents.contains(QLatin1String("hicolor"))) - m_parents.append(QLatin1String("hicolor")); -} - -QThemeIconEntries QIconLoader::findIconHelper(const QString &themeName, const QString &iconName, - QStringList &visited) const -{ - QThemeIconEntries entries; - Q_ASSERT(!themeName.isEmpty()); - - QPixmap pixmap; - - // Used to protect against potential recursions - visited << themeName; - - QIconTheme theme = themeList.value(themeName); - if (!theme.isValid()) - { - theme = QIconTheme(themeName); - if (!theme.isValid()) - theme = QIconTheme(fallbackTheme()); - - themeList.insert(themeName, theme); - } - - QStringList contentDirs = theme.contentDirs(); - const QVector subDirs = theme.keyList(); - - const QString svgext(QLatin1String(".svg")); - const QString pngext(QLatin1String(".png")); - const QString xpmext(QLatin1String(".xpm")); - - // Add all relevant files - for (int i = 0; i < subDirs.size(); ++i) - { - const QIconDirInfo &dirInfo = subDirs.at(i); - QString subdir = dirInfo.path; - - foreach (QString contentDir, contentDirs) - { - QDir currentDir(contentDir + '/' + subdir); - - if (currentDir.exists(iconName + pngext)) - { - PixmapEntry *iconEntry = new PixmapEntry; - iconEntry->dir = dirInfo; - iconEntry->filename = currentDir.filePath(iconName + pngext); - // Notice we ensure that pixmap entries always come before - // scalable to preserve search order afterwards - entries.prepend(iconEntry); - } - else if (m_supportsSvg && currentDir.exists(iconName + svgext)) - { - ScalableEntry *iconEntry = new ScalableEntry; - iconEntry->dir = dirInfo; - iconEntry->filename = currentDir.filePath(iconName + svgext); - entries.append(iconEntry); - break; - } - else if (currentDir.exists(iconName + xpmext)) - { - PixmapEntry *iconEntry = new PixmapEntry; - iconEntry->dir = dirInfo; - iconEntry->filename = currentDir.filePath(iconName + xpmext); - // Notice we ensure that pixmap entries always come before - // scalable to preserve search order afterwards - entries.append(iconEntry); - break; - } - } - } - - if (entries.isEmpty()) - { - const QStringList parents = theme.parents(); - // Search recursively through inherited themes - for (int i = 0; i < parents.size(); ++i) - { - - const QString parentTheme = parents.at(i).trimmed(); - - if (!visited.contains(parentTheme)) // guard against recursion - entries = findIconHelper(parentTheme, iconName, visited); - - if (!entries.isEmpty()) // success - break; - } - } - -/********************************************************************* -Author: Kaitlin Rupert -Date: Aug 12, 2010 -Description: Make it so that the QIcon loader honors /usr/share/pixmaps - directory. This is a valid directory per the Freedesktop.org - icon theme specification. -Bug: https://bugreports.qt.nokia.com/browse/QTBUG-12874 - *********************************************************************/ -#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - /* Freedesktop standard says to look in /usr/share/pixmaps last */ - if (entries.isEmpty()) - { - const QString pixmaps(QLatin1String("/usr/share/pixmaps")); - - QDir currentDir(pixmaps); - QIconDirInfo dirInfo(pixmaps); - if (currentDir.exists(iconName + pngext)) - { - PixmapEntry *iconEntry = new PixmapEntry; - iconEntry->dir = dirInfo; - iconEntry->filename = currentDir.filePath(iconName + pngext); - // Notice we ensure that pixmap entries always come before - // scalable to preserve search order afterwards - entries.prepend(iconEntry); - } - else if (m_supportsSvg && currentDir.exists(iconName + svgext)) - { - ScalableEntry *iconEntry = new ScalableEntry; - iconEntry->dir = dirInfo; - iconEntry->filename = currentDir.filePath(iconName + svgext); - entries.append(iconEntry); - } - else if (currentDir.exists(iconName + xpmext)) - { - PixmapEntry *iconEntry = new PixmapEntry; - iconEntry->dir = dirInfo; - iconEntry->filename = currentDir.filePath(iconName + xpmext); - // Notice we ensure that pixmap entries always come before - // scalable to preserve search order afterwards - entries.append(iconEntry); - } - } -#endif - - if (entries.isEmpty()) - { - // Search for unthemed icons in main dir of search paths - QStringList themeSearchPaths = QIcon::themeSearchPaths(); - foreach (QString contentDir, themeSearchPaths) - { - QDir currentDir(contentDir); - - if (currentDir.exists(iconName + pngext)) - { - PixmapEntry *iconEntry = new PixmapEntry; - iconEntry->filename = currentDir.filePath(iconName + pngext); - // Notice we ensure that pixmap entries always come before - // scalable to preserve search order afterwards - entries.prepend(iconEntry); - } - else if (m_supportsSvg && currentDir.exists(iconName + svgext)) - { - ScalableEntry *iconEntry = new ScalableEntry; - iconEntry->filename = currentDir.filePath(iconName + svgext); - entries.append(iconEntry); - break; - } - else if (currentDir.exists(iconName + xpmext)) - { - PixmapEntry *iconEntry = new PixmapEntry; - iconEntry->filename = currentDir.filePath(iconName + xpmext); - // Notice we ensure that pixmap entries always come before - // scalable to preserve search order afterwards - entries.append(iconEntry); - break; - } - } - } - return entries; -} - -QThemeIconEntries QIconLoader::loadIcon(const QString &name) const -{ - if (!themeName().isEmpty()) - { - QStringList visited; - return findIconHelper(themeName(), name, visited); - } - - return QThemeIconEntries(); -} - -// -------- Icon Loader Engine -------- // - -QIconLoaderEngineFixed::QIconLoaderEngineFixed(const QString &iconName) - : m_iconName(iconName), m_key(0) -{ -} - -QIconLoaderEngineFixed::~QIconLoaderEngineFixed() -{ - qDeleteAll(m_entries); -} - -QIconLoaderEngineFixed::QIconLoaderEngineFixed(const QIconLoaderEngineFixed &other) - : QIconEngine(other), m_iconName(other.m_iconName), m_key(0) -{ -} - -QIconEngine *QIconLoaderEngineFixed::clone() const -{ - return new QIconLoaderEngineFixed(*this); -} - -bool QIconLoaderEngineFixed::read(QDataStream &in) -{ - in >> m_iconName; - return true; -} - -bool QIconLoaderEngineFixed::write(QDataStream &out) const -{ - out << m_iconName; - return true; -} - -bool QIconLoaderEngineFixed::hasIcon() const -{ - return !(m_entries.isEmpty()); -} - -// Lazily load the icon -void QIconLoaderEngineFixed::ensureLoaded() -{ - if (!(QIconLoader::instance()->themeKey() == m_key)) - { - - qDeleteAll(m_entries); - - m_entries = QIconLoader::instance()->loadIcon(m_iconName); - m_key = QIconLoader::instance()->themeKey(); - } -} - -void QIconLoaderEngineFixed::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, - QIcon::State state) -{ - QSize pixmapSize = rect.size(); -#if defined(Q_WS_MAC) - pixmapSize *= qt_mac_get_scalefactor(); -#endif - painter->drawPixmap(rect, pixmap(pixmapSize, mode, state)); -} - -/* - * This algorithm is defined by the freedesktop spec: - * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html - */ -static bool directoryMatchesSize(const QIconDirInfo &dir, int iconsize) -{ - if (dir.type == QIconDirInfo::Fixed) - { - return dir.size == iconsize; - } - else if (dir.type == QIconDirInfo::Scalable) - { - return dir.size <= dir.maxSize && iconsize >= dir.minSize; - } - else if (dir.type == QIconDirInfo::Threshold) - { - return iconsize >= dir.size - dir.threshold && iconsize <= dir.size + dir.threshold; - } - - Q_ASSERT(1); // Not a valid value - return false; -} - -/* - * This algorithm is defined by the freedesktop spec: - * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html - */ -static int directorySizeDistance(const QIconDirInfo &dir, int iconsize) -{ - if (dir.type == QIconDirInfo::Fixed) - { - return qAbs(dir.size - iconsize); - } - else if (dir.type == QIconDirInfo::Scalable) - { - if (iconsize < dir.minSize) - return dir.minSize - iconsize; - else if (iconsize > dir.maxSize) - return iconsize - dir.maxSize; - else - return 0; - } - else if (dir.type == QIconDirInfo::Threshold) - { - if (iconsize < dir.size - dir.threshold) - return dir.minSize - iconsize; - else if (iconsize > dir.size + dir.threshold) - return iconsize - dir.maxSize; - else - return 0; - } - - Q_ASSERT(1); // Not a valid value - return INT_MAX; -} - -QIconLoaderEngineEntry *QIconLoaderEngineFixed::entryForSize(const QSize &size) -{ - int iconsize = qMin(size.width(), size.height()); - - // Note that m_entries are sorted so that png-files - // come first - - const int numEntries = m_entries.size(); - - // Search for exact matches first - for (int i = 0; i < numEntries; ++i) - { - QIconLoaderEngineEntry *entry = m_entries.at(i); - if (directoryMatchesSize(entry->dir, iconsize)) - { - return entry; - } - } - - // Find the minimum distance icon - int minimalSize = INT_MAX; - QIconLoaderEngineEntry *closestMatch = 0; - for (int i = 0; i < numEntries; ++i) - { - QIconLoaderEngineEntry *entry = m_entries.at(i); - int distance = directorySizeDistance(entry->dir, iconsize); - if (distance < minimalSize) - { - minimalSize = distance; - closestMatch = entry; - } - } - return closestMatch; -} - -/* - * Returns the actual icon size. For scalable svg's this is equivalent - * to the requested size. Otherwise the closest match is returned but - * we can never return a bigger size than the requested size. - * - */ -QSize QIconLoaderEngineFixed::actualSize(const QSize &size, QIcon::Mode mode, - QIcon::State state) -{ - ensureLoaded(); - - QIconLoaderEngineEntry *entry = entryForSize(size); - if (entry) - { - const QIconDirInfo &dir = entry->dir; - if (dir.type == QIconDirInfo::Scalable) - return size; - else - { - int result = qMin(dir.size, qMin(size.width(), size.height())); - return QSize(result, result); - } - } - return QIconEngine::actualSize(size, mode, state); -} - -QPixmap PixmapEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) -{ - Q_UNUSED(state); - - // Ensure that basePixmap is lazily initialized before generating the - // key, otherwise the cache key is not unique - if (basePixmap.isNull()) - basePixmap.load(filename); - - QSize actualSize = basePixmap.size(); - if (!actualSize.isNull() && - (actualSize.width() > size.width() || actualSize.height() > size.height())) - actualSize.scale(size, Qt::KeepAspectRatio); - - QString key = QLatin1String("$qt_theme_") % HexString(basePixmap.cacheKey()) % - HexString(mode) % - HexString(QGuiApplication::palette().cacheKey()) % - HexString(actualSize.width()) % HexString(actualSize.height()); - - QPixmap cachedPixmap; - if (QPixmapCache::find(key, &cachedPixmap)) - { - return cachedPixmap; - } - else - { - if (basePixmap.size() != actualSize) - { - cachedPixmap = basePixmap.scaled(actualSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - } - else - { - cachedPixmap = basePixmap; - } - QPixmapCache::insert(key, cachedPixmap); - } - return cachedPixmap; -} - -QPixmap ScalableEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) -{ - if (svgIcon.isNull()) - { - svgIcon = QIcon(filename); - } - - // Simply reuse svg icon engine - return svgIcon.pixmap(size, mode, state); -} - -QPixmap QIconLoaderEngineFixed::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) -{ - ensureLoaded(); - - QIconLoaderEngineEntry *entry = entryForSize(size); - if (entry) - { - return entry->pixmap(size, mode, state); - } - - return QPixmap(); -} - -QString QIconLoaderEngineFixed::key() const -{ - return QLatin1String("QIconLoaderEngineFixed"); -} - -void QIconLoaderEngineFixed::virtual_hook(int id, void *data) -{ - ensureLoaded(); - - switch (id) - { - case QIconEngine::AvailableSizesHook: - { - QIconEngine::AvailableSizesArgument &arg = - *reinterpret_cast(data); - const int N = m_entries.size(); - QList sizes; - sizes.reserve(N); - - // Gets all sizes from the DirectoryInfo entries - for (int i = 0; i < N; ++i) - { - int size = m_entries.at(i)->dir.size; - sizes.append(QSize(size, size)); - } - arg.sizes.swap(sizes); // commit - } - break; - case QIconEngine::IconNameHook: - { - QString &name = *reinterpret_cast(data); - name = m_iconName; - } - break; - default: - QIconEngine::virtual_hook(id, data); - } -} - -} // QtXdg diff --git a/libraries/iconfix/internal/qiconloader_p.h b/libraries/iconfix/internal/qiconloader_p.h deleted file mode 100644 index e45a08d64..000000000 --- a/libraries/iconfix/internal/qiconloader_p.h +++ /dev/null @@ -1,219 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the QtGui module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL21$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 or version 3 as published by the Free -** Software Foundation and appearing in the file LICENSE.LGPLv21 and -** LICENSE.LGPLv3 included in the packaging of this file. Please review the -** following information to ensure the GNU Lesser General Public License -** requirements will be met: https://www.gnu.org/licenses/lgpl.html and -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#pragma once - -#include - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include -#include -#include -#include -#include -#include - - -namespace QtXdg -{ - -class QIconLoader; - -struct QIconDirInfo -{ - enum Type - { - Fixed, - Scalable, - Threshold - }; - QIconDirInfo(const QString &_path = QString()) - : path(_path), size(0), maxSize(0), minSize(0), threshold(0), type(Threshold) - { - } - QString path; - short size; - short maxSize; - short minSize; - short threshold; - Type type : 4; -}; - -class QIconLoaderEngineEntry -{ -public: - virtual ~QIconLoaderEngineEntry() - { - } - virtual QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) = 0; - QString filename; - QIconDirInfo dir; - static int count; -}; - -struct ScalableEntry : public QIconLoaderEngineEntry -{ - QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) Q_DECL_OVERRIDE; - QIcon svgIcon; -}; - -struct PixmapEntry : public QIconLoaderEngineEntry -{ - QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) Q_DECL_OVERRIDE; - QPixmap basePixmap; -}; - -typedef QList QThemeIconEntries; - -// class QIconLoaderEngine : public QIconEngine -class QIconLoaderEngineFixed : public QIconEngine -{ -public: - QIconLoaderEngineFixed(const QString &iconName = QString()); - ~QIconLoaderEngineFixed(); - - void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state); - QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state); - QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state); - QIconEngine *clone() const; - bool read(QDataStream &in); - bool write(QDataStream &out) const; - -private: - QString key() const; - bool hasIcon() const; - void ensureLoaded(); - void virtual_hook(int id, void *data); - QIconLoaderEngineEntry *entryForSize(const QSize &size); - QIconLoaderEngineFixed(const QIconLoaderEngineFixed &other); - QThemeIconEntries m_entries; - QString m_iconName; - uint m_key; - - friend class QIconLoader; -}; - -class QIconTheme -{ -public: - QIconTheme(const QString &name); - QIconTheme() : m_valid(false) - { - } - QStringList parents() - { - return m_parents; - } - QVector keyList() - { - return m_keyList; - } - QString contentDir() - { - return m_contentDir; - } - QStringList contentDirs() - { - return m_contentDirs; - } - bool isValid() - { - return m_valid; - } - -private: - QString m_contentDir; - QStringList m_contentDirs; - QVector m_keyList; - QStringList m_parents; - bool m_valid; -}; - -class QIconLoader -{ -public: - QIconLoader(); - QThemeIconEntries loadIcon(const QString &iconName) const; - uint themeKey() const - { - return m_themeKey; - } - - QString themeName() const - { - return m_userTheme.isEmpty() ? m_systemTheme : m_userTheme; - } - void setThemeName(const QString &themeName); - QIconTheme theme() - { - return themeList.value(themeName()); - } - void setThemeSearchPath(const QStringList &searchPaths); - QStringList themeSearchPaths() const; - QIconDirInfo dirInfo(int dirindex); - static QIconLoader *instance(); - void updateSystemTheme(); - void invalidateKey() - { - m_themeKey++; - } - void ensureInitialized(); - -private: - QThemeIconEntries findIconHelper(const QString &themeName, const QString &iconName, - QStringList &visited) const; - uint m_themeKey; - bool m_supportsSvg; - bool m_initialized; - - mutable QString m_userTheme; - mutable QString m_systemTheme; - mutable QStringList m_iconDirs; - mutable QHash themeList; -}; - -} // QtXdg - -// Note: class template specialization of 'QTypeInfo' must occur at -// global scope -Q_DECLARE_TYPEINFO(QtXdg::QIconDirInfo, Q_MOVABLE_TYPE); diff --git a/libraries/iconfix/xdgicon.cpp b/libraries/iconfix/xdgicon.cpp deleted file mode 100644 index 36fb7d42b..000000000 --- a/libraries/iconfix/xdgicon.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/* BEGIN_COMMON_COPYRIGHT_HEADER - * (c)LGPL2+ - * - * Razor - a lightweight, Qt based, desktop toolset - * http://razor-qt.org - * - * Copyright: 2010-2011 Razor team - * Authors: - * Alexander Sokoloff - * - * This program or library is free software; you can redistribute it - * and/or modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - - * You should have received a copy of the GNU Lesser General - * Public License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA - * - * END_COMMON_COPYRIGHT_HEADER */ - -#include "xdgicon.h" - -#include -#include -#include -#include -#include -#include -#include "internal/qiconloader_p.h" -#include - -/************************************************ - - ************************************************/ -static void qt_cleanup_icon_cache(); -typedef QCache IconCache; - -namespace -{ -struct QtIconCache : public IconCache -{ - QtIconCache() - { - qAddPostRoutine(qt_cleanup_icon_cache); - } -}; -} -Q_GLOBAL_STATIC(IconCache, qtIconCache) - -static void qt_cleanup_icon_cache() -{ - qtIconCache()->clear(); -} - -/************************************************ - - ************************************************/ -XdgIcon::XdgIcon() -{ -} - -/************************************************ - - ************************************************/ -XdgIcon::~XdgIcon() -{ -} - -/************************************************ - Returns the name of the current icon theme. - ************************************************/ -QString XdgIcon::themeName() -{ - return QIcon::themeName(); -} - -/************************************************ - Sets the current icon theme to name. - ************************************************/ -void XdgIcon::setThemeName(const QString &themeName) -{ - QIcon::setThemeName(themeName); - QtXdg::QIconLoader::instance()->updateSystemTheme(); -} - -/************************************************ - Returns the QIcon corresponding to name in the current icon theme. If no such icon - is found in the current theme fallback is return instead. - ************************************************/ -QIcon XdgIcon::fromTheme(const QString &iconName, const QIcon &fallback) -{ - if (iconName.isEmpty()) - return fallback; - - bool isAbsolute = (iconName[0] == '/'); - - QString name = QFileInfo(iconName).fileName(); - if (name.endsWith(".png", Qt::CaseInsensitive) || - name.endsWith(".svg", Qt::CaseInsensitive) || - name.endsWith(".xpm", Qt::CaseInsensitive)) - { - name.truncate(name.length() - 4); - } - - QIcon icon; - - if (qtIconCache()->contains(name)) - { - icon = *qtIconCache()->object(name); - } - else - { - QIcon *cachedIcon; - if (!isAbsolute) - cachedIcon = new QIcon(new QtXdg::QIconLoaderEngineFixed(name)); - else - cachedIcon = new QIcon(iconName); - qtIconCache()->insert(name, cachedIcon); - icon = *cachedIcon; - } - - // Note the qapp check is to allow lazy loading of static icons - // Supporting fallbacks will not work for this case. - if (qApp && !isAbsolute && icon.availableSizes().isEmpty()) - { - return fallback; - } - return icon; -} - -/************************************************ - Returns the QIcon corresponding to names in the current icon theme. If no such icon - is found in the current theme fallback is return instead. - ************************************************/ -QIcon XdgIcon::fromTheme(const QStringList &iconNames, const QIcon &fallback) -{ - foreach (QString iconName, iconNames) - { - QIcon icon = fromTheme(iconName); - if (!icon.isNull()) - return icon; - } - - return fallback; -} diff --git a/libraries/iconfix/xdgicon.h b/libraries/iconfix/xdgicon.h deleted file mode 100644 index d37eb7187..000000000 --- a/libraries/iconfix/xdgicon.h +++ /dev/null @@ -1,48 +0,0 @@ -/* BEGIN_COMMON_COPYRIGHT_HEADER - * (c)LGPL2+ - * - * Razor - a lightweight, Qt based, desktop toolset - * http://razor-qt.org - * - * Copyright: 2010-2011 Razor team - * Authors: - * Alexander Sokoloff - * - * This program or library is free software; you can redistribute it - * and/or modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - - * You should have received a copy of the GNU Lesser General - * Public License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA - * - * END_COMMON_COPYRIGHT_HEADER */ - -#pragma once - -#include -#include -#include - -#include "launcher_iconfix_export.h" - -class LAUNCHER_ICONFIX_EXPORT XdgIcon -{ -public: - static QIcon fromTheme(const QString &iconName, const QIcon &fallback = QIcon()); - static QIcon fromTheme(const QStringList &iconNames, const QIcon &fallback = QIcon()); - - static QString themeName(); - static void setThemeName(const QString &themeName); - -protected: - explicit XdgIcon(); - virtual ~XdgIcon(); -}; diff --git a/libraries/systeminfo/src/distroutils.cpp b/libraries/systeminfo/src/distroutils.cpp index fb9ae25d1..05e1bb8cd 100644 --- a/libraries/systeminfo/src/distroutils.cpp +++ b/libraries/systeminfo/src/distroutils.cpp @@ -36,6 +36,7 @@ SOFTWARE. #include #include #include +#include #include @@ -88,7 +89,9 @@ bool Sys::main_lsb_info(Sys::LsbInfo & out) { int status=0; QProcess lsbProcess; - lsbProcess.start("lsb_release -a"); + QStringList arguments; + arguments << "-a"; + lsbProcess.start("lsb_release", arguments); lsbProcess.waitForFinished(); status = lsbProcess.exitStatus(); QString output = lsbProcess.readAllStandardOutput(); @@ -170,7 +173,11 @@ void Sys::lsb_postprocess(Sys::LsbInfo & lsb, Sys::DistributionInfo & out) else { // ubuntu, debian, gentoo, scientific, slackware, ... ? - auto parts = dist.split(QRegExp("\\s+"), QString::SkipEmptyParts); +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + auto parts = dist.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); +#else + auto parts = dist.split(QRegularExpression("\\s+"), QString::SkipEmptyParts); +#endif if(parts.size()) { dist = parts[0]; @@ -209,7 +216,11 @@ QString Sys::_extract_distribution(const QString & x) { return "sles"; } - QStringList list = release.split(QRegExp("\\s+"), QString::SkipEmptyParts); +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + QStringList list = release.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); +#else + QStringList list = release.split(QRegularExpression("\\s+"), QString::SkipEmptyParts); +#endif if(list.size()) { return list[0]; @@ -219,12 +230,16 @@ QString Sys::_extract_distribution(const QString & x) QString Sys::_extract_version(const QString & x) { - QRegExp versionish_string("\\d+(?:\\.\\d+)*$"); - QStringList list = x.split(QRegExp("\\s+"), QString::SkipEmptyParts); + QRegularExpression versionish_string(QRegularExpression::anchoredPattern("\\d+(?:\\.\\d+)*$")); +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + QStringList list = x.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); +#else + QStringList list = x.split(QRegularExpression("\\s+"), QString::SkipEmptyParts); +#endif for(int i = list.size() - 1; i >= 0; --i) { QString chunk = list[i]; - if(versionish_string.exactMatch(chunk)) + if(versionish_string.match(chunk).hasMatch()) { return chunk; } From ff2cd50bfaeaab89ab830f1223c1e3649642dfa3 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 2 May 2022 19:48:37 +0200 Subject: [PATCH 293/308] refactor: replace QRegExp with QRegularExpression Signed-off-by: Sefa Eyeoglu --- launcher/BaseInstance.cpp | 3 ++- launcher/CMakeLists.txt | 1 + launcher/InstanceImportTask.cpp | 2 +- launcher/JavaCommon.cpp | 5 +++-- launcher/java/JavaInstallList.cpp | 1 - launcher/minecraft/GradleSpecifier.h | 20 ++++++++++--------- launcher/minecraft/MinecraftInstance.cpp | 20 +++++++++---------- launcher/minecraft/VersionFile.cpp | 11 ---------- launcher/minecraft/auth/AccountData.cpp | 3 ++- launcher/minecraft/auth/MinecraftAccount.cpp | 10 +++++----- .../atlauncher/ATLPackInstallTask.cpp | 2 +- launcher/ui/dialogs/ProfileSetupDialog.cpp | 6 +++--- launcher/ui/dialogs/SkinUploadDialog.cpp | 4 ++-- launcher/ui/dialogs/UpdateDialog.cpp | 2 +- .../pages/instance/ExternalResourcesPage.cpp | 6 +++--- .../ui/pages/instance/ScreenshotsPage.cpp | 3 ++- launcher/ui/widgets/PageContainer.cpp | 2 +- 17 files changed, 48 insertions(+), 53 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 0efbdddcc..5a84a931f 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include "settings/INISettingsObject.h" #include "settings/Setting.h" @@ -335,7 +336,7 @@ QString BaseInstance::name() const QString BaseInstance::windowTitle() const { - return BuildConfig.LAUNCHER_NAME + ": " + name().replace(QRegExp("[ \n\r\t]+"), " "); + return BuildConfig.LAUNCHER_NAME + ": " + name().replace(QRegularExpression("\\s+"), " "); } // FIXME: why is this here? move it to MinecraftInstance!!! diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index b90f8cd5b..66247038c 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -978,6 +978,7 @@ target_link_libraries(Launcher_logic Qt5::Network Qt5::Concurrent Qt5::Gui + Qt5::Widgets ) target_link_libraries(Launcher_logic QuaZip::QuaZip diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index dad2c1ad4..14e1cd475 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -325,7 +325,7 @@ void InstanceImportTask::processFlame() // Hack to correct some 'special sauce'... if(mcVersion.endsWith('.')) { - mcVersion.remove(QRegExp("[.]+$")); + mcVersion.remove(QRegularExpression("[.]+$")); logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack.")); } auto components = instance.getPackProfile(); diff --git a/launcher/JavaCommon.cpp b/launcher/JavaCommon.cpp index ae6cd247c..6874f6b0c 100644 --- a/launcher/JavaCommon.cpp +++ b/launcher/JavaCommon.cpp @@ -2,10 +2,11 @@ #include "java/JavaUtils.h" #include "ui/dialogs/CustomMessageBox.h" #include +#include bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget *parent) { - if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(QRegExp("-Xm[sx]")) + if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(QRegularExpression("-Xm[sx]")) || jvmargs.contains("-XX-MaxHeapSize") || jvmargs.contains("-XX:InitialHeapSize")) { auto warnStr = QObject::tr( @@ -19,7 +20,7 @@ bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget *parent) return false; } // block lunacy with passing required version to the JVM - if (jvmargs.contains(QRegExp("-version:.*"))) { + if (jvmargs.contains(QRegularExpression("-version:.*"))) { auto warnStr = QObject::tr( "You tried to pass required Java version argument to the JVM (using \"-version:xxx\"). This is not safe and will not be allowed.\n" "This message will be displayed until you remove this from the JVM arguments."); diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp index c32d89e11..dd8b673c9 100644 --- a/launcher/java/JavaInstallList.cpp +++ b/launcher/java/JavaInstallList.cpp @@ -15,7 +15,6 @@ #include #include -#include #include diff --git a/launcher/minecraft/GradleSpecifier.h b/launcher/minecraft/GradleSpecifier.h index d9bb02079..fbf022af2 100644 --- a/launcher/minecraft/GradleSpecifier.h +++ b/launcher/minecraft/GradleSpecifier.h @@ -2,6 +2,7 @@ #include #include +#include #include "DefaultVariable.h" struct GradleSpecifier @@ -25,20 +26,21 @@ struct GradleSpecifier 4 "jdk15" 5 "jar" */ - QRegExp matcher("([^:@]+):([^:@]+):([^:@]+)" "(?::([^:@]+))?" "(?:@([^:@]+))?"); - m_valid = matcher.exactMatch(value); + QRegularExpression matcher(QRegularExpression::anchoredPattern("([^:@]+):([^:@]+):([^:@]+)" "(?::([^:@]+))?" "(?:@([^:@]+))?")); + QRegularExpressionMatch match = matcher.match(value); + m_valid = match.hasMatch(); if(!m_valid) { m_invalidValue = value; return *this; } - auto elements = matcher.capturedTexts(); - m_groupId = elements[1]; - m_artifactId = elements[2]; - m_version = elements[3]; - m_classifier = elements[4]; - if(!elements[5].isEmpty()) + auto elements = match.captured(); + m_groupId = match.captured(1); + m_artifactId = match.captured(2); + m_version = match.captured(3); + m_classifier = match.captured(4); + if(match.lastCapturedIndex() >= 5) { - m_extension = elements[5]; + m_extension = match.captured(5); } return *this; } diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 445a1bf09..abc022b6c 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -473,25 +473,25 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment() static QString replaceTokensIn(QString text, QMap with) { + // TODO: does this still work?? QString result; - QRegExp token_regexp("\\$\\{(.+)\\}"); - token_regexp.setMinimal(true); + QRegularExpression token_regexp("\\$\\{(.+)\\}", QRegularExpression::InvertedGreedinessOption); QStringList list; - int tail = 0; - int head = 0; - while ((head = token_regexp.indexIn(text, head)) != -1) + QRegularExpressionMatchIterator i = token_regexp.globalMatch(text); + int lastCapturedEnd = 0; + while (i.hasNext()) { - result.append(text.mid(tail, head - tail)); - QString key = token_regexp.cap(1); + QRegularExpressionMatch match = i.next(); + result.append(text.mid(lastCapturedEnd, match.capturedStart())); + QString key = match.captured(1); auto iter = with.find(key); if (iter != with.end()) { result.append(*iter); } - head += token_regexp.matchedLength(); - tail = head; + lastCapturedEnd = match.capturedEnd(); } - result.append(text.mid(tail)); + result.append(text.mid(lastCapturedEnd)); return result; } diff --git a/launcher/minecraft/VersionFile.cpp b/launcher/minecraft/VersionFile.cpp index f242fbe7b..a9a0f7f4b 100644 --- a/launcher/minecraft/VersionFile.cpp +++ b/launcher/minecraft/VersionFile.cpp @@ -89,14 +89,3 @@ void VersionFile::applyTo(LaunchProfile *profile) } profile->applyProblemSeverity(getProblemSeverity()); } - -/* - auto theirVersion = profile->getMinecraftVersion(); - if (!theirVersion.isNull() && !dependsOnMinecraftVersion.isNull()) - { - if (QRegExp(dependsOnMinecraftVersion, Qt::CaseInsensitive, QRegExp::Wildcard).indexIn(theirVersion) == -1) - { - throw MinecraftVersionMismatch(uid, dependsOnMinecraftVersion, theirVersion); - } - } -*/ diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp index 3c7b193c3..44f7e2563 100644 --- a/launcher/minecraft/auth/AccountData.cpp +++ b/launcher/minecraft/auth/AccountData.cpp @@ -39,6 +39,7 @@ #include #include #include +#include namespace { void tokenToJSONV3(QJsonObject &parent, Katabasis::Token t, const char * tokenName) { @@ -451,7 +452,7 @@ void AccountData::invalidateClientToken() { if(type != AccountType::Mojang) { return; } - yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegExp("[{-}]")); + yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{-}]")); } QString AccountData::profileId() const { diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index 9c8eb70b2..a5c6f542e 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -40,7 +40,7 @@ #include #include #include -#include +#include #include #include @@ -53,7 +53,7 @@ #include "flows/Offline.h" MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) { - data.internalId = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); + data.internalId = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); } @@ -78,7 +78,7 @@ MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString &username MinecraftAccountPtr account = new MinecraftAccount(); account->data.type = AccountType::Mojang; account->data.yggdrasilToken.extra["userName"] = username; - account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); + account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); return account; } @@ -97,10 +97,10 @@ MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username) account->data.yggdrasilToken.validity = Katabasis::Validity::Certain; account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc(); account->data.yggdrasilToken.extra["userName"] = username; - account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); + account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); account->data.minecraftEntitlement.ownsMinecraft = true; account->data.minecraftEntitlement.canPlayMinecraft = true; - account->data.minecraftProfile.id = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); + account->data.minecraftProfile.id = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); account->data.minecraftProfile.name = username; account->data.minecraftProfile.validity = Katabasis::Validity::Certain; return account; diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index b8e0f4b07..73ab0b138 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -754,7 +754,7 @@ bool PackInstallTask::extractMods( QString folderToExtract = ""; if(mod.type == ModType::Extract) { folderToExtract = mod.extractFolder; - folderToExtract.remove(QRegExp("^/")); + folderToExtract.remove(QRegularExpression("^/")); } qDebug() << "Extracting " + mod.file + " to " + extractToDir; diff --git a/launcher/ui/dialogs/ProfileSetupDialog.cpp b/launcher/ui/dialogs/ProfileSetupDialog.cpp index 76b6af498..a53474454 100644 --- a/launcher/ui/dialogs/ProfileSetupDialog.cpp +++ b/launcher/ui/dialogs/ProfileSetupDialog.cpp @@ -18,7 +18,7 @@ #include #include -#include +#include #include #include @@ -39,9 +39,9 @@ ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidg yellowIcon = APPLICATION->getThemedIcon("status-yellow"); badIcon = APPLICATION->getThemedIcon("status-bad"); - QRegExp permittedNames("[a-zA-Z0-9_]{3,16}"); + QRegularExpression permittedNames("[a-zA-Z0-9_]{3,16}"); auto nameEdit = ui->nameEdit; - nameEdit->setValidator(new QRegExpValidator(permittedNames)); + nameEdit->setValidator(new QRegularExpressionValidator(permittedNames)); nameEdit->setClearButtonEnabled(true); validityAction = nameEdit->addAction(yellowIcon, QLineEdit::LeadingPosition); connect(nameEdit, &QLineEdit::textEdited, this, &ProfileSetupDialog::nameEdited); diff --git a/launcher/ui/dialogs/SkinUploadDialog.cpp b/launcher/ui/dialogs/SkinUploadDialog.cpp index 8d137afce..f8715dca6 100644 --- a/launcher/ui/dialogs/SkinUploadDialog.cpp +++ b/launcher/ui/dialogs/SkinUploadDialog.cpp @@ -22,10 +22,10 @@ void SkinUploadDialog::on_buttonBox_accepted() { QString fileName; QString input = ui->skinPathTextBox->text(); - QRegExp urlPrefixMatcher("^([a-z]+)://.+$"); + QRegularExpression urlPrefixMatcher(QRegularExpression::anchoredPattern("^([a-z]+)://.+$")); bool isLocalFile = false; // it has an URL prefix -> it is an URL - if(urlPrefixMatcher.exactMatch(input)) + if(urlPrefixMatcher.match(input).hasMatch()) // TODO: does this work? { QUrl fileURL = input; if(fileURL.isValid()) diff --git a/launcher/ui/dialogs/UpdateDialog.cpp b/launcher/ui/dialogs/UpdateDialog.cpp index ec77d1460..4d2396aee 100644 --- a/launcher/ui/dialogs/UpdateDialog.cpp +++ b/launcher/ui/dialogs/UpdateDialog.cpp @@ -58,7 +58,7 @@ QString reprocessMarkdown(QByteArray markdown) QString output = hoedown.process(markdown); // HACK: easier than customizing hoedown - output.replace(QRegExp("GH-([0-9]+)"), "GH-\\1"); + output.replace(QRegularExpression("GH-([0-9]+)"), "GH-\\1"); qDebug() << output; return output; } diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp index 02eeae3d0..d06f412bc 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -32,13 +32,13 @@ class SortProxy : public QSortFilterProxyModel { const auto& mod = model->at(source_row); - if (mod.name().contains(filterRegExp())) + if (mod.name().contains(filterRegularExpression())) return true; - if (mod.description().contains(filterRegExp())) + if (mod.description().contains(filterRegularExpression())) return true; for (auto& author : mod.authors()) { - if (author.contains(filterRegExp())) { + if (author.contains(filterRegularExpression())) { return true; } } diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index 75eb5a3fa..c97253e4b 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -50,6 +50,7 @@ #include #include #include +#include #include @@ -154,7 +155,7 @@ public: if (role == Qt::DisplayRole || role == Qt::EditRole) { QVariant result = sourceModel()->data(mapToSource(proxyIndex), role); - return result.toString().remove(QRegExp("\\.png$")); + return result.toString().remove(QRegularExpression("\\.png$")); } if (role == Qt::DecorationRole) { diff --git a/launcher/ui/widgets/PageContainer.cpp b/launcher/ui/widgets/PageContainer.cpp index ed8df4604..419ccb66f 100644 --- a/launcher/ui/widgets/PageContainer.cpp +++ b/launcher/ui/widgets/PageContainer.cpp @@ -66,7 +66,7 @@ public: protected: bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { - const QString pattern = filterRegExp().pattern(); + const QString pattern = filterRegularExpression().pattern(); const auto model = static_cast(sourceModel()); const auto page = model->pages().at(sourceRow); if (!page->shouldDisplay()) From e58158c3cd629717a9742fe08da9b09ed39bc198 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 2 May 2022 21:34:09 +0200 Subject: [PATCH 294/308] feat: add Qt 6 support to CMake Signed-off-by: Sefa Eyeoglu --- CMakeLists.txt | 13 ++++ buildconfig/CMakeLists.txt | 2 +- cmake/QMakeQuery.cmake | 6 +- cmake/QtVersionlessBackport.cmake | 97 ++++++++++++++++++++++++++++ launcher/CMakeLists.txt | 45 ++++++------- libraries/LocalPeer/CMakeLists.txt | 9 ++- libraries/classparser/CMakeLists.txt | 11 ++-- libraries/katabasis/CMakeLists.txt | 8 ++- libraries/rainbow/CMakeLists.txt | 9 ++- libraries/systeminfo/CMakeLists.txt | 11 +++- 10 files changed, 172 insertions(+), 39 deletions(-) create mode 100644 cmake/QtVersionlessBackport.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 134d2d126..c49afaa90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -154,6 +154,7 @@ add_custom_target(tcversion echo "\\#\\#teamcity[setParameter name=\\'env.LAUNCH ################################ 3rd Party Libs ################################ # Find the required Qt parts +include(QtVersionlessBackport) if(Launcher_QT_VERSION_MAJOR EQUAL 5) set(QT_VERSION_MAJOR 5) find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml) @@ -165,6 +166,18 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5) set(QUAZIP_QT_MAJOR_VERSION ${QT_VERSION_MAJOR} CACHE STRING "Qt version to use (4, 5 or 6), defaults to ${QT_VERSION_MAJOR}" FORCE) set(FORCE_BUNDLED_QUAZIP 1) endif() +elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) + set(QT_VERSION_MAJOR 6) + find_package(Qt6 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml Core5Compat) + list(APPEND Launcher_QT_LIBS Qt6::Core5Compat) + + if(NOT Launcher_FORCE_BUNDLED_LIBS) + find_package(QuaZip-Qt6 1.3 QUIET) + endif() + if (NOT QuaZip-Qt6_FOUND) + set(QUAZIP_QT_MAJOR_VERSION ${QT_VERSION_MAJOR} CACHE STRING "Qt version to use (4, 5 or 6), defaults to ${QT_VERSION_MAJOR}" FORCE) + set(FORCE_BUNDLED_QUAZIP 1) + endif() else() message(FATAL_ERROR "Qt version ${Launcher_QT_VERSION_MAJOR} is not supported") endif() diff --git a/buildconfig/CMakeLists.txt b/buildconfig/CMakeLists.txt index de4fd3501..cd09bdcfe 100644 --- a/buildconfig/CMakeLists.txt +++ b/buildconfig/CMakeLists.txt @@ -7,5 +7,5 @@ add_library(BuildConfig STATIC ${CMAKE_CURRENT_BINARY_DIR}/BuildConfig.cpp ) -target_link_libraries(BuildConfig Qt5::Core) +target_link_libraries(BuildConfig Qt${QT_VERSION_MAJOR}::Core) target_include_directories(BuildConfig PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") diff --git a/cmake/QMakeQuery.cmake b/cmake/QMakeQuery.cmake index bf0fe9672..b1025d653 100644 --- a/cmake/QMakeQuery.cmake +++ b/cmake/QMakeQuery.cmake @@ -3,7 +3,11 @@ if(__QMAKEQUERY_CMAKE__) endif() set(__QMAKEQUERY_CMAKE__ TRUE) -get_target_property(QMAKE_EXECUTABLE Qt5::qmake LOCATION) +if(QT_VERSION_MAJOR EQUAL 5) + get_target_property(QMAKE_EXECUTABLE Qt5::qmake LOCATION) +elseif(QT_VERSION_MAJOR EQUAL 6) + get_target_property(QMAKE_EXECUTABLE Qt6::qmake LOCATION) +endif() function(QUERY_QMAKE VAR RESULT) exec_program(${QMAKE_EXECUTABLE} ARGS "-query ${VAR}" RETURN_VALUE return_code OUTPUT_VARIABLE output ) diff --git a/cmake/QtVersionlessBackport.cmake b/cmake/QtVersionlessBackport.cmake new file mode 100644 index 000000000..46792db58 --- /dev/null +++ b/cmake/QtVersionlessBackport.cmake @@ -0,0 +1,97 @@ +#============================================================================= +# Copyright 2005-2011 Kitware, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of Kitware, Inc. nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#============================================================================= + +# From Qt5CoreMacros.cmake + +function(qt_generate_moc) + if(QT_VERSION_MAJOR EQUAL 5) + qt5_generate_moc(${ARGV}) + elseif(QT_VERSION_MAJOR EQUAL 6) + qt6_generate_moc(${ARGV}) + endif() +endfunction() + +function(qt_wrap_cpp outfiles) + if(QT_VERSION_MAJOR EQUAL 5) + qt5_wrap_cpp("${outfiles}" ${ARGN}) + elseif(QT_VERSION_MAJOR EQUAL 6) + qt6_wrap_cpp("${outfiles}" ${ARGN}) + endif() + set("${outfiles}" "${${outfiles}}" PARENT_SCOPE) +endfunction() + +function(qt_add_binary_resources) + if(QT_VERSION_MAJOR EQUAL 5) + qt5_add_binary_resources(${ARGV}) + elseif(QT_VERSION_MAJOR EQUAL 6) + qt6_add_binary_resources(${ARGV}) + endif() +endfunction() + +function(qt_add_resources outfiles) + if(QT_VERSION_MAJOR EQUAL 5) + qt5_add_resources("${outfiles}" ${ARGN}) + elseif(QT_VERSION_MAJOR EQUAL 6) + qt6_add_resources("${outfiles}" ${ARGN}) + endif() + set("${outfiles}" "${${outfiles}}" PARENT_SCOPE) +endfunction() + +function(qt_add_big_resources outfiles) + if(QT_VERSION_MAJOR EQUAL 5) + qt5_add_big_resources(${outfiles} ${ARGN}) + elseif(QT_VERSION_MAJOR EQUAL 6) + qt6_add_big_resources(${outfiles} ${ARGN}) + endif() + set("${outfiles}" "${${outfiles}}" PARENT_SCOPE) +endfunction() + +function(qt_import_plugins) + if(QT_VERSION_MAJOR EQUAL 5) + qt5_import_plugins(${ARGV}) + elseif(QT_VERSION_MAJOR EQUAL 6) + qt6_import_plugins(${ARGV}) + endif() +endfunction() + + +# From Qt5WidgetsMacros.cmake + +function(qt_wrap_ui outfiles) + if(QT_VERSION_MAJOR EQUAL 5) + qt5_wrap_ui("${outfiles}" ${ARGN}) + elseif(QT_VERSION_MAJOR EQUAL 6) + qt6_wrap_ui("${outfiles}" ${ARGN}) + endif() + set("${outfiles}" "${${outfiles}}" PARENT_SCOPE) +endfunction() + diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 66247038c..d91bd78ae 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -88,10 +88,10 @@ set(CORE_SOURCES MMCTime.cpp ) -ecm_add_test(FileSystem_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test +ecm_add_test(FileSystem_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME FileSystem) # TODO: needs testdata -ecm_add_test(GZip_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test +ecm_add_test(GZip_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME GZip) set(PATHMATCHER_SOURCES @@ -338,7 +338,7 @@ set(MINECRAFT_SOURCES mojang/PackageManifest.cpp minecraft/Agent.h) -ecm_add_test(minecraft/GradleSpecifier_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test +ecm_add_test(minecraft/GradleSpecifier_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME GradleSpecifier) if(BUILD_TESTING) @@ -347,7 +347,7 @@ if(BUILD_TESTING) ) target_link_libraries(PackageManifest Launcher_logic - Qt5::Test + Qt${QT_VERSION_MAJOR}::Test ) target_include_directories(PackageManifest PRIVATE ../cmake/UnitTest/ @@ -360,18 +360,18 @@ if(BUILD_TESTING) endif() # TODO: needs minecraft/testdata -ecm_add_test(minecraft/MojangVersionFormat_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test +ecm_add_test(minecraft/MojangVersionFormat_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME MojangVersionFormat) -ecm_add_test(minecraft/Library_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test +ecm_add_test(minecraft/Library_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME Library) # FIXME: shares data with FileSystem test # TODO: needs testdata -ecm_add_test(minecraft/mod/ModFolderModel_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test +ecm_add_test(minecraft/mod/ModFolderModel_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME ModFolderModel) -ecm_add_test(minecraft/ParseUtils_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test +ecm_add_test(minecraft/ParseUtils_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME ParseUtils) # the screenshots feature @@ -393,7 +393,7 @@ set(TASKS_SOURCES tasks/SequentialTask.cpp ) -ecm_add_test(tasks/Task_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test +ecm_add_test(tasks/Task_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME Task) set(SETTINGS_SOURCES @@ -412,7 +412,7 @@ set(SETTINGS_SOURCES settings/SettingsObject.h ) -ecm_add_test(settings/INIFile_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test +ecm_add_test(settings/INIFile_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME INIFile) set(JAVA_SOURCES @@ -430,7 +430,7 @@ set(JAVA_SOURCES java/JavaVersion.cpp ) -ecm_add_test(java/JavaVersion_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test +ecm_add_test(java/JavaVersion_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME JavaVersion) set(TRANSLATIONS_SOURCES @@ -524,7 +524,7 @@ set(PACKWIZ_SOURCES ) # TODO: needs modplatform/packwiz/testdata -ecm_add_test(modplatform/packwiz/Packwiz_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test +ecm_add_test(modplatform/packwiz/Packwiz_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME Packwiz) set(TECHNIC_SOURCES @@ -549,7 +549,7 @@ set(ATLAUNCHER_SOURCES modplatform/atlauncher/ATLShareCode.h ) -ecm_add_test(meta/Index_test.cpp LINK_LIBRARIES Launcher_logic Qt5::Test +ecm_add_test(meta/Index_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME Index) ################################ COMPILE ################################ @@ -880,7 +880,7 @@ SET(LAUNCHER_SOURCES ui/instanceview/VisualGroup.h ) -qt5_wrap_ui(LAUNCHER_UI +qt_wrap_ui(LAUNCHER_UI ui/setupwizard/PasteWizardPage.ui ui/pages/global/AccountListPage.ui ui/pages/global/JavaPage.ui @@ -933,7 +933,7 @@ qt5_wrap_ui(LAUNCHER_UI ui/dialogs/ScrollMessageBox.ui ) -qt5_add_resources(LAUNCHER_RESOURCES +qt_add_resources(LAUNCHER_RESOURCES resources/backgrounds/backgrounds.qrc resources/multimc/multimc.qrc resources/pe_dark/pe_dark.qrc @@ -963,7 +963,7 @@ target_link_libraries(Launcher_logic tomlc99 BuildConfig Katabasis - Qt5::Widgets + Qt${QT_VERSION_MAJOR}::Widgets ) if (UNIX AND NOT CYGWIN AND NOT APPLE) @@ -973,12 +973,13 @@ if (UNIX AND NOT CYGWIN AND NOT APPLE) endif() target_link_libraries(Launcher_logic - Qt5::Core - Qt5::Xml - Qt5::Network - Qt5::Concurrent - Qt5::Gui - Qt5::Widgets + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Xml + Qt${QT_VERSION_MAJOR}::Network + Qt${QT_VERSION_MAJOR}::Concurrent + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets + ${Launcher_QT_LIBS} ) target_link_libraries(Launcher_logic QuaZip::QuaZip diff --git a/libraries/LocalPeer/CMakeLists.txt b/libraries/LocalPeer/CMakeLists.txt index 0b4348034..b736cefcb 100644 --- a/libraries/LocalPeer/CMakeLists.txt +++ b/libraries/LocalPeer/CMakeLists.txt @@ -1,7 +1,12 @@ cmake_minimum_required(VERSION 3.9.4) project(LocalPeer) -find_package(Qt5 COMPONENTS Core Network REQUIRED) +if(QT_VERSION_MAJOR EQUAL 5) + find_package(Qt5 COMPONENTS Core Network REQUIRED) +elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) + find_package(Qt6 COMPONENTS Core Network Core5Compat REQUIRED) + list(APPEND LocalPeer_LIBS Qt${QT_VERSION_MAJOR}::Core5Compat) +endif() set(SINGLE_SOURCES src/LocalPeer.cpp @@ -25,4 +30,4 @@ endif() add_library(LocalPeer STATIC ${SINGLE_SOURCES}) target_include_directories(LocalPeer PUBLIC include) -target_link_libraries(LocalPeer Qt5::Core Qt5::Network) +target_link_libraries(LocalPeer Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network ${LocalPeer_LIBS}) diff --git a/libraries/classparser/CMakeLists.txt b/libraries/classparser/CMakeLists.txt index fc510e686..05412c30e 100644 --- a/libraries/classparser/CMakeLists.txt +++ b/libraries/classparser/CMakeLists.txt @@ -10,10 +10,11 @@ if(${BIGENDIAN}) endif(${BIGENDIAN}) # Find Qt -find_package(Qt5Core REQUIRED) - -# Include Qt headers. -include_directories(${Qt5Base_INCLUDE_DIRS}) +if(QT_VERSION_MAJOR EQUAL 5) + find_package(Qt5 COMPONENTS Core REQUIRED) +elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) + find_package(Qt6 COMPONENTS Core REQUIRED) +endif() set(CLASSPARSER_HEADERS # Public headers @@ -38,4 +39,4 @@ add_definitions(-DCLASSPARSER_LIBRARY) add_library(Launcher_classparser STATIC ${CLASSPARSER_SOURCES} ${CLASSPARSER_HEADERS}) target_include_directories(Launcher_classparser PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") -target_link_libraries(Launcher_classparser QuaZip::QuaZip Qt5::Core) +target_link_libraries(Launcher_classparser QuaZip::QuaZip Qt${QT_VERSION_MAJOR}::Core) diff --git a/libraries/katabasis/CMakeLists.txt b/libraries/katabasis/CMakeLists.txt index 77db286a2..f764feb6a 100644 --- a/libraries/katabasis/CMakeLists.txt +++ b/libraries/katabasis/CMakeLists.txt @@ -16,7 +16,11 @@ set(CMAKE_C_STANDARD_REQUIRED true) set(CMAKE_CXX_STANDARD 11) set(CMAKE_C_STANDARD 11) -find_package(Qt5 COMPONENTS Core Network REQUIRED) +if(QT_VERSION_MAJOR EQUAL 5) + find_package(Qt5 COMPONENTS Core Network REQUIRED) +elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) + find_package(Qt6 COMPONENTS Core Network REQUIRED) +endif() set( katabasis_PRIVATE src/DeviceFlow.cpp @@ -35,7 +39,7 @@ set( katabasis_PUBLIC ) add_library( Katabasis STATIC ${katabasis_PRIVATE} ${katabasis_PUBLIC} ) -target_link_libraries(Katabasis Qt5::Core Qt5::Network) +target_link_libraries(Katabasis Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network) # needed for statically linked Katabasis in shared libs on x86_64 set_target_properties(Katabasis diff --git a/libraries/rainbow/CMakeLists.txt b/libraries/rainbow/CMakeLists.txt index 94cc1b497..b6bbe7101 100644 --- a/libraries/rainbow/CMakeLists.txt +++ b/libraries/rainbow/CMakeLists.txt @@ -1,8 +1,11 @@ cmake_minimum_required(VERSION 3.9.4) project(rainbow) -find_package(Qt5Core REQUIRED QUIET) -find_package(Qt5Gui REQUIRED QUIET) +if(QT_VERSION_MAJOR EQUAL 5) + find_package(Qt5 COMPONENTS Core Gui REQUIRED) +elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) + find_package(Qt6 COMPONENTS Core Gui REQUIRED) +endif() set(RAINBOW_SOURCES src/rainbow.cpp @@ -11,4 +14,4 @@ src/rainbow.cpp add_library(Launcher_rainbow STATIC ${RAINBOW_SOURCES}) target_include_directories(Launcher_rainbow PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") -target_link_libraries(Launcher_rainbow Qt5::Core Qt5::Gui) +target_link_libraries(Launcher_rainbow Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui) diff --git a/libraries/systeminfo/CMakeLists.txt b/libraries/systeminfo/CMakeLists.txt index d68904f87..33d246050 100644 --- a/libraries/systeminfo/CMakeLists.txt +++ b/libraries/systeminfo/CMakeLists.txt @@ -1,6 +1,11 @@ project(systeminfo) -find_package(Qt5Core) +if(QT_VERSION_MAJOR EQUAL 5) + find_package(Qt5 COMPONENTS Core REQUIRED) +elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) + find_package(Qt6 COMPONENTS Core Core5Compat REQUIRED) + list(APPEND systeminfo_LIBS Qt${QT_VERSION_MAJOR}::Core5Compat) +endif() set(systeminfo_SOURCES include/sys.h @@ -19,7 +24,7 @@ elseif (UNIX) endif() add_library(systeminfo STATIC ${systeminfo_SOURCES}) -target_link_libraries(systeminfo Qt5::Core Qt5::Gui Qt5::Network) +target_link_libraries(systeminfo Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Network ${systeminfo_LIBS}) target_include_directories(systeminfo PUBLIC include) -ecm_add_test(src/sys_test.cpp LINK_LIBRARIES systeminfo Qt5::Test TEST_NAME sys) +ecm_add_test(src/sys_test.cpp LINK_LIBRARIES systeminfo Qt${QT_VERSION_MAJOR}::Test TEST_NAME sys) From c36342371819a4983b5ac2b928acc6a78b857ed8 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 2 May 2022 21:34:55 +0200 Subject: [PATCH 295/308] refactor: fix deprecation up to Qt 6 Signed-off-by: Sefa Eyeoglu --- launcher/Commandline.cpp | 4 +- launcher/FileSystem.cpp | 4 +- launcher/GZip.cpp | 4 +- launcher/MMCZip.cpp | 2 +- launcher/Version.h | 9 ++++ launcher/java/JavaUtils.cpp | 27 ++++++---- launcher/main.cpp | 2 + launcher/minecraft/World.cpp | 3 +- launcher/minecraft/WorldList.cpp | 4 ++ launcher/minecraft/mod/ModFolderModel.cpp | 6 ++- launcher/modplatform/ModAPI.h | 1 + .../atlauncher/ATLPackInstallTask.cpp | 10 +++- .../modplatform/legacy_ftb/PackFetchTask.cpp | 2 +- .../legacy_ftb/PackInstallTask.cpp | 4 ++ launcher/net/NetJob.cpp | 2 +- launcher/news/NewsChecker.cpp | 2 +- launcher/settings/INIFile.cpp | 8 +-- launcher/ui/dialogs/AboutDialog.cpp | 2 + launcher/ui/dialogs/NewsDialog.cpp | 2 +- launcher/ui/instanceview/InstanceView.cpp | 50 +++++++++++++++---- launcher/ui/instanceview/InstanceView.h | 4 +- launcher/ui/instanceview/VisualGroup.cpp | 9 +++- 22 files changed, 118 insertions(+), 43 deletions(-) diff --git a/launcher/Commandline.cpp b/launcher/Commandline.cpp index 2c0fde64b..8a79d564e 100644 --- a/launcher/Commandline.cpp +++ b/launcher/Commandline.cpp @@ -47,7 +47,7 @@ QStringList splitArgs(QString args) if (cchar == '\\') escape = true; else if (cchar == inquotes) - inquotes = 0; + inquotes = QChar::Null; else current += cchar; // otherwise @@ -480,4 +480,4 @@ void Parser::getPrefix(QString &opt, QString &flag) ParsingError::ParsingError(const QString &what) : std::runtime_error(what.toStdString()) { } -} \ No newline at end of file +} diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 3837d75f5..8e984b2bb 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -346,7 +346,7 @@ bool checkProblemticPathJava(QDir folder) } // Win32 crap -#if defined Q_OS_WIN +#ifdef Q_OS_WIN bool called_coinit = false; @@ -366,7 +366,7 @@ HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args) } } - IShellLink *link; + IShellLinkA *link; hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *)&link); diff --git a/launcher/GZip.cpp b/launcher/GZip.cpp index 0368c32d6..2f91d4254 100644 --- a/launcher/GZip.cpp +++ b/launcher/GZip.cpp @@ -67,7 +67,7 @@ bool GZip::zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes) return true; } - unsigned compLength = std::min(uncompressedBytes.size(), 16); + unsigned compLength = qMin(uncompressedBytes.size(), 16); compressedBytes.clear(); compressedBytes.resize(compLength); @@ -112,4 +112,4 @@ bool GZip::zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes) return false; } return true; -} \ No newline at end of file +} diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index d7ad4428f..f20d6dffe 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -421,7 +421,7 @@ bool MMCZip::collectFileListRecursively(const QString& rootDir, const QString& s continue; } - files->append(e.filePath()); // we want the original paths for MMCZip::compressDirFiles + files->append(e); // we want the original paths for MMCZip::compressDirFiles } return true; } diff --git a/launcher/Version.h b/launcher/Version.h index 9fe12d6da..292e2a180 100644 --- a/launcher/Version.h +++ b/launcher/Version.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include class QUrl; @@ -39,13 +40,21 @@ private: break; } } +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + auto numPart = QStringView{m_fullString}.left(cutoff); +#else auto numPart = m_fullString.leftRef(cutoff); +#endif if(numPart.size()) { numValid = true; m_numPart = numPart.toInt(); } +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + auto stringPart = QStringView{m_fullString}.mid(cutoff); +#else auto stringPart = m_fullString.midRef(cutoff); +#endif if(stringPart.size()) { m_stringPart = stringPart.toString(); diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index 24a1556e4..eeda8bc41 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -195,7 +195,7 @@ QList JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString DWORD subKeyNameSize, numSubKeys, retCode; // Get the number of subkeys - RegQueryInfoKey(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, + RegQueryInfoKeyA(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL); // Iterate until RegEnumKeyEx fails @@ -204,31 +204,36 @@ QList JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString for (DWORD i = 0; i < numSubKeys; i++) { subKeyNameSize = 255; - retCode = RegEnumKeyEx(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, - NULL); + retCode = RegEnumKeyExA(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, + NULL); +#ifdef _UNICODE + QString newSubkeyName = QString::fromWCharArray(subKeyName); +#else + QString newSubkeyName = QString::fromLocal8Bit(subKeyName); +#endif if (retCode == ERROR_SUCCESS) { // Now open the registry key for the version that we just got. - QString newKeyName = keyName + "\\" + subKeyName + subkeySuffix; + QString newKeyName = keyName + "\\" + newSubkeyName + subkeySuffix; HKEY newKey; - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, newKeyName.toStdString().c_str(), 0, - KEY_READ | KEY_WOW64_64KEY, &newKey) == ERROR_SUCCESS) + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, newKeyName.toStdString().c_str(), 0, + KEY_READ | KEY_WOW64_64KEY, &newKey) == ERROR_SUCCESS) { // Read the JavaHome value to find where Java is installed. value = new char[0]; valueSz = 0; - if (RegQueryValueEx(newKey, keyJavaDir.toStdString().c_str(), NULL, NULL, (BYTE *)value, - &valueSz) == ERROR_MORE_DATA) + if (RegQueryValueExA(newKey, keyJavaDir.toStdString().c_str(), NULL, NULL, (BYTE *)value, + &valueSz) == ERROR_MORE_DATA) { value = new char[valueSz]; - RegQueryValueEx(newKey, keyJavaDir.toStdString().c_str(), NULL, NULL, (BYTE *)value, - &valueSz); + RegQueryValueExA(newKey, keyJavaDir.toStdString().c_str(), NULL, NULL, (BYTE *)value, + &valueSz); // Now, we construct the version object and add it to the list. JavaInstallPtr javaVersion(new JavaInstall()); - javaVersion->id = subKeyName; + javaVersion->id = newSubkeyName; javaVersion->arch = archType; javaVersion->path = QDir(FS::PathCombine(value, "bin")).absoluteFilePath("javaw.exe"); diff --git a/launcher/main.cpp b/launcher/main.cpp index 3d25b4ffe..bb09ea6c3 100644 --- a/launcher/main.cpp +++ b/launcher/main.cpp @@ -24,8 +24,10 @@ int main(int argc, char *argv[]) return 42; #endif +#if QT_VERSION <= QT_VERSION_CHECK(6, 0, 0) QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); +#endif // initialize Qt Application app(argc, argv); diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp index dc756e06e..e974953a1 100644 --- a/launcher/minecraft/World.cpp +++ b/launcher/minecraft/World.cpp @@ -321,7 +321,8 @@ bool World::install(const QString &to, const QString &name) if(ok && !name.isEmpty() && m_actualName != name) { - World newWorld(finalPath); + QFileInfo finalPathInfo(finalPath); + World newWorld(finalPathInfo); if(newWorld.isValid()) { newWorld.rename(name); diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index 75d0877e6..dd6c7c0f2 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -301,7 +301,11 @@ public: } protected: +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QVariant retrieveData(const QString &mimetype, QMetaType type) const +#else QVariant retrieveData(const QString &mimetype, QVariant::Type type) const +#endif { QList urls; for(auto &world: m_worlds) diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 0545352ba..5ee08cbff 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -317,7 +317,8 @@ bool ModFolderModel::installMod(const QString &filename) return false; } FS::updateTimestamp(newpath); - installedMod.repath(newpath); + QFileInfo newpathInfo(newpath); + installedMod.repath(newpathInfo); update(); return true; } @@ -335,7 +336,8 @@ bool ModFolderModel::installMod(const QString &filename) qWarning() << "Copy of folder from" << originalPath << "to" << newpath << "has (potentially partially) failed."; return false; } - installedMod.repath(newpath); + QFileInfo newpathInfo(newpath); + installedMod.repath(newpathInfo); update(); return true; } diff --git a/launcher/modplatform/ModAPI.h b/launcher/modplatform/ModAPI.h index 91b760df3..d11ed7ca1 100644 --- a/launcher/modplatform/ModAPI.h +++ b/launcher/modplatform/ModAPI.h @@ -2,6 +2,7 @@ #include #include +#include #include "Version.h" diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 73ab0b138..0ed0ad29c 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -36,7 +36,7 @@ #include "ATLPackInstallTask.h" -#include +#include #include @@ -557,7 +557,11 @@ void PackInstallTask::extractConfigs() return; } +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload::of(MMCZip::extractDir), archivePath, extractDir.absolutePath() + "/minecraft"); +#else m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/minecraft"); +#endif connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, [&]() { downloadMods(); @@ -702,7 +706,11 @@ void PackInstallTask::onModsDownloaded() { jobPtr.reset(); if(!modsToExtract.empty() || !modsToDecomp.empty() || !modsToCopy.empty()) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + m_modExtractFuture = QtConcurrent::run(QThreadPool::globalInstance(), &PackInstallTask::extractMods, this, modsToExtract, modsToDecomp, modsToCopy); +#else m_modExtractFuture = QtConcurrent::run(QThreadPool::globalInstance(), this, &PackInstallTask::extractMods, modsToExtract, modsToDecomp, modsToCopy); +#endif connect(&m_modExtractFutureWatcher, &QFutureWatcher::finished, this, &PackInstallTask::onModsExtracted); connect(&m_modExtractFutureWatcher, &QFutureWatcher::canceled, this, [&]() { diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp index 961fe868c..5bc01ed26 100644 --- a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp @@ -103,7 +103,7 @@ bool PackFetchTask::parseAndAddPacks(QByteArray &data, PackType packType, Modpac if(!doc.setContent(data, false, &errorMsg, &errorLine, &errorCol)) { - auto fullErrMsg = QString("Failed to fetch modpack data: %1 %2:3d!").arg(errorMsg, errorLine, errorCol); + auto fullErrMsg = QString("Failed to fetch modpack data: %1 %2:%3!").arg(errorMsg).arg(errorLine).arg(errorCol); qWarning() << fullErrMsg; data.clear(); return false; diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index c63a9f1e8..1493e8f2a 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -88,7 +88,11 @@ void PackInstallTask::unzip() return; } +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload::of(MMCZip::extractDir), archivePath, extractDir.absolutePath() + "/unzip"); +#else m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip"); +#endif connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, &PackInstallTask::onUnzipFinished); connect(&m_extractFutureWatcher, &QFutureWatcher::canceled, this, &PackInstallTask::onUnzipCanceled); m_extractFutureWatcher.setFuture(m_extractFuture); diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index 349273697..bf73829c4 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -106,7 +106,7 @@ auto NetJob::abort() -> bool m_todo.clear(); // abort active downloads - auto toKill = m_doing.toList(); + auto toKill = m_doing.values(); for (auto index : toKill) { auto part = m_downloads[index]; fullyAborted &= part->abort(); diff --git a/launcher/news/NewsChecker.cpp b/launcher/news/NewsChecker.cpp index 6724950f1..8180b6ff4 100644 --- a/launcher/news/NewsChecker.cpp +++ b/launcher/news/NewsChecker.cpp @@ -61,7 +61,7 @@ void NewsChecker::rssDownloadFinished() // Parse the XML. if (!doc.setContent(newsData, false, &errorMsg, &errorLine, &errorCol)) { - QString fullErrorMsg = QString("Error parsing RSS feed XML. %s at %d:%d.").arg(errorMsg, errorLine, errorCol); + QString fullErrorMsg = QString("Error parsing RSS feed XML. %1 at %2:%3.").arg(errorMsg).arg(errorLine).arg(errorCol); fail(fullErrorMsg); newsData.clear(); return; diff --git a/launcher/settings/INIFile.cpp b/launcher/settings/INIFile.cpp index 6a3c801d6..450ddc3f4 100644 --- a/launcher/settings/INIFile.cpp +++ b/launcher/settings/INIFile.cpp @@ -29,7 +29,7 @@ INIFile::INIFile() QString INIFile::unescape(QString orig) { QString out; - QChar prev = 0; + QChar prev = QChar::Null; for(auto c: orig) { if(prev == '\\') @@ -42,7 +42,7 @@ QString INIFile::unescape(QString orig) out += '#'; else out += c; - prev = 0; + prev = QChar::Null; } else { @@ -52,7 +52,7 @@ QString INIFile::unescape(QString orig) continue; } out += c; - prev = 0; + prev = QChar::Null; } } return out; @@ -117,7 +117,9 @@ bool INIFile::loadFile(QString fileName) bool INIFile::loadFile(QByteArray file) { QTextStream in(file); +#if QT_VERSION <= QT_VERSION_CHECK(6, 0, 0) in.setCodec("UTF-8"); +#endif QStringList lines = in.readAll().split('\n'); for (int i = 0; i < lines.count(); i++) diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp index 8dadb755f..c5367d5b9 100644 --- a/launcher/ui/dialogs/AboutDialog.cpp +++ b/launcher/ui/dialogs/AboutDialog.cpp @@ -64,7 +64,9 @@ QString getCreditsHtml() { QString output; QTextStream stream(&output); +#if QT_VERSION <= QT_VERSION_CHECK(6, 0, 0) stream.setCodec(QTextCodec::codecForName("UTF-8")); +#endif stream << "
\n"; //: %1 is the name of the launcher, determined at build time, e.g. "PolyMC Developers" diff --git a/launcher/ui/dialogs/NewsDialog.cpp b/launcher/ui/dialogs/NewsDialog.cpp index df6204648..d3b216272 100644 --- a/launcher/ui/dialogs/NewsDialog.cpp +++ b/launcher/ui/dialogs/NewsDialog.cpp @@ -16,7 +16,7 @@ NewsDialog::NewsDialog(QList entries, QWidget* parent) : QDialog(p m_article_list_hidden = ui->articleListWidget->isHidden(); auto first_item = ui->articleListWidget->item(0); - ui->articleListWidget->setItemSelected(first_item, true); + first_item->setSelected(true); auto article_entry = m_entries.constFind(first_item->text()).value(); ui->articleTitleLabel->setText(QString("%2").arg(article_entry->link, first_item->text())); diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp index 25aec1abe..41e0ce123 100644 --- a/launcher/ui/instanceview/InstanceView.cpp +++ b/launcher/ui/instanceview/InstanceView.cpp @@ -425,7 +425,12 @@ void InstanceView::mouseReleaseEvent(QMouseEvent *event) { emit clicked(index); } +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QStyleOptionViewItem option; + initViewItemOption(&option); +#else QStyleOptionViewItem option = viewOptions(); +#endif if (m_pressedAlreadySelected) { option.state |= QStyle::State_Selected; @@ -461,7 +466,12 @@ void InstanceView::mouseDoubleClickEvent(QMouseEvent *event) QPersistentModelIndex persistent = index; emit doubleClicked(persistent); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QStyleOptionViewItem option; + initViewItemOption(&option); +#else QStyleOptionViewItem option = viewOptions(); +#endif if ((model()->flags(index) & Qt::ItemIsEnabled) && !style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this)) { emit activated(index); @@ -474,7 +484,12 @@ void InstanceView::paintEvent(QPaintEvent *event) QPainter painter(this->viewport()); - QStyleOptionViewItem option(viewOptions()); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QStyleOptionViewItem option; + initViewItemOption(&option); +#else + QStyleOptionViewItem option = viewOptions(); +#endif option.widget = this; int wpWidth = viewport()->width(); @@ -528,9 +543,9 @@ void InstanceView::paintEvent(QPaintEvent *event) #if 0 if (!m_lastDragPosition.isNull()) { - QPair pair = rowDropPos(m_lastDragPosition); - Group *category = pair.first; - int row = pair.second; + std::pair pair = rowDropPos(m_lastDragPosition); + VisualGroup *category = pair.first; + VisualGroup::HitResults row = pair.second; if (category) { int internalRow = row - category->firstItemIndex; @@ -618,7 +633,7 @@ void InstanceView::dropEvent(QDropEvent *event) { if(event->possibleActions() & Qt::MoveAction) { - QPair dropPos = rowDropPos(event->pos()); + std::pair dropPos = rowDropPos(event->pos()); const VisualGroup *group = dropPos.first; auto hitresult = dropPos.second; @@ -709,10 +724,18 @@ QRect InstanceView::geometryRect(const QModelIndex &index) const int x = pos.first; // int y = pos.second; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QStyleOptionViewItem option; + initViewItemOption(&option); +#else + QStyleOptionViewItem option = viewOptions(); +#endif + QRect out; out.setTop(cat->verticalPosition() + cat->headerHeight() + 5 + cat->rowTopOf(index)); out.setLeft(m_spacing + x * (itemWidth() + m_spacing)); - out.setSize(itemDelegate()->sizeHint(viewOptions(), index)); + out.setSize(itemDelegate()->sizeHint(option, index)); geometryCache.insert(row, new QRect(out)); return out; } @@ -759,7 +782,12 @@ QPixmap InstanceView::renderToPixmap(const QModelIndexList &indices, QRect *r) c QPixmap pixmap(r->size()); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QStyleOptionViewItem option; + initViewItemOption(&option); +#else QStyleOptionViewItem option = viewOptions(); +#endif option.state |= QStyle::State_Selected; for (int j = 0; j < paintPairs.count(); ++j) { @@ -770,16 +798,16 @@ QPixmap InstanceView::renderToPixmap(const QModelIndexList &indices, QRect *r) c return pixmap; } -QList> InstanceView::draggablePaintPairs(const QModelIndexList &indices, QRect *r) const +QList> InstanceView::draggablePaintPairs(const QModelIndexList &indices, QRect *r) const { Q_ASSERT(r); QRect &rect = *r; - QList> ret; + QList> ret; for (int i = 0; i < indices.count(); ++i) { const QModelIndex &index = indices.at(i); const QRect current = geometryRect(index); - ret += qMakePair(current, index); + ret += std::make_pair(current, index); rect |= current; } return ret; @@ -790,11 +818,11 @@ bool InstanceView::isDragEventAccepted(QDropEvent *event) return true; } -QPair InstanceView::rowDropPos(const QPoint &pos) +std::pair InstanceView::rowDropPos(const QPoint &pos) { VisualGroup::HitResults hitresult; auto group = categoryAt(pos + offset(), hitresult); - return qMakePair(group, hitresult); + return std::make_pair(group, hitresult); } QPoint InstanceView::offset() const diff --git a/launcher/ui/instanceview/InstanceView.h b/launcher/ui/instanceview/InstanceView.h index 406362e62..25d8ba0bd 100644 --- a/launcher/ui/instanceview/InstanceView.h +++ b/launcher/ui/instanceview/InstanceView.h @@ -143,11 +143,11 @@ private: /* methods */ int calculateItemsPerRow() const; int verticalScrollToValue(const QModelIndex &index, const QRect &rect, QListView::ScrollHint hint) const; QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; - QList> draggablePaintPairs(const QModelIndexList &indices, QRect *r) const; + QList> draggablePaintPairs(const QModelIndexList &indices, QRect *r) const; bool isDragEventAccepted(QDropEvent *event); - QPair rowDropPos(const QPoint &pos); + std::pair rowDropPos(const QPoint &pos); QPoint offset() const; }; diff --git a/launcher/ui/instanceview/VisualGroup.cpp b/launcher/ui/instanceview/VisualGroup.cpp index 8991fb2d0..1c2dd7fc4 100644 --- a/launcher/ui/instanceview/VisualGroup.cpp +++ b/launcher/ui/instanceview/VisualGroup.cpp @@ -55,7 +55,14 @@ void VisualGroup::update() positionInRow = 0; maxRowHeight = 0; } - auto itemHeight = view->itemDelegate()->sizeHint(view->viewOptions(), item).height(); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QStyleOptionViewItem viewItemOption; + view->initViewItemOption(&viewItemOption); +#else + QStyleOptionViewItem viewItemOption = view->viewOptions(); +#endif + + auto itemHeight = view->itemDelegate()->sizeHint(viewItemOption, item).height(); if(itemHeight > maxRowHeight) { maxRowHeight = itemHeight; From 15c5bbcf222e9baa705ec0dfe5504b9db2d030ae Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 16 May 2022 20:34:07 +0200 Subject: [PATCH 296/308] fix: fix slots for Qt 6 Signed-off-by: Sefa Eyeoglu --- launcher/LoggedProcess.cpp | 4 +++ launcher/java/JavaChecker.cpp | 4 +++ launcher/minecraft/auth/AuthRequest.cpp | 8 ++++++ launcher/minecraft/services/CapeChange.cpp | 4 +++ launcher/minecraft/services/SkinDelete.cpp | 4 +++ launcher/minecraft/services/SkinUpload.cpp | 4 +++ launcher/net/Download.cpp | 4 +++ launcher/net/PasteUpload.cpp | 11 +++++--- launcher/screenshots/ImgurAlbumCreation.cpp | 4 +++ launcher/screenshots/ImgurUpload.cpp | 7 +++-- launcher/ui/InstanceWindow.cpp | 10 +++---- launcher/ui/InstanceWindow.h | 4 +-- launcher/ui/MainWindow.cpp | 31 --------------------- launcher/ui/MainWindow.h | 4 --- launcher/ui/pages/global/ProxyPage.cpp | 16 ++++++----- launcher/ui/pages/global/ProxyPage.h | 8 +++--- launcher/ui/pages/instance/ServersPage.cpp | 4 +-- launcher/ui/pages/instance/ServersPage.h | 2 +- 18 files changed, 71 insertions(+), 62 deletions(-) diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp index 05d2fd746..e71ad1826 100644 --- a/launcher/LoggedProcess.cpp +++ b/launcher/LoggedProcess.cpp @@ -8,7 +8,11 @@ LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent) connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut); connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr); connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(on_exit(int,QProcess::ExitStatus))); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + connect(this, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError))); +#else connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError))); +#endif connect(this, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange); } diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index c38462885..87b7ffacf 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -53,7 +53,11 @@ void JavaChecker::performCheck() qDebug() << "Running java checker: " + m_path + args.join(" ");; connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus))); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + connect(process.get(), SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError))); +#else connect(process.get(), SIGNAL(error(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError))); +#endif connect(process.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(stdoutReady())); connect(process.get(), SIGNAL(readyReadStandardError()), this, SLOT(stderrReady())); connect(&killTimer, SIGNAL(timeout()), SLOT(timeout())); diff --git a/launcher/minecraft/auth/AuthRequest.cpp b/launcher/minecraft/auth/AuthRequest.cpp index feface80f..65b51f099 100644 --- a/launcher/minecraft/auth/AuthRequest.cpp +++ b/launcher/minecraft/auth/AuthRequest.cpp @@ -20,7 +20,11 @@ void AuthRequest::get(const QNetworkRequest &req, int timeout/* = 60*1000*/) { reply_ = APPLICATION->network()->get(request_); status_ = Requesting; timedReplies_.add(new Katabasis::Reply(reply_, timeout)); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError))); +#else connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError))); +#endif connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished())); connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors); } @@ -31,7 +35,11 @@ void AuthRequest::post(const QNetworkRequest &req, const QByteArray &data, int t status_ = Requesting; reply_ = APPLICATION->network()->post(request_, data_); timedReplies_.add(new Katabasis::Reply(reply_, timeout)); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError))); +#else connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError))); +#endif connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished())); connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors); connect(reply_, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onUploadProgress(qint64,qint64))); diff --git a/launcher/minecraft/services/CapeChange.cpp b/launcher/minecraft/services/CapeChange.cpp index e49c166ab..243f7d15f 100644 --- a/launcher/minecraft/services/CapeChange.cpp +++ b/launcher/minecraft/services/CapeChange.cpp @@ -34,7 +34,11 @@ void CapeChange::clearCape() { m_reply = shared_qobject_ptr(rep); connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); +#else connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); +#endif connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); } diff --git a/launcher/minecraft/services/SkinDelete.cpp b/launcher/minecraft/services/SkinDelete.cpp index cce8364e1..6ee6b3192 100644 --- a/launcher/minecraft/services/SkinDelete.cpp +++ b/launcher/minecraft/services/SkinDelete.cpp @@ -19,7 +19,11 @@ void SkinDelete::executeTask() setStatus(tr("Deleting skin")); connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); +#else connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); +#endif connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); } diff --git a/launcher/minecraft/services/SkinUpload.cpp b/launcher/minecraft/services/SkinUpload.cpp index 7c2e83377..616a8810a 100644 --- a/launcher/minecraft/services/SkinUpload.cpp +++ b/launcher/minecraft/services/SkinUpload.cpp @@ -44,7 +44,11 @@ void SkinUpload::executeTask() setStatus(tr("Uploading skin")); connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); +#else connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); +#endif connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); } diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index d93eb0880..6613c336d 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -126,7 +126,11 @@ void Download::executeTask() m_reply.reset(rep); connect(rep, &QNetworkReply::downloadProgress, this, &Download::downloadProgress); connect(rep, &QNetworkReply::finished, this, &Download::downloadFinished); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); +#else connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); +#endif connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors); connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead); } diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index 835e4cd12..76b867437 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -129,10 +129,13 @@ void PasteUpload::executeTask() connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); connect(rep, &QNetworkReply::finished, this, &PasteUpload::downloadFinished); - // This function call would be a lot shorter if we were using the latest Qt - connect(rep, - static_cast(&QNetworkReply::error), - this, &PasteUpload::downloadError); + +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + connect(rep, &QNetworkReply::errorOccurred, this, &PasteUpload::downloadError); +#else + connect(rep, QOverload::of(&QNetworkReply::error), this, &PasteUpload::downloadError); +#endif + m_reply = std::shared_ptr(rep); diff --git a/launcher/screenshots/ImgurAlbumCreation.cpp b/launcher/screenshots/ImgurAlbumCreation.cpp index 04e26ea23..294449b72 100644 --- a/launcher/screenshots/ImgurAlbumCreation.cpp +++ b/launcher/screenshots/ImgurAlbumCreation.cpp @@ -73,7 +73,11 @@ void ImgurAlbumCreation::executeTask() m_reply.reset(rep); connect(rep, &QNetworkReply::uploadProgress, this, &ImgurAlbumCreation::downloadProgress); connect(rep, &QNetworkReply::finished, this, &ImgurAlbumCreation::downloadFinished); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); +#else connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); +#endif } void ImgurAlbumCreation::downloadError(QNetworkReply::NetworkError error) { diff --git a/launcher/screenshots/ImgurUpload.cpp b/launcher/screenshots/ImgurUpload.cpp index 9aeb6fb80..e9fd39e06 100644 --- a/launcher/screenshots/ImgurUpload.cpp +++ b/launcher/screenshots/ImgurUpload.cpp @@ -88,8 +88,11 @@ void ImgurUpload::executeTask() m_reply.reset(rep); connect(rep, &QNetworkReply::uploadProgress, this, &ImgurUpload::downloadProgress); connect(rep, &QNetworkReply::finished, this, &ImgurUpload::downloadFinished); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), - SLOT(downloadError(QNetworkReply::NetworkError))); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); +#else + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); +#endif } void ImgurUpload::downloadError(QNetworkReply::NetworkError error) { diff --git a/launcher/ui/InstanceWindow.cpp b/launcher/ui/InstanceWindow.cpp index ae765c3c2..f8f100bfb 100644 --- a/launcher/ui/InstanceWindow.cpp +++ b/launcher/ui/InstanceWindow.cpp @@ -97,9 +97,9 @@ InstanceWindow::InstanceWindow(InstancePtr instance, QWidget *parent) // set up instance and launch process recognition { auto launchTask = m_instance->getLaunchTask(); - on_InstanceLaunchTask_changed(launchTask); - connect(m_instance.get(), &BaseInstance::launchTaskChanged, this, &InstanceWindow::on_InstanceLaunchTask_changed); - connect(m_instance.get(), &BaseInstance::runningStatusChanged, this, &InstanceWindow::on_RunningState_changed); + instanceLaunchTaskChanged(launchTask); + connect(m_instance.get(), &BaseInstance::launchTaskChanged, this, &InstanceWindow::instanceLaunchTaskChanged); + connect(m_instance.get(), &BaseInstance::runningStatusChanged, this, &InstanceWindow::runningStateChanged); } // set up instance destruction detection @@ -152,12 +152,12 @@ void InstanceWindow::on_btnLaunchMinecraftOffline_clicked() APPLICATION->launch(m_instance, false, nullptr); } -void InstanceWindow::on_InstanceLaunchTask_changed(shared_qobject_ptr proc) +void InstanceWindow::instanceLaunchTaskChanged(shared_qobject_ptr proc) { m_proc = proc; } -void InstanceWindow::on_RunningState_changed(bool running) +void InstanceWindow::runningStateChanged(bool running) { updateLaunchButtons(); m_container->refreshContainer(); diff --git a/launcher/ui/InstanceWindow.h b/launcher/ui/InstanceWindow.h index 1acf684ea..ee010b2f0 100644 --- a/launcher/ui/InstanceWindow.h +++ b/launcher/ui/InstanceWindow.h @@ -55,8 +55,8 @@ slots: void on_btnKillMinecraft_clicked(); void on_btnLaunchMinecraftOffline_clicked(); - void on_InstanceLaunchTask_changed(shared_qobject_ptr proc); - void on_RunningState_changed(bool running); + void instanceLaunchTaskChanged(shared_qobject_ptr proc); + void runningStateChanged(bool running); void on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus); protected: diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 18e06349b..d107635e0 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -235,7 +235,6 @@ public: TranslatedAction actionMods; TranslatedAction actionViewSelectedInstFolder; TranslatedAction actionViewSelectedMCFolder; - TranslatedAction actionViewSelectedModsFolder; TranslatedAction actionDeleteInstance; TranslatedAction actionConfig_Folder; TranslatedAction actionCAT; @@ -709,14 +708,6 @@ public: actionViewSelectedMCFolder->setShortcut(QKeySequence(tr("Ctrl+M"))); all_actions.append(&actionViewSelectedMCFolder); - /* - actionViewSelectedModsFolder = TranslatedAction(MainWindow); - actionViewSelectedModsFolder->setObjectName(QStringLiteral("actionViewSelectedModsFolder")); - actionViewSelectedModsFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Mods Folder")); - actionViewSelectedModsFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the selected instance's mods folder in a file browser.")); - all_actions.append(&actionViewSelectedModsFolder); - */ - actionConfig_Folder = TranslatedAction(MainWindow); actionConfig_Folder->setObjectName(QStringLiteral("actionConfig_Folder")); actionConfig_Folder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Confi&g Folder")); @@ -793,9 +784,6 @@ public: instanceToolBar->addSeparator(); instanceToolBar->addAction(actionViewSelectedMCFolder); - /* - instanceToolBar->addAction(actionViewSelectedModsFolder); - */ instanceToolBar->addAction(actionConfig_Folder); instanceToolBar->addAction(actionViewSelectedInstFolder); @@ -1894,11 +1882,6 @@ void MainWindow::globalSettingsClosed() update(); } -void MainWindow::on_actionInstanceSettings_triggered() -{ - APPLICATION->showInstanceWindow(m_selectedInstance, "settings"); -} - void MainWindow::on_actionEditInstNotes_triggered() { APPLICATION->showInstanceWindow(m_selectedInstance, "notes"); @@ -2021,20 +2004,6 @@ void MainWindow::on_actionViewSelectedMCFolder_triggered() } } -void MainWindow::on_actionViewSelectedModsFolder_triggered() -{ - if (m_selectedInstance) - { - QString str = m_selectedInstance->modsRoot(); - if (!FS::ensureFilePathExists(str)) - { - // TODO: report error - return; - } - DesktopServices::openDirectory(QDir(str).absolutePath()); - } -} - void MainWindow::closeEvent(QCloseEvent *event) { // Save the window state and geometry. diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 0ca8ec7b3..d7930b5ab 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -118,8 +118,6 @@ private slots: void on_actionViewSelectedMCFolder_triggered(); - void on_actionViewSelectedModsFolder_triggered(); - void refreshInstances(); void on_actionViewCentralModsFolder_triggered(); @@ -128,8 +126,6 @@ private slots: void on_actionSettings_triggered(); - void on_actionInstanceSettings_triggered(); - void on_actionManageAccounts_triggered(); void on_actionReportBug_triggered(); diff --git a/launcher/ui/pages/global/ProxyPage.cpp b/launcher/ui/pages/global/ProxyPage.cpp index aefd1e746..f53d74afe 100644 --- a/launcher/ui/pages/global/ProxyPage.cpp +++ b/launcher/ui/pages/global/ProxyPage.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield + * Copyright (c) 2022 Sefa Eyeoglu * * 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 @@ -36,11 +37,11 @@ #include "ProxyPage.h" #include "ui_ProxyPage.h" +#include #include #include "settings/SettingsObject.h" #include "Application.h" -#include "Application.h" ProxyPage::ProxyPage(QWidget *parent) : QWidget(parent), ui(new Ui::ProxyPage) { @@ -49,7 +50,8 @@ ProxyPage::ProxyPage(QWidget *parent) : QWidget(parent), ui(new Ui::ProxyPage) loadSettings(); updateCheckboxStuff(); - connect(ui->proxyGroup, SIGNAL(buttonClicked(int)), SLOT(proxyChanged(int))); + connect(ui->proxyGroup, QOverload::of(&QButtonGroup::buttonClicked), + this, &ProxyPage::proxyGroupChanged); } ProxyPage::~ProxyPage() @@ -65,13 +67,13 @@ bool ProxyPage::apply() void ProxyPage::updateCheckboxStuff() { - ui->proxyAddrBox->setEnabled(!ui->proxyNoneBtn->isChecked() && - !ui->proxyDefaultBtn->isChecked()); - ui->proxyAuthBox->setEnabled(!ui->proxyNoneBtn->isChecked() && - !ui->proxyDefaultBtn->isChecked()); + bool enableEditing = ui->proxyHTTPBtn->isChecked() + || ui->proxySOCKS5Btn->isChecked(); + ui->proxyAddrBox->setEnabled(enableEditing); + ui->proxyAuthBox->setEnabled(enableEditing); } -void ProxyPage::proxyChanged(int) +void ProxyPage::proxyGroupChanged(QAbstractButton *button) { updateCheckboxStuff(); } diff --git a/launcher/ui/pages/global/ProxyPage.h b/launcher/ui/pages/global/ProxyPage.h index e36777740..02c7ec407 100644 --- a/launcher/ui/pages/global/ProxyPage.h +++ b/launcher/ui/pages/global/ProxyPage.h @@ -36,6 +36,7 @@ #pragma once #include +#include #include #include "ui/pages/BasePage.h" @@ -73,15 +74,14 @@ public: bool apply() override; void retranslate() override; +private slots: + void proxyGroupChanged(QAbstractButton *button); + private: void updateCheckboxStuff(); void applySettings(); void loadSettings(); -private -slots: - void proxyChanged(int); - private: Ui::ProxyPage *ui; }; diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index b9583d867..e5cce96c1 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -623,7 +623,7 @@ ServersPage::ServersPage(InstancePtr inst, QWidget* parent) auto selectionModel = ui->serversView->selectionModel(); connect(selectionModel, &QItemSelectionModel::currentChanged, this, &ServersPage::currentChanged); - connect(m_inst.get(), &MinecraftInstance::runningStatusChanged, this, &ServersPage::on_RunningState_changed); + connect(m_inst.get(), &MinecraftInstance::runningStatusChanged, this, &ServersPage::runningStateChanged); connect(ui->nameLine, &QLineEdit::textEdited, this, &ServersPage::nameEdited); connect(ui->addressLine, &QLineEdit::textEdited, this, &ServersPage::addressEdited); connect(ui->resourceComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(resourceIndexChanged(int))); @@ -663,7 +663,7 @@ QMenu * ServersPage::createPopupMenu() return filteredMenu; } -void ServersPage::on_RunningState_changed(bool running) +void ServersPage::runningStateChanged(bool running) { if(m_locked == running) { diff --git a/launcher/ui/pages/instance/ServersPage.h b/launcher/ui/pages/instance/ServersPage.h index 5173712ca..28339748d 100644 --- a/launcher/ui/pages/instance/ServersPage.h +++ b/launcher/ui/pages/instance/ServersPage.h @@ -97,7 +97,7 @@ private slots: void on_actionMove_Down_triggered(); void on_actionJoin_triggered(); - void on_RunningState_changed(bool running); + void runningStateChanged(bool running); void nameEdited(const QString & name); void addressEdited(const QString & address); From 3562e94650f603c35c52e5d88071314a53b8f0ab Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 16 May 2022 22:39:13 +0200 Subject: [PATCH 297/308] Revert "fix: ignore deprecation again" We want to see deprecation warnings now This reverts commit 47d0da2d96bc375410f5d494ac9371d47adf33d5. Signed-off-by: Sefa Eyeoglu --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c49afaa90..b98502c45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,7 @@ set(CMAKE_C_STANDARD_REQUIRED true) set(CMAKE_CXX_STANDARD 11) set(CMAKE_C_STANDARD 11) include(GenerateExportHeader) -set(CMAKE_CXX_FLAGS "-Wall -pedantic -Werror -Wno-deprecated-declarations -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}") +set(CMAKE_CXX_FLAGS "-Wall -pedantic -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}") if(UNIX AND APPLE) set(CMAKE_CXX_FLAGS "-stdlib=libc++ ${CMAKE_CXX_FLAGS}") endif() From fdf574802972bc48ab9d1954a4868f73c3b1c139 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 16 May 2022 22:54:32 +0200 Subject: [PATCH 298/308] feat(actions): use Qt 6 on macOS and AppImage Co-authored-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Signed-off-by: Sefa Eyeoglu --- .github/workflows/build.yml | 63 ++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4e52af4e3..76a66739a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,20 +16,31 @@ jobs: include: - os: ubuntu-20.04 + qt_ver: 5 - os: ubuntu-20.04 appimage: true + qt_ver: 6 + qt_host: linux + qt_version: '6.3.1' + qt_modules: 'qt5compat qtimageformats' - os: windows-2022 name: "Windows-i686" msystem: mingw32 + qt_ver: 5 - os: windows-2022 name: "Windows-x86_64" msystem: mingw64 + qt_ver: 5 - os: macos-12 - macosx_deployment_target: 10.13 + macosx_deployment_target: 10.14 + qt_ver: 6 + qt_host: mac + qt_version: '6.3.1' + qt_modules: 'qt5compat qtimageformats' runs-on: ${{ matrix.os }} @@ -63,7 +74,7 @@ jobs: cmake:p extra-cmake-modules:p ninja:p - qt5:p + qt${{ matrix.qt_ver }}:p ccache:p nsis:p @@ -104,25 +115,32 @@ jobs: ver_short=`git rev-parse --short HEAD` echo "VERSION=$ver_short" >> $GITHUB_ENV - - name: Install Qt (macOS) - if: runner.os == 'macOS' - run: | - brew update - brew install qt@5 ninja extra-cmake-modules - - - name: Update Qt (AppImage) - if: runner.os == 'Linux' && matrix.appimage == true - run: | - sudo add-apt-repository ppa:savoury1/qt-5-15 - sudo add-apt-repository ppa:savoury1/kde-5-80 - sudo add-apt-repository ppa:savoury1/gpg - sudo add-apt-repository ppa:savoury1/ffmpeg4 - - - name: Install Qt (Linux) + - name: Install Dependencies (Linux) if: runner.os == 'Linux' run: | sudo apt-get -y update - sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 ninja-build qt5-image-formats-plugins extra-cmake-modules + sudo apt-get -y install ninja-build extra-cmake-modules + + - name: Install Dependencies (macOS) + if: runner.os == 'macOS' + run: | + brew update + brew install ninja extra-cmake-modules + + - name: Install Qt (Linux) + if: runner.os == 'Linux' && matrix.appimage != true + run: | + sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 qt5-image-formats-plugins + + - name: Install Qt (macOS and AppImage) + if: matrix.qt_ver == 6 && runner.os != 'Windows' + uses: jurplel/install-qt-action@v2 + with: + version: ${{ matrix.qt_version }} + host: ${{ matrix.qt_host }} + target: 'desktop' + modules: ${{ matrix.qt_modules }} + aqtversion: ==2.1.* - name: Prepare AppImage (Linux) if: runner.os == 'Linux' && matrix.appimage == true @@ -140,18 +158,18 @@ jobs: - name: Configure CMake (macOS) if: runner.os == 'macOS' run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DQt5_DIR=/usr/local/opt/qt@5 -DCMAKE_PREFIX_PATH=/usr/local/opt/qt@5 -DLauncher_BUILD_PLATFORM=macOS -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -G Ninja + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DLauncher_BUILD_PLATFORM=macOS -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja - name: Configure CMake (Windows) if: runner.os == 'Windows' shell: msys2 {0} run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -G Ninja + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja - name: Configure CMake (Linux) if: runner.os == 'Linux' run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=Linux -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -G Ninja + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=Linux -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja ## # BUILD @@ -252,11 +270,14 @@ jobs: chmod +x linuxdeploy-*.AppImage mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-{8,17}-openjdk + mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines cp -r ${{ github.workspace }}/JREs/jre8/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk cp -r ${{ github.workspace }}/JREs/jre17/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk + cp -r /home/runner/work/PolyMC/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines + LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib" LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64/server" LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64" From 3e4d1c04de8b38078929caaaff06258c05b9a12b Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 17 May 2022 21:56:35 +0200 Subject: [PATCH 299/308] fix: include TLS plugins in bundle Signed-off-by: Sefa Eyeoglu --- launcher/CMakeLists.txt | 19 ++++++++++++ program_info/win_install.nsi.in | 52 ++++----------------------------- 2 files changed, 24 insertions(+), 47 deletions(-) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index d91bd78ae..b255f5484 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1051,6 +1051,14 @@ if(INSTALL_BUNDLE STREQUAL "full") COMPONENT Runtime ) endif() + # TLS plugins (Qt 6 only) + if(EXISTS "${QT_PLUGINS_DIR}/tls") + install( + DIRECTORY "${QT_PLUGINS_DIR}/tls" + DESTINATION ${PLUGIN_DEST_DIR} + COMPONENT Runtime + ) + endif() else() # Image formats install( @@ -1093,6 +1101,17 @@ if(INSTALL_BUNDLE STREQUAL "full") REGEX "\\.dSYM" EXCLUDE ) endif() + # TLS plugins (Qt 6 only) + if(EXISTS "${QT_PLUGINS_DIR}/tls") + install( + DIRECTORY "${QT_PLUGINS_DIR}/tls" + DESTINATION ${PLUGIN_DEST_DIR} + COMPONENT Runtime + REGEX "d\\." EXCLUDE + REGEX "_debug\\." EXCLUDE + REGEX "\\.dSYM" EXCLUDE + ) + endif() endif() configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in" diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index 987798b69..84c3766ec 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -129,6 +129,7 @@ Section "@Launcher_CommonName@" File /r "jars" File /r "platforms" File /r "styles" + File /nonfatal /r "tls" ; Write the installation path into the registry WriteRegStr HKCU Software\@Launcher_CommonName@ "InstallDir" "$INSTDIR" @@ -182,60 +183,17 @@ Section "Uninstall" DeleteRegKey HKCU SOFTWARE\@Launcher_CommonName@ Delete $INSTDIR\@Launcher_APP_BINARY_NAME@.exe - Delete $INSTDIR\uninstall.exe - Delete $INSTDIR\portable.txt - - Delete $INSTDIR\libbrotlicommon.dll - Delete $INSTDIR\libbrotlidec.dll - Delete $INSTDIR\libbz2-1.dll - Delete $INSTDIR\libcrypto-1_1-x64.dll - Delete $INSTDIR\libcrypto-1_1.dll - Delete $INSTDIR\libdouble-conversion.dll - Delete $INSTDIR\libfreetype-6.dll - Delete $INSTDIR\libgcc_s_seh-1.dll - Delete $INSTDIR\libgcc_s_dw2-1.dll - Delete $INSTDIR\libglib-2.0-0.dll - Delete $INSTDIR\libgraphite2.dll - Delete $INSTDIR\libharfbuzz-0.dll - Delete $INSTDIR\libiconv-2.dll - Delete $INSTDIR\libicudt69.dll - Delete $INSTDIR\libicuin69.dll - Delete $INSTDIR\libicuuc69.dll - Delete $INSTDIR\libintl-8.dll - Delete $INSTDIR\libjasper-4.dll - Delete $INSTDIR\libjpeg-8.dll - Delete $INSTDIR\libmd4c.dll - Delete $INSTDIR\libpcre-1.dll - Delete $INSTDIR\libpcre2-16-0.dll - Delete $INSTDIR\libpng16-16.dll - Delete $INSTDIR\libssl-1_1-x64.dll - Delete $INSTDIR\libssl-1_1.dll - Delete $INSTDIR\libssp-0.dll - Delete $INSTDIR\libstdc++-6.dll - Delete $INSTDIR\libwebp-7.dll - Delete $INSTDIR\libwebpdemux-2.dll - Delete $INSTDIR\libwebpmux-3.dll - Delete $INSTDIR\libwinpthread-1.dll - Delete $INSTDIR\libzstd.dll - Delete $INSTDIR\Qt5Core.dll - Delete $INSTDIR\Qt5Gui.dll - Delete $INSTDIR\Qt5Network.dll - Delete $INSTDIR\Qt5Qml.dll - Delete $INSTDIR\Qt5QmlModels.dll - Delete $INSTDIR\Qt5Quick.dll - Delete $INSTDIR\Qt5Svg.dll - Delete $INSTDIR\Qt5WebSockets.dll - Delete $INSTDIR\Qt5Widgets.dll - Delete $INSTDIR\Qt5Xml.dll - Delete $INSTDIR\zlib1.dll - Delete $INSTDIR\qt.conf + Delete $INSTDIR\*.dll + + Delete $INSTDIR\uninstall.exe RMDir /r $INSTDIR\iconengines RMDir /r $INSTDIR\imageformats RMDir /r $INSTDIR\jars RMDir /r $INSTDIR\platforms RMDir /r $INSTDIR\styles + RMDir /r $INSTDIR\tls Delete "$SMPROGRAMS\@Launcher_CommonName@.lnk" Delete "$DESKTOP\@Launcher_CommonName@.lnk" From 3b4539de797b28bdeaa407194e961648f18efb7e Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 26 May 2022 23:18:54 +0200 Subject: [PATCH 300/308] chore: update license headers Signed-off-by: Sefa Eyeoglu --- launcher/ApplicationMessage.cpp | 35 ++++++++++++++++ launcher/BaseVersionList.cpp | 40 +++++++++++++----- launcher/Commandline.cpp | 42 ++++++++++++++----- launcher/FileSystem.cpp | 35 +++++++++++++++- launcher/FileSystem.h | 35 +++++++++++++++- launcher/GZip.cpp | 35 ++++++++++++++++ launcher/InstanceList.cpp | 40 +++++++++++++----- launcher/JavaCommon.cpp | 35 ++++++++++++++++ launcher/Json.cpp | 35 +++++++++++++++- launcher/Json.h | 35 +++++++++++++++- launcher/LoggedProcess.cpp | 35 ++++++++++++++++ launcher/LoggedProcess.h | 40 +++++++++++++----- launcher/Version.h | 35 ++++++++++++++++ launcher/VersionProxyModel.cpp | 35 ++++++++++++++++ launcher/icons/IconList.cpp | 40 +++++++++++++----- launcher/icons/MMCIcon.cpp | 40 +++++++++++++----- launcher/java/JavaChecker.cpp | 35 ++++++++++++++++ launcher/java/JavaInstallList.cpp | 40 +++++++++++++----- launcher/java/JavaUtils.cpp | 40 +++++++++++++----- launcher/launch/LaunchTask.h | 42 ++++++++++++++----- launcher/launch/steps/PostLaunchCommand.cpp | 40 +++++++++++++----- launcher/launch/steps/PreLaunchCommand.cpp | 40 +++++++++++++----- launcher/main.cpp | 35 ++++++++++++++++ launcher/minecraft/GradleSpecifier.h | 35 ++++++++++++++++ launcher/minecraft/OneSixVersionFormat.cpp | 35 ++++++++++++++++ launcher/minecraft/PackProfile.cpp | 40 +++++++++++++----- launcher/minecraft/ProfileUtils.cpp | 35 ++++++++++++++++ launcher/minecraft/ProfileUtils.h | 35 ++++++++++++++++ launcher/minecraft/World.cpp | 40 +++++++++++++----- launcher/minecraft/WorldList.cpp | 40 +++++++++++++----- launcher/minecraft/auth/AuthRequest.cpp | 35 ++++++++++++++++ launcher/minecraft/services/CapeChange.cpp | 35 ++++++++++++++++ launcher/minecraft/services/SkinDelete.cpp | 35 ++++++++++++++++ launcher/minecraft/services/SkinUpload.cpp | 35 ++++++++++++++++ launcher/modplatform/ModAPI.h | 35 ++++++++++++++++ launcher/modplatform/flame/PackManifest.h | 35 ++++++++++++++++ .../modplatform/legacy_ftb/PackFetchTask.cpp | 35 ++++++++++++++++ .../legacy_ftb/PackInstallTask.cpp | 35 ++++++++++++++++ .../legacy_ftb/PrivatePackManager.cpp | 35 ++++++++++++++++ launcher/net/Download.cpp | 1 + launcher/net/NetJob.cpp | 1 + launcher/news/NewsChecker.cpp | 40 +++++++++++++----- launcher/screenshots/ImgurAlbumCreation.cpp | 1 + launcher/screenshots/ImgurUpload.cpp | 1 + launcher/settings/INIFile.cpp | 40 +++++++++++++----- launcher/translations/TranslationsModel.cpp | 1 + launcher/ui/InstanceWindow.cpp | 40 +++++++++++++----- launcher/ui/InstanceWindow.h | 40 +++++++++++++----- launcher/ui/dialogs/CopyInstanceDialog.cpp | 40 +++++++++++++----- launcher/ui/dialogs/NewComponentDialog.cpp | 40 +++++++++++++----- launcher/ui/dialogs/NewInstanceDialog.cpp | 40 +++++++++++++----- launcher/ui/dialogs/ProfileSetupDialog.cpp | 40 +++++++++++++----- launcher/ui/dialogs/SkinUploadDialog.cpp | 35 ++++++++++++++++ launcher/ui/dialogs/UpdateDialog.cpp | 35 ++++++++++++++++ launcher/ui/instanceview/InstanceDelegate.cpp | 40 +++++++++++++----- launcher/ui/instanceview/InstanceView.cpp | 40 +++++++++++++----- launcher/ui/instanceview/InstanceView.h | 40 +++++++++++++----- launcher/ui/instanceview/VisualGroup.cpp | 40 +++++++++++++----- .../ui/pages/global/CustomCommandsPage.cpp | 2 +- launcher/ui/pages/global/ProxyPage.cpp | 2 +- launcher/ui/pages/global/ProxyPage.h | 1 + .../pages/instance/InstanceSettingsPage.cpp | 2 +- launcher/ui/pages/instance/ModFolderPage.h | 1 + launcher/ui/pages/instance/ServersPage.h | 1 + launcher/ui/pages/instance/VersionPage.cpp | 2 +- launcher/ui/pages/modplatform/ImportPage.cpp | 2 +- .../pages/modplatform/flame/FlameModPage.cpp | 2 +- .../ui/pages/modplatform/flame/FlameModPage.h | 2 +- .../ui/pages/modplatform/legacy_ftb/Page.cpp | 1 + .../modplatform/modrinth/ModrinthModPage.cpp | 2 +- .../modplatform/modrinth/ModrinthModPage.h | 2 +- launcher/ui/widgets/CustomCommands.cpp | 2 +- launcher/ui/widgets/CustomCommands.h | 2 +- launcher/ui/widgets/LabeledToolButton.cpp | 40 +++++++++++++----- launcher/ui/widgets/LogView.cpp | 35 ++++++++++++++++ launcher/ui/widgets/VersionListView.cpp | 40 +++++++++++++----- 76 files changed, 1838 insertions(+), 297 deletions(-) diff --git a/launcher/ApplicationMessage.cpp b/launcher/ApplicationMessage.cpp index 9426b5a7b..ca276b89c 100644 --- a/launcher/ApplicationMessage.cpp +++ b/launcher/ApplicationMessage.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "ApplicationMessage.h" #include diff --git a/launcher/BaseVersionList.cpp b/launcher/BaseVersionList.cpp index 506844093..b4a7d6dda 100644 --- a/launcher/BaseVersionList.cpp +++ b/launcher/BaseVersionList.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 "BaseVersionList.h" diff --git a/launcher/Commandline.cpp b/launcher/Commandline.cpp index 8a79d564e..8e7356bb1 100644 --- a/launcher/Commandline.cpp +++ b/launcher/Commandline.cpp @@ -1,18 +1,38 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * Authors: Orochimarufan + * 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. * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . * - * 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. + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Authors: Orochimarufan + * + * 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 "Commandline.h" diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 8e984b2bb..ebb4460dc 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -1,4 +1,37 @@ -// Licensed under the Apache-2.0 license. See README.md for details. +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "FileSystem.h" diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 93dfa98b7..fd305b014 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -1,4 +1,37 @@ -// Licensed under the Apache-2.0 license. See README.md for details. +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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. + */ #pragma once diff --git a/launcher/GZip.cpp b/launcher/GZip.cpp index 2f91d4254..067104cff 100644 --- a/launcher/GZip.cpp +++ b/launcher/GZip.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "GZip.h" #include #include diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index f6714614c..fb7103dd3 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 diff --git a/launcher/JavaCommon.cpp b/launcher/JavaCommon.cpp index 6874f6b0c..4ba319b80 100644 --- a/launcher/JavaCommon.cpp +++ b/launcher/JavaCommon.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "JavaCommon.h" #include "java/JavaUtils.h" #include "ui/dialogs/CustomMessageBox.h" diff --git a/launcher/Json.cpp b/launcher/Json.cpp index 04b15091d..06b3d3bd2 100644 --- a/launcher/Json.cpp +++ b/launcher/Json.cpp @@ -1,4 +1,37 @@ -// Licensed under the Apache-2.0 license. See README.md for details. +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "Json.h" diff --git a/launcher/Json.h b/launcher/Json.h index 06a45a728..b11a356cc 100644 --- a/launcher/Json.h +++ b/launcher/Json.h @@ -1,4 +1,37 @@ -// Licensed under the Apache-2.0 license. See README.md for details. +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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. + */ #pragma once diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp index e71ad1826..fbdeed8f0 100644 --- a/launcher/LoggedProcess.cpp +++ b/launcher/LoggedProcess.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "LoggedProcess.h" #include "MessageLevel.h" #include diff --git a/launcher/LoggedProcess.h b/launcher/LoggedProcess.h index 03ded98c7..61e74bd9f 100644 --- a/launcher/LoggedProcess.h +++ b/launcher/LoggedProcess.h @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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. */ #pragma once diff --git a/launcher/Version.h b/launcher/Version.h index 292e2a180..aceb7a073 100644 --- a/launcher/Version.h +++ b/launcher/Version.h @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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. + */ + #pragma once #include diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp index 684547f8f..032f21f94 100644 --- a/launcher/VersionProxyModel.cpp +++ b/launcher/VersionProxyModel.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "VersionProxyModel.h" #include "Application.h" #include diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp index fe7c34eaf..3a223d1b6 100644 --- a/launcher/icons/IconList.cpp +++ b/launcher/icons/IconList.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 "IconList.h" diff --git a/launcher/icons/MMCIcon.cpp b/launcher/icons/MMCIcon.cpp index 29e3939b6..436ef75ff 100644 --- a/launcher/icons/MMCIcon.cpp +++ b/launcher/icons/MMCIcon.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 "MMCIcon.h" diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 87b7ffacf..041583d1d 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "JavaChecker.h" #include diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp index dd8b673c9..0249bd22e 100644 --- a/launcher/java/JavaInstallList.cpp +++ b/launcher/java/JavaInstallList.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index eeda8bc41..0f1f9b5ef 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 diff --git a/launcher/launch/LaunchTask.h b/launcher/launch/LaunchTask.h index 6ab0a3930..2efe1fa22 100644 --- a/launcher/launch/LaunchTask.h +++ b/launcher/launch/LaunchTask.h @@ -1,18 +1,38 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * Authors: Orochimarufan + * 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. * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . * - * 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. + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Authors: Orochimarufan + * + * 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 diff --git a/launcher/launch/steps/PostLaunchCommand.cpp b/launcher/launch/steps/PostLaunchCommand.cpp index 9aece9753..cf765bc0a 100644 --- a/launcher/launch/steps/PostLaunchCommand.cpp +++ b/launcher/launch/steps/PostLaunchCommand.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 "PostLaunchCommand.h" diff --git a/launcher/launch/steps/PreLaunchCommand.cpp b/launcher/launch/steps/PreLaunchCommand.cpp index d3660b30f..bf7d27eb5 100644 --- a/launcher/launch/steps/PreLaunchCommand.cpp +++ b/launcher/launch/steps/PreLaunchCommand.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 "PreLaunchCommand.h" diff --git a/launcher/main.cpp b/launcher/main.cpp index bb09ea6c3..85fe12602 100644 --- a/launcher/main.cpp +++ b/launcher/main.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "Application.h" // #define BREAK_INFINITE_LOOP diff --git a/launcher/minecraft/GradleSpecifier.h b/launcher/minecraft/GradleSpecifier.h index fbf022af2..27514ab9d 100644 --- a/launcher/minecraft/GradleSpecifier.h +++ b/launcher/minecraft/GradleSpecifier.h @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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. + */ + #pragma once #include diff --git a/launcher/minecraft/OneSixVersionFormat.cpp b/launcher/minecraft/OneSixVersionFormat.cpp index 1983b7bbb..cec4a55bb 100644 --- a/launcher/minecraft/OneSixVersionFormat.cpp +++ b/launcher/minecraft/OneSixVersionFormat.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "OneSixVersionFormat.h" #include #include "minecraft/Agent.h" diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index f0f236251..5e76b892a 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 diff --git a/launcher/minecraft/ProfileUtils.cpp b/launcher/minecraft/ProfileUtils.cpp index 28299c8f0..03f8c1989 100644 --- a/launcher/minecraft/ProfileUtils.cpp +++ b/launcher/minecraft/ProfileUtils.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "ProfileUtils.h" #include "minecraft/VersionFilterData.h" #include "minecraft/OneSixVersionFormat.h" diff --git a/launcher/minecraft/ProfileUtils.h b/launcher/minecraft/ProfileUtils.h index 8b80c488f..5b938784c 100644 --- a/launcher/minecraft/ProfileUtils.h +++ b/launcher/minecraft/ProfileUtils.h @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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. + */ + #pragma once #include "Library.h" #include "VersionFile.h" diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp index e974953a1..dfcb43d88 100644 --- a/launcher/minecraft/World.cpp +++ b/launcher/minecraft/World.cpp @@ -1,16 +1,36 @@ -/* Copyright 2015-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index dd6c7c0f2..aee7be358 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -1,16 +1,36 @@ -/* Copyright 2015-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 "WorldList.h" diff --git a/launcher/minecraft/auth/AuthRequest.cpp b/launcher/minecraft/auth/AuthRequest.cpp index 65b51f099..bb82e1e26 100644 --- a/launcher/minecraft/auth/AuthRequest.cpp +++ b/launcher/minecraft/auth/AuthRequest.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 #include diff --git a/launcher/minecraft/services/CapeChange.cpp b/launcher/minecraft/services/CapeChange.cpp index 243f7d15f..c73a11b6c 100644 --- a/launcher/minecraft/services/CapeChange.cpp +++ b/launcher/minecraft/services/CapeChange.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "CapeChange.h" #include diff --git a/launcher/minecraft/services/SkinDelete.cpp b/launcher/minecraft/services/SkinDelete.cpp index 6ee6b3192..921bd0942 100644 --- a/launcher/minecraft/services/SkinDelete.cpp +++ b/launcher/minecraft/services/SkinDelete.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "SkinDelete.h" #include diff --git a/launcher/minecraft/services/SkinUpload.cpp b/launcher/minecraft/services/SkinUpload.cpp index 616a8810a..c7987875a 100644 --- a/launcher/minecraft/services/SkinUpload.cpp +++ b/launcher/minecraft/services/SkinUpload.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "SkinUpload.h" #include diff --git a/launcher/modplatform/ModAPI.h b/launcher/modplatform/ModAPI.h index d11ed7ca1..cf1163533 100644 --- a/launcher/modplatform/ModAPI.h +++ b/launcher/modplatform/ModAPI.h @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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. + */ + #pragma once #include diff --git a/launcher/modplatform/flame/PackManifest.h b/launcher/modplatform/flame/PackManifest.h index 51fe8888d..677db1c3f 100644 --- a/launcher/modplatform/flame/PackManifest.h +++ b/launcher/modplatform/flame/PackManifest.h @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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. + */ + #pragma once #include diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp index 5bc01ed26..4da6a8663 100644 --- a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "PackFetchTask.h" #include "PrivatePackManager.h" diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index 1493e8f2a..83e149691 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "PackInstallTask.h" #include diff --git a/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp b/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp index 824798c0d..1a81f0265 100644 --- a/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp +++ b/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "PrivatePackManager.h" #include diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index 6613c336d..3061e32e1 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 flowln + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index bf73829c4..bab35fa5f 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 flowln + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/news/NewsChecker.cpp b/launcher/news/NewsChecker.cpp index 8180b6ff4..3b9697320 100644 --- a/launcher/news/NewsChecker.cpp +++ b/launcher/news/NewsChecker.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 "NewsChecker.h" diff --git a/launcher/screenshots/ImgurAlbumCreation.cpp b/launcher/screenshots/ImgurAlbumCreation.cpp index 294449b72..a72c32d3b 100644 --- a/launcher/screenshots/ImgurAlbumCreation.cpp +++ b/launcher/screenshots/ImgurAlbumCreation.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 flowln + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/screenshots/ImgurUpload.cpp b/launcher/screenshots/ImgurUpload.cpp index e9fd39e06..f8ac9bc24 100644 --- a/launcher/screenshots/ImgurUpload.cpp +++ b/launcher/screenshots/ImgurUpload.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 flowln + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/settings/INIFile.cpp b/launcher/settings/INIFile.cpp index 450ddc3f4..733cd4440 100644 --- a/launcher/settings/INIFile.cpp +++ b/launcher/settings/INIFile.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 "settings/INIFile.h" diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index bf5a6d432..848b4d195 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 flowln + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/InstanceWindow.cpp b/launcher/ui/InstanceWindow.cpp index f8f100bfb..0ad8c594f 100644 --- a/launcher/ui/InstanceWindow.cpp +++ b/launcher/ui/InstanceWindow.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 "InstanceWindow.h" diff --git a/launcher/ui/InstanceWindow.h b/launcher/ui/InstanceWindow.h index ee010b2f0..aec07868b 100644 --- a/launcher/ui/InstanceWindow.h +++ b/launcher/ui/InstanceWindow.h @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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. */ #pragma once diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index 8136502b4..9ec341bc8 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 diff --git a/launcher/ui/dialogs/NewComponentDialog.cpp b/launcher/ui/dialogs/NewComponentDialog.cpp index cd043e1ba..ea790e8c2 100644 --- a/launcher/ui/dialogs/NewComponentDialog.cpp +++ b/launcher/ui/dialogs/NewComponentDialog.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 "Application.h" diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index c7bcfe6e9..5b8ecc5b3 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 "Application.h" diff --git a/launcher/ui/dialogs/ProfileSetupDialog.cpp b/launcher/ui/dialogs/ProfileSetupDialog.cpp index a53474454..64c0b924c 100644 --- a/launcher/ui/dialogs/ProfileSetupDialog.cpp +++ b/launcher/ui/dialogs/ProfileSetupDialog.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 "ProfileSetupDialog.h" diff --git a/launcher/ui/dialogs/SkinUploadDialog.cpp b/launcher/ui/dialogs/SkinUploadDialog.cpp index f8715dca6..e4106255d 100644 --- a/launcher/ui/dialogs/SkinUploadDialog.cpp +++ b/launcher/ui/dialogs/SkinUploadDialog.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 #include #include diff --git a/launcher/ui/dialogs/UpdateDialog.cpp b/launcher/ui/dialogs/UpdateDialog.cpp index 4d2396aee..e0c5a4950 100644 --- a/launcher/ui/dialogs/UpdateDialog.cpp +++ b/launcher/ui/dialogs/UpdateDialog.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "UpdateDialog.h" #include "ui_UpdateDialog.h" #include diff --git a/launcher/ui/instanceview/InstanceDelegate.cpp b/launcher/ui/instanceview/InstanceDelegate.cpp index 037b7b5e5..137cc8d58 100644 --- a/launcher/ui/instanceview/InstanceDelegate.cpp +++ b/launcher/ui/instanceview/InstanceDelegate.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 "InstanceDelegate.h" diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp index 41e0ce123..fbeffe350 100644 --- a/launcher/ui/instanceview/InstanceView.cpp +++ b/launcher/ui/instanceview/InstanceView.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 "InstanceView.h" diff --git a/launcher/ui/instanceview/InstanceView.h b/launcher/ui/instanceview/InstanceView.h index 25d8ba0bd..ac3382742 100644 --- a/launcher/ui/instanceview/InstanceView.h +++ b/launcher/ui/instanceview/InstanceView.h @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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. */ #pragma once diff --git a/launcher/ui/instanceview/VisualGroup.cpp b/launcher/ui/instanceview/VisualGroup.cpp index 1c2dd7fc4..e6bca17d6 100644 --- a/launcher/ui/instanceview/VisualGroup.cpp +++ b/launcher/ui/instanceview/VisualGroup.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 "VisualGroup.h" diff --git a/launcher/ui/pages/global/CustomCommandsPage.cpp b/launcher/ui/pages/global/CustomCommandsPage.cpp index 436d766ee..df1420ca6 100644 --- a/launcher/ui/pages/global/CustomCommandsPage.cpp +++ b/launcher/ui/pages/global/CustomCommandsPage.cpp @@ -2,7 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield - * Copyright (c) 2022 Sefa Eyeoglu + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/pages/global/ProxyPage.cpp b/launcher/ui/pages/global/ProxyPage.cpp index f53d74afe..ffff8456c 100644 --- a/launcher/ui/pages/global/ProxyPage.cpp +++ b/launcher/ui/pages/global/ProxyPage.cpp @@ -2,7 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield - * Copyright (c) 2022 Sefa Eyeoglu + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/pages/global/ProxyPage.h b/launcher/ui/pages/global/ProxyPage.h index 02c7ec407..279a90295 100644 --- a/launcher/ui/pages/global/ProxyPage.h +++ b/launcher/ui/pages/global/ProxyPage.h @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index 1d8cd1d7c..fcc110de0 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -2,7 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield - * Copyright (c) 2022 Sefa Eyeoglu + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 1a9ed7dbf..19caa7320 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/pages/instance/ServersPage.h b/launcher/ui/pages/instance/ServersPage.h index 28339748d..37399d49f 100644 --- a/launcher/ui/pages/instance/ServersPage.h +++ b/launcher/ui/pages/instance/ServersPage.h @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp index 23e2367bf..468ff35c0 100644 --- a/launcher/ui/pages/instance/VersionPage.cpp +++ b/launcher/ui/pages/instance/VersionPage.cpp @@ -2,7 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield - * Copyright (c) 2022 Sefa Eyeoglu + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp index 0b8577b19..30196aad6 100644 --- a/launcher/ui/pages/modplatform/ImportPage.cpp +++ b/launcher/ui/pages/modplatform/ImportPage.cpp @@ -2,7 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield - * Copyright (c) 2022 Sefa Eyeoglu + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp index 1c160fd4b..10d342184 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher - * Copyright (c) 2022 Sefa Eyeoglu + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.h b/launcher/ui/pages/modplatform/flame/FlameModPage.h index 86e1a17b3..445d0368c 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.h +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher - * Copyright (c) 2022 Sefa Eyeoglu + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp index 0b180bf36..6ffbd312a 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp index 0b81ea931..5fa00b9b4 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher - * Copyright (c) 2022 Sefa Eyeoglu + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h index c39acaa0b..94985f634 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher - * Copyright (c) 2022 Sefa Eyeoglu + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/widgets/CustomCommands.cpp b/launcher/ui/widgets/CustomCommands.cpp index 5a718b54d..5ab903950 100644 --- a/launcher/ui/widgets/CustomCommands.cpp +++ b/launcher/ui/widgets/CustomCommands.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher - * Copyright (c) 2022 Sefa Eyeoglu + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/widgets/CustomCommands.h b/launcher/ui/widgets/CustomCommands.h index 4a7a17ef9..ed10ba956 100644 --- a/launcher/ui/widgets/CustomCommands.h +++ b/launcher/ui/widgets/CustomCommands.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher - * Copyright (c) 2022 Sefa Eyeoglu + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/ui/widgets/LabeledToolButton.cpp b/launcher/ui/widgets/LabeledToolButton.cpp index 3866b43fc..f52e49c98 100644 --- a/launcher/ui/widgets/LabeledToolButton.cpp +++ b/launcher/ui/widgets/LabeledToolButton.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 diff --git a/launcher/ui/widgets/LogView.cpp b/launcher/ui/widgets/LogView.cpp index 3bb5c69af..9c46438d6 100644 --- a/launcher/ui/widgets/LogView.cpp +++ b/launcher/ui/widgets/LogView.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "LogView.h" #include #include diff --git a/launcher/ui/widgets/VersionListView.cpp b/launcher/ui/widgets/VersionListView.cpp index ec5d57b00..0e126c65b 100644 --- a/launcher/ui/widgets/VersionListView.cpp +++ b/launcher/ui/widgets/VersionListView.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 From eb5ed508248d922edd8f516b90d38b55841f0695 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 3 Jul 2022 17:15:37 +0200 Subject: [PATCH 301/308] fix: set UNICODE and _UNICODE for Qt 5 builds Signed-off-by: Sefa Eyeoglu --- CMakeLists.txt | 3 +++ launcher/java/JavaUtils.cpp | 20 ++++++++------------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b98502c45..5041e580a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -166,6 +166,9 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5) set(QUAZIP_QT_MAJOR_VERSION ${QT_VERSION_MAJOR} CACHE STRING "Qt version to use (4, 5 or 6), defaults to ${QT_VERSION_MAJOR}" FORCE) set(FORCE_BUNDLED_QUAZIP 1) endif() + + # Qt 6 sets these by default. Notably causes Windows APIs to use UNICODE strings. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUNICODE -D_UNICODE") elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) set(QT_VERSION_MAJOR 6) find_package(Qt6 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml Core5Compat) diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index 0f1f9b5ef..c2b776ae2 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -197,25 +197,25 @@ QList JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString archType = "32"; HKEY jreKey; - if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, keyName.toStdString().c_str(), 0, + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyName.toStdWString().c_str(), 0, KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) == ERROR_SUCCESS) { // Read the current type version from the registry. // This will be used to find any key that contains the JavaHome value. char *value = new char[0]; DWORD valueSz = 0; - if (RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz) == + if (RegQueryValueExW(jreKey, L"CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz) == ERROR_MORE_DATA) { value = new char[valueSz]; - RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz); + RegQueryValueExW(jreKey, L"CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz); } TCHAR subKeyName[255]; DWORD subKeyNameSize, numSubKeys, retCode; // Get the number of subkeys - RegQueryInfoKeyA(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, + RegQueryInfoKeyW(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL); // Iterate until RegEnumKeyEx fails @@ -224,30 +224,26 @@ QList JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString for (DWORD i = 0; i < numSubKeys; i++) { subKeyNameSize = 255; - retCode = RegEnumKeyExA(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, + retCode = RegEnumKeyExW(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, NULL); -#ifdef _UNICODE QString newSubkeyName = QString::fromWCharArray(subKeyName); -#else - QString newSubkeyName = QString::fromLocal8Bit(subKeyName); -#endif if (retCode == ERROR_SUCCESS) { // Now open the registry key for the version that we just got. QString newKeyName = keyName + "\\" + newSubkeyName + subkeySuffix; HKEY newKey; - if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, newKeyName.toStdString().c_str(), 0, + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, newKeyName.toStdWString().c_str(), 0, KEY_READ | KEY_WOW64_64KEY, &newKey) == ERROR_SUCCESS) { // Read the JavaHome value to find where Java is installed. value = new char[0]; valueSz = 0; - if (RegQueryValueExA(newKey, keyJavaDir.toStdString().c_str(), NULL, NULL, (BYTE *)value, + if (RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, (BYTE *)value, &valueSz) == ERROR_MORE_DATA) { value = new char[valueSz]; - RegQueryValueExA(newKey, keyJavaDir.toStdString().c_str(), NULL, NULL, (BYTE *)value, + RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, (BYTE *)value, &valueSz); // Now, we construct the version object and add it to the list. From 4e99da7b6212fdba121d93892f48f6bce158e2a6 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 3 Jul 2022 22:40:05 +0200 Subject: [PATCH 302/308] refactor: query Qt variables using ECM Signed-off-by: Sefa Eyeoglu --- CMakeLists.txt | 13 +++-- cmake/ECMQueryQt.cmake | 100 ++++++++++++++++++++++++++++++++++++ cmake/QMakeQuery.cmake | 18 ------- cmake/QtVersionOption.cmake | 38 ++++++++++++++ 4 files changed, 144 insertions(+), 25 deletions(-) create mode 100644 cmake/ECMQueryQt.cmake delete mode 100644 cmake/QMakeQuery.cmake create mode 100644 cmake/QtVersionOption.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 5041e580a..f56c4070b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -185,15 +185,14 @@ else() message(FATAL_ERROR "Qt version ${Launcher_QT_VERSION_MAJOR} is not supported") endif() -# The Qt5 cmake files don't provide its install paths, so ask qmake. -include(QMakeQuery) -query_qmake(QT_INSTALL_PLUGINS QT_PLUGINS_DIR) -query_qmake(QT_INSTALL_IMPORTS QT_IMPORTS_DIR) -query_qmake(QT_INSTALL_LIBS QT_LIBS_DIR) -query_qmake(QT_INSTALL_LIBEXECS QT_LIBEXECS_DIR) -query_qmake(QT_HOST_DATA QT_DATA_DIR) +include(ECMQueryQt) +ecm_query_qt(QT_PLUGINS_DIR QT_INSTALL_PLUGINS) +ecm_query_qt(QT_LIBS_DIR QT_INSTALL_LIBS) +ecm_query_qt(QT_LIBEXECS_DIR QT_INSTALL_LIBEXECS) +ecm_query_qt(QT_DATA_DIR QT_HOST_DATA) set(QT_MKSPECS_DIR ${QT_DATA_DIR}/mkspecs) +# NOTE: Qt 6 already sets this by default if (Qt5_POSITION_INDEPENDENT_CODE) SET(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() diff --git a/cmake/ECMQueryQt.cmake b/cmake/ECMQueryQt.cmake new file mode 100644 index 000000000..98eb50089 --- /dev/null +++ b/cmake/ECMQueryQt.cmake @@ -0,0 +1,100 @@ +# SPDX-FileCopyrightText: 2014 Rohan Garg +# SPDX-FileCopyrightText: 2014 Alex Merry +# SPDX-FileCopyrightText: 2014-2016 Aleix Pol +# SPDX-FileCopyrightText: 2017 Friedrich W. H. Kossebau +# SPDX-FileCopyrightText: 2022 Ahmad Samir +# +# SPDX-License-Identifier: BSD-3-Clause +#[=======================================================================[.rst: +ECMQueryQt +--------------- +This module can be used to query the installation paths used by Qt. + +For Qt5 this uses ``qmake``, and for Qt6 this used ``qtpaths`` (the latter has built-in +support to query the paths of a target platform when cross-compiling). + +This module defines the following function: +:: + + ecm_query_qt( [TRY]) + +Passing ``TRY`` will result in the method not making the build fail if the executable +used for querying has not been found, but instead simply print a warning message and +return an empty string. + +Example usage: + +.. code-block:: cmake + + include(ECMQueryQt) + ecm_query_qt(bin_dir QT_INSTALL_BINS) + +If the call succeeds ``${bin_dir}`` will be set to ``/path/to/bin/dir`` (e.g. +``/usr/lib64/qt/bin/``). + +Since: 5.93 +#]=======================================================================] + +include(${CMAKE_CURRENT_LIST_DIR}/QtVersionOption.cmake) +include(CheckLanguage) +check_language(CXX) +if (CMAKE_CXX_COMPILER) + # Enable the CXX language to let CMake look for config files in library dirs. + # See: https://gitlab.kitware.com/cmake/cmake/-/issues/23266 + enable_language(CXX) +endif() + +if (QT_MAJOR_VERSION STREQUAL "5") + # QUIET to accommodate the TRY option + find_package(Qt${QT_MAJOR_VERSION}Core QUIET) + if(TARGET Qt5::qmake) + get_target_property(_qmake_executable_default Qt5::qmake LOCATION) + + set(QUERY_EXECUTABLE ${_qmake_executable_default} + CACHE FILEPATH "Location of the Qt5 qmake executable") + set(_exec_name_text "Qt5 qmake") + set(_cli_option "-query") + endif() +elseif(QT_MAJOR_VERSION STREQUAL "6") + # QUIET to accommodate the TRY option + find_package(Qt6 COMPONENTS CoreTools QUIET CONFIG) + if (TARGET Qt6::qtpaths) + get_target_property(_qtpaths_executable Qt6::qtpaths LOCATION) + + set(QUERY_EXECUTABLE ${_qtpaths_executable} + CACHE FILEPATH "Location of the Qt6 qtpaths executable") + set(_exec_name_text "Qt6 qtpaths") + set(_cli_option "--query") + endif() +endif() + +function(ecm_query_qt result_variable qt_variable) + set(options TRY) + set(oneValueArgs) + set(multiValueArgs) + + cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT QUERY_EXECUTABLE) + if(ARGS_TRY) + set(${result_variable} "" PARENT_SCOPE) + message(STATUS "No ${_exec_name_text} executable found. Can't check ${qt_variable}") + return() + else() + message(FATAL_ERROR "No ${_exec_name_text} executable found. Can't check ${qt_variable} as required") + endif() + endif() + execute_process( + COMMAND ${QUERY_EXECUTABLE} ${_cli_option} "${qt_variable}" + RESULT_VARIABLE return_code + OUTPUT_VARIABLE output + ) + if(return_code EQUAL 0) + string(STRIP "${output}" output) + file(TO_CMAKE_PATH "${output}" output_path) + set(${result_variable} "${output_path}" PARENT_SCOPE) + else() + message(WARNING "Failed call: ${_command} \"${qt_variable}\"") + message(FATAL_ERROR "${_exec_name_text} call failed: ${return_code}") + endif() +endfunction() diff --git a/cmake/QMakeQuery.cmake b/cmake/QMakeQuery.cmake deleted file mode 100644 index b1025d653..000000000 --- a/cmake/QMakeQuery.cmake +++ /dev/null @@ -1,18 +0,0 @@ -if(__QMAKEQUERY_CMAKE__) - return() -endif() -set(__QMAKEQUERY_CMAKE__ TRUE) - -if(QT_VERSION_MAJOR EQUAL 5) - get_target_property(QMAKE_EXECUTABLE Qt5::qmake LOCATION) -elseif(QT_VERSION_MAJOR EQUAL 6) - get_target_property(QMAKE_EXECUTABLE Qt6::qmake LOCATION) -endif() - -function(QUERY_QMAKE VAR RESULT) - exec_program(${QMAKE_EXECUTABLE} ARGS "-query ${VAR}" RETURN_VALUE return_code OUTPUT_VARIABLE output ) - if(NOT return_code) - file(TO_CMAKE_PATH "${output}" output) - set(${RESULT} ${output} PARENT_SCOPE) - endif(NOT return_code) -endfunction(QUERY_QMAKE) diff --git a/cmake/QtVersionOption.cmake b/cmake/QtVersionOption.cmake new file mode 100644 index 000000000..1390f9db6 --- /dev/null +++ b/cmake/QtVersionOption.cmake @@ -0,0 +1,38 @@ +#.rst: +# QtVersionOption +# --------------- +# +# Adds a build option to select the major Qt version if necessary, +# that is, if the major Qt version has not yet been determined otherwise +# (e.g. by a corresponding find_package() call). +# +# This module is typically included by other modules requiring knowledge +# about the major Qt version. +# +# ``QT_MAJOR_VERSION`` is defined to either be "5" or "6". +# +# +# Since 5.82.0. + +#============================================================================= +# SPDX-FileCopyrightText: 2021 Volker Krause +# +# SPDX-License-Identifier: BSD-3-Clause + +if (DEFINED QT_MAJOR_VERSION) + return() +endif() + +if (TARGET Qt5::Core) + set(QT_MAJOR_VERSION 5) +elseif (TARGET Qt6::Core) + set(QT_MAJOR_VERSION 6) +else() + option(BUILD_WITH_QT6 "Build against Qt 6" OFF) + + if (BUILD_WITH_QT6) + set(QT_MAJOR_VERSION 6) + else() + set(QT_MAJOR_VERSION 5) + endif() +endif() From e2a74dfc307ef89e7b365f89f3ab8f54a6772293 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 9 Jul 2022 08:59:43 +0200 Subject: [PATCH 303/308] feat(actions): enable Windows-i686 Qt 6 builds Signed-off-by: Sefa Eyeoglu --- .github/workflows/build.yml | 8 ++++---- .github/workflows/trigger_release.yml | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 76a66739a..3d7096ab4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,14 +26,14 @@ jobs: qt_modules: 'qt5compat qtimageformats' - os: windows-2022 - name: "Windows-i686" + name: "Windows-i686-Legacy" msystem: mingw32 qt_ver: 5 - os: windows-2022 - name: "Windows-x86_64" - msystem: mingw64 - qt_ver: 5 + name: "Windows-i686" + msystem: mingw32 + qt_ver: 6 - os: macos-12 macosx_deployment_target: 10.14 diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml index 91cd04742..7c780cbfb 100644 --- a/.github/workflows/trigger_release.yml +++ b/.github/workflows/trigger_release.yml @@ -43,9 +43,11 @@ jobs: for d in PolyMC-Windows-*; do cd "${d}" || continue ARCH="$(echo -n ${d} | cut -d '-' -f 3)" + LEGACY="$(echo -n ${d} | grep -o Legacy || true)" INST="$(echo -n ${d} | grep -o Setup || true)" PORT="$(echo -n ${d} | grep -o Portable || true)" NAME="PolyMC-Windows-${ARCH}" + test -z "${LEGACY}" || NAME="${NAME}-Legacy" test -z "${PORT}" || NAME="${NAME}-Portable" test -z "${INST}" || mv PolyMC-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe test -n "${INST}" || zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" * From 203c3ec233ee8a1c4cf1a838087666d0128500ee Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 9 Jul 2022 13:33:10 +0200 Subject: [PATCH 304/308] refactor(actions): speed up package installations for Windows Signed-off-by: Sefa Eyeoglu --- .github/workflows/build.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3d7096ab4..c56827626 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -74,9 +74,12 @@ jobs: cmake:p extra-cmake-modules:p ninja:p - qt${{ matrix.qt_ver }}:p + qt${{ matrix.qt_ver }}-base:p + qt${{ matrix.qt_ver }}-svg:p + qt${{ matrix.qt_ver }}-imageformats:p ccache:p nsis:p + ${{ matrix.qt_ver == 6 && 'qt6-5compat:p' || '' }} - name: Setup ccache if: runner.os != 'Windows' && inputs.build_type == 'Debug' From f464b347b2f55884cd614f91da5a83b3dda2fb68 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 9 Jul 2022 13:50:02 +0200 Subject: [PATCH 305/308] fix: install TLS plugins for release builds Signed-off-by: Sefa Eyeoglu --- launcher/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index b255f5484..9708f65c3 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1107,7 +1107,6 @@ if(INSTALL_BUNDLE STREQUAL "full") DIRECTORY "${QT_PLUGINS_DIR}/tls" DESTINATION ${PLUGIN_DEST_DIR} COMPONENT Runtime - REGEX "d\\." EXCLUDE REGEX "_debug\\." EXCLUDE REGEX "\\.dSYM" EXCLUDE ) From 211d596fddaf75ccd8faf1040bef583b0d6c0bfa Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 9 Jul 2022 13:57:34 +0200 Subject: [PATCH 306/308] refactor(actions): switch to system QuaZip on Windows Signed-off-by: Sefa Eyeoglu --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c56827626..fbf0d82a5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -77,6 +77,7 @@ jobs: qt${{ matrix.qt_ver }}-base:p qt${{ matrix.qt_ver }}-svg:p qt${{ matrix.qt_ver }}-imageformats:p + quazip-qt${{ matrix.qt_ver }}:p ccache:p nsis:p ${{ matrix.qt_ver == 6 && 'qt6-5compat:p' || '' }} From d77237ca5d717b7e00e8b6517cd2c32d7c21fa74 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 9 Jul 2022 18:56:32 +0200 Subject: [PATCH 307/308] refactor(actions): rename Windows builds Signed-off-by: Sefa Eyeoglu --- .github/workflows/build.yml | 4 ++-- .github/workflows/trigger_release.yml | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fbf0d82a5..5b8e53658 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,12 +26,12 @@ jobs: qt_modules: 'qt5compat qtimageformats' - os: windows-2022 - name: "Windows-i686-Legacy" + name: "Windows-Legacy" msystem: mingw32 qt_ver: 5 - os: windows-2022 - name: "Windows-i686" + name: "Windows" msystem: mingw32 qt_ver: 6 diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml index 7c780cbfb..2dbd5cd46 100644 --- a/.github/workflows/trigger_release.yml +++ b/.github/workflows/trigger_release.yml @@ -42,11 +42,10 @@ jobs: for d in PolyMC-Windows-*; do cd "${d}" || continue - ARCH="$(echo -n ${d} | cut -d '-' -f 3)" LEGACY="$(echo -n ${d} | grep -o Legacy || true)" INST="$(echo -n ${d} | grep -o Setup || true)" PORT="$(echo -n ${d} | grep -o Portable || true)" - NAME="PolyMC-Windows-${ARCH}" + NAME="PolyMC-Windows" test -z "${LEGACY}" || NAME="${NAME}-Legacy" test -z "${PORT}" || NAME="${NAME}-Portable" test -z "${INST}" || mv PolyMC-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe From eb33a87ff5ba01d05f6a96d4d06a0d00fdd85647 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 10 Jul 2022 18:10:41 +0200 Subject: [PATCH 308/308] fix: remove TODOs Signed-off-by: Sefa Eyeoglu --- launcher/ui/dialogs/SkinUploadDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/SkinUploadDialog.cpp b/launcher/ui/dialogs/SkinUploadDialog.cpp index e4106255d..b5b78690c 100644 --- a/launcher/ui/dialogs/SkinUploadDialog.cpp +++ b/launcher/ui/dialogs/SkinUploadDialog.cpp @@ -60,7 +60,7 @@ void SkinUploadDialog::on_buttonBox_accepted() QRegularExpression urlPrefixMatcher(QRegularExpression::anchoredPattern("^([a-z]+)://.+$")); bool isLocalFile = false; // it has an URL prefix -> it is an URL - if(urlPrefixMatcher.match(input).hasMatch()) // TODO: does this work? + if(urlPrefixMatcher.match(input).hasMatch()) { QUrl fileURL = input; if(fileURL.isValid())