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 zcmeAS@N?(olHy`uVBq!ia0y~yU}OMc4mJh`hM1xiX$%Z3YcoS4N+NuHtdjF{^%7I^ zlT!66atjzhz{b9!ATc>RwL~E)H9a%WR_Xoj{Yna%DYi=CroINg16w1-Ypui3%0DIeEoa6}C!XbFK1lpi<;HsXMd|v6mX?*7iAWdWaj57fXqxx$}cUkRZ`+oP*8vx zo?4NKFg>pr>iy*WTz!y?zM-ChKC<$X%rqRzK{6Q13o`Oc@{2R_3*a6=u>vlQp|K(t zhn9-O0&I$XeX-jBQ=XfP;*H`G*Rm4)I*LOo3sUuiQj7CTi;`1a%Tn`7l))hZ3Ky$@ z%!<^U2$#&<)V$)%{5)GFBU3Xp*+^Wn&iOg{MZpD$$*HzVhG0#gu(R^aO-xS>&rB)F zK$wP*@JY=}$B+TJE~gX}y`g!TCALaN2-6}Ea<)q5MtTMasYt9+@VKx7yU#a2CDm5R z7_18+<>~AkP?VpRnUiX(R2<~)q~Pi5Y-VU_W~OVT2k}ZpZh@6=eoAIqrAuN-s;!cd zfsvuEfw``sNr;i5m5GJ6fuWTF#4%u{sU?Xii6x1672AQ*t4&32fv>NXM`m$Jeo-Y@ z2$INyQwt$H2sbAaq*_5i0UXCxiOHbgu~jN4%1tml35;S=*6-WtcG zp8C1k-Qbhvud>M3b}YZuVq&ImJs}s9=lFZF)U`vbE5q&x^&kE%Y0|#nt(esGE?e!l z#lJWX^8Q>UeZ#&X&N4s#AM0e}^j{xd*(otFFmM)lL>4nJa0`PlBg3pY5)2H2dpunn zLn`LHy<53M)wS~YNBw{Xovwxj0t#&%C!{9SHt}#NZ90)AG>wVbYte)S$Jm27TM`y6 zt7u`LXfuOlYNEzBS7t+p0>wiI4hv0jb(!GmV8*Bi=F0QnA{tz9H|Oh0Cj?BK8Xgvzd-R<8xY~ z*ZrJtLE^fTvJD<{r!d@mWA)(b9mkF>qC5aqI~3o=iQL~PT|;v?gOC*Y!6I6@cBS>LvI6bc%=SR{zhb3Fs_VPdfQl)6Mo2@`zDu2@D)d#*F5IuUm z_gH(Yu)g1)q;sr&EA7oPcfS5L_fKo-#gp?7H!qp|x99=;vG(P^WGz0Pwv@HtvYkHl z;9jM?rqcx0DAbvK~z~Eb0FE@Rwv?Ak&I# zix1d5h(6XHe{NaB$+Mg5S#&nsdQs@f9%FvMFgi@m_4^*Ltb6m>{={*7}I;i=9e5%;B+{=;`C>(mv{bjeSElY6W82Zj>T#pG81NR(weYw>j~rka`RZi z)>ut2G%tKqfAdE{{ms_>nFVf!{Qm?liTF-@sbhJ-&_6`(=#Q$_D`(=5|0u{1Rk!~l zF*mhl$K)$q0yEDkB>$3MYt!|yMDOL1|C=+}BBm~S{=w(5GsC{f)T5WG^5q}QIIY#6 zwC4}kjjKO4#9vka*YfgM`hV41>i)leUtl;M(_$7ad&KEM_5rqI?f=(oezXtd1Eme- znQOM62-$tMq~YU3^8@`^9ZS#Vv2HW7tGV|hp!)vpEyi1V_`fsVX6C;t)Vt;6t&2Cm zvRn9VpR{)`%OBniGe15&7UOuv!gaGmqKSCLf?owoQp@JQWO#hI@<9D{0q@y1OlM5( zegv|A>d9JQd24bM)7BCnkL)uu7`WOe%4KujVaYK(VDKu6XOD-o;jaw6%&O%j$Mu(0 z=K7VC9iGW$V0Xvy)st7dHhW~Nn=FgZT@f19DvoTJ=+$&9V@Tps@%mYkrlnUk(g@8PdE|2q5P&D4fc zkI9Ym808G@Y|MQQ9)0Jq-Dux$y*)lsmu~-X34e9Y;o{Asjs8sWOkuj4b5`ChJl1|H zth?{ryWtUhU0*CoB#*8`5Hzx>~^vDeSUZq5|zj>IdQEHg^Ju;&(kq(E)vOVhe|(6TI@KjzKeeILRkuK_AYtPQzw9fUVuLR#SqMf*TQKZc*jJUR z;>|RF#lm^U*((-jU1wn5{`N_G&+TUS^!vPTf@^Z;8jHPrt@rrwuFx=nxwYJxPZx-; zTC?0qpGjI;{@pp*6FDZ2mUN%{$Cr8Hh0AB@+P~n_NiFymdHH2+g@++o-=vM zXP@W4^*Me_wc+HV&DzY>6Y_-1T%xBx{lsH%{KtpQf!tl^-Z95mRC(OgW!YwEw`G_8 z>WiESn;yk|{JZ2|(^}7J@ih}oSLE&Fzp*OfS$0@gW72je=_z8NcJbzRHD=`ki?e*0 zUMziKeWT5M(xJ2i(VN&z@2z7jGq5|8)ev4%Ci>uGCSN6EM44IY^*4-{nC3b2Hs&n~ zyB{yktsN6lBRDs9l8Jbtl4g#R)YKcV{-?XnW4vefqVelC2f+u-PuKAr6zdk#xZ#GjplcV#D(@MkLD=x6wSc2kePg#0u9-^(7AU(9#x zXpAAkL{;gWH_mHzJSfx|r8 z_J-@*o7lZEm~HNs?ysb)W_)$OwEVZ8bLZIk=A_1iMsuu5ui2qpu8{gN=kkpoA502r zKW^=Kl(m)Z1K%aK>DrV0=7|2T@!j*yY}Ry(bKrysvahk#@<-j}N!q zah$UDE^|-S7hz-+*5knRg%lUva2@in&Y|m^S${hcRzoZ zUKHiVR8jQIxow+fz7JQ`yk|L{<*B#b|9oA{641P8XY})51o&78pmDDm#nz* z@wyy#-(SCrmYj8QldFqq+PLRevyj8wXIDece?FjTEK_{7qx4Q{O-0!eqkAe=yTv{{ z58hX+@xk%ZlG!oq4DEW}-fQz>`1(`tt4?oO*T;vQj7gHNn#Rn_Kj{2oNr=7lVC~RdG8dVd zUn8;H)mwZaOMvp@!%PpRi-sFt*i~|@eRX3qyY0RY1|PO>=FF0cPycnb;U4RH^|iaA z8JAy=iGMqV;Z^w884Zo^r}pxvPx|&(T7}bw^-avDtM$&8COmk#$FAw(j{@Pv7I}!Pqx*)`fjhMLd6d z{$7mVm?m=av;z*dpZ`ZO$Yf=a1;_cjKFzkgP}nft%2HPyUJ z$bs!x`}37~sdv6=Yi$p4UH|X0vCOxZal7A&EEagbLdBrs#1DhM+0(u)VVQ7#PuHp3 z%U!$ou6rl^@9d8c@t619D^{E?)bsVg{FPG}KK(RY*%`f!KP(_Ev}$XU7*joeO;s0Jnnj z>4g%`8(djk%U}JIpTaQf z0h3$&!sXMG7j3o<3&<1Baq}ult=b_WcvI7hVW#N$E&A&gWjy%v;*FK)l)1OJiB7q> zbG5w&qls&%F-tg$o@SY&l=|(^fMr4rDW9Dm9PBz(Ctr|PQl>NShludBGR>Vg!W*@{ z^)`!6VUSu-@vJtjHS`TnjO^UMf4N?-IDK8?e{uEPj}KQ)I-SF++VE^e)xrsD8|S;F z+ea4KPVGGRjx{IV@#5T^f6dF7&Am##MZLNy`P}-zLgqDccO;H3-f!KY6=gVEQa*3p zf;-28xfeu&@@3qC+>1BoX06Isxp?zvgYe2{PyWZRy7^>@=oE%oPiRpKYBhUu88}xD7>CKp*(>N&S>`j$@@_Z<-W+Io!F>gV$1#~a?3sDAKxr?AXI#p<_z@P5g;f0ZAk zU6N}ko%bi;?5yr&SIz~kUCX}R-1+#$LD%%}bAGs&l!;#wi$0y&@ig_p9iFe-x_&W4 zX<6+SEXaOmQMF_C7W0Fj4$M8UYUZOKeIFCw`;=~Pi(=3XeRJja7l|pW*)Li2CdmtQYdq1JANaE{^IniBp(fd~1Vq zk+25imKh%(7Cz{@Q)n__)_>+8rVRVq8~5KlPvs4K$7IWzG4aKlG94?Hip)y|;a{Jt zJik<(IQ4cf|6KWB)>DNXyj{}6ReApfuFfs%|Eh>xkYd#-5j_&^v`$PMiyVPGLB;m1RRVgm-Y!X6b;lbrrG8mqZ0HWti0lto}GrEw^#nWFd#m znv5aIl?m;498DU99GqR#!_{-=DP9dIa^+lbvdn$6>FO=j8=V7}YciTlpBEmUZJMz9 zj^mSMEEgOu-rUY4e@fP!z2u*n1LuO7Wo*+<$-1y>Fh1dPsCxB>eY4tY*YxWrWnKKd z7<&pTbtWXtSlc;Ylyf1(j16UTw9= zJmada$!OwHQYQSMy!iM<$GN_{yco`2y2+oECa)(J_f|%=AuW*UM6%?A!#kT#IC3sf zT(mixarU#m#fPnC)=XuXpZ`0|d+nrlK=T@=Gbd$T?oMHlQn1?1 zWMEz{b7`;28kP$@yP3S7@j2}>XN~4uAZb!Rw&bg3whBBC9z>SXMU4h%A-! zny6A>qqn|5Lxp8&)0Ibp3$L{*z0ka&rIi)yxRxVg>8-^< zk?-!zd|7kuVgDZU&%ghjs40GaF1C(Y5FPv%=eWzc!ii6{`&)_2r9P3fh5}108V-tW ziqwBJ<<+^1{4J*#jQSiknJ4rx)bU+e|L_9Kq^$>xgmgrTf85)x^Vd~_N5L`R1V_*j z!IBLXzZ_NilMLKNHphB4*{OYCn-HAgKgsogD4+9Grr;9~1qIcg+H6h`$Y4I<)TU4= zV{!QChI}PSU9TH&l6>w9MKGUWQe)s}YHRv%pON{f&b=Uk+uCR_@MWUq_8ZtD@IQ#s7>?;-t(QST-8F`FTC$jG0 z`{NV%W3J588pG+#U5~20Rzx+(djzgAsN#NAu>R1Gc|v~7SDvQ^SZq|Dp}=hs9$CZB zxs*{$tvF0`$_m~)iw*Xrw_IXi;J6pO#=uFeXj&5Q)0XQDlYF~GvnD?>?=qbtoW`6J zyiRe4Z{N~sMY63e*BK1`r&XFRv#Yha5a?FH8!)}dzr;jx5ATZOuZ|M@L3YE#%2Okq55 zQ%-B%QF z4C5}(Y0hQJWU!oAb>&h10oPr{jMqA5) zW!<+fFEp>4|ImKl|Al`(ixkY*-Pm2a{Gpc6 zO6%5=AB$KgOwM4wv)-DY_08qE4pudZ3_Q=A3!YstcB|Ure3S7OuY!7^YQfrfHd7Q{ zy!*cP@h#qlgYQe0FPPA_{+$cA4k%-MV|1_bB`PxUf3Wpjo7N>3O!heT&z#8th;- znG%tGS@>Ozu++A;+UPw&Z(SJ|?-APEWXJ5mn|j@vztK9(*-!ZM^H0igtIkiFobhOB zf?9@QgUor!2OlLW`>h{7Q%yVm-eTpF>5?V~KioeuVV;=6?0F9!e*Sn=Iq`zBg5S^g zW;t&oxkP!^%y|2Sw~A-NBcti-+y~!QgxTvrezPz?sRye~q11tmc1kHaT!_ zn?aGKgsF+y+G+>c^WUe=^X&N<*L|%;j@hIC+RwXRrVGpNd)0BZ@$;|BNvAY!^pS2AhkqNAv*KRI%Ng(!@GL~F8JRr-Qg@&v{0|`LOR1%<_~|EH@^5MN&lEqo z+#Mn98}yi0+*H55LiWnJQnAOMKE8XBBboNwy>@=rpLZd-f#rXoPaJqqNrnH*O{lw zwm9#;)A`h`{gM_NpM2sQk5^8it#8lf?-V}1_Fs!AOKP0=s~E9&cNn&=ni+EE$DYR< z7-Vj~_mk0HV)1J0l5=7Ya*rFHdl_1~^Lo0z)1im&4GTrQzNeMttl4I~_7q3iZgCys zFOmxFrVX3zH*eN&wBw)i=jO$~AO6jkX-$2;d}hAn?wTg!wWm@(Ppv*^qHy1B1TJi4f!*g7!&rM99 ze`wcfn77~V>)zhmbAet77$-=8~sgiXqR{ClDD z-hw-U-#&!1nr+9iYco5OL+y<>o#_t#$&`9(SNi4q|9AJd?&A;nyQfzFMs{_}Z;r2( zm*1WAyKtAC;h@nQJKia~cC2}@`|O89vCHk3{f?0MbNto*)32Yo{4Et+e#MhvcZPX` z#xsQt7p|Rbk2N|lvG&7}(tuyFU+;4>|2mfVVe!@XhAip7c~t#x+TWDC#mnFz#?O51 zd)Ds22M<5UMO=_B`@8$s``6nIev8z~Tl`)1@XfI@;~V~Z`ZsKEYVI>=m~m1}<=XLr z?e&j*Dy}a#SkGP)d{%hIMAqqjjh^!lu}X$)+-zN~5wT|VPxkr}CSR5BiV_`D=aj8s z*Ic{)xxebexAq5C{eF1k;pF|@S6lVW4}}IVnAz%U{dC*9L`zS(EyatXe;i{-GMj&z zNsMuOYR#pIwHu0=xBLlw=5+h|!Q$=nE3S#RS(aURB~-k;;5g%i+~lZ#E7neZ=ihlV zXDdWAgG$HbqXjLkl^@Ul&pXTDYjxm6>I4nr|F?=~&xCIcqpjwIaD_K ze&b%d3gOq&r?HusPh1iasulKOE~CpFk*3=dCC~MEa%Rhi&k2?KR-gRM)kxW~EBc6Y zMbzSTYBlK$41s6VShhR0>Bszd!1=t}Va|v3>b}ZpO)7e6pBrzr7#*L^#vrgcqAexO zja~j0YqrVM2a`1;HY;3SJU!sh`_N^{qW7g38rI%vw~U-J?|OPg?9a<7ca5LfI91(K zKA15vbM-U9eP#_2UXx_plsw} zv_9DcmZts7Y=2x`x#9d__0{HGm;4#M?=78Ex9G&fU+%6y?lCYVF+LSBmN_UQU~0bb zIN#yVkN0?HJ!h*h%`IGMAAIa;5})4N$YS~UYa8rkH%&O$vBq`DB-8Wzc#A}%V(tA{299M%xkvtz1h>6OD9kL`QiM{Py=Y;k8N~TKhIUVo3%~1Z2h0xr@zmt=F^%STFu;BtE_S;hM~4pRZE6i{D%@ z3QG0wPI8?cW2+RxwxZ|Ea>>fr>dOz-9Q-nS-jg+Y;tX>)n6buj)ttF?ylivbOU7;a zF$ed_zVEVp6EJz}_m*o6R~478VcxdJf5PO>A6pfw?C`QR(Lbu|&xe0b8<@ zFKVcU2;DgBB<_|cl)W%SNMvo6u%ISOVo;z&TbS4x2MNiQkMCSgYI}C^PWAJJc~%B@ zc0T`e{{Qd7_qN~bKJ0Jr+V1E#`7~3ngP}oZZ(OP1`ArNR-CQE&4SObh;TQ2;)g0Z) z5b-D|dM3M@(x#gnwT~WUC^3Az-&D0GCxPRJVBoBy?kiuV=*aE=v5sK@+sB13x`Vt9 zpL$yH_YoU|!{g0P$S9BOmT(5`8+j?g=`;HSrzo80(g$B;y*Aa zB>bhxQ6@7QrvC|9s|eT47cvkXgS8ZGb& z-|*KUBg&+|M8-som*LIzwNp$TeB0}m7q~|89yeuUSZI-Dm#ixD>bt!0=dZhpKNe|P zGPs6asmWzu;gH??@cVJe9le}P9L9m4m$Eni-)?nqW8D4tLX|>>qfhKDv$<|uaxxLq zFs!mvD`pj0fB0PP$_KOA&;Kr%Yiqtl{n1sW8^Y`#K1od2|NJIvK&jlk($?3@n09Yz z$_k>To5kq=5MwmCkPym6UN=)o+RwsL;O6 zoF?<@|4eDRxVzvs=hNd0UaDGLYwcgUsiKilxq7x{!`$C1{)C^|ar(o*wvXx?HeQO? zwteuw_1O1*#dn%>UmaTW@ZW(iTr12>mfbB)dGePj;l-@N!yfzp&)E8l*FOFH%%8`% zCJAcBPz>du7!}cwS6Q!@FFWvsF`Ao$f zAs%0~dHtuq-d%iYZ}9)$8*eBo-Bj8X=5lFz?!}{h_6PmXd^+AC*T%rWz~JfX=d#Wz Gp$Pzsrhird 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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 z zmEifj@fAg7KORjk&t*pr6_d>eE-m$zw|=wX@VVLtjqKCfKkM8TFTY;8U*^95@9NC> z>)-4i=wA(f_k8xuX~nAp8|)cZ2(Aik-)YZ&p`7{0dzKIKZ?{+J)(bt@?zvA`;m_O4 z^RCBLzx^*^lyV|&?W;4D%8>RU0~3eI_5)3<+{@};1<#+gr|fMM>qoIQ+Ap>THSS-( z+W+>7F9-M6FV=HZ0bNeCRM*m4h#`8>b*9_9SQ_T$&SBY*V8{8$dF6q9;hqhP`|YYC zeoStd-Nq~Z&GNP&H&T7*tF$5W-=Ck`WeW~4?&o^GctQM9AqK85+N}Shmgm+!$p4z6 z_+vUt>MRD%1{2X=r&zXy2|YN#%rEojMsok(obPwb?Kiz)$UrL8E;KOBojX^0e#N8C z)qNI^I_|d~{?53Ysh;(#)>MuUOXXMZntwWF5(~q@dggzVa-KSGE6khf}3{5^{lz#KKT##^jCFzw|Q=xZ=Qee*FDSw5U^H(q3 z&fEQVj#N|qUG?Kj-%WiS^!;JjtwozwAN6O^;+Vj&qT%qWvrN(2oC0wg)_it99#|h1 zUmPRz&-uWtv+fQ^bztK$fr!m%Xa98T@B7j7`MiC-pG6Yu8@mbL5AOT=d)w8k&$nN+ z(c)b!^2h!A>ur6kjdDzXI34(fth6|DmfEv>?92EtD|?+}?Mc8jjjWeRK4Xm|9|?m>ch%wr4MI_tkIsZy-eo8b*VqMWoDiCoBrUwNW?241;zkz z@n+uz{F52JafbTDndOA8ZA1$D0FTBGd^OMS z>@3dboS*i0dqD%&m9-%s-g~qDy|%n+j>LtEvkNT`tFioVI}o&=fwSS)*&9k6PbRPg z7%gxYQegSdJg@TE%=~xz|Nnd57JIu2xme?px~j;@z@R+Q)5S4F`NF+>`~Lj<{eJ(W zE5ZJ?Rx(qVO1U41?$Ms{-LWn#Hu>+jS(ne^z*OuOwo?a)td%NCU;qv*5-tGSF`u*|kfQDG}NeuHDGM20kJ&^o% zu|Ly`yY>H5Th+iCs>AW6(_zZQkYz#o3001lx#xN9 z6$sNZy*B;i9)FvUM^*_cyZKZ-ndn}3;o`-OoBaJ+Jdv_c0n_U6^>rVQ+yDDm&-?$; z`*Q++ejm7dfBvetxBJ3go8Ehuy>{L2H0|8LlgIzu4CHRpeus+Y)qkH(kDq5$ z_U4B5G`-j?`TO4;cU=`d@;iu=DSp+v<16LPXR>>J$mi0j)pKB4aNcv~1(qxyrXMK{ ztY0G=_y4M8a`?JQrQz+bhwbuqS%+FUr}N8L6ogxgA!o2xqM(B3bnr5tpD$g-V@=jq zN3*^X+oAmif0dAlrX%nxjQ>h8*oR4wK({$2ZjN8#gt&reO&)@}ICxa*bJk>mWV zzr%i2+4ylXoLzc}@6@Z4LVqjQ?$Q*zp~NV}5PjtMv8yau9!we)+YiK-{_orWs&J)0 z!;iN)zc(FUU;p>@MqzcoJvA2=I38XttNj`=+7lJ6!18DFy4~+~&5tcUYs$S#RbeeQhl8~;KUTjzzGB_m zTA>B^#rOX>I(ND6?6QCNs^8ZZSxjrR<(t5V$i36VIQ~afy*xA1IA6-X?$3Ls7N%n3 zjk{Ri7p`out3A<%hL+#1?v-9^=-j__D^Re6h-^c#dyU*We zD*7Mr;azj()yEC-xog8;g0k{0_f64E|HFR2y>0f~n<5?^|RyBUwA|z^A9D=kI+wEm|$U z^6Aw4md)P}R>-W;?&$Ys{jHU|cYU<=Jneh_O#YyTU$QpG1cn=2C*CZIQSf4kD9Gz> zDv{rztvEsK#qS08)!85A{@JtY`|a}kwd}SZ4lw`E{r~s7{p$Mv|D1R6HO@zrA7}M8 z)cyUnU9R%U#CqF@EyDA-o@X)c)a5!8`s;x$-=D`0_I$toR-AqA{dRr!am8Q$T7Mi8 z;8rhp;Pqr+ea-dn@+mJ*hWQ*Hif%=UTiUi|R( zh=Vc5mhId1K{ft<`T9Q}%k?8RBshP!{CVKc^;!Y>>$L~ozpk6}Y?B@9kHVAte`dGE zuVJ%h7h>QoJ2>lQ!D|nuhy(McF$B!_XZcYzhb3V56owby?!5c+`MkW`KII2|Cpy)HYx{_of7!}ovsSAM&he!V3)s30$W`7Wl??B5P{>y9(-&(DsI zkGd++}|J-sZ26@o$IEH}lF* zPyU{>`~8OdkT|Pco-9%$vR6}p>3{FTx&Ny5cfDAo$#47R!l?o=t^=Yyzk@g%EFOH% zm4EZQF8+J*s=S6Uf3bh};)SN%c4}Zbuq_~czAeilmj>47+m1Ny+B%hIg(~L+Gq>Bn zcmCNosp`k(hQI#yf4^-0ej~Y`^X~D)zetv{igPf2JLcLgX8UFG{69~=X$CLbBh43n za68kd9a|Xg{i;!T|9UU)pTBj&rcM7_8s%>XG_V{nJCc6=DJVnMe3&Qp4WcCC(e}e-B&#+s}FbmHpRsyWd4g z^_^vZuy0*MyyJc^`7hCX=N-6zK2yhR`M1nmyKnO~J|stTPhhxlExh6HbWRsfhUjpv zW3je=3@c>We_s`1yyf@%L+Z_a&TKy?Kc83q?xvz(fscjT?CFSZ(EGf_+9ZQLrm#Zu4|$nqW8br%JR3a`s_<@YnE(v?)SgEn>KNP=9LOW zE!yOm0+kLh6-U=Jyn9{cZtusiV(wIq4VHhl)%jihTlc%wp8JQ#k4*UA_?nOP^NY_}{{OCD|MT>&cgg_@dA?U`UpAMs{{1?|Z2Geg zpI5E>8+CH`zfLCmE)@r+1s>te$(q6|Ca`QM(tEclrL`cay*0&fhQ0&M&uT`Pu{| zoA(}gb#-<8EaUWZ>-G11IMkkgcGlKq)oR@Paz8HRKN0%9asOA%8S9r`sff>t)6VVm zZu+k$UMCjOz~a!T&(|KNC!orhvy}D8wjT#<)@eW3UUyY{YTcYyALr~_`*->&+44IZ z|G(XS|KIn=$NTNg{zxBG{{8*E{fRK7QLPYhj`!czS3RFwp4Q?Yd9VCt>h!&DwskP) z$xk?5D*xxY@Q2@TO_qPXxoF$eZ7benwdDWy*81bH-~hrY z7whk=-v{phsz-G;Vro>LZKOEQ?ec)^1*KM@)WB^lhfR`^z2f{|X6@TE7y@=M<{?anR+h2`2^`jdYxogVk= z;|G0K^*39u$31p3dSJZp^0Op(`j{1U;NHvsx9|V^_QqR(@0TZ6!{h(HILvRq$H*#C zzT~zR>wT@Y=h^>UTfa@ecEkK@>)&qi`@DSWHvj0H*i8aIyqh*%1O?A6(Z?0l9H4^e zJ#*n!(J8CH9ITF-F#l@$?d<5Ovy9hX`FHu$68%3j4{YGp+wtH%pVf;6g}dMH`#qhV zM`FWdq~towalzibwXQ!Z{-61fTIUa1Lh9dM{_A?%XD!yt>(<%DN7=r8$Xw3)+svWxWv~0W8?+c9xs6}mPOWC)1G~hf@{2c4fmQ7e zimXmGcm9EfO?m%sdq4BIeEpx9?`z*zf4;UZc6CF{@ih-_-Qqg5`Bl_<%Qb6vG1YuM z`nS+JSMTf6xA%WdYAye(>Ui@6Xv*v7tHL+7;6xPu;f;5EKXduk+RU#za*L1ry%zpf zux-k*6?sc_t=6pz%a~tqh*RF`{hrT!obgH)YBg35xSQE{f6WLBQi2V5xC(-nCft4) z`!D_Z+1cN7eq{VN_P6`_E7@6s{etK zQt^-P$2WskZSF69e~10zwWqgNMN3|Pyp5^4H}v-O$kN-#)^|Ve`2Ms%`1P9p2ljyv z+>JRWFk~!SeRAg)ffJgbe9wB9>(lC~Tt6OP>I<(ejLp@~-B?)XdF%19-rJUc(i@xY z{=W2=|8lXv?#qD>txdo4_y7GS|BZPid|)VADdGLQ^)Wj?Ej`*TzTYDI+8RsH5YnFO zan-rW-{+(<7QguXcE`GHQww8j!?Y^CmwsJ3HM6#E-o7Qf2O$~3}|Jh&W$Hf}Z z!0-E6MQ#%vyo3(8X)O4*^s2V)C8oby>}EUvEws(9lbyUa{Ou*sh#WP)>< z&F3@5-|c=pX#Or|m~=$l{>wuCTBAQd58P8UG*H@cqY&2abLi%1k=U-w@+-ep|JCo; zn^I5Ti`Y@{Q2OEAzx)4vn!dlL_~Ihh+`ff67VqBm|Gu&=2wf{z( zU75QkxBFLZ^xu;Ve>wD|7VCMj%<^ksakv~C+0v#@79HPnuZG?-TebSk zEcd@!f9)>6x|ZJEd;QVtYvS9hd!#Lko_qtX_UM?PAn~))zb5I4yWOXWwxH@{qV?M? zmrux+-AL^JpwC%;%?*}!FIX@=jDNqD$MN5z4{v{WAF}?{eMtO^Zq?&nb1~b$UoLkq z^PT-I@B8*ouUj@T-QRJ4`{h^HmjBzb>sQT7=5Jee{dyEsydpk#YFS>P?bjO4KS!Nz za)IU-9%>c$tz;@yW6I%UeZFsv$c^8nk)>y!Ue_+TZZ*}aEy_zex%=6xtgkt%?Q?VX zf4IxktnN2w$M3q|Z@)k6umAJ7`pvf6dGdVo)ves5dh<6}#u&2l2-n0|f_Umqv?==b;OE#>d;SHE03{T^@p ziR~%>{``~<>N~*nXpL&z^s8a@OD|n}nfN>S?x`&ecfYS(XLRq__urMF-+u?+^@`5% z$yEHH9~h9S$SB01Te8veYDuG=7mJ3+b|b!L+FKu&*3H|Ir~csg6+i9GziReh-Fjos z)oEu8laKY>{5T`I?`HD)z2ANXcnI2nvK0!sos^Y_gg^HdVygkHtc;f>y z`scFq$yi*GdnnB2y!!2wuaF|uWT8WPMSERoMU8;mYA1*PjumUT82`Td5FvVRXS^uW z?@+}J@uK(k#*6ODt-spD${luQhT-GC9|Hew6}36Vbf>2pypViaPta-{#Ya*hBN>c{u+cOn5M_ z`d#M9M#hKsQW1+196VIsNC>ej)=$uJKD6ttvs2>o51q}7(;_bD9hUeV%)0rF_$038 zL&@x`+q10{ayC!Cx_kP+g>!$U^Csp*m=<)~pA>i|Dbkleq09rk9B)G^)3?5IjvJTm zon4pxNFZZ9@8^;wxhaknFSaWfMXp(^uyOI?-CUa zFP5ml1{MeHxYBbw7dhw)Gny@D-TT!hi}&AS%gf)M{tj6m@~gDac5QC$wktw6t{W}O z-u7jF^sd@pYra70sSR&gL^gc;#*uZ?F5BnM``ekI(skeYS5-GESI!GNc5v^pl&`m| z)_Tvr{e5NLQr&I2H=Q2DA9GxMl+}Y_%MX2q<@0p|R0R{HwF15_y?Xi9tMy@Ns;rx~ zT`tVno4@?()%2yh+ve(O?T)&7JM!_B^+xl;|3;?neZ2YELV55oPU9lZFC0FrWfHc2 zjC}q0-Nz`dzwe`P6;->!Ph!`@jCR=*rJyzc6f z3m?lS)*>Ia8-AjDu1BZkzW#mr)vMK@VsyJI-`Yu66AS0+h{_Kr_8 zYYb_$5@Ou8mnk~_*w-!FzJ|V9zb)>!pY-z5tyAiJ)|N`AclYgI9s18WH#RE%XBf1H zn&HF9J>{|LTL0ur*M9D+dLg~*g+uODqYr~dBy+p1Smv%`Mv+cEFjy-9Y~>nncm z(0_YpYwtC|FVj_4Cx|ltBdA~QVx*qh)aNoM0 zYtK*1dV8wK3(5t&$7Q}B&9z>;Lx1&KSjZRdjfy^=_4v`oy1CFI!aA~I zd%VyTjxTWyEDrDA-Vkc^Y1H*#yD^7r-sMwSwRN?+rEkyohsH-mu06kg`!3yCyH{WR zdind$s%sVdbxR>*Z&JPvjGOvyZ?Cn@-TU>YVdht_+NkH=@z?+CTYs&>sWt&Ly4 zvu6G)t-oede`Q6cC!6T$UH%rF>2qu2Z_6!qP}?TVX5ehNlQC1G@f4HjB*txtGB3hF z8E5sE;+6iXw|3q8@-z3*uC2KrfA`#;dQDaOGi0ixkxe{c!J5k-a^tsuEwq03`Ss;j zwHL!*%Y58;_0l%OlTH`(IlWl6&12wfSet%!p1=x?fOMw=&sK?8{4Txv>gu)cW${+= z_Vu>Knbls9sF~%cpu6H)<&>oR%YM1~t%*EzDUGY> z39gdI|FUxD#&0dG+q?eux2d^leh=2@LR7|*i zoN?Q;p5KqlZf)Dzv$Ayen#U{3tIs+^v%fM|2-mh!yN?oIqb1jO-_A_F^egM8Uarrr zm}}wN1U^hJyx_2)0Mvml_q0%8$r8SCXi39c72Ol(U#(Bixh8XMy=v~hEq1-jAqi!H zB+DhH*IVrB*G0a5{c?T!ns0Nr_Fnt0d_zAlz;Qt$sE2oE=_3ch6*>XQP7aepxVLRj zsok2JZP^y}{6Y-0u_Y3^AV?uLbFTe-ug6+zH(PEiseNg|@xxmrL?9yqw7j|cr9}Zt zmhcK=!-UD#gloQky;1dSHE%;8q){N^ydX*;&pqT_T$D3aK>S1 zOcPVsk^gM<>79xb<}+|M{K=Uq39e8OMB^cjFZyRD7AQcRj~IPcfTw7LVlb=m5yu1u zjdNQcc)$k!z`~3y0w;VMSRC@U`1C*(3PFkDc~pT4Oa!LjiJAkGf_aph8^paZ zgB%uEfQIObbuENI9)=4uFtQX0D=}Q_pu8ROJ8O?`g}7nVDr@IIL9lWiV9IJ>`CJmYV6L9h3K5Co#^3o6f1%`G|I-_n7g@dOPv3m=lV8E) zew%k2etN_it`;^aSz{rbwQ7Q_^ar^E2t`-sTd)`=mudqqqX)!H)f=oh{%XWDW?$^RU(%uE<{Q1kk zCL7_a^Vn!^M*fb12ls+_RVn%slH=t5@Bg-F*u?SMLy$ zb#e2bYOHu|$pTf!(9n->tuGyZd*ATXiucFT7HG(A;<{^;@QE>Gn$z(Y>*psI)=Gu% zy6>L+qte~FMssS!sfOEqFRx1fcr<-=M5Rr-``K%ogY{qOdnveGHdv@>r}M|OmEZmT zL4NM*cg}deU&`a5Ei&n)1#fNVi(B7YLKYYmS-+B5!JBu9^Kiq#i%&F4wgq$6)O=<> zW51(Sd++;)6$U|ByA-CMJ+Rk3(C6PrU6HAET3l~ho-aD*o;Tz7gF~DTdscM0xvx84 z-}>_RV^)LZo{{XcR^PfX!8Y;wPKFaP9lN@Zvz>9B$1WZzuxnb@qO#?3xu&*$J6g+A zWjWfEDlD&dEwdJ@`C~4*YK84*g-r^J&Ng!gY~uAx_+Y-|zQ{8Sa z-}qzpTjAeZ7cS*KE+3h(@3rIKZS#Fs+^h{?5P5J&Fpw+lXq557p5hp@3s--;d7ghi z``q~@YELW`tR2?gdd64bq!@Da(u%I^4_iYLwt5{*E92hPaqC>mM)UpY4wA|i`#S=1 zC7xID+09tOBk-P+z3CyxD*vqpUw7@TNQ*qEy+8E95_6ujs}-4LZ>ES{`>Hc#{`Y^} zyH+m#d31A!$((cSudL)=G@M9`&3=B*U%kAgWt+&BwJaa*RH^Rq?)jniUaJ1to#lNR z?$=wr{}dES$2|1zb2Z=V;myA1AJ>d3u~o~I)=KZN7e0LLnAv(SxgW+3hh``0zlrV8 za=QA$qW|7J@qfr8zV9rZA=ma^=$(0zb4`wlq~?R~>?*eU`91#{ zVu2d%uKV*GKvK<<4xNl8wx1xpWIx^zi3PHubOE#_N3dU|@_rarPfbunX$ z{!Aag&gPdI9ozEGpF6kjDa*4Ezv%V*_wO%CNOX_bTnTz6MzIyjWKk8OG zn!NGGod*vd_&Ym0-&VCcf8@VA+dsxhIbzJ}Z#0=0kPF7Vw0s5zM!TefoXp}91A{A! zOw25-Z0uYSDrKof#hLkekt&Hr$r+htsYM|wCHVyrD)~uSsmUe9LJW*;3*rU%{erzy zD^pV(p^7eYaE27+=a)1vF6ZVEmync_3suQ0&B?jU%f~MuEEet;te2fySsdjTtOqqH zOeHxdvAB4Vpil!-1M?yg(FT?V)&{nayu{qpcmc7*;^Nejmt^MWB^FgKQdExc3-$+l zfJ0D9MbpUK(P}$B13NZ!-1{Ej` Hqdoxum$21; literal 261369 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�#Zn<^q432ko@@*})oIAh0 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

W`2HKUwcQUKI}Qq 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ⅅ8(QRZP3T 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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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ðE>xB11BizzyI-g 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 pF(qBURVSG?HeKJDFSoAq*8@x2>)Go>fp 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 vciOh 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 literal 331581 zcmc~y&MRhM<+sgDHZ)>j;4bV8@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hEk44ofy`glX=O&z~GV^;hE;^%b>-;z`()4*q+J20#d@jz#zo{0?Z2-8JNK$j0_A5 znBcMw3z!jXkV3aWrwt4YT$epv978G?-$q5}$b?J&OMAcX&8>~G@lv;U-rN{uUYN%c zpey)^LCAlFxPXTK3LzyXDOZI!E+J8-uM3?P<}cB0Qkp6>HRP$HNvQIl2~&=+T=#o( zNA~!}HBQDftTrq??VJ5{%lqHs&siEXuUTr!c1K@xt@x&rjd1v;8dG>7w+Y#A@%JG9&N(ZugIUePp~^$Mf@)h}4xCCxrL( z8TlLEPnQc6Q9V)|cTytdS}*6V^QX=m*`MFjy>r&hvwtd8rkaZi?V9Otu;1NCt+LLf z^TGV&cgb}zmreQJRBQLmo2zwZe{D=)bkA$kQ%6L@)z%t)u#)fb+#fIaWz{OX({iSJ zuZNa(xGlb~-_i2% zC%%hKd%MGZxJxc_74j((|Bn~KNw z`i~#Ku3wyeVqSWx(A@)K-D_DlOw5hcJ3MR7?)Ko?{D)Z|WMtk1ZCM-m+N$8!{<@=^ z|18m2cl4vCMn$A5xG762~X@Jwz!PnoMqSQ&o25t$7QE~b8Ccyjo`tTtMebr&)?U^ zKCy5|klOqvo+R^$2YdH*^6Z>8)6rwb4Bkim>GiFTW7jLMI`_2YM$F93Gk?Bs%8uqf zaV5i%zxyA{^8CwB?zgbmEI-WaTKJ-&kLj+bG}$uCe9$Tg6)Ay&fgOU zW>4{s>|%*KbeN&i_C{~G|6h*(`^uF1C*CyQxRv3znzwgKp7`Yb72BH|&#lb1nIpmr>tJm-GS>yE&PQCbF@Zq$*^8$^>cIhu2bX8b|6_)9# z)%(xC=knT9-t3H}^knrX?X%(+{9U3dp`sCEVJT{EwQisP^cP3F-|8>^da_n*gIHHf z$nGiIxsM13)*RaT_q}*s`^K4x=iZ)NX_kA7No8$qoaFz>L2@fgA1~Lvw5OBhuGA4z zmF_t=AAfn6w?cmXJe!v9yZc1N6yDB=^s}=o7MI+8)Hgd6?|5?r9*|*qt zcLS2NQnv49h!A>oX>qITi?>D-7M$m4d*&Hn^>JR7Mc>?YWfd{owC|Rb?(W{hIf2jo z`a8$R9!0wr%fDFfBrw9k55JD+Y-<#629qw>PjO-Jtd=H=IW zZKJ=vepJKHdY0+mFW-zS*ACw`Z;9v>etgyMj7V|!>SMktGs0_CoX*@ip?OxHZLZp( zC6%$?H(Bscd|t9(F-`LFd>MZ@Ty|IpB4+<&+wx+UsIdfj=(YjG_nQ{FopPkO} zjLS;&8hE=6OtO>|*QQRK>G0ZR$-nMn2d{1@nzsAMy-F3fjCG9=bLTM*Tl931Z*m8^yy~*EPML8`NAEW_#S=?%-~Z$bzC&3SEJ+CGsCdQ zt}CuI-i(+rQPJ_OUjDyKC6A_M0tFNJeb-H9sT7s+nl#a4#fsJUJC@~IpK(6<+G;ZE zi@fbI>;1BJWqmHokeQ+~Q?M(&@zifFvoG2oj^EYz{OjS=w+8c>bvWuYef7Up?(J)s zFF5)6&iT%g>1p>TU8#_{{k={!EX{MM_Y(&1g9?C@8Y^<9gT zke6V>v=rB-g_Sq|PE=EH+w;6q?Cj~?>6bm`>WQEI-%t`-u&I=BU-aoyOeri+zn;~I z3vb?evYNZ&g2jsi%nO!YZz$?uyBUlR6%Mwg`OtDbYwq zIa2Gk?V6>ToFQS?6Qgh6Yd@s3ULrZi_rNtK31-PnMc(t0>|0op169=YH^1Dv&@e`d zLrE(9=cmIhS%Kf3nzpRe__%0}nBo!1g%M)*O!wlqmK`k-250yGb=Y=R{L1572gSPA za(}oyY2&S1w^-Q!7Ibc35dGR-l&58#l6~I2UA_MHIu#9cxrzg8v zdAIG}{kwhjRaS}PaatTTt2U+=6nwamrJPt%@k3u`f4(Y*LvV0#gUX(bQ63M}>+-j6 z-~N1GQdHa8N9WF+n=TQ`E+4M+?Afz6{g@peiXTPHY-bH?k+CXS(OUR$de6G~N0K%c zp4(ir;`2|Hb^Io5yE+3jO4co3{`|UopUlMRES9>qMXPd@Ty9O3x2xH)aP8W)8`rH{ z*LD91+mFJf)4A34{``pTIBU!pEN>ZmD#;^5?9k4gooCOT^Xu>Jz5D*vD=Dk0FE0eF zC$m4>wtL^4ylr8Xy^WBDbP0l+cu!Y+%m7AbTB4 zgS&x&fuRB1;67}ZcpB7prbdIi0ja?avVp;)kENV}LDmx5;Ev@ihz+m&cYpGidz0sV zJKOz8RKIYK(s54?Gu{#}Yh95njwy}Xwp@Ox;-g-o!LlsTDkbA(fP$u`2!}UE zf4x`e<6M2lc&2?UHJm?$KBzwMJ}|ujuRwvWw?cr%RBsibqZ-~4c1-b3_7)ZqGMz3> zJGP&z%|Cr^_3wFG%yca8e46>rG?Aln>paJk58oM7=V!VF_FKF%?BV$3_#;9=;6d%Z zxuOAn+)NwRJGZTV5Pn#{bw2ZbhIqz!F8L;XqkGdX#tE*OQ|q_S$jI`H-Rmir|8G@L zi$AGodB!gK-M5+71AhosG}$rKbN;jWv-0A_i_HfU3e?|~J8JeloE1EMRf3IpQ9?q3 zT-3qc6F;1PIQ!vhTjQPEgab6o4%+kn-yhKCymLj~HBpAOf2%Tnx-WeC-nliaIB;dQ z0>9Sb#~=Q)y55+0_E(B8qm`AFR#tYlJ>!G-POPgoMRsP-D7Tk7-#p>ren-I@sVR3- zT^ZZj+OAETGNp!hY3%fmLi_n74|Tn3X1c@qKWN$N`Gu?wiT9hG>^R=f{(WZARq_7g z@3r*w{`slh+_gM3^Ruj^fteo6G_7lHu&pe@|%++AGUvk+?)-QqouILG`@HxnQ_YH&8o%z$I zP5YO|bxCva<#$YcvQ{CSAND_OwqLr&*o`4_dfVZL?C0m%o<6#HOUx}UgYtKG{1-Xo z`>YnJOFA#qa7NtR#H8j}@u4LwnWc<#=gvL->eZ__R)4*Rzx2Kfb$tEuC1=H|RktQ( zUCWGYICbh&zhbidpOB^V>vk2GNbR+jHPh5S@bU3+@dfYq+s-}ncgss}hSe?aGqSUz zCu%L9BD!GX#zN&^iuIl5p(oQdWDPnNa97l?dJ`1Xa65nhUJmcQ@4ZZe?PJqa8%!I^ zZ9W`ew()*=bEyf(ovqo|4~c4r?O}WS`FH(!rSo1*5#HXXccq@5_V(K;TUBm@gto_|uzHr%qMOdcLYG*Y^0+mv{d+C);vXK0jrh z8us+i>Rom(CS6~^#9zJE#fz)q;(wv_lA=O?zfC@Wx>%Rr`+q@yLS(4iue-~i_D}oU zyJq(9CnEL_!Hv2xG{`BPC`V$X4=8Ld;EzyWj zV%||-^Su7Fe|e4PuO#vHyQ(%Gn89~0ZcE0*=Zy=}Su1|uz8}|h=G8hgP1*e$+~w~5 z$-H^-mo{saf(J|6SAa($3detK~!&Jad-+6>uf{psn_S-0gS8 z9Mhi08J`mWo1vt)UZ&DV?(g69r+1c44vIPA*3`VqX7%cK7F-wlRb3S`+0Qz!|F`%jvcO6mNY-+eOH`@`?;{iGZuK6mLO?my~5@$v5^#m{pE1P2Qr zI z+ViKs*Vnm4?f$0yIrreT70gks|07nHfB&6)z&L-gz>Zr5S=B2vl#VZmh_C$lc+<-p zmsg4!?Ow?wF{k?2(ao#}EKSlLSuW1Gx;KACbli>q8{XLbmHu+qHLJ^9&c&V8M{osa zm>|y)u3~PR#ie`v8miR}?oV1H&BOEkB>(n$PrmXulX_R%DTz*;l@of_I6i{${Ir{! znG%k!Owc*d`MLUv#(vq;yXM@ByrGlO64IKPb9Hau;vC-v3q6;aYRZ0?*`(KD+RPx< zZ#Mr2>#kX1mGz(PPhL8<)yio9e#VIO^Qs^2O!BpVY&rM&q*z79=ig=pJN;k?)8Bp{(+^@F3b`h_c*kAS+*h&f`lJMxTUCB1!sB*D&%60M*0glX zmhj70H?uBaT9TAyq$+am?fk=E-d)(a_S7BmgKx48bT6Da$?%RLt4O^edE>fkZ~4r( z^eyUXoVO+>##rKL(#6NS_pRI2vOngxVdk0#v)}(LtT0sn9oxWozu0Z20O#{>a~_rQ zm#hlh$LeypT_&V?YeJUkg`Yw$vU7b-S1c9r%T{k)`h2Q?+g2sBM4oi1t!V-iW0#vJ zGE_y}+y8BG(c?SzDo;Zn+C(?hT*>12{Qkf7m*ksOk0hto76zVVaWD-K(`hzaA-RZg zOF2jIg1%O5CiAsk=R%GNW-#yYT74;dX@FNlx}HpIOH$q6r~apJPurs=ef^H&M4|KE zpUsZXiRF%CwAfxRR{Z#%^N|%cyWg`HElJ+~=jz${Q+}477O7nL?@6hF`P6`I^K1BK zTYNvYvYY*^L%(f5+lhZ!H%@da{!rC>HedKa!PC$szfT6rxn@^RIK;ZIFSt>QxqQwu zYq#@4MQ0y;*m?KU&-C|G#G~q7spub>-L!?_&gA-qPpdcmKI6SAR5baP>+%Lu39AFE z)BmQ_uU)X!=tW6e<6c&K)5DNkPZ;uRrkJB-7n&rdzk_^__>#rFA= znRBh5G@Jk595Pkxo?g{K%>y4l?@n~uuPb|FTI&9+yAnURw0NZ(1OM}!IKpW7_)tiP zbxzMTAGO7y55pPeD)ckQOl{uNWBxJ1@PJ!L=boAunU=RU`rFmKUY_E5SvMu#U|qs4 zhP;FOik_r@nwia5D z;gG7y^V1yEZtZ@o!+-Ypk=rFZ+PajBmi?ab=lSOU8vVJyclGp$9T9&NU^KC514HcJ z+*McQ?Y8^o-Yu$|`-Y=c$Yec>qouIh%+(QWee4dc+C_8Q_KVpZJ+wr5E|>X&*A}j8 z9LgW9TObm`cl7?VgXxdE3*{Qwed@M6J-y2N$L+8Gl{T+>W_k0;>$F8nieo1I&D}pW zK2djmarO3-+mBv2pb^12kvHsn)D6$Xia91Snx^{R>vuBFUs52Xbnb0v)amf!)+U~X z?@E>g-n+nZLA9V^$Kxm7MW0qSf4ZLkYssBmFZ=~&+fO%VeQ@WWIeWdvZNGm(SLX%q zNmh3TyAYUl6fcioG5TxY3*(5w))WB1-n0QbeOf8De@zuceoUL z!KIrOad%BB(xw+|+#f7g@lg2ErERfx1(#!kq)wgJ+`UKRz|41tvn;=dTxZ<6==ijW zZ+OB)<}b&&$Ty^j?(G|>wi%c4LE|xEOHka3Yp;o8g@(fNTxP-fuxpa%6IQ8z~qf1Y5)%@Qc&%$39XtrJNIoAQ**FQ_OH?-9KTw`h=(jvNE zaD%YZ!)!;*sJ7MV!YdYn9Y9fczojET)(G}pG*~beop7* zyy!F9v-0*W@|iR#<;}-W8Yw#6ojqcS|6}-n9BX#)V(sC&CSd8bt&qjMZJ2~e9ljOmD`)&s|l!$bRE!%r}p;ntw=*D#hQ;)T1tC{_k&=30T z`CsW?)037@ZD+Rn_iaD__>pgvbo% zc^0pTRN7nU*(3d5GGWSto);bMztUS*9xj!5sd?>WhN-@8Nl-JFz1Cd6ltmINGIble zt`&uLn)EuSKa#2PUTisQ&z+yMJDzZ#3p;AvzI5jk-Jp;u(?3@HJNM1>Z2mOXhCQ7);& ze)ROEjIv!ko_~CXp#7^E(^eriCb%dGspXBAH$_pNi2TefVf@pJDj3q7lD z`=5Ffwr>ZgB0KXbp)KeAJfef0y4<=8c&rtiZ|L?UFLdd?3Q&$G(S4<~a%+d)U=+)8x`8IcA^6 zr86=(B-c+l+VJ#4xL}CfW5I>NGgfQgW1M5MWP7-Ca^)?H-)q)Q$>D$XQR~3*|4)93 zHRkX8y7uv;Cyy21a|M`IFLZgG?y2-LnTN$zX0Z^9st2Q#_@RAOBGIc{H*j$!{1cM2 z`0KOb%U-^#`}rQQOp3dholwf8cFE52-m-C?LEtdX&Q7O<-&g|QJ`TU)t1oMJN-+#q4@NN!ox_6;% zbE$=^X$Sv;BLzMM8gnK%{;50}^k#MOv2ut9e@?PWe02}hUQZSeK?c9_4Qz2WtF)+wnS>^}wD z8$!g~m+pzESI%r=RY*uk+50Hd(KO+fMd?OK(G9L(lYmzjmnxh28DAc_1RbZmG`-hB>llj_N43++>=6^W06>A?V8uR=*orrs{`$huRBp~l_z2&m2P@+($m(@ z;$=444J%h%-monAXI;=V@8YH}q zB`m^6X@*9NmdQKLZC||%eda7uT-*74>VeC%cDVm|^E`C_`Tnk|2@g)3*r1cY&-K(P z?&r#{r%Nu6lQZII<$Js#bJgFck-i<7F6I|@J-#k{XuXKTg`G(|D(W5Byo7}gw_j>A z2!65J!$Z>VR?CK}io^xd_gVW^ww>Cf&A*JRAkKG2DAzOhrGabbg+%mB@{2KC&L4AV z>Vf%(uP**#QL8)UqWx=+=Nq}aA~+lRZ>8V6dTwu=*XmcP8c!1 z_nc3sYt?xy+E+L&X8$S$JN0C(M?0mW7^i%Y5u3SNQ&V^PtxEHlLbHOMcUG{JEo=Cz ztijCIc_YzBBlf6Ya^})A9oG`=8RyqNwrE}}cjj1S%VTY6shTvF3Cz|!KXSCLRkO@r zv*Snc;aRhn?AbJ{pY?Ij3a*w(s@x7MSKM@9+a<2Wyu7RQZIyGUSG1Vc)McmKH2J5$ zENqli36ivMkY@YgWf>vR)N5O9$?^WC-=u>T$MuTe{@UKluhXD@o{PcpQtH|lTNoyu zx|MmJZJCyb@EV5Q>{=|R&&nR1Df;zNKpZpodV4d+2QTMV6?R=;`29_&xk0E}@R7@Z zuA7}{V2vF&SlB>1b~d&op?_cuyer&p(@ zg*Hn)?0>MPG5H>+-SUdriN6f@o@rsa@F#x%E4P(v*7@os-J5SKZN83W)6$e=v4o|D z$uFZUH$NBsJ>%L`?e?XAmAt>PrW#C|@X&+dHz%iM>=pIqt%rkx!JbgjNyxtHa>#ZI~ zjxM^iZ~@QU{?iMC=N7GG=UC5`eAPf<>+U9tMRJqg{ZoAN$Z6-y)L%DO6&3gd#)kL* z{O>D#DwKm!Gt_u?vjdkZ&xh=1I%hr16Ix}CJi736ALGrUA0~DdM;#dr?#WGlVCM7b zF=wY+_tJ%CN)tEyd$+)M`n##m>R+BpiwOY-uS!neopSu)=1bNVE&M)71qW1_XH`_)+3cknv0~-=4v*B8f7LEdt@PJ7 z3*7K*{?qSwSbf%CdKr9GKZ4UcK=p9*U(J4Vj*|ME2)-?4UdNBx%_!Sf?C?arJZ$ai zQ%)7_`#Ii!I9^cJ|9I}nm&>`H?sH4^ox3Kc`gF*WnH~O1HU+=F$;89Q7vvWfHdEur z5*ww^(nG~eot}PNzOxR9dK_qC*u8gk_%=Bk-RBH;^OtukuXW5^;UT5vwXv+Bd4=Y+ zY1#j~^CNUiBN)%LdLK2Z4r9oWyykIrWBZ!xo$9T7_8FfSlRIwlRUzu{y2WpWq@^yZ z-A~Tsw9@s+t&CW;SW$yNKIr2!i@0+SZ`t$kO}E%EQx`sqJQxtU zw12q6`O#i(lsQ=6NWW~daKi^^9vTa*YxlMY#-DGxP0)dyK|=KWi|oe-hG?u|p!(Nnmq}qU4bY9bzZmX8b#?z3R$r`*$C5?kS(2 zaqw8M**x6?-TM0^oWl>ZUY@fC&g1OB;o>$jDzH`#tZ%yRA5=d&DZE${gsP!d|pEW3SLan8Sq<%?c#JT7;5UAy`7 zNX7$l^;QKhdba0WTAUZL#jPSu;D-0@);OLoe%|ip|9+V>O%6Z@V4b#_B>ZsR`$(2Tg&sw3vZoe zh4XU$-c_?Kb^LBuC6IPI(dy1+|IHCQ*DzF)cg%$`(byJb^0+D7p|nro*izV6iO*FT=$Sw3Hb zdCH8rZ#`Rmy|^}{o}OlVdb<95XVv0wRo%y79SWxl*-{1AVJu|ITmgn*>;E(JT(I8zZ0e-BuU@@+Wm*32&gMAd&o4JC zGnSmxXZW)&WXJL)3};?ExBvh1vVpCQ#nat3UJMdEiMh`>MGk&;6X>2BB%W|->elVs z>$h**_;GK_lihzj7@GG#fA#9sz4kkWXHM=GT9UT7!gy&S*VN^Yetdj<|A^L<_n#CRFE981FCpJ%Wa&Zzn;=wUo)rn(rclKGT~LfzrFqL^vv|bWBUy= z?51fJABg@L)GvDI{12}QvAGAPy|10{m8as=o&=fYlYbP;JHrJzwq{|Myk6`8?a|Z<_m~XY4z4>CWSV+9C47)^-oEqyfkx*0JKpd6{g25gnjxP5 z{C>^ZpPcwF#a(=QXwHW-jP7fGlud7Wu=K(s*{Z`TO1f|NZVys()(pBI$$8qg?UIO=Vv+r!%Q8n6v%Tl#i#< zwb(_KH|5;ibm?ff`1GV=&7Q|;iE~U3m{0ZYS|IXcmy|#AqR-Ond3dxu(jA_EZxdpe zrJuWMx>;3qb^C^1IqSklM;QD+JjzjJRJ%}fzG-!(&84I1zvirY_cVwxK>g_q^}V@I zzn#)MeC*>a)3xmHYb>JGuX#tZ#$<}>eAu)6eCFQB6;6fQbiPGtJUvxnsdM1ltjvFQ z_QXy`s(3#5&uGAEDVmvhTpx8>*U*@O!Q&w2v@RoPB9{R?ts7IcA`Qf&!n7_U17cbi zWCH_3`YS0<(4tLG$U4ee&N;H~mFatTzV|kk^1SA;DKC0js;9*58*H1oXSAx$77)62 zlAl9ojpv+F4}*s%tQ<}_a@-W)b3T+RT$8}3E|Bw~(NN;Cw_Qr?w3NHkOS9dMXPo={ zfAy=^>)w687c=Yn=e|`l>q7s=hW;(BjlX{Ns@Km4rb8bZyp;0r-F1XX)WXIlrg@S1 zql|+Wk9>LXAi=w`((-btMaTN1fxU|_$%%HHE|~JTp!B3k;><%48J^BN4~H>4HPq*9 z;qE;kvD-wi#^~~yY6G=`6Ng;bogPn%&xi>-RIwxRfNkNWGn2PWVh>!BD%bP-?Zx&V zJu*AmZFg)}FDMBQ3oDy%SNqE)Hui33h*qhTiK=`|>a-T40Ktqi#f3VuQc}AXtXY$@ z(RP044$&WMA$(F*CttE%k9o6DT)cwoc%ck?!T;|M{^h$>Ew(Lta8EegXyFTOOP*bu z_V2Hk-19$l$LjURHZmQTk*Qhq`+=!~RD$*+hF7PnEE?naczFISTD0g<~Cetz5V6-jvlolO{!9;CpalqOz~9uI~He4(ECUZd7_| zo_+qQuP5X$%vzEl%P{NroBQ=b$B(wIs)>Jh=sLrzhjB9X%(b2l8;hf&`N{+)-Xq7EP`0^2feByw3e}(au{s@0s^xON#s!kCBzc{S~*PVC8)#i;52khqq*2-uGnopVol6HIlB(6&bcInup61%e2hb2>$;R8nQFbtrws4*{eJgp z+qP}d3pQNu{VQa%^t!{pY=+XR$l~J9+kZBkUViD>RS5>MpgTR5SN5M$XZ^XUg4bsm zn}O>O-YXx!J1xtZW;FBBab4~FCPNMK?T_}KK~MG+ zFjRkgGqX2bXMamtQ0hL7W77|3D}+m}Shecc?&RZrYtKym^YO%4cJ>Lg>}r3dC;a(m zdhg>MOaJH%QVy~CSvfg>4kh>79$Wi-nTgL&35I3v_u4Ek?OxcbsegLPJXj>h9*4yJn6i!G+Pc`=T`wp4J2Kw;v3!%*k3Gv>m|t+!q+DMY8~iKj_7tlI z*KV=Dg0q~xm%IG73tr!-d5z&!o%OE$@^eom`OIX#u_^U*&YFq;9O%U<5~qL<~u z%S%iD@|;w$OF5}i`$0;BgIC6`=Erv{E30=tn>KiEW7x9!;O)9Q=Wa{aPb$CpJXApH zz$=Hkt6gQ2ZZunZvM;#sC*L9X`K_iM()%XFF>_`!G&hFV|Nnje`HhIAyA3L`2R_`s z|L@z=3eh6ZLsegIR`QCpux2i)04va7cLS@=S) zJthSVXT{?x9wx1Bo72y%a!vlY*YcwU3|Cd_>OVjF^qcM6iut+wf^GNxb+0(XvpV*; z*{c7$o_r52b&ZXivi=J9#ap*_@mT#R6sqz#!S&Z&pyQk(dT!b(O;?Oz*Rh_ zyQoqz?%NBgh|RB;zl!evzqIaA__XlVQ}Pm)g``&Y-l~1$`t|Jb{4oDFJIYHO=P50h zI^gi&=Lv<0*NTMr8cwY`mbap~XqBnH&1yEkxwZ@IKHPbJg+G3W>sP)x*Zk}H3{PqZ z2aBdoeWkOD%|52Qd)M!q=U2_!e0z$sYWtH?n=$khA;St?b#kW|p!XXSOmYJPgjSI4mvo@M=`?8gu=77Y{tR(D3W% zwfC#6=lzl{eeopEA6J|zBU{&c zCT^bprATZWV@|KM+%+3#=jK=ceD4QY8zlUYK7D`vt=E6W_}0xUp86~&(%YL`e#WlE zX{&2A%&+S*#Fp*(f4{{gY4z-qWvah6KRl59%8ch(uH6rlS)XNIJhs>`T+!s*b3cKn z^xTQ4&(-pRQ~Hb7F!Hc$Vx91Lf(y69!_OypS9Ek*%$}32 z<8SNX0xI4txwN~TSa1FmyzRBHRRQLLk^p!__n!oqC zR-AO5bZCZTuG(|mFLTll{BHc`W5F4#VxFO;=lty3`>4;`Uwt^a_hs?#Z2`wR*8cn? z*`&~>Yr$+fLqL7y2H6LfyrX28TQ_+ZaHRhHC0xI#6${FrsCsGv9R-RqsTcK4J+kIcw$5p!rfnANiI z;(ou&_m^^B>a+0l@(FZ0zKMsI_aNh)Wo0tA7u?(Rhs|{FLhT@)dLujD1<~%aJMY~6 z=qVvxDx}*WvTSM5+l*snTUou1e*d<=RsBtvg#GiU4+9QsH@ZqKHhCtoWab5RHYYa2 zh=rW3Eh{HjXEkE?lH03<( zb8z<5{+%xK3@#jq+_U3z@t*DfublgB(sN8dO`R>{9IMoZS1l4Y{&%*xwry&fvhd3O zs3qFwzD*sSN2<;;#Hsw+ZaP2gcHf%d{Cyv6S8dzmc=^ZtjoA!l&bOB=FV8=8gSBU( z(RDrt`&A!GPL$du@40hG@$sF1wKHztU%ULfPj-NhLDrvVvzAQb=#9>2arc^AHt$}b zmG?^nkDw&32{t@B2QpsTXnQA~W%ws|^BZ#sr`gK;k%fzA-F~&7$^UP^vdX6mK8v^y z{99Pg@OZ~(|M!17cggBMdSkoKQsRa1VsG)sNn1*@W|y4}>WMkL=A-nU)7h%gf(vKv zUYTK~G+oVqp_;Jn8m`|w1!oWF?*BER>d8|1jO7#J>iADCVcfzXwDGJ8&*gGPqq`4z zHcZa2vfuF0?9Jux+K5}bpAm3{NHRBSuE+mv{q^xz{)&GG_ch#8 zU*^W#X)|*#^NE_`{sRx*9}lj7y)gakoqc&4t!-Lr>V``rHW&F^sP#%;Wa6W!rWdaw z^-^)}6IbT`OD?A#8h99a`Dd|cH~$vq;pJcXzlFKt7S>XESDHXZ%^ey75sa zZ4X&f#TnwaPAGfUaW;>Zuvr51f1JO(#SnWZ^IryLJ0iz4p*g3?Kc zrUXc2SqkqI+TC~jfDETbiNo_HOr1%8m=abV5xlWOsQd0N!w)~ts@t{xWuJds``Go> zyH>fxH5GeJo$vWVYoeHgQX41xgcBWUb@xtf2>IQYf0enm`lDT~tEIGSO}@Qt4-0qU zHHJXnBkMFg&jh_tDJvF~O4F+lW|!y{nDemoyo}Y{pBnx7Hh7?Y~-XTITB| z?U50XsFinSron=Zf39qvv7+@c;YaJD1Qg$VXYljP6h;*9ncFDh0@)HDs-Qy&*(Z zTr^Z%VPf~m?OL07|3>wFl0SAuJbp@|tN3C=Glg`W%8aBkn+tJ$ji=W;ix%uKI4m+% zyMlM>$t7|Tw0KNQ)UT?JEbL?cfAv> zUpDRhu0vCwT%Nq5(d8PWPf{hflgRUL^&6jBzuKMsFYcphoYa14Bgy>|3dx4X;$k19 zn;MQC;9dG}X~FTkQaz93EYcEMJ>Q(_dzP!w;x|2dK8I+E-&tUP87F4=g z!0EFf#_deLaLvn!_Tk@czKgDrw2i(^Po<`f(FaK_E>TLN_NvFw% z=~5Z>OD@e0p2Ny8UE8N!vpuBSwCCErOP_gM*d-gZrz{KB^GeQL>9T^^SR$Iuu&BMS zLU8$GABzJl>Ki{yP5zX@@k4&!2h%e`dluCFzcl;9$4j48qvidZ*lM^Fgr;aHU3>p~ z>8`(>{mEy47ynwRue`Idb75OI?~7CGB(=;Mrrk=m>+fy*)E`n?s$`x1+DPPee>ubRiuCV^&S&N)t=7xyIVm2e zBwL_+J0v$**ev(+6fb!ZMuvqPg$r-CYA&2?v1FOfbU*Hv=u|T<$7y< z#(CZ!MHZuD*+{a5|3-RJu-Vd{g$AF~c@XE?Un@IrCk|AW5USMmOqzg+zL)4hG# zr)IS!JNvov`SiYM5PFk$%j>ej!lGi1mAvAGd8d}??(gPZ>BQIQx#s0t&t-R%`x=h$ z^j(}4tnBc=OEFsNVgKWtY3HV9oQ&G$z4|&2&zDEFUFR2hf0*;@Z}`0>w_aK6ZtOp{ zit#Xr+nIZMuHLbb)bH0Xv^-OpYCCuOgc7UpX@1=YBN>(alcFYi z2+o-ET1M%Qpijc-DGG`PQHDv%E{mDxsEX;ydfVyAzhDvXl%1*LB6aSRdf$Du2ftbR z^LNK@=eSpRbN*`YUF*cSVdjm$ zqBA})rvJOVV(Z$BISI3vn$}BXI|dBqzy-0E1h3WG?=B;?Z|y@%H=6a zL1M=@+`aRt=8$rsS^I(1Bka4suR3r0#j*7JEd5pc826_s_^yv+*vC4-)lhGS{KFmc z%(XE$k1tz(=Iw5iN#|00f*NDz_%SCIT#U~S7towD)5ma%*fO2i17ee<#e#wy`2@XA z%XcJt9=Y+@P}Q?B+2W+jX-2=K#_X`a!jJCQn9fzU7WPY-^}8a<0+e=~Qn z&_Tm?g)LDjJnl0%j_pm%PWXFI{mT76KlWy9{A+OFhf5mo4>5 z_OEB=S#O`;%9;=?dN_W8-yb!*;Ly;^^@@!gr?-+Yx5Ov<%>&3^gL^QlRXUabPx)H{C! zC#f9{%UdY6$5fuX?5UN6;yI=83yJR(7f!M6Sjy0|)W__aiAQ#2Q}N4_lN$A%9)0lM zd8dYDnwWQ;&~3eg-wsP(y??KI@2~S)x4-<$pU9_ z?JsI`lUGVF6S@^I>$z2>Jfk6S#YCAy4&w7HZb~iqC%X7TM7Z9DY1iF~UY+3dPRdxc z|ByHbQ+mRiZ3&9KZnJ`p6fC~vvQF`g?`KZGr0rjrCR;fFjA8Y%d?IzD>+zWbx&43l z-rDkC@+*6Ioy%79GbP0uH@B2C?D$%hC;$HKXWxB4R_|VXohSJ2rTW8M>w>q1?JAif zJ7ZJa#`I2^9c>F9?RGq;qIPZxbEedy#2u;Ev`X0!MIt8e-mTa26zGI6iA zDr}flKbh|&b92C*$P;Xyo;n4`*4?p)>+yVGHSNQbb{(luNN0O4`%pyEG_2tldamW!ocl0rRh-4 zzrvl@4lvn$zq9P<1tuxob8-7GWbAy{zl>E+D7r*O?1fK?C(kbh|3=F~8SxXG-3x=$ z&K}tCi}4yyPG#lVs`H`p!LhmBNg+!MqbBJ#h<~$N^!!`7j9hJ=eYL5<)w5UZS5__A zHg%5X$~jVAF%gYtXEL_PC~i+H{SkXlWA8*2)s9CwlBS*}x?co$n%VbuKb4iJ?NJk6 zd%)<}vK`wlY}+znWoBt&j#q*Zv-pe4(;8ES_9<-J(QxhTdC%hYZLhkbw$|Mjezm^t zi`>`G;{Wd+oaVP_Nfbl+FaJkw_ZQB)`FXD%_-43qag4+$g9yc??^(Dn@Ww)?53N>3r%NAZEVQQX)|JR z(>8H@IYD5?5f;w4l+**BH&w-^Z9F3L+45$!Q|-^hHIfz|*8JLg`d^6YwJklZTQxGg z4l!=9dT0A>QQoK7;pN7Ax7%;~Eg5p6A;za;>eYj{xUv;4-k+fLgd;KN<>rSDk^-|^ z{v127t7V~xrh&pV16lTqL3y1g#SCuDiIKTD%`Iu3mq3Jym`d`AuE$??9_3tlpvfXk zZJ}|F(^IF-%Xgk<2;-e$%FMnojUl#asc~4L_3t-6u1%VqdJgYz%6*kTU&~kg`}P}$ z`$zKDtPK0{bibXUK(P3s61T%e+C8eGe8SfIlSF2<7aUYH^K@YdDN<$?3WYrzRCJnEtpE;(j;h&azI)x{7+=Qe*ktO_{wsybjK}Yel%K zxxBpFlUig%6sGvJT~TaXX~5ws(SB(W+k+FV)f!fF?WU+MRoweDz31>!!9=FZONFJ> z#dbBs7wGiI2qg&CiJJ@cKAl{|^>XKtH&W;F;ugHh_Dpo<;%hKZjr+|N8h*2b%k-aDl7-Ivz8KAgdlo!;6(w+}GVA81#HOIum^`k` z&-#y;GROiw^vjThcQ%n;6W-mQbP-wT4sZ&?mc+)QqLr1R^FP>!! zVwfh+u+TPHz-c5|pA^Bn(ru5)mZWp5kDZTMAfx)2A!ynEJTLxzb5^V)lv5V5=D#-?Vug`zEAFs`Nb(;wXN? zuo+;_P1tK3RW#-oPVK3T-D zcd{|DSuve``k@#5SFA3PVOUoY_*?60#I=sLxBy;Pe#zwNr*1f;9ljPIApJ|Dw=GlV zp_5=*y22spLo>TBaWCz5yd`;Eq)S0uYC0?1CKI2AX_tf|S7@dlcyVDuc)I*Co2fpJ zEG91Zl(D+B+hUPRo8PVCg(p+KWLkWh+5UxVU3}LB3MLmki7M3F?b&XkGST>8q?v5% zolDudsHwLw;*lhAemP z3`MTA&r>X>dC$>T>ffT|oy2Pta?tch%Dl^&mYZU%SA?D4=k_r6L$Oe3#+k*l92dr3 z$atC?y~U|iHakey{mKfz16S;$Kdwnwx#roGqpfXezLNuwl(Z^oIjdN1nz8XG`=zB~ zoDES@Mba4^#gQ?2lY=z^doooP^JnO6o~GWWS;#ZpjoGTvv+~i#>@S6LrYJ6WXnOq9 zv~1~epU3wNS4fqauI$#|Uv%e9>$!j;z6I-Y+p^|a#?SqJX~8Pn-r4hyJ}h?R*ghpm z-FnT0Ns9y(bN@LCA8Ovh*80I9b#;$K>$9?r9?H?oh4rjrXZ~l!i*)msw%LM#0*yNlFv1U9jjqYRHxG z#q-Vahtq>Rbbhk_aa!%l7Sia@$Z}mFl}kfy#)3WrkttP49=xT=U;dxU{?hKIrm;5R zrq?;f4}PUO5erYfeii!oW1?xcc~7;7?gh>%$?lSDxl-H0W@#As_Fi@Q<-WnDbD8e$ zw)V_1R(yr<^EFHL6-D$ZW2( zRxatO$Su##O6&QDzrK+U`g+zk>u74YZ`TbcVI!}r`ddy*MpwSRoxXCJz-)CI3gzUXzu{_?;1d$pIo@?T`vw}@A&-j!>8 z->Yj4YgcSjYWaG_Fe2BvIdK*@oqpe-siS0V!9H=P*5vp(_m2k7o1%JX{!*QZe~uS5Iog*>`2JshcCWmY zRLJz0e_4NDe%!c0T>7j==)N0z=6mM7EMO45x>bTjChX~7uhLxYV9}1vyj*Q_=4dkI zO3Ewe@NW2ZORLA@)yj~pPcOGfurqS#-E(k?NSo8RYk%Rs0H&aA3PP`Knh)KSX*tGq z>4HS>fkzvk$VzDbP%>d;6f&F{IOQ2vrp%I~EY}|`P@9&Ldpw=_=KJs)ToY8+t<4V5 ze#qcuI%N%W#)}ziXIVX8weX=G;@shf##Pxk`vMw2>CUv zZCtR3O*2fL`RabB1=*6TV>vFcbp;2*>8af+Q0|Wh0TWq#ie!&I>Or+=P$2p3@Zja!9=}pw2yPj(*-3 z#`6`zdlWN{Uc7Ks^2^6+f%z_ZJr5sT&0J#mb@g1PMUH#^9DY_`>3VWj;|iu*8V3&) zHmu#h$2~m8&h*EW^>2*b|9W0i6XuXV!nM)y%H+SB+pkXKdt;oxYr-rCe!fGUUmp7& zc6^?y9DMPpiKkZRe9ePx1qZ!kBtlvyf8m*s!09@TF?y$%q{Q?zNyg=>pN@M8*XdrF zX47_A!~6Ls&!4L2I*Na2xSx8tEOy4Z>?QKIK6o4adR~}Zz;AhUIa}cdfjv{-H3lnX zWxamD5V!ce*VlI5rIUDj=N5+~r%#tEiW6M6@$-ad``Hfi;%K@o5C(g8V zA1GLqd2o8CdY|`VL1uAFKcDsm&TKb>$~t);YU~fOob`3(@`A|*Gge14rYO$oYczZ> zKDU9FIpg1XyL!c43>D@}TAbHNO`oQA>cf?H5e1!6$2>knFdb()(SIzdxYlxF=X4u_sSxnW17RZ11k^R+LU!hY|7$E-JMcd zmd2&`W;i5ptaIQNiD?j0W_C!)j5*W6l*}b@xaGl?oQdf_kG@d7Z_#^zIg@YygQgn? zIrnxe>u7X8@t+~kuX-%kW9`Ao{|$EdJkHjMS+#1J+uB`^Op_PsrY)LO&fsf&E}Zx6 z^F%WRnIqC~+cy6^&v^J{gp0^~E)My9qi z;_%SNe35|gVaq?W+#C&RA%I7-a zltu6D=Uvn>`+t76as1inn#8i3s!f`e>;YkcD{qO%*gVT?xWZk=o|Yt8c)9b9U&EF+ zd{-Vm7XDSsbR{^O=|Ia$1>YLB#{yoRn&Fq6^q(KO_`*k|TQ)|j_h=^H2k$kksRt!r zI(W^LwL9oJ$8GUV966NOW(oSOW9|9$b@zJ2+Bi|mgN9QN<}F*SVUs#P1e|8djs zbXqG@14=ABfGLXBvOie|BpagHDx9 zqg#COF$>1yWyW3!+zZ5X8Rgb&oD`fPz;Ihwxbxyio|g>i!Yxq=Q9Lq|Ch2Ok9A+#n zd{KJIc6$+r&`d|432rThg;pH%k1P?Dx!k7JBRj`6wJ+mMz&yE+&$nA2i)^<1ZofHu z-mLv!7M=Oz&12>FRdjio1;-7$`ky2fQPlj0{T_WN0f6?_pS53D*exl>|X^Bk3mG(VXlhk)J&Jdg9 zu|W96o#{ck?i(XGYA;XR#T>#S&Sx5SWu?>_X@=5-KWh1lSCzDOb}P77bx2QM)+xOr zRk&_p=MlYLiM`U@J{OMuWZKEsJH@20dwTc_qhmF%J}2nsnyc(>NOm$Um$+OPnss&c zx<&>06^)!L3z=3;{%y~AEhS=Bx_;0d-HbKaOplf(i8pk&MD=Nf_8K-C?cUOG&arPt z@`DEnwUu*g=PQ)3EZoGfSzVFGGCJW-M^mwwo-l7hl%Il0iPpidM>3gOCKqHZiJ7$h ziFa0`r_6(#e~d9Mmrq10TOLs7uHZ)+)-)_mgp83X#eO;@huGal4+WPo{TxeeS=U(+}sdCY23(no|F=r0w{CUs8 zUhYHsM?XjT`#Y>Oy_+}iEIUr+f z_OSc-!K+PnO^Z1+n*}1JPWC5w9)E1%_d&^Pbq90x{$u>Jf1K&@uVk}g`+ng-=id)Y z=U)x~H@(wPT=P0h?aLoq8m0BVrq}dv25&FX4cod_R`?y;j-yMBA4D}S_}OogD5E}O zqx$ED#M;WWWh^nC8veDN%9~v0nu$I2T9e7DZrJFNEv4RfqBK*9L;L0_t|vV%lNpak zPU{I}+~v8p`RIEGJCm|&UUsvVNFOLZbY5}8Py5UtK|Mr(nS|Zv%!$pSS%GAfVtt-u!l%5L;y?w{-ovY(x%|;%HvWIJ( zY@;VEtn_ZZI`#UBYpdM$Bt{5IOh0$r&|qGAP~I%VtV1P>uYP*dKTWutN3f}+?qu{Q z;i7r{8GrVq^}Kt1wMORQ`9$9*>-;ZUrLSCiI^6qXO^)dV=dJD~I$u?o&K#e->K5Bo zv+sxIlrdM_eZysvbY%AJN&Kk=3`-X73{A3Guyt3UzUnmt!@7A29T#71d7yGlBv6Xm zNbp6AeO+@?@j}M-11f#n4K${-=^x8CI{B&l_4U_3HqNWJ$^V!-(M&1A{J;Lr5L2`5 zJs#m*9`6~9?tJwtWB%b(-^V=H;rMCIt9FOBJuxvWW1eN8ccJN8!@cGSZ0DK}9{Z+a z5F{?XaA9MV-NTDp^v+DwH9073Cwz`e;PCUw^+yiKiQRwjcy^8DoA2I|JGi&x|LZne zw6UBau=a)VHioQO%bsqLevo$htjA7WOIfFF4x67=v%EFcT{Vlj#Uv%W=axsMUs|JQ z4(l$9g%>s2{WD5@0;icT5RYOPpTcO-x8?NC4a<(2{8N^A{=oXr&hHy{Nng5laiv~F zVP;gIjly!<^&M5syF?nFz4Q#Not$+z;y}eWju|hO7T7G1sZGrDo|idYF?b8`K@~ zv}kDjXSemsqpPcSmfl)%RNJ*laGvGl<$NKR7QFMBcjb=PrOk|-HVZ36WAqhnPx?GZ zmvL{Og7d#cCzh?Mx*B(T)qa16uUs+57v)L2U(w%UDD}*nUFpxn8;_JsRE|3ymfv{t ze0G9vagTidfo@fE9Qb|GSG^9d`u3AMbM|(hQ1fl8_FcB^ ztTCD7c0RVPEkVL|#;SM6&7Q<~*)&+I@HO6gB%;JWOY1t9&-(1pH1>XVvVRTRuisZ6b%(9G_U)yG_Kpb) z|C;~2KB0vvF@L2gfGzyB}+`lQK1X z>SLSo``aFdwD)r7N(JZoNQAA9|9UHBlh%cpNxO6%ZM9TveXaj1h2FieM*H8CDbj35 z!Wic^*(GFD9T4E|c*NXha!$*l;oPsxRV#NnO6%Fz%{u#{`8DHwK9d6ODKq#S3z{?h zijB{1nZ}eof#=Of&-3nF{8p@wUa%}v_^_9m?@#QOU!OEj>@C>zg)gC{`lx85?hW;X z3K?s*euccC02zZP+n(I1R*=g$6tmx8?K)TA_4$|OH7DvTxMv)_p;|Vv@8ab@nID)X zUYxc#bzZ5#qdvd??0=To-}5FKFWvbg{o*FkRkGR)wI|jwzGv9<+AaA8uSfN(-b0hK zO`@5rqKo!~JSme@iJ!K8->R^)Idj&%(#k%&nSET#T z%nvbVHuJg%&Gk&NoOU2P>gnwNi&tq0vNv!od3}Aw(v!|j-+qd(UiItPo)RYGk5Pq} zIWOCGtnOnwuwMLpMvi;;g2=_3+)Wa@4?LN};_GE~Oc{UfOq+fGk@pfY&tr>^ z=X^CW+rH}Qjo;_nCz?*UvbH$u-U-%I<~gyiORgGaC&raBySP_Hf6D!#rugfgjCKe6 z%d6WDGM>qqwQkjhuRH5D2lT`ywNI>lUT5>w*z9L(-&VG}6F75E>lzhF6hEKODz0<< z_y5hsGrS8szVRjSd@ua;i`O4ykh!n*WJ|&}hsEm;PfOoC+d4Fu zmyILyhecC@EMxDyWAnG3^k=h@TA}n+rS8J76Wf>DcU!dN+pasb@4x4s^9TQMSyr$c zwCz5w&mQ_n*U_x^vu**;f~Pa2N~4RUY_9Wc@V;>KN?$Gi#`1-GX{8^RhI5@buAFhexJ$>cO z&Eln3?BpDo?=ki)ka66oIdlEph6Ozp3gwAe8^pAmcD^={W#qRt6OfjF5WGoB-Z}Gd z*6~$Z#(EABDihkbHn;g5em?KdrfU9bXTzBdPh1~7lfU!bzE!M`e{r_L%9FDr-b(FF z_~~|W-v^=4$l&#DA6gur8{O|(&bwFW_N4j!xhHqeKD_qC?~S6B_U}F^@U2tyNbLN0 z;lYGgFRfReb_c8{QUEkcEbz>LH2+XuNhq0?}RQ(zUWn&a=gl< zTR3obitV}?ZtG|Noz}gX`RZTI(8@B8y?g%8sQP)JaB9}UxR&YHS3T6*_FC}UmzArI zSBL(d=yjEO@q4@5%f*>qw(;$CnfL8`>CL(Fbw7%qimne#wY+!b>d8$HCU^e$ylIn> zojsRb$pYzsz+V5~M}D8KtVw;E!8oVI(SG;ahFiZr8UEc>`0B_6t@;mBw5pedPqn?h z`jq^ECI1Sy{C>zkOG-P`-8hP6YHC_(f?WLUXp?OzKaS4#*4urHRlLYbaJJ_W#vY6H zF%H{T6jn{onqwD!-uQX-#-CsG4xitrZ2M~F+3Kqo-(8;Z>+xfm#&TKa%GB%!4D*gT zG4Z{+Im_XG81Liv8EXQ+UcJ#f;|r_6k1z8#Cv?7=wSNVJ*KB)NpCgP<&Oe^pSK|)J3*;2{6v_8brf@s=#$mL7no;mE#39se&Xvh?s7i1>Olu5 z6*2r=@tAY!%8kYAvfjKn6e9Kf)F<_VwM=hoj-Nh$b#wIhmGg|#53BvR6q)Q69~!vq zmULEV@>)p~pCxI1i_hfzJ3nuGz}eUhB{DJdrbv05Vtf)kcjkw?`p&|elOJ~NdBwJ& zvVL2>!`=;lQ@2)rPc-eAu75LU{u&2$heF-b(5xtD-fxm8*6T6O{y*pU`W0I?OHD3) z7_6->INS3Sqt99`b<6L4uB(M^s~0R;bot25C-xBJtX>~vY^ipD@zAMH zE2R~loVKs|yndC`X_;RE|?uDIOrzgnX@XY68DPx8Ej%0)7ti_^7PLw}jo%j60 z{rPKl{*9SBeJx`jdqKYY0-E#`P zZlIYOk&sxSAABanRzQ!_3@!LQBO9x~n zi{vI1FlfFpZ8!Jp(*JPQq0TnL$X@wen{Ds>;@{Z@+v`6_+`DzSvd;8}`~K-AEDvWf z?g=qD*&Av0|L0ZteT)%3jC_4&#wVk^uZOIYWzbK!5$99&&XwV%i+O`Sr>`5wvv1#9 zihrB@bK*6r+MKqa>e6+dDdAJZb3f1jZoRTSZJlhxy4`>OetP@LY3-?-r|;g{8u9Tq z;~oG1F-CamsxgjH?#7eb;(h?&eyQrvh>rFlkW}%{66AkS$k4jZ40;V z?C*bT^5sAOk}liVQ&M;4t-lZV;^K>(EZJ;QYWva}tpBas^=a$k^oRo@wY9ZL#?K#U zxraNvd$!@wp5WvC^7gGO4=CEaEP7jTC`Q<4>$%89S+kd~y!S3Sxs1hV`+m;+_oega z%?U3T-t(mRRF~T1@BQ2SEBEk5j;E6*eS2wq^-_0uf9sxv%yV-rmH8Bw zU!Tz8xQtKMsziuyfkrx0Mfs`^3wyWi6z^Lptx){?>a2`hDl$K40~1qp86}Q)ZdJ z`yWj5{(9WLICNXhgz7gp3~O2yzfZRJv|!kpdwZL30mGfZeHqN+teu7FyFKi_{=9aN z`!@dyU5S_aQ+EE%xn8`kAoF%xHk(`h$_}fz*e7S}*UWyonXf@h%yjPRUB;i(A2I~( z|8Z2m%0SekKWt0ltv%eKzQNMZc3)0swwQSQ-@nr{F2`@Kj6Znp?eka9HkvND&XXWn zm>c@dl-Ypi>NP{>*ou?d#i3L7Oz3WZy7KI7^I|QH=ig?&=GpT7*Uj^FWo(zF+CMq% zw$yS7t=*h8x7abtyrDU+=F;cR^Xm4|46QcX=PQ4G+3F;E;i?G_U)t^ThO_@wPlvC3 zb}e(KmRsD3qlZ7;*!%rnv`I2U$g7+8?%ms4x$v%x*rH<3l7uebx^EwM_U)hX>|60t zr_9%VThbngy59Sky^rWQZb)Oum)InJ=Dj4SCnUaZS5C?^r2~%= zcM4CRcw#}N(B+0bs)yV87ur2;hKN*jvUgCTYKx4wYqQJKCV~q-reij^zq}_#1ACLb1;S;gx_6wg)hunnCvyWzP^t~Q<^`hC5v<2J`4)4rn_!m6a zE&SEg^}pqx>#bo>d2?@Xb#TzpGh3M-lwM$5vhVZU_iyT6N8jHiKY!7@$ybj0y4o2} zTNjpp_+j()11=^FGyCR&PAAZBobJzG+2H4LWk$uM3YWq;jn-z@5?mj|o;`auUn2OV z&BW^toLLOo4A=D{H$BxvH#CU?|+koq8XVo{^8 zx}Od6VzN)@3$rn@%$*yj5GS zsL6hS+pG51<@xt2zP-8m)^e|s{+;Xv4yFyP4(^6w+t`jSkG+&uBsWQd`GIS}X_>m9 z?M3hJ?VUf@H2d0rr3>uq8P_)kFh~6Ve|g)sZRHGy&N@^)*|T*{3U`CJ@s}&X{(o8S zHn{AO{`C1^_JRbH4W+NIRR=~${y1Ivd48AYDMkZ*tA;D>_J1ClO1!+|(7+$Z9LMa& ze1q}D)ARNJKKDQ9G4qVp)M7u-{V=%x!%^{gn|;O4&s9(O2HO32AdA5`<$LY#+xMUS zHrg`zNYn|>Z44EGznB%{|2z_}ZxNT{yPf@g;&lhlY=-XobN7nR+m<_XCMov_XELgo za-5JpvuDSr4SepRQH=9v8Khb%5X}~3^1Jy>~%SuZ2@OwQtAb+3f&GCsH%pRLE zGvs6#7&yd1X92J(+-0aOjQ@EmJnwn+`5giBd)U0fm?Z?>?|#4U_dY(>58e)2dXN12 zA5t|*m*Hf;KJ&+7z0l}wIXf#K9%?lgc-Va1K{Jb?_W%DEZ{957-QoX#kqq;Lzp2^{ za((ymemt9*tg6e_Wo=<-dznp^|NFgS&j~ zm$~6_l~2<Rs|nNOWQy*TFp`})R3*BIFLSA&kau>XBXeBTB^@l(s~w*U7k`QR^NbCu7*pLdaT z!JDh=>%MOM`T6;NIo8=-d7Uo~ACF_UynbL;>Fa+#Z)LClt7o+6V(|z6U;i!7ThBWf zA~oBrPNYMh)AZ4bs+UWr=d}q}`0ttU?@soD2$P1u|MQccy*@T?p4_*e{*MIMCscf% zZ{=Gg%ka6l*;>p&mFIx=KV>mjn>Ru~w_JDFnDOAs%HY@Q_y7B~zvbinW2PH#+Najm zUwj!-&Uoatp`LZsmlpzd%HN*MzV~HWc+&d=ews`A_p))tGE2O^^_&0y2Y&Me#ye#( z+`s>Rdg5Pl#eUuKm1PV;a)m5UrOoqfnEih~xf&k-SIeUD(La`+n7#eoQOp&W-+wUQ z|MTqs;>*6~w*A~sGwh=oI`@5>?R)U&69y*r4@ZRkOOiXEOb(AJJo@w=XUgtUxi}7~ z1C#%MkS)LSQT=`O{oiu#m6k7#@B2Tc_t){#Y5IH)r(D$!ES_V`JVX2M*U<3Tt($i3 zx>e5Z;%uiU!TX`OV*9_je_d$5@n*WZeee8OhI!9jXPnABki+%h49j$` z;;Idd?pJ?4pMU?y1!w-+f_r-^-#2~$^B<)Yn^N(ei=H67oEa4r$^3S&0dC~lj>q~xqdb&9~JNr4;#{Ru* z%c7WbKHaZ;K3D!=)U@hfzkdC=^4wv*gnjt0jdIV=PL@x-s2Cs35M;NFWk3`{ z^KQ=2E7z}IzgPeN@BY16S698x&dN$-c-DOVz?6)J^ghcGy#t>n~rZt&eRKuO?^D3_Qf%g0B|NmpZC?%1XlM~Y*-t|JI=7W@n z3v)d;7HTjGkk0Sge#V1^2 zIL>MjvBK`>Nq@cC=hpWt@4mUY`F@L@lX0S51@99l<~s+3PhYM7cUb=4hubr5*GpA) zOuWpv;r{vMcE5kiyga{K&Hj`KXX_(|h>U4d*>|%Tc{`{dXg;S{`ncEp9B6m>|NDl~ zAJlpT=kU+qk7J&^h*9T7rKJB)_Knr=>uWwV%g3-UG^vrY;GN$tvCpDqk9pmcoeJ@L zmkK9bZ|z&a#U9O4Bw)9v>gy}B9P@8nN7f(DTRlBV@uJRw+7D6^)7dWUw{P>mWxu)X z?Jf3=Q~$0{{PSDUfoC?`d#QaqU!ImuwqM5}6|BfM&H5cjO!@*@#*9<(-*;W#y8rv$ z_p#fGo}TjF`16sfgu|TvJg02MxGnm{d8Q4$R(_9GhwlCmbcB(_Pb{{#HKkP1c*ks_e(2C)r-s$tt_MZ{kknP~Ez<$0{czsOu-O}q@ z_y7C4etz{qR&kviZSmnud{t`;}hYG zYw}0F+;=WBh( zaOd>|vBnbqj6X#ib{}ZA;4Es^-nDDjE4^JW7VSzsJ zFxw>D`J1t8erx0RBs;m&AFKTjo~Yind$)hmhjr3_f4uXZZ8kSzbJ|%?30wEqnOBsi zf1miZ>A6XPoJpA_iyCtc>-Qtax4JlO3=N9o=fCG6XHrqfu})YZU7=*1+>>QqJc3cn zyp;`(**$4zyYu^~(8oKiJHPv1a&m4i<2NihrZ=b0v0{SGAB8TBKiZ!Anc}4RrFI@E zVOy6xp}3GmZ|w`WgoLjP511$%dvz@7NKa))qxY@e*0w)9LbEq3{g~<%$1>gMk?M)k zF6D$b%Z{E<726}-Kcmt8*;2Nc!~M%M@3b6!Y!Mq5cOyl#OLUJ&bM@V%Cs&$hpDT&$ z+NJd+TdaB7>w@z$(>ji`MKSGPr#R)n)4rENd;N|rS=OW~l&pV)`vdEDDNqwV#dVrDg=mAEf2N)TQ9$++j zfYImyMmL|19$?fsdVo>#iLTKDj7ASI8a=>h^Z=vL1B^xwFd99;X!HQ1(F2S|4=`dI zJ-}%60He_Zj7ASI8a=>h^Z=vL1B^xwFd99;X!HQ1(F2S|4=@@%z-aUUqtOG5Mh`F= zJ-}%60He_Zj7ASI8a=>h^Z=vL1B^xwFd99;X!HQ1(F2S|4=@@%z-aUUqtOG5Mh`F= zJ-}%603)g$iG=R}BNGM&rcI7P?oJaK*hQqIq@|^#M4B0xmAAfp-}r&~{mbnt4GfG- zJKF!V?qX(O5PjSJpY@#>1B1-R_W!IOWsn)4+W)hDLKcz!096BLz_l~)Y5&i-kGX+? zNqx_|=Fd#;_GvURFfvLtD=vy!~e>E zpBWgw{rms_<-Cf5zQ_On|N92w|2^MRU0Il#wf7H%|7&V(RdG&AMEbY?VEO-NCYR-; z#f40UsQ>@}`L;#naZ#biA^d;;f9~DVl@^}%5zPPm@BjaEliNCL=l%Z=vTxO||NsB} zYWUOepYhlK1_tJ*KYssd{LB33_xESb3=HR*uQQyRh}F%1u3q_r>o%@epu<@lye8>A@7Xt$WV{wqX6T`Z5GA|hz7+g{#JkxxA8MGJ} z7&sUh+cOzhKuQ=G7^FZ)dND9BU}Rthi!d@UEMS7kGBGS*MzBFDxqobJWnge%0G$(B z!T6?@eZgG!(r0@XWkqjGi7q{$ zRLN18t=h_S(J5Lr(uHK&N`Tp(Cb2E#dow@Vh%l}gL_kEwe zzyD7^SF`-g_xtAQ|Ehw!R3$jrni>-nMyb&t7&56~!R*F~d4(R$jd^U>)<$oCv0`!G zt?ZAFj-Eb#?ASE@$W19fudR)q-q+KkqsIHMr@vqS-oD!3Myr@NXCx&teJb42-`Cd{ zTs!f$Uy$tm!>THa&+>`ih;W&IQg+d+W2zx{XC~fgc`JJ}^#0bbLCx>Z88|45eb~6T z^{2vt4S#C$7Vpcs8`xfa`*H9A8^;-MzuXiw5B}KxcWWxwzlNDJn9eyb*u40?(1zPD zs`u;GKmTanQ0E&^%l#zeo>%ILfV!T4^W+xqSN*^|^S{lnACLR(z3Q9$?f!f?+%H#j zLa{%nTIb+_DH1m>%3qwq!;)bkvE%bO>+f+Jla6M2y;!}%eRakG%Z^FE6hq>C=bM^H z$U5AveYrWOs7UCftY^LEwl5c1?A@M5G&Lq9q)8rl;-qdlLuAYR_80kI;(qyg?>C#z$Axoky@Gn;GN?!5% zhfS-U#~1CFo15%BK5S%RK6s$Ou)$c8|HAXSUw6Fc&wjbN@mSO9j6(&=b9e53dP;l! zoh@Z=qvj-bq%s-^2sbF+JNf-i@p-OUFXf%x8IA{UvY)u?m(DMnPP^@Ah1!qt4(!H0n@J^rmp(41rU^Y{Pl zTb%Mg$?KQ?^2oo2U)rlJG#{DN>Kt@15a4+u#B9=b$2IxH*AmT#w?qD^-OVnzxheH@ z-W^qC#(CR$j<6)$dpBz(t6TjP_X|FMTc>>aZ=7i}-Ogg}{>`(`EK9Qq02^b$ma*ri zk8smhm0v=&=XSqbHe0Xu)8zR!Q)ipcovLw8oGppTApYACy(1rQB=^hi%(%Ene#zoL z^B38>&ADY?A%2BfiU$-_a>fpm8`VFhJm5}wbNb89j#oP@4W}_CDyT6qZ(97KKUd!B zQoX9`U-Jv{H@!99G_$FE;V_oqU~8MxXd}5}hMs86EIr{l8%thZ%5L9t+WF$%84PDW z-`$aVdRp?!nZL~gX4w1AUw-~`f$|=3&V9G6^Fa8^gr|>Pj+(z{P@ShWWBL8gi zoA_SOeP8ojR43-`NT)EF2T@W?ov{Qq3zmi}dlGCf<* z58C{8KOX#J7XHh`Wpa+;%;LF!d-H3O|A?-yILaUBvEOrk_(k^oY2Bd6Yie{%e-LG* z{lVYF&|>O2jt7BFQZ0DOJwmLDTU!ud9q1xsCJnhRX|4qNFT(jSMTHTbi zV}^>ii@gpz83^#4sb^ca(3q*-TVLBc_3Nvv{OA7{v>P9q!Eokc-BS4v7F}{re>=|- z{cq02XdfZ$Z6LtI!pz;pxXb;)ZWF-@^Sy09zOY{XSb6E@42CnG`WEQ_x%Z{DUR(9w zeYNWqs`-$lq?5w-;dICRrKcCXUv!=CvFVYGXM~sy8E2?2ddOD(?efci<(F6f6F@X1&^0)mb@4FCACiTuhRYLnm_mV#<+f(pSbUE zje!6Uj~R1G+n3Oi`YSiTY&mfI+WL6;{nA_6~%(DH)43|A#3H#f8O#8KH-~37c?_JI?^8uSI zz$3u?Wygc#FP6Q!AGfpU>EDoEZ>H)WYe6yZrr^JzVDJ32hxO{wg=z`noV}a})(IYq-@qqA9dz0>l)f$n7-ccG`Ycla_rg6kNX-lC#*^FW!&{Zan_&tZYTd2Px;dy|DlJ4 z5nMW*;b)UuXw0}@W^zO9ZOKCZ16O`Ldg5;%^zli}6Zw<%S$kNuz|Fav2?_eY-Zt=G zj%JScKKJE?Jzqijp&B2CGA9Ak;!nqU>sN=bKbHAlxNMzJ2h{zMX$kUGBEOYOk{_%- zneW52%%RSQq0C1}{l9enp2Gdk{I*|`r~L81WoQ!xPFEak&$<|;7KdM&+0cJUxoL-` zwHl-KO+iD(7yc9Ee(Ep2zwDnt?Z&zl_9ju_5anQd#>{LIChxQV=%;nqIBTiM&1q-nUtCamSKI?`NCH> z?B*YyclAcogjsn{lRrIdm#-7uoA#;r${v5-&M0t>Ja}M%fkQw0zHT}G8up{vkAE*X zVt2Xlm}Em-n|l8Kzu%N!nogEqw9Mn*-ajoWV8=BkHq7Jsp}b4;gSuwIUU>&~Cg#iS zGr3IE7+NF$1zmVtcbd=U!vX$FFBdQN3Z4QD1-6NdQm4f)`Zmh%`2BA8e7}#!RSvv8 zGJ}Cb(&OI`AGv}9jP(xYfB)`ytkDIIau#N0kYD%~%jf-muylIduImit$+w*QgB9QH z5;!YRKjEFsU#5*R@sl$DuWCIA_WrYJjJxC?$d=e2*sZ}O^XE}c!#u$mw+}o!@!QFs zIYD?qxBk8#2c~L=|C7JPKk?7=7pEoiz)?EGlkG)a!~MnaMYWbcXDsNS_j9i2^v}1x zIn8HCc>ZDK|HpmS@9O*e`s|A1-mm4*1BXFE!gk38-i+1jBQ`$D_p|@|Won8`|G&2m z1>1S~S3GKZYw=cp?qAeX{N%29Siun#6!~W^3A)kr}Fdau=R1Z zRwuxe%UL1A!|998xb=6vmVt05Or-}T&Y;2eeM#N#591`kGx#Kkkp&rj_Z833V^dv*=7=S#i>S@B7;Kx4Y$j z|IWR&lY8m=A4>oGoepqkNuQ{q?Z4RwYajWu8DpH2<8>#pPabk%vk`{HJ(`rStv z_KN*mA-?ZJtG(l?e`yX+CM=9;t7Ll6|4$usMZxvU7yoxHywiPgB{T*z6C3XF{rLR8 z=DGFs$%bbYFDI4TNl8BAZ1`kbc`I}I+J(W^>q8xLz!?hE!PaAY;q$R`{|D~5F7iI2N%s0)} z2s6Cld1LY=e@@-om;0YtWB7CK*1od0x8xVhtvnF$ zcIyitX!!dW8Eh(g>UGDwZ95ZR?4$5UZG}t?`(COfe6;)hrrF&J;fiHv82;KFIBoZR z=XqI2u748`XujrWDu_^Lcu@D~&F1rUTJK)Sw8#n?vO#LTGxBUN<}$GFf5575(WxYL z>omh*GsX+b^*QPB;po9$h`n;9Rbma6^sZM_!`( z!uaIh?{>ehZ&H9|t%QVd$p!KpRWEnn|NCy?-u4^`b%qi&D?qa~9@t&tz)YmnhWm`v3VXYpJgZ3KA+jeW`7?-n!O6ZnmaIIkp#*J{iY- z>!IhlR`+uVHXo+i0fzZs6PXX(GOi11-3V1D3O@>kwk z3ly~(5<9+MynT7*>;I~;wa-~Ib6o!Y*DPVa%{Y7iZo|E=Zp`;TUw4VWVygx_Gxt2kQ<{rchpnwDQgixz z_P}Q&9btwy9B-y@?uz~#V8y&4=fQWM5(SAFYHV`1e-&7jA6CE2YF<0@TzsDY|iACX%>g_V=uZywfp2`ekor9b?kS%ZG3%c`t|?Cw<_L;)?Bfl<^TKgRij_w zZU!!fRz{~Ty}w&h)pW1l?(x}|8>SvVlhcrBDrT_<|ID*LM6Y81tLEVQ z=Y{yG#U;!S0&~?D%kk)#u&Jr5U)nW$|0KVi4`N@Y?b>(V{cp{w_+#r%+O%9%p83bJ zw_k5rHPbpz`TY;(zPxSz$9>j}1Me3c5#rIwVSC}(;2i(!()4$}IzRRpUs%HPi^bvO zby*{~^|>}dts8mS#FUu%?dtr0{aIQ++i(9Pc_F|5kH6Uca}Y|e)w&q=KjBS8TFW^O z0}qMjbJKGBm+HsYAPod5P%@Bdl;+$W3c{ytvO_r){f>Pn^HCa+V| zSu!qoPrv)^Y{_qD^JVh2zh+-D%=U|#u*YqAGvlu9IrddT4;+$`4sY0C=#$Q%7XNwH zbr}cdYjuY=6)%!Mcpyab#;(|TZ~tF$t)Ka>svPRfM;TRV?i$xw6m z7twzUwj2L`T-heNBOxI}V!@qFJ@Z&B8mwCE`R7LSm;7&g$iq;^ zPKrQ)tjcpGd`kk~QD@BIJQtnWRp=f$aC|MgR2f3?Xf zw-qrfFYgI@Hlb^=t{(e?xr{r`e34Vbs&KEr{pGb5TB7p60qf1m5T{zGi}=b4#J?|!_m+7|CIfv4~He~tRv zwGB0U7WFe(Y-2fF@2Awt=cOa0yuVpaNq=lVaR>zW_%%YHt#C47(ZfsGPJ6H^l| zE^_@bHPVOSLwe)>uEsdFN~Q}!_D-8O?_k`Js`|?L_<^-wdj8FPZ9gY(-*+#+KNWTM z%U>>AKj~a#glf@(*yw9Qp33f1+by-8NV2#rR<4jgmGbLcZ@ka;JNp&?-ng}WiE@~& zZFHBffx#S}C$rDgK62mrbFt){ef)=4^1NZF`M}p;!E9i0e{V;Lxa5ol*&oDnY?S9# z9i0DZ;`bjCbswYl|4S8VI-*~;pg&QIKU7(kU4Kz{wy_M`hxywgZD-3boXtD^0?UJ2 zUp8|8EB|$k_rF`Z@}s$o^L}gai!+z7F_<&HI?r^yUWfU{Rmb@ZFZd3WO}WP?Al$(C zN677jb@~Ivs%`)8>-}Wdd-eU{mmk;mpW6K=(l>b1l9DSSNnvl-yF^VmR;%?rX#L$( zc6G*Asgsf<{rz*QZRR@W%lvub`hICS+nq?~MPfWWoXk5;{sG;&6?bLsJo|pfH;)$h zGaT5-=c&AB8i|aGh z1pkGEu+P|3;$W&Ti4tDBjDl*;kS=w5M30+im)i z-SRt4=k9uE`z7Pi$vUEoTf3phnlM{CXcn>BiI&XGd z_3>w2WjTXP$F_B)(LRMKON`Anb*59#;2wf-LY_+NSL&F39VJwXNrJ9uPwt=*&5`j^7-n5-(C7blu)J_21X(_Ya<& zfB*4#>_3U97n3g+E)DOuvJ&ImH%~SsE2i-H_oafHTj%_#d~~2Lc5`oe+_c3fxH_2n z)#BWz>;L;{T|YwMIGsg(_#TzXDAi{iyY5e6}L+_W>E%rX|d2UpD-dT0|U`z*F1EUYa zi(2DC&IGwP+C9!cJ`{a8$gaOU-Q+P(gOJee#-s;gzkbHm__}N@=aMC5C+@~Hr*rtOUN!0duBGW#6KAIypS0IvnVWyv znmKP--J?kdCWo&ztq$a1W9wkF$y+y_;gtMihSCG=Rx746*nG5^_HdU^4AY0*W~Z7D zFxcO&zbyH3OKJVN^Ou!>er|g6a{tq7lD@0eHZW~ZP!*Vbz4hGWe|}5%H`eJ+-Mval zR$$^DV+MW0Unjo*3%C0I>-@!h?wr-m-BAVx3_LoqyF|XvdTGA*^ZF8oT?ZBUPOCP! zShKqS-yvu)Eg?ZcqGH0lxAxudro`9(t}Hn@_ugjz^*4@g2$D@Kmc4uNquZ`yoA2H6 zj!`=?{mo_<=~t_+l5tQPf9YOx#M8>6wt=@c;oy8fhuFO@t(pDe|NEDi zJy`W6`Fw?Ul%)O#oh5FW_lzCh&pv3&@W*ui_k-_L4kXTC_|O-zE$60^6o+`CLa(aC zjQ2b-{^j56pPuUf;Qzf(@#FLNy6RWw7#902U6=7oXRnc`-@lmiRdYHP-qCRl@@kMU z5!Z^&S-j-eF_qAhnqRJ%zu8%l?Z4>kdme>W@4UZhyS_HQ&J_CatmMu?{a2>4lM)i- zj2zxSyuit@?<@0Oo>!_2s&~s6_AV+kRQ;bg<3h&$BB#%_YTGI|zP0nXZ#+n@&$y>y z&Lv>@!ME$qz0PN1j*C0W*>wEA+Z0t>JzcclBYx4z3)6D@jnu*p9;iuE@Moy$_`Cmu zoGJfl=g9^yRUKw9@ce2?ldE%8TJkdK!8`Xn&-lpy`d>0+=Dm$iIseW3(Q%2OON-C3 z9@#!Q+d$ys2Ip3-_A0o{9Hne_O1w{iv42jDDUAsth){Z86RIRsYn^ANMGiySzc= zjOQ)Q!oT&;e_IO_A35-Jd#&-@PkZg3K7UlWc>ey7qxm1*)6RH@bNWu5(dt?>?KBv$QW%X=&-aHj}oeyiFD92fkmnulvn^ zxz)UW>nHK42?=(F4)wd8i#ZcA&M{n2Zut4_<8yB6Rgq?t6%<}Z>Q~h z!G7OGf6s+)Y&)IvrvG`G5ofHjY4w!Qq*W|OiogB4%P={my?=vfN_2*G^t~Cs&Kc$R zzn#si`9vgc$rANd_g0v6yRX@3@^(%5X#>Nmj(d_HR(ju?pSS0G*B52~|4X;{?=g0G z(fxH#VndPd9*&HAZ+dMuT~uwT;JjdSOH1F6opk&c&%%eIeo3#ww2#x z_St1;iYJOSSm`eQsdaDr%Zn#`zvp|{SN>VQborb5JtoJjBxbPlT##gVBK7xBvh{}3 z8)BFw7J4(BePOlXw+fSd0o#|G|NPffm+zW8ull(3-;_NXvWHE-ShZHO1TFWUze>F* zFVLf{H>G;#DXz7(GeS)Fm0gIl4=|U#_xUHA;{m^a2@*$I4(WeT?4I^XY-@5}!XM8uEY{#EKE-gCrQ`J3v)k+ZoNwhmZ?557aEsAo z0Z&nUSv7mxqd!Rx8jrv8OOLND+V$yM{^g50e+~G0MQuB0I32TPxY}EyGTCIMXYi}1 z-f>SR{A{k>cE0^9>*8xIb8}{jycKFZd_8KPTi6P&6}lPc${r+NVt4Z0YPO~5{Z_^N zwf`gAf3xg}K5$p~ww!l-#Q*-6YP(cE%z9jB`gZ>xh4zjl1B2f@7s|^siq0-5cWGC< zrG9vGaN2)uS-IuSjlAXS7K{I`(YZUfeph%-*0a|i7DV=MD>As(E$TV(yUdo!dVYVm zyI*ltc4uV#Gey#2rb~v5?918DIpg@G4LD`ZB5bA#-AXZ(e0P*^~DQbD9`y zxBV}^|9^IgzU_6pdtWDu%=*sJzevqVY`58s!w9F4mb4H~*RogK>glh4kKCy+?jGTZfCM^y(XF>MHqtJlD{_zS`gF??+PZD``+4_juC)w0-!SQo zg|YcU?T&4;nnRb&w9a<8liE0YV!`dp|8DkKty%YZ@`Se&=goO~KA}iq=WdPbs+|i? z9I?t-urxMG=GIXsJw}IgU4ula#f9SE!{?Nq%`EYJptaBO!-d*@CQxN^O+sQBr^6h^ z8C%!;T1c6(>FxeHr{ooD{j9tNPvY%w-=BExi_@;OgL|&7Do{TDZtFIgpPPBKv$r%| zj1pSjX%YGQCfn3+S%=pD3GT`9ye9QFv`mX>ox-|DU2B40Ep1_2{c*|BtTo0?)0qp* z5)Q{sU&254|L>R9()*8mieP2le8S-li$!r|t9PVYhFPu@i_yILbJ`QW^;I`c1y*eA4cZuV`mNr+HBbClOb+yA zC5Nm^&8=@()8*;1S))O^DoVTHuGZFfb$*|cr}pR68r1%p^xrw1d7aPypOX1r+K=Wk zip~$5DarPN=YWBXg|X$~63&E-Uq;69w>MbH^5iVKezorFs{RX`|4RQ@YG+gb^A*Jt%$ zW||t;!2>*o1}4G{i7z?YIxUzDobP`#nEm6{XVx!8v-~gmW^BJ-Z~K2jZ;WyN&CI?g z&3Hw|u`n-99q&>5sH*A(Op}E?4EREwJ7H*8lT{7b|%0ih0ZEU$XeKXr|o- z*TCuXHWW1#S$|dR_?1lnX1`qFhXYWbBV>NfluPG;Qwwu|m8nk^{BbWGj- za_X0J4Y%d@819`GC-Y^EbNs5a8+qB5bvG`Y$-v|5{)p#5N$vK-d=eAVfBSpSKfd{I z|GmVM^0^)R{XE3y27*np7}BT znL3&)rYyOgE9<#MCfPd6V&S#Otyj84CyP!rzVtM>Vb1@EnXfE*)Ev)s`t7%UsqY)Q z=Vtyz(Z-`s*C?)ZekZ

- 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())

RW~C?7%eUpvR_RKd;#P3vgZVId$aVs+bp^|4NJN(hs=& zdUbXArQ})DmtVL2z5UYUZ`E1NU0DVjqItH&|GaBtBtNC?8P5TOy>k-nY7E7)c&6X| z)m!rF`-dF+N%ij^*XGt`y?(%JWc{nOVNwA1?n`g3&)L3u@w1gZW;q9$C;sW49rJ3c z#*qsthy8vRC(F)$v~+sV@o{ z-?R6*rMhaCI8=f0a&P2nk@{8%miwEWJ1=c=CGTXo!BeLr2^6Qb_mnP9qW)x!za z_N%UcTkCsc#rDZfr^F-;{-2wXkhSFG6?@KK3UgF7rTAD&CQX-Pj?8+|bGdiJV%wmV zIk)GfuY5aW<<-VROMLE27X0H~zI^t}xs3mxM(&^27gyUP(OGJ+ft$z2$LEH5o%oe! zswEkEj2CDBET+^{?o6unK z;9DNU#iO?_$f#96a`F2Z^qJ%5_C0hkmf*HoDTbV%-mE#-a)#aPEr;jvpgXIpL0y+7&S->!Ne^9$C@api~dFIY44t;}`Rm3VRZ zfWcfwiDj3c6|yZjFK%|klv(;+jJy7~pAxpW>!sztUSI3sq;zoE68#fP6wROWC@c7# znKOlljWw!jZHLYFOr|q#mTzY>JFa5Au}ST0>8kY;uQ6_JbKI1i-m02&nJ0STk4=+i zPcy08l)1V6*|(t9#0O5zi;6x*eBE$h_L4kLyVF$C4rP#$fV;~ejZbu^=rHL zz4`B|ONyudnJBI!xbtw(#7u6Xg!e{H?rUeAugN|*ZPJ$Dy}mvfCk*m^T_T=unQOS` zV(@pFe79$1pM%fL;`q2~{ka3tvKOatB*fP)xcukk+r0-^RuqJ}?3cBX|Bya;onx(; z%~z!(F&`DXR=jRmkjw0p`s-Xn{m*yvmz|IORDU@+T3(EKBOlu@1Bd&o*R4;K|MEwS z|Ewg#EC!ylrzM|#4^J~#y!-RQ-Y+@--CyW^IDOgTu3ppZLlur{7wpr0&9;PYRO9lL zO>An)Lr#Z`^z>MGar63Z@CJ zukQU0DS0{V+w2RG9d*Aizh1>${Wj>fAWM6^l*dwSrlRM54-cEGi>qyk*j-nga0D_ z%V!RxZm&_UZMw%*E*$&*Y5MPvTKOrgQ4`F5E#;dy{hF5R#C@uZnG9#gyvUw8Wpn-Q zX-zAh?dUo$!~12&3bqxqx>b9EjFQ(Kxc(+|u28)FmJ_9O11m49rJ3qK_ed~iHjQCh z;j?YhhR z!Mm;3$zTD`h40H=uU>VQd8_O!V}>okZOinTzw*vHe(=%Ff5k6u{`1cNY31s|fg4d+z&gX3Y0@f4aXP^4h*F;QE6$6@gX$`Rdh2kHr?22j#7J zRi(N>!nOFd>F-eAulbqb%^zna9iFsvR?3R7D+_X-`m`R2+a9nsTJ`X&d%vFCNj`3J zLgoE|V+Gp9lNmQpO}$|9Yh}~>6UtxqT9)38U;g^tPNluyewZ)+J54qtE0BZDAW~*p zYR06r7$%8n>z{o2^7vWx`X|?a-uw7~_fwAj->&$nYBftwF>S4HeJQrqqvM~|-V-rO z52qw$ZkAZGB)QDw{FsJAe?PDP zpLDC@eCW&lmsA^*uiGp)75xnm5CaFbk8(GaLVholRu_ar2+_z6o~47vqyZ+!c>dtXbeNt%gYn~ zew@S9y=(GMaiysdS&L(fZn%q!_v=1XXZiOpx_-jGlBXv#Uw^AecQ|{mY3u)hWc&t?u9#cuGaZ6d+9^F|1Z5BE%chq=(Tl97w>`37wawGpEJMK zIrojTO{Hnxx^ps0fuT)I??2@jU0ZB%Ley!(Q7)mjuF}x>CSa`ZGQ9Sm@v2!v@Vp=Xs7Y-Q(Zb zEA)SV>@WV8$97*|KHu`;_DhYwWnP&qlHlRdW3FqFxf2)gq>wXVLHT_-_WHNaxG!H{ zXE(EM*Y~oW7tcpdSr~K8&&PGr(}QwrJ#^|`USjB&oPIT8`I1$d(p%0}}dgpngV%z^eukB}FyZhzomfE6h_fO?FyJQOE z9y|#(TFCcr*VGCIrOs80Pqg2;uzKRsdnayAI8iazSE*8|z4K?-@^#UBUIpiROsKrO z?9!@@Um8o!#xZMNeH5SER;uE7a`(=;W*S?=KNM?k^;mgDat^b`y|M_WPw$yD#IIbN z`Dd=*()V9(dA6*q=;2&y;5VJ+-O}&we@>U*^X#7-H?i)`j(ES*;_ojnSR}>6!^eEs zgz>-yiPKC;4fEeFm;bo)<2~&k@7`W~W+ou{NB6RkaoL9xkF;!4p1EARyy(833Wrwq zJOM-LZ?wK=fA1C zpVT)d-cDVf@6hvp^0n%zQ=Z&8WH-0x^)_eLBNr`tB+nZ4Mwc(R%2-mRR&L}_5Sdw2 z)Kur*a8I(}`_z}u{eI0Ysn}!8aOH?m0}szx-v;IrwJm$td2Vd}`+eQ>%bROXS=~PT zK$o-IE9jw^w%0U+$gpJHDUF+1BcfI*`!aP%#GP5iz?z%tH({z{=BhUnHFg``Uv{Wd z-s(wb%Nt$uuJTTYHK(GU#u(?gJ=yi`(yC`Sqn&oz9-Cg)8~)ZIDu{|k@rlOJOb^EO?-Tbfz*|9YMA+|3U^ z+h}gESS-N9!@+#Gge}2Aqu=0rOQZX?JM;GyzRi63Df+ykv2x9m`>FiQ*8I12l$|}% z^N4G^NtV)Ut3_L5E-X%q%aYHrR8I>%cfsr<^C?BoEg`SHc6dz6*%GBFn5sW@j&_pB zvm1>$M>=LT`Ak>3ykli6L+rP!C22E!XQbvW+3`Z}=)D(Lx~o*hE1MSYF<7*C^DRE# zDYb8OwAFSVNo06^PGH7&oz1&GryH5Qs^agt++6>sO68tu;VIVli%%X%t;s)d>gmUG z4EGo}N^yYJO$+SSY+_+HzkKmu`1(H?yB;5u&ioMT;<><=SylC=L*8REo(&Vib_N<| zXnE^C6=5(Jcx@JbbwbvQZA^zYTz0;yziM&xjV;l<5)n}`dZ%@l`sVJMa`fTD%*Jc> zE)jdSwVl`*z{s@vOX%_6jXqx|s+dL^UkjVTccuH0)=KYVTKR=DOe!Lu&YyCHKiMH_ zcgc?x3hVBE@17%eJCbqzlw;>5=hy$0{PK5?+TS=L#0)&r=(Buo%RD z_c@XZ8u$3`c>de}&_kvFT}z%^o%(0tfy`sP4<<}qwXAKD$_`oWD6iGmHnT{dX6eeg zxQJOh7piAuuv(ro;eBopisJ*dQ^T4Fgi1k-yw}c<|@jl|Kth3T8SNG(%163@N zr@d+xoN-*Kdry0>Rn?dC5m8^2ew{eQXKEg|eaF;_3&-C2o&ONKNhVmn};a`;bNesRYAY(wIbbKTnCrf*^1r*(PTYokV!)v0$UiQMd1?YB%R zaMiV*B-w+rr`=@SvuZgJEY#4uDo+2NGDFYXF6-`ze)Tb zRU5C{f8T!DQJXJ+%3>*=GxDHxQvc1><&r_cSK0fI(|^CuuiLe~apx-U9uuw4FY~v2 z;#hllOLMW{OPS}qUe-cgC$FUBP3t|p?ODL}w5z2`t<%+5pYJ)u>w4@eQ(EbfSFR3= z^Ss0K!`3X-^w@uP+AC8|KCh$G7RK^qoGrR_<<-2K$7<)Vih7)u-t+X`m-@~n5|1qA z_HF&(bK&SCgEaNiH`qjEkENttC}fJ6$IyF$-#1aI;HB>KrP1&9+x zlak0deUeG1?bY0~VkMe_+kEHQ#z|G{?Oe3<>55Id41SyBj?XyOcWmK3o4C}s+m44b zZ}@giyx`XRWxUf@emK`qZ}(pI3wLw)lKB7VX3x6*&hZrR>3QR=3*@r)FXgDclu+;q_P^vxEz)K#{5msPZ+*2<~J|2VK{ zpN8g_2P<}NJy5zPVm{X^`E9dYOS&(t?%elOg=y=HMeUQeZW4@|8UME7xb&_Ug~lg~ zgB?5K!rw2SG~uuHA5*4nZzttMv#;Cw_OWARdB0ljwS!#UYDHR`{@1vi&s{gSTWw0y z_r<;wdM{RM3S6G~+TYXsPN{3vavK#+z$#J8b%AGZ=iVuDfZrkB|- zw{YKgJt?pM?PdMq`RP2&YgK329Qxd_=H!&yrAud7r9597R&1;;bXM%jHIvnXH;a_l za;(={bs?T#w@r+F#VRIM-s`jeW+tz@)mGL2iU?p!%(p5)~UuYbQ-V0BomEGl|uUqngy z1GnR@U(Pk~mdDMySN<*f^3Cx2Sy$BdEY)CVEn==Hl`xKx z3MlHle!FS@F1;+%;1{)eG9*zCYQM=VtT<>90k?$Kq@z z_j-`_){9%Hj4C^=TPnWvbbaJ&s zHLuzJO;?P}=(%!r*D}X~|Ff4}n_ybp74=_5}|d7FRyxg>Pw%28jlH(z6R z>L0tAr5jOlAR+$ZrR#m4Q!Vs5uIlYQweQw4-FuP-R#u1J^W3O7Z~Bt={QtRIzCCz; zI7;W>fjI^O(;1$%d@I;ho0R?Q9q-r8e?4S(Z#p6H#>7K5f5B(Y4eZVz^k%9taW7=P zqCKfX+j72FxnXMkfgd63tahdTPw~^a_AA#nx3c=0X1QKm!G$pWMF&kq7hYBMcJ(gq zJa$miV^v`AYWCPW8j(j?BmFjI9B0}awtbVn0mIgiZGP7-p0a&A(RumR%e(R(9`t29 z<95w;@1&nrC3hEDTrRXLuwz&+c+8sl-?!KMC;$69sou*!r}|E&yP?DkTee3$2i_FQ z&NE`J*!KU;YCpg0_bMOy&42q|iI;QPa}8k!*-CH5O0DZ%VcC;~#Y(tuL<%fUn>Cet z$F$W?<5sG;<%)-|Fq|aB673SCJK>+Iiils>mQ>eU8TQ8}oEx%()q>@9N~6xq5pQ{Zou? z*06pYcJ9em#+|%ArL5;T1WQvyHpYH${KBFCQLO#Z<=?`Kn;axLt&Q0duPbTkTDP4! z8%@k9hj+ree+;r*DX__i70{Rkr5dgByGXU+yxcwg3D4 z-QOog_L8ZrnVR-rJ3XoRN$;wkocqFm|4;l&D=X;_x6=2kzTKM=-``EPalH0dPT#0SUGt8AO z+xkD|+_xL?*|Vo+K7I0P^}^N1Quc1VXgEv4f0=%KA=h5FAD_N_KL5Y@%M|A8tFGQ) zV>Uhnny^yd(iRA+4Qf6|zTCq7uV>!d`1AcvC%692e9-QmxasiX)nO&8zun$-s`U2y zy(hdqOK$C&o+Z6)l1S*IK#j?F)VVUBm0lEG81-PSiB(Cg%VrfFMz2@t`BM#Z*KQ4L z-WGAe%zIzQ^nc3r*SObw)mt`wc0hjWTCunTkHxP<$(Fq@SaWb=r`|PN#X~DzU6}UP z$*i^Y?_|E5E_RVMDn?g6ACHRa`K4rOAh*2KXF@^qgVNdh-tYguT7U7f;ru0@j(dz9 zR(8MrVl0r(ux$S24IVr;d!GLL(wpt)IsN`GUoWZP`fc^cpPby#xp6B069zqH&DBv; zrK|d`vOZ=@7ppM7sqn@_NmI4-+A8t+S!>Hzhe@117`!rgqu8{`=Z>bm-T%OQ(xjlP z)d!3Y^d0HWGK*sgb6UeOWt&IaiqhkAeSHcqtCbl?m+QUH=w!?Owx3zvGOBCie!gTW z`zF1%wd;PbG6^-TEcBmQGeg$oS(WtSW2=5u^nO_EZ!^d4-`)LQ(l2rjyq^DicIMT% z>@Akt4j$NIC?MRBx!8zDllfE5fA>qz*z*@H{uf)lEk5_fHl77bk0cv0xE}ei%QPsH_cK60xXOXIFH=9(OQA>j63Vnj;`3UXnNafh9F02gNk&AMe<@+`G>_X`MXCp`TtQ+ogLl{aAn<#g+3WY2_YA4~YeU5#Fz>2L)#Z=PP3i4IxXgu*+&wu0g z!6vsAhx6GteB|NFW`Ldr&|$Q}lr{FBh>Aj!n~AHQYPk4>_6=W~9<17|P`YMf&EM6v zyC-d||El*|q*QO^?{j_DI>#TY3B|ZCj}=OnEQyK~UwJ^WE~oUQ#pH>y?~*U~tx7DC zex(&Gx8+=|^6jtlCU`S#e>j&heZOt(yGQjN(U0ab&OCd=haI$W(<*{T`sF_N{PX9( zt-rlC{rB&{3}+=bulc5i0?7jBnM-U7UO8kR)}H;``h{Xpw86ovQtSm2p9q;1w7V6~ zE>+a+eA%CR|?am6EpJ5OG%>D}f1XxY5NXA4i;A2>Jfm6QAK8&}sEZJztK z&mZg&kH^OTWaxq@q2a~b!)#Z`tR<~88fTyUu(U?dmf9R&?IS|H)hO+ zj5l&$IR#4Y_|^CR(&oSN{`_yAZT_2eJU>-o%fkuoX{#3=Xx{I!i!C9ayE$`i!OE#2 zXXl+h%16hXN$hNTS-a(DUPg6bj&=e&;4jFV}1QO`Q@wM@38ng{rR7$Tj?+B9F7X{yfFgB zUFs<(MxHw#5AeQR9bT)FyX*U^KRNbyx0vvD^PHO{^7(?M(@9>NqUnn-TuYVI^6r-H zeC2WGc8)VvgH!zMm4XdYYzchS8NrH|Bed8EbSa5aQ_Dx!1*Iq?z{kL-eFN-BFo0)Yat=-lO9`om# zUwJh1W!vs+%j0V<$}d|SCa*suf}MHy0f##*JGfI=MVRlW{Q9?S^Ck6be=B!A-M4!A z_nUP}5$vIIp9~&6v=7ZOC~4kua>v#yIrga&Rp-Y{|9*dky5na1;44g;D3r#w&@f?o)B*8adw`mYKW6C1pN0oLX#IDWI6js&(T3 zv9Wj*X>&nusEVsF=)OWV`U;x7!3 zW;y#*{=WHqZyMi?-wDeFjwURyXEtPfao%@}1zX;p-icH89UB91WUxq=Z-mugSEa+#6w+O`$Tn9{jVOiJ3!d_u-^&9hfM zPJ4ZeG72pEZeQIwbL#8Re*N6Ml1LvT-utgta{YFXY1zD^c+RcbqhVk5;aV*+0vq@{$ zjl}q)mJ8CH-?o^`^S$Z1X`0x(vsIZOs*Xax>@?c8p9@z#FM9bl*->I2U=xM>pe);q@IxsVkm(nB6SP?>MSCRYdJj%;94%xAJ5Z&%0G}`}3Ze z!s%Bsw|a8;u3fX%c$w#$wexnG8Z|6f>U{om#QB#cHw|Y-h{!Euz8#k)Y9FeSxGldd zr2ol=grZf^lG|=srO0}p4v$WsD$~5``{4uj@6I3OZJM6*E9};nqr5NE=l|dKWz*vL z#j7^*vh_61V2}_EWB_$^qp#Om#cuhy+GK|B{mp-alehECw|VEhQu4MIt>&EBT>#prrf4tus zK&Ger8k{@2gzI1;6 zhjTvfkC~J2dA;X}czM>n5^~PUKh_U-_djlYeL4Ka%{rTV?bd%@+ST8;QBIW>C_eaV zZfA+Q;)RA&zx2GPE{YWEF>Wo~@nA*ct63GYJ>5#{FX*_ z%q-?yAEFW6xj5r`VIa$`5c3I}%q*%|zFk_k6^j!%O)FY9iKM+&H3A(Qu|lt1Uk1m$4mde@b=NnT))Jl z?|BsT)8h)Y_8UK#9{0)p<;vCbEm_Yt=&-OoQajMPVS4!`cOHXJIsbQGUi&@v@bW)T zBdXN9E?@F-R&aCd>G|eS+CfRb zwVpVfl)f67H;c=_F<9fRh9`g5ns*adObc7}+{=E>m;Emc??@WhsyFwoE@GPHCB(Gs z=+^5$kJfKIr@uGxQkTyn*ZWd4S5M!;y4a((lP_n(nVW8N9~T=vs0h4W=bbulgNek~ zqaQWn3@fugT3Y^IzU$TNX^L6y+YcPvfBAiQj%9w~kLY9Ib=1}8eq86FHGD&t#NIE z@7fzmQ`nRse zUAehT;@B+l^@~$;X7<&%tSt)5NL{OMcsp5I*yp}{{YvKpPnK@=<(DiBy zCVSj|c245e+~}1v{`PFFatqzvR`Xq@>`{w)y_vCTVP3B{gY(|%-PYT4o94aU|8Dck&G+l`cRimNoo@QmdzDK} z-igafY_@qqja{xCjeDjo$P$`@Jht>o`9lr zW=H2mys&D_cF)aGsow4oqJKJ9IpKBvwWBwgZaiMuxT0vuPLErMmS44uGt62zoBvht zvDF;gEaMvPCe^-Be*EO=t$>`j8(i<+m%M!{YyG(e-Ru%Cmu^|zce5zsX68A*u6=uR zFIgqd;9k2qYCX@kEBhU#f8TkRqN{%)+c0+9u5XPGmjxUMX4-E5;GX1$uNyTpSW|9* z4$e8e&At4Ryvm>Tv0p!KdAP3o$<4Z|ef8VKWaTQm1NHjvm}Ts06!cZPzE%F2YSPx@ zTXWXWFj#$Ujo9gFv$~Y!o*dn@=IZ9@3(sh+I)3|>xIyf$j_@lhuD+Z;Ba~k(W#z9G zPFK(7iC)&{3N@bpWJCF(MQ*%zBR|*$v`zD0v@e#oZ0tHNRkqf^ z&-~XXYvvbA9e1cS$ct88TDAP$tcec%CZMkAg7W+8SnHp@eZKs%UWK^o|I(7h_m%8K zcV>%~$;Yzow3+jix4+zGmMDwY(l+J)_a64KyG=K+*tF=1V8&&R$(C$6)@`q3TARA; zrVH#aV~m_-U9&ir;i@O+kJcH#72lpy`F;Fit!{GR{EDlk?gml{HC>%N&ls{gldi_h z*L`%UE>vLo`M3M{=Kcwk-Nww8tGhQ>eM>KG_*}kpQmXWa^WO5dFG^m{ zT;sj|Dx;hIqqW7nv&HyNiIi8nvCas)mYKulRF}^1_sECT?KReae@f>2-QV+#JNN?E zi46t@)$R3%58Yt+);$01{SPZkE~-^+n_uTvQd6S0?z?uOSxy=wo16U;&2#zDb8QTs zHHJ@YdUbRY%j!4FR&DOB`Mpmin$0X}oGEwTg)jvDDc+om;q>U~KJadU5H~wr-&htam#L->!LHaZA;jFGnu=_JNeh_@3Y zuK#?p=y1t3*?o_izWlmXVkNb^GiHs}>Nl$dZkw6i-zJ^9$N0fN_xI1r*@Tu0#o>CvT~%S#7ow;4wHS-ueXhM}y;)rFwx+ftTobZZggoIo z9{NCM+SEl$H?}G~nHS@;Xc8fzIz6&x6tFBE; z&7J#jP00GFIaebcv-Y&bPPn=;#42*V`?ZC~1N8HMaY(OqKR#i@|`u`q(*<$+fT!Zb?QoGm3RU3ZpDgUOhjnnGV{*1>nUrrYP_fy8^c07CE zLEp9#$wzl0;uv2ynk3scG5nb!lWt^oYOU*fS*N*1_y78pIxI*C&^D0pOiOdLKJ<<+fav$=wOvIGe znJiU#T&7gLR>mTE-Hlti7p(+D|Gs=%Ct@K&bzvJYuyI&^v-{(b+?d|*aWwN(EO>+y>Tp9EF9uu1{&vwb?-Z0ad z)fUr#-xRi7KX-0gL>l+2Lw_c1c@fAtbx$Je_M|zhqXpec6$8%cs|fby*^0}uyOy#( zQwS8jTaulaqWNgtYiPpy(y{RcX@GB|K-Vy>h-_(a!zNt zC-I(VM&Y^Vha7zvo;7`y{M6a_WLdSIfBMt(-yhv=u6OgMSP5l0TwBnzE|>A)iTJ)w z*>zHptkEVS)1Tg8=x=pB+n}eb*VyrqM>W>;fRJPPkMn7F)E;lPx_;`^&)}&&*S9RZ zdZ*g2Q0=lNOG)%9^(hkzU(uuSB@<>d_C<};*I&PpvlATl=E9wt4{6~irw(|`S0{w z*RQ<2{Gk8ie7ma;rcHK|G+26N_Om0x@_)q*__wUPr7kmPndtqZjAc@L(}lf?LVe6u zhWfGmWvgJ#lvpQj`TgY5>Z32iKHgZh#^umj0dr1|38sEqBad%r_t=~W9?<;fgKyB@;lCUJkEfX3EIN&zZOBzwz^v`n6LP`;SM5R61xMPK|yw zhiS!3n~Y=?{l`lDi|3X^FKXUrc}T&VX|?yo(9cf}y!P06D1~{_oY!K!(-kw69CxN) zjH=~eDe`W#NYqT592xLiY~couT{}d-8I@afUpj2K_2-epR;RXpeq{Jh{JYVTTNxoW z=KD7vHLH=0%H2D0=EI)m+{GHF*W^8j36Q;I#4C^<$-hPL$G;-smzw6!{8Qt$*I0IM z>Gx+|u=n7XALbwKh-~xE^FA*BrD;pb$xS;s751Ft03GKi81?+zi{P;N6U3J8+_`-- z&pD060AV=;wIadQG3R`n9&cH~yyithZgT=-ak6pPrngKBg%~Hi(2{Y>J$HnaGubjW zXyu8&4E;CVG8@^%6snfKVD#R2%1yx8(QDb=ei-Tthf#n$Hg!=>!Yk3CDUHZz>(XYnbu_kL0V<^+Q+Q<@4z|6bQE4h)S@5^(d1?U`a`zJMuo!qSw4 zyrnI^W)Wv^EA22&)tVc|(4wa+(NY}9V%el;%eW$`Z)?~&vsb>^tM4Ds-ys#76>@61 zl)~#**>ATlzNVtIG1#UggG2h9{k4KDS8UcwUKu@y^YOMlaIm%z9+jo?7X?_tyOl7n80$kjdSCS@o#tZ<%R* zc3)a7yA+Z(H-MV~pIpB)nL;UVlmK zojZ{`@Xa|3=OvGBAMatgtR~r~@?G*^iOV%j?MCmDG0)jP#x0g$jz2eFanqSUzg6-U zXo_sA-OMaHX%Gdd$%Jx>t$$eW+MNfJu$voBgebm8i{q_7a`BP*5 z$M61CnfY_m-sjF1f`?6vK(k>g#rtMdy_)s>f@{Og`&;c!-q1L$Y!G%yOVuFM%S_>V z(iN64Go4GKv#)XGy9T}S+}ox7*xTeN!{XVFJ6C>FVa}Lt;pMqznw8hmuq|1V25XN* zYuRdxZF%ygC{T5msQ1@fYD~HAH*+p-nRIyLnX9g?OM2I=oxRp-$~~z!DbdRwztVBG z^%sijku0&2QC`oIYZckH>!gSO>0PWo$(uf%`?K9@jYgf;ZiBhEpIuHXNiwm0uBkuY zU(r?P@{t2?(mOM`Y|ea1e{g}h&sTf;x|vT;Gkm?!8YNer+wkqB_r;#quec6GI&QR< zle?P`D{^B~ePh7oD^DZ)ew`@qb@kO_uh^--ndwW>ox8_ewXPX0GGh9^l`(Uj$()yK ze54=W-nz}z`^P%PXamo5*<;b{zI)yMOJkbe9Gnn7%`!Sxg6~|SPQ<2I*+}c2n(d20V0-u^TH)P>hK`8V9Y#a7TKZWFkr>b?5XWIoHnK4s4D z#s?jI7}A=qT5j7}@iFdYN9}w^;fBu0PL`F|)g#NY7RO88@Co7VE$x_gjIm8CHzgn} z_huIFtvL*{cK>z_zLphSnv(5o+rRr3TlEF4@^`D3-1FkizVzgv_3dUwv#{u#-xucV zuMoc*q$PKAb#}scfoXc7W?5%<9h=-cr(%xh%V6HEQU!L6g?FQtCTyFKQJeHQVXbNN z(zu%+tsHh*E#B%EeLZ(0dxf6nwoScemx6i16;H`u`MJiaJ8(YFiPd)hY>ymY_i3Hy zyngq>;=M;-{!iw&FnqiB`o3aWuOIUnK{YD#l5^~n{jGm2F|nHJe9@LCjBV|@?Dr`a z{b#No$olu>)~e9jgD)4Zu)C6Q-r=Ns@mi5tHv_iCX8F1-J-BGa+OKjyr31LFk`1CY zJY4rA_U=-dr}cM+pV_pz(ommDS+<@vS9j{->Dl`O;SBaM{V$3Zq-y7$*<~Bqz1Gp)F5>o~W{tjUp92^CR1uk4`XW^J z?kqb|AGYW(8+S1~UeipM&1#8{l3<^?aQBZyU9ses{kwnjckeaWJTIBaVDq1IGrunN z4qu}D-Ex{5!|gPcHm$8cFGiiW+R<3k-dxhx_x7WQH`A>}9fsjSyCXF67#8r~3QU>v zBDMIcLriwMcIDx|O_MseEI9Tj?CR$Fyrwno%iRww=rRwT#;|kQ>bRJ^#P}^r|6^1P zodo|^Djf;sNIm#6ZGMVZw^Bm$n~e+CIL|g)Q&%P9(8qpDu6@(`)xOgX#(z6m^jyA7 zq%*EQapYlDR6 zT$c^+BgKC2`2>`L8h z&doZv)|B36nw0p-Bhp^w$DzYpc;%cri^%P(G|rG2k>*ZM!N%e^xw$^waxqil%6{~pt<(@aa0aw^VdmqqbGqvzKW`~H{8e* zug=B|S0~pgoIZIb(%84>--p@49oz>mw4D`N9qlOP6&l?2!^}B~A>bg((|^S>Tpywt zwPKqXGaCoF(Y?}>AL-n*2#1DW;@?iy??E~th0~%&TYk+ zH%${3c9+hX=eTL!)xZxH@nHfb8h)Klt>?l8uP)V#Zkf?Fci*1ta?kcnE5G%vH2QL3 z`#uHT_>Jf992XHhBLQk67-l_hf5_ZtsVH0Veo@JRG^rKe3i6XXrX|k$)fU*#)@`FU z?c6txEt>q3&t_yYCvQ`4lb$fEAy~GB@uGj;_8UdA$ulD_HRUy4d@}#zCc(vPb#s$< zN~x`AS?rd&+SIhW%GFyg<)^?F#_sz{AuDbb*%}2p&h}Vw@}xx2h3_7Y1_92JYRaXxb)rX)vLYEi%IX7d_9~^y*>m zmz8s-m1b9cUfN*zXji96-Q@_YhetoY`~P!ObedM5tq-TMXxNRpdTigSFI~*MS-1MK zqq+aGy>d%}tE)F0ubvmXT_JNt$;?$!7PZVDB-J~wXf9kE*0MzKVqo;*Lo@Uu7iTXM zJa z3-_MRw|&nN{;2StRQ$T7N8Tqj_Uc*d6i?o?PvZFjgWg*9mEA4Ivez#u7k?8;o6%=> z^h=Jk-Nk!HYy0oTa2z}Fa;}M6%obhQ3W2%*?BCri7IghF$Cd4yg6x^3DG#%@OGa$T zS-<7~fXMbXx_W*sQ(3qkfJksx379O+^t+M_UT1Nv&!kf*Rln<3`ZPRaR$C! z(O~L3gF9*N^=s^_Oqq)uJvmR!37=z`r+L<)Kzq^JBPwqv3NYxgrnkEBuWd>=Y%qUy z+WA)*L2L3NwO@CvdA02FS&!Qi`_&YUr*1N-kUUekm-+b_kBh3N*NPcdTU*Oy`MqIt ztv)t`Pik<LtR0IrUZN)G_t|B9Ks@~7bkYw4CS53%!er9`9DY zaF=u1GDZnS!xL84R~O7YyUj>iVBzygN)MWHO&%QHx?#7HlC&O!#LLYyLT@bYUo~@& z+^4f$N4)Jyu3dTT$8wY}X1+0h~@%1HCzq=DSWXh>sFB z-8R*AQRuC;S6(>YOYQzBdAiK{%avu{xzBCfVkUY!uXeqbbyCa2wg1JgUyHnI?c`(K zasTn&wdd?6%oTh5`oraw&mYfRZFJ(8x3m7Tt9OMnb1qg`ePk{CRllz6c+Rh0&I37{ zY~A;T$(8ZMvMrd-wDfJ}Q(Mio`}Y10`4w{bv5kx575n%&-m(;quU^NPRN^jOyEr$+ zbX~@jNp7k_a_b}Ine|ReC9GT}eAV6I%Bq?ByZEOS1WQ~?ZPZ=4SY_JY`l+J4Q(RVO zJP8w0V2n8Qt#4D`EkXYf=TFzUK4?7OB{q{e^v#V;(tJ@;+S?7>b7MO_?>e16wSjrh zbVdhn)#85*&-)Lg{NG*n;^w2A)iD;Q4K1Q?t^0lX)y;_L{Kpq&XvzH9{l5KJs>Jj7 z+ZD4PuS;nTS*T-BWLj&UKWB-1oBAcjY(77CdHMJMOG5cfX@$Kp|@1#SjyVfY>%6_6jUBkzv%Sr zu_4c!E!&Q0R-43pi@zGZcb(PA1&gbsny(%8yyxt}u1x5wmpk>Vj@~&U9G5;9G*n~twD@ts)rZHIrT%_5UrqhVQU`DO#hwRx7jsT-w4KLt z;e?@Av*N#(0r}2yqA41tp63*dmMsdMzNKrTx!ho<2m~s z&zR%Sq;F<18+lxFUiv~*vj41r(~{^Jg(umrZc165)wF#HhjY>%hm#rMMyw7|9_qIf zzvj5{m~`G-lg4`Gro5_|dQ-$|yQ3@Cv~X}LraYd)5?QcehB%vdNb z+xg4&Tn~N!Bj(U|Fgcp%R$10<=^1B@{{HQcam>kC#IMJEVN>nxZ6#+4)t5|VUnAzY zrhQ)H3M!G*U`R#6u%SrqhYuXPKMJbpO?iGp|<)zh(atv)k;q?wg4J#{1TUstvir6=KG}0EaM~n}crS$x z`6Z2+YYXn0Pv{k6_Mi49@MYA4+kcoBh%#O`+idziJYiedp1j%+waR)coSIJcg zn;w(6c&h00XO1p@{kcD^6mJA88^=f9OFGkh-C5G}+G!IF_1DtoiD&h`EO`9oM#wtp zb*t~W`&XoD-_^dvylm6l+?hJrVcRyvZNF$aC29G^?gKxT+0Xj$dvX1pzyGhK+Zey~ z?`Jr+sX(HG^T45-*Y8|5J^tn1gJy5W>dVD=R2juQYIN&cXG+%uYuI_}*RR zx@B??^VxL!1m+D)OOsZHoMd|yQDv=lQu}o1?boZgbCZn?uF6O{pX=Uy_0^BEDQoW@ zJ!*7iZqHYnc~h+oWUaE6e@*>*Y~87iSB>s`eDwBt5r135+B2VL>V8cKv0Hxo&s~|F zQ>qHAYd@9N_j2A}|NO|y`lZPSbCQ`hB$RC}5qZxXvFUuBN!hg13>gPi9yFX)&irq^ z{Cyq6Qn7S~WA4W)loo7yx0YwU&A(kPxA=BlID6t$kFU%HuV1lMQ=QgL*=V1AA?^7t z2aXFHc0QPF8q;y3RO9znUWN=eg%0c66Thu;$}Mq@n!I55qzO0MB7c4PdhbTkUKxfj zd&=YUno>+-LwDpPyBlpO{C?G;Z{^jCY4>(X_}=?=H1UjBN@QMD$o4XhlYZjMMcJ%+ z=WfgU_T0Jg-#+(s%jIIc>%aScn!iB3Ic%Mkf$ig)>SxVhppxLjwM{X zwx2Cw4Pjn!P50cTZG!(kHwCTiobcLi^RG=>m$r38bMv|Q*8E)B6e7y}UgM%ev%Q|> z#`@_S&S)9%9WRT$YxzX!O-0X5!v#N{Wo={b`4t{Nb>88bl9NgkFT8uY@64{Xa<%8V zzNhV7ej#}|`^4XGpT12|-u^b@e#O^FUFC=OedoWwKDn-!aryuGMQ;i-zbdw`)Y!%i znv2kxAp3Ur&R-cZY%gTLO=vi_eQAlrUik$7dXp2sKc~hBMLapr_JrkuVa&1!HO&QT zq9z-R_7vw|5i5~Sna(1xjo*0HgzY(&=_$EczGCk$T{8V~c;AcbVO=`YYj|!7MrExv zYwEKqyztXvmUqM(uboBvs@5=0F3i=D63>@icbtuLS4CpXwp^Pu>#W-bZ(Al79C+iy zU$=Mdw!VE|k6b$?B`qVoR?GI#ncJ)?7p?8x^cFnvzwH#&De<1I@?3ZQ298Br5E_j?i+{`SG<&$0;cY5|!i3LH+?)T^SLZY7+zgz@opv|6{QmWO(R;T?-CcWc3%AM< zhHnWn{PxEWwClN0M0FTJ@(31Snl} zJbvPwkoZoO_QQf~%$bb+lcl=)G}|<8>YA^a7b>MA@bN%*>ACJ#?)z9=CwpuTYPR1t z?~M4f-bZr%!o0WN2OU~v`80Zk%+4o$Q7=D4YW95;{AlrLa)w_~((al2LiTyIg`e-3 zC*U#V+*E@xurvFy}#dgSZWY~=6oGv49A>|g0~SNvgbcRwpv^VdK2v*H3ahtGG5 zFWc_9#k1z_ta8bQ-;8Z~X=helu2?Q_%=E*qqcleB!O<@snYzsOdYoGlJsZ;5U1ar{ zU+k!1m00@R;<2i=Y(bxS!p2it=X#YqT9u9UX5al)mv>t9zwood&br#-%tT8CgJ==q zr7uoxa=mT8^G)K3aH}5uM#9^RVXz@hYdMA7+{FzM;=(;?cwXg)!@*k2qh{V?Ml3#(wXu!luLA`#o1b_}ORw z_|Mr-+ke|Ixd*;xdCJc>=dXRb+4~#0Ve9v|2A2A^G02OT`gSl(XN;?U*||q|dDC;v z#yb)gm-jVr=L>v%Ws$Fm z5-k%MJ-a%M)kEyobswK0v;4?u{vtOSx%DS}z4}*a`*7|%>prvZlkX(e$i-Va1Ok+H z1@B)`)2?>&ljai-K1tzEM=v=i9<&tuC^BEnzc(xJX2TteUqyijKj+42RPMW#zApLY zbE`kuD+J>|CRYe0`u)CaapG}>(Tj!VHdE9?PiDG1@HT9@|Ip^G^u3bVneXKcj4unn zSiH17^30s0?oPMTCiL;O#+dL_1in0iZ;*G zv2!uXPZLWM*Ic(FP@rVz&OUL))jWagBfie;p7glShEY`U_WYI}z^W^LgrC`Rz9A1fET-2${HJ&XJNQAHPeu z)J$QI47_6=6yf({jjK(fT-8yFXPdVxtSR>4W8IOe_OWG4L?oxlqv!WtMeIIw##7?> z6FqY!e%`4z>!nu+o4p9S8O`eN%0KI%rQ9_MfyE6!?{HsKIsWqhzk`XJe>eF3SATe! z$HbIj)A*1nA2d@{!3Q_sm1ZX3229y#jTbsF9Bf?2M|FWbq!6J?0yY5qB_ zXJ#%pPxI#2VrP2ezPNW+y*fJe(If}&WoPfqnQ!{}UUpaa4$c0#GO;%rjc2_+oqI;@ zwERh-l`Ay%pB8KpUakG-&^uAZPn{79?3gqyw@qN4suH&CpPyskUY9z#`{6cqb8b%R zVbRuOIUzQiS=)!{YUqM#yMFY~{go~)rv6l;?@fpP6xpM)^JLrv9-ij=sWSh)d#lx_ zIqZsF+f1$~a;}#N4zPP1x>U{)@$gcUVkXE_fi&Ues3GU5?3J z{d{oa{GtOhexH8%-}iR;%&pSfpL9rNX|ep^Sefm`V$^n;!Bd*CyY=u~-U9dEHh~+8 zjgr~o&fhTSx@FLvAQ~swwA1YV#Y=5_84oT>U68h9dNHfcyLqibPbNm_`7fH%q08ZN z(O~up%lUq(kF+Pvsc9?vRJ5?+@Ufrj9vxm>%oE*z)=7FFoq6-DxTs^RsMgvJv8hi& zk1q5!(DYxocUwSW@1F>{<$pR3a_-oZXR-0zk)tQGD<|D@Yd&1q8UKa*Gw1B1eo9{= zr^U;i)bn3@c#WCe)ww_IO#ZIc;B^1)wzy{5$K{T$`fLt+W_oCxyU6h1=Kt0-hVIEr zVtTt8q!@auo@)iXpLllnMxG1enr&PiJuWwHGL(e;Id)*z*yYPNg;%=9#-i?&Ci7eWl}_v$Ib$T2)tZ<`lN3iTd3z z3vY{GC$GL<_rCUlQ^hPlb-S+KJa{FI^YO;r3{sz6e$KHG4f!HHb6wrz9$h|mp1XX@ zmd7jyuX#MYacCdRc^22y zao^*atH%HEfXz16-=|Lq2r+X1FTMSp!E5te?XLKlmpUCfV~eaDGp>cDE=V~3F}H#B z4a>T1(Zxo|zx1cqoiT8KFo*L&s%S^3h+}T@Tyqc3RVOmT=2##1uCnnp(p@yk;ql>c zuZb^)AW@;>ekohKsy)06A?^1xaCo0*DO*)DoyNjU8KZ8cxxrQn#ALh0mj8%KOz4>gnr@%Rm40G8EVv@v>o_iL$_^X#JoDy@`qc=T&`N(WhwJ zWnH9m_T=#${>8IXyG7-+;%3?6N zCA+$y_V+P!iMSg19kcqp_6u9j{CP7`iutJoW0~iIt#{?*j`-PMUu!J3+#`C;tBHpW z@qB6eXQ$ry$MMWEt4+?H0`i)?>e9^z=P^&4|71mu(@j4+q1?NZHd&lHzU@??-!+E9 zM`!YQS^CAaxoq<8h`sz^?0YF!`ml-j=O0cFA~F=(>Ra7wW()o4)J$3S+s5;w%B<+P zh-F8cj#@wcdGN)&R?bPspC}se(vU0#v_q7`jX13i*-*CGw=l8)`I?M(32Np$vZaW0hAm ztiEoDeqPrY!GGSv;Y4du`R(+rbM2>0vKQii;=VM3@k;E*KMSJnJ{8=XF~fXG8`p(n zHK{XJrv>m`(w)Cpq4s9usxl*`7pM9z?cJnQHN&)Pvi_5$P16JIZZ6v7EO$&fN<;nG z!h3a9I(|R*^=x=EV~)Y2=cScNnR!j(+uWLh*&hGf`+bv+e%F21XO@2u291G_r4WVVfl6WAPg z_IWeK>dJmIJ{)4WP%1{}+{>@bu7~zd)QUbLztP(vV&#gKwiw6CNhex1usJPCNh$a| zVaob-|2Tqs_?CN2nNTdW&5pZ}o9nTUu$^cBBg;UuCt8_%!X zwsuZGzsuE_GtoMEQ>6Izzlk=-f)}jWaKfQYU45F`M6X`~KP30P`Yy9B^^aWFTA{*M z@7|hLzu&R{hR&(!Ni10(PArw!JH=#e{a3Zi#=F+NI?cc&zF&94zlvjZB_3(dRo9Bl zU(Zptt?Y>zN88NBeeq0>^fm89`Y*fYXcwr{_@0YPa*mPkRE>^xehXRV>u-$8h*nGw zmOYqvHtFX9A8={ij|S!A&zC(tWOg^uC#QRT$E1{q zc?R;A->$eBxGGSty?trauEi&m_igYBymPwUOR;#0RDrRaR?zLb=!W+48R}mArx)t@ z&+_feNZhA!F65f^lI|G^XH|O)&-(LE7Mm*1$-!jzEbh~%MLIIG{~Gl>fBt;u-%_p3 zdoC@yQ@nZejDPh9Qf^lqXxNnoYBHwPGadTYpSSaFiuJBfWz(j%bzc6zH{7^wJ$u-p zk82v<joUd(sRu3fv&-m@k03XT$Wj50a}CIygLQ0(k@HG#X4=a)7z? zaTF_K^#s;>=cOv8LiHPGaRrHK)}IvkwN2%O$ngp5ZxpX&oZkDpa*oB$MK*GCzltB9 zuy($C^pV3nhnVfv6(@*vx4#tBJtKa!S?626kgkV=my@N>=Z{NzX2@vT>s&wexl-r} zN4#szy5FA{%oi`bx`F-Km;bYPXP=oiw_58GkLaZz99KSk%&ysR=}yzMoY|81x5=Mo zi0N4~_c7bwt?EY0*yAm=zb-jYvo$CGaIHB_`~iO>tqzwKDee%RF9ATQ`7N0w|NTd{iHX~OD}0lrSw-b zuC}DHtf79x%vG&^I6}@YEDPbRbpVd!f@fZf>T1tYrD&*&Mgd^tLXJi zWA=QRdj(sLZh5%-*}oOmt71AC>moN;ck?z`m>(#$bUifBT)en4$3oGv^Y*vF~@=I!3;qdZFM9zuOZ&Mymf4PpafORnL_8 z-cmFE!zIg`%g(LS+x&i=xz5=&GQQ0XQVhPutEcC^IIwH&vh)hmX^dIc+zw06_p`07 z6`lNJ!Ix__O2#*jWIt z|6Q6_>GOYmRC)K!ve$8nE7xQvg6a;)jqhx{37!H4Waq<+ZrdL-+7)Xebw zBzkty4(249wj9GI6iVI@YeD zf}f?Fk;U_Z<-K`6Kb2?-P`T~dadAdIr~2F2rnP@kZ0^sT9?-A6BWul{$d{3R|C2w* zyN8$m%)KyYrL#jiLr}T*^y><{c^I4*ypm0tm$lArs$p%o)@IEMx=qJ9zCX}q+^6SZ zdg7aFR^u#Q6n*P95K= zUdnH#E1&Fsp=AE$?xrH$X?GgmUtVmH`k?F4#~jAkQ_fL6`b*rm*c2@)nfUg^W|e2f znRyYPHoUPZX>m_FJR17EG4w z8kzL|D<<7px-;?iZ2gN1k6&51yhJHd&Nlr1f;Wq%OjTA^yS~u9$T{kW__m9^lf!4F zz2vOq(yZRDv;W@|acyl!v&&0sw6#~t&XbuYJo$8w!IUJEPfsSMo?iJy{@aF#dz;?> zPVV>K6F5~*CsJv?(zhFw)~jDnmOi(c;aGvmhd*38%p2T7e$Bn1 zaI~N8(Ar%vWtc_!cfK6{!@+4;rAF`Vi4QeUb*TwGAe zb47CdW=79hi_dTcaejFadN5CdX`&Oy)6G{MPZz&Ee=$NqV(lXiuI)FK!j3sPIu?Dn ze(=&JBe_dOk&l1Y9Cfif?U>51{xj#y#Qf_wb=^O>|7a08I@d-}H)T`d@;)8qM~t>y zyZ;0=TyWawA@;)1@`?9^sV>F6vlxC?@alxES^rqT5 zO1aWUk^g(vHzZ4M&V8Hq-!?y{@zT~4RqhJW??rXC)!o=B_y4Q-?7*L5iUvFK+F#1a z7zJkJb32L_MbmKI%MprkxsVTp1J6PQ`8i(OP+nJ zUVaSfy!27Oa!v6kR}~f|NtHI6NyohkQ*PF97U|5rsp=qK!}{2+eunup^(o1F4QwwK zzdHNppRMPa$^9C=%k{ub!dzsam9 zy%-h8tS~`}nIYnH`Py|%99v4i&X{<%`pj4E<;`0^ah=eKWU=zqV_b7+nS)lX))~ti zO2)a|X>7lLWbpVre)1@@Wx2KZ8JT%0hCC_n8G4y+f7kn%azmBz4fl*W`!kn4TlP`G zkhxebWr>nw(2~ba+mb}Rj)-$@P%U(k=S0@8ny&wfwYK#?-T~KN-lJ zGulwOPr3fan+5Cam>&h!%0vqG{zSQ`L6FvS|bGO^<7o;zeVn- zHdGe1(SH+Yc>RMKv&i0^jV$}F_iwF}eimgveSL*z9ZUG3k8l3pWq)tBx0YEuaL=^J z=Nu~-1D=~_EL~;3?QetS$0z22Hr2M@lC&SM`15kbnawXMZl@V&ALtY7>za4H#ydgg zPK%*|^I--yM&6d$b0vC8b!SBCeAPE~K9Jgww@1+-`N6R}OJ+vT=#TPcS+n?4;><5j z`zM%nPTBj!|C6xDBx~yfI$?e*CgjT02h=#IJIZK_SxHao;u5TLDL>qovuW4U=PE{u z;>AyI>@rZEdpP;AV1Sv}@5z(Jl-(z~zMQbPL#(j9(Z(k1K%>t_#0tEU&-o!%beLt^ZG(ZGY5qp82~?#P35}kF+^B@HVvV@ZGxp z$%nSN%d!*B_{3S}8{ILCSNEH;FM6(6$$^^c1+vQP9WEwcW0-5Oe&xEJ%GBq3OgW|* zF==dOKF+Z{t?JB?J5y%Pd;I*?p-C$$HG&rjIoNpW{ya7RW5)W%6CAZ4WhOeE()<}v z$=R^H1kh%O|2|>xV`A}649kDt^zfu zHdQ)Rn(4jdJG<2Q&9TXiONGH+sT=H1XLoqoXJlk3y=%xU^L{paUN z@N3VhWI8oJ{ag29-ZKv;NicZZFi*-=t)2IGfA;;@W%UIWs;d{KUN>aQVz~10Y~;&T zy~&abmbUhHTozncveZjwKHEWsdm0g?Rx?C3RIT}WU**3xj6Bm2!6H`rYl&I{bL(TR z>b^gJFV+Xy-m$6MHATtp^5RR=0}8g?vg?E9Il*uv@gbCC^? zPH5b`PvZBxES@d;!JR0zy4~f&o*Q$29}oQeYvzwhw?yA=zN@=-?Xw4a`NihCOU~wJ zG-a6eexCTSZLC`qc7~^ zQXbZn&N%h*|HO?~pIy7ne>LO$^O4p}@5e{S5hTM+jl_eE977rt2wdv_OT1SIP) zKVg-gYx$jXjr}~sxs#WkX9-yNJ;`&y`n9+Fo-mglKCb(7VzIodbeWv*?9-i_&z%gK zF~NJGUC~pwpE7dZi#PFP2Id`$wNv+**U``PV39?N=dPueMXhp^rBnK+yGp%gd4B!= zr5vw!{vl?bte>719dQn+@qBt@r%=)x&h?L~Wcaztf3k=ACFJl3mH&T#r}_j>UyrF< z>(*)7U^g!ie{CJca#lplYTsjpUru&vy}bJ0IZAvU&Ul!d!Tak+k=2iN)A)>UT+6<- zF>r!XjLRoY*>bZ6n|*N`jaEd+KFyL}Y>;G*iPMjzOhIr zIY8ZhuIIkZUllKAI;$LCxcg^B{d{$??T>0sJt}igb&{J}AKBO2a%ubVub;N=V|y>! znah2z$FkynbLzo`GiR|kvejQHHs_Mfow@(t93Ea>w}czn$XIkhLfp@sKQnJm&9@Mn^dAyQQ(MWxjXa#YH%-b2+@&0yVOY1U`st2TZ@FI^YZ7hJYFakcNPWJ!OZ`e~ zQFl>=p3(UQzb#lZdH1~2&t0l$VzP@%n&G|3q+eYQN=z#a8UFYgZQSamBiVTFU!rkK zIFqvCLMmGV$A{j=BkAV@Jr><@iS(RVXSe#!oSH-G z`_7*4So}wM*5rPZ?=NDG)rv2+tXzAD@3A2NV^0%~-ikv^cZwEk)@a_*bHy3_VeOmrRoTupSe~l^sxhI5u`y8;LWtRH6$8Y|>-Ti&$#E(}^^`p6SbXbBu zoru10ci+N|@0{jdFRNiU7dm<@t~BoPp{vQ>*Y{Y(54&m>-B_D*YGUd44e!>8pFaL; zSEpy<@^@yICNYa@e2%NW6%zb;Oa0{+%Tv15d*fL)et%s3&wrB~$8S-=eQaO&?|u(p zz81AQCYvQFnc;Fjq2-)VSP%T}MiQizu+tzgbn(Y{BEi)13}BF{)&bYhh~ zI+G)E53#5-RT+Q;C8R@kkr`Aez{_B{f&oABGFx&aiwdFc7JlwILcD^*6XVCw_ zwN6NO%ZUvN%9Sr}?msARo};PQe^}^gcZR2p56?@{nV+T~+VQTQ<<$SwD<-a|=AWH? z++2t6?*9o}LTX(XD1frSg;TD7{SHpcPV(gFc@XiSZ=T~BrX~Do`$R94iz?sc`Xd!( zko0WQ0f*}Ov2q=aKB><%t?o;FxR`q9(w>^IbwXD|s*R?_TO^n)!lZ`G58{_p`a| z@A6)hWjxJpcVOMM=T_*p0M8dHm%exFU3nn2yp(^v+28E#OR^*z1d9Id7yZq!Lw*78 z{0iN2`-Xb!T_0yX@HpO(nV;Kir2X(>OVO8Jrf@UWpO6ZZE_U>GbeMXp zEuVv@{L5`&mXq)87KFXD?7Dc&RYP?7YbU$e^AmR-NY0!$X{(9({tuQW@tW?Qesgtn zk`k& z?D_ATxjFmP&fBw=d$3&kV(jtidsmC&%`V4;ipzPI@7;~x>9=!+Q}&U=FaIlQw1rwI zF9_PqP^0isQbB^jHjSgr;X~lU08KRwyTGMl1D&6SP+7te6-5q3eM` zPs71JJDyo{HNU7B?44LB)V(@nWx?N9lhP`kj^;g{x1eL%5$0JMk2}tve&6>mSLo@3 zPp==PFOE4SvQy@3_|wq1Zw1#23-8vm{RhJT{yn{I>!U|<~MZ>TBZ}@MP*(}ZCTG76xlUtYWoQ-SFKAI zTnpRPGS}rlZLtZQr{b*S=_fQl+V1eW98HNWF}{vVWMT|wop0XpBJtYHuK8>ZM`gJu z_WyYxEsz=WT|KSl>-zkJ3vVo!?Ec@`zUz^oM>pdEvE#qyJW>oe7bNgP_IARruw0+F zIt&{hm^1y-f4tZ+f^WqWorVWKQ*Yk9l;^g)&4<5-@w#i5wThbU6;G4EbL(`)hr9MC z81pk{J)S0;c6ixar=WO24X(y35)2ZK7JK~ybHkSz2|T#+b=BJ#wi(*W<&RSqK9WD) zc3>fQqoBiL2fbyw*9{izdODNQd2Vfl*o*DyY&(=IltgVWgltRPqj0vU_2(g*N$1T4 zCOx_F#xFAP&P36R0v#z6G=AE|eNy>YcqM+KW_{nM%8lxeryg}pJ^#UQ${!oOWy^1z zef(vA^Y^_cw`^H<dGbAs1O&Sv)2wvUOjw6K=rK3b=M+~Gl#=@`n4~&&8@n*NwHea#P9ob zn^)qhe(`$iq!POB#|5uYy!OjDTyTG%!TIm|8Rpwe5jo_5Ap zf^}jd!(s&kz75NIG0dyND@^ zWv**#!GW_s`kJ484UczFpF7K4<9+TUNyc?Wi;bSYY`h!bCHc;vecHlB+rzz&one}E zz+ge(h6KGn{fOj+8w3SjtYX}wc%kn+cl@kfNof~$cK_=-RW7<+BjbvKS9)oT+%aeI z$DWH+HGaEpQkY%Zb&~09zxT{9KW{Jjr2otO6q8!OIl&+13XvkV?muHHAE_!=e>&Bg zutnrRfv`p0pXUem@;^wQ@$Y`3@SStB%ng10eb?}>vYg}__Ta>{y%B6Lju{>BSv8%J z>uy-!FFpR}3vU&k(ODnoA*WuNDwiPUMOgN66PdlSv zCVWXl<5*|X+xIU_S68+9y8j4NT-e>CA*Xdu!;kG!!O;vw+eNKEUFN-Zw@I8g=cHg! zWo7IAzF!Ofc-L*8-!-pmzV-L+BPZA9Z*EP0o_40OTF&66S&m!aa*ipD4q0_+s^w;> z4ND^*arETvyqj`+chcg^7VXD$m@dqTk%}mnIn5Yxewkyyn#I4~ohh3Ux_9xi^?Q$} zZC?B}U}JP($dPxy_AZ{Wa?0XIPF9(fu|oVAXM|6SUy8BRX_x;n^Wc>IXEb)aWIg<% z(`foemXFK~uEBPzN)E>O&OXt+!)kZcnS0hHzxHR!H*uNRn6fc!==ka{dce(9r||%T z9seEUU18H1ryb}#8S+c^hWLhu8Qk}DILvs(CZ63C@b}Z1hKcVSck3v~i*71%Ry1st z*5g0Hf9U(-BIne2139f$%?lt9nGmuKF?aF?}xnoObq? zZ`$j%)iSHtL>n)GiiQi94h7%2dR3@#TF!3C+uQu7Gj_4ez5M@<#Ex$+Kc&{K-+Oz_ zu^fl+=yZcqWm9%&{HnS9Pv`2>#I2J38I_L?xzFEJd;Dxh%xnE>F+S1Le+2CB8&5c! z%24X9WYB$f`|tO4i!a?uYLH?26d&w=f6CeX>Z1O%w8qbV6vJP6DQdA+gBJo zb{yQQ(3^WZKZ7IS<_c{-Mm^S&p_WEN<0oZ_^yJxvjZw zPZzwa`pKe3@b38o6+2QC-hT@}bnl!E=hJ$*O_#55t0sm!N)_h6e_`-@!@u=@f)RR5 znZI6&zS%#Gd&;x({RbZGdb_N;KxUWAbjDAMw^+2g9r(bSe5}BY;qX(d$`9-xLl2tH z;C*r6nU55k)Ak}~*Yg`>Us`{R*tz6!*Hczz^B-r=oYBcY70>l(m;LoWJHM@;{f}kp zq8h*d{p%z1x4pj?F0$W9lc`l8!fku7N6y>J36j5S!`^CNT%ww0y^E)xZQlILsSQPP zjaQc&Eqm`(Ih*P3rxn|F8whXCOSr?6Q19S#@%QUrJ?(nG9;*CpaftrrX0O>{H`TuU zw>@vNVL0n_=fkW54{rXie!oN9o#DIagG(x|3oJmL*#$cT?ZaX&PhCBU<3q@SX*%cf ze1nvx>S&~H+VStaKHoa~S%$K~4f!*@Cb~Ir+g-G|XPgrDwSQKajOLl^Uy>GA-CO*@ z?x(%fu~xU)v$Fs0X{nF=usHYIW6zrV^6}q-nHQLtRN7a(y8mytyoT=LB5kHhfdFO& zegiK(-o}Hx);`nqn5VEso$y_Hzq7aCb^q2GUUSnNRqtKD*nG1gbeXiV$)CJEuf-0T z{*}<}xxm)B?2q=>vUX9fgZ>934tCb>diP!_K4!-El#;Jeo0HEsJ~GmnuC###)L$v# zOz59dF20H@8ZQ~{rU84UUT{Ed;c0YeacpIJ>c%Z z%OEeNK2Ce$bKu8J9-hXh@7V=JcBmXMW_H`4a$ql`jd8YelZedUIqQtNfsNrC*) zAW$VCpw=UpUtAu7?j%WMBy)$D6u`jeX;{<-mrPEsv z{P@;9!}r*ne2ZN=-X4?FAGtUGS}(C#>Nu2%lzQpO;N6L(5Ps1-c^5=aEAXp^D^p8GCm|7 z+wyqt&oIB`&2Eny#P7>2NDO$R!&v&+=39Eq+`WnGEn9Z-T)2NfpN);JNwqO4EzjlT z=I;+q%o7*cSNh{vqwVeav;WOI()xd@NlcT|tk>^#_-@>){HRM&K(xjCf*FOIc0vR%);yq$3sbN-?$VsEzZFtg09t9a65 zS;Z$W*5K*We88&e$JX?|Gc&Ha=g&C9xkkE0`PZ43krxb{KL|~!KlEbX`psv1zLq_Z zc=+JKf+L5fMijC%AG3Hxr>|uaWmK9lOOhcc>5^oFRrRvt<#I;P<7)Pt z%bL3H)eYI-8P`^Fho2Sf`LjGdt+?xczTVV-Pb)9~Ixn17r(ec($RMLqYWL5y51$jG znjZ77w!NL@_S@?0)swTI9W&`+FAa5Xh@Iv4KV0_NGCc)THizK*N3C-I81%8`9S?tz zwrt(wtwwWmRvpWI#4(?1e%Jpmu6b9_KMDUA#CVTu4#SO4PeL3x)7e!lPc9ccu!(KA zp)f=8gHuO*jhA~fc?N{G9J~9U+4*qV_r~4YO8%`OB8^vg%z`g#zh-f8mH*Ji@mex2 zneAHM#ngtb1(M=PrDh_Zp1z*^?8E8wGnyLp=U5)p7y3=9J2d5g5L4By&1pQF8Rr_X z1YH)^yZSgxmHRROTHfD`ZW|m67=ANnc)#e^mt0-6(q`kw3$^^L>_$_y zT|S4hHk$C7m2xi7>hItZWpOimf6T~X|IE4bE~GLrrb^nG|ME?6|JUyyYp_1{Sel&# z^VX1mL8V$X=ghv8Z+*;iFsxXznmO%xLBRxr74c?FbN;=Tm$>xu|0cmTdB;q6&4P`Y zC#bDnv#UQpcj@<@HKxp~%@f5M)K9L^7H8Co*7|i_@9u#q|1)13@4Lh`z0Dy!TKk^R zftifCk#jjUzOz~Xd{+45{Ia*Z&+LlcW)Z@BjhiX+lIcoAhD#|^ubys>>#Qnhk@-inb)kB?~I;(Z{ycx`8A96n0GAD5qzdG>%ZUJ{aw9v%axz*ciATD|LnwmW@(nn zG>3fm+g;a}U(`Bq`M+7t%}leLd-pOz^Jo29=6QX|F$Ryxq0_d-3oSQlPGzvHHp|sr zlT&SRXn$w<@A!(ZrZLY2j~VV=VA{1<@W3y=vhA}M&f}B5rrP-Eot*!F>x%cy%UJIx z1ccA$SmJqoNgAhuZ&+ll-M7=LufCYJ7c%1WDf;uujn>oIyQa_9ny38VyJGwQrNZI` zujI}>%U3x1-t@RzWg0`jz_VzkVB;T~5`-pS(qX!zar^T1{BL&(*DD1b2OGUi-EVSe z*zxmqH$z`XZ@u`XZ~gVu1{;o`kHzArFK({cE+7BFyYW+cu;r)e#>~I0H#!jXR|uk-9>3<&q{2ul-WnmH}Zc;8HhIstp7>ZK6$@wq8o!~eSP@szq=Bor?r%L9&pv*`21x*^LLw!sLi{|lMJoA zRbls+q zmb1^QtT%ltb<7jWDN{};D`UI-pMS=-ZQs(Wd8F3umB?Ae7?5A?o6hj+ZJ)~Z^qkjk z)o$OrI^{G&$frGipYGPlJU@N+-&uoK{&hl4yiGR74e?@AjG1R$6nHQxB0ao+=iha| zKRYh{V&%lJ{~@RhGiGjhzIbQ*)Y31^njusFZH@Lle`40MXto85w!i--Uw`%w$NTU1 z=j5&_|IK~(^3x@O4=-yxn#p+BcJ4QihiS%47L_~G4(q0!K4*KnG3fI(4#v5RD+Dhr z6)|QH=i2{i9S_r;hc?@C>o4mt7bML;_5W+O@H5YzU;eQ>vP9)PUfHnhy8SUw@eEtR z;sj$!#?8jYY4>CEP2S4Rl~a;h8_ZI|x!_zLPtPXSxr{3|&z`j2YVqxFGp}vyyQ0rr zkYe5cGIrnX|A)iR&3bLY)BgJZt4V?jE}efQdN8pjazZ-Wg{5}|9(<{HPZ#H5nEP+L z`5wl93HxVCF?=nXe}!SicI!#|?V7W<&wTgr>WjO=M9dKr+jskal-B*X@mC&j z31ukpJ~-1LD8XdoqH#9Ax9C^>)myx2w{w4Mbu8#wWyBWSvb-@o@LMb_xjB%KabF$xk?7X%fD*vj9O{PP{QwU@_U`Dae=%8cYLMS#SeX5 z_xIhd|I}3Z?S-Xrp?{P^p_`=JwL;sr`o?ZF1_r{#h3p-&Fh+alJ&4}Izt%SuRlxAoqJ?G>y;E+ zV~+duhZhfg=Kc4H&HUHL`zJq#pGkc4X@5=as+ITOuD`~7Vxm1i&!b5W@;!{Z+~)Jn zIQ-DQy6{VWx&Qp!{(qS~EC#pN7&Ck=TYbqyH#q-B*lN4v+ut7_R{xNE%TdlDIUl3OhQ&d<#@{{8XS{9VVK7|uU9 zdu&zVzgvsG`?k-XIdi6;yZ5Z+qR!G~v(_~l8W;XFEqSw+_i{nri^WR2DoPLSDS6bi z)2{XU?;Q?n*QIfkY)|)F3({J{7c5BlRloM;&hH7VOICJ7DK%8P z$6kIR93Zyo${R+Gd@tSk%(~e#IkuF3oAL1MZs{z^hOYQ~Z|i5h|F-&5{IlSIxUz5i zY>XS$bA2mMY*u*GdquhUSN->cyM6QjJ5(}ctP^!+Xg<4ARC~`M*5$_-c2o*Sue)k0 z!}Rzzzf_&d3c+cNZ+8C;_;Eg={r~CZ`m=WaWR?AS{h)CAd7cBDO}#~F49oxT{8IX0 z9{Xzs+qF(BITH9Aq!^Yqg{jsV{xZIN`Lgkk#HGpJl~WI_a^L7IT@G5iva3$IVe{`f zkL-W{GOeET{^x1)oEY`LuOD|Nsb83R)bJ2*QQ85fvj6=(Yp=ch|Er?LYaMIaY<2H* zAWnwNGz(rIUp%e+cl?pvf6vQWr!UJp_o{H8 zXg$|7{Rwj!%u}D8<5_W^x2YoeJ*oV6eZ`ZWeYfKmr!N#b`lW2!%s?H^Ey-aE z_RZ5+SCV*>W4*E5p6>^}OhQxFFfd&(-NmwjX~%QzOaG3ktE=xc4*t&g#Cc`kOK0iw zM9Bn>`D`ot-_4T#`@(OZ*q+<9=l9{wN)3P0cCiGgf7iPtA+8rQqos81dZue9do`Cz$?k1Vt9ZP&^kZj2<@WzS zAFrP&KleiUZ||M`Uyq#bYix_zJ6F=6+c+ifZuto*#;+~YuPv=$F}SfO&ZfM!*zA*# z!;(LYh0mUxoILrLRzj_PPHUF=zP|$R9-Xr7llfe{_y41*oS#+f9u>1m|6Acu@*w`j zI#V~P^E?kGF_?4hn(KBp`~1s#o5$?$YlF{rUt`j4JMf1^X35ed{tu_V?hxHA9~SWJ zX8Qc6JPujc(zLk!|9VT`Ye{7|!&1<-<=VM@xB0IYe0ig~`2Dx*Qh#4fcVBwmn7N|o z!vU^&k_P?Lr?9oVpIuj59=88tlvAj{0fo=cuC5MWnSO;~1=pda+*>!--v4!NyIKk7 zf|3ufRN3QMWsaO|;QnD(r7L^??fe6~|N6^YO7FRy@1D8xNA7i@W5+bOw{Mk~W^oUD zai+oY&Ah;Zl^IQ-jsBQ&Y9y$u)@14T%alD8!jLlQCjP;p1aGYc@-( zZi~P9nxpZf>{e^Bsf+6>x6haVxU(U-Rx(=&D2aW_dHt;eGM$#I+jF2NV4M zhd*3*%}<55;d*?1>@)_csGq`Huheie^SMf~N2P z4FA?YSoe#?H9J&<>4o;PK6!gTW(Co8ReRrX9cX5jYU1Cx-r;7{lTU&#q@>K5UvIlO zDSF=q8=)Pi|DWpB{4C=3cl|yw8@)Na0oUhnd?`yiD>02FgCnHa;`qz|Yj=N}6`FnB zT>XM(rm6WXdAph!#%JG{{5TQvdE-i}tlm6^9S_bw{(I(gsJjpM3N5C`)*sCrMCaVS z$iGwlT&$u0!-V;#4VWy17p&VU_+TdETMOfaDd}tp^$c@wuMFXmoflO1;$ORh-~63k ziMxb~5*T6**KNL{W4b(%vtfVu@7YJ~8oISV24u{f>YR{rIq&kl%lT=`@)?Zu=Ul@}ajjz}|^uJUf<_7pHbjIC9_z8!{KbFfc=JoFC?Q<*{jwffGa12TO7Z*EE69=W$JI9F=X(hy!o35GeT z+kP#}YP}Lruvm&=-BHztt=`l1UJ5zz%FkOR!rFQ^l_Bp;bl>7f^N(xvN&l_u2>-M0 zaj5+{mKDJ)T#5&d>vca*S)y)Z%+PA=jEAc z+x|||cJs3p)+_~V47>~%tsh;Fwe4OTu=pIqkGGF|Psmu8^{_c)oy+_teN*S-y^C9y z)qhN2XR~JIrZckKSb1V5qiuwg#__X{d#6QjyPI__ z_fC*s+1cQ9jt4vpdd8nGW>h@&&$yhxaH98w&8HK};)x6~iHEP48Zb#@t-V#bL4~nw4Ta zjd54i#(%H$wyT~#vG;a8+xu_q-{h^cO1m6X1z#N1anMX>`>a=qcYB$7eIBYbgb`uF+uH`&ioN{;FQq zbjB4=Rz6$#h5zy0I=K&*S9R69zK@>8-9K0I!p1K%83QEJ4o`cV_V?M_%(kVOw~pO> zs$cVvTPEQYkHg{Zw!CZ&*_CJG_t*W+IDB6;x8d#|^L@4D#8v#xZ~WO_cR6fx;p=mvD`tvC^0Iv3deE;p zovne#!u5N|;vKV2_%t&*EO^qk<uI8$|Nf$Ddy0OQ{{Ct<_tSd4e_DGzSQh;JDa)wGx#gioVwqd<<{N+O-#4qx=KaPc zqUm|_m3Sj7k6!=6szuo@KfB}io9YK_{uGy|tFWQXlO?XLz9`s8{_hL-^M4=8|9dzy z$87huw$h7>R<#_sG41x*{{4ShKfjv!`Pqq=-f5qn^{o3E&Y3N`+lFNaW9bIvWIg{g zQkO3#e!Exs{}W5ryIqb9zb)hH-|c+vx5!Dxt>`dH+&U8D&P81?0|0$ zOU=UFyLVg7pPKCXDyz*wo{vT9*53&`%}S2Fxqp-CY5e9zTNPXv=&)4ulrCF)JLlfM z)ocFDTKP}lfH^~4^^b?`{tMGg7-IGY*H6`5=+>LZ;1T?^?ZW)y6Azs4?eF)V^^3R1 zfqPlN`=8(ccVBD{k^Z5Ubyc*H%{?J2>(x^6_?kkdvjwHSTe`Nim1N##PRM7;yW+j! zP`cEozKo@!jIIuX0W7b$72pgDG6=l(&U54TW3g${oL$UY zZocJESA)_54h^Q#RtC1ooAvj8x%7F(lot{oU$2_?G(?S;jp4Sg`H_8#e~QRa5ZFp|Ef~E1-B&%b!q6Uw$k2oxrW(fK7=m^IG`)757Fx*i)#hdl_k@)@}O6T*+giY5# z9JGK#q?_UT|M-fBt?CEg^BK&%+b^!YY2A-YNeny8n-f2O?63bLb1tuL*}4nYPJz9! zfWw4`>4s0;ugmlQed!NKth?yRHY4n~iH|Z@h>0#6!)y(=hC54cK0e<6zqoN*_9Sp< zbu}nW5NDk8|NoKu|K8sJBz#~|e2jhmJ~vN~X2u8i1lo==oTzy$eZQjVenH-p*$cmh zuz-EDLY}2h&i2>&6}zlXNoqqrS-~0VdsSNw}uk;fP zfauYXYP@;#X7~RO{QnCiBzOG2Z_eOyT|{@ioaE&ch7-IKrA_-EOD~VB{d#r%b>$CH z>X5*0{N?)K*4AwM9~au~N@PrZ?!K;Xs+z^au4pZ>>e&%67-X9q%~Amg0B|L@!_zyDW$ zUhTKYce1nj^xv^QxcJG0EoDJxLh^2tvkV`WbrkEGwVl1b|KHdDZN`ktS`g{=fct}~ z;c-8Oru+}R5NoUC6<=c??R*Rz>=UGq%of*W{_`yV-?QL~wU4V0E!hW484r{i&eo^f zf7{q!D{4~yHlBxZx~b^HEs_k2{v4eyZQiadyIj8d&BpVO*BScGg=rFSesE=FaD45L zhwZ7K=kqA6baz>Pl3Fx=U%yc@+}33 zt&N)m4zPWcj{k8;+^+e|yA${CevO}Yr|SHaW0DL>dW)-nZJgO)oPN&d?-l>QSML1! z`uhH?lN`}|@2jny5Am(TR)GU=K7`-o$r9yPtGLU_?f;@b!JIx(=?qWO zP72tYaW`z8_mW+{=EJdTYoq_WpJC$Ye-aoE$xaJA1rCTO|7Y8LsJ{JKkobvMYjxAgfu(Rz#PKf2a0 z5u35)Ji~`$CpqTZ=}Xz_T8L!iot~zxPE#MvbmmAx6)w)4yH z>*8$;-ypM~ote|=6*$vQNMiX=dSn0I@AsO-_37Z&NFQ2EfP)@ zJ200qVaHe5_Z7$OADwM*Kd}btZH2uY3CF(w_%q4|EEv+m%0`TV5xlTQqgQ1fvnN1M>pS7-JpA9p*vP7%LW6c3wTsuJC)?_PuWf z*YJLveDD>xybfRqYGEk*yZ=M>{%_gxu9H`=FL}RQzia;G|BL<{W!K!hZYB@Iy?~u5 zZ;zWY{ZP=^^W66R&&jW^uYaxHeEIj8?9-q+@1(#1qxVzy|D3)5=VJZ(pQoQ+EP2KF z?Yqp6PDO`Ug-X}9NVr$mb?(#c@$Nyirbm@{V4-; zU;Dy&|Nr&>f31I)S#R_pdH(rsD~BojfAC&9^fY|4txJ|NESuo;%ioYo18qgT?5HBa9^T5kXM<^8(*zwdq5$vX9KPVhgY!`jWZk_Kle4c&3#y>jxc1KD8tcdt0aA0BH!V& z`}vMjSKMClf?M+8O*UcNp5{=Y-H+wXpJlVG&{xjCZ? z>Kb0b8TG$jF8}}eU^DxFsnkv9r=6`c`g?vy&!5yU_oux6_s%wu`GBjk+_!W#iSuhKg%ODkhfH#Z9r{$mS@2~%VJW%gs(zFn}2PrRNK*Co=L1{|_phE$vC)|7q&_ zIdXDxbwXj!8EdT%w7Nqoh7ftiH{a$T3XicbxnKLemTzg{ezB=G*Z=%2t-n;cF@HL@ zV`&zHP90lJ>w9VDL%#FE_GA_Dsr%IO&-N>OJg32Q(h5kz>~i=jaG?6%^bP8-?e~6N zoBx-6ij(cL^*?t9|9>~DexKdms;O&}Pi8TQlo$VLZ)0ni%lYHq_x<%RTgBsk@SWql z(GkwNT}0h)C$#uD<;bA^NA~xf=XT$%-`9QLy}$QQWXz&JcPH+bVDPQK;jh@U^2ilc zWd*N!cD$-_xeu@M&7Xec?6%z7yAme;)4C`0!}Wrt>{4ifx}b^UL(YHxx)06uKaSV` zIlkUujrFJRQ|);fV!y{TRhu$!F>je7=)m02#;`VE^_N19qq{#mUHRYlJeLN~nq^v` zHdp``le+)BKkN5@;NJh?`2AnkwpVPk{~g`;XR^qCt^Z1mS5+I78kuJ6Fn7Fv{)GAF zr%wNxN11yR3o4$Uo4fng?c2v|)Ox}7!U7JF{sVXJ+~Jk^@VWl6z_#{&8A~J4XFjvO ziO=f$)7=uz+o06Qb<^c%_xoqlZcceKegB`Q!9Us#Y=5`^|G$a%?%kU^vqxp;8>uj8 zBe#XYEOP&co9FAkmEZr^S6(C9#$X+^?q&C)=N|X>dW1ja7w9ToHM7fAtDKL)aPh+< z-QEmy{!L%XV7{+Gvf+*SzK^~2<=11&|5mxXAJ_PCMHA%N1spn!4ENvL7d|>7UjKS~ z-Rs5m|G(G&m%4HDe$dX{T=9oO{;c}L=(N%=ox#gh=fQIxhf6vQ9?4PMy4Uvqd%eGo zjsJ6b{{Fw;Oz+;kI~(3CY&^>0P0+ru+!s2Ip(PT_5h=r%_+N-M&@q z)qHXBTT4XdGHU!iCTzU(N5U^-#yPALW-^@PGpMaADA@3LqPtw>^G0U&I@ZmMj~A>A zh6DnK;DLj7n{T}MzSRELV*6k9cK<&1&u5zXSKa;Zq9?US&+qX0d6_|AslJ`ZD=x(a z#tf|&H0^xlEPfRknjbb1i%Phv_h4b&uUA)B|8F|0Q)^?)P|Yk-zi0_4^~{oL%)Y+P zw)^@D`wr);c9lOqJbca2T)SAK?$^Fg>b_I$9U}TJOE!GEDE!8P`+$LbMBFBIhcw0` z{b|exE`R!YzW(37?R($m{(pVozyZc1SHZ!1p@ieinKN_h|NVS!|8@2LU#sie!&j_l zV5q;M@75r}ASC`&pIPtMD=!y^DG{AFr`WnUd{~?x&)fo%Wm>22#!#7NAbjEfhr|5) zW$z3Bn7_e3AzLHZ7F@*y^)kf2J*>8W{hycf|GiZI|HS{_6Ah`;`zCtbnfpmRRsU4{ zr5C%q;-}g(Xddrm(pF#pj8i~>=ViB|1FL>p!x@H-_fm{c=Kno8|If*b{PjN$|Cd|N zv5)({NQ8LD)&1a>3QE`-&b9qb^rI?`@dJpT9^H4 zU$<^uFJt6?a9+D`gCpTc{o6A$jsMH8|5E$Uyza62Jh$u>XKtEzIf<+4?_2%jq;ieX zCw9-N_Kp#K!ORJkFLcjoSL||Q5Z$x-!`}^O#3uCff4_77&zbXUsz9xuX?J&*@87(B zeg64JNmCYq%3_DV$_BQ!wT#OD+zV%%7r+1i-}n6;Qr(Z1pYP_Hf9h`dA1C3X{&9i- ztX{E8Of6WnK}*p)m4$<2*_T*n2a&z`3?6e{8WlTuZk9I7d2v+gW^I1WW9k2QFI>1F z`(vMjy)Yyn1+3?Ia7WnK(D36SZ~eVrLS=gYeeJLNvRFMJ{3W;R$KZ|Klf<9C|F!Os z_|x-ht3Ro8i54wdqov|^)PjR!+ouQJMavg0za$>d!?18?8^h;M?i2qWV7J@w7Sx!& zzbEtZvh9yV54~LwI1kcsd&R`q^DTaN#m7hA_kW)I-sTf~{Rejc0}MObx;EM;+pBfW zxBIqK3l_&b&3vk*@EwH zZOwjAUjM!P`y*>+{xy-CpYh(%F7VcnhFX}_&Jg!_cg4?7Pt)gDKAX9|{$26>>No5E zy;|?LxJ0YqTXM;wGdJr`K9y#05})ri|7Xe;P!81UY|vQYbt)vXTToz5t#gA{oL=Cc zefr0mBm}0MlkciLEPc=5&*}YtPVeSBn0sf(#A652bc9Qcp&h0kfrvi-kb*Z0>6GOgIM^#7tuX>;s; ztF*c91QlruIp=HC{wjIIZumD^l*L6e)O81!uZx2LXj4y}%c3VMzoe=;@GyLgjcbdw z{B$_`=C<7E`_=b8&+GlM;Mlgc(c9m(Iu>Z3lZaqloFL*Z@Oi5Xq)&B3V8-0JdymZh zZ87Wn)ZpcQw%z?7_q6EGlKcHc-M&(+C*)qqo9-L$13bGgE!Ur}@!e_P2d}!oACH{p z?8;I;FndK_s(`?oSk`x1<$*uey_e$9Ti+5JnG>2KpfIQQ`|kU;UFKZZ9?ogpS}@x% zdjqSwz`E0Eo0)TSbN|lJeGx0Eu_eUSL2!jAlSR*SwR@-cGo{vxUjF&qaBlsZH#axC z_sQ8-eOkGEo>jBpzsw`*f%01ERxjgM%(;6y^bwDD{bc`-zyBz8r(Rh3iN9dQ&GV*e?6y0cXBRK`_!IL}e%~Lr@BKVi&i7cm_3#W$iIzYE z6C)#|u6&W6gBA}=xgIBQ9CHl!_V%`3*LNZRxKC5gmqrUvIBjs^&~aGl|M=_f*&*+E zoDY89)p+rbs?YuSBOUXsznni9d*H-BA-8Yq?SH*kEZ#3`eeC?(P4*wA+RlEYm9dO_ z{*Of$Tpt?G(|_|^{O7v!8+J)4IV@eU&Q4%L^+WBvz$>0y@frIR6)KM`_i1|>^OI5a zyYqDUm8T~+3SQ{j%WC`j+S=DqTeCu+|M*d%&ZqIJY2t;8w{PF>y)Lx-W9MT&39h-1 zl%PrMrI@{3jXWpM}yGq*b?)dZVN%_-bv38v5KbL$gn!2^+rTDIx*7wf@ zXFhW7DeYOknwjNEG`z=iMyTbD1;t(6kqS z1ozdvl+bQ3EG=zm zxZQX?aO)N=KBjlJlMMbY@t$IL`Ok(6G57h6^~6^{Xm53!-}kcQ(eK3BE9ybL%p{J0 zon4bR{8;ikvGcNiiK-;Sq4!$Vms+m#d;eV0U3}Onc)y=Of%QA9m6O62GcdjTJISEd z+~=sjd;Fyr-Z3?vpC3Cd=i$7(T>FW4)Pb3gr|$UG)u6P&h9xFdEAf}mvJ%eSC7j}q zcPrOEEEnB(;mC9&N%7}xt;_0Ul^&GeDvfYm8zjear&|4?%KR-s`eL4k8~i?bSDTs$0vGyrJ7(keVje&vTOU0F63=9m6#X;^)4C~IxykuZta7m5uO!M_+&|+X< z;9y{E&tzZ$DPdq>kOG;{z`THwff+2q$iT3G2`0o2UP)3hq*s;9zTNOi&o5MuT9;q=E&r8z<%!dNeoYv0Ymmz5T_C#eKK3KR!Bo z`uMS9)AS=ZrTo0MHhOwrPmhio@4ue@e*Jstm?O{+5w6)4Z$x%=rU?e%xIl)a6b zlh~2UXdocmpm^`(_dCVsxn{kTcXnqu9=yqZ;;vshzic|~vR?>$&hzL1l|Ma=)+<{! zvxZ-Mt~5(x&HZh;x3`vreW^(%Ks#< zU;4`<{~CU2ueQ*9WKyeh(7`}}=Zz4vN!uOQ@G#}m$`KNX_yWr-g)YEx)RFxU$ zZ3kUUd+*(>m8@>{Q`|53{B51`<-c*J&2&49x%)TIKC>*%CID=V1zX0Rn?Ax#UsZky z)t=k^a@lOX+E0_`+f1EpK6k3dIdQfmCWH8INA!+-yph~5yEEhBBKakY|IA-x?>6U_ zeTDcHW+@&}P{|oPOm0;Fl=6T(<<03YJ3C(Our!>;n5dw}z`SYki~d}Bt4sB&s(;Nd z$lvtVc+gj38 zFK7NX513)^JAe84&jreRz&ZEbvd#nHFB6_Vb~$SPqCs_@){N!%J1JR#4KmWuJ;a@JF;xF9qdQoFwV8A2eaPt3ija&McCCc<{JwIsk+x>X( zk6HLH6PL+3hBJ%j{_V}LN&X|czTzlrR^`{mff#^v9O(Qj@atNlzxd0V}@#%`}4FfulzUtvU1IS?`d^Y(vBG_-Y)h! z>|`LobEckc-9lrgdT)Jg>(sBWuJWJ%U(jxRXa>WXi*-xoKUj3hJ^k%GOZ2}v7o&ZI zu(yE#4+}GQ7vnDX2fIxKE6n${{rJLq^<(9wn==^BeCk`E|L5M9)_QH#fA`g{SE%Mg zl9Eme+lSK~^Ov4p@P5&CzQ?9VHl7gzUF4~{=pkGAx63d8m0w=@PyFnn+XbTF$W|~Y zF!yEp_o&bM-H!{2?Xur)D6rVPFg(VRAofi`u5n@fgWtkduj;vXM%!`g9d-q~iTNbM zTvgj{HKux(eusFI39=Fk7Cc_IS@K@wm()7Vze@L~YyRBd8{_(Ee&W8vH3kAaJZ8)# zZC^r5>aX1VvgN?-YwP3X_h%b5?>0Wia7JLKv{_C>(UXbp|Mc$O`Q-j2X}x5Ta9T5{ zc9)1^d%^JP^B1!#+b`=2A_6~%(DH)43|A#3H#f8O#8KH-~37c?_JI?^8uSIz$3u?Wygc# zFP6Q!AGfpU>EDoEZ>H)WYe6yZrr^JzVDJ32%wEe^jlv!VZza?=h=Yc)pen}UXn zFZ?IS{nTH4f7w5Q+KqK9>`kJ;AeWc|1RqcWHi5*G$+e@1V}ae7Sulmx&rfYvjM6 z3yBLUQ=p;1Hjz>4wD?8eM)@7T-|e37_wl&OfwxCyFmOnE{QKb} zS8#x_-ogCu-yM%Ny1-G+!pscv3*Tb-yx$L&PLJDlouNGWmUDlw;=5e}X9emfyp#FM zv{5F0Qs)0vtp~y0e>RPAm;3|S68i(YHMnH{Jj!X9CphEwfoCUvJJ~ZQ2ruZ?-}mFd zRPFG8^0)XW{(1i5v_u{_N@sYoy{K!rzc{|A*7E0!1^x4W&h?!B`PMh5`3woqKdk)! zxX=1seScq{U2)v|wH$ijFi1$)F1f&)v3h;P#z*;n_J6-jO>yb}_tv3cJ1_r=M@?@n z-s;c&>wBm8ysiD7tgEa3E8M)qc(-4$3>xGjk_*cB{Q0>l_4GgSgW_M7Hndi;IjGxB zp22zPtm6BH`c5kUHUE8N(E;1#nAR|l>j&?z((B>@@?s#xR*VPA#V0qM4Bz`Ibid#7 zfAg6gAw@U~Gj~&C?aQ|J<+WcH%YS9Bt4i3Rz~P`S9@Abt|Kj`3UTO1nOXYuAwy)!U zSbGuFx&gHzbY$4n5?|9JE?9nF8RG6@10biGRNcvsAc{lb{eqJ56KCag4q}XNkY2b)y zYGggZa97}m-Y&%tvRA$u{(3Nvq1Gzl#mxwhbcUQwpFz{&oOi$e-@T<=`6vJ1a2qQL zPzB>q&-UV|!~eg#UazaxQWU$x+c-_+_hn-f-51(+!e5Tn|XH2Mjah^qI3d@R<_IuygzQ5fq_xpG5t)1LU-~UiL zE(Es5;6i)j$B!TXZ>#(JYwMLY>5RV=+P(J#oMTup-{Z&am$&}=U)=iN;+Ooo_k~~7 zz%gQ*lyG{QZoT(=_5%kWHd%6S5a?@q`{MuK_x1mcc76LFcIzPb!u!=>^tZ@Y zyxDks-^3{ek_=FLYAe0qAi z{#%dnxdYXV2AmSVQg5g;+-R)%QvY;nxLtQ0*Qcw_``s5eL)Gs-%CJ}L-wN@4A6o4l zPyI`CcrsyOOj{+>gZ_W&{Pur7T)%wrf7il0-4|CvV=yzZ;U3?Q&+luVTTh>CcvkUp zQn{U!h)G-H~p+FsMJ+>D36STn>gM+k*%CBqDaDot<@E(BVA8i!ygB=9^|~gc;uOyfOKbKd0{P z%l*%+a+APu4C=T3GIn^+{Ntj2-N){Bfut4koHE}y4;8O&w0pEbET4V(c*&sFF8F{uBa~at8KVa3j=v0!rb(-O@8RG@z`W*Ea zU;cl7{N?|jpUqt!-~v-Zfz9p3^yK;(kFK6(a4y$OxS_`IBQH^XVSMuMce~%$Hz~lf zRzgC!U|Qlk2~i*Ke;nIZ5^W-K(;4KcKOgz`%2%mZ5$B zx2@}Ay^k*3!(-qh%uxTU{R{E5~Q$4RG|Br#7f?S zRWsBW>ezcP=>Jb#t}EA6D-sKe=mZ6c8Om(mZe_0zpYYoCi{!Q%%Np5J)|owZ7Zl_E zIj0xL00?!|Ilu~#$IquI^zb}AE^;(CUwhw>OM5f$M{~)>y!PlS6CKiXfWH0 zpihqaRTsPGxjFLO{msyp%CsO}z=uUG?%Ss6w!LLi_a4hYn>r2#8`ODp?$!VQJAWqc zi-XVR-I#5@>#zyq1zBTH^IV&`^Zv&hPr`~J7N`PMy6a;ttb9AnSYe`68h!%)Edz_H}7ytNi6YBMBue7|`6 z^32!&Rby+Pvu5VF{QI-N?rmn~<=XJ&@{zxnl{~ojarxx{v8xFR52t=|mo2^W(B)|8 zmxGPQ&u2R>Pm54vxFH##_TsoSU+?$-+>#sl*ld`?@07(^y?!34R&Vkd5ott7q1RmTUDgy^!e<8&qg}J3~xBz zOyS%W{W-vjc|*>F?>;385;N4;Sewo$0cILVGJpaeZzv|X_es17AcsTl< z_y4k+?3b7QUhlcy=GXH}pN?v;vE@2=z$rc9=clI?TV%|ck{tJ4>~#3){Ak(1NZ!*d z4&}#Qbct&B$;tduz69#n?|9qz`qK34|BG)`ybrCpVn56O_vNcbzrx)NTnw#@PF;F` zx1_4+UccSrvoAMHJ$@#qA<&e@ zsxOw~(J^6DQ&+#VYxe$0emft;zD(P-@4Wlpnp5$|)}6FzxvD(#k7aMa-m+?@b)NG3 zAIyDu+x(CFtQiO1FE}E^qm#q-!n46S{@11H?|gNB>@mKugy$EF!^!KiMsDkKZGu`i z^0J93G4tEi`TzQ}w0^eV{zvjce*YhTvHRyBlwPZKG3xLZtV>c!^?2P78!W(eE&(l3)HT=1TL_uJW$ z-_GXCiu3IDft9=DRA=I$@1H+DUL7=2m& z#JB6~Y)x%9{{6VJO>{>>LWabGJDYmuu~;-%wb=8|jpi@;-}aD)p^TyI*b_%~*)3gZ znZJ3y$aOwwZj4QNkmq$Se&;LYm$$61f86{xdGX(WYd#n5e!THTL{nqyVTQdz6}(@?>Mj2V~*eX|F2o!dtA?pQ@{S}r^fzjlT~gjVpd+>6ZC9C z*J52g_6Ktrcbxe!@%bmGkifYrpjToB7&)PTs!nUVeWn>g<=lT(*AFxyT6B zq6M+h*MvNk-KVx&YCVx;aapWfA%806*SX$!pY3<{EB?K4Yx@%AFk9Q`E?)zKIXq8h zpQ(N1zVqi|$vOM@53l5T!%*{qufc-Zz~cVijuLUn84I#Mi09ZS&#gK*|I@_pKP2it zM(zKXD$;aBzidH&q85LsvMjs)qVQ~E8MY7ew?*2{mR~rVclrgE2e-a#l*KW zw{+!4a~bFT*5DUsE@5LZXMA;@>3Y2m^Np*H^BG?79VnY}k5NFlf$@)!+X?IR2Z~kO z{@>U8$*}k8`@=6kuI)dy`%k2A@TMgtS3;7)-mZ6vnsBUE>wD1pyQ%EzjIUBBB}w}G z=TzIwbLVJIQU0MO^;2a36y7%u6?|iTM`+lr{ar0mFr|#MptNki( zs^`Q{+x)Y^!L*ehf%u6$GI=r94VJG1r zbhNkb`rWU!Rom)&`q#Bi{pBIQSRiOuuiLcyn^l;foVXjndoW4Sd9&lHk3Z`w%Nb-k zwyi6T_9;wRVr;glW4_OiTU+OQNWb5$_4mlf|H^A`KJQ@a2{JI)!J`wqvuJ7k6`6XQ zU*YQYBFtB|Ct5Hcc=G>8|NkFFXU?&8{N^x~c#-m;>-N5>|GrkgfAHk|`;W(C|4Br> zn0&c#X?VYtl^Ex~d9oo{F@?v!FBRO}I_FR2qXTuZn|sUSrY%0f)xp%S7Uw=)|KCsR z`Z;>HO2Qc3H6;Lx7=0LC)EXCZCdj?f z?s5L{q3FXwcKzk)CXaa-5H^IN*Vu}*jD?o~>%0u%QbGw2)s zI`REqxYhSx=P%}S=d5<_jxsP{;L(ZQCGvgNOY^;-*OxHtI;hBZTD8H&n$`XP4nc!y z2?+`k6%*#YweNm6CBFW5Wy#68_cr^lzj1U!kZfYH?A?nW-F6+@eD98TjM|CmZ#KI~ zzgl&rWY4O%A0OIFf1I}aOZS>1o>msM4ZO7p2j}}a#O{4*&FmNd-@nA{!KyFG=PSIU zB=tY&EOE=cXYBBP_CZ^QKc@4)AAF~BAaMr6hrWnyIX9K0IK&ebdQ~N6yyuDWFaKWu z^i=-`|L=W@AD_S1RlhpNu-I?ux{O~sdyPE({>7ZHn$xlHj*e@PSA&F!xK?z|;w8V1 zsf3=?{Bp(o&CZH!|3zot^C+}>=lxCF^|kSJrqG9HC3g<$zcQ7bl#n22i@(U7c%ZAS-0@mIG+DD-?{(z!<%(BuiCAjre5;O3^q30 zq*l9Gb@?-{>n#GFt0FG9tMRRwwtA6DN8Ybj+g7>pt)0hx<3VzL#yt&lE&;<2zFl|j zbv_ewT-;I4rsMbBrl{KL>7xA}@rzDgn3mgbq!xDYKuwy0KSNE&-~AutO!-ecPd0d| z>M(9 zIJbIjO%Ux`Yp)e*PxKZ#FG zNU$?>sNd~e%$bmJj^ToG!_RLoe|`z=j1aS8?zj7xV3mGY{qpyGJ8j`vU()-^0yglE$z9{?uU%JJAkFmpx?yq|i8;X4Q zaAe$j(`&QoqH040=LMTvnu1jj zR+-l_gTB5F={aKiXmfcRroYhAO<>1)-tt^6jl&n`PtJW;H{ zN_X*3t$W*FUOeIZJ>SE=^3VFE%iq-RF*#->F@v4wf+WKeslSJktv8(B5W^&~(3|1x z3#$#kRhZ-p*uLES=f9@9eAnE0)yJj(rtHy>J#6~Ls_VJJATdVFosu20|cFJIL8YrxklYTG%(>6k6U)!q`7$tEj3gI_)Mj(alUXLIeg z^X*?*7hh|cn=@17tx)6P>rwmM!d7st(9Jkk_8|EZyOZx$vn@sMw<_kZ{U6!>n`KAz zfxE)D<-Fq~{`bFB+okeh*5f+UxBLGnw09&K82skBP+p!)5KW2?SJw8|FcW< zZLizi`#M=<)_0EnMQToByUlJKzNGCmX*t`!ZO?Prei`#L{IF8kli}9NI7L@*vCf3J z`PW<+j1v?qr1$RXJ@UKRI$S)ZSKmleSIO_=xrYAr)&5q0Pw_85{&+6qrprmTuX!%0 zHaOZBn4N1|rfkC8QO%IE-7RsO#DR-;_g<9O)@{4r&%0N1t!2>phDmQMjLjEncWj&0 z9J*wtb+*Hu)W+Ep3vOTjceBrG&AQK%C%m0FZ_d;62}KG!cWYc%?Obr;h*i#lrLj>m zw~jjLF*>B{8YD_BE)@SBKBx3-W{Kwmt$mIkF4Xojfhv=05)#We9p*63*t*`=LduLy zZ}-za8HisHL16uWm-(@6xKcJS`++gX$#xxk4uhbtuc0*&Rk%Ya5#4Q68^dW zf4{Vr-hbp%1S|9A6ApJ+EV@(PpP1S^|4UBV9L5`h!57VPc}{HpJAXm?@AGQ2EsWOc zb6(S2y(85!%yOkzjONv!)1L6Huexz6uwr9x(8i$CZ}s-AdE&=na-c6OIb>C8Zhga= zE>D-u8V%A_QQ8G}wYI*i^ZS%MwLhoUp!V0K|IX>m>wNzIl+5?iel(X+bbjDWNwya} z2MlB^j4cnBa3*B@GBS?8y}?SBCuh<1t94&j^0wv>zOgKrc}lfH-$>zs zv3He}qW(vt2$6_0QMYp3);jn{_Do!V^2=N4mvb4v$Ih<%HFN*GKC1^a)6}>Q9^f%F zFcEG@e96()X~AsZeE*xl>>sy2vwkU><$uvPWBdJj+y4`KV~q1}X7)8{#w#+0UB4LV zDm*jq_K}%Sf23s#ne1J3xhj8cf$jdc{+~CzSiyT&%v(nPlEt4zGwm+822P*1p{V(I z1E=@DjPt!Z*S+pl*}h#LXKI*XIB~C{%dbGis`E_uBtQ7y|2zMs&aZ__bA_YV+Jc;A zV4zbudEN%eNvd+pGLvL2`FDRjkX*iW^Li`G*sVXUOZND`|Gaqi#Z@gw4ZnLutT}nx zbn%Lvp}Swt@(j7YB}%>E>XqD!nyOigbxwKiDC!W=X$)LFE8Tgjo(jujb@s^ksEzB+ zn3~;4V7qXCsV`H=`m18cudI@s-*w=d-rk_O2@mEnMsK@h&Ajf%@{-54uP^)CeqQ^s zVK3i1Q^83I3H*`^-k10=e4B7MGkp1Jqe8ZXTamoY((m?k%zOJ^!?wxI{_gDcU%xL? ze7JIIcG0Pw+fG$aYN$3*`5gFZioUXXO2yVi64o%ioQ@~~ff`11^_ z%}U)VnVMBgm+<~t$N25hm#&*p%dad^x8c`tGUM*IU36#BY(XieW9sggQ@@;RxGlfO zaPPD@nJ;Uc<5!*C$ji2@yK&)61|DDcM?42gYPTQelbDeH+uwWs@y&nx?l^y%dfKgNCTdP=w$9X@T)0kL@wLd_YsL+#O4^x^R2xr5gz{K_j`R{@*etib zc<$`E%eJ$JtCe}lLEMRUwU(W&i2)dpRMdM%Q?tA@lW^cm{(IZj$BAN?DxAk zS$6iLrPG5R*QH+eICC$o*S(?FwtJ?U(3K_I8BJoAJq zQcg3!oGkvI_rAi9S{3Db$ax>o#BLV`LE&&6zpw&$7*x6%(A&AfT?y8f)?vm2v7 zT4THhNh zwoh(4B_?U`|J;m(tR*L}*mM3;n4_vG#m8DQX}TP9WY&wG%e@;G+Xk)7xjip^<=Yu6 zuQnc9;&WfJ;2-bu<+ESTW&Hm%a{s)(xY{O(&QgO7+&n%$J~zzk#IHP4Ey>Vhyx`kV zVVkUT@W2$w1Nmp_zaP_I;{WL8Klb&@x1ZL1_T&`XkD4ti>*`*vE>&^=Kc%3|z(yoG z>d-dNvL*KwQ)ezab8WjuhwRoEgIJ-D2PEpw`ClmF+E=pRnvPZ5ga(TT-|`qP9=&xz zMy>jhi{Hnf&m2Fu@A13P#>?4r?bLd%Bm1sNFeLjaSXGD!a;y1uADjNcn%S=8pZwDG zF{NC4jURM!$xls4Nar~s*)Z48g#B$HX9Ayxe8Tka_GRh6ZiUMGhv)y(la+mRvu>t+ zRM116dsDku5-lDvbe_D_m+v55{N%dUX{jqm)<(<}6v@7y#2C@ANl4Un`nAY8=5I>f zwJp0Df|D%E!+F02wwCHxCO%(f_f0*wj`y~z(#I#a1h>sfG35O8X64auD)lohEs_gd zkL9Q-#yWlqkKNii+iH96{Yn4+cGdfsU$AD5D?gNf!J3(GWv;8P#EZiR4CXRQEW7-y zkZr+vakC?)%+l{--1Wcxl(4;BFD?J|`dSYsrGv|s=$}}kX#Si>S;6njoGCnPtWi~K zJ8ZURGM#a=d^?-jaTV*0O=@RLSFN9Tjd63ExKifm*jccoxVCptt#}1z+Ao4#j?{Y#V*#$O}}K#oLBvz?aSr! zzqWpPqrNvLG+CPGOKQT5a|}FZRU0grFMR(k#XGZ6_qLq(^O)+aU)#O!&3{*2Qats~ zL~$L#ori-aW^xN9yf<=kUpwo3P4>ZQlePr!_4Ua(VUX|Z67hV?T*EyVgTKqoZhfNsmp@|sXC)bCG4Px{ zE&1$wc$&fD-Jcise#!ao{zC7=>B|;(^_pfMs&G`hV4v=5wk33<8keUem&#n82P(`P znLdZDeskDVJV7tjdc{gV&8bh)mjAmtO*QD!r2p4?DR=62^-EI%KNo-vdsFjc?;f# zuea;f<=|%1>d9nqc;8Dc{8D*R)(G?o(aNWH>wKMfS`oo9k~+Yg+MaN7s27 z-Y+{=u&tQYt=bc0l)Uc1^*5n&h2rhEoG6_eSb143%~b!nM}jf4X$;#6pKX&SFYPHX zQQ6wMw(w2s&NH);jzlRY%c_g>vu$kJ-uv$X+rif_=Q7G$e1H3L&+GjR-fg{31`BvD zd|&o@^{TVXTV-b%Gi(WNTc*$am3P+hgO6_hD}HhFpLhOGD_76`ivH%mJd-9h%33m| zpWoSL|I+5(bKiF}W4^!p)BXLB*Y<4z*B`X02(0qYSFb*LEVi&bC~w89D%AxNuEnoS ze~0>h&Cd*P{x~b?@T8ryQdWdrS&;M8r}aqO_JFm~s)t|Q`}O2b@^O*E6^^U z%(!`K>IIWuE1TY*Q2w&lvh;5J^4ITnD((ID!+iPQX|fqvfgEfGkuuX#GbW|QFiA{X z|K!V;$Iq(QKe_(%-p2>LpK|Q~cEwLst66%AX={D!OR=>c9sjKMo`_L;I3+1_v&51m z$z>+@pQU7dYD&|$%a(EzmMVF<_^OcOT%WT`Bs0D)IcPq+Pi)J?zfZZ2>oMf!WvjX0 z=RB_^Fe`6<#MS(JWxgCY%HHky$$0PN2fvoGkK0t{Ng2CMZ&&O1`+5EUq+1o|LtpN{ zq}rH#-DbI|@YjTd2a*!gycu?cS(r?o+4w)~Y$E%QoByQuO|UDz7{BZ+|Ggv2|D3-2 z!sNp4jd6Yd6dUfCM*Q*k(mQ8W4u8`BhgrG~MH5?}+}mg7@h#JKLzjE*G2GNUY_vx;~b{$U6X%` zD@~2aS{z$+!(CjwU-zLp%fElo^%M4$JUyBD`ddZ1!`XXHYhT76I3R4`<5@i8m(hp# zH9KE4)Nhk_FWj+nwa$;(OCQ?(f9dsTq1R+audP$Mcn^HOSa13MocXoRxo@0pDoyj& zos&@t3~ge1|0&1l+G2|nqD~WzatXC{m99>FIqku<`lBY%5iE12hw+zAP4)0j>0B|x z!SnOxj~Txt-RJx`*`=?&<+!QTl>)ZXpXrguLjMLIHfT0F&vTUN9{`IZm2Uuyg<^U7qA1P_lMb6tzfow$G}g`5cs%J0ju*S~$nefj!2yP0*nzL)L1 zcs_E+zs}AZr0aLtD{atugx;k z`+AAn^Gis$`Igj}NYA}13Oe-*e=PCZ%)8p@`d8&>6YfwMy@yf?)mv^9InRFc(__zE z0fy|9uB3jsBVswnPAprq^6q{64U1_LcV{y zrdBX0b*@@`qW#W=)f1QAJ8^TuiHf5e5;Uv(lp|4r5Xq`ooncIxtc zho1M7uT@W-^5o7TySY8Dw>h&OxoFWNdDf^mx_rS^#*!+vawCU=$jqXmraJeAdy)m; zr@nmd_iJuR#U5jZD@TkPczDkGHZY&4ZP~-lb7S-0@9Uml-duCa>h|FWx}4o!K@Y{W zy`~vNh9&DxY23^j5w%L$m#IS{?#wC%*4$LT2~!<2SG}32vD@(evO}HnR!=%x-sqZl zm3KO zBE#!*0yDnrY~J-b-N@`!6@Sm==K4QXD)&qaPqDsVeDXkQP5yyXPd}bxxW~9riUYK6 zT41+k6AQEX<%|Eq*Z;}b_4uH4=7(4p&jr5Bs;Vy?@*bP-0qvh!8_Rg0rDQTh=_{OJFUCaH+R>RqYocuHeR!LiP*EP?ZnOi zMyAzYLXZD$^!Ykb#Wd3RTG$M}E8UN@R(c=P$}gN@QW5!d{*){H$qrGwOMa|SSa$1<$EFSFkXDp4zyB#UTE>&yiHnxW|9T z^WXl59xDCsTJq%T)ISRkWFF&vFk$MdWo?sGcF1Z+d9A*-nML|EOIOatMak?NzhjjN?k( zd)j-gs=l0$i2ADZ>%=KOQ}ej(JEm4#IQG`>{D;_0G674U7u@Nws%g;{rz30DO z7kOEb!++xPi!<(L8xohC>(>4@eGBtGt;^e98#S7&PQ5!xnbN^kHvAD&f;-mX5u8V8c<3DbB`|)DI zy;7F8baU6|^gITW4WeBgvzEqR37fUq@ovbQltjkqlT12o zujZZ=E726(<~z?ePO4gO=c1)gS8UQ{@Y^JJe8#c9V+-%u#HGI7c08PU!?$bV1-ITW z_JdikGT6gEg_K){Y|8%dp zmHu0H%l3|mQa8PgXQXf(T)F1rrh~SpZ??##uCmR$tfD2gR!%+s$ALxrG&H|FSg~{K zfzmY*^SNHhZ=2;>(tTld=f0;ZOj}G?z756uxRn?Z6O3Xvz07{Oh5NqiNqPNm zFY6c2Pv>D?t2)c((C3CVC#T#lT{_Du<@wsMVqXG}69{hPR@A7M%XDw@U=gLX*BrjKZ{rklNtHWYtQPDg5B1+01xE*)>a;|~5 zJZ{##@^8_XZ-&>;x}vscsRlc9@iB)vj5Ca19o}f9aQ@r;S$g}Q#zkGq{Zh4v$5X2;~Vyn>Vbm6NmOuegKnE1BOy1B?Y=u2)|cVVikgmH{iKvC!Q+fDO# z>1Eko;tJ8)dr#x0b-Y*$)8?#=2^`@82CnQ1tMW3FKXb|()J@@Mo$hjT{_4e&-TjNX zAK!Lee9bU&@`7mF)z))f{j!K``+H9`d$o6Ifbx5u3X8d)_I_Xb`u#4Ozh{c;l-I`W zyLHB0QDO!+D4&Y2Y4=Y~P`15YA6B=mUeNaT{mGs@H={R5e=QO|7H2cL*R$okl-UMj zzpSl1$(CO@8rX6Y7qM=v+btxpK5}|k@Bb+04-*V!Sifn1y41y{ldC1FdCm53x?*HT z&y}mYmN^#upS|qb1k>Wiuq*!FZ*~ewA9-5J+x+9tC80Z4j{2It`5LoR|Jcne-H4I{ z3Go*%UGMvxYN6M0Rd4U9eYckB-jg)2vO4sh=SIbO)0e#G|Igj>?ZNZIQ91_?%rOv{ z&hV_|Tfwf{r0iesc)xD`>mj>)(+Pn$CLXf+3qEshV0ZqYH&cy?dm-}`?MW5dmh-*J z4O8n6{0LcRwJY_1il5H4U%9@ymDSfY%k|<4E`;eXI%q1o@T#h}t9Nndv4ff(s{(^p zv&Y`ih&;*~>9;B4IMd#+?VI!s7`BFN^SgHOlFNFQebh~tf|~PrmcP&w^GF| zS3G=$;UpoJXqO<}3I9}8MEt_Gq`KbPkhCtv_qy!2BQv5RzV5V0NnYx;bdg)`OL+rA@4|HPWNh3D*} z8fBNui{9!hXRG<|TvC4^^=0q*|9eY5>~l|fdV_^|GpLkIYk&S>lH`uId2j81znXmc zZ-4GH>Az)DC1f*HQzx0RNX*R@YBf@^wF1Ydf&O$aUR5ETk^Ne9UQYJzpQo+%@y!+d#rpZ<#)~C4 zb)PTazsQ(rz5UDWm$>Ksd37o#q^WUcih>%0@yWa!W^5mF{#RbFHCuc8M{4D^`R_j6 z|LWRy?b|K$+Yi;Eo-STnwZWB_c{|_Pn5PqC&dpeTS0~@h)!VD+pJH^ghV|pHb5FK1 z?&S3;Wj)6sSehcTG4^}o7Y_Z8V(pJE{}x`{aF9uE_d+ogLfx-#M5s#6?5J=eM5Y`S4-HbvNiV}+~70#a+fKs{om*B{yr(PmrP~N z)U^NF=}E;;dRP7A+!y})f8t+SSxJAmmA+r~?cS95_MRjI17lDXkXFIFy07u62Hh7p^{*vUlS}!&wsk%k<+5x%RsK`1IxT`TxydrZ8V$b@c`tv+*I&gq8A^ zwm?vAQ1dzRl(Ha%Yl1M*YXip3pxEPf?Qw(Nbunu8lV^{&|}9$NA0!nC(eX05G%C-dcW zv5TxxF}m{kcvMu+FC|L@x#guk6AGFil+M=oe*gE?`iqwh=P&Ve++*ypvit29V}W#r zW%Dm@@Zho8^Yq`B-fTb5>Gyy6dPxP>Z>vB4r;4Ht;{&OTlv>?(X-JevxzF_59znGq1*FZ?W8V@W2*B z0pW(s#YQ}u%%5`pyI*?7p1)}Ezu4++@wqRy@hn(+B-x0;^~i^1UT4>;yuQJ4CM!?1 zVhuNos?N=@yEooCi&R~^*`(Se*YK-=_v*%UzEw|DHlL6xJ*y;^eTUcSiOai~0Kr+e z&hJ`k;yB&8ydkb_w@~%$2e&LN4HpIHCHhQWa`DaE^2D0mv$9id|8Y<7zQ*_8Qg1Tj z#K%c9lQObr+*y})bj6lM)7w@v1UX6@RHQpBk{7eeKP-OPw>$ z@LjUQn_>6)*bFnCoZI`R#ua_q`DJGJKcl-pFPyD872md*Z3lO#CDS?QwDTu88#$_H z-Ef~9!ccnHM7gU~C}i$BE5{#Z>Y#(jCLP`YGERHXRI1B!Jyr6(;WPn3O^e7SE`Vv+PKtzfw= z=W3O2f1NkMn`!&Qxs2)iZEN2>s`rR~G?#Ja*&9CWppBbW5j@f__qpevKmTq0?X~H@ ze+OnbE4g{iH#HPU7C6sbVq5UaA^WiQ?B~`m6oaA-4qlaFFPQj5$gH5P@#&K2M+JK3o{$WLCt8VNO41cCF z{rg1WU>;SD&9RIt9vR$u@@h@*F7HRn<`q6$c-sEJxp}Xg+;`u&y3T0x+_xoP9Sw6f zAD&)Uesz0Z==tALi|32qv)ia!`)$#GcYn^9S#|$f>mAV>Gk^9Oi zP;$qwzW0|l|CRUWfAehf->l>LsR~;jPH<0Kz3@Queve&j3HjX3nR5$PP7OIb@AMg0 zHlC{|EKW2x>(I58Q%}Hr{9XdxZ3&giICRqRa+uCS1O5h z$!_@_^{#GW;o>EA?}fh=#y6_zeog)^mEssKv(_iZLnNa3yKMcwy-TGtU3 z{Wqmyk?-_1=|)#po)2U9OFid)U0<5%`A=iLHjk(9wAU;TcE#oy`A|3uwNe_7{nREXz|5h(6bPdPF2 z-1&Hb_vPyFT9w>g-&g&~vA?^;gtwdL+$@pL7c`wt^4b(lUwq+Os-%{8w`}Jtk2AM( zOb%!5W!kcE=3lNGDUsn9w%VE$eoEo_%6|UNb-#uef*W1f>z@a^=Jc7?3SF+)>%Xy2 z?6v)=$n8^9R%}vZIocI-+>Z18lz`RKG*vT*sbBkBx$Eh^)yu!%tW%0$ z50(35@Zh0+XpTWi^Olo4wqD7xPo1bbKW6&(`zzEPH`@naVbWX?nRC)yJtMcR!(MKQ z(LrOA@6D&JBTrTforvS}-@W>_(1dA6%&*;H=Vh%cy7_B>f-@}mY zKO}D4Xw-Xj=(zsQcS|*1w=ZmYxzhPrZsE*4fhFb~kIGs8Jv#kgdG5}Cpv6w-OQ$S9 z0_xld`7$yw^XKo>pIiKP<(GG#iZj>9`R=gH+$An4^SR;FV#`Va#Z*?U6aSB`ooBe> zXllf~SurwG7slRQwQVk6IAbnrY)Oo;`0B>nQmRI8=XFWGZcQ_DZ(_O1_B1H)9Cub< zR90M9mS$zS&1I#&v%Loc7%njx$y;l!2%F7ubB%vSmdfcH-Pz{WU7w@*w!O*RcK5^; z`|T^&eO{_`bnDl!^=qZ$xX*1n{>$jM)1116Ro}lKZ9K89KFjI8)hlzFREV^7j1RRC4XAymz_nj>7)=Z_CqGCxu7pR5rBv z_bf0;dsf+Sd2L(RWN)RDp{qJ{j}~d0uW}KZ^Pu~F%=;OYB76IUq&4h{M79X#%$~Dz zuCUX_&`Gu1CN~#xggMU@jCi_Kd*YSLjEvW|tyspC&V6E1(q`rpGM;Omz3Oq=>sypj zV9|H`>du){Ux)VV=jN3}`WW%vf4!3Hw|h*><{iazZq*(Q`>Geu_2$MqkL&7%p>Mmk zZVGIOdQ@xRHu+=VubS7l^e?G4YQNNMyzj3r^DpSIi=xDf;|?)QFOK`1nAw>7W%APJ z7dQW%zxT)9m+#umd{1xw8+#>b!RE{NE3Z7}?SGOdv1HNeiDBVcLYaU6RK5;fb7k8E z;S=+?9!48`E?y@f-+OY4!gZ-lYAu%(R9QVYe|1XSqRXL~IXR1C(UzG_TDxu}#vip@ zkmmfh#ay26P1jA+#NH*xdRG6c=0D@M`1#un@0Z=3sqLEZC3wlyoaNg*?k6?%ZkzX3 zs`bNly~wbwTP$uIbuZl-yWKBu-=;Igi~pKhA4rkbiniHja<}fk@5{gOwb8%M?AqU7 z&Ze<_@S!1Ucc9w{`>gLDgV#Z#8xmYo_g1$-GxQb%|-U^ zr*|5mtR;sw&0E0Q>9~VuqpNh=O19?~e|u;B5-rU-+bY9s_AldB($=zIlTHyA@3Yh0 zwywV|Q^#1a^4esn?1a`Vwun79=a@a$x*a4L{;BKyni;~5GuCo>Ea+o-rugP`;*Ua$ zax;dLYip|-VxKD)Zw*y-6#8YS(YF0uxaxV)%eNWN?eepdk>A$4aa(+@^@CWW{AF*0 zr~C1@Rb-~d)_r*9yT|y!W$}Nuu|hj^{vPW7Zy>wt{^Y&CHQ1St9XQkTBF&Dg`P6~X zFFE#;{xkRAwao4R{`GlNn^DicO{)^~Lf*-KnDxb_xthQ2%hO4W)+=(tZ`5pwJE>K& za*M>d$U8o|8D|Wy?=VVT@zlfYW?6p6QO&6$YKLMDAA7l#C!=`Yt&-cH_skSdzmmDt zlf!rInzhEuJm0LHx6{<9VZl=8^QR-uzbv_FI5R>-ZXxsSxHM7wP?f}O`DG#fPc|eJ zt%{c1cFQV7*86mLbox}8=2hPhAFzLS{vdDD^qgN|x4s>Kd$&Wu4-AZXKEE#aV9!k5tHrr0PHP(jWcJR`a{SDvu(oIQMZ3OxMcrF>ZNK{C{njAwGi*oh z^&OgHdo-!L?)IgH>~c{(ud4TYJ*%ngnY!{o+3df!iuh`#_uQBK@FMo5^YcHP^Lc;F zoP5vgJx|2Tv+k9Ub5{Pbez?2;apUXD;V*91+1zWl{`1nV{=SWJsQuAXnndbUA_h3%2rfz}Pv%P+a}7<|h4zx(pq@3DuM|9KiwrQUV< zl8>{3n`2MUj}MblL)IO+7R5U|l52xxf@HuXyI&8CxuVMcH)a+sj1tujO8Tw!#Nnj$ z)yTYATn3K88gDf``McJ(!f@|xo>q5(=0C`re#OBUjKQt ze&ad)y@{8)d=|OhmzueH`VQ8`9<`l(IUCO0besFQ*yuq;;O#o^)Oj0BB)%T~s2OKi znf=ky^7ryxuU=16%yQp;;Nbqt@56H}^9z4O9|NzWu0HqU>X)ad{TKdb*|vSd3VjFA zXbaPwm-|lh+t&I2I<|HBQtiCIYVp={I@5pewbu=AmhUaHde?hl;{IukYYTkW-cXvt z9wq9;v+&XjMV-ZK&Ufy2Sdn)sYu@%!uPJI_#<7kOSGQz!N~N02IV_*M<^0$}>geqMJnHQ`UN zh)&*@syD0h=Ih>*{2&qg==Y7E^%K|aeEj<*ul|p@B@gV{zc{v6C>jU|H?(e;{`-sI z`EQR)_my9KTq_}yR$q8nq5qnRfzuTBC|@NZaq|lYUY6`=e5Iqb#sA)_JBJoBOWqQl zzcFX_t$?>d+0k5Xi%uomXC_|^yW4eT`<8!_6CbV>>tj{byWXV4d9ynG0&BMFmz|s2 zZ#FPmO34Qany7AjF)6w9%AJJOTJPU@In8~^wcv!;%8SLJeeJL2CXJeIH=ZAR-zFw2SJ@q?*MG+>V^^b~uhR9c^3PP0wjSS_vwnub z>T7GnPEVWFr7ZX4=%zJSH&0)9Mr+ma+qc9GVs~|fUs-YW<@6b${9-99f30x3dNxn= zvOZU+@%$$n$`37a9DTV0JIvb;rE2SIN@(ttbCoQQGjzJGEnD*Kw(`wFZ9Xzdl(r zzgX(HL#07pwCd8TgnlMClpTrG7skW#4W>g0LGkky%VHDLIMVeE0xrNniW=Z2rx%)1JA?Q!><|Kth{L5JFetKa{+gbQ_&GU*|s@8lta?!UBq(sK|WUtpK*yeNn=aWT; zORmZ8d(`yh*R2vOsokA1YqVCsStW4W%C`>O5B|Boe^$;GlJ-x5RSoH35?M>^=;)1s|wH@J_khLP@3D@z^2RhTH zE?T;=RpH6J7@tk+>dNjddG?E^e($$v&I~Q1DLeH2u4S@Y91`(ekWpB5ZCYyX+=pvI z)~d3#=W zed*T!_xQ^e(~svGY@e3ey*{qm@N-Z3H-&ASR+si?Jf8V-viQHBGB&s4+4~Oqwv|Xe zx)Tw{_`1;~*|v${&kUJ#BePR$UDwMx%{99J*RRxJK|+AGfrM|;`tWnSb7UlD6$Z6A z+D_B^*r6-8<*H2X&e+$h^6Ekrmj9D{&C_=yllkb+3hoPMl;eJd{9I!qro7H%smkLr zrRudZ7Rl>w+|s>hB_R6u<=Z+5dqsn{GR-faNUY_15R&re@1*>krQWYvt+EuOL#yR) zzbF?!_vl>VTb|3G!e9Cav);GbWBg#3wZ8xRpO>O9Nlu@8<~yg)GP(agFLG>e-?uN5z4d9DTcGC3nAi81*nD}mOE&k0na-@XnEv~w zu;u!>bJHTyxL+OmGil35S=vJy2a86%Eus6?ET$bInl=YcHpy=gN z&)BWeq382TlQ-0H9y?uoQ`2ktriQ-}8?;Q`Z;fxy{@T@6DHOXrIBJE@=>8s?Y&c-Lps`dQSpQiu*=x%epn>WQuD9hp6f~Ix3j1N!5_jSsylZs@G zHW8Wr^aewJtLxbYJzc%Vj*mR5v8D%v9Ls;4PrIY`c(c{@Q>T6gPwlzBW#QF3)qaI) zmo-^Rx;JoXf1g>LrpLYO!PVm3xx&HI+fLqSj9Bq!@`|m=Z~fZe?$~v7QtGqgQcE@% z+b;0gzV-d9T0_741`{UcUFG-T@OY=Ot!{f2%jTs|*?8BkSv&g^kNA_hjNO;Eznse` z{eI`H`;s5t9FB7mzF|>lbNI^nSwC+~-t6}EjPBcWj(H8OI|F`fBx*=wKl767sS_ywJH^} z=|-l{Ir`9GudAoxweXOa&AU!+nxqsht*UcrLD$cxZI^DndYd&@c(SB;Zp3%a?!6DE zxX0~~ed^|adnf1Fmod%5tR@3ghyr4MALDek=2y!e<`n5*^HuIG7K zQpUZ9R2laeKX~?KpK-l%Y{B8{X}1z@%y$J%9(Jdk-?CbDa<@?IhR4r;r{B7M{S%%W41EXkL530 z1#70nI&sVICzn+1b9ziL_1hYGeBScEedFx&l zSMO}S#Jo27^xgB@7S4V7LEvYc#sARcbu(lSg$7#Oo_a@`k#p_5eD|HF>!*bpZZ9)h zc6|HhcC(uAtKaX>ejaeEs&D0`#0PiQ9CJT@`N_7ETPli9-eD1lOK)kM!ElCSZE^CK z3(VJ-F;A17q^9=#UAtYyhR5%+HTiohlh1tNsi@+ftGK2?@%|OVY0=$cE`lLnA_ta&ej#eKMaQdL`7X_1|Ut+;g|(s-G?O@{a84 zm71V*yEwqI@a%_oQ&}(E<BHvReCHk9_wMwE z^Cz!KFEui7Ywo`I`rGBtGk)cH6$Wkn{xN@FamJUXP(LHlrO^s6~cD`whc zB&+B@R^negwS)fZt*ZH*oCQA^Odz+@kx^VZ*IIj~uo-we|BO!++x6jh5WX2&pmOzxk+H zjcioz-ib3G_B7`%);PT;??Fs}>@6c+f%HiJErLJ(6$!u8G=Juw8n?a1vU^LvKl6gU z2fzF<|8PfSn}447arrMzTT)JL+R3S~=Nt#+ z%NeK@39gPg=iBsn%M#`_FA{Q_6Bvt=jl(v*Wm+i2IN^nsj9c!xBdnasma#!APyA)* zzv-6Q$R?&xwe$s}_r_Ch0?v+JE3YJSn_g}?AohB{=aL7PB)QMXiLaQtZCZPT+QBVx z3HM7*2HDro^LP+>eB$kE+W%BiE^b_~=|XOejFwrXv^vAh{fQwf?^em2s;r+Sm-1z0 z(l@Jf^Y+beDCKNk`%+Wybyn#1XZ!zhv`~p_~|hx7;Kr+R4DrQx^8h`XoQl0n^$bl6f^S$OraB&rX=JoZSggW zID1=ZhjFUb+%Sd~Jza^G;y@P5COuom6-j+t!_Jw#^37g-|A77uso1QLQ_H0kUcbtI zyLIt36{U^AHYFJx(&r>!GrUMy(_S@Aa+&FkR~sZh@=Gl}a>#4)&r{!cnOolT-wJ&H ziE;9|Mf%3`N)(KDZca3Mkv?bEBeV9@O833D?r*r5bmf6e?)J;7M@@grOzX4z(qh@A zaOa!|$UB;8&riJjv1sYb6MwIr`<2>|rEsS9`NuH#JoX!repljmCL7OvkhEq>!?VZ# z-pn<-qrowI#*}MD3(8M^QaDpv@K&SjcGLVx2|9Y)nr9tjiOJeWbiQIv2 z&RIAwd35`D56fjW$v&0uk_Ss%u4!sFdY_DW&h{~Gu>^Dcx%rBl&iwhUlD9xpWK->C zX32v+tmhY88*bj;YIpL6#%X1PuuEF12BBVN3fGgau!NcE zToRpqjVs?Z=#A&zF73zOCPx_-&vx9o@|y~C#&ioW&o$Gmyq1P-$&xf!dn8)RR$FY# zlP^Vqs=GwJzur<~%5}e)b8*Y0!yC_Bb!}bJyJqd|wN_K^NxeylUiSEvja=zn%)R~Wa#~4}iS2Vu{rUcit~!^G z9C(x7naO2y=1cm63(S4K+SAv~e0rMU>xI@Rx$@kGZ!f(s_Pl<@bs*AlqqUse-Go?? z8=LAI11?{A8rk>jM0u~PuO55FPW{bHUyAPBJ?5%)&1jJk)Ay~6nd?mEyj+W9~)AZ)xgz#yW(XkSI=Mr@yHpR+DTKC*M`A<7cx^HWK zTchluFLyq?cvH+%`{(fXpZTXQyuQi5;r1=IfH!GTjMd$p!FkgR#_}w5a zxtpuA6TS;f(+f4rI=k!GNUF*%oDD7O8&~vHBQ}u^Lb9Jw)pbW6yB8Mk zJ^J#0GQWl4+r8KK70Y`4n9m5RQJI&VW1sAA{bPxV)l}z;wme~MYu9DJPqFAfbM-*h zzbCg=h1MQ?xp0Num4x#SC*6zJip;thuq`&r*JbI!MJv{RmHR0jz-^Um5Ut_ix+k%B zm&!b?zcc*Irp=u$@k;1=l&s-dwhMh(+#fxq9UfGrcobP)+$hf$<@t^E*34p)OOrls z6J43Gg7;RAW8u!d-p70;ufN(_`omaKWNuv9#bVichLQe;y6kG#nYOLX4S!yzA(qav zx9O0 zyaz*$6dW(|^JrSZ*>omVJNL{k+sN*p*@^nF zMSt12i`ns-X1Z)vOMH|B`^<&A-xMau9ZFRwx7D1W|117!QfE_=M(2vtdZ#VIT=cx( zv4vmY*0J5SYH@T!4a16L$LeDxH$?^3ZeI8Eq}8kY1+`q(TWm%D%a%R7{m4;rexF?x z&uqndTcbRU)$6|LMP8h-TgdOf{Eq92CBN+7{hPmgufgVd$xH^D|D2oob*Xpw66Nof z)6^Jlr>V4QZT)#M>b%vC#+vr#lD@vTA3eO8ZY}CC3=i5Jp^?Y1fd5ut%A6Oe#aA6- zveUII5BF`F)VXEBu|Hu~H`nJit#M!OeqceDdFV8Toy%6o#pET%Z&CUmqhjbJ_`g!= zNGM0@!Ix?CQ@pyB5}My^T)4)0w%MAxDjA19_FHo8o7S)Popvz(+sUHm@?|33n{*`? zmz~`dS75+4O&4`RxwXl|9srvPg}kxcMHuuI(s2&^QsxgeyF>y@#b6`Bs}LzcMH2? zZq}nIhFfn0OXPEAC+8Wu?>jYd{c-Lij0^c2=I#2P5dO~d$Qt)q9ieAe>Rxkh*15H& z^fuF^#7`cP_9{=7_Ik&D+@yO@bW6o+A0xLp+w(4NbieUYxam@q*INCSgH|c~bG5Bk zUswNqC`VsDb+ul8@ft1dd&MhP%l_NEhrM4SbcSJNct`yUGYOUdVeY;kwq0$sOZoD# z`&#I&d+ZN37RSlW_34(2nG#p_qyKEBhTwrSpv4O_AD5leT_5>*OUW5$_T?MZ)s~f{ z|H@(vV59s2oBV)X5t)e>7h zdEZ1n4at8yYn?X3unx9ur=JM~Ve)z_HcbHqAlbRK{4)PB3{tiHOUIsfKgt~ES-E}6;TFw6O+ zKQAsleOd6g{CvKBnN{^NCxo)(bW``7c2?!zx0gBnyn`2$G|SzFnZ9M0!fttWHg32& zxlZBq$up70zCHgw%ogt8K6s(+tkCLcM=7t+;I1EL&QS~j2U(u}E0*E<5Y4C+6BRJ| z^3Sz<)^YE0nY?x7ez1rS6DZN}>vU>87cO{psa|x;jIO!+_FR{Hwr^Vbt#75#mkZnXDd@&; zJb&l7h~OCsP!qv0>v{V_<~~bB*^2jzN)Dt+t@u`upWHDman`T4z<#!F8?|ZYzHw~P zZAHss zx75|9rrlMp-f}5F1-3AD-&YD*ajVGID9~}X$BL6DC4wIBblCCasgU~G)d`V1uQ^qB z2f9q#zAfPS(yPU#?^dr~?R8#E;_r`RVbgMFz872lHBA4s_Flf|dDf;^4|Bh)oHMO7 zyXy1O2E#|YI!)>>M_4^P`tjZWpPQo7wEAp)IE_WaZp_tV`&NDFV&=`d)t4R3{g>^P zTM}Gdz2SKEyx8punJY?Wu9C8-W&R+k-g!lH;o7j4C4v_NqZc2Vp%=M0dzt8I=3Sn$ zQH&d6#5~W0gp2YQr{;#8UBwr3tW1^Rpjh$>@mkHNHYsH`PPL!J9GHdg__AHN_jJDP zdzSD=h4-Z5*DXErKB=)+&swK=@}_+f&kq>%*0QhcZZVdKC`1=a-{7p z-aA^`e=mmP*ol{OP26I(=*m_I%>8Ho?q;!|>yJ6EY~K`Q&m>KGn6+IpVoT2YE&nH9 zUv^RV_T@zL-epJqFEE4@ozc5})wAJl<$AGCFEW}{P6xi0Ey!g!;;@P{@a>8QQ{Nff zNpr7XV_#*;T;%AnL60@P)s25`Q^H|``K!~;zsd+& zlNYJ|x?|0&WtY!-+?Lp{rf58MlSzf-nZmuy&(C;VR5iU;%&^+pS|-cy4V!EAu^D_) zbK_cFCv(lI7qgT-Y;k+lwft-=h6!>N8L3nCety3b8QmVIeoo|BAmhvjfxonO6e#7^ zr({pbsYz5YhN9IM2_Gc2Ig$Qky0=2__!Z!tq{e_eaUoW!7J=Ec?!VZsQg+(c5{o>$R+tS{|w|Dcj?;2xhbaWGNw#& zQx%e1A1TkQcTy^0?#jg~)ArU+73H1cvO42Qn2-Wv z#G!9}oBD1E`iD4wy3X}MGY`$%zLIYI(VxV z|7&>Oe<0=m?y?s*ALXo$u{dpL5q)dj@5`@lMnva7zA!^e=Fjf;?Z;9jp2y#=nEiNN zN^{6U9fKm%TJ!ulOWfPkFEM8G`MJx>zyBw{BzDK{jmKCL%4bR|>^)k&)?+naebuZP zH_owdV!vxODeR8C&d{k?Y6 zFJH8OyX@`lmv{f4ugbesp1S8x`nhi>b*%b059my?oxYCG=9kN2@>pu3@e=6*8U3sF1s7nbv8= zbH1rP;W4?qrhlCG=P0gNpy=qijK_6ZQO@jEnXE-?KOS&Y3R-h?@uF8!7b3dau0*!E zgjgNpo%?lVLf1{1nZf}R7Z;UTM6G>&?Xkx)+f_Q6)Al)s&R4i@7JB0ADZZ0cUpV5w zTHg*c&f30-S(f<;)6KH#_(z$!TT6Q*x2LT-UiCUR@0A3f+@imp*Z*D)(+X~PcXPko zsb6*U&Jp3b^tqs+8mp(pj|;9oJiaXT_q+LO>Q9zBc*`&LJkYzCb8@5YJeCV54858a z|Gf;zca{@P(J=Kqr(m>fQRwttEmE~l_B$9TiBDLyGne-PqfE2m(PPZ}w;zpCjZ1I& z{qT2_*^btkLJr{ylTI`|YW*vy>>=VC?des!q!zAyX&D)vV`wFr9P{mY zset*P%K3l4B`%%*ee?CtZ$Gc%33~BWLHTtCLq&tqGM@(Hmf!Lf0ao9BJU_EKKzYW@ zWYGA{@wa<6?MZq6_tL4uFSawj=$GM^V~;7U_u3(2ur#oQYp>)|W1b(++3$G99DgQ# zGmF{Co?P8uj7-pn|Dp^@y1hStID>^`_4<FS^BQL` z@ch-&la1*;&5*a_Sa)NA^Mg~nzyGOY_}XO5XvDcdarQB>Z|#BN^Y@(i5zP{HNjD*8 zo!eiX)=2Y)?km}oT4J9clr!5jmyPx6yrY+r*jBHO+P*IH`JO4sPg0ICZkErTuG^%= zouah7H1K3lX5f}{N!&3@78v-d2T zW>;!xh52IhlRj;``{jXDv~JzGSC5}mD!cBU*Y~fMZNdB3 zo%+8TExEO2&f789eLuhN`v;5FJZr2Tz07>sHPs~Sl6+lUK}*$(v&xy4wn{sePiH!# z#;cNCV#csMs*-2R#3;cVVv1fy6-l~QceFwqgCY$syp6Jox@gIw@LziAqj>3@73Iq$ zmX@v32<5(giFsODt*6|%2)AGJ7F<<2$+`HP^p#V>oa>LqMcgyxTrnxJ?U%1?VBbB< z++3#nuV$Qiy;Ars`3|kV@hJtUVs{j8A-{Y~^x%ZD5 z!=!(UbW7|H_%6=uIda*QlVR-)mY$2gu2&=3AMcjkx7G2)`#DV>+k&ieeplIPHntubm!xvx6h0C+ZxuM`8-qiYeIZV;jD7zf9vJ%>ll`b zr869JKUSf%VAH#`JnL=#?Q*%rx9h^$6Q_E7WiELAimjUJv~J2q`|Jy8&v!X+T-dPl z!DQ2zjvJ*Kzqj%-WVk7GSl^!bZIx4QiF4HC1-mCrxY-u@>&w@BH^QuC&mvNl*6JIXMX4N})Ti&#PiHAKz3z+wP&<775zbvDxEWO~KZjr?0l$U+BM9 zkNL-r0;o7zRYzb=! z^NMS_=Pqp%{P(#jXl3Vw*LItKZPL25ts|P7&&9Xq=hCJSQRep=7af}I^(;5mPv3Ax z%Yg5AS?pcQCrWQBdTtsn`0*@j8*|UE@c60o4$qXFRGN6<-P3(%cCD4GJdgw6!n zx4U=#%7|fmA^UAY!?Ep4OCIIS*5s$vUR)3B(wSbvb5k%XYpq#RpH<<7 zpBA&cBi?xJEZSGKhH-LXu8x#=zU;c=Y@E9)5@WXI+N4=$-8OjJGO^&m8z26vXpZSSVH;EDfjr>IVe_iUBty6ZP^EZUL)$~{dtuP$Bu zTYjh6!@Ue|QyCN{e178I{I9+`pGoIt;5_cMyV2$MuiuN_yFKde+Iw5LRgN%xOOWBW zKYq9++2;Ji1ewPh4xesePPTtzm^Py$@zBSdo}(2xACzz8{4ua;o_O>VPa2P#Q`SOJ z&aJzm_paW3^>^&AywA0j=jyfm?**5xeie3gt<~4`?>lGyd_KQEt?qfqzj{4&emDP( zac0x&cPd5-J=oN6hAm=dkX-HU<+tnu3wO?FY2u4ZYUDYR#NyVfKV2X|>7wKD6X%4) zcdE1>7Gz`2WbB_T)zzojrg2l(e9gR2DII~22f9nob-!}o$KpEKV{=fm{kC~$#Gmy( zlIs`dz5PDu&??KP(JN$jKIw~k`5{uX@1x*Hi${|){ECux&)gTX&!a8;e8)Tik0~#= zu|IS<6FZM(r?%50U#Dgxe}|v(4hLrcN}s#p4|}`&S-G0O{;{7G7qB^ezFU0RcF!%I zHFsx~OE&ywY|~3Sv+8oia(QE>A9fw3F=7vne(}iEWwzJj+>+?okk0NRtIzymM-8jQ z(&rYBRjp+U`pgqHp3*wktK`wDY^*o??ytJM)1v={pA~l2)fQ(aS}GVsiwG}$acYz6 zZTp>X5>JF%_2@q;F%V!3tW=nAzn9y8(R>B(A4_7&H!oOPS=r*>!+$dW?}mTgA19cH zJvWF~IX(R_%Y63@eMS?H9_}xUSr>i8`Jx{4;e9gpdv6ss9p>Kex%$D+KKsXi&VJhd z+lI+K@HNX*e!e+>?bFTP-^dMHzrQuG)VGa6UcA(|gJC*jT=mP&J-W-Ao@+MVk+8VD zuYo&X;NvTkv|Y@0`WG^JA{IBy<(s23yIVfs^MBrHEVl}@UapyW^q9-~=x;`wey2J* zn&xyi__KjG`uze?MObKhC_nSGypC#gm*-qIluptLJ^|B9M+ zwVR(bpLp;|3V%9!$vN?$rPxQ2`C|UPS%Ehj?pXXP3Ox8ZH%_B+->vj@$uFN<{mEV- z82>T3LMYMi_hpL{k1LE`EHt;7q8@rO)7^o$Vaxr8HgBcxmCVk3FK1wUS@^}`rR|Yt z<{Wi*x|KGekFPbxgr_3#<@r=&0~Rr- zSem%zx*dT6B|CTai7T$=30xoXb!PXZ$9*=8qKYRUuspsa#4WM*UV-ba8YPzs=8f;( zu@)IbYpC`p>O~%958t!aa%SF%a3`P7Q}@bmw^=9fY+^;o#2s^vlsx(PUBabi3VUSW z9rK_FzaMK{Z4%|Gj#@n1yj@{Uu@4{Xj#RaeEn6ZYIZYltzxOI)_n|YM633tDnJe-0 zPPJJty+YXRMbOP?R)1IiSqClUu1N?iZuohJ`=ZM6m;e7AOx*mt!SBEN!^=D-rVO(l z>ZxzpSYERBW&GxoF&3YCPNs0%u)XleQP-~1=$04Eay@?8PWGKBLo848&uKj~bGdn% zH@_A;(;N52y}Rnw(W#FnIe0HSduPsk)6e&^ySjI1_Rp1xz0qhq>-Fi}Gis;hPYSJE zp|St8V2ki-?LUX!i7I~Tj96gDq-nWr0`pXruxwQ==dEGE3eV$yQK1A+FUw%YD;Oz!IEgB#}; z9hmX^^vnOgx65a4mEQiOLn=#);IKif!X7?{%YTL_ra8c@lv?bGvS#{pcYZZDjF+$IO(UcBd4ws7tvsYNo z_e*`GJ!wu&ThXVYg$;*~{Z#kp@Zw^g=>D@#();Mln`gyE9a}}U)^>6Tmb;lj@NFWjFwXCL)b`Vu)UUhbrx z|I)*2%|~hElEG2Xyx{&_HZOsa z<(JE?Q=|m*?&zLc#iw>EwYf0Q+%0k+_o?qI9q*i-eWKB-x{5QWur*E8?}k};Tl_kC z_4T^ZGh=bJ+D zPZKn1CdyyCoTebJf1IBasmzh6%6C{An07wMVn>K^y#^0D*WChQlWaXG~Ei83G!tsx}4Xkfi)@_R} zHcI}bKfUgZf%}6woDWh(J3>VqbCc(qdvLBgkr_6}`nY$MjjxgJqDc;q4~KhAY;m<@ z3Y=!|aXOXvad+rE5&55PRK&`5(IZR3Vb^b~`5G^mnCUID+_ZSwZj-jk zpNn5iP}lSi*%xwtt^Oz1$`-zxfx9PPI_WO=Q2)X43JvW&D&6wpRYz;I{#;(cdi`&$ zf>xM^LdWv|vU}e9E?c^_E6lC+^_oXo{S8tKQ;%<1sM42rFZf?$&McXdxBAv4* zkMHm=o}Jn)DyJ2RygDtZcU6kzgrl|gd;c=0onQD{ODFQEsT5NdgUKz~)djV` zkC{uv)yVId)#tTe*m~yAn~74)PbC=3JQr-eD<^lv&;I&aW3lBP(Q95!JamZXOVd9) z^~OJrXO>xQa`qIE*W^`~Zaz4VdD{FZD{`D}`q>HP-kr3`;?(hNrvm-1F%&*JlgG=_ zFQ(09lXpk#RvNj=uf9+%Cg@!o*z|aMaM-fJKA*A z`svSuFXpv!PCEWH`Ja=~iPxtKKkHljXng#<`P6@-L!s}cTK+x1@_+QUHxXr5LmiTp z1D?9C-FPsw?N<7R+jTj=56;qIF0enqV723_MdF4#f_zhRcgG54@Vg(YysBaKbwl*? zy2c3p^BxW-T8qkWr*EBWKV_1=5dRbRr4fu*VmJO-5Ow#d;NFZG=1bbRE*z^#ov}JC zfbWv-{KX2jHyc-#87aLu)pu#{CZ(zwrd^ZupDb;f9%y%S(I#iPW6Dt)>dzM5tEgSaAvWG)AlHMSgo{r4}04)u|=3Z8V&~=D4%Zn<-XT z_M7qH5W|I1F*@g7er0w&w11*j^bz@u-VPBfSG2UnI95(N(XxTfX;Df_!RHB6*01}= z5!}PK++)gwVxetz+=bj+k9~ygJo_J62AVwyo86*fH~rX#n4c-T|8&}Te%-dUbNcyR zuEv~+*2$Y9#kc=Wv^f^MV9kaT4sGh{)6^z<{R;RYx$o6?nRTguPIK%K_-TwIcKjD)9Zbgc7R$TDAlV^l`8VtTOb!Mw9c zKMyG93RDTLKkpnjecF`cr?_8BdY8XaO^vEdJo0H%T*vWGKXdsi@9bIFG3WUd(UV0P zmd7eiO!NK8r|=R8c0=ORlzx?6SGhCg(w!I`)$VT)oLJZ9+I1|m;jFS(%!^06n}jmn z-P)6?+J8L#Y5SLmzczOE_ZL}kC&;-gx^KE$W%Y2y9;MUj^;6o5CiLGpnlbH7u-Nm} zKQrckEtQpzsIq~=E7&6(+If^_RroVlVT&2*#;aL;N8#t%YVA7HU%%zW`SQ)D) zu+}>-RVfv!-#CjaNKCW-q`Kf{P={m^WCG5 z9OgO1Y_G03L8QC=rJ(K^@uST;-|~fYJsiB8EPXzIT+%Z`M$=yB`l-*ALQgp2U2E3; z{=8tmc;VFz?9aaZpT#@-%(S`HT9Nnx^Hjxt$$^@!Ir%4Ny}NcUqj|w;_f{#5mIwOJ<8n2`RW+Zt@Okz5Buz;8 z-(^%-v9`g$Z|aQGuREsIOc854%Ku4u`C(-ft^G5KA8i!5uz1HAr6WeqlnnP7%#VLA z!N1AvBhyoN)d|NRcE4CBqp0@5HFctTeDt51j_0|}Q&{gOy?I`GNn0wVznXEarOdx` zf4IW7Y}m13=S|7Y@86lv`c_tRI(n`W1IrbL3&$0l5=vg%T|RYgVc1+nuV)&w=gZtH z*m88s!`;vRt*~Ad)5%yDxxu=dx5>i%K&hqcp?T)w#g#c0ik6+Xzuga0WIc85Q$&(q zQqu~Dmro*cPK17&@_735W$ux4tDk;$x8eCw)6>31W4`ld-vpW5bc=-D#!y`sb%lRcZfe^E~;~4*$=arpT+Vxbb^ZCC{mPro{J_n(-el zS>9ZBZk^ud_v_4c&aRR1ZElca@GV|FJ@3VVU2B)6SC~#?%(CWoSbDynZEdaShUX{Ivx|0U z-`5QNbUP(5?2)&ey4=ThlVi0Ry8TZY^(&Y7ZL~S`{EB8^=I)n?dtKJCb`=%;Eai+W zo);|d&GY%GL{osuZO@L2Gx9mr-^Mnr{gYyIf9CXne&romYyL#OjP(1T{5jq|y!>bG zg*hvo9nu+s%DtywSJ=(N;Jo0KY|^}}b#_w?Ys0lRYhKW8I?nO^fiB}dJrC0p-(0gA zXK7qOfUukLlf<{YtU`Gz$t?P*VcRo?MZeluP9 zWcLdt^DlQd73og9)A;`KVvE!VU5`HIFvgy8j_T20;=aYEXi>?;w6Wos;3!rq-C2Z|ZZWRANxZwQN8`Io`c=SS+nvdgpI8m+ZpvRv25r1xJj z>CV!fiMMC#UtD&^8|E7p*Yde}b{cInP z9LTbeQ@+!#+W0L@u1!dz#Y5BLlwyF)AL(F4zd4nML$b6_Kl|LI=jmwh|Gvu8Cs))b zC3EwhdKe#FevYGM&&7>9eYm|h?^E`Ekyx~3x{}PCJyTwPR=;$|MoFS>$q8r6z&j5= zK6Njc)c&)^HSgrd7|-WlcfZ}Z=)>`+$-B-w@9VNKbF%wX%=u9CRqoH1_xHBHGrep3 z@Qe7lxKkyeCX`Dej%c@VkOnCv467$6^n~mvP6|sGt7Slp~}6ZPlD_$EQ}xl|G96 z-?P3US$cEs+qD0-`7w=`ww|bRSBQQus1%w>ySCcbnN?^fFx}VOb#r4v-X%51=U&YX;&bx|Y6MUxKEWG)0T3X_@ zZ}%G~MLjCynvvr;P55IC*U{y{Gk3QC{PEEtV^58AvfcK~MHif+rifkg>|6EnV^HU% zkNTBsia)ukuqa8YwAoBL?p2s_vxc)sXYNf^2l*P-$9DBI%%`bON#1Kb-uj8_gia)jm9HM-nnTMRv}(1^Sl&=F&gD*H z`~4$>$LH~rN0}|lt;NsC%u6xkNqNuE%XIs@-p7<1s*G>AXUy53x$N1pj|zs&#cC-_ zlpKSWJa*caBp^H3c%Em%wMNaEzWA%6^-{P(1r@br<({vmw^Vw2BzeP7ZVAy}{Ps-piba!0kHvZ#&z zn?S?sAJmvd_U>$C*>}BvYn}A7DEsN_D?IC1!Vi6X^ZzdUd$YZ@%-Vr_rbRyISiu2on*7Z+5w9Q?X zop8n{&NAQVj$ypI-;{mPbHz#y)Ko8!RbKCKG5H$9T!Zy1*Y#AUKHp=?G0licV>9z{ zj_qkxXO7&NGIQSJ=eG_`T2ZMHyhzBw##8s_srer>);FHusQoB2(dm@t&wxtKe(m^- zNt$BrcJ_KT*UCjodh}D0KI_`**q?fR_4p;>jBR%V)wn*tI=sbZ)7_k(pN_tIbDEQN z1D{@;;Z9FMvnrwOzi(G;&}&r#Y0Cxw>ksP8_pkti736 zo!9X8_1dI`LXP+S*#a3V8#d~kTM;#-|3{#if1+z@Wy!arN(cLwch=@xa8DRdrl?O zsrl*Ox)<}Fc{oXe!P|y;Qm$(4yubUi@5e5yFQ`ymy)gB~l)DoCmA8S?j{rP*b zKFIctP2H|3N_LkQUz#3Jusr7 z#P2>@*t>n!xqlO9)hljz!6kOmL)C>j;X%+#b)HWW4;ypz{REz9b2MH$n|b(&;PUlv zBKNKD5Ieee+YK8z-Jq(4mwFy4%76aBWjg6_VCjtWpDH)yn)qx_dNW@pJ!IP9$u7l5 z-FD7wpKkBOpL+C%!hzn~2Cv^)TvC#2Q%nz>vn62Pjz zsh9sJZoK;J+HL-;iEp=feQk2c;$ir6)vtMpvU z@0@Gw=NZnOy!1Rvz{2lIo(tBmz1{bOx%BXH-JcVS%k@H@>i6=8K?^vvzy3f3hex?VDEK)poEwwCam76S`(m&l*>NU&r>-R6^c)jxv zG4o{o^t9-Rb4ZQn(<3{DlHPExe^e#I&sF}DJ=8BDhexRV|NA@DCwTgLOx;?yPSXav zd4c$A>oAtHB4Sqi9xMEEvQz8j)&I^>;`4CE!{iL!Uq6bheyp3uXME#Y_N|S96O>|H zK55F9n>E<%i`!_lB0~0Qmi%Iayu&)DJB|CL)%*3^P4=(pIIqTb>hAN6MLNj=>h^Oz z_ig^Fcrnvi<@mzgKO^ertBY-aRCDT4nR}{}+|>HWzTTEg+mCm)Wmd-7=I z@1wf$J_}Dh%5E}Ys_0tqUQ9Dp%QEIsOtM49q5~4*e&+m{d2?#Mh1jGo@t^g6QH|eE zrTd@SN)~S9;oQAxQbyn|<>(BgcvZ+Su^UYoAS6Yj@iz@Vt z&M)|F!J5gt=be7;QbiM!U0l)(??op4>T*zGT4~7e$IocvRxcgN#&iD?ja$N*#HDH_ z6>==onU0-Hd9%DdQ((&T9V!=6*%CND^fn$zKOg9^=#ERI=gd00)pzF998%wR_I$_U zKgzQv_nUlw5p%3oe6eNa+CzMg1^FL)nsD@19Adgtv{}RK+-Qm5t z@RRD(@+aavMR)&eO!?0}A?(}dfDJ9P)XzPB^Z)Jc?=vTUylSc+&7Gse67=ar^o6_o z7H)j!H1~R04ZFF}(PMF?agPsOP4>RFudBI!-SmC(2d3WXOm1nv^~*=yjN=#od|My& zl(b5-yMZBg_L0J(Gbg*hJD{|0!ttpyx7U2wRkP^E+MH7pOTTY;w@&=@@n5?-JrkF| zGqW^_Sybb5T=lJx;Lls?FTYrx(yiVb&$99Rz`@(Kt*Qf`ADw)W#^a(|cp z+F$rS>220LDdvQAj(lrY-d(#k#xwSZ!G#CAa#u#ODHz!#?O>PQXCWNGd42XR(yXC$$6Vy)#{1L7DsHuLF$Qv&WElo*NNfbj{UUrrQtk-{tvEoLb6*< zY*0|Hd~tLCL3#5WO~wAhLQlIhJZ*e$*Szlm#xFa{cRfa9VbfCr8hNhzEW19M3Q<;ZNHqdZAoY`7YNVsVIY_XOj*% zRL_r<>uB^zeWq!3U*f~X)H|2<)P${b)^0n#);okRwZ)jtr+-SdhV>(7sn@3}TuvW$ zw!CIIce;JIz%ya9gDR7{S)}#jYwLbnBYzEHXJy<6|f1F7Yu{OisBW^Z4TCD|ZQ^mo7LZ-yQ63wY;O=$6|z z)LZZRIO~DO@rKO&+-4)~hZkFlzVtGMo2mZ1wBxf~&>h3H{>y=T4C|&zp4w{Zv`@qQ zv0q$ti#-47-jhoc9aZ0b^ok4$wfD1n8n7@lmJ!qs!m43cxxPsvQJQ#Wf`tN(DZqqn2O)LU)&96aS; zZVR)Vd~dfP?4@Pb#bd4-qRU@9+0CAxxbr}A=DbN;P0aUyur!I+boca|tD_q~C$%f) zpkUhCwm@@`Y*%N5K%WVy3gMu@Zp)s6?OVbbC%81KayORcur=|f8Wf_ z*{62ip0(VA<$~4CXe^!Qc24L65&sE|0wW! zNkz}GnjrmOI=82*zFg>2Whm*y|5`V2TkN&ptzm-l-_1WOYy4ll!u8a(zn$lquKoLY zb^l$fzwr}~FO6(j!FWNZd>7N(H?oo0Y+?Ccca0hxoR98*6>{uLeB8ff4ih!)_Ul+& zvux<+Dpk>Ta!n8s>@^)J8_F1LU+pl=` zZjtJ3Xpmy)EtvjlXI4-9%54))pc z%$lqDMa5w6#6qF&)gdbj{=S-&R_Sy!@A13^9n+35&(e6@asKrCzJIwwPak}G{V08L z%qfwbGGD`=hQ@s>xL#Ozx1Q}^kSMdaveml(@<$%pOaA`(&Ml|$TBWLhM>pexZ>rjj z$(N^CZmQ&;#`x>P@#WV!9=JEZscXs)4^NtsZ*JgIjXLC3z%RRCG&jV?J z%$V=$X*FNh=O6iZF#f}ktE1u{yJn)%%^X8>Ix7}?%{5_1LC zG}*Mn%icN##S3b1HC~ZmkZ`ov>lc_CzRXDA!IiJ8-o~)a&{i&goU-td{PDH}3%MHw z9Tq$2Ez`Yjuwd8InT*bJYa_&7Y)@y~p)I?%d#7%JU{-8KDq4U^GRajuhTDGT2YmA{KPcdWotKIKXcYEdBVwgW^?tL)wBK< z)>wW>I(BBu;}<{rSdH&ChMy?BaNozeZ1v)oMS_f2KW5?A$$ z*IOr*&~-m9c!lD%U&i5r`}+*ef8WnA-)55d!7Y}LZr(L-c=^Bj@->rOe_jEPUdD!r znWAEX*#+!U8#weBzx3uCO!wQDVzl<9DTBqS{G%@J|5Yz49=y%C}Gk2vRdl)UH&g^mT$a0=kCmDYMItq9S&JM4U^nOOj#^*T~iAV zoc+<){OoIZyo37OS?(I|b00}Et}9w>^!#Px-2gAicLwd#7B1Qz?tSbG)1(6i3j#MJ z==JGGBrn_`DDYwx;~vEeedoF3XYEQ#yRftSU)QN}(d`-;R}{R`OJn4YIg3B`T%@Y; z+jWz|?9#52OlSMOXMXv4d&wvLU*@Nn)B?^4{xDaF6tQ*x8B_U4Rk`}psn&!oA_oeD zE$aR}Kd_hoLHdk;_Y;NhoSS8C=5N==!vcTl z@jqX9tMH7@`Zyo=^aEE)m6QA49@w(|bcDj0e$K7s>4_{crq|Qe7T#a+@IlN`y~K~q zeI4pyViie+4@*;>eg->kRyqCp#MC{L-le6_Rk^#AySFlJ;gYVhH4Fx4{xAsjJrsJX zC+yo}tn+aZ>+=4I>mQeW|1xWD`#bOW$6H)Y9Tj}-m0uQTF~w{aztqp%Smkzo>95>| zqW2pm*kagb95(sJFijxrakKN$x||NJsms}lJQG-+vt3{)a6kXe@p*=A$-n0O$9+uV zcCW2V!uw8q|EzYhEHm$hl!lVLo%>%^&H6Bb8L9JHSwCp0Y*##|$Tgkmh=4;tYy-ph zpf!6_yKnA!Y}V=`sKMQ6BFQjEBqUs!CCcEd{l32rukN$0V3}jWaqN8B83i-pOBx!- zI+NbMe_^`1s?FE^N1)=u?j8*}t$P}NY?lg-W+>V&YW?Xl@3p&4;=DO01&b;xTkrS% zTKLDiZu|VMd0q3Zzjq%wxi)`uYx?uFGmX`91~<)e+ya+#Olfq;s!LNXH%o0;8u^H$ zCvWH7l-s+L7GJh#Kc>TUVNQ%xM7hjq#)$LF90S%Y{`Kxm*^JP=iZy!*9x@r;#I7C&;b%B+kP;?FoEd|LccjHOPy{D+wbr|dtYvEwD{;TN4o(>Jnw zWM*&;wp&$lFvfTGiRK+vyQ|LJvo86yKU2Pm%f!Z%jbTH_SAWq1Zmv3w2N>-5?-=h2 zo6b1xK z-xn7-r^Xw|Y5kgE=NItL=#~EKyLDX8_5L3Z49;8CBPw>)kC~6@bMfc2v%h@PUazf| zS;Z#WcnMTAT)1>7_|DasO2MiJtx=V1M6u!r4@YQg0=L z?z7u}zpq=I`ToG~<*!5H6(^H%x=@PkX?^SEEjyp9NW zJ?q%NMzemUnn>LF=}ni6!5t0fA0P4SGwKoj?0Gx>a}mZv+wo2JIXct%tEF9PpW@?oT426N`KnmllLY1+7x^3 z!-qDgD;+-n<E z7Bzx*&mXARk)rVaTlk@S=WIBi*2`_We1%&zG2BtAF#r7vgWntet@jg*&|}K{^-}cB z{%PD(o}KSM@L<>5Wz_{TyIiI-ep2D3WWuy4+~l zd#}pbOm{!6*tXk1cynID9iD`G2cL_-U;pZ9*ZcKQQgK~i0qV>y*coUa7IS&(>PZ|QLJmyRIhW@fq%>7W zBX!e`f9Li2*4fW8lnrjkpXoKx&4JtQqRl7Mq>WAf`+8Slz8pT_uOZCL42r31DMe=;tty8Lvn)Qk{0_ntfa8^pYs?kIS;OT`{D=a}+Xj^bdl_wv8#XgutYJ7D z@^6k{ga6U|<$qiqRBN>U2x~l!oEFU4cvijn`!Z4QoZs&&H~f@Zue(kPY&0SWUz|zH{Y+Jfinc?XPRfc)|7fb_p&SVH{(b~_R&bmfED)-k%LCMu$R{nVS z=1q;oEB)UV@!FaX`KxDW|C%_TCB;kWY94iSzpN4 zyUu9$x4Rp2BDEX@3&4fBsnK2L2lsA@a+QNdMWges`m=;H{O6gMQD>6zA?etb$9sQ< z`7LjDd)y#?UuHpKz#ARL($6;E(qrcCOvG zqthtCrZd^<)}iwTdCd1&c39dcuRH5EV`l44SF4@7SD)UJy7}3wt=VZ+RZ^!dCM2}B z9Fll;w3ajE)17CAtOtM0b8LJ4`?|oq27AF@HZRx@dayOUI_1E?7RmkY=)!7w9p%kw zfh=6gpt-KIDdmgT@d-%mOP&7mg|w0R`glW04ZYIS4hk*(`RpGyEb!Baob;n;{uzcd zTc7nb&o<%t5W8&lj%B+NBvKM>9)#D`=ADX}zp%~k+R2#obB7otQ2#UrfoEGjwqDPl@qE5@^16R7W@Y|9){+J9q)2@VYsPwYV;$q0nDHV8eti6%#dhX@zjH{UQ7hMs1vweq|Wo})?lOD?|K6$YQ zPoL%kR#iW?ruUtham_t{#u?5v(k;rr&b*AgVBq{gXiEK|7yH(4KHKxP?199?2M-n; zIW#q*kmXt0^E20CtIz(J#Va~}Et4pt(u7%(3^_@cBpa-%mmM#cGkP9Zv*%pa)P1jR z$o|f_wvs#itYFWd<>_g~UH9|#rv7_cdHL6Q;j}vaGOj}g8I@AIf2Mu-oEX*gn18kH z?JT$7R%frCoc-*WNe_EzsCz@~EWiKZvd@<3DVVZ31m8bumHWq_k2UXj_=~h<>mF}4 znwzugSneZ^`CRk6{(o`JyL$df_`e{=dt7rEZhU$Y;=q~Cu3~v|x!{3KY`YDG8Im8I zI^t`*+?&ZWAhhM!-T%zahts|{?$%cFZw(P?yuxD^d|CT7i-W8Dhc1rSl5xpw*YYl= zHgqkJ6i+HO6Z!P?_2g$CPN$#I)Tlql@}R!ZZ%W;vDgT3*s%~vg%4hUd0W@K^k4Fat^NnQ zpSxB1KV$9wMXIXYkNMZ~{$_OB;8?)$n>oY#MZdn}>Y|l48$VvC3e*aj5^|8m&>?D}ChWraE z)v7sX_N9F5W0r$q#gf&`Y0nD^CK#-UH)ERf@4dXlrI-IV39iXIX2NS0Y|K1CZS|U6 z{rR~|zxS*$WnOKbDAu5Ua)q`yqgJ%muj_hu4@~)=`Pz8jC9dgh4&l+-_k<42WXz46 z%c=35&HCrF!XM|Cz1@9gSM)ZE5Z-ItOqrKVR~j;0N|}1~baPy1RY8lC4)Y4e0KIuf z3iwWI*xQv}v?>0wX6B4}2ivQ|qP{+la#mg-`c?AS>Zaxyf(f;yo(k>$D;3W%6nx)5 zZ&9RH(bk9llh=8k6=JA2dG$)TapKu(pX&1UGxu69JaAO@{B*y|Hc|g)C-yT-vs9)z(?^R>r0L?cuWqRwk=+0xlwZ}gJrc@uI`$gYKuer zJIjB^S9~>%c`kU&aPI=suEl}}e({xUpS^G%pY%1=#z*hu{Qp~5yl-B{dOsl`d_Kn# z&+ALlI2C-uB5UowonC$Q#k9SU5uZ=dpI2_Qp3dGieYVy-<^SFl+y5^W7B6@uckWrf z!qNAp$K5K^82SaCMKc8(|JaluH2IPa(;bc5m#^o4yHmJcDd;%Z=w<4DlS9LfpRcv9uctQHa0GoU7C(J)bIo@7_z&KVpVEUZKTS7g{$;(<(O`+8Z>&tCr3Axk z^IX%}+urr4XRKK#;4yifXD?$wxPM1jnjq86X<^3u{wnY=#Ik)j_|R>?=bJar`TkG$ zkKbbbPrCNW`)w257)0yq!)O29l^{K>rNr}qs|LsCFZ-Fl+hjy--c_DtXyvt%jVaTJ z;frZQmW`t5l#{6qIqv5B^w;0DPkj2uPIT{{+x2qnzYXR+%Pgu+V=&`*HIwn9yx8*p zyQ{0tzAe8e?tdY|R%ya4J{?mwhACZqy01Sje{^MK{n@n3*4zB1Gd6H8|C9b)?9J<1 znV-dM3cic$o^Lze^v2Ti@j+wewhxJlk0#8zXL9Y*C8arGak~pQ-`w(e+a)8d?_LTM zW*tle&B!)fVTkCQ@5Gt9ZObENquYE37&6|SKN-XrH+zPe{gJ5uAB(5!HifjDeO6_? z>07B|o={Giaza@d+vWfKGq!E}mR8LpwQjFO&ML-${BqxPhF5RAU~V8octa6KdjZvN3Lm7n@?tJnN#sgGmwT;r%=RuKWGjap@N; zCx-nGL1maRbHnq+JKLw0eqq)Onfh;QwD0*7vzA4(Em*Yu{Wtmgvwt|=f4@H`cTM?k z?z@+tE(v^iS>w@6#>2LAzj-`NGiI`=+?jS*H|_K}+tZCfpRaK+&ShL7cwwoCF?%@I z{!i<8nC?8Z*_K;>S%;9Lq z`)>a~9DZ)rYYU$C*Z*Hl5?pZU{3FqWi8YZE(%CL7y({qGOTBx#I1j_zf7{LXF#b!} zKU0d~YuWrO3@f%$}vbV>|zOcx_n>Iqv+e# zXKq}pZM$#lKl||&yXAk*$L>nw`~PE$W&P7#y{~ud-(r3C-c)bK#Rg2fK5lc+s=Cd> zk@oQ{d)~j-Z(jL%ga*x3G6-J&RcmL|N<)SceutCa>s*Zs&U$Vcg|5pLfRLhxXNl zU-HZS=jZnS%j97(xV^@h;cMCIOD4L(`8UE=+a=%r{`j!^52ey(J?4Td=gWW3ul_1? z@2!1)+LyckvTU`&^hE7=*D+S7Ik4Z8$hec-V)=J|Znp97kH6;cI_AW1{=wN}s|x?! zTJ+twefG?mGyUAXXDt_XmM)vMuF=rA@TY0Xo3*@`3-VqpR@zljdT39{qo$p9t=E6= za9F!8jiY3Hy07#!=5;=Ij>L6a?zUf$)*8NGLBg;4wKsQuPhefLvLi~Vq1rw6@(bYr zu}xRrFmmL3>CR`?&7R4zrS#j3hi7+7XGu16#ov2dKkNOs)t};@1qZ~HecNYa+_0YO zTX|x$!lT|R%EiCxzaQM~oB!XTk|ATAs53+J*_EQ&dk(QKKgO`5QaF0uRZ|(J$G7>V z>Qq(;PGfwt`)|OH^9k+$PcPS>weu&d?9b~5h11XT9N=u~ElOip{(t9}(g*X{Uo+UQ zby~@hz~3Onu(T;mwa)OD@#V{xjejIAP4=#wdSI3NMrY}A(At$!d!GwgE_um- zIia`V0)xldcW10>M`@cQ`TY30A;kL>CxFdmpY zGm9bQ)5{eT*L;*^Kjho+_3P|O<-hAIp7iXy9ltnzq0rGUW!q*3>TqsJ4qLEqp2oV8 z#G4%Jjpg=yKj>u=n!1L8>4NDlmJLiho@-zFcT8PfeW!8ocg82qEBjtLOP41~CUDGW zThae+mh|5je*47s+^&E5Ye^<=W+~6&FkKf0t7|0=C;PsZUAa4xan3(^DM|C~?{+)C z4{ug#_?xziB|!bV-X#ffy_gv-rEAwST|3#Uxl~GaZ*yA3aRJ$TEAxQ zto(dx=JmJXe?BbUc545nw}F*kI~l6qUi)ySflq=tTrL0B5_};gWzPJ1+r>%I`!?7J z?Ku7aRIlb|5x2kV_leo)&EXBWK8NE=S=w2NX)GBWA;lKQU;baa``fJ0?Ca+07c?_X z&1cEm)yyzH`^MzQiIC45S6XHD<}vJeaQ^Y%GoM4F+H~aXyza~=k7)Ro$BXe z4gDV`%s*|wWFfp@-A=&=Ga27n7$;0gXG^GOn0tF=2$$@^MKDYUd0tYIY#R?n_XFCP#Hxzs!8I{U*EobEkBL?;#Az#Ed9_*L?doJcm z`ETjZD_8igz1dli{2(KK`pc};roAcOY-)}y>-F4Qj3;`@G?p;%t_t$Ygtz7 zm4Jf9QVi>ksy=M>p04*&$bnaW-YOB+*0ZS$d1s>g7C)MQT%%9=Z(T?DpLLHz?a#5S z2xj3@JaAmE`+3R|bsJ-b{xe4MuGQO)F8nRGX){mjscTz)xig$TlOEtblQE%n`t`uI z1(T&KIU5e_ez5(4#e>r`@@5~|eK%ISXSLXV9u_yVw}yYb9=ti*xoH~XkG%i9HBygM zYYx6=^80prf%A3E|EYH-q@Ce9dfu4%*5(MI89Orb&YgQ4sUyqb_y0RD&rI9)cbc}F zpRKTFDPUvZWw>bl=z6Se_u7EP=NNvxecXFO#=5MB%^~Yt<~Qk^Iv?*{+`6p(>#|Ay ztUdSM?ytD&_w#o7?}dL(zt}#TH7hrrk>$q96EhiYBcwErpMBgrEqdGCtZTVFL4W_gW%Xn3QZi zy2WCz%|k_pJ1i#*S>>C9u^JfyhE&A*3!_d6ys@(yQ((+ zd!@Hs_4J9ox9i#7e`Eh9Z=F@z<)A9~;;4>;W;)x4-C}!dnbY1ctk#a?ox0VD!(n?& z-M^pD^%qox=XWVym~gD&_5{(Utx88v`OY{#n?YSmDX?{o2Gich(mwZB^|Gcju6VNY z*~%~ckMGvWeYm`;tKRi}^epcFxsn$)ewoP_Adz->+S|0h&)#OXEzP`j?B-Mbnupvn z38#1*4sW;RWoyW;JR85i?r+B7`=YrGcmJ60W6o!O^3zIT`Jd&6XV1U=(3cixyPm)M_uJh% znK#MlOeM$mD@>aeQOQyfb|51E(@aK_nBCRC&%7 zJ*BV9)9dRNYR`RT!hn6E7k_g6*pOlNu`d@$rs&G!Es_ukh3%J`9I zugg^0b%0CZK;QDMrWVEx%Q<#^K2&_;&-S{@VVetIpA%g%Q!J8~ z7hT&^^sDsuSF^dF*6aP#+Uvox;O9?SMm^3g4>c0Y+=@5f_*?(JS#37&H!cxP&zrBr z8(Deu`WIF$%69qL9lzgHKVb8xxIA5j4Q-w*ac%WQ!AA0bU$~$D`%wPh!F+1lGV_x7z`^Jmt| ze*y>08RDvcJZ$%0m}bHdvp2YYs_sI!-aH16;HPaD<{zJU;Cyd?zxS+Pygd%w%L3m2 z{QkfDVsnV}54EhTqK$0s30YaMmWs#M6f&JHDDB|6X(y#Ay3^X1Ezf3;~S zn#XZ=F^2|ISA&v(Vgql(bITPhjlU*Y9jg^Jc$9s_VX-tr$e%|?I2PDR1bQ zj|ijd0vQ%HHMPE)C(81c-Fu3zG{2ZtbmVI2JU4R&j%9avuFpU2EC<^eEg?DO}zd3rQ6KDZ~)c9h{n&132N6;1aG@}|sQ_%(zD?3)$x zEPZmezs|4Vz5ic)uhOq&by_=nW-U3$P%vBHyuI%9`~Uym?{7+F*tdVBpI`t)kA_s^ z&6_v7|9{~BUmzj5A5m-k-y7+otgUsB|xEFar&%Kz0|Zj(_`6qwLxhGl*)bzRQ&yV{r}VR|Nor#SFqjs z`hQOKEgpuA7gqe)Sv)!Xbo{@k@&CB_Sgm?DSV26(=&GR4Wb^0w?)!iB*n5pSO8)No zDP`Dr%{OY!mR2`kv2f{ieIHKEv8nv@KW2O0-Tys15E=y;=luPD=WhA^zw+~HzeT>2 zoz18Jj`hLCPbO?B3px{$cblAL_^_;_Sl6uW?DhTszW#4BW?a^SNUsOnA50C8`zbW# zf9Qo+TP3gf8vAJHW8h$)Abn)ExGwXbXZio01y`(nTzzQCK3K|lpww`-KHdJ?#{ODS zlk&ImJdD##MIUaFWLWg)=yYlGc4gV+^3`uPo`1Z~(049OlYsMsD=UNJYkxd!PyIZf zM`5MA%km>Xq-`geF(23~|Bivt&6u<-sJI^&&H>}ZS&VRH#c9-um64fxq055A7MvY`5jB48UAd~ft|(A_x%yJ z|0q14*KkJTdEfdy87u`3Gp(i{lVsR<>4p9*)*oNv|9@SdIQ=>I%H5N1DL`y(+$3;- z?W1)3k3-^i&1c@7xPSL+{Iok&=cgQ#WJuCmT>Wd~%m(B1b3T8s`2W3f=hxTQ_h+5t zh~9f&ZS8!BZymM@9C-5~{NAT&+iSF@oWHpL3`2pyNpH4kir!XxOwKXvD0j=7mwk2X zzfYh8D8E|8T~2QQ7ySw5^odGmc#?Kfz}}3z zVe7n??D91qj$K{?azY5cPy@t{O$kr#r*B& zZ;cs>9><*SGvE4kdfm(E`zn8adYUiH0uQZr2K!&1)9b&b&*zEOTU`IqwSI}%j4kIG zJ{&vAG2c#K%2wAxBqQ(iG~MO=G8P}oe3-U0FN5ac1)TyFzg{l4|J1$zNB4b~$t$HR z?yNtpm{3w&<#LoY7I6c1VIBeEo{Git5s{X$0()pSP%?Mx{N2zF1e`e&A7ImUw&T~Z)5ld znFa04oKCO6nRY@F%ZJh%`}cmoSN-4nhRe)v;_*BUA^rbue%o@MVMA|`aH`mWxr_-r zzRJF@IBx&wY=irWHBfIW?Bz%}_Wj52`~Uy-FKi9@BJ;J|FCk)zebdYGvj)ruc5772 zruaAfFlmVTAh+V+lned$zAQ7}t2+Dax26BLWpjh8u?#yFF)^`cA2NT~SG=#j|C=Ss z-&SF9-F1!v_TH6jDoD#x51PhUvADAH>T!03-`lqDeJi+z_v7S) zufXMX083B{L)qW`AF}s<%a(VYyn=nn`{nvw^DqBj^yetM=H7KPc^K{m>`Zxk+?45u zg3g}jw(oyVetmuYYxU;Kzt3c!2Gw~d1r8X!pSu6&?EOC%>(~E0{rqCdE5>i%Wqx!j zI>ajcYOD+1krvN1VYZGp<68GVnVAt=v#xfR-zhx)|H7q9mvng`@vwlyM2;!qyScy3 z$0P0azn0g}lHU7wWqmo@f=rS9caEkRGu+6#$*v$_@GkBqNZP8ISxGPV-kzUlJ+hZY z3LSKTghfyz!?nNv-@V)Y{@;tUvu*CH-}<87{!`@b*m*WLep@4HUcsef~V{}~NI~_mlf< zUt?*8oc$JtY%%j6UYj0YS1EMa!@lD4?E5wT(b2a%QUYK_#6N)p3-cz&|Gsto-{tdv z@0_od-n#gc_)&c~hIGb17wi9;E@3mxDr0sK*~e{~`EKgpgZy;`JC`qi-frRWh}S}M z!~N828z33i;jV%~x90!@Pi=#~{$6qU`Wu zi`>k6-_GXOeZF&RYxew`+w<@LUAuN|Rg8gP5I7S%%vET({`cAZnolSBA1m9>cbvN7 z_KFwWn*W#0{WU#$8C!$G#QW24He6d@_jUF1?fLir9m?H)_nVspqwUYl8C_7<@CweT z|MhbD|IY`T+5bzWZaP2hY@N~H^E-O}q<*tCT$Sa%rL#$#uY2hoZ?&=X z^)>Obx3^05r!iMOjx<>YsrUoDSu8#+|8#$U{r}^EdcQOMes@1AU;elB?)}|{Sqx7Y z1Y7m(f>nIt^`scruZ!RRZ>DYaw>yignTi;)JV51eW0VrZ+0LmK85sBtJzX3_p4$KZ z@woqgyv5G1`xnG~ySVR@xbuPVf4=s=p4SF3H)uSOkyrSCP~mK8Px}5(Q`gUtlas3x z3VY62Yki>A9a1rb$TPnAHvdp~jD5-d+V8b|OAGglO|`lH=Wl8KrOJ)@)43f>vlw*h z*kW4WOEVwxofozztB6nCrV7++#m6Z}2K7I(zwbP^`)>Wd?)&cjy?-KO7X7(9alZtEZ~YB_#h#T%uBa+2c+Iop zRgKGic$IJd^do1t<=)Q%Z8qqce>!5WnyB9t8xWCsU{3*XcSLv#mU9MW?d<=$*A0FxUW|;GD z`cek-eGQTgZ_M|7?5!`q9$Wsm%H93A#*ZtSAkQw~&}n42|K7gv(Gl_b*W2q}FRuUp zz5c({jhpv_cJAhiKNRw3)gMNum44|AUamS1p7S_d(rNHWj^ft6w*TMj{dH{opUd<2 z|NUlq_wL=<@MdA-Q4Swp-@RS=>uUtsf7dYONBA~4U;FL)aQ8lq`tt4etzxg{i;Le{ zA~KgzyGPBpQZf1PEU}Z2Q5I6)6 z9JJef?td3OsXcmrhtJQ;3<68_?L1y_DK0Q(XuY6m z=PPINtH{v&u!&ey!d1No3+sNpy1M#*(^;Kb8)JrQW|8_uOF*e-mQ-W*^>w!0*H_qg zIA67^{PE%8Yli09#Ts?L_I*C z)6etu|MqR)`!@Ig>jMW4Fdn%I4&DnT9B0m)nN$Do=X3k7tM~s}UEdzQVm$*x{S|$; z1_=fs@u&LCdcR(Ixj0OT=)5__*2Uq&;{15#7LY8{I(;{W$}|Jv3;#bH=HD-SU--xT z4fYAy8o{>UDki9xA^z=Qwf*b=yqy2T+_1hZGFm$|^Vtg|H@5%XpPG01%|8e-g+;Wb6-1kKy z#5=C;2e%}ba5Jv?>_5-0_E+}&pJ&qTe((Ff_kH9RzhAzUd;O>E`w{X_NUh)cle)#K zKb=l9`yQ-b&BOFb;c|I8a|XwbQ}fvxswYm*`xg7Y>iVqvzxUq%y;9b?>`(i;b?bT= zBmaZ*+Jze&2}kPRo|$RK`YSYm7dzdrq}? zjOYtyPOyBTdrrGzmm7oVp4A`zZa5=0p`ZWzo%4UroL^G~YW+;RySse<=Jo6I&p%3< zvItZbJN#8Pu(hpaRQ~5)IODwd{r~^I@9&W6ezg32H`n}AcfH*;|xm`a7Z|t5V{`CE?b&tfKo>yD_ zNu5iyXwe!i6~Chv930y|J?Ji4zG(R+@pvAFg*)3AK7Vqb`1b(2-G;ZI#`OI?nU|Ms zepdj@|_@BedpH{ZeBJ3A&GJCLR$Tw)CE@P#@gFt*Q{yQ}2orOgHRf1V28 z_vwne-KUAd8tq=OY`TlS92Gz6H@{{bqeA8(?^J#9rip?FHtgb3R_I!JC@D0CiK%I; z1mmI$sbLE96%|ShSzdJWh$Z(nUc7km9cVz}UJ8#g?W27hOu5WA|I7&2=ZJ zNL$D`U!(R{$s=~dztN&BE}EgPJGgvZ91K94dg@#jJz4oBRn38i;bUxETdd`$!`U~t zM6T1nc4i5qE*lTU{W1sv`n3=FZ)FWbSW^S>LAy zFZZ+U?*F)_MSqsu?)*V&x!Ju>&bI2) z%H{K{ng#!59#IdJ*Gji~8NXuA-P56uc)aT;`+xlXN2xpY!pcwl1uJgG2RfN*2XIHV zG~8hhuQb-N-=)Ibkhx^H-QhgDc)7=)n4j|d{&0U6ep%~d=ihF!|1i~d_9Lx~W!&?BEV|(O z(0HEyo9E&`*PY+6OG?RM>4J530u!nqYUc%B@#Kon*r%vad1Sdy+sl}rjH=(Ar^~NA zJ-JcvLf>9i+t=6DzK+_O75eER`*!bjq1_)lAM;6Y&3&YNr*USh zp9EiIOs;`cRN+lwLBaGHrQ*y=&T=b#{eyEIdC}4Y7`D>D!GBUY>p`#o*-q>Fyt;>))5kT%W$-_u;7<{!|{#Z=iH(M?7SCOKGc6(uIX-mWH#_NGw zw`lP(y|bNU@OO##6uZlRHe86g&u^?JzWPCXtK0m(mnDyWC(d3`59(zmaRlt_n!Mr1 zlHZA)m-S0jB^eIA*Q&nMa-HA%=aTN?!%o5b{R9fE-&w7k6tD}K+2DRosNB!O7 zFTL=Nsqy^$*l9Tr=jG+vPrRcJ%zQj`$FHsir42SLF{xUKzl4^RaPBVQ6o0&1x%OeX z=)Ma_rW;9$KW}SYRwt|Up!`;8gzMTMIhH%s>JL@sZwb;D^E}+(_sPp*zJ99l+;jcx zA%9nMXfQohdY~1?d3n0QQ?DCKy>Iv~4q9G(Swbvz{*-6WrkDIGnIv}POTM$hg1IHm zca|;EdU?;Av)iAM$J<78j{e#6uaDoY@mVY!C2)CvSA$Z3CQFQ~cH;H)8T;+S4{px6 zRJ*UHWV(Mv%=|>FhulfOwm%fO(6{ShUSy!C->&se!tl(G$|NA2)c zK!EklZ=H=2vp(n?)z>Zf*}ANbSy^Fj1+%&1>Npi9&+oUE>#DzduDdDf6sX&is_;N7 z*<5E)%q(d=v$-{%KUMbs(sXdJek*e4NUB-l%Jc;RRSUPIhIwz+X6ka_sh+AoIyPz$WS-5(=bXw+QZNR z@KFkuLRK)d{Hy1h@O$Zzo*S1K7#J8lUHx3vIVCh@CL5SCFtA;9403mR%)riUmRMX- zSykOw!(LrgSy7y1!P3FNWPbQx?|-I$M=jeJ7|q^J{LlE_vW0<>?bzi1jHlR}85k}8 zP5IAY-ORv{GW9=0Ml%CL+0_3GRm}_xHBr&ejr7-p34&87BVkU|{_JZ{q(p28Lgg|1-f*`t(k%0SNnhF_lp<4YyZ#itC@k}XWM_)8+}#ft(V*WGyH63VEEqh zpYzx5rrO%d;=*-5TOfizoBwmaTv*>wS5;n|pVslH87kcLpZ7vvV^dvqMM+_9W@6U< zCa6Tif5Fe2TU(mzYbr~N^0LyCW5cJuZ$L<1YG`X|sI4q3&d<(BNr;W|KY%3pb!JOr zT~&EWK~83BVq8>Y(kqx`%i62UOAB+e(vsq%qe43xA%^~L{LlYr+p0-bg?ZWO z$q6x0(ILB`Qa>C2^MBp7W5JN0_}a|C`0QWPf2Q9rUp2jEe)am*&*uLO zubUYdSN)j$pYg}$76!)KzbF1@{C%s9f$7qhe|`TMzg+2HU=BTd?@{Md=12EVCy^TE z-%g$UiWKE9_HNp^>lsp%|GBH(dOU6y=|eZ`rza%jS*iSL}L+6y=Y1Z{4;}~TKO6ot{_bZESRVtZF`RJflT$Tm|WeLa;GP^q7d z|M_1gL`FsWgYDAS)i!;EkYf7R650^O9OPhT*r3a#o`e+L?AwB)qN1aHOd53=RTja# z*7TqEXG@qMVox2FHB{~E#qZA}}@Sxog*W07Nfi6BWRkoY=plknnZ zk1HoKl0F^1qtCgs>h2=P$?uP~=*0iY6Ec~#qam7QqI3bHW8fmkn<H3F`=9pjf6uWB4ZeHX_VV4(yU)whR^5Ah^WOcJR;BZ{`^|lIb8mI|K{iGX2L>jA z1_l-d1}O8u5jBI%B>(=Kj%X4LED8!tIt(AmVJZ=tRxmP(F=TC+JW~l_4pg>*mEj*7 z+;FHM1Cy!30VW6DGNa%YxDd=08yF7U#}IT_z{V26u;QR!S{F~_o-zgZ@kaXpdM)2{xlNc*}nZ2q+4RcmdnpKR($xt+Vo@YF!BoVKwUf8N6TPR1u;Me^>l zx0BY!GM_O2d2?^|_qMJ#YT4QQZf%Q>{p~z^=AEqvZ{6OWd;8m)W4+Sbbz*iD6iZ3n zO#i&F_Bf~=ny{bqOM%VJ{5FY(11;(ao?kXh{-r*zBI(u~%i?F>zJ5I$x+zcXnbeA% zfl>2?Tvb16Dc_vjBc%KF>+R%awr*OD#=$wDuu))W3{+pxDzD3MbWxMo)GZwgxBc4M zu%LBDX_Q02-E+sU-*jGTzb*g%zJDjx=f`~i^V~lE)8SiZ*t%H{E43N0ymD<|ZK!&i zpT^y|jiqhUgZovl*VZ1Fum7X?_wV2K*?%0rRrERj8`qQGaVo=4tj>g56Ba6Bo)eJ)X?K&G5K* zzo040oC66b|1I;KEq06db6k>dtppYZ4?WF>*L0 z%;&Vp-1+Zo)s4NZ>vn&+I8qEc5&?ofFe<9(8@r#Lr!-2*a{i z!lF0*53c@`aeG^C_)T86>DxXZX>zdE`r?@Ig>wUg!LOc=42^0`Zzuf|)eiHq|8Ri$ zen#^0+spqfpLyt@ua^ld^)?*%p|W5$S6p8I`?&JErRMwoeO-V5rWLOkb5!SrwgWE& z6BrY|+(?pSQf(~r{BL~Trg*{HuKg#4{+ZwYv3~Z4GeM19kaWYqBrv0b&&*-b-hMSZAYUdh0SfoC`=2LaMY}t)z*H&(Z81-#F12@C(^8(xuqpq)w zHa}ix^jvq^7yV5iaW8{)sK;qiBW&$_&BQ|Cy!jueL0o!W9A!D+U%er4LhdS^msFJKMaz^4Xc0 z<=eP63V^-W=H9^C@bIIP6v(8`|F^bg^UpR;Ket=w76pSCCj0G2 z8`iIn+4<=;x42%+*3EZ$|6l3^yj)Rk93d=3kzu)gw=l?f6F7tWzv@ib` z_U^ye@-wd9vj-96H}aXJa}^&O-w`Bj&D2+*n!Q}G- zR#0jZ`oDaB-LDyv#%X)P@4q*H_|lgPk&+l#jx`HACY@^$n5VIXBFY`scoz z|FZMicaau)$%wz>Uo!mYtiR7o6o9@zP`(P5+fL})N!vL$ak0TrL9wKymsV1+oPcW{t}DppEiRz5jat(=J#6 zAJ}Lt^mABVmwuy`@!iJx{C^Gw``d24@GN*9cUHh{i}}hAofA&5L@-DgX|WtPZY;^j zx7zXj#q&QpAOBz5l6g6A!^i9W&wmP9BLe%t0+wHI)#v=r`1j{0{|%=DH~DQo9PpW} zC;Z>+c~`SkvH;WLsh~Qd^t|Br2^?E2XMBIrkk9^a|E8%AtaJXXObacA;YUs)#MUWV@sGcu~D{G7rP#sA-%lT#ucjOmLt7rW>%Y>WZ>#h4+XXIu- z%AfphXG;Ip-`a3M1!=6M&FjEE(Os{lv#+hO%s$w}y4t6+wPE2T=e#?D z8M~gO-f{ImAh1Fp;L-ZEx2EcU|KGCq)x8X7mH(m^AxMVIX{gmsxb$AGY8ixZ`@qPL&I3uG`YTzPj3e z!}s65w3GkKeJ4EGzr+jex3T!V?e_CePV>*YHK+RBS((QI zFSM9q7%znGZcb=(c3^+dwdeZVO>6Dnynp}y{x$pUMsA{r@`8csuv$Vm^Pif_%Y5rC zil3d?y{V|uwV`+F*FBa$cZq6*}sue&=KRK)&|{Qv zrk|(LOnZ$jzx|&NI?=ssdd}Y6x83*E~_-fGC5qqoDL+rv+!dvikq(W%sY$Rd-?1|J7$TPySyq;kX7; zCQD#>WAn8qx!?AiS*^y@r>FSWMA_|;I>VDJAW_T|!)WmAM4^QUlPCh8&dsST=Uco-szIC4 zYDaRxU6+Px29t*iE^0FAp4@Nu_5IEtkGhw?xv}xF_q0F%H*fX)XT7!f12>`q-4M=H zX7~G#q;Xoz_R`na?wcgq&*xR0`#P)JOqIn7)SUO5cs$>8!PA5n?8n^YDxWMcK4L>rQu*6&LJ2tiG$$uVFod&llS}M?YtK z{t;a%ukiNwKmA#DwZF>O)Vheb`)42(T>=sl8e+fwKRY{r-%tLypQpm@X1D(EI?%91 zLPDEKhoMI_^N)#OhUdS>MJwcXr({iBJFD(y%W`wg3&?}m2P~8mx*5vXL~VU#&Tsi- zLaODLB0-1Tcf1DY%3iQ=OlNK2+#r1Xwg{Wozqe`i@*Cd&{y+I_`}$=)+vj@iL2?YU zU__!hyUpEq`~|x?GZ-6O8yY<>+P`A)*y1K@$hV5+mg@iX`L$*9@4k5-uk-W&l9Qjz zBU#;K5z(BmN7&-XzI#<$YtMeYo~u;o->`#eTZW~*-1}nHe|qW%L>XfDHLC?x=(3yJ zzlg2-`83$y`t6qb%^O{XGuNF#Ds>o`bXm%7oMpV99bf*p>+A7QhQLpy4Pp@{?(GcR z42utot`=p|?cDEH^R)QnBvp2q+AkOHZ~Pb@dSoS13E_~iNa#jx_`ZL0V}Jg-Zg$Y) z-{KU(ghlVZ>dg|L9$kL%+>DNeTU7qf`*GW*dYWEr)ehDF*C+n*KeG4S9OR~(!iCG% zuYWI$X8CdDb=>z|CyxF(6>)O=9BclBraX=}){Q?F-!A9Qa7tic(q-RZvEl2Qn4OJ^syuc!nxB8#@=HtVfg@JV z4eJ?7+J3iuzg6@=Yw~~7>}xW{`Skysou40_uYFg%|9c~U zO+tI;%he1|uQM(=cAlk+>9z-`tD%yabefZA%AfVIyTAR|S^WIpg=6!XwGf3CBZtER zjU2%T)%SlKGr#w6)d>_ZmC5~m4Qe5(KA&IzFXXkxemm)ksB@ba&lX}jtagCuz=nwn zFZ4N_c3f$b0b0s=HO|P@I{e=|Y3Ji^GjK518F28MS5Wlsa z`Ac+uWt^;#?(@4BeK{Kyg&)WnbSTpIeFZ`siwd(7u#m~>r|1T{4ysiH2fs5>5 zw?Mqt$jEV{mvR3;&_49~f|IJ#-KKBDxqf0Ap1;|)U-14c0h zwzR`16hOwj-}n0+v%kf|mif1S7&fmkW`UXjNrP`%5A?}e|J%2J|Nr$qKArU{{{uI1 zY!I*GdGFa!&G6=A%e=h;5*k1Ezu9zpS@fs>xs(3yWk``gj%;Uz3)ioI|DSVn)6@D{ zEB0kY*WEP>i9UB46jjWi6z5>Vvqkwr(F9F?r}y>$fB*Kge!Jy!?w|czE-&UsHb_9i zu2D01*__RpmzQmynReT?;bFAP;&UuNxE$tw|7Y;rF5pvsTGh7P+utO2+q|?rY|?3g zsNEP?6gHGI&Az@qzW&vo%FpY}FWTJVm z>OCX`n;;fF`uNA0Xk6%Q~u_?CV7=d<}~ zLr~nee6#8|MN96_v8ntN|E|Bb1gW8>z|eS*^M>V5M$j%#(e3xDUOTEub~6;_#ci3h znLn@R;w_;B#s%AU*UeXxW0$ahVDt4#u>MT5+*{#igTB0<95@9v(d`x9pz{C3WIwBy zuj@al*I&xNROdI(J3LJ=;XTi5y@R$4ul|W|nErFSjLWI!p1*hQ7r*KIZ+_#)!fL~V z1}Gt8(8L02sjV*)eb;{W$-10vRq+gUas@1r{wBdOj0P9Kz2G>PzTnCK)A9d4nRx%x zSNtjOcw+`i_y};`_`Chy_QJ=xR?8fTc$HESY76x-n>%fKi3iaui=^Ie`c3}_IX~` z`)j{k_b!0JSCZU)mkFW)Y+3HX%1 z$rjY*yK!Y@@c(Udz^+04nhJ9f+pFFY1r zy<0DhNvayuLi~1S_qpU$Mg=~;x&u4@d^-JnUfHe8?YDji#)_LTq87c?0vqDr{@R^+ zdD-7y-Xoz5fxC3V8Q(On36s@fxRO==-twl`r}YOTG(#*;=Ba&}`tRSr`!|ETHJvPsO)z{X?{|}q=Kkshw z5muzat&x%Qi)_V%#`xVo9(AYBbcuPW{V=Q6A(c(djBy>qj2pkp58ULP_UC^2{aWsR z+iy2+`#fu4};53|I2!Z$Gyq zQ}sjT`RaR1#P>X4eLm^W_M}C;$jUz?jI_`1ek?kP)@oq*2ol|G=6 zI-fUxZizszqn0oC zQ`1Ky^GA2wnQB-Qwv7w{+hy}^*3K*1dS$ADZCSyOwD|b@FHM~fp_Fn4 z6FJ|+?Jj#e``P*V|L2>Xj9^%CPq^VXqtBDQ_l?Z=Fu`y{=aYApM!Qh?!6oLgFCyaITN`(w;+sVTi)GY{K3op{_gOdwVvrp)_HUPX{-%t za>Cm}Bd>OQ|NpzGes;n6ZMnD4ZQb4S?_bMY8{uYXApmNBF|cd{?NL~7_iDxBcLpLg zq6?PQ^VhI0V6)yg>Cf?;WWfc`PpZ$iIXdZo`EdqS)HqS({32iXU}HzvesV>4gXti&GDzmwM`4v8I7E0 z=2(7y587ID)9h#j%Zg{5uBX{MglD+eE?0IB+wb78Kj}`|kUHD@v#DbXG7oR$uNj z^V7X9i%`_8D#xa!vU3dEWOu|Ier0`Q@0%xp0#Il-iJE_Kac-$Nt;hNoLE9TjQ&r zv-|V&`!j>?%XO6?7vu~~Tfq&GW$N=P9(^^txhzI;d-lNveUnod6O!J4wT(Li%G;~o z9<6(IWS-e()TsaFmT<6%wZ8hr!gek7kb4`BUwhVk)HdaXL_DaK`r}r7b?>&Pu2HcJ z7r3i0^~qYV+hqNJf7<#skgJjEYBih2t=ZT2761G5bNWq}@|(`znOE3#-QDI}!C^W} zA)LYI&fHkve|z_(ot<^rZ`RadaH&|$9&n;o1=5bP# zK{&-dOKPhB|G!69=~e$wwN;;AQ*{5V`Td&5OFSq0P0^7?NeT>%f;ToK9hiy9!E3d`ceOgtKs8*{#v%FPwdk~v@7EN{Wxy_FL%;^2WL*C!itfDrSazG z^#AKYJK!&`i9M@mvc~K-3uqWdI3+zyjNy^%s!RL7|KD|e@4EY940)Sb8=m`5cxl9o zk{S|NIBouZxhy}&IQ?8@T2MGcf#?B_O>fnLV;C>^Rwv6RuT=S;xNuj|_0OQKt`-Ftd^`u!Yk z2KA8A6-Z;GpcJw~XhSvQpN)69AJn{ltks}3gEQngS3}`GW9^gmuYGC^ADpv(U&DQ} zUR&xGa%?m>Xfy8SdXW6>>qEQO`F|4vbGLCifJWGU`mNDn*s{;#@9+QCYX} zh{=iWbMY&_->ZJ#SzBAXUam>g1t~d$m{VBztX?em9<}&xD8o{osUJNXSQ|d>yuT_i zJp9?IUxDtjUm1miW+Rp8ptAJ91kM}mo%tJPcgkmTrPf_sWpjdk(%MB^c6TsvGsJE& zZj(Btpl?>+SohrezGcO$D=YKmSJ^%RSqF+Q==enl$Co`1SoJIVx89CrJ<#4G8t{1S zDW(Gzx0BC)3=4m9>X-EWAIFS+bFwyT=%NG~1Cz6|LF%a~_jkTn)ctIB$;|XES$v-k zrmWU@s|RYsPk+-P$Zf$q*cf>k%61RndK^PQm)R-iJ2xFSpPB zzZZISAs0|fxO7Q(mvsK&-Mv^;E)Xj3Q)!{4$L z@-lW~($RdGnhyuX-?noj4|sz-^TzQ&9$Q6xe*XK@xe9qK5~}9Rcbdb>%L`Z?%HPjF zxF|b{W5ewKf8Qk>>OqeKai%ik1CQVD|Nk%E?Cz`O|E|`g#GhGonUP6{p@iXAK`h^g zm;2BB=zjNk{{KHQzw^#Xw`Gf0q9-NAf*%jt^^Ghi{abk8Rg$C_!>)?wZ&bH@I=VgM z;-c-d-b~qh!k7om=kqyyW}CeQ^&yMqYfi89Sa*8fuN{&L6iR*`V{(}Oe!(>V|H`*s zb?04rb93`nDe$@lP_}`#MiN3eBI1fp92JlMqh%a-*7eEh$^QcjyDkcZZW~q47humF z@n)N^YV>!(2V4#BZmU;!uZZEe@bv$*Kd--A+yq57H1~m+JywmNveTqE=$XpYT{Wxc z&t~Lunx3b_Q1Y@*Qen+9@mtdOf4J@aaig92z#^3KWC4jtrf;SV=CY+%0{5nEoi+PJ zr}}^8*Sy)vz70;L^R4}F4|l7pl~31Ox( zeiQ|?OcoDvtm9c`GVJ=?i$Q!`5h!wZE!i96Qqn^3iG&!kXzWi^{-t?rpo%hSupD8}H{{@N~j?V3qjs}(EngW7rVchlK5oRCsJ z1CziFf2K0$PuJ%Z9OA6K>9hMzs=e(3J1NjCBJV2Y{LR0bUd^;Dezv}654R4QP6;W- z-w%q;v5Loh2%6j%qbhRs+OyQB%;&D0E_UbKz;Ge(k8_cnzJ|WP?bj=s+irbZrI@M2 z0tqfy*`F|lL#Fb{#P!CPueR)ul2=_nxj);U>22h=&^h75PH~WK)&Oyy@b~~*ZgIVuT=63=9H@bPmh*=G za#+B#{`RI%j_GGROY45=FqG8E^N1ak(_gSMc=^BS2Sp^%)2bVb+Wz;o z@2zjvnAT>@taaBjc3b!$O#>8OTuTintX$J|lsmQmQeF1u`}_W{m(JaQmRMg{G&b|w z{g^7|qQg*PBgZ5GTG`V4`Q?=i)1Up~!ROQMf8P}T`LBL?8D|}8?G(mjoOY&S=hvy0 z-b?RkdF|%r*uZds)jE+w@dx*-6^r|-jtZm*B2A)#!q`BKGD-C7VdqfBgS}W z{jFPj*2H|AQ&stngHepZs;Sn)?=fr0G~@JhDU1moF9eXso+g8bBpy=Z z`frh*|3B^I5Nn?X)&{=!3_I7aX*=4z^p}(7?>Bn0ckO9IYn3=$7qECXBYD+##ud+u zVuh6(RX`QN`*)e?kocCQpq@Q!cG7 zPGejU{oO}dum0nlqEnjcrn^?4hbtdbnS6tIw93RUt9#b2TwnCGxF@*J5Y&^A_#tpN zG+rZazIpz^;S)p$2;+wzx``)(iU1Hhb%8cS;!I;skq+J70hJck9Rg-}tEe`uxy4TfS8PPUM7k z(i}V&{8yI$d7?0NwdL}aCBbh^8u$6JH~d#)n)s*sQoQ8Lji<9B7*;SWe|Ml=` z`J3Id{WkAba@%?sRAzkOyU|@8&+((Z{zp6WPR0ju9)^$_lUb-D>9F(B?eBj)PkOsd zRBiE<`}g8XtkRDD)y~r~`8Qj=c7;Vv+}Fdi8P6I*hhuIuH~dZ6X}96aAJyrKi}(Fm z7yRa$=!M<6-HxX=sw(_Qel3{q%M!JZX@PkAzG;H}ajQX%+r9ZV|9(8y&kS0^2=KDH0;ZGMXF_vTHW^h~VEd8C~g}@K7?N;2E#Zt4DKA*?RE%sre&s2_5 zaAV?zc4Ke!b#d`~pSB+jEt~oOT&$J&W?iwrpLhGJ{QqT;aQ$8EQB(fdn3}K0_G?_r zjhgwS5;{)9GtnVF{(d31xZV}6P4`l7JvDXS(sCmMG?;Gk^4-z0r8<5||EWEm|0_Ct-;eY6+Sg>ob8K+5 z-JIg?nl-o7` z75CBCWtwLVox|NBBeJi&5}vPnd$eCHKH$!tFTYir=Dc1$Xa63i148xxf80N!lJMTl zZb`Z9?QJ>tBWhls^B46FyC1gaXKQ`Tm+yX_%r?-L1DoQ9OVjs#VKw`cAuYJ~@R8`C z*@?T3b%9pexmL;lJu}NRd(z)^&pv+(lr;s1>k7_BQ)zkqeWkybcZr_Y`Pv`%N9Xk9 zKXdLLKmOB%iM_=>O=Xc^Wj2)_&`$$!7N8Bp&e4)hl0z&R^5> zenf2CH0zJ{#qDqIo!|CmW!2^TA=?eVN?T36bt#l_XJYQvF#FAWek{7VukfX)P$IPG z=J81Q_2uQRqR4#twf&5x5)#p%5!G#b*gs#hcc7uUSWCx5@I-v!n8?+g$w(!4Mq7^6c zb4zx=Lru-UKRwcjW!A^Zxu^u($YyaBXg!ndy`Z(fjq8 zW;9$qU9PlN#v^R$uha#bJl*@*!|uh@{JX_3e)`(IaLJ{R!bHG><<08T=eN(g=XT5X zz!g?d!O%1}fHSdQyz4L1fxxMSt047aC~7Q zzW2-%-OK)-HB0rM|5>^~GlN$l_g9s}zQBaVr?dU@D_8y4E^YtLTw13-&LBktk`u16 zyxE&P?XS>*n&t=j&Rg3qE}i^bn;~{ra#4hB@G>9EO}m{{tiZ`hLYql?d#;Aw-p|HI zmH&ONv)bOp{U?|um1+GGTlT4TAI&5G&oBAnSif`Sx;%X?&-!Va;!H)>qPx3a%l+%C zRef;p(OdPuo`(6q4xVH<5B5qxHPiArMO}6WVp93{?tW4(aeUIfmLPd^rV^Fp|3^eY zDNLdNB3RjEPl^mSdmeFb;jyQ=5yId{CG3DT(m z^{!6Ee<>4;-t&4z?A!jrQ-&%BL>ZDfsyWuJ((jnJxZiHq^=#Ie>oAKJD zp4>uKhiMz~VoN7P?OuB$$y{mAlW)ac?BRd+RD9 zsup*+>spq)UJ}Xh;=sI(lUJ5Jo8rI!Q@65`X1vDyA4k;rHlO<^v7iOylg28+h|M2A zK8oI6f2!_Y{!Cw+)VD7m)h&IX>FmGPGCus%y7Gy4R+dZq|B{_un&mV5^oq4zEnnk=u>5^L+fkLkkCyTbY$Qv<_8Y>&$npDDJMmlFhcRUFiz_3hmchTi|C&-(3gZEDW) zC3aW$%$)b-Wv$AWop%;{d%X(DG@AD$dbiKBwQ}=+><+9B?=oMevb1d9CWoawC#PkF zt*a}mEWF1nubUTB<(m5NpzcRV!_-tU!Jf5ZiLdEByH%-IvYi%LFFz^DaCdwBmqQ=K zkMF7c%=doZhYk*KqH&viAZ+)Nf7RdTgx`s-TOXIt>9ygq`bW!h|H|!5flTXL?oa-A zXWOEjRr7Xz-rc?b=#F{u6Vlk6!`vQEt`DC0@)M8WTN%$Nl@5-WNN#q|Bv6Sy^?U8k zij=c=kKTrqAUeE|=A(lz$Cs~PUHK;8FUT_bSZL(Cj^V{U*{jC6x3)O%Hn&j%mvoI2 z)js_ArFv9+z5VIQ|Br1yI%#@YdSDb!M#}rJ?>%?z7N0*^aqjG1>&TNv>t)y-&ZY^;)od(kzO%MG2wh)w zb&5^s-l<1!ckWswmnyaxx-{nH!M{hi9a4X8IGrxWx9%8Z^^*?^)9&*3T6q;uE||L8E3oIj^11S)vgmA-IQZRN$)!M>{4Cq{bBMN^{-N=PuA|7kZ|}?_@jGM zKBPyMYe+}>zfB3;x@2Y4t59oQmKE-=S%XTy-dR}jf^)I9e(f4^gr4^^XJGfb+I~Yp?>RHz}fp2TAaI5f|};v3)(ZUCCF&rHQqy0UZ(Fn z^)q~@!-O8y$eU-40@F747T!F!yl~==Y{dh=r~Fv`P&PBI|K@dRyZX27-Mi=S*tW$H zQY)*eHU9kk{PprPmuH_lGUw5bxuW3=Js;O)?>v*bK)m()ZC;ak#>XB$7^Hyx+#L$|k0%3fXlNw#y_{^}=BqCb7q zo1FVQl<8*7j}>v20(vp$>-+;22{x|Yxi>}O<1Bh+_#zdjBcXXZ{22&hIX9$(K$rP3moBc>S$h>H793_B(av{@%d7#35E+ zOn%bdJt2y-^R#tce$4ni@xI6}+5W#vDr){ZA6+!PW=X}lH_y{+jh%N}{?t!L$`*LO6|_1R>KF|R%{yCwb7xuqX7>z(yfZ%!*#=j8P;oU(SW`=j|T zf2W)g=Q^}ICDwGx)`STysbRApl~reju&;fmq_tM4$aA?ynEmcO7Uv`Ge0cFRD8wAn z1}Qi5P$eE?fUZf_xCx`ceX!bJ3q11 z>`7;t%9(55SR1?UE_1o_w0M?|Z0eObx2+Q$eouIFL~}at(ITgk*4bG>>+DX3PLtfD z{`K_yt7|%1yRsy@+_%|su}*lsv{wEEo5?hmZdvQ?V&S*+S!{Mad-o_;zpCD__LI)_ zr;vqB$IKabPrvDC*8HVy!l@P6Ngt;PKK(K8jYao7Z3e6ThaVkXU?g{Rj%D$eMUeJh zW1-)J!redbPG4ugddK^d(l*Y1oV!vF)P3>KXS7WJHq&j>yh$H*(j<#dEnKQTcfzDD z)%!p19?tc?;2LvSb?Ut3M{H}O=D+ef;_OzI5;gxvxEN!N#ZPZPl~kc?^J`WvT9;)b z>a+h_5A z+J3!#mRA&77nl?ic(@S5qs42;Q4Xh1@?)$>q|1Fwfl*$#( z@J1A}lz7+V18ryhMaA!wlog(x+j|N9o}ih3Mbp!Z1{do z6BG|ypZ{rkl24YDpHimTwW4?~o1ar-qBwruyrkWwfA2&3(ObUShh!jg!#&1K-|iXw zz7p&|H!1OWqy4Euv&o&}dfW_iZ!Z7H)DaZ8Lhh~H?m{+@fO!RI0Bu7&i`ea_Z{l}7 zT$A*37rU2rs@~j9S+D(1=Bb?Y>iBx0c#mL3)7!n@mc*T3;CpCu?bnsw-3*@5Z{3>a zuUYbO%C*x~hvzTJoxHQ{_?@YX3xrpx%j>;!KDq6<%Ka{HM~#{Ok#%`#JG&}oE!0_h z*O%hneK&kPef<{KoNGn% zJq_;c?kQbq{xZdvb+XLz9W{yyk4`LGvSxzGvs({Me|mU}tbAAZCiC^Vvqx3G6?aa* z^KoU;?Q^;peIb*Vk6R90beEU?=_B)Cv5dO$j2Vfav>E2!UVl~GA$#xfyg7#3ix)nW z1lJsG&JR|m%WLl|e!jeWs=t2jiklN{`QB-2Zl9)l%B#%w@708vvCFquJgL>tsoa(? z6|||!zyD;Et?HLm*$3G7>InbUnJmV0CjG~zy2{Bh=ifcqIOXGuoI5otv)^5Mb*-pc z%QS8F@zm6`fTCY$+X~e^_H(k9|+#;ygBDwyBl~1r@{tv zrt+Jquj2pRoWJx*y!)4$knekL_~i!)-SManzBHTtPDPYJ$L56^${YPSw|~7=fAw+C z$~B&flM1KiMJ@iOuYM=>(meIY7d`wA-SW}D8hbi-3FFV4tX(iCm)ez*tBwJaC%AkPGn`Vq)sXl7 zjh%acJYPbk*R_w6D8uB`++n z_ODB7T9~}IGUD3XiR>xv|C72~x$@JiKP{~HG+P;7Di>2){c-*Enhg~NSI-~jfs`;M zij2_=E9#b7-m6o(l;JM>+WqGH z%k`BnCOvv>{=f9&nQL3;tx&tJxia+9DqX%;t27s$z5CQdWs8>driIgmpV$b99rjbX zxpGS&=MCjaX(w9`cewjalARiuy-xkT*{t@(Q}RBo-DvV+=^qz^o6FjQrypx-3BB@u z^Y?RYbG>vDwQF|7&-`%qOHJI$in7Lxk#nU+s0T(Vwu z#rB?qTB~-?dU9sw@wNZF?&|Jbkh15g|E0-)&QFUA{F8L|(WANgKRpcVj~!}bfDB8$ zuxR`<_1US5x1ED-_rH{6afYsr7dClwYMHT-(&p-eDzxDq34sfFLe9$=6Tr_^y zKTwhXTl?fH*?q-KacRq^&D65Iy0Z6Dwe4%+@1L4^y`yUHxP=7A2E|R=w&?OP+sFmV?i?dGcm|bF@cisBR{9{1}W=$@2J)?O< zWcHK2Z<&^b&aA(|k!>_H_e6Now6D2qtmCqd?c1({rqipnU#{TG#8EG3gu6!4` zD{j@&=K`y}cE`zu+2%}7JtB5$(>sUQV5wIhi%x&*J$iKe{N(SfJJwdb?!Fi8^KPz` z(-cU-s>oDZTRZV`NZ&VEF1GEejA9I4BC=~k;zew~O_uza^fDvBqMd<7At0S;_59el zn5yT?yF<_aQBpnhabt0tP-mIJC+3~DOIfT`ANN}SQ(yV&;JwMOH$*u*K z`_wwEsXSJ8y8TNmGQ&z@3~ehn&DT{I*4#J6KDwX%n#VuUzF#$cMh|7D?l8&O_*=&0 z-E5IHkDhHYS-{%UxZUH6s^HfmzI!w06=`Hm`6pF8-TJeVV)d@aA6IuDH`CFqd#zd@ zr8B2^ixp&|bb&Ld3%q1b+J@~-e6C^n+iO@8-h8j$!?CD`L)KinEdbr=xnmL7MdFG0FpF;ibtbJE@c*m`& zM|OFt`KURk9k=>&zvgB9iDwT(X2edP9Q!|AwEwKg;f*ONx6O)mC&#Ta?)19G_9Uy`o#SApZp>}p0(WlS!A)TxWDc1 zh>ts8+XemkaWMZ$#3ln)aF@A96I2?+7QSdN&UTNKTx2zkWd~D&PA31w>)`Qi2_@|# zph~1irP25HJiUG8=f%5@YUg`)u8<6OlA0e7UK?5z;-B@m=h(b2EBC8kI_0)?X2Rnf z_0m;uO{H%?K2|?TzjoG?+a=z;%QwVIO`4r!{d(7i)#~rP&e+b~mgaR%RnY3>7M~?I zf>md3e=fmMnW=X5RLtc0U*^0?U7dP3REtS&^H%B4zH7V}|B`CxZP&kgH6teSp{!hJ z|JT*3cjtb~=C!Z-uqbKf-OY&?J>~V%d}oFqbAWVekF_=2WmXVl`12trZn4a4h7Xsf zF@!U0IcoH2ZdpIE~f0RoMmlmJhE+_|>ScDd){!{_k4widWG-rq5CW z*=Cvdo2OshnIU$9@6fd9fTrA!UU6?ZY+ttbhn%W2@I09QSu}l9LpXzVk^HCtq22`Tzg&_h|F#`g!h}pcrsSSi^B+W90rEvGyln z+nK*^KXp+q5wt1E;6mc8?BDJ8@86eSoqE`$9kMc?XZnG6cXxMXPm76(srYsJ$htXeoFj5BT3a+d4<=|z37m|ki0ztG%R%32y_q`fv}`5WEbtRsJ0`EQ=xwkIU^ z)V81M-#0CPa`T_-oIo$N?o+E~?OPc(_4~q0?#e687p_T_T3S_Vw=%d;(>?#zrgeW# zO+T~ye4FC@h$B%(y+1>qy+5@n-z}&}{#?q{SMO}j-glqX)HzdqZtv}D;_-W5zIpVi z`n-18n_KrppljG9Y?;dbysLj5ecx(rFT3oSn^vG+5?^C};p}shifD}Vjw8>!QKvp+wvQeQl8N!VoXr&G#~ocp7r{7mm7wC&azt0 zIn~4UvBLbIApgYRs5!h#Q!J#vYlwJ!6TW+MMaox=+~*OoXJwXhT=CjErOW#kPh~hq z>k^@wpTUpjd6%wGwEvVpo9n8ni07W~tLCy!(l-r?ch>v*((>rMTXyg24y7G-`jj4c zdih`PZApesUYc?8-p@X5KDNcw>+Ih)_x;f|2iJXw*!=RO$r)pa>#Z86?|qYM?0%P% z54ssfsx;A_chls>FBiY#WCxX=S$+>1Ma{+H_qnK3*9gZoZ6ti zL~*9jlPTM@&W7C5veuH;ToILcdap~^Th6jcq2ZI|>)i{C|2+x1S(3SW(S3nQ->1l! zD;XVK)#zm-*ropaW~^j)=$)lrtC#F*$vKvlVZ8h5&)q&n*BBHwUYcm`<7DQ$t4I6m z?ale`^yjqbe^#;nCGM1`zID;{s7jmZ`#ch2`|~SS{5a!pA8;pczt{oD9M)p+x~I9y#&H_5T@2en-cr+Rd~3a3G~B)-vGDXJ?cjMcjKaJ{ zi=AdIe>Xp*`cby$av{&BQ>IPOV*I-2>a9AnWdD`=y`FbnYfYA}^7JdaG$SQRyEsOC z(%Mx$b9ONMPgCyfyRk@iTgb8&i=N2#yB_{^cl)xkZWLIyr`)sJtUrCK$C<0ya?_8$ zI_4AA_)BKB-Qx2Ss@hN4oko{)S7}-@UqW-8sk<@Edi|y3N-Lv-wV) z4BNVI{@JdX0)kiBPgH_aK2D~w6FH~@;A#LOsqelvgZqUW&NX!^W`^H`8i5gFFdpF?hxf?~cv?gY|`^YUbO^R0$)LfDwWxD-=f3KkF;pK<6dgK}$ zov_uo_OgK!WP0DN=fE7x;#HNtGG7nZ?RKv03G5SM+{a|# zmC&BK?d8z}q6~+ZfQzBVy#fJ3mA^m9i^lJKc)siEv_IOCUrYj0PwDIpxqI76%l(t) zK?|?wl}pT5|8Ou}nZu!_@cqH6t1On9w`Zj9GD|WGUNkrP;|!@^Mhf%e!jh)Ct{{>Hl^*-oo4qR#i8OWZk*uT8$IXG{9bkMRZHfrS^o3nx>>x( z7A4ufI#Z-MPiDHihIi!!ZuN;QpE`CGuW!lqF80|PGCj7i%rblf-|zipkGmdyzCJ(I zUh)TTclPw`&yX1bWp~i*q4Um7H~T**KNt0G1}&vJ_@d`s#BTeNuU|JX80>nmk<% zO80?fx#vZ~?|j~wbaK`FXsyil$fqrZEZc%==goQMV)k`LgkMqcvlTo>g~gStwPy0% zHJOoOb2GHLuCUT#f(qm`QV;&h%fCedk9Bef;68QmT4bv+jN7nRugH zn>wzSSI4aC>g`d9FtqRW(5<@aH``+QPjC0n{9lXomMWhu4*w|Mt-ob8<2&tpm6zJP zBZK)loWYG4g$?srmfhSoUoAjB5j0Lu`$T8Pj^f0br({n~kL0QSxjOIRH4$5`$)_(a+vHPn*u}5yteeV@peuih z9m19wmM(qw=1A!io2^g%zfPG@t^E7U*Pvu$-5=%rJ?SBJPSN8^YRwrb!DyrC zR~Z(OR~-)Tdv$Wnvsd%HEYl~8Mn7L)&GhH9>3e;-h*IwgXczjgN`igZmWJ&8p$x};>l4V5Sf&AMe&-BZ4Kde?2@z0OUPM7_r?`N)* zy`|5?BRNg#Q%Z#FwG)-u&etcOHkh&{NJ+Bx*B8s&i;KfHWh#4lY>q#=xaG?65dXuG zmZdE@hufZPYG3m{+06d^)Y{+ApGV$H^v`ryw^!=dJM+}ro=0qFZ@BI8u|?ZF^KoY3 zL7vp;RX=26uJ11PO%rjyRU@bNvi8!~lecChO}2dRz0@S;-rVPnS6)7P>K7RQYU8CR zhxT3HntwI%rbHUVk6QaVE6-!SBcrUsXeC|b+<;8QHi`lNo&fapk=8LD=(@kDpQ(4sa2bSL# z+V(HjZr)R^X|^Timo~gIoY~V~xxHrf+-e&G%V6uH4~jRa&-`@y|5N9pn`K%>JvV#4 zzdZS+>QXtIljZM8#paWquPjUM{^@$??INxtxwGaZZB!~%UGAx#bo#O8j<&>?H})6p zt8~eYXDKX|-v0A+R?#A_A7z^5GKOAmm#*vo&D)rB_wUi&albAm{p)EBN`lOu)hZ-} zGlbaR*uuPl?@-QM&}8l0CH4*Z)}E{O1+Yzku6T^%wAt{;ZTFM^$wxnJ)%fv%YIWDvmKcb!*a(JP%ivdCW)?L zI%iKzFF%!|vQ2N=?UnOf_XyfOjs2*;+U&``)U^+f3+G(F6zuV=uym%wnfO;ynrAgO z*~q`&9{7A|-O1LySA1$C@8x>M8l?(sy0qc_-Ib?*2g~&G{k(L|V7|?=^x5T-88^<_ z6!FMaJ6kT&J$-2h)5Wy3Y3t*IYJSb)7x@M2Y2Gkr+Pin}W#3@->!$)M?&mBtH3GH$ zQ!M&grhalwRJYS%=wS)WabRHDs<M^#g_)#6Z8(()yN zoXaL1TNV@|RBT#pH}>SZ{zTxVCl=kotT zUf)7bZ4f`3`f0ZBt%^@8E-fyVegAcV?{$_G$Av%Ac>QOczM(Q9iF5uW?n-{;)(LMi zeB5%2t>d!)i{z-r9eaLY$^4|xv#dSMgoRs0{EBDpO+B0AUngB9IPv&$|G#&`i$dp4 z&~eLIyY%?fvs@3S$3EG=1Ur<-T3Koeu`q3GDy*aZ!BRpe=`bS%FfrjmRGeYMKCj2`QsMFr(NGV zSj;Bbf1S#?T<6KQBU?2u`fd{4`DVeCjoaQmd4G3W#k)S9Tda?Svy7+ZuGSHL*ER9k zoX|s||L+BO?Ay8iiJ|@O!tW26Zm#P(u{=1!QZD;PQ%k>k_NB=_Csx1xyuIOW-`1d& z6SqwL7P|3l?y*06mmB?8d9e4BhUmttYI|4R=Q#58PG^#5wASLOzigc(4Io-Li`NiIY>TzX0i*;IFka>4DMNoo&&$xhj z^)`**?&pT82f`VSiF}Wn`*qJF(e(kJ&ez@425oq1+$9{4RCaokcK7G}KNZ2BYdkrty=>~4utyVOXXf;@c1=%In4V|2a>>sb7k2rs?$ZDBCi!T+O~t~S zH?J=7V+M~*-xAI!&fjeDXj}M#@++JLtOr&o-&@_hO#EP{`n(gVM%AvM)nNhUOtZJy z#>Uirf8N#pcKL0k-YW+~I%lg_7N&$sMlMmUpO=tcG3j%PsZPbE8I#^kIpP(-nJlq! z)#()GFq54d4<76N8YH7RJ@?Ay?kPQ|dRXpcObh%oaqq8!SJN|c=FQPsV^n9FbEP@+ z2iMP>dw-AAn2P=Sv_c|9DK|{}@g}+1A*CiuCLQjYW<6<@YexSK{;RtrKmRvcpS5%U zm6G6thuiP^nQnV>R=MnVrrpV9`&I|uDi-T9d9&2g2B#rA)_WUrg%Z+YM>bOpp)&Ksf(r*ca^==Prx_52#zw`r>2s>N;% z{-@!p=wzs*@jpm~V}&se6d+j%_i%lAOt82;&mt++6 zR&lw>Zc{DKTi&Dp-Cby&?~a@+ll6tq>|3(PkpE`RuE^|)pmmP7V~(pwwM~7Z`nIju zVA9$DG5-GBDs2QCwx;So>Ua`0>s|0NpRjkco=s%Z{_1sole%d8v{#pW1XY*YZPy9^ zI=fY6o`~v-|GCfPH>!4f#ubP5^ww&we>w56=Cn2Y7XP^7|1Z7p|EcJl9!O)CO(`Lq zfoIC?7Y9yVgKT4{lAivQ8`LI@nR<~;AQsfCDSLY_GUoRM)uXQ4|HOVgGp&?OMb=8W zvU`)yog#OYiB~U8-ZSUVw9Z8Wv;A&-Oe~)meC@=)P3b2ycSyCq-ZiJn_x!7Q6<+6r zpDdZ+b8@Ebq!pqYBPIx4=(uiTv!W<2wZ-mH^1JY}B0aNx{V(Nq-miUj#D+IIC&=&N zj)PZ1F3f6mD_Ys86SLB7jh^VYrPE*M9lG*FUG-6rS!VC*XRW+ZsufEGJ(srsahtFG zbor9m?kVwq@A=g7%J-IaDZY*n{5(BwR{p1{GraX|o&26|zHKHFzxU^xN1H&6gEzP4 zEnWy(N6gTu#TdvYOt?{$@;yWK{1Cp`>;PUN`QJ`1s$xMbH9ri(M|SNCved9Lc8bulOW z-=?T}?U~tYj2?Wuq8oYhd*2(Exwlr;Uh`Q~tFqgRNz>EsYr>nFGku#+KHFok@e0pl zv-vT;GPxmDCwpJ7UKgqU_lnoS&d?su^+H9N2Yx*{Fg4irXW?SszeO|uJozhQX7XV6 zv&l6(EVFlXl)uxHi{8KF$Cs`08hKl02W@732xl;fDDm#qW;j;o|0Gzje2;tW zsVSOoCU3m2UeUt9vTMSDZF8f$05|T^C9r;~By$_2jLTo!*aO}^|+bF9dDwZ;9@%s;CBz58?)Ex6nH z=J>OZr~B3B?cSSJ`f}=`pR1ads7G=xi9C8#S}&i;FnFsr+oV-({p~%G?zle-lcN6fl!DL4 zTw&e#N|eFs)~3=K3@6jt%N@FZ%k5@rsJ+D0s>Y%aV9%sIuWreXFXzsSWSiSX&aCk| zWwUYfy(>XUvxRNHhJ4zVRivft>$%{x*Nx&6Ztq@nCj2Y8nY*;b?0Ne{^<8m4ML5@| zcX(-Ry}4O+*}Cb;lOnqAR;`UYCM>l~-gAr4?G)9>;JvG)J?q1Cb9eTg+3mkEGw0l4 zHX-rN(`r3dOfkMY&#P(OlZd*lQ;lw>1h2Ui{AcMDX|IPf7hO;(-!$p@%f;8uwOwX% z|InQmth4>X)XpW(%dW1ub-vf+{1TasZZ8i$`B{C&Ed6`@t%>(PsHa`*+F^2M#@gzW zb?>VlS?yPP5Uc+;qwopi@#PE^;JPxHsZ3{H%;lBkZ@2v9a(G_Wdroi5%ATr=_wS!) zYDj*_$t18snNc-kyUyRbk6Wef-<{lFw94z$)2~yeEMs0CwBd^0<`XSvzu4T|JWYL> z*EOYw!88Av_wAc>-Cla~X%+2-9v-%~Q=+UA(xc_(u}gZcwO(^$zt*j9(jl zM1B6#Yf9RCjW+Tw)>~6|_NucVU#ffmi!HmN!)9vPJ$ZH0|A@lLAL`~+Kb}D99=&S&EypKu>A4V|l5J{zh1-k%8sdgagZR{l&xYeE#|9Vd(bATT5NE zCuP~&M5$;6Uz%1GWG$pGziFMvFQq`g$Cf99o1UM#78ZMU&1Cj9+CNwP>fZMx&`K=Y z<+0i1+LtQ-du29GIjvJ`!#Xidrk{my7Mvn)jK0 z`+qLhy8C~{heMAhZvB3Pm+8D#n*ZGSsT!c(^p$po?+h>Y)t(h)xVzQ=&7G#uP48~0 zx-c_x^sqKA&3?B{`fu%)iW8InXElj-PTjO-N~@-3j!Ne>S*4w8)avHtEazG8BfWRg zkpN+1+36?UR9;1|+tJgzGP?iDxm|KCUw5qW%4}R-zIyqOryfox+qu@S{8Htt_iXl>CqFj*?a=-?g;lb*weL!qWav{x-sMKD3FkfU zJ)T_lO#0uFnU9KS#Are_#0;V?zLWin#p1MTk$&91*gk69y%~EE%r*d{%-Ekt^Bo{ zcf7dve|tsR>0Ot?Rhm-Um&R+}_X=mzkoGI+}+L|^Wa;_S$+zy{(hDX%+S_Fx9z~{V2EIByee-6;={}E6`n>vBp=36*W5?$WvrDbb zBwncsXQm&HOR--y{rCOWT!*FC=Q?c4ZaNvpci+WiL*ehsN2lliTK(u!^}c{NH@3#k z1@GR;ay-y?Jf`qoYV|DHOE+a7EoT#Rn&O(Dum5|==HD#u1sV=W2;SIGSz0*r?u+*u zzw%cFh0AfQom6~i%9*DXHXlDvIU8(jUl8^#{JO=2w4X*FgWc4cdzDV^l2kqJ2 zv4Tv{Q4K}V0b;p+;{25|5v|N-j-kf+c>Gq_4EF36TQ1t%Ulnfs8Qw8o_*v`3BG7O^LwHC1(eqvF?|d-y|2KK{ zlxv!mUY*fii-WAB+C6)p9$3>;rF_x5D0R`zfYV-H@17j^Up@cyvRSsz9XT~JKmSvS z{9f^h%TjI4pU*w}Pif7nvspe$6NTJ2&-;3K$&)Ra?+oiB`Dfc{%ssZC z^l6}W)LGYdi`Vgo%O@W-N#B0OBTe=FFTE_r(2zT`_|2_smuT_7<~Sx-o~NoOWvH!o ze?!97g5*@6jaR*P{-1pD*ycx?YgNx@nSZ%_$7$h~_xmR8IUmk!$1fSub15J`W0g#%OPZOKJ?-}zxiGZsMK>MTsGFL6cdBdOi?@xv_ z2DCG~tcktVHDUh$XV;txQP%Wp zOa7O&wLA82-pw~@CU*CO^bdHP*tTJ!a_L=>Dg3vBg+kus9X@sU*7TeEj_W(W;*!YP z^*P0~CM8%;`m@&Yl*8sPPlRWk+}*xr+4s!#J34J9Jic6!>V3H+_jPZOn$+)*N7iMz zpVu?nZ2ETZk*xl|1jDuUX-B!ho7RN87(k))E`@PHv9-$lG!ym<*RLNBl|1W_;mE)w z+;X66x%@}(f7f$rHu)@D)fwS6&*Y}tqbX6AlH1>ToH~`_!#RJ=%l*7sj9=!(Os_jU zGc-5gaVEER;HvGGfx%|4%S@k~Ow|+V$bDJ#F=YDlw%p!Rk(-|H%vN3IaY{7WEc)2Y z^a(jTGdBwtg{583v^js>+H}D-TanGzZI8Vg}c-T&?W;S+`#dwTpgz1r}0o%%Z6Y3rk7 z{+v50uRX8&0O!id>7aEQ!rh=_6}H)Xn(fYQ_nCEMke9hZq z{o>i?Yc+&dO}P@;H1(`|(^Jm~<*A!KPTAtBv`kSoKr^sAMSFQj+Ll=vQ;#|Cd*uDk zHhA*ZD;uJ^)RThhx6MhNRO_nnU-RYUFilnelifM5>bEIY-VEJ+=gBRpO9jyP7D@&St*fi|TAa7O`LzCRKd zn^Gs(Z2Kr}&xZv+7OeimDKnk_UfJJI!t4L3pZ320sb1gxyA1Eqn6q4GxXy6d@MsCl zpZRM?t;Ca01)hm!v)RkkpT{#*B$%hP8EYG87sQGF_%PR8fB(0(`n`Mpw%I2cAI;O0 zsFdnZ<75{8_BN2=*0LL`4O1VBFqnrKvEBXU^N@|ZK2n{cDdN6P~?shT-_#|_8Rj9NeW(@=+u@o1`!W&15P=id zORw_Cerz+g;c#B?=*T3FpAH5Onmr!fIJJL=qF;^ASMmQ-<+-| zG_R1g!y-n(|NqXVJ~a<)qv}5A{y+bz@5gh4{SUeKSO2_m+-Bmh%9)}wL}!RrNQ5N( zV>!@x_sCCS2KF-NNBs209Im%Qj5XSJotl?& z^M&J6Hc@RQuKfLvd}Yrh?{HLQW7=_ChS|nx@!IgkT1%z;k{E6j-L!wQf^i4abIG!2 zf+4;-E&uohs*lf7=8#!GyYQW-49At+x%W=_eNST8;*=-pcXmR8MB`1rAi3y8YnZIw z?_Sg3>~S(R@yxl?#xqi8-?(8dI^&@$Z)@h$(jRA3YZX}@Br0yZanz~wknI`9#S+SG zM_SGp9%fs=I)6{i&xzqR&p$1D9p^q9DPX?MW-k+d&TH8tz38&z5ux_RpTUyth5l(G zo0&ETv0v5cJ-W?^?j~ZF9@$0BlW9%(X{viKLsRMKg}CF!t*e-2L(KQr&|B%2H-<`*-5 zNoAz0-jU39&TwLMS;<{zkN?UHKabAi=`cSY(Wdk1c)i@WsbM0QjTA$ZITVFm*CkK3 zIPT6^cyNyWCx7K9tyTw|n=YGtUK;pRU*=%w_Ac$@l{XkpKfD#TR&lxFvJd-zC36HR z$sF;uohH#CUVJ2`eu_(U+kDLeUWv9>GASRsH*UQ4QoQD|+m4*flcv)<2- z=j}Y!^)A%DEre0Qg6GIf(`kpCoHAF2sJE`te%zs2JiViE^43pB|ME<_k=KdX<4ZlripJm>y_;EVJIiy1ATwJ5|`8)geyg4;L z?w-W|C6dzGCEkWNjwCF6$Ej`R7xSTs&C}W}@uJzrMlQ+OO51}nwlFbBT4`?yk7-|7 zxTslf*Ao^qY0VpsfxOXHY$l;5hoto5ecdc7Co9()Y-pLRuGe>VpG5L6;gHH*kv!ZT z$rBc;olKgYbWBJ(XX`f~&c9mmk(bu>h)z%T5@RepD-gEP`B~B_*2XW1vwM2;6gKbQ zV0!LmU~l(lmF*lGe6;lTERT-6_Hp;Jd7j+vji$QW1^Zw1h&;Y`u{&8{T3x2dGsDyA zma1DnNB>-`rdKq@`e*Z*t!6!68SIm~D)qw~)_(7?XZvtSyUysJUaz@M{BPF(pFiDj zkIgz|c^*>NiD((rAO3mZ-K83af&_Dwe;fz)sI!;(r&)jk^LY4!UHp5Ke@?akck$`6 z+xO1ro$?je*>KE3xw%n}+xxu8rn^qTEIXx4UMaRqdTuFjZgD%;%saR6D7PFC-*@~PQ-E{QFFeP)O8e-oD_QEU?rN%HE}f0_U8P~gu; zlkRk-Jn5erEVY^Ev6l0X#`7K*3McC5-@bJy?+-Wo2a9)lma`2vx}@|dcNlorD=PDa z7;foyRc&Tq7OeQuI>#_^ujC2F$2(#c6!)2}|Kn6%X_HWW-8VmW-$(tr^Pgt=|FAjm zdf&Jl*%*jO<>-w_*tES?@b$|s{0nk(0elV`ks`ZKmUJeKFw}lBwMWz zvntV%nbq6ecB9MumW4NU{oXvbH+7%3GB9Z}D|2y!v8PSH+O2;H6Vx(}9q?5uc*M&2 zXT`*?>zcTG|7M|)&B0m>m1B|4XSKi z6^3F$y+6*ZwtNzDagz6umbp0_FIAbQ>s)brbFMkXgsnlv`1J)?FZesl2rNJp{d?nPKW3B-2)cX(4&zRa` zrZR#32>1U7*0*2$+PAIu9y?FZ&uey!@;&>LAFIcG^`AC<-_PgM7ynq!3=OFYj*tXU z?Kk<^do%ePyqyKlMd_vvK4mn_vkP1&{lGgY2QT1Bx^x3KlUPo~Zk zZr-=r*LO~5{jOj3I{)vd{!iNZ_1o-4 zDvN&xCNFx|(aUcg7|P2loH8SI;p-bu-ydibJ1gyGd|j>QW=f*kp=+FeinFy}B=awk zT63}BCYRuW7GtKLW( zr~_Rd9o0P!=_$U8xejdqsZFOj3;uiE-DlD(k#U|yx`5UWdu7G`< zV~EVgvZw?J=5q!73CjBW?te0$5NG-9km7^039=`;1DY(_-fqj*`uu6)Zo4giu4LCI zR31$I|GMb;-RdU5aSZN$x z=K5yg6A|uPt5?1~E3=W&D=W9qH*e!9xs+3a)zMqN=^c^zAn9=2rSNwD4w3bn1mtrc zu>8Cb`QiDcpVJ$Dl}LL1Qsj8n6&lUj=5f;{$l{T+$Ze*O&6%?c7T@re=islIuB#o>XT4<*GLyU}~P$>KHS{ZOw%Qbr-YCE7du~4?PyzpWztAwU+1VzTfkC+KyfOzI=y* zGLORA9Y>Frtu={IktC{F zS*V|vV`=pNg0lD_1@nBJOy|19piQNoW!HGWsR&13<@+R4Cm*?eE~9ez2bp%(WhHwA zC!FxpetEX``o+1YxgYDl*z@a{_O$(9TmM&7zFDkingcD^_`_H~2wER!3{9|Ouvjp= zG2l^m9inH{8rJyz?w@Z@UBmxG{P|q>e!ZM#wenY+yiGwJVjTD1<%BpLo2H?cv+B;p zR6+OZHZ=pO?HO!WomR>>Pn@k}ZfYP}{z52sK8NGJ#OZH(xPy2iRy+)`nG__=JGG|U zSM274W5@o=zCFWfx$EkW(2t!8BrMc(M<%x0^mjpjjN$J_d-1$Z$ zC%RhO{>!|Zhust%Vw+8S=ROi#@aK`DYe$8Ck|WPy=UOMmihliVNep|n=Siqb7X9S0 z-O0o$Z?sVM)BWoIOJ(gu=T$fz|2}EPi&_`vc&7BsrK@H*%sJSg{#RM+p`h&K&;^Qj zcK&aCarixVab;X$67#j%U44gdeRY`Ak#^sJUzzug==FVBmEV2Cb?<-q@;>t48)5tU z`6s7926N7E#Vin=QP1+=K;EA}oDIyo6^-XaHgd0G`mQrQ_Vc8ttm}U-eA+BuXK!3S z|H({V4ylQ`ms!3na}3ygC_|27|)Wz&OcT1J&XLY44-a31>+u>(C%W^JtF@z+E zzqDeyrTk7}$F9I;kDmcc0~Al|ri(XhI~CtA9ku6VugQl?7q}w+q|bLynPdE7;&StE zy)4!Lba|ff$)A~-S8$SDdx{!&Y1grT3lGlPcFQ2+9iwjWktrKP&nPB*P)e)GRNDM( z!?XyE`h+RMv0dG-YWz3wF&?_{L|#NFSp9d`OfT1l1-k69vs$lS&}Ls4zvmGa>r z#?yssPV($gIios#&V=aw>FX+|AG}s{+v$G$*SmVnZ#F!Ne8|+uUSa?5)cdHoPtWGZ z_r0@>x_Ra-q>HH;mhkssSy$ik)qh22*fVa}ug5>jWZ!}3wv`uF*~^~iirFFh;{<5z z>sQ8W0ThMaotibMBLI*=J^SG7H_{}VBz-UhBKqRlpyclJf-s{0@`)ouNM}+kK$qb z*vk8LqWv?eFRgo}{IV|X4Y+q?ovKsj&2=FU6V2;)FF7~+L)6Z+TFLMUr^{nD9*}z{ zsVb0BwzqrdBQNa&MzQ24Yh)vS#CbOO{yeVW#Ag4grE*PG?o-vN8xO|W#2oCY+xdX? zT$RtAt~sV(;v6GgrY$JxS@MY0uTb8x@%OSh8;&Q`MHR)G^3B|MtdRew8~gIP>_75$ z=dZ8eh`Cv|dvW!-4~z${e*FK{-1c$xwEpTd>J3?apt=);xpy#uI|t9s&u6gMAPwrE z?_kjSaPG(FL+9(kgJB)d!k(%vI3d4m zhm!c3#3L3$Y=UfG5Aw@?X48ycxFp_}h5eZ6!`hD8e@YxzKA6U6T$`$5Am4e|>wJebsqwvwP3V zK+U(X2aM|(?3O*VSip7Wey-hu*%KF@+0Xpo!@H9`8DS3+BO7=3zl*B-p1Xg;o`18Z zuiDtDbI~Maf8)N!Ue@0|KYo=tJF#jp=`5=_*k*CU^4Ryiv&^=7KVmO@#P~RhH%y8> z-7Lnj>;IdMH9JCbHXe{yX+NMaDdHHf{#AjkZC<^>znYd$YngWN@X@^FTN@8|hvo4} z?oMvyiuvLFfh~8PXuYd%j9KwUJsS(2O$I)>zV!zfTI`R{_x2IZf2O}!iMdI_!(c|) zhZB0wzj*RIV$zG7m$*Do=ZWLo)UKFAhuCH~Gs>@%e6!QvlG7zhVq?4PfA7?ZViDa8 zm*)yyQZu=sq^7z+;@!jkQ@0r|2;SCGo0VGov^D%mrVLBO_rf*n9!rQMot|Ok7#)4z z#p_hl6URv&pSM}($JTu-wcoSnV{Luq&t3X|+E1&B*H4*PJP+J|oguoTLCZj%LFUHU zdG?G2kiqN^25S$@ulsT9sq6IrH;Vr1USHvP((Qz8^DjXY{l$fcrJ670_-&By<(Oq{ zqU)BZb~xj5)1oM)i=4^F?=0o*V0x_PLVYYo=zZ!erLEfk}QhRII+^h^jMS7yII`J$h(;zotbMI5J6-e}`2cH+coViI~Qs8$G2J3-{kqvaG3k;q?|Q#>l1`nWP=NH6dP7~6vb`~Tp6*t zQms+6!~DA7(G$^u4yike8}+Ah&5-G3mR#5+W5#8x($Q_#^L>sfZ1_nox_ph0k?OKMKMFY2EyLXC9wn%4F*@BB6yheqM(|fsRO{v*9(@@}_V2kfY zm8BIc7Y7+$aIb1*sZxCDx#j`4=ASjpm-Ma{sYFSn^Ta9`ehgXMJ4LS0LTr(STWt#S z^)uJKH2$30!7!EMtX#v2Kp$nrUz6np*VkN%`?MvkBy_Vv>7GlEwdQ|X{@ZhYfQQES zTw9AX;(;x7N19TMbJ{2Wyez+nLw!}dRP<4fTpkt&u7nLGj1MGRZgfX{H&Hoq_{vuA zd0$v3*M6UGvgFKn>gw7kh`mt!<_a~4J+k=Ne zjn?n?;KRyuO z@oR;Iv&Q;MyDXQ=x;*&YzvuWeFL$Sf?-P?YxzBmFEaIKyzQqYSl7W{(diu(x3!XmS z6VczJA8X8i;&8=-g)KS89jCuE=<_H)imOSS{&TWr%gaShF?at5Sk*Hb)d%wAT~9JM zyK#8AK*S^0M6vnbrz&JE$aOwe{W;yKNhx0Ou>Xg!GYh}TZR%@WTAFloZ&_{pWwVEU zywRVNza{7hPCop0BV+Q>R$HO*Bp!&`0>^M|GK9er^jS(KBvt$xhKWxzb_}FO6hXwz67V+5`7H!Kv)5EFt!-e61h+lSs)%gzhdAWx^9A;^* z;ZPC|IdgDI!mdq;XCk=xdPMq!SH0b!S;2nJCDQwq7R#@;_-B&bw_5d!j{3-IC_HfZ z^JD7I_A~!1)Vo-Ay1iM^mHwjP=mPZu=VMGqg#IaZJN^$?cA&B}zM+d>`)$KvuId?= z{H;p;g4&H(4*Z_l_GzZsg9+z%%;LA4$Rd6tyE)R%SWjyCgi}UbG3P_?n5mUN5VS6k zICr1to4@7GvO?L%GH2c6ANU?R$M18W`O)7n_qgICJhp$D%3o_IDk!u4I=#O3>Zd#R ze{6gD_T%)%bB5p~XrTS!!}miAB0mAY{Che%tH>x{h2jtYsHjW?3#R{Kq1)e`x^pKKCxCn!rc z?aC5^GKtNfm&c#@$i11zEV^q8wa>LBud0zfARJSVm!Pd z$zS}yWt~l0j4hHy=g!=%I(Kl&$wPc!wwxC@khy;nzfCs_bHZh_up`b(Bo3}zV9UID zRsOAp=EEN|l=t;0JM;0fKGd1^aZ>8O_Os5vzqVSQn6)Q%_PxvZPh2*eH&f3-$?t^C z?9~M;zuz-1cyPc#y!2qG#2VIzIr7);m)zz(-M{aVyzc&g%jQS-{Sa4FtMG#i!#s~? ztaq7fe>ufsBG(yq2KAt0la*)Ib3QnEwaDl!SIi4YoALj~XP8 z){O!copSEAlcZS9IZT?Iyv#&4Fy7mCAS>jzpq0ttY3gMX0(sUZir?HNPZ%c1>3=%n zbM8avJE0{VrAsyRznpV8dc5|9R*$Hj*-PezF7|s5g-=Q3&G2DbS$f6T>-c=R;-V-I z2TK#yX>0is7?u^Z1bOv;P(8n^{bgWMM#_rcqI-5Z4zk?KaNUV z68F0DzV&UyuEYg5CPaT|$hcx+_JGNZ?dxNwyt|xS!i~#j7R=5Rw)w)Z%E&os+9I*C zn>+kI1ZOi}77IQqll|jm^%f?0?8_WeN^CPAi_c{chNm`66v`Vq!F7$fQGI-kwE|J9{IY zPA5t1$X~o9=T1$dpp#9;ferp0DvLRb3MRF*m@ItI?v*GI`*Y__r`0W6qtqT;mdJZx z)>ax3zI0{F+XJGLYBn7Sb?&WUJ1mfCJx9W$zQZYy(_w$-*S`m*eRMn1xB7S&%M8A^ zBC9`fGtRNT-c{b=Cd#17_;|B?fwI%Nh{fl2n$Me{vTB!^UhoptpNi6!8P5$~a<6!z zz~}X=ASER=t3^@Iy6fi8!`BQgONI)59JxtvPVlyWAAk0-W2V!QA!#bn(oe}PHHYxc6JNb7S6_GH_f zQB~f?ZQOt0^!j(Q$=;$*W(HkIb2@jwIA|HCj$3Ea+Jd0noxDA={`Du*_up9enV*rz zSmu~$_PzNXCyn2p;9GNWLZ745D*@M}q_V6Yt0PxtH6Lnt{^o6htkr1&d&?7hw*}-m zCH4N0?JV7vu+H<==Qni;-g_SR)^0bx<{8x9(%ZlD_tyD`8V|5OGFqIyX`Q-FZr=Xi zhfkm1^R0dQ{QrNS>y^*jDSOEhGK`hRDyJqo12ipr;)gPWxO{{MsOKEn*zUn<_u*mo z^#2t<`+Mu}@%!>5^>hE1EqON4*MZ}%yF<^`s_u&+j_=%#_a2gB*W2z;a+vMdS;g7H ztKUwmyJFLLzwiCQ_|I~O&3ameVkWr1a2LA7a^eeXFYC-M1s&&tz)#D=YVOYHJQ!+H zs-VEBrh6yjAiqk+-zCDHhcfe%Z?8z}EMRnOIark-Rd+z+c0{3aym;=X<@GPV_pDl6 zSVh3gB(JADEd!(TNdR`WieXkMKA zQ+wSvIX*^jZi^kK+usW`@a3&^z(S* zytl&q->m-ht1&pu`~7%IzZ*lgId2w^Qmi@KO_d#r1?dV~Z?V0;VWYBOmU%dz*FoKh z=M|D_6f&y{HP^k#KBc#WHSQ;yc%b4^!7@Qlp@e0r6WgUaABYP(=_YUsPFNtQY+ku> zXEx6ZhsK_1JdDqlsGQGOvx29!bk=NUV<$o9KT*nNckdo~e&xO5qi^+}ScO=WjV`=e zIw$<+<=I*f{yen|=H_~9yegzi?nsAuNxO=2@pJ zx6ZT&p6gD{-x$ad#g~+{RA#Z>RGHQz0n;UZX!86KUN|HC-y7{8>waG_J^laR?fX0T z{8{y1AR~Io!#R*t`))7OSHrUR7dkC^xniE^FS=Y%rue%fs@%r>zVw~Eg8X++=iMrc7G9tAG`i=>!ikRxEN@O(ex zz(dmmcYho3Y4$!9|1?kk&Gz4~wh83Fn8{vy&{=w>ee$yM7nfFaY1=&J5#3tGHT62n zftcP6k(aEFoVz-yEdH(Z!xl2+ip8j=W0-;H|7j?AF z?gv|yzC3in(D9w=MgfbuHN_M3Kg(ySb5A(ouE*nYO)RZOAn&uze6{CuWD@iJKPg4U zmp|IBl=fPosBTw(bLMH?>(2hScR0Lr%ssO8@YbohLZ+*0r~GA0x1aI$wCmx0$3JcH z|9k1F^t_LEPrKW{D!iMsE}az=%6)4cYUJ+8+CTiWgyD|t`XADr*>+u9Yuy+<^2n6$@*6~<@rh5{`WRt`Va4^ zVyHd;>9<+JPHq9STMIK8n9GDzVI( zx6RPWecv%h!;p`={@e=xtk1FMRoiLBdC8FBk2viI=Z{sh`W;vq3R-5RIo0akpMQGC z-rWCte9te=2J82GK3|<4Q*`oAbJ_Be_+_&r8{3mu3nu(OT)sEw=iKf46#kvfv++6a zZ1>FOK=m@d_R2Du8>Ku_%x^T8u8XfqW+;AJn42=;^75~oG0q*FXIBXxJg$`EnSN`I zCV%Ur*IpYk9IV_-^gho2Vem@VVM-!!$m z$EW&4BYB`_`__G}pPVfF_QbNkl8tX#dHR7{?OBs&f%m6rYad;}v|{F$ z)iPCYnod67d*}Z{t34gcwi8^o$}iCA&J*~sE_ZfgSZ>I%iTTea+&H*v_axg7k8UZg zDesH6efvnM?V#bk{|gh14o`{^lwN#C*P!v(l(my4=RIEkJ892hQSDe6Y0JOr;`jL_ zxs`r<|Gya@|L@o3f5)FbazAr_dq(Q))1Y?3U9LN_^Bq5{GnBplw7~P(@^8&yzmjFo zR~O}EE=vNGlcC=t}gBnA#E$b|$wTebZ)^_}2HH~_+rL!|l zU*xLF1ofQBZN3aAl2$NsnmxGcwj%MQKu@FG;my~#d0y`FFVt$Q{vp2qpq=ga8%!Ga zgT$xo6N%q7tu)}p`-$B9dIBrmtS^6m$y~@3USF@kx=+LIs`xR-c{0zKg#P^FzW1iZ zplK6R=#{|g3687x|5$&$e2rvvIa|9VbGn$&A&C>VYKLt<%Q5j-C%N$~_{JVp~?+-dRE`GAk|Go6-T=RXIKX0G^bMPsr{9jF`^RFSp%6Y86 zj-Ojs=H<7S>2p8BoT`)O-xd7RW=LOu{>nM7m=mHqUj4eRZ~yt#^EYPy14U#Xl+Gy^ zemr^KxqrXzx?Jb?FiXhEN}A4l=v(xxzYM(xqz+e@cy2p>hvXfK8_FOZ1B4r zu{4rH=k>m+(`%;hV^k@gJbR<)>Nl5MKhOVe&*}a)itDWV0?FG4HcBv-Z93;9G|MXD zR+?@=u~Xvwz^7}ftb)$g^v_y8^Ml##Ip0o2FA8*jb1-^k%m008K^&cSUd-Bia~&NF z9e%Lt{g~9dX_8C;Phs!Z9nUBI%#wNj;r-sPeaDKkD=q!>=m5q;*XmI z4=@}#^W(bF@8%H8B*RakeJfAJtj<0)>&PtR|av^-h%YSs68@7yahwr-ejwp@3I?Y8&)C#vO+ zRlch@CF8)9WBtkfgW)E#ZPPW1Hr1XxVWjIObL@ado8hxpn+14Dtn;IDkrSr7De4O6PWFgtmkQcS#u~ms} zpIdn3&9heC@7XL5_v@;LHD74TNqchLvZYA<*hQUFcW$)YV%cMOD)d_azd0pZ+{%*+ zOLH3Um&>yk>^0gZcW8pTq2zqsDP=AjvYtITpQb(aUC*o|3^&>hwe2h%i@sQHdQ#2X zrI=*FdA4-@W~Ip2oXM&iKUK#aYi#k1`SOo*Z|%fYhN(G^|LO-HK6zd5_r3nuZI3#W z9G)k}+^^m{S9W2p75|2`)LA!f_vgC^NrkiCcyadVrX{?o2cO5hN!C`kDPB^$`QY!D zll5zj{(BslZ1--n)aRIUTxYmq9*EB1XW)CoYaq`s=MrzHerQejuhMPo4Edex4D#h( ziN5BbaRm05MsrXL*!gm%nl8eCGakpA@6~ox3a_KfG_Qn)fWcm(_049fzD7 zs~ZzePdt{Zd*9&JIQF_KZVFlh`W4j~g`mbJidh46t+09v- zj)m@>RDFNTs={lt_eF9?DEN792#(QQmT)9uqvJiV^%9Aidh7S7^Xf@B?+m^jYR9Mj zx5ucLjsMTS{$+1J_Z=?c)8@73NO&O-C@8Mhn)|EyklO*vby2VP+RO;vXvD>3K09Fh z@A#qW#c=&nv*2$J$mHk-zcwkH(cMJ1#CQxRJI_ z)?I$0NxpqiPwCIIrlpB_Ju|L{AJ4VRdYB&?aC60@%=w0~MIRSW+y8CL`;Pr8A682q zo>lt}l;-pfa2<(HFB1H>XaZ>IbiVfSH_2pw*0S37v%RcMQ~{sc%1R| zeevBSAZX*mg2xXfCl>HNJEW?e#C3Dmb_NgU52c&AdLQ{1O3mx$~ zG28zrny$WfE_8S3<=YL)?bj;4F4@rbe8bD64MiSjRg+dZ?s;G6;d>zUduq3Wl3TTD z!zr_FHt*)Cuhr^bTvcFZTk|H={Oa~1kF4XY_50q>J6QDQ@ACF{{4d1hKg$Qo+@4~^ z7jaw0C~rqcufytJLAPFc3qKV&Z6N;Jdnc&^fn z+o0wF_ZrA5+Ydh(9!$yesA+s;eQABNJcEp^>|WyqpZPD$y;M6hFO8LNEz|XTHKzZk z{hyt;``;X=%ZYOGdR8;SuQ#&tKV6hq{P1v%+_jY-rHtA9KPRq=jlKFOlY6ezk%-J- z3;i1^Dar}wW-(?x{My!Y*3DAWzyBx_nug0+WGpQ^prm{(|)}= zo5SG2*Ys6F$h|irQG3qPBVU?617E zuEOaZqy4nxV=wmgE{-{+_2iWQ3yJN9Gots1IE$?B|GDD0{{3I7|4S-gsoOI@0uL(a zUEn&Beoo!}6==Zv$-ndPqSa~txo->)?lSIu0xwmUFn!l?KmGdN&(){3*Z(R%ebF}k zoYYkQPYgd3&NbWEB|b`at6Ecd;QP%QnSWmcS-fv1$IZWgc9+wNMz6ZMS&nm??G$_B z`}*bF+NT}NXo_sx-F2~MMtIXH|m&~p_8mzDm=?9;jnnewZo>ZC0n;OuloFfS+tmQ`G%D@4hXJ) zR<(7V!|8Axro{bECUJ!Go&FHIy_xyYxg;^Mwfmm%$!@grS#j-k&Hp^nnI}K!@BYNT zH`$5rNOQ`61O6@NQoBu#Rh>V!bK4DJ>3=ip)L0~ow>zGDwzcB)=C8>z&t&amIcxVw zG(YW7JsuZ(6H=td$W(W$LLob`83_`cfL;iU-5m#JAVj%j<-6u>3n9`1E*D> z1vkvQZ+~Vypm}$~kESErSH>T=W9ZQTn9a=oR|ZtnihHoymHeKsx4-JB{}KE5-=A$x zd(&*{kdxvR(4Jp+gkj-B<6X15e!MuioS*;kT1gg@lx<2b&nji*8c$90-IpmXE}eL8 zVH(RS+p`Nk9(z||?(o@an;^foK=kweFB6R)Elgb}$o+Vk+*yxfEdO_&3n)_yZ>xNB zM6>>`zUJd1m-!nvtbexr!%N@NtxKD>@$9yn!OfBMV&9?G_v$SlC(YVAVZ9nlf+&M_ z=*-4)TN%e69sga{niei|dw5d%5`*QoPpanHS;pEKD|kNXSQIW>KL5xuDXZu4nFnQ* zlP~>kS19I4ILcng_sHRr+rzFJ4bkfr_Po8H++!r3>8|7Rv30GkaoBg=KWS!tQiSP! zcBwYAf-n8GJ)t_1@f=ILwJmp?E`9aNZW-U?$G0vXe11`|c6}1py5%+xSDxNH|Eu$9 zVSA~1$sdqV3}Ai6+`zp1CM2XScpmJ&uzs^G!;W`ToS|GpICmDtYEdHSLNV>NSG z%i!JZ)h2mekvh$x6P?PJsI9lTE#H(|k!KzLV8OS7nj7D5tV!>wig*wc&Tk_kaB+?B zhs@I&AqhvPG=zRmwNH}R!LjN0XDNdZpAwlqpZS#*l%t{e-n{p=n1+P{^UTMGt=?Yq z_;@-yV}%QggkUIp^;^k~C->{)kFI6ce54(7dVkI2^NDGbeK!}ph-%sIbwBmSWc$;t z7OPKvFv{ZVJ-*6uo5Az7=ROASUAg@9qggpCBD8N;zq5%buh-_8;wNK~W4Kvv_V@Y| zwjoVB(*7PguxeJhh19k`412$p=KpM;cPH`J3vapIHGePf&#pYS{D1h>h(Qe>{>gTxA1?o`|)|Q zYwm%r?i*7!cQ%;4shz>zZ=k(-Tj1m4{_CG4-WTX=e{XY#+a>xahtDTw9`0jur#TK7 z7A)ghE%9LCWYe})J7!2%7gX_!ufDOv-Fd!^Wc>N^9tJt&Cg*=`6HFZF63pumT9=PZg>3p z?}>99k1x3Aar@)Sf9DE+7{x#InY1nR@QvhH``Aeqw`3o$-t+Xgh?=&2;&a<%-*a-` zcRtzHeDhO_Jm0>VTmCFsUUT&m??;6Pf&Dcli=LZk8)$zpSO=c#GB3AduvjWB6o2Oj z+m~z*dzZgCconU5e(m{BkF@7)kEuVs`<$kY?30UUR#~n&wqAbCRg~S= zca$MX#%`U-9g~~;pSCciwa0v0Cc(7OP}6K(*lQ-$Ia7>+g;|A!mjs*W{fcOz62S#{&=jIBj?o}A^nkUnGcu?3oow>Zl6@m8rd9hG<;6Bzq+KkN4| z3`%;R-&abr^M~AC&f~UWr~8t0>2n_~dR{I1-XinPUF@8pdd?J`u)x))x?N>2u6<*j z_xN0mfbln8|MtnX!v5FaKh3D=JEwZ>w{BUr#rBWVk1MRNtH=I&^7Qt+uji+!+y2|U z?ctffpnmM()sU9Kc}UAZrtibum)C0;3cO0^L)ruRd%jIQeR|!WmrvgK4ZZ?EHt0PW>aoKXG>Bk15HY z?|fr1l|HskOyljxiyaHi_Ldzycr3{4)mOtVnPvfp)>AWIGD;gAlVf^Td4^|2yL8AK zQC9JnetoAz#S9xf(e^_uZ3O|x4lOhA=oJw#*{>b*j9-#{ z@)57^p-y=({rMGDO82c%{O~dUsi461GV8-tQ@dYOyuG0|jW7Jz^4*a#k==dE^ZMtA z6gr+X2>WODL$tkgp82*_-XRx%%{a*$v!|ll=-Uk~exHv;Izr}r90%G3w;hi_(x zdo_2n-xqt>*@Qa>xPQLXVZ3e15p~`)`4hi=Dp!0+bXk*rBO+2N;erL{?IVg8SIjsx zqnFL$<7waa+8HS(X|LpO@XT0qR+K9>X`{aD;}v)Av|e#r`O0_x<4nD8dYh+q798wO z4Engaol7$|KvbNon9t_XjbFwsHPS+tE?ocT{q&*KWyix!3QR%&^8PIJcb)wEpZU%;TU}*q zZ>{9=@0`iEW31l09sTor@0zb>`_n4?Y8s#0UhvJn7S)t&T{UY9uR~#q&D}PcY1MoO zHrg0$yYE}qqxRl;FMrbh{_Oep3QKKLPk!9_@jOf2YvcX(m3J1~-@5=_Jz!G`j^#bf(Z zZW~@&FFTX?AlY@6U{1bY_>OIgZFRf8tFXuS6rJ~t39EbJ|5ZTDpr^E_SzbFxptUsH zFZkszrbnjFjC`8Tsn)-(J7Cewz^5FX)jM0F>d)i4Q_C8i8zjE`GdNd#bXwXBx0=2M zn&#e1(*J2$xt?7v;KzMS=F^jEzG^R{u*KVqvv-Le+n2=`TM*jUId%ITflb|#Q6Inl z-S^dO+T)tA9IYE&rwS8~U5LJFee%~-yU(_pCfyc0wE0Q=KY`QJj=vWUT4MLp;Ht-N zZ~4DJo<{F`GGDL$U+}&iA;sXtxnTu3ac(b!Ce9~6OoF}ijo4xuQ3sWz%<;I6LQ+A7Sa0N+JN-RBAa&>`P%GH(& z@fsczrGt;1Nj+?E?90ivvw7OyH$7~U3g>lN@7dI)d)|eZ|9r^KHV%zxW7S7IsB1iy2hFI75g5Yul@SzYW#=t>D}@lJ!XS< zz&uzO+X&vraUWE+nyc{f{#f@FDOkfmZP(sEvH!o{`Eu>Itds1cg9n(6#Y%J57h8we zIlVqJ_jsn$_4d*_k*5n~%(5~P`ekQ5EfICu^5x_lJC0zEoo|F6y>+fRD6xMDv-PoA zCAq3C2@4;(FMm_)nf-FiFMNtJLFJx zW4%}QS6)ZI^6~$zcj~UM{eE(>-zGHv>!$WZ2+~t`Qj=y}n(Du7^ zdv4W6`J4x**Gh-I4m30pb7F5uJTRHD?8dN-Sbdor^{h0 zm!63Wc1(#+efcZu?atpfD*GiwijTjXx6Eknx-Vz**9-Kj0P{%Lpq-`1yJ-&eh? z+`Tp}aCz#S6<;}-(&o>&%*}tPa>2U8RS!~k*=1N8eDzjhy2LyAiQC%QEs9rH{W&DO zqNpQOy6E?Y^O8l4xkY=<`&cu0$j@JJYI}p8a`U{2`e$OKZ@$3P zo+G&9D?3}UXqik%Ru<1UnL`i# z_f2rw|KHu+{fz!hZTV!K?`v)yZuEY6r1!#`Uy0Xu8tFFmtc-kc>EYk&L4k{Q?EKkg zB>uDV@=G4`GI3!o*Sn86Woi%i#HGIf-s&1{cUtoHud}&VS17CS1{*GqdB4_3d(Gp8 z{HqUcKKXX${Ey+&_J93Szs1H%jswydm;h-EY|n)z;f5c5pAiMa^3X>6zaOfn|Nn8^ ze%GCX*Sp_u_m-by-I$uSV8O}9MH)ALd}!dWG%K9VyoLYrv&NqmtEW%-_8@FAlik{v ze9@naICnc7Fu%oh$t1cf%0@+zJV$`O-SFLSCTxY9H^ORhKhbj2Zl2hWN@|IFK9`7b7^CZDEUC_|sKi z3+BBSOzIJhSDMWh@pECQfHM0jBRz}aT5q}T(7A`*dea`Co^`@lX&HOKnznaq@7aeK zH1y@aU(2xLk7#iBdBYd$c+9^sdK%8oI55RojKS=8+tR7N55l%K^y@L-H;j1@pZML9 zMJ;aCnhy5$n%tWCq4_zbxLZCc1_ciWOL66_swc=RQ{Vk zd+wrT^^-0n&NTia*!U!)XmKG^zTlHZ>aNjGZb-QtNa(n@QSpvh9n;HK6)l;){{wjL z`N?JvqD~#Cul^ z*0N08Ua)aP;hE<}|2^lLS(p07C5IJ8Nz9m6zjJv#`?W_-g{#btUl*A+ce~-rJ;z_V zFMMzN-|yBBg|fFh`n;cBE&VX{_u9HU$xg2>WqU~9I}?z8=h|V0?7n1!W9tge*{3}Z zs9R&4pESkwGyC4!BuV|}?4rlN{cGQFyz!5I-4WH(_20Lo>&Jh+vOZp}P_Qo$yt>QA z1rnsa&>)>IzVH1X5PRjj%V*adV2*6O{ccxl{k8wAUh7*6nqOV;;Dz-5R~OFr_gnF; zuX!0T?|9~enhSkff2d62k~xr+(W~aW)cI)>^TlwxytKSvT&bi$%V>6S&GKrb>-tjk{tpC-f9Jun@qEmaE z9?nwL_}gi3_T{jpOQz`O2*oLeBEFf$4<(L@znBpGR7S#mlZeH)DgEsp>x?JV&Yjk? z_uqUbwV(Z$qwDV2d^(WDb1mnvuWr`;dm8)h`>Z_K`1a0fi|sOvTWt$X_+EeUzblp; zU%ewC&M@ywh~Naf;2(Afrqn&UrBXy+VYwY zdtx>e_V%-#pX=lPO6_E@p>(^A+2;Zo_RRAwM~Wd*2+dK4Z7$ zx5|2nEAB4}@>^_v9yo6~yW!ZS70mL>-bn9K=7=?ZKFc`aw#3_7*G69D^PC3@EOrS_ ze3e`J;^qD|!4nG)NmgiQym6cCHT@}f$m4s8dHk0cdR*I%sNOYK{CmXi`7*_+KDws6 z(@zLG$6PAiwdl_DdH=Uy8!rDBqd9K=wg`)W!JLUd- z+pMn_yz|&1nXmVj-#_S*^Zm(v#e{ir{Ka2(KHgS1RgdS}v-M13vb8(yG*f+(_8Q+V z>pm~_{zrCAgi}9bT;G=0KgHS&4Xyd^xF>fSt35V)-*@g~UfN^d!{*_?D()YpbAPettqI$A?2suey`Puz z;!I`RTfbim7t3azS^MYi93_eM?b}N`dd@ROxbA=1F!@I)_wTOC&^e)PDU$x|5^9nU+4YL2ho0$%=jK; z9Qg2Y!9nMO_a=&RSxpry`b7DkOgSR8!Ad5X*>;LsvT02wZ>?EN#5>8m?jH~HSA6Y$ zc#*sI6#Lg7FPfdiHx)~3FHs4N`6=jWy7cxJ&NeAFGoN^YY74t}!rM<-b}p5gxM5*~ z0bBO8*NP9k3axf=?JAd_(NUxb;PPhhlkDwU-~d;C$?iLDJp(=@;^!UaNk0@7LdFyR?hn&hXx^ zuIJ#L-*@2RH5;alGx{d3;+3ydFpksC)#bLeoW*Cp;uC-6PlNr>FNo@8N1EKIR+(pc zWY?!`n}#y*81F}MmVck03hUQSihO1RI_oJy6f|}Dx=shwuDd(Ig5^Q=#r2@^pI3Hw z&v4BNVZHb5pM6AJ(bN5rdHY`6r@E`d>{6cQ-7Rbda-)J${e9aD_y^vR*_R*O?EkcO{N}t9&xum-{n^KI5um zCkqb>H1FGDcx*GnR6WcaC_`Kz6m=kcpwO|6F-kDl6|=)oH0xUo==v!wX( z56i>a8+}4foU)YK5Y>Es#tc#2kB9TiUzxsN^e(s~OytwHQr=X_0e3sj=gu zN&JyuN8V-)`-jYz{?3`0e!BkqLS~QSH?Ea?XU+eemb}(@wdKb2IbmCNChM5_&tKcD ze=0cPUad{ThO9vK{rM8wqE{vym3Z;v-GRNgMMP&z?VDDZ8Iim|>)eq-`Rt-4J;HBp ze>k_}Q>M@9`qomJ#@pA}4PX5ipJ?-JQh(gOhm!v*em*+?WBpmk65hirL46d9?@bfI z)9%iXy1DLGBDFngvhsXtP1lM+wc~~io5?KtV}wtAD;Csj`4s>dq+!s{%u-t zG2)t*_{?JQ&1#A_PRiFMIs9L9{!jiR#{=cM@}(z}HD*r-by}LiV-^D26Il%98G52@ z7Oei^bi{lLsNcdI_14o)yTC|v$H!~mb@X>VsyxlReb4I@4(V?Gn*vuKR(^l6&no?T zWYdqn~!uc20MPB|aaDK;K zgG-Um9wi)*$jU z@$19r6HHeQm9CYX^&s`{2|hcs8Gw4~qL zpKyygYdQU_`%h*`x9S54bzXvek2-muMrpWkC$8U6ytFes!6ab%*XnapKVR(HS0>x@ zZC+ty#hI8HWy&)Qvsca8zRYibn#${qRp%pC?5tUPbgQQwo8tE8kvBNR9*2udBY&+_(X)_al9H^hjzW!Ua?Rj>F>eEG^4p}rj_zf;co_O0xrnCC3VX`QT`#5j9 z{{FAe>UV?sjh!FMl*|lnYCN3W$ScjaY2!K1`4X8flBEY!ICe#RHptsOqcH1OVg&PQ zk)pN|nKj$@7i;M~5IlZNeCb=Ih@;x`@5*oZl&h(7M0I;IyWc+^kIP0E?tK*G4t&Nh z;l3>DD{ua}jk6ww$GUCnZ1YPZ)=GObqvU2z zj?B_ywoJWi^!(79$RB?hukMS9sefTDR?VN>cUk)ROiB6A|9f^eyscRmcCvJ`?@r^{ z%Pqp*w=yoj7jxS(q57|r+{e%Np6%MNfA(IkV)DJEJ#+s?vh}=9ihMVhJ%6*|48sG> z4{fJ?)Y2(wOguABvHN33w_e=8SI_HDf4X1yZ#x@{`#MYTOwtk1J^=otkKcFB<7fC5 z`jm0PQAU>gk)R@l-=SCi>6!zcArH1$=SSAr{#*0^>h=BqR9>7rwb9DramMS*x@(H= z@^4?=Y-$%&V{q>4EdS@}jy^JL5>^ROW@RwO>)0h zF~zRt3@B)y=-0M>qfo+;ryN=5Ziz;|P3_eEGa*r;HR1l>^#Lg|;m3Izuh01Xq3*fu zW$sPq?Y?w**C_8Xt^Z$tMdJ7N{CDR66c+FM-D#S#ZpCwfO=@!zXTzppsYqihfcC1I!9e$ehI>g{yY4E=W54 z!B+Fg8toQ^;y|y0boz(Xe-{p)S#!?6%+^Z5CdB$?o6U|% z%aWXYW0NC7YnClMc;kkC`th`K%iYsNf4lYTO1S2}GwqP++kW-D#`P=J=fCZ}#m@hk z=V77Y+CK*+N>4tF*tj+R^tOLJ3p0P|=5S0jcANfFE~l0$+;o@ty4o9c@o$@JKJ?E@ z+Qu(%WW$@BjW2AKYziJ6XwYT*zCu3fnBBGh>NCgn>wnL@zrW_+a@)z9z-JIzd<7-^ z<#C@jThHNVI2Six`0}yoV*7f*ZS*ypCTD~__@iA=a(?F1t?z$KeR?(j|5FPCxw-Za z4SDhs7Ra^#v|)RmXm)Pye+$SUb0F zyTsCOH?4g~(gR-x3%6OBldpf_OkKRj)R`sCcnm2&Gqi{~%X~y58*D9Um^q1cLV`7_Q z`0b(2;(I@jJhQNSuwwGT9iEkkC6XUA_LxuZ{Jc>(RNPt@uHYL z+O;<--p_c!{(kv`ZRhQT*=^l4_I)bem-h44{vZ6)Kx5e_pOu2v5^n)@{O5!%J%90h z4MRa+t5tl2jL8SxC!l%5eXMi8ZDvvrX`G$EyIACG66`F1!pg!wuJ!NtzRXoWQc)%) z8`^vE;nev&k1L{|hnWjm*|3YvSNN;2*YZc#R{6%iyD!@OZ`=8OR&CZcdH2w>N-UKJ zmc74m$o4SD7$4f1tO$K}3BnYr~HRxjBr#JR5X`=p=CGA@|#Tn@Xp+NOM_woJ6OW7>v& z{9E{#pDeO@GUuc9!3P(A>v9TnN2@%mUdZ)v9f$vKLEp7clujlUF?!zE_JHkF+qbDd zx!2XO-HiA;qxO7T*&2!ZBEFaJPSr%~El-w<5bFLjS2|_cm%lGIm!1{-sri4?lX*qA z51q3+cldsxWusiOX#3xUHoZUZdrw*{n&iw@b$IEXN9KCPtN+YC8(FK$xAEQPrSm&h zS_plA>Hk9pH0)jRa`FL#c&q&K?7~~B3o8tcGG0AiBiB;8Pd@#n#l5P>lFv4L zc8s}wc4w7Lcj>-u@;|qozH?EN|F33*DaVg1hd2(LIGV_7FS>le+PHe#JZbLf zQ`3KauTIc@Zp+!d)&F<*&b!6y-*#J8D6cP^x~0fe^h<58u0!sdE8-Hb=H_oKUwxpb z`lOJP^qr&a6C|eBM9q8JX5?1k_4s35woKy&*^4LpO^=yPk4wMBX<~6{r}w3gDml|H zwf0H8TBYl3~ zjIi(b`2?KyoN+C@*7fPx*MR`*cuq^RM>fEwe-|CUV`WeRl4tX!zepMK?|L zZ3XugE3Rx_!N1&&so4J9ng>P23rb^3e;o}|{#F@x&bee`yWE2d$LsUFR4*>Hyn2YG z({5J7_Sq31A8Vm3)~Cp7Kg z|DmMuY%W`L&y~aGI}8jm=Qn+c+?ro4HDzL%t^M_z7KO+4Rw|0`yW1>iRQOap;z!%Y z&KQkPODwKQv~2mKD#$K<{>=T5R1xl9KK9@I?<<*F`sSodU#(fODMnd{no5g-iW}fx7yxYgxpB~%fH&LNBY>{9#L)g>O zxwmq7cI>(L=?1@{?zB+e%l3bA3-@*|)6=W!Z*f2J`r;17ThjiYH!pr~lYVe#T;PUJ ziSJj)&Ly)CE%AR7{*m$OBL$tmAA)tB@fB979 zaq(9b?a$qf*UrTe{}lX;Iq0EB=dq{0eoZP4QArZB+&rW<={;ng8@q%(YhUm>s3T@4^P*hwDr&AJpKNkQvGKndvzY_7k-&obL432)5pthsw_Lc zg@1cj=HbNH`Lgbpp4VRH+ZQAKSnJ*%_YTIz8)ol0HGQX9@yEtypSMgo(y#9Z%y`@> z|9R!H4~hv;477z@f??Pu(Z+J-oU^b@D8gkD)OeOS3tH!Sz>-R68Gb?$M^zkzuGrcmRHh zCRt;#Y!3J4tCrWkaLN>C8U~7p9}kJ)z5O}k7$l9WNib$L#rY_5AN?Pxt@lKHvB0L4_H(df)_)6+dj6r+>GGpujL*}WA$6awEb@B=})iof7nl7o&V$TW)|X|7<_&yQoafw5Q^SVHWczUyhzc*%o2uRI^JPr6O~S`dDVLKQOfN zn^b7~{CB*K^82oh@3vI*e`n{_dO!2#cU?{q>sKQCCg^`OdV0@%){8w)Y!Bw83RhPr z$J`ST-{t!;l>fx$WeQ6a#Uz4u=4S&}m++&y^lGyb>(6WXu-;E0rso7~h; zHfL(-jm;l_I%a10{r0BbyE3(D z58o`$GU;f1n(=YU8LKn<7-QKA_xvea5c_F^-pb{jKX*1NUFg#;{60T~kd=+O@cE7D&zJUB{`vGK{qKQKThwb;CKiKE1G4xJsuG^Z9~MrX z!O!5gWzO-*M-Q^!w*rOx@#$GvX{>%rnB2e1=&r9l&OJRm{;zkAmS4-4IFE`2M;uN6 z6>r`@w=%2OpJ{&N#^V=7<_rAoiQ;9dF=Kt&bJ#t$X3Ye}ge;}4>>jpDo$8d2-**)e_gucoU5EYID@|lS*oQoZD9ansn#n2?x`{ zJ-yvoPxEihN@mpJClih-_Qs9FW+(`kiaM`h<-S{w`6p@kw%3 zWae>v#$;38`CfXK=;3#Nr=MJP?!&>F1M@F3E&Hu;e~sw$j!w4Y=f32v5n_mbw6RFn z==s}flVleki8}t|b@vPXc$U@a>uftyb-I6c?{@!NM_Dry-dqlsWfAmy5IB3;XQ9ZI zHj%=&RU9IZt9{nD|5vY8I8Wm9vw1U;pSqie>^WvWzxC~=BQy2yiyXc9+Nt_RnZ2~$ z_3br{@{6Bl`AnZV`5!}ncvPEK*)zpZ*WK$j7aBh9XRLYp{Lk5^+4KK>pYCq=XwrsR zppBmoY`~R8I-~!s8aoDymtAs)#oL8zR6sd(HR0 zReABwyq$kh&YV4r(edim=Xd;Q$zZ?8cX$`SQ}BUhE2q3(w&lpi2aJ`)5^2m=nUfSG zlPoSSl$s;4*z$6o%$DDE(%SqF88>7Of-!nIMTwX3#0(Uxa7Tfm*^%S0po zNnP2hDtco^{ohwZT*Pj1%GPg zZf)GOU$Jrh-Rj$wUqt7cayn06n|$DUWzWX>qT413i@$q*?YPDPxq5}8e@{LXT|GZ$ zTB*!~DWC7{I-q#wPvUz3tEf!|;eAUcEitZD?q;}uip;;t5;jqAb z2g8aJM{j*xTjRyI=IDo)GS`z5m2Vn$dY_w1V4&6QV^1(w@C z&v?&v#AkWS)uVNh3{HaneZA+^@+_6Te#>m$IOC!7&a7P7VuyvB7sRB_IJAUMLiE-%3X<(W7}6Xo17F< z*(Ir1TKC%W;qIRk_SteUC?_3q_#~10K1gxeLt}%N@&VU)%1qvO-+(%4z>VDlax?P#i zlh(8OFP-O_`b@3_!sv-U$NJgvR9 zJKkjH$IjaKrtjm{M~&+{y8m>h-idO6^tPQ!yqmHv#!#*zIW*~*6m#|Z>N986zpPvP@JX}9>=ieHO;;Sr-aU2t zjV*zdEj_sccY9;1{;fKkvE{9N-rbt*0xw;6JvsO1xUGC0%d+CBD#DBQepXo#`-W#x zgtEA+Qhi5f{XV%JW^O);B~4d;*L6!++?)4o`TgwY{1MFwyNyL|_f@EV*n6Wr_GO9k zvA_Oe$E3552~~dmv1{?E`Cq+!i*E5RIc2|Y<>oUxtJX=W&Hp)hX7JN1J9f>xW1Dtd znCtmriyW4CX}x3FHIHWW=js?A``OH{{LQxPQ|_OL`_DH&jkox`ME)1|{=WR<*Ujps z-t08Fx2mw&@f+v#x>uiMn@`>e#jm%1?w)ob z@qicSu^fSv|Mu1IrAng9Lfdmoz1NzXOmcK$`Y52!-7~>)(@GBup`(kt0uJ!ccHem_ z^pRlubnVcUe>lv_grq$}|FMV!7D{T_^Vm=7l5IKQv63ZR#-+6!U{kZMtbpsT_0nkJBqQO19tJ7$GJ% zTjto5^D*9cm$Db~GS?Na`F6{_!&Gt3`*NKF!p(aR%IJUJ_Q^8*^YKmZSYKyL6qMQh z5}dGe!}7QpXAXb&{-CRr+iVqL%Z`XZnoPVe8d;kCa-hnFo3+>*tDaoqUn(#7DOMGH!p7u*rMEXsU zK)?sVEY=wOD`NvUXWw=V_8&$rIo4ohN*mEVqz~p zo%?#8*J19Zl>aAI##CR|RKMEhS|6%?a&oP#6cbDImx_WDEjH`sTXG7odi4FslHQ&r z<#+2|FdF_pb>%^ioOxYt#xk=2uGXp7qRxiDczSrL@B!Y95`TNuxL+&RJ-1yp;Y!+{ zE2@%>8+NSJKY21rNTF6!)lz##^!Vq zeb!6g)I8>G^X}J-OKR>M(W_XY`1s}fSv{w3T$=c!bX8T1_Ordeg6%(r%J06{^RV`; z%8{Aoj~_02HY5JSOZ|KMp0ECQ`t3jU|E)b1%N8@cv#-0@_?WvP+vIuKjy`>cb639p zXgL17Gyb?ZL;9=V6Q8|%bKv&<&j-G7=huDxcCh_kwIk@XuRr|MB13-ErRz-{!V|ytH~t``Z@>E;fRr;9|G;Myq`c7MF87_>F7qewNlS z7A)H*pZRVb-vjy2|F>@c`}u8n-S_z~6RKyQ`s}!I`Spq0qKo!6luqb#KlLN`Ky$jz ztN-zGuj?e{-gIbbj90F8c~&qn-|To}nM}*uq`H6^eWw*K?P_^{V58Xa3y%D*7Tsyf zB(9jO+Sk#sf5*9559jdx-MIYUn(Ke(${(A#^A)@MR90IFjTO94UEZaw(MX=^`{vNs zJ=bgvchr48caYUp$L;E@ug1mE+^w%K9C3K}T*XJ$Ai9%#@%J;L*4HNdY&5KPR@<#S z_2TtI)(d54WJ=e%-i}_S)x4Ma-qvS2m;Wrd^ZU&{#_fEMSL{;VdaqCYywbTlzh<<( zlU^aecJ)Ufgfg_wPjT`{r})r99W4n)d37f9Ku)?66$ruNPl$pWnga zyQ_L#M6myZRrcS?{?^x4|%BES3F`^>kc zz0Rv#Tf6pUcHD=xx9|V>B!6RG!TVa#I-Nc~8QB?D1vQKYTOWVR2W5s5{zuMo^KI*7 z;~D0xeD6MM_j2a<|3Ap*+y7q{o?HK6-~F8?t`8Yj9$p$|m+&c||8WLWO4T(pdEIwKBLuNu0>(VWYqj*zT%7qFSx$^z`5}?Xa^P=D--!UBmWo7cKmrYdA(_T$<@Ep!@rBhNCnI19C`EW z@-dE%j}d>&lEeJ(Dx3|=mw)}fruV?CGh4Hk?R{9OydmDi*YfRgnab1cd)=>ZJ{(%v zz;9pr?3P^_hfUx4ou8KWe^uU=ys`h`FYEB9LPzpL->#`XD|+wLMm~M@- zf3uOTPIl?aM1Pa*H)3@k_?}6(|J=Rp{}cN^?zcDFzpTt&m0Vx);Dy7VmSgV^`S-ik zn8f|yH>>Q+cQN1pL0Q6nmbLwchZe`qzCG}f^}yM;&s%do9y|WL9aMUWm%S>Gk+pbx zAbNoMk~AZNF!3EKbGN$ty#aMpO*koKxya;xR^vpq!y zn#Qvdnfd1=Z(MiJXgf#3;|&ZCl6qt0%66V-3y^j=z}8`rRiKgF>pS*v@7Cman_F|96!BEg{jqBDxx&b|q5BnAmgZc& zzp~ES(7fjPtICY{U&Z$y&)@O)tN#CeZ?oe+yq)_tLSELwO29t!qxIV#Ob>+i3WGD0 z@uS}U^L+b3>?_}u&(6Ku*e?I`^V_fce+J*q-}n0et}CrS4la{juD;h!=TL_!|5iJN z!xH-?_k6u6mBuT7@#}_`c%@&ViyoIeo#S`C$s&KghiOVDd6{Ontm^M6;}Q6MEca8B+SfJ4Mb}TvSQsMrTv}4g zY1e}jjAj?N%#xX5vGfdk-+^G~dnw18&z+LtlWzOf8oK|#bJy$gc*i5Z1uS!l{oXlF zb^fmXzWzo;wRq@`rGBfnUb6JxTfh0=I`)!F3!)~?o4i%3`8&7qm+EWGMQ3l7y?OSr zdCPOYQordng5lrae`0z0qAlyoZ(FW)d245>>7I1-v9f#kYx3sT{#kyh`4%r?-6Ic` z@BMZC#;W~$pL0Ks`5D)Gw{{N)bG`hvXVo`9Ule=L=cr?8^nBKN!yWbFd*!w4zr6pj z=f~##Rn<@HKa_7@e&_p^Cb{m#%;9^OkBc)*f5Te{-jPpw+o=(*5*4r)g@lx_#zk)=Ml>P5F9X77nmN@U1^zVg%D*|Ur>?r>` z@y?IC-@_mIeY(*X>Gw6r`s<#bmturh=k7fH@Q3i>OXr`>e|7)l%b2&%uKaD_7ZrHC zHLt_qgz#_S|3Uk!=iHs~YW^2X_fNaNUt=_G`<%({9HF;L_s(9nBL?bXg2}lzie_lP zj!K@%ePG)8s*c=`$1INXUfsX={Uq>DcGC3h7mjYnoz_=IEYZ}n z@tK#!cC7tz-3gZ&{ENE|t^0g<*6%0l*!^~7NZ0*c)#%)wruXBudWCUdYKK9~X~sSl z0}t`!<8MU&M6KR-#JoKH&DY>$6}A%Dcbab&ufMT0V#2n-JsVint9|M>yw<<Ftf zu0=vC_7<(y3VIW>>%MoUMW{)vN5<-;iq|WYZnhS>ji1@aDFalQvwfu2^}| z;_m+&Gwsu}m-59J9N0LSUq<5Dm9$$^|EtK`96UKIsWNEtXU6MUb1IDb%pTaxiu-(d z>dy`D438+pAKCbCTiP8y`NEQOUuUmA&nX>z&G@SQo>k_r_kS<^o(Ji#{8{vV-Tdu* zw%6R**9EZm=`-lvJZlGPnDKVf2B1+%Bp)uWvYV6$@ThHlX&*YtYeH;F!s4u zaBG{~rO!7PaGt4~l)T0>L}i65f9i(S^S;_`&}a+fDbQ*ZoZK96_xv=c2~O4#QzvRq zyQRt@-P6hvW&6w4wCC4i!9$|SEg7*pB0~00P%(39kmoBoFb{}h03oJ+$LW0tNZbMwjb-a{ht46 zrEJlfe!iHcd@}J2F`IVRLW(tZ_L^xw;H~93e0%=d=HH9^y(~OmzUoKq^so}!KMxza z=0B6D^7tAU;O-{-@6cq42frFGwCA3gyti*|$AJ%qBHB+^)RcYPU?aCh;3&JOj!gdE zX`cReSK54^Deu=#N_tO3&Imjk4QzID!k`*;q{m1Dd_=;A@#|=dY_ks-~Q4P z^JV*Ip*C*m`K^MyV`hqi@pH9)_hi*2aI2|B<6oY^XMQqBYB}x{WN~b&Hv~OeH+hYu{PcK3QP?zWw4u z*`;q@u?Q{xe)aEHotAaS)*8N++qmVUl(^_}%Utd03+IIfu8!|rRDE6CGBWSMgXv70 zS020N7=JlsUtacEo0Fb3-Rui2l6+-*PMvOQz53PqTy^$=e<@4WR(~rxxIg-Tka^ae zPZ7(7x9=^Ss$aX!nx!}~CuYN!c3*pSrpDv%r6ayo^uF9TIY#%|`^1EK-!5!Auev2W|HxLD{JZ-pzWTQP z{ko@X|9i_M{l56=Q^xw{_!pO%E38u<6?6*P->p1%#crLI#-rnQ27g2y9*3yz={q_p z;@_$RTt5#i{;<;ehJl3P=NIfDRY8aTTww2)6uyw*SkNCX|8k47k(OTt9#;qLEHE1nA_K~H=MONb6sjm$RV~%Hnnp1Dod3mTo{f&G;b)| zvy6Sgj?lNZb0vO!zP3?6D=}j7>3^4+ES`J!?d_~AFF1O=xgq=KgGGGjwa*7V_x$@I z*+r%_!#p;UU*Y4cufkhi%-m8kt!?wonlracxBoixT;kj2pQrDMUH$*yNuXYF_}kp| zi{n_@bJxn-Gsk~!%D=ns>rwk1wh!<9j%rJ;EP1f*J*b(iy89@obH7XR%A4Wl5^%3mK0l_=2qno&eKDmhdLsFN=TP;E@7vbP8r@(2&0z*ZifZic^?i3| zTg!KEd?&l_({tWpJ%`A*yicC|K4e}qQ(gDq&hP)q&xy`1x_*0kn{|I_lQpxN++!Q_ znnUV#*^T{QkH5Kh`?bbsdgngc~Tg-`g})03iXz8!UZzQd=qZRRc^ z{waTB`QPsNa`BU;_?PL=j#SzIontEZ{BzHq$geLvAG|y+v;Mc-|0QqpYmR=;kNEMZ%pOG;m-M%`Vr+{_py}pZo9EJv_U9-xvP>r5)YM%lJQ( zZh8DrF!%Gv%jx&!rske?Hk?>^kVoV>qyD;+EL|oB^BQ9Uh3`n%ENR=xx6tTxc((Cl z#!GIux1`qwO5X|9jwv+|i>nM_zLU0)??B?2hxaP`ckWOwI8+v3bs>u!Z|88ibKQr@w~d2e?3?9v#3fX>>*Vi#DO;uRr<{7b7WD67TlqhD;lo|`4*&0v zGd;L1AbhXv`(1{=Za?q;5~cE@a9gHNVGFh0DEOd2E5>&E1{cmS@=a z&pohSd1GjJQkHzhF>C%m>$IOe@|*WbvA=NUOBjRC*XzG{n>OuWP6 ze@|&TbJWK7KBv3Hs{2Z@ig)~LcndGtn`fMSxh-_v8^g-U=VNXNz5l(TS#{ZOW41pM zlkcrN`g+dX%fAYmGjD$kwl3PXQQW`$$mZ`GUw+S6U-#hjz3Y4ToojE9*gWa)_bC$? z*riMpD`Y=+*6sTI{r|lEN1O6r8J|4`df^xMGK8};cFvw9(?WP8lybDR4XGd4}n@#g|i!;k6Y!_PjXQS$b8%pjaa!Z1@yw<4gyx_e3Lmk6+8%>U# zd+Wr65}zbjl&LxF;$J1YuFJcFEBNoVrHhz!ii=j9D2zXR({hG5=LUy;d+r}mDe%p! zJ#c(}pti(~qnXS0%@cmz@b$OsY(?##-K#Din`POXsc>i4tspmv1+r(pWctc1xFW#K zvm;s`5qtMrlJ~Pe&N^7Raqa%{UGmvUO5&kTPgDvPOswQju6>c(d+y?E zjx%5OHS4pP_dk2^v(h%c@@Uz3-#)XzIGUXvj}|Ez!Cj}Sy`v< zjN}SS{!0+`_O6yy}}i3 zfi|Z!Io8;!*BTs&zxIULa-(=mn_pV{hX)S(?j(zPzm&KZ+O6ys**CxXReRw1)jA)W zcq=C>dn6vL54m{h!OY@Yhdg35X4P!mB6Vwdx6Qi1&lL%3B}@B`EX{q?<=Yh$|4?V@ zMzg^A>Dl?)woX>kyJKu#adx4|AtiB}4~m~-+8-Ttt~U|hX4kXv?7y#jQ|A0VR=sWS zqd&(!CeAs*BX3XldrB7=n zC4ctp{i*;4n+ zxomeNv3KIkt?$1czg3Ye*|5X)@)PxpeFy$GJ>OID`*!`?{rBd5yy8E5?(ugo4$Qp^ zn$Gbq`wtpoD{+3r&~%<3-o|_T7Brdjtm4Q2lW&{(_kRDz_0wY0?}b;|o#d=)xsEvd z%3S+YqaOaF=GkLMem{w%4+WOnN-s=d?|9H{|8>KZba%Jkm-r_4bnG?p5cw+PZI#Jr zz_wFZzk6MS@-5>vlliI=d8@BkXI=Rjrpm_rnpfeqOa7AngW@qOW-4~FvWuw59#Z?4 zC~;EK**z)w?V^Nd0jfR8`yTRLAY_NRp$1p+o`}a27?Q7^wp+na)GP2&V@~#W zk(R0A^UgeExHoytl@)rL^J?>p0GWD7^I%wE{K>qvBqSXE5@xtWf^2?FgBPd-;4l9|Wamwh_YOh3wQ#+g*}=VI^YJlIl}92va+zG;KG z8k0s_&t0ZQK9S-&eQ)-E8&bj-0CbysPb7Q2+uM*wtmV$KTz>Z}Q`f9ywdR+L_|C07Gp6f(h}G29 za`JI!EzkOjzp?ZC{m^RW@(FtEzdAM=rhjX$;R?<7og8>(?L8&q872FBF2A{#Wd1yx zP3Lf@4RcGXYfJKu9VfdDi`O=Ld@nho)c-rXD*H|Gc}APs6^8#7?3Xx~TXwX1d1Nc& zU6a<1TaD2vJF=Es-Z|y?xyo&65gU)~xK{maV}gg;p*8WAUpCJE`RCb=Yrpq*Z26$O zq3o*uTiK|8YgYf>QzjC z&7XIs=Pb4FCp=koUwEh7_Pvj~_oe=x9shHF?)SUDm$266^z+>*l&vtSlebq7d3>L- zcFEcPW4HAGXdLZts$Y;?-V=Qdl;NVU-I{4}@5hh5@2`JkJ#c4Ua=Ccv1AqH3>)x8? z|N8j$u6)IB*~n1aGfy1-+0EoXCb{qSy8Jvu=F(-K)l6S5_qng<*qXUs@l^W01CIox z>m{1EL?kSc)0NJgB>c)H)tK>FDMw1b^IZ0=FRzO|+SJG3cx(yhY%ag}ti3Oa?}(hr zW%X%hd&0DHNh60E+kRJvt6OxhHwYO1XgEK2Urol&!+n94k<1@<$$KsPr7QTolf%nP zPyKvl*a4Btt+rhiuec1}*FXEV*P)?0vTk#VW7YWtVP&9;9!ua`cWUdM z=v~6mHjv$xJ2nepNyP;+F zvDT>nbBX-5Z^g3HJ5T=-X7{L70A8Jmo<+H`iAFh%WrWlN)fbC2GNEXRLetS>N{zD{I} zc6z+X=B9PgwtJ1%JMXkF`n0IXHD-Qu!Xu^CKjFujkBr`<+MbZn@#DcQdzce7|kZ zIonHHt?J#y#b5P{|2iPJ`Ekd|1%2(DGfqw2crWaZ+p3|EDa9NH_q)$e%8jEc6FxClLgnF zF!8PTOh0}9zVXr1PrQ9L`|iASZ~46SpLW${od5GQ`{0JDdtd5Ae{OEC?fCJo{qOz% zFQd21|J%8L=if`r_P1~JKZP{e?Kbks{AW3!er>;WUtj!_e}4|u{1Ij_zq6CMLcQd{ z!v4C7>c7!-S=Fa^*FE){uzR&(|CY~=J?aMLh7U{-}AsInKiAcKAaxWEfz;->=l~-$cQm1G9_t6*xJ%iUWbssH`yOO4l?~>aO+@x z2ve-`+YLvxuRWMie3&u*%XH2SJ_$3P`tnRpu=RM^$?bd~n3HYIyZTSeZ5xet=6$?< zn&*&p|MLw_ou$#&CZ%1NxU^}{gubx-iSy3x->1K8zMaIXy6!8s>-*+*2y?G0(LMJ# zlH+RTZ7b>KnN_v?hNo9l?o`q@ncbgv{Ls`opVrJd-!7-Gd0f2i)*Erj&qTp)9wZ!MZNyxFm$`)BSA_oe1P=iD}&k$ZS=N!{(9F!EU>;3M89|i}O&wx*2Tq}PN`~Lskx6ki?S@m`=|G$T8{mZ(I zeVJIaYgWj_HGOW17i#Tpl~4P4`!M^- zmyV2uwjDcEw3qP(G*m?E&A5FqZ2zX9WFNh$XI8daDIGJO$(%8F2@mh8c@x*9i|JJF zU)Ix^F2rD%aLR4%tyOQLUUc(6kJz19eYgDjl#iR|tx$Wt-fC0Yhi?<_atD7uaBE-3 z#ck~`mxb`{^sC~@^>V1nV%hR&LNPY(FIX%n&<#i_s(IxtV;eZ~pNK zvmYk#d}BTT%V>7r&ld;gUI5im-euFFjY4MT`QGr>MfAAh{&kX*EH_-Qo$=XHb?2tM#}oFer2IVVGn=*v3h2moszeT+YZ*g`MNXxK=E9QHhEYH}wl%&zsgd{C!uO{GvsPfhco=Lc-8*#svW$R9FDxp=0Q+>X!%-PsA zb63Wfdp!n8w*4m`En1)ziX7N8jm@5%+fk7 zaFy@es_MQge3mP;*PYtFz(@XB9CzTgzBy~vKlJ`%X*4~!@k+c%N8^6p%+JiLWYlMP zJkc*~D2TT;)|wRHpA%(S(mTt2Uf;vt)4nQN*hF$0K0C^@?cqA!>T6ZmYuC+AT)|j- zaaq=ZGFkQS3mA9hud{D8TQhyl`Pp-K{)zk^QvX0(Jt>y`*A!;$YqR#g&OX+*-=M?L zeVye|)3m~nHIP1{lt*`c4LXn8HqLg|6+{wuFYH6{*kHR?L3LcJCj%IuVBB^ z@!jKGc2dLJ9X(s?K2K!6@WubT>_>@;{I@R~^ouhxr-j+eSni6uy{z)#tSu%9&kdg~ zac_LTL(ERM@#W!~yKP_N|9n}0W8Ztx{~5ohEU&rs%IcdtyIwS;=^<+gnKF!j@IwdD zJN^Tkoq84y8ls?98i~_ZFOA zquYPtS+*>PV4q`6PE)kZ-}i3X$7?dCoK3$uPfv8llrKIL+b`@qr=}nq>g;-s`^Vpg zh_lKrv!-Y#A4*xBUKP}RcokpQ`7DVASs9$Q@1hGW4K9B@Svn;waH_)hNW+IYUz_*$ zYnl{0b=$GEP5^=zp@`$x-Y@uv#myv(N^4{E`}!VAeXWpqZTaS`w#bK<+xxe#y!qB#H|_f>lamoK!et*7&-Y%dUp%X4 zcGV4+fc!Vy>G7*C+g;1AKiB83A5nbcdqKnW{fU#?CjZ?wv)!^l-{6y-->jh3h0i|z z4ru&+-87B=>y+Q~iNI3Y`-U3TNOlFftEo;zH@nH0gah&KUxqmvxm>- z(+l_Q=6k-@-~0D|`+l=?mOt)qyLYph-SA(;u|nN{j~aQp>#DkiKKo36o@tx&Lm<5U zO<4xZ(&TG=4-K36XV{4Ra*%BgR!{wPWQp%Cm6q2XtuAtwg(rJWSgk)xH%dBN@+|L^ zO1W6H#<<1u!RL9cU!%_6&goot()93ytyS?8qC(=QMwW@RoVf7tm-YhPGrW_pW$}Ld z8qn|CcWJ`c(!3cn%${vsFUy{mmicwL>gEI`>F*C#RWKy`{P?h3#`0nQ9g(Rsc=YEe z<;v=uTlcSb-WPLCL)-pHO+CpZ+n4RzD&8KIVwd{LpCEJ~#O6+dy#CTdSv^zJUma~I zJZdvBxu2b(c$u1}BIT^hT5S?Bu1lT-IPz3YCpT`&2-_1`;I zuVl64Pg=3`smU3cCwz;mz5g$gH2eL!PQXI%!C`~EiesmC^~lPt)!Vdw$KJo*_V1;) zy|1}>|L(i$Ka;=R0j-mK3+^Mo-N)?x?=g47QpN3A((+tC?tr5(%=FiTy{fW5F0iP70tHsS`Ob`4nSElaO^XRrXmvBOZ_21TC zhaBTud@EE>-jVcu_3GB5h}$pcu|51M*Hz|OHCMdghuyczr@NL-`?l=Vs~hYEE6(IN z{O#$yn|o4zR?e*xd!FUw&tmeG*>tPGvRM?yy!*2EyP%9jVsc-E zU(CzHEqC4+ny=){et!53u!8AFp$pw&$rD$w!8GzLnA$PiyKYf4}HHosaRj` zPUvUWpH+O%KlN1fonh5E_R5@n?mituv-teFcYo*a{(B(&zxnNSyT_l`vD^M}XFmrX zi7GDtWA(F7pJCnQ1+$;_#sB(|^A$8ym}dIz!Oo|$6%Xd--}tv3G>`Q`xxVgn@!eM} z7O!5t7kswRkT2@j#gH_)OCJ_XmC0QH5j=ZQNkg{#lOq>pUt2wBO_b3%>D0kjb?!D_ zldI&7Cm$6H=T7Yo_lV(pbKT-YRIr@RQHI4uRq;}a(#*GtE{L<)>qSW4-7C)5aWg_F z(%_BHY<|%!^NY>KvoHN>R5-C{Nf)OxUxoXgwV&7gI-!uOaz!ru$K5N>7R7}a_bz>Q zRC~qG3)>^UPq=jI$g9wsMuBgu6>0>H@AFT5u=j7qGtaJBEBdP%j&}653SPN+)iv2N z((3nVjR%5;_qaDLS*B1j|K|A;rTWxm$7=8N7BD97n-RX0NY%z2e8ZqkB(BB-^w{E`FSIdiF`Fob1h?pYf*aueomUD)!3- z@jE^82Ug8ocBR?$^*alx&vD0Z*<77}#$C6!J@G(#?2X0muf4ZAn=b#6#hd$ZQ9jT1 z$jSF7KX|+GNxNCk*1E{$+5KA=7q9vEa$eNwMTv)Ie6jj0{BCdl?`_ln%zgWM{}bzV z%{F)3+0WeqwH!BZ`~B-AC=%~4p54?D|Kx`Xcp2HUlCKIor^;5mSS)`p?*Fv;x9)vf z7r%3AW*+zVlLy!MHBMu0mYZPk*j?QIpiNF(0Y_-dx#bN#3oG7y6%b|jHRSCG=iF*1 zC)F*npm0JOhl%0Leb*%RaJ@20IJh8pC1cq&ale1dnDm<0-c+xCC%oiN$2@i?ce}T3 zi%L~>F1uc``Z7%+Q!zd!M&J0^Ji9MyzpFkK++qI@zYtxd48;))3`6hj($U5gt z+|~*s*V=106&sE^o&>5-|kAWC?2aj|8mvIJv*r7>$>4`!Pk70@!|dxnZf(R{n%s}JYC?=P)>J3DUc-tPy??{AXX-Yjuk?~g#z3rDv0 zB$;iW6V>L}&Hkt!79e+L!xP0@3`IT7iXVzByVIX0)f~H0<*@AV!zt+k#*#$~*j^uV zWA}=D>&nN!yeSqrpxX*@l@nM(HW)})119pHmx(9-;(}!aoUno z?OPbme~-6fn_ax}{E2weC5wV{R-L(5^t|+3sIsNg1HZqM*DZ-z{z|*zcEnrpLfbHz zAfC6niP1qP7cJkpbB|d29MwmGap~gSoJmP>tr99opb8b5=-oN&RJE~Ve;kQw$ets#-qmbf35kk zpXJYsZ>{I+E~+0WYLS!Op$T3lqnW#>QsuZfgZcXD*HgNhneCGQSnXrjk+z-h`P(-K z*8l&fpZouK{{PEA+&krW&Na%b%9H2wJ<}W~`{~fD$qEcRuO$ZXH`^R~y09eA=*c^681!RAZ_omM8{p+_p`KyY9NM!SWTBTdp(n{JE1Xm-t85 zH9lt7g)?WXm`twTE}479``rQdxgv#zPg`R5%LzL=9?;}BHD8-@;@8|;@fH4dh$cgvOGYQxjn%ig4!zaem|Jt2F!XTzxXP1KI zW2*^9bDtD(32X|?Okn$Nw$hljaDlhr6}IGK;!}hCuK$~-@m4q~$Eqx1>!;d7=U&_X znYXrT>#;EY%>jGbAKwq&?svW?JSk>#aLg*ztWC4jieqmY{Z2ev&G{}$eb%kDCwX|; zCr8bDANe`yoMGXDc^|&UoUpul)v||wUD6i$KM~1~Ot)STn6^&(^%mjX1qaMB581`V zRww54OC}#x)PMX&Sxax`=OzDGum7EU+T_{8e~W$z$5?Hud*8kKVMP5K>t$P()bejG zs@?OuTCV*3x3l`c-o1@}RC?a_d)*!r_d6FG{kMSnX!A4J^Nap`WIf=U_rkU0Be&fA zbWra)=E{o0b+@fPJUINm|Nq|m{(1YqtLN{@l{zvU03rt zAcFnDT;`VzGsPu09OJK-NyuJ%rsT*{zV?Z17CoB^mvhUXm|@O;A~DSGd9Yslncaz3 zXI9#n{BVfc%NSy`f_eYGHOI5hr@b{U-4*ApoLG^>e^&0wi+hJM(r?Ik@h}@zNKA6` za9zSV`OMi1zqok6RiBf{i~gJC_A2o3NzNGgWp9q?=*pD-uU^MfmcKDIe&Y+b%|fKKr?b@5IAfZ?O5uKk=SpXsxR`@9qtQ zUDxMSE9y2xRsD@z@cC+w)a-9=hI?JVNrlPy1|Pqfe{NO1#EiqySDG6wJ|&z>TxWOr z>Z|!RXWxBitW?f;6@Ob<-Evd@vGtW!_fKuT)*q34;yKP!NtM)xlnce&Bdnd~;{^m9m|0MEIecz8i{=YB%Sk73N^83ni`|rZ) z))IYuHs3%qmNwt!-M!TDn7bkR);&kL{eM2rd;%&LGtO?@#UlG-@Ao~L|0ezq&;KFx zAWyc=;s`SjoBXn*vxmcHRHQAotqa)sywF(Ja4WN+#IEvVv#lK-^xGe?W=LCadsyu8 z6NMA;(gy{0_~<`ynXru8=2{eM+F!AUzdSQnt=Vt?CiXQ$_l8&M9jn%zI``#3==xO) z4luMmJZwJgW71Ef_t*O8m|Ml9-~E3tPli?sT-$Iht$v5ap7it5;Wv4Y)F}UyIwo`B#Bay04QaOP zHa<=`Tz6{fk6p`8Ty#IS<6h~!gD_zX;NJ z$GW?X{dT`5zK#EP?tI>l%(GL^}lW#byAr%5L{UspxQ{@hZ; z;px4l^SI5`3ARf)%BOAZOm)dT8fV#N+2H)|@s0vCUCZQRh#Tp~9!$~G{PiE%>Z z>72c$-A60eW`9Zj=x+3V%I3B9chFAOVNO0F(x@z#iT5{d}z9)yS{an7|!~7fK zeV)l5LhjC7{QK?aIRbubl6&KeR0D+9KYnC$%%@ah+lRN$Ow7_hOYPqAbKi~Mkp}CI zOFzFkBieNTm(Twfzn!Z8cj4R4{`yO6y$V0b$wpWg)G%%+dt&y!U!UPz3G*XyYrET^ z3d$n*e!q!$`2$<|s^{Nkfi@4g*MIvh-u#ZKpReZej#cdHB4TE794a+xgP4X|ryEYJ7+Gnz^nsW*oS-u=v#O zc>5j8FD4~?KfKQHam?I$b%7re+no8>rCWboW^cWiyx2YH;pg{0))p2uJJ`%kZ;Bhg zUaD-JwQEMH{3Qb+X5ZYHE#1z!LH9$Mb9A&F)W7e4dC)f~ZgY<$*UyWBKQ7*$7+QO! z?unP({r_)tzaDXwcRk3)duO@dyObq+I`(^rFSMR6+Q0WxY>aZoHH+Y2_q?8me>Yb! zPfk8n8u-`zK>FQtH!oeebj#-O#z#BfTP4Nz>Mt{>?A;~Rc4w!4kC5Q@<&)pDeO8%f zv+b|P!Gquxxzx~Xk#g}mVL!STTsC4eta*F$2&o``&<%QxTG zXBjWMy8rals#EQ?dCTT>M(6ffU;i`bw|)BW#_&HkzU@h`JGe7Qu5w)&WPs_fJ$x9$ z2HYG6ZJ>Qq{-CjZU*rGo{rxtdF4#TL+xp4j7;~)lgylZ6YK0nit$%#nFiX*<$L)={C0a(Zy#V&GyCGYuC8G%bE4c2NiHvT_pZM8>=~)kKC!J2_~{z<{N@cKCdS&v z(w=$UYX7(<&;7T%jN$0vBh58 z&Y8BRmpJn_u}ttT>d*4CoZON0mNm}#yZ!21%fAv&e_gk1?NZq~DjnmL+A4~z9q8vgHm_OMTktx+oC+NCWwcvpODxqs)|*O}k#fBe4lH^Nw4 z%-ZTrI-kR%)IFvQ&4y1`)Gf39yW7$z|9NuXvi5y9jM;ZQ3XSU7-LU*ua!h}J>4a5f z>kYRz#$5K;vvtR^L#6vTO!7X>7MN8u=RpF$-tnD$*KC_s%-wi0Rj$>2%j>?Ib_KJ} zhs;^K_o~Iujgd9GZFEoEGAOoiKb%n6ZZLFepmbGTdw}kgW!JCxk>Lm zik5;hPr=E&|B$6kePGx_cIdtX;?eyn%rV})3|vHS*$ z-#=CgC(B)W#dBiwg9?dL%XHX%BWl^^-gdiWa8%KJ+7X6?&pglaY=XV`i`Zqjn)ZBS@`l+o?R0?Cb(w%3oe$3(46asZ zf4H-w#{O$sk-os&d#`Kx?6r&L-3V4Ma4}AJvPJi=C^xTR*7>_NJ2%uFXOy#A_UqCy z&gl|o7B2l&(U^9PPbRB*-jx&XKciovk=X;UWeAAhmlf!+!zg>HF>vQYvZ%^C5etNt9 z&-H%?zgY;-JgOR z-j54|mp?Rl{&14n%ujbD(@zdBT+bX~pH8Gu1MK z-yLaR`Ne%fY2q=5{@rJPcWzTn;awy4=TSyb^XZ>O7dsDia%{8tEqggH(czPzm1()u z`s-2|<+hx!wpzYh^wVv_8A(eG>*^D;zI|GF;fTfZ;)H$+zGSJoLpSsH94=ZX##Fxc z*V*@#PrHBI{e8b+spPgRPk+5#eea9SO76rD$rAbV*4>?RBsKPcZ`r0T-j8nVJ{JGC zQP*Iv+wU^d*;ZSnvyIa}B$oI|1#w@mG!(ntxjHfY?SXk2d3#&xWz~N$?6|JJ?{&f2 zJKL-NpIL5~{rldJ{VX*XH~;^%*01zK{8>-0i=en3~ZkpH!0Ldb0A(>EIme?+KFm`e7-u>75E@>?6K5hDY z_D{VlqEABWtMx<9&1L=Y>xJ~jhMV`E%hd%1f3o=hbwhRC4T<~j*1s=(`ns|w+xW5d zGWAnicg|SJ@ye+4ye8zi$e8YwZtpGPc(~ zIMVUAv#iqX+>P%wW#M{1nyW8OT#)ztch1+F&4ZoG0{ciJa-dpK?U-EP7|7!0m-~D~w@%ws`?)P?Y26cR+U0{QlAkE{wp&NOIeFJcv)^L5aE?F(x6+<4yCoM7&YU%AQq0tN{%OBNo!VTs zvir$Hm8y&r6EKU~w^?uG; z<&jx?f8k4u9k1m~bDq!pvotc{LCo>4*IzeeTi2Si<;?AV5J)QRa#3?$6?R z81voo@mkq)5#jMhCuVhXf0XDr9k{xc+i>05ir&I?ZH#x1upWLi-{8iV#B&>BPcZ+j z-tpvRRpP8y{ygee8YlkR(Z>5nVy9aC>V@~?4SxJRwBp!Pi$KkAqyAeDV%UC`=S)@q z`u5?p4YzmI{##ym^4s6|zsa|k+y33TJ@oj*KkHsWwiV2UE;D0nfUgc*J?Gi|zZd`S z{@cy|ul?;|`JWG_W-|9@75;eKxVrChpdEjU_;yG3O+P+fiMO%%Y;corQr|Iuqcc)b z1?MB|H@s}$o8V@BK}9gwi1A(Z4v!163PF8*vKy4Swn zD&}6V`d;3`;Mf> zbMCq;CCz$XU(=UZ|Ln2a*3iF+CG+b1R|xOBwpw`J2Wj`b_$a<3-|N;?ym(>h6IlGC z`tJsRE3Or;>$3}eZ+920{yXbdQ{hp6PraMZG{4)<{lEIa`kH^&V~tYV=U9Ws6{f#A z20D4*W!n*UX1jJ+_DY)NF89~t&*OjgyZ_EE-=oLp{95o;RtZb#=lAnI6?D$3-7x3e z^u&C2FL^u8{PsFman__`8-?sINo@Mq5WZ}~$K!IA?s9*%X5{{zVkNiks7~a`)+&QT z9_J#oHmCinm>61_IOBDp-M5pa>gpGBeRq|uyqA_XCHt&${CfU_i~s$d$M9YyE=4&x zXl)>K@uFE9%R=U_UGAYeX~NF)OTO~3YwpAr~ z)NawA`LxO1w7YK2Q^!8D6^36GjGCe{InU@tUN5c6D!cpmM7@_sbbY;bTK>F>SqC2R z&dzH6wqjl4f$Oi+`oDcidBpu!@Nl)j4$jR_yrjNXT)nj6T4c=2=&eop>;Ju!OS^RL zsKkxt={tpjFE2V9v{I8$)59l+^`|(3PzHDXt zzSH1Y4ezpg=u(R5c6RBon2DP8z2*b|w(EO-s&BLZb>Myc%1f^|h`NW^|9I`r*M7}n z%EOqp`TKTotp4_6&$8kyxqDj#71lqFFrRrk``4^&L4jwYTS~$nZ745FK76d*?S(GG z9N9T@W*kVrYiQjs=p+6x#b8>Y(}6~bp1(TI{00tuF>i7jjN5{*=oDM7;NPYanAR9` zEb9MOxnC=7cfM+nbunTMR$2ARx&SXm1|+k(Z64LCRFb-zy4du;)uGP z+Jv1EdB2wym;T=M?$(Rqznm$dk)NkjD{9S?_1!JQ8(zwq{&d6r;vZbw?=auiUHSa9 z`C-rXGQqpf%KyBvTV?i+hR!=*cTfCYf3auB7WJdQ)@_X}PVn}QSQl>Je4u~dlNVKq z>Aszs@!vkGgcnFS$FHw{=wy|2aBB9MD?P34ugmwmwKm@R?ndIR)1{1Gf@aSz{{KAZ z{{JrdyLPW<##gK?m3yRD3TfTFg-*cp$J88yuA7KoYQn$gGw*#L*3m_Ek0k%Pimg=zQ|fms{GRzxj4W#g~}@OPblQh}+JY)n2J|gx`1GQKt4S zC-kykYF~&*Ddku#lv;f7(%X(x&lxP1&G~uMa|5pqW8z8nHlJ0G>x`^pH7D9G)ZlW> zPCNJUX-oB_XJz^cf`-M((u*y&%HFvryx3wv_kJ*%R*mWP|UC);V$OB_8LN%eyzaor%&v6uN!EQQxSv?1g%9ZF%dj z&0G6bSln{W;o_BFtMzz<(_ag|saY?vIg9OP({s76H>Pi$xjL)VsM8;`KFK!z1|sK)M_wS>R+ z$-%d!`@aU?&fohwe0>V@p9!sxY70IyU#~JbYRT{XJVE|V0;ju#TgOQn3CRbJ$Lr_%O3&fZd%86t>uP_6 z*STMUTg_vX`FFE;iQ)%>*p~G+`QkYhMvCudi{15 zhh?b_F-;4S6Q9+lHU-Z=92T>^eBU|K#9wT$&!lhu9sDuWrA$3L^{=G{+du8)2{>kgQHLKHOwn;fFTm0HFZ(ZNsH?P90`s!Q4_CGgy+kS~% z2a2qib-y>lDj_C2L16JH@cYA!| z<6rGcFAi!#s`x8kGITZlHS>@_vy*B89ROyysSNc^uEBdMqR2ac_{SM*ryT}Hps?}s&C z%r@Ma&}Z6E(yqw(*~RbmI!m#W3vKqjKgarw>G9k{b7#)I-H{mo%KyO%*DZVfj{DWN z8J?VJ&2i&6%kHWhIyqN^e)BvLIc7Jx&p~kIxjpY6P6_?(Q~U5r_9mU%ep%jPKi+-iAt!?{1|BwHb>zO2>50bW$L8&~-C=4cZ?)9e z)O3|iy0&Rc+j;(`SH9<`PhDd6wfMMy#Pwx6yk4`Oov8aQZ~Le0c-wU~i*F~iXV|kD zM1RualX=MhDRUjm!KszD&4SsfoX3<}bI+~a!y>|O7<&4`u?KaToNK4%U35&W{=VS! z3E#xIPgCw@FWr3A-euK&(Kk}H%u2jPhS@)M*?m^N_WGUvxos<=uH1Mg>0NyA($5`y zv6FsqUa=1^iEXo4 z@~hy4r9;hxX$%vmWaJ*b_Ss}z$2I2JQ+PxpdL?9oCig%5*}&z&Cm+A|{I}Blu-0dX zlN#7Vt8x---v;!@SML4z_Cw#CGVA?m6B;=xH>J<1j$7M0t5CHss!Qv=b>_)4p~4T= z=p8*_D9zY?&E|o%KhsLf*H8Yom0jPPaC~;y!BczZ9}lt&c6zX3=Cz5jDp#YAbI$mX z6ZG=cxozK=`{h$!WVNiaH?dfmY^|1~H~Vu}sn3}y@4j=t*}04_FIxX$e&vRv8osxB z?pdmdn{FOd|8<(=bnkDAip3UvJz1AA$3$Er_vrgcOVxg?pSt+yd)@C_ zqEFAu?|+=P!S;Xf{+9oZ2mF68Iln{tlx5bAQt-@d>E6FPX7uYb*sa@U`)$FGf}_v- zL8Ev5dt2RIN*j+ObpA*`?RbyT3=DH8bU*^1$&Y9TR5H#_|j0`Uav#nVj3SXD#95H5Rn!N6p&$Ysh zV$lbj3}*;-dp}XBeq6M6R%7wMBg;;7YaJIVcGlevV2|RNueap59&^VEnKjBE&dmPFI&b|NVG;2wNwUQv zMf|BB)>eOJe0p!E-h-FN7k!*@(W01ThDEU=&*CDze|?{4-I;lM+oO((=Wlizac&jz zc8;5s_iKZ{tV_&-&RKK)8$BhyKFPY2l>gy2zp!D%mOblMpKrM0p<|*TcX&?rxmRlw zl;hvcogNeD|8T8(;knX@YQHb;e&q1xQ1jlchiZfDosXT5*jvu<&BO0`q~VpO-(rU) z+T&VE&vcdFxoc+sbKmmZ`#&$q-`e--XMFwksbyD+KU;y8`F*ac44%=a&yeROz209) zUgQT0c=+g-uC>5g=6;)B6TzcL+vDqx>zls58vE*oxV>Lt#pV7mxpO~~&HASP-P2LJ z;FW`)$cLf}>zJka4pmsZUi|(;GS9Lt6FqL!ebA15=;R{HCVFm*pa1@!9#i?-7d^e2 zIb&*Tqe_~1{a`Ko_HlQyNlJuzqRBd?2_mN80abT-Po zSYKSY*5{4q)~=m@?4&nG6o|H0vvw!14!h04)%)<0P6 zTQkh#^*^nZskT{aJSXG+snq?(vo=4rTK4Dq1Lw&l{xb}u*``VC%a>)gmfCHc5nFTZ zv1DRg`Q39zcQF?S)o0E8V{K7ve7^79r`=`@whLtQC3<3lAD>FLPxyF>W8K4|HPb5e zx!Y^wnUDWCbMp7q4Nub(_Wi$X|M39ZUdv_o(?8s7EWO4Q-2ZXj##3&@bnR+VT|M7lq{hx#7G5l4ke@Zz{XZ?*3UT*TQrSMayVcI;=;{N$g za-X)WV4iDf*#B^@+>9^ZnF~35b~h_Eu)7o;W80bJ(|+=d{rnE$=~_WTN>4cqiasjp z{+rEjv8H>Os#;LRwV4yA9j!O{)=YN1igO_ejFzj5Pv!K+w%fjw{Px6HqWIQ=PKyxf zdd>BJN|){}yD47I@ogQ4%sN%^mMx`kTaQkYS))Jg0{f&hA^h`Jn##!RwUgLYc3pKz z&9=n!J^LZWrRAp;1Vh+^8yLQSs5`?c!M63xP8p9}UTMkd zzke<@PUO4r=3mnB>S?B%_8()<{o^`!LgU>R`c-hy@6%*JEcfI6zZt<|;YVDcIuAK|&K7_>R_!pJ>e)~Qnboyt8 z=869ci%lB7i%GLZhzwP?IpP{$k|2;YT z-4&s{UGEAfbhFR@v3RlUr6c{Ja&j|G&X+z8P~$lBn(^$Mj>`ux&roS>p7-VZ9=>yx zPqO*v_(+N5s`c5JnJ=>Ox&7!3*Y>SKT?{d6>w*jn3bsyc>tjyI|Iql+W&T7xmETjU zejjFe?|S2Ee@AxF)%Klz6aBx7GCHhruG#f|&;NBdrIZ@ZOz0^6aojms;<5g%Ct_;X z*LiLJdQ!9Y#Q2i&9cVUol&+21e+@j zj-TH3&EnK_i=;KTi+|XhDHpu{#9z0^xA&?3p$#m3RmSrT8xqbM-aVhVKke_y?jN=? z#a(-Ug?wFZ+ftb7yIStGS@^>>*Z*v^-+BLo>Ny*u&}lt(M+4JmL{1OXUH(%o=Cg9j zr9b9)Oq^t4vTo*&>; z_17jvczwGn%c|$ibCwrpCTA#(W#8!5wbS>^Zt>RelDPTIci0>12 z1@{YA^;8pAeP5S&lRr;5UGAJ7-;M{z!@hPmyo-q1W_sBA-Qv$$$HJdSe%}zi+c0>q z(7_E;Y_9vw{Wc@^^ATp@9U65HW(Z4#SwHxxWR2n`_H_?yjNWVMp@OwK2 z`JZQ+E#00L^Ss%3Y{sX{lPZ1)mtW#P&Um@tK(px24@qnVKSU#*G@t)zxPHgF&VA>m z%80VBvpaOYCeixh-C{=Z#a{}Rd%ui+wSC>EZik;u>z^|pyLEC_%vAl)lbOVS6o<^) zFtfQh>Baeq{o7@jxo)f3+Ft8i_u|Pu~Wu&OBY6&zBm$ zgW13OmhpD~pC^LX7qCCO^mW6MW);3ntD2|Hj`QvnSV%v&U7NVtc>dfYFBMKQryO}5 zF~h}%Pf2-=ox!Peu36D>>Vf+@3=w$!Q;E-^!f4}L+GmZj`d(2-lwcBgl?bqgOr@W0`7MNWMW_$1QWU~9-Grx?@a|$+#pJ+DCK43aGvCQtg=1LRg zdou5;FA2W-72*7(r-zPK%kZ#h%n?9Ddqy@5|HD!6Mh1CD%*cRDbbdfqZ3<`kO7R=3kTE z+my74|31RpEpB<1(f-W?j@gwRp7Sju+8;|y{kfq{_D99e`e#LTp9}Ino)N8ny;%E) z6@O01t3Q^Pr`_UqX7V`s`}&3h*7AuLWRh3e=U(HKcUyno%<7ZV>|(V#{_Dx>Zv9%x zUw7%-8vojRq1wM!e1CC37JPiO^&>M`rS(bd^ZxgU#~$5Op53>2F5hOGJ5L??-L2=m z`*h&?G@CsaWyIx9Z8$ldzjedw71P_#T4^zJEDN$Ijsle2@ z|4 zCnDhXye;yzN`k|qLyzs7k7pU#FKBai%KFB`sOK!xRsK)VW~Bm$Oy9!kF016dpPjgK zK+|6*fqTDv+>Q8xr2)587q8pp|7hF(ER8EG&i{D!C@3^S`d9oRwh*VC`{VSE&lY!0 zt6XIt+3OwT`|aqOI3=UsO_p(?$uC0egN++v)xSw(DhGc)E&1+9f!o_>yt_AbAGSEQ z(|xVkt>X7{?zUCzIWe>H#c|c27lfa$vATS|?ueA})`YosJ~AJkp0?T2Y=7*Cr+3kl zS-}aN&C`0C=k9EhUi>ch-P-nZWs3d@6J%%X{qtew`>Ltb zf)9th>QkKyB6?LBD>ro8@^`+8w_sYZmv17+mz!rdpO6q>KYT{mEkZ*3KurdZI$zUc zhZVLCTNK-}-v~>q$r>FFP_`)Qwe$`Yc=NQ!^~vP41rNBQ`JI&h9E~hZIBUOQ>%rIy z7V%eoVE@6%F$DQ4k@+oVt-b4 zKbgD6;bfzyw2r}r@IsB&8=cOjJZ#l#Ch8X}8LeKRQmdZzps|tPLfKuPsnGw=z%Nd51YW zDqTp8f9sO4HP!b$PM%(Kv*xHU_pPSORa~2c_g6-0dp??E`)J$O6E_&UHGWL&PON;- z8FFoveZda?QhlFEg*TY8_kW*p{bHf){zvOx{gaOWb!v6gufXpY8@=y?%3AOGZobCw zt<2x+KNWBLz5d63wh6~G=cyEKIIMWu-CFj=vvT&DQh~>c{B^Q?l{_gnx~1Dx_%mlT zRy@7RI4^sC4CBEL+s8Tl!lkYIe>8aNBuum_X1iqLWUwpCwTC_Hz%21I5gIotPr3U4 z=r|}cui~sN=Y>$-4Y!AD#VY#eeePVS`s?z9-Jh^c|NucWJ8Oq-3U)iT~C)OHDl- zzTnv2kU5_XuT{z(m>JmNvLS9u|2w9}nxKX4Y*7k!kCq&*RS=e}NNjA-YdgBAoPmC-6!fFt`@5e`33lkK6u&E9WwvuKp+5E_N;J?>+qu9`>J- zuNKz!T4c26Zav%{aV%N5xwL4Z=sH{R7?~6MvpCv|Rab3z(kc7dRVK}Bb>WE?Kh5Wz z&sH=Ret6Dpcywy*{8uh_8NU8fR?ppV+@U7%)~s)5GHafC-MuHy{%9fddb176X6eTl zw#O`2dms~8+Q9vD0qc$@Wipo!Oe_1*xmfw(8&d{rOb-n!ocJH=oa3|Le~a_W9dv-|vhsxO%(bN`1+LOW>yI zrF*lSZ(MB5t^T)s&j+vVjO|)7Ql&Mr?PijXXRO$q`g$U-)rxBidD;)y{9(L!ermJJ z#uM#pW*BxkTgN^8@{qr7&Dq1Hryhtz6ge@oui2UBq?P=TVY6w{20wYV=YqmD0Sk_F zFqJ(BPF`x7cSK=YSkj|O5!b@In%PY~96m;K$Gl<<)1PF7oYbMk>;{yQDM zKAoF5Kc;}Lp!MC;RIt`$jpT+6MwS~+wCcV~{&8uo=(++vuToxH&d29I*cQpQY-m&A zZ%*$0c#r8oV%Zwm%y;v|Huf#QziX4kky)#cy9%^iXwlx9AinLCaJEu{a?p->lVz-{ zR%)7VSl1R9R(LS|Z>W9Blhs=k_W#^)#O}CA{@TvDugY!DNmlbbzob^yakfH9$ld8H*`~e6wpNuK&0t_1ksHj{*;! zGwmLHDVdt_WM#8L>xp-lt@<}jco_A@!2igKuCm#O!^0lvh}rZoOS`#5nY}SMe){WX zzDJz%_*O4x4^O!=f91>tIWHr$UnHdLH9Rg=b8lAkCTE4m&b$Rb*>9=XW;3Ys7~b(u zh|IqgE6M=$fSkJ}9E4SJtzbo;uoOiX}BZ2v*>wXI4^MCs0D8py`Gm5P2a@M2 z?tMGSZ~yd^DUm5Bmd^pL0ul3B~KYA&M9;>x%N!aT-Pe+ zb;5Gz;%3VgeBECfpZ-sOe|BEU>Au(d*5~yzbTrOz6zH8PVZq48$dbXd$YtUtIhlnU zTogGyIJ1X4%c8NGXYN4L%DS$`dm#ua5e(j)X?lJBRj!`!zyVr{H9*8P6TWxtn zpWEwk<^m?Jz=aDr*7xY;Ty^*SS+YV{Xnhrb>?IBTGtXQ3?-tMJD4rSob*k~U&e{9s zs1`I#S^jvkf8I+|1Bth}^J^+uj|87x+qGip+egPv~ z&62o#`2Krt`_=zlBJwQ`Y_MvJel`2+{CoBv`gTs0w*G2XU&QV|&$`%j{`Nx)xaS#t zn5dQ%d9&7dS?+^r@_n1M_nrInP_9r$cyhT=%;7z*{vRL4zgJTDlk0!~L~-wFSsuYd zLKb2YKh@KJ3VZvmd2vKezOef4y92Z9f4w^`Zud8MyX@^v)*GxpWLRyeXZ(;6cT{YX zbwN41-S29s1r{HR#&Ki%1qWPMo;;e!AnLz26t-AA1na`(yF+4UNxN z*l+G$!?QPteYeJ=gz3hTf76X_iRsiT?3+C4^iRI%59eQ(KRUSU-vsNbUx$zLz0%uL z6T9Hn#b3+X=G^rEDqi+eVbxN*X|XY9U&~G1{J2$m#^h&*)^r`KycsqBlS8J_^T#pX z+s>8tPQLeP<0SJ^Pvbvt#aGB}b~Etyu5nJc*b@Ko%&)_1V`76jgVr6DzZbaq(J8+F zmy8~LTwZtXlAyGO#l_I{tM@INl+&)wt}bHTZcy;?rRe@|iPLw#ds+G6;mq)!ql z?(R39ue_Pzz&W$Xch9n~-+%d5`}h5XFXg}fzJD~|js2TG!@OhFx3vEAl|PV<|Epe~ zzUsWeHm-G@{TFSUXP4zH=UmIaanpP@7YCl=9N!lPZ8kiLfo;#eSpE!`E1dCk3G1{5 z1znl&89#S5^~*o!)~*)um43D@y;+E_wx;jf(F-#ST>Zb@*u3~O_f*TR8Jg9hqnMjVCjWZKe45$t*N-@Ui}qUwx3km)ql)-s_!k5@o z+VJ(NCZo2wt8 zs>t8|;Y#3-Yn|`+%{sLD#}Qt+PiOudTKccC``IFkuiCRF{g2xI=fiO3SMhlVdcGR3+JilZ&2i@e^+f^{y@I| z`F?xpZ@J|O-Hh@FcN+QYe|(<){om2q>5skZE>BnIO`VffbDHIP z_4&4|tvzi|HU3Clkq~+r*tXv_vF3br>%(_4a$=mHf>xbd{>O59bmapVjxxI^&u^&R zytT8W^ca)%k`G)->sfol=c;VlBD&C`kS(BXolAk1LiMLRQUTWIxU5}s4|YrVP1cGJ zFxuKsw%sgit67Qaw+k6;TV&6zi1YDwHd#Dz^H#gCxNrsr2L?|U$B@u9MdIPt8CF}k z)_(|@q|E>E>HN5Mf9`_)?AIIpCicZ_J893dX5T{H)k#bDzT2cX{eExa9%aAJe}BJO zCLGcbYJYsw$Lo)j?}_jA-#yiOSKq$|+i9<4@3bGU{J>u9W!~`mu1NouqwYE9is%1i zWBd1DYB$r*9ggL{)!si)(!0gl*d98=uJV2LCpB>iyMm0+nnz6=51)VVCy(pqu1>z| zhqjyDU0nG}!tyY4+0zG;s;c_8g|g3x>SCNcXD<8vB&+a8VSR@l_sjl?D%fJbH)6** zRx!Od7pK)`#SF<+r;fd=tZ@SZu%>Q?1;ll7&54CKYe>+H?k>%O_O4`Ov#@71j z>m3Q->fRlAUH|FW)8qR;oZVb_zl^<%T`m@s;pFaW-)t&l&ntgm{QmEy+{fN&i4|uU zmLK$CmUK@2oAD^+1Urjf1pmJ*SGFCujBgm;)m*}K!}ik8BbLeg_NMCZ$tmT@%8J@0 zr7nMiZTpihSL0@5%feR&QlI2x@058bVesg!)tRRr4X0ci_*ks1oV9j8vTo5W&k~Nm z8M2qP>4N(8R(G?~E6Z%2FtsfFoRG!2F1#qI#E#vfd&h&Q);+(m1iPd+{W7x1-w(7DawsD)u*0=zCykijY>e zq{#Y1XI%b2+_W;sEWB-wHnW6ILzdgMdNm$lT`s;lhpbC(rdI-L5<>GFlv*}F|j_qXKl zu4v$q+4JI>T>RUW@*4k+28SO_K4&S#=3n!s=Ey|ZIX<6m^ylk*S}lK6cFlz+PW;_b z*5zMLpGi)T%9T6xA-!>R|EH8m;Y) zWdADj2i*9RBou=<1vi zBD+sdMj}{5$5BFh`;?Mj$D|V88hIK!K3tLW^n!JEaE$w2p)g0qx8I|!_@Z(v zFO@gHu0PML$ZpzxnV)u_l2;sg6!#$|*Y9U-n9Yu+CH?De8eW(3%};u`YTa&wgCF%= z_Qx$VdbKgc)cxLnrzw9m66g5tOV}qSH1FG7lioW&zCK@E+|?o;s$CT_T_<3g>Brt~ zwGW4m-Iw{eyllmh%O8Ipo>cQN{_A1(ZQlxF=YQ?9mcMhbb92(v{v&G^_OJeQ#BJU= zQQJ2U19K9u&;9Z5M4Nt$ta^PRTlll3j)uM7e=c2*y0rSkA!fJg`JwgC`TX?cV$XB- z@4ddM`28H2;AE4w_B+Pyhy8R|YybVVlH8&%5$nX*yp;h#`AH{_ao{R8!niq^Y?q_I|`{sXCK;Tz^I{rGurj$VnY=0x?axfc~Wd)HfOoruRRGmcjQ^@6 z@9d-v&CK_+pLOR~a?h4I%_hF_;ft7zwATEe%in!ky{%8Q{CBBd^`|*S4tFd}nneAd z2kZU$J5gTd-!qZ=3afeA+A&@F^4WzvYi!Q0p5ru0tS$NFHJgH27xyx5eRnWnb+O$c z9{KNA%wF#o4nLN$|MdPx>rI;PzWyou;}8GeBcB;>?*vfB61f5&!@3Go$~R z)8X5CHd`5AFkBqu{_N;2)8`MPwmB7FTh3@Jvx-%cr+NBC@lPLx!g8!?ObO`G z{l4f!R&0!^#jKU{HYH3wT2Sx#RdPCeDdVp_Svrd?YXdyj`s>W$elsUDknzXGD9#6c+ujucE)wdgrC9nkM@-Fb+*s8w*AI0)^5J;a{7fZm!oRaU#*rfKd`ZD z{+~sw+H?FaYpIS~QrP;RFeY4iPae3aHf)Jakm$hZj=NwtbXtCzu zq%uQh45L|8i;F8<97s?_*Q*yV*QFk9p0hk6o?& z_SMDh#xlOOUl#FP5@P@U%VqM9TkP!JvHwn~{?qnlW6k~Zo60fS;A;doOY_`%18shX3%*C^F_!SvZ*mOu zE|ig37Ez+u-u_VG{^_ilrYr zQ=e~srQfrv(=MsazR>7Y;EQvvcb*8USsrZJE%Qjx-J&CxXWP-{`KZR1;KqrYTR9I`GQC=J&qQtA zp8C7d*H`}A=38GeNk8Ug5(;Z*JsXWl>RwK+fOMci`w}`5 z7w$gawg1%W9oaee69u@x_eFo-(bfney>}9C-eTXyN!1nkKb(k%iVDNXWX_`cN?v(KLpSJf7JS| z|H&7Q>)iMH%rtFyn(jM=HBs;J=`#U2T$8pe5|x*hkN>liOpxOG4wSsJLmCM z-+cV);U=ZrK&3vuZF)u>d*AS`u+Z^TT)*WQQ~TEg2l?wY=N_t*VZnmTM+r)xO@hu(ssLc zweKtmh0Y!GXY0+mvN84e?oVZbe2Vwp%WwV_W__crP-u4HxrzV$dKzr3W}MZsIH}R? ze{F4XXL5O--_J>*7q#Zvel-n!=d!Kt>W)|XcVxZ)vd;g}*x#0LCnD>^q4S6Cx0muA z>tmigE6nG4Yjh=tMs!i~eC~TSEr-vm|B|j*#UB6Z3m^NLOMxE`9-i9shMQf#^4O`| zt4;s8lz(hkTK6z&dIi_vb;UPt`rkM=`_|3S;O9O63QojNSjuZ*xYuI!{a|Ukk}K?X znHhUZO7(oY-AK!^Ddvi%Z=fE-H(o^9Q8*X&WH&rj6*+0W> zegQ+((z*`NRZd-((LegER`zrQmN6oc;9K{4q5-D}%|yN$oCKkPjJ z@6FTR{WV|f4k&+{({@1aw#0iArRxIA=JO=m@~(R=p;db2aE~P4*7Cn{(wBMqCumPU z)4Fbd%j=cK&5vdHQYTNV>Tq+E5<01#d{Z_$XRWvC$@d0M##`1|quKV|tPB=dOODNk>v&>wF2Osz^%BF}3o^aBu={i{SWMXg1)8KW7HZ8IYxzxP3 zo`XR%j!DX6j^u;vZ1(QpKNhco%VJhF9_DJDf8e~m=T+F; z;OXz?YMZY4bwKdWzbA43Hm$EPxD(yKMPsAuiKyZuvvdhi#Q%ZIKiT5ftAUAd#@)`2I*-0g|$t*t-2IUm3C&mHr)clDtk z=eFiQes@6l-GP6s2Od{fmj5lk@i=&==-&fYAFfHy|JnHT-~UM#hZSVxZWw9E#T%Fy z&ig0Ku#DYI@XLZIw}TRD-Tw3a7D$FTIX=03ZkN#`t!+L@7oSRqJTEw5kkQ3t%9mwP zaM0?b-D&PK3-@+=hw)69`}+8jk{woSC0^;>EBbVJ=W6?>Zrs|t0}loj|2=RwdzxCX zIJph!H16Ui~kDVKSy9v``$y3 zf~}Zlbt!v)eDdb=CV4OR#}C(<$Xk`TAGXbYy8F!vcw%qNsms+C zoPqnc`P(I~=&tV^l16EF1B+0rg7e+93K?z zXKhIBIC3nqS;6qsONq?pM;=!S6@A^bJ=7|Nnd37rw{6-~d`wrGooUAp{fjQ)C8s>* z@D+y}NanoRbIv`MPa}WrN7v&GMzUX9(2=7cXdcAF?{+?uL>r`~GuftEXA zF|tour?)P)Tas|_sOxP_eT(EL>y-oDm(+Ig`yaj;W_x&B-_$pH^*cVCEq=7ZVg8k% z@{c{%5eD7%F{>Q}+V6jr=Z&c3WquL-s5t-sl_Qb8DeC&+=hhr?&HkMnKJ8Ou{8!=0 zJ2tiR_de3Szs7#&5l26Pq-+%7#{rNlpm-_jy`On`Scz59OYe+6skG}ZsK(Y0QrTR7RKmCf|_bKK~ za?w1u^Ro__tVw;c`t%>8VB`5=XG=_bz8Yw9Gbe^$J*;S+Q)3fwO6kJH&21;nO|bct z%B3>RWvkHQ-4(yNgXVfV$%P#jeLkZmkSnw7v2aC%tKM=;etx&*n$drkE)?4#^DV)e zcXz5-v)nO(1EIgJ_;74maXS4|x<{Y6U(u20bKF$SBsWC$dtQF7_hgNzj{TJd4syC~ z`)s5s6Bv;b8_6hh;nS;Hv53eAEDO3DEAP*U+*QmlBkA>>uIcwB=iarO ztfyKQdFB$|`mgG)M|eC{j;;NryW{+gU+xWcI%h9)ls((~sc`PUIS-5O?=BCwnDX-S zj%>MiQ76xyOaE##*XFtZ--Kd6?u}un)@#(i*I}=Jn>|h?E_KcGUuGw_bYoIb;>!c*|nHG{*%V0 zt;h6h53sN6yL4unjah5w?CDjZ?nm2qzkie$yWr?k=JQKWt6dk}YrOv8x!$?EmesF1 zB)4x*|Bts5qdz{}c}#laeEnAkHt*lD^G|dB`~Cm_yVlK_053U^>X>xO*-Qy1_o?x~f!B=JZ>y4vdWn~F2tlD9d;8c%gyJv?{6j0GEi zyh6{34bx+VwRj{2raNw_N~ySVfN$O8g}pw8lHoIN$+N9Kx%L|q!$h{m*KFZ}oyIM) zNmkm|ZnAxjQExteX6@;}R|0mcEUBu>d%SAn>&O5;R;FnOEj0J;t~=PnbaLyolj7yG z|MvA1=$~rTFLe)@DYD4jqCNf>pXYmjIrPXZq5Od=xH9zqQ&gU* zpU=MU+s*ototEcy`}ak(t^4@(;f!xym1&-dtMt;g@?>VuU=c}R)R6DlC$aLE$q~Vm zOXmhCIbA!P&=z>e^ueL+Y_6X*RSS20QauxMOf=iXvN_0NQ}MRgTg|RrTW0X*9Y3IM zcvkx0Q~l!;xsPsr5a~Me;4>?Aaf6+F_JO+;l{7;lBD1%jta#1oe!#XxoZJ4~%C`@K z?>TSY;x4SXXqEflRJO7Y9vZctcQmIiVHI(}40EN1TaBWne0gBIRXEWPgKxBt44 z$=pv&7v_3=_*b0%c;fY2;VZHOWLre^=D2%&Z)DkV@PW@?t4EXa_x+zUJ#Im)cMVs_ zhlvLJ-W*)G}3uFy0yTe~oXITTj*H$`7sH)lX;qQV?JE;bN07 zw{3Wpy4ftI_`N#KMWJiHv8m{rzL;an^5pf>?*^ar{d3lu|N5{#CL{0e2c7B@?tFbe zKHs~#``dYYZ|(YI=AHM1=g-;Me0lflSX+s&ai8D(F189d`}gSlKl$^RZ>$3~Q;**Q z=fPiT+t%G}d|vW^JN{4g^zC);@1A$G(Gz!$Xw&0Osk)-EoN;4qs&JZbg`BGCj$X#z zfE>e@>xzCpQcQaLZ`HN)&tk5eb(Dzt`NU4W;Hl; zqVG}vD_=?NSl*Vf>G<>+&fJ%F8JU<}|I)sAkNUrv-14hW3-CV|KEG=Jo2l+M&Pm30 zDfV6cy5gUpb#W{6Y>NlkZ=OW``N7s^a^zL>+~W6#uG((=G|j&{J6P`=%QZdQIo!+t zJUMOq{7!bb-lvDk%Pd7M_b*-k_{7qAN6ybJKFW3aT=yNwPl4OBn%-A!ombJZkAL;A_W$eZ+Tykb*ViZA|9#l{ z!?E@I3V;55|0kTE|HcVWe7au-#i#pct!)$THnx{H9DJs3|7m`F-4A{_xzB#e)gfiK z&+}3H! z-!1AmcP?MFYl`aLMN%)7^>$Y8U&kJJa%!yMg={#*PB!6G4+x#yvQ7bn7T)#Eh|KA6Gu8*C@cb>F#?+tsj zbEW;s%loGG+k6aNbMEZqKLrJ0f46G8e|q@3BJsoKDED)h7I@oE>fdYkGTY#yUd7Lc zzI-_ccV60cJHzMc)O+U+M*iD3Isd9h%$qscb8E~_)V;p=^?XeE)&5@5vzz;#%=`NI z`td~X`#s#@3f1+}vOmnHpDwri*sr&K&!>`ql`i>d7B3CT?D$+bQ{2XZlRTV&&$WGg@u8d`_A0diFVWu6-Eyptr^woZ?&Y|)H)p6JoyrZtWCP}JG0X}4c#a@EAf9G(^*<(t)U zPIT!E=4W%*tG~!`FeQFIFLamTsVkdASzzFD-iO|Q70%z_W9eW1ugFbu%GKp{eCcf; zC)dXegs@qqIDpeq8Bx9|9Po;bIBkCAEC4F2s5$A7P1{rbJEn!>^r-RrBQ zpIS__wY?JQPm)9v}Gym+#9rLhwiQ&e> z2WQT`T3rz)|42IG$IlgYf0CD)pAIQZ*!mzrRoz|k7_&)s; z6TAH%PANU6ec{GScJX^pyz*YYn6pxF{+}l=KVQ;5{j>1&^+MsL^Cm5?DNq-eczWsJ zSL@dcC9g-77wE_HJ$W(9!1MkN_HEzUOCI#UK7Z5x_v_Q!_kZ{1XUIPRYOW@~0X0{X zYqX;;zdOKe_2J<8ny;VMncu7a`DOjxn0dAT85!RzxHQK;O_sTqaC}~3cVchx-=hL$ zXYwRJa^&#LI`}0|Pj72Vip7&d&h3xm!WDR#uZUlg&Q7+Eo}9Nsd&1c?wrsp4UYot!M6dE|g^wR5o^pSRXw28Xrf z*LECcJy(;}F{4<%`ncrnm=_DLy_wJy{8#PL@AhTWKkH50)@k2gM8tL_zjR|RRBZY$)t{f#q{{xWCSq^#W%hLooo6)8{yuqL*@DG0 zmu4oev$S4c#+XLV&&=| z4XxVm4XyXBWUqb{F4MlV$?^M|yPNww%`r zjx{{9c8)xoZf3BecZcbQ(v@A7KYuIL+rH8-OpY_0yzz{rR^#Lyg?(QIbZk=>aLw;u zva)A`L3a|va=**coJW>dm2Jp6vd+%LilukrHs?c=c-j@XRv(PA;eB#u;|?cfhsI0c z{Kt<)+nwOb*_URpV6)pTA;mS-JKcUSvMdsMw@;1D^_k+2zVG)v4_(w;lKj+sZI1W) zmsZ;s2WdN8ufFoWZp*c%#obbOxcAqqTg-dnKE0}U=HWk6PD-u47Zb~AS+b^`Z|-7; zlf0_WU%Sd~U+ebre4*^k*5XMj+#c4kp8g`~jh_;aufH1e`rfk_0eU&D-gRFW`UF4M z+-Eu4Yv-ik-xrJwE%ommds)~1tkwJcrGBMZU%mBbd0eor%Y0_{sA0o_PlEhxTh93Z zE=+BW-(l3Xxb*%bDa-YryVaN6Y<73nO%l_4eyky4m*w`nrz?veone`MeNTJ+Er-_C z&H_a;ycT!Ub0=rtyeND(R+?qw(&>t^^=!8%{5~|@cH5CRe-mYQ{FLYZF0VKJ{~K%j z`pU0Yugi&T2?S3EuLDm9znhxlxSN^({ek2A-`qb?|KR@p7`s33pS}1}(6?V#o{yh(!u3U__Iic?JUd;jNa3O2T?saE9 zH_3#j+&mJ>r>Uy;kg3$Am~(N}ikkc(jZm+;3;alzbDTe=9A` zUMyYyA#u;^?lVX9-%V8B_>e1qV(}HO^#-}Z@1H!JGvi+PyBpSKR?GgM5EFR6D(vIE z_QQSuu1xTF_b_?>zk;~iQ`EdKH;-OY2p ztxkXNs;lhkj2{oG+856hfB(u-;+2f`jin#${0z9HZba4>a|!p|dl>iUq-~L({jJY= zX?9mlD&1^0rQL5jy6;ob%F^2X=hZ&c-MQa*f8F-a(*Nd5*L~jo|3lrKZ{T^UU*LJE zU*TI=-|^kK-^gG4PF-*PzK8YF2m1e4J>?pNV>FX63qJge{W@~l6W?dLf- z&Ytx&`%!xC@VpxrUf8FBZX ze>BPXUZ?%f6B~a$S?6;4^DBORDe-+hr!Mc>_?)^##h;H*NRdc%*Rs)LSLHH+g#B=FvO-;NhH#KWQSHE}3mx zm+-@5`Q2;XYu`S2e<(40?GNeASsCkoEL~o><3aT5*8w`u_vzQmemFbz&gE_A`QIFn zzc#9|jKRu#Nx3|`GOJ(JTtCF+N)O@sfw)Kc^a)RiTM5E%EGGWJQiF?|- z7S1j8?K%~1_B319J39Gj(98n|7Vh14NbueAx9%)I_uM#W*l;V%T=LenMMuNSVm~sk z>wm4@&7S;qVa$Wb)%J6KCznj-NL%xH+V=H#Ixo&WvUaIYTblmv)e8fI=N0_@-y@F3eD6C&lRoH{Hi^*;j2UN zYbEi&9%~eSY&voMUqah;o7PyFUfaYdOSVE<_QZWQhZC>s>xwI1o7Wlq-XT5&FoFY2mIZMRD zmBHqzA2n-tF0M;5ozs+a=*3ySXA$ST?S2QwG~PAdcBD{DwUT@OudjBuIKyHdDRVn- zEtmPYY}T|D5|f3KvR`$qySiO4CGSV1*Ogtb?(BGRykGMnUt!N8@tYrA8BV3LMIC(T z{r3_l$0OEfJ)I0U9zVL&c1trfk-;M4>va9E3pYH9(b&FxosqhH`Nu;yKb{raTl@Ck zld#N#hlSS_skY5MzvgR)&113i3^J;b!9Q4L{ya57=EtP?9X}f74hy{(j8u<%d)zyBI@_r~n| zx%aRA|N6Mo+w#jFXqG%+|Lprvvm`D1j%wkSZm3=(_@%``e`qTUW-oAX} zOJCR{!Hq#i`WX{mA6$4Yrn+p4+PmwoBwlc=Z~wN;uy0q6s(s$4sfsRGrtJ3mm)!3g!Q#IIi`lBy zxXV6yu$ZyDXgb4f11+aV%oEL?hI~9!P`YnUjJ{b=oc@oe+U*qwJ$&9;$zPYO_DyZ& zJJ=redd=*XTc;fSMPE-*9olgdvkiO|5J_gx_^b&?whdw-hmYpB`&4!ec>HYq!=e^zP_}5X<66h zYj2pR*LbgQytH(|zV)BKGXBj??qBvn)qGY@1mWCzqB_t4UZ_qAmaL)7{Ye z9St4XMQ4mf4l?B3e`WtY>eNE#`4@~|b;&*D>bvplg>7=Sp5ZdN97|?FpN`=3hZfd; zI&iva@!LzK?CTp3xScz9H&ilc+sx-8)4kswJfMCzS9poVl;t%CdoPs6)Q8o@BUtMBi>^Jn$q@;~+e>VHMsfB#llb_CQ5S^Nsr3t3zx8-3>8f!ps7Xs_Q}_w$5! z-7k?JAM0z2e=fAQntAUdH}fpE1M_4}c5LPH`C%H9woQUrs&GeS&f%+KHXmm?+GT%)bcHZ68}<%-Xt3;w-w49-c_U_8h4#zIqLpV*rk=EE07nylGw8=gt>uIJn9 z6sBQu&UMl9^4M2}UyfO%mrZ@-5}sY$*O&hK@j@}3Gd3$`>N_1h?cZ!6&3tZG>*9Ie zGPg^2PkVHjvDM^iqTu$;Z&If7y#26=&s#Y)sAA`nxqV#=;yx`|zEgjfpu5e?iAUc_ zDI5vjZdG*Y6r%y3W$)o7zd1WNW!#cpMLHB&@T*y#=03QwMS0t_`}ZGq>k9s8_B{KM zb@w9edV`Hm_Z2PEt6)wFpO~Cqdu8t@*30`|aPeBa%su?2y*W1Oz>4^LJ2xGA7;Sq| zQuoRHswexLn>yI zgXF*Y%roBpDEYgVVg28y$`UnEPUqWasY^Ej#9d6euvme`0q zd(Ee^Lq?KG@yZ8@`yP`bW}mo!N9g!XquN#8vezsRFRjRZn(^65mL)^Tk^dm8%m1$z ztcoA_yB#-cfAl0|?h~G{nTAW^5^LF;D|f2gu#t$hkNK(OQ}A1~`bulwLl=%VnR^x* zYvd*!ifuc!;K^HsH4|DAuj@rmIic8nVrpH%aos!tCYfDUL6@dXw_$m?zE%C)Pg#B6 zUi+;=z1)waLi%&oDSy>6-4#;7_-c;Lt2xq5k=*gqs$Ev>z4Ki+nd*?D0 z#Z`YSJQrKJD?mi&w<%j(<6j2#nw%H!r3^IP-@XV;Jv?9L!&4uh*ex4hU-@3ZVO`M_ zzw1KCzgFdaJDU=Z?>9I&-S(?TzCqZMTO!k|-#@%6s>iqYxcTe&pWJG^Z;ouZaC^63 zX?V`fCA{^8+>6q0|6a59?OH!=HKrER&Nk2G zlX5ck7c*gAzL?|Kp>rEvCC<7r=c(C_1-37~$6DT+#u)O$Pl>NkuwkRlr-}WmKbzhW zc%#{MmoZ|iRnT+K*2GK=fm;W5@i(n^|I}D8_w1~gUAwl=n9%6?=OWweFQt8f-Weem z4ov2rJ+-h)=)|o3-!lB_8+B8>-}(yiO+4q;>7aJFXSaRV(SAlmE55`Oe%8MJLmZrW#**otMj|!v8nc*5uU}AK8SWc@3hs z>uNUY#{3MJ`Qys|KbOsWuJ0(|nB=zN#H!9e%F~j|pKWM*E1IWsseAq3hZo~2MOypk zFfYFGOxXQTZ>Y?_U9Wk<=T6;S`M}4{;N;2}S?SxKlGVdMwVHpFJzf^|UW$FZnSudnc%JXqZ?7(V03)@x6qMGp7-*7gd^@2q~h z#A+k&{Pm7g<`{X1Tc6vwWMR^0zD}#Bdzq!zO|4G&_K{Wg$MUad|NEbx5gu3j_0!h; z+V44cwu3UGI(U(e`p;Y2UfgXgW{<1CZxN@CQ&zh~U&r0Zh>h9#i8;8RMY`&el`?Htb z+b4LVU_klNlezXkmhw-2W&h|&_Phr>p8b{gJaVV8^jrM7@~k;cZ^JttUwQmMBzyN4 zQSW=dKZ?G&a6p8~W1<1? zUQ28|5?&qf=F+Q%qsD(9aghbl)Dsf$vZL z*YAJ-=UG*={CQ%&uekDj_5E^*Z<(NlawowH#Grd|H-1n0J(%-GjQW$KgngzXBZ+l>@#;SsT_I@SH8Z^F;rIV;t9=w#sT9P>a#y z`EZ@p`gKfw&VgCWw_Q=pl}+y3V4>&#GJoNteD`zR($(2r`Z9Mu3))plE%tNwKC+`5>9 z2`8j>iN=3B8eLJc;loC;`ngXT4|Dmy{ldTc&z!@Tt;;^Dv{lz0G5oPXQ%wBd!^b_j z`?L#V62oe;h16qC##Vn}>5k@JcdS#o@0_&9w<~AnZZ5NR&wcB??}5~|Mz!?pUnB}8&N=F_Hgq0|@aKQWo&TX@|KFv~6SyJ_udt`TcG%vYti0^r zobbItf&K3SPj7PhxFzOc-seD>sRz1ScGl?UCr;|0D<7^u-P!8eF8*mpPG0)D^IT(K z9>=e4|Bv&F9!;MXw|`<}iDN}5`~L9j`5pgbBnxb=|Mo9nF;V%-GX3|&9F0{sezfvGpXm3M z!?@-lTU-C{EfZgti~*53MEa;&wzqTyhF@vF(5?rRGcD!=!s zvW~OjDgDc}YQ-UcnY5$V<^HbXowt%TLI3FD>K_|b>#}A?yt(xC@4wmW(rbRU$eLa1 z%znbPeFH<5r~QnmlgrDqn%{hTrE%ua)XZxrmzbAY= zYx>y#+b*Blm+lqnW%K1PG}^D^`?q=Z{+Ig?&$F#A`}wQ9?jNIyGg;ywZT4Qb z(XApRCw#WVFTcYH_x%qitPqWA>`JwW&3q*=p*_f)h!%zaeG>ZjtZrA=N4a&kir3s(TQak4b*n|@ zCr*BQr&sI!RM!|j=@xa^|M`&inh9?#f4fv<_s19d377>4MYX-?{BY+cbFh$}lEuCC zf@LyM;ht@i_wSt2y!q8CzLI}C5|cEWi(V?8zIb1G^?~sI7amWhMu&gV@nv@4`S#{o zq}xl=f}_3{f4o27;eAuo_>bpIo9@lBD+TQL+&Nm>o|otrQ;=)5f7b)H{#UZMO7!n| z2LE|;H3F5#J7}rSDj>yFlA~hGfk1J(Ck2%=pS` zSNMtl?ZFlQp6wCeeBgIMKKqY*|4)AvdoBO3{Q9p~*Y}lH-e<4-p?7^7cwpxscwpz? z^lhr|`R-hA-2E??;o!c={|@v0|AyuDC)bBH%gaC8b*Ak4;YXgC4<*$5^~0XNWoWa% zCe?Cub%T*vpUt6`+Y^^2!P?n`bL*8S5`pCvq9v&H+_8#R^S zPg7MNovMnm__4w)X-|tk&xBta+ZJZND9+1JV`pj4{Z*9a-+OZ{!`HZ%?j@&gRSMPb z4eqzQE_0{-SXp+r+9wb1@2=&Cq~5Grkooq(6@%mh9~+t9Jd&+U=Jxw|cHy1Ysr_?O z#pUc}_CAi6VzK^xuqkn2^VHq>x$XOF4|+B;oeHjd^MpzK%q6D0qzfIM=RbDLdni7s zD&oykez0^YZq_-}C>~?^eIP$@;?-@RZ|}`}eNxvo5&W*kAiH zUC;b{+|EsM*ZKb(Jo~hHe|`258wUFZ;p9i!U(d64tV;fqCb6-!SG9C%y(HJA=7(z+ znlUF^ShZPB@4s=g^|bib#f(opr3_f#Iz$UU?@&{5de`N<;n5uRo{O{Y%CyMY-?4F; z9v;JUSAjpTs6%=8pR-|R@qcYzXC?^x`Fc(MAh@9!f!yA)k8CPyTK0oG2@if_cebHq# z?gf7A>pm>Mw@CWR4dE@HCN$S=Y|ei3WMbbp?%Dso&69bQ$G3d%rDns8rE_*p^0(gk zNWQA6zhAcIOXl$zS8Y~tOMjnfZ?V(u+^*;6_bWJ`KRNH$WcGr&OE;Gvz5ZB4{PDf_ z?+@f(|NUd@?*GgGv)sK>Z(sj7{r|teA8&64ZMS&>E-#hKoJbQ`BX{%m znp+mr=Q2uKPEx7=*l}w2eimlmuoGEdmnE}vJIkJy45*7fLuc(2|1E#1<$p5^q)9#zx*xk$+T{i&au%(pOlxoT`Fvur}2^nyYw&0)~@~+7FG6?cVA)F z1kT;_mC7Gmv~4^8NWtvm+kUyNdjAr%(xYyDcs%#Wh3b2ve@^wrRYa9nx$pb2)m*Kv zB4xepoX!6pg#G$(G@xcO^XF5}dSP#GtlRm7RaoZETKiIsI-4VbKhmbT|FrV^_(Qtc z|KFP@T5JCFcH7VKPX9gG?%$oQ@r840Y8&}uAI6$K(w}es{$uL#?dRrd+i=bKC-JSW zp8em7sDJwJ{(tg+(EsuKxYhwg-0`pO-&)9KY}PeYOcJ z(!bvSI{)wVum4N`ua1yAf9^kD!+zBR%B$M%_iU5=Wim(1rTL9S(Z|n=6F0myy|u%l zQMP%1y3f;@&z0W`9xHg$xMSWU)5u3RPA;l`9_S#s=H{#={OgvUwd~kh^kS~I|MOzr zph94Y~R1vu3K8kDcDl-GtJxdFb^cWSFZ5#Mn6`Pc(tD3;)v#M4Wnzn* zGFIO95>nL?yFO>mn@48SoLesxzta6V9IPxY)`77 z9RJR!@Di5Ad%E&s&h3)>W!SyHRD0jKhmwhQeZLNF*t9&1Id}QGAIazSs-JuP{kZY? zv>$JTH=pN!;c%A0e18n{!FW6Q-)rCheFWR?hMfOC{Neksp7~5eiTtOn`*+0L`*rsC zPx<;|pMM<}omR4Mh4G!=-s`eH#oV0ya_6oqd)gfR_g$#CnkVe6qn0P>baL|3c!7fO z4E{^IGBa4+WsJ@&EoSmLJ>~WH3(f0xOgbd;+U@Ko@#BrFCl@)tSzLNO^}(B52E9+$ z&LuzmeQw(?<&M|y-|PrqqjzU_-M2Z?s(McySZ1{(szw6b(@JO7MZ!S)eF zo$U7Ad+pSFMa^Hgt8Z*sQa9I{U7|wNoo`O^Q@Q$*4sjWqV)^G`T6Hgb`R|tYeS301 zHzwQre$!-o3CI3l;d+0LY}ZTr9H`MBwURyN(~V%EB8KZWMyA)dJe?yOb8dS{cZ&VI z+l>G0|JRrN5-#NSi?4t7{NF*{-)7)h#JL zzv5wSeZ|h2FXhjFd_6RwoaexWjsJEsoS8l~GWqkQ*9)IsyU@PVU{krwqUtB#6jn`~ zte^hv1Ixh~IoB43w5lFi$tzxmrX(@_4UsV>gETVFIf?rJ)^T;5gJ-mL$P%az{V z;J4S-%+5)D`S-?}c720wmdo^Z9e%s#$g-g4Ci@cE6@MrlUY8#9?OjcGT&3+4aR;Ri zn*(!xM9BA?ikP;kd(H2qbDm~z|Dcz4)MRy%e}8MU2-oBkb4}Z>xp%a$Zklauv1P&9 zwZ+$e3al#ktKZGG-~YPrme(!oL^_QvPep#;`C;?A@152^y%zjnKY#Lm%ykJbk7vUB zek8fOZ&8n{p2f7_laRb!;X`--$5+-hUY&USz{KFnhmU>!#9S}0IeF2hYelhAvAADB zwO>_o{@RS7ypl6><~q+x=PSrqxzu)6)$;gPzQwHEb>F7MoMd;iD0&w2`_v#TKd;9$_ssB6m{~vkkEdTe&*2??k>}BlpD!^lFKe+FFDrbN9>cH{+pU&<+ zvpq-t|8?`}-~TNC-M%WC@%@Izdc6bMU*F506JIfPADfHXBhxR1Yad=_HqqlZTiH|b z+jhN?)%Q2IrzrI6>)sJL?ZEAn=eMb=`^PlL6)~5B1)d(x*zw`Q3I1*37J=*Ul)3Gn zF-tjy>n-EWPTyyDR=)OlG+DcW@nT}oEdqVulx%R--+N&%M z^#%O-Dro*KB(B5AJ5$4F$@5=L203;687D7%`ZA(+rTlm68K1rjR-3fCue)|M@;D#E z;aF4Q6$#HXuAUR$!_c-++dICEYx10p=VR-aC2PFdBYfP;{)uz+hrRywkA$rkuDkr- zlB>@tRo>xx{+xSYE58wSddUfZaslT3{PQIcabN~I?`EmOhgkSBw zf3eYCu;>x$I_52U+UVPs(_2d2hOe?A+e*Ng)yG}CZvuI-NUXAio?=1t_c<28x zIV88>$k9W3H*ymB=X%XuXcn?JJ7Mp&up6%(!|t4P%+bwWAoOaBZu_#3yQ_{XWjHu; z`Shg;UhitR5Em%@7Jq8{BC%x(vC=l#p)XeP?mE;VswcbWd2NJG_Tx))cy*iRRJljp zjIljj+nNyhq*O50@>JX0m_JW++K!z!_@z~~;TPk{F1F`|c1w0`I~;wXXqnj#OX2r6 z3a>Ah6^Sfxikf|B?Sj6jmD<+FcAvX(ByM+6QF+ium&tksYYtnR`{X`f!vhtB$E%I}7PPHtkJpCUoU(n@u`blVDC|QGdsv2BCc|}` z?qroIYd@OMV#qIkU>(f$@2{A=Ewx_;xvjqgzwJKG zEUmxSsQ*9zsy^e7)C0+}Jgt9p8PDgviZ6_~{4}#d=i>43ref9`+9AwZzt@W1FId5A zdq?Z0Re9Xe8Pg3u9_p3Nk=#-q!xpwQL-4@fHFNa4eIG>a`1U))D_;BFPc`Nnw`B{o zUI(t@%Jho$?{D@?%hcP__VJ2i$?Nu$RykdfrMm@>KF$fcCi7spWQXxoxZa2t*E|U z*;V;No6cE3yIRnAJ$C1=a~b=;%4vNI@!ny*ZnK`>hKpNkav#gZTP$_#vkF~z_4_0H z#1)LXZ}uKCO)lv>))?wxvfVbK=AqZ$H!t@a#l4n{Xe!fpj zr@i?ke0kQ*ubwj23SpVE3J>1g8qoBJ+4|%49Z$OE{*KGv%)CG7{-6Ip5)J47J2YKi z{`W+GTb@1ORZsq_n172i_`fR6-MAaH>Z$u*F2m%P?XU8gfBe{A`RB06|2Bp?!3hT^ zB?dYtoSk$tG<@nIn>FjXW*JNBd<;--UpMu~wj)~SWpqqu2t9phGUw51g)-i?TceqM zw$6HNvP5jge4XN_dQD|hyQWF3DLZ=oq~}!eI*BdjqAMFamXtkRGq+~WRPkBAF1|V| zUM%@~b3%fG>-2N`qPlq3O)Xw~CEi_!YxUt%oNTsYS+BYnm#dqasNFtvD5!9k@PVL< zvd%fI%l+=Ki!ZPE^>_NoI-4D@CI4>SeeBtrnn}9sWsCl%8-AU8&w9?@1HWQJeV!N8 zJkOhBpse=s&B1o=##8FD>_wRxyI(2G&p5|DspvuH{?pMjUWVH>J$P_A-LIx)>cw+l+H>vId8!&xoLuH8?|UmI%qdd{xR-k~4$*1DATd|J5s&#K84J3n7rS7N@usByLb zqcHwi)^^`Q#VdCmo7(1d==cgt2n`J-jmst40^oE*UwmT8_BcnleGi|% z_f>stLR-YQp2JfP@T@r-#y^qAPO@%~v35G=c?W48KSmpt$=8hwZ(dgW_Upl8ef}j6 zjup&RNnrh+_%w6(-Y08qR3@cjEtwU`1EHTSncoqqQN z-Y#7ww5UQrV$$r`^rzYE*!53*{~Y>m&EC)Z|7)Fk z_+xE{ubhW>h7}b|o3`orv3YqD-Tz&&*rM_+q`1I>y??_)t>2}8pUj^BV5#i6Z|iq# zTlu{q`{eaI-0>ELQ({+}e)WB?SGY?4UcxMG%jx%hw8i$<z0?U zpOb66;L(9Hn;Az93m;~_y;%Lofjj2rY0JV>{rj(7y7c{l$o;y%J3V4^m8Y2&Zu=oQ z@5Zyg`)1teeE4V2ufw~)ujcy`_x0`n54-n%x>No4+V^kz4DWK!mA&D!`3N5U`Y68R zDR}g&y#B-E>EG+Vy#0Ir`@LUhXP>iN)u%RTOm{zPB{? z!jT)#Bc5|N3)Xi3d-Pm8roOlN-g8HF*H-uSUpTHX%(zDf7c?b=61JS(&)1al$p=;RKsWI zlEsSGTUW*lTvbY!&z11MekmsPe654Xd@Ie=M5R3k4;IAE`1VxJ=bcFDCAW!Nm;9V1 zzkAO!adwHMO~>zB&rVvl|HH$B{{LQdPP=nZH|KR{{~Y!D4G(@P?+Z-caisCx?wYfk zKQ-(=Woo+o-_6@s6yrZ??yKCeQ2EcHMdJ1Ae{9gvo)Pu2aNoB?<>>}*jAZ&BJXMb` zTD0=K_OeGC&)Rc$hR<5&8}sc&v<=g2?)#?83sU1(7qv0p*1gx*JFhd|=669o`@bc> ze)+$y|7Cxx&i=+c|GHnFr^{EqF4k|#{SR6oA^aA!Kti~-F#6lO1I|_-HtO%+`(O0H z=KZ^3>^`6PU&*jJ@883Q^>)=qogdHQlbm83QWfw;VAadzYzhhUn)@1mzH7YHa%R3< z(%i_;88>gJ|9|wN(e(S){xyH6CLCS7>hKc2+B45L6<*jmc^k8jR%VB8(Sp7EC9bTi zEBWUldv!OT(lsridc&uDlrzD%-Q5?F7>QWzOi&`LHFiAlcS$Mc+fc6 z*7DVvpqlx@%09Ob=)|47@p^O4S@C}lnpd|RN)FfKOP>2me|JT@+sD4$f0X~n&hd^f z*xJ4P+Y3egm@8W6p14J4hw*$%Xl`77y~_N~?Y{8Rt9t*od27cg#~H5V?~8i$<>Id2 zJYi96$B(e7o!iB-`;ClMc#W~$u9Vd>Z)#dXGpve|?o9t}6>$Ie*Qft;|0VsO{rms# z`gr&4wdD_%z5qp&_eZI6!QIT|52~yKw*Jg+?|HHxyZh<^Al)udxh!Yp_yOCSq-3(jm>5&jI~cuGB?s zEbn{b_jkcY!P&po7k8CUn$&#$id#+b#`8;mH1?jmu~@V4&7-E2Bf4i}7ZU|6DVLGn7ljZw-&7=IgFVu$D#=2Ma+FOSGy~v_pm*f^x^7_02 z%lXKaucao%Y-Crj?y0U%WY7C?{qNuVRn7WG{0?mYcK^Tq?TzPmGwXv7!He4!zKi>o ztcBGFXZfEOPgmQ0QEq?OxSap*?%Ai=>*JH3F)P=7*zl{I<%&MjtN%+XqptG2N)$XC z*zLaXsCgXuuQwY>OO<_mtLD#$N5 z7i8x5??#&UO{w?0OB0Lkor|3P;}oCW{OEWlk4SIHIci)gKLZM{tpCCiy5@1kuiE5k zlUAu(Obv~AtKAXtC`vl;$ceCP*Lb4Oy}8(5t5>7*=NC_U%}4Qs%J+}ED~>Lk&=*y6 z>9*TBX7=?WSNCnY8o9b~-8{Y>Zke3!Z%@xJew)NF*}u4HZsoO^5q3`)j6akfUEs@a zk^M6AdGNYJH*eQ9Y!qboPkQB(^FiDB_R_t{2RY0w7NngF|CVrLx!Tv+dwS)+E_G1&{=N^1 zr+4$#{5m9h|DOD{#@Y8*#{GZA{J*xo`IEBz|0hq^p0Cf0_*Mv-4^IW}vp)UJt=x7u zbGYn}8`JBbo%*!lb^pI>XP=(^Rn0#CQdQV~h6>;R4F}R~SIlbiP&Sh2K} zb*Zgs!}Xoa;b!|pj+S-VoXRSGHGOfBYWbU_mB-(xr+>d>+nn`lr+VD4tILmlk>trf zR(S6CJw1JK_BGK-n~yu5%f9{P<(%)W^X!uSuiv@$PU)XE&%c@%v5$_}*1TjcOWajA zxBK&w&7Xa4pAvku(0SInB5!lEg1OoY?|huUrAczmk%!{`^FR9g&iTmAT=^;Gc-ZR} zPVpHJl8kF!2CVzkseHrQAUJqShWfI{!q-wRm8||(^z6LmoYntWlAG6CPByn>PTs%m z?05bDYwB6rgeRMXtjpkfGfVs1-uv$koUPZoZEwH#_Jp4(DHn z64zCiHtEYfyuISi;rYLAM&6nlGCgln%&A75`dOic?1_FikG}j+Wp#|{;`_Q2G3z9Y zuM}SsnX6vFD)-}yx#!7~ul5x>85tjMn7y8VemaEAC{fw{ zqf>T=M)2P!!Y4ki{&&#z&7lLAqc+4}mtVEDd8Kz&IFDb&E9HCVSp6dYoLrb3`CD%P zqgl>2a~`g=mB`rAAsb<$z3=YlJ+CBV4zy&>yJ}vP-zV=^T{wMe-DaKR z&a<@mOFt%RuCgq7erFad!{s|4i=PX}9y}Yn^Oiu)BlW!b?_WCn-CU!$Kj#0@zv0Wb z*Yf|^_H*|A-$!pB%e@Ke1g`{lf`j&+i#_x1z+1jO?}g{@-SfNMzUuZzhSSnt`T2fb z$xpdc?QlT3=|eupihBP8K~n->gzz3VN!Hoj7XEIKDV ztlG(Ew0I}q>N>~%?-wU7+{9NH=6~quI{x#wPjB7L_Ehi07QuzpoW1c$&PR8zEH=^e z`7S*_E3+r*s{a1VKQ^h`eP1&3`-|@NlU{eo#{Ajz=rp@XDaYI5tjX&>Y_Yc#kN@4u z>MWaM^Px00X7i%<*sYD6>&lK!^RH`)TK#4&|K5uXD$i5j-?`B_``?F+tMiKbmqzEz z&A;Vm_>uko@1&l+g`UE;JLkVzFL5={?`f0*&vX0e9qSAq$?duzWP9k*?n}z^61(m0 z`TzUlsr-|z&m!!@zPtO5@B6;ny5O_^tNL&M<==nXd*au@{+c(RcIyBA(VcL6zx4*x z@um+G_-ej**Kgj>%kb{||A%LvD(^416|+-$%`f+PP21_~^Y@lL%;!x=Ex2lVxWw(A zoQtHL>6EaOKYuB#XtI4%m7$w&`Q@XNRvhQ0qS59n*71;Z;>u`MIFY zuLN5Y7d=Ra#4OLx%I(*?dt&RX%Q86{-AUiyDHsVv)i_1>KTulF~WHdh^D6-&_n@MK15A-l5e+jL>^Uv2T< zs$8bOdm@#c74!U)_v-^%_qkuSe>);)dzQI=3%B{-*3SA9p9>$XaFt1Q7XG?UG)D5t zeT4=A_=5th_BUKIcR6Gv%5>UHLuLwriO8hsFP^ z&))a4y_NOr+F$?wE&spqZ7rx4dJ3t9zWbHC?q=R@^}+n_Z3d?=o%N-~mFNFYzJ2-j z&uFF}20M(T)-H^2UfX^yWa&*wkpryjm>s`f47io&_I`Jp>YJ8L5|@khHs~l#ejwYM^uDI)-ii|m>>2G`FeDj^Zsu^#Z6i;2aclY^+CDRPVuWGt~O1|E--p+S- z)|uFYFBp9Ue(fxEn(0zfr_!G#TF=fi<6i0g;P(aLTHm>(>)uw$^@qC87RwXeu!KwS z+|=J)*Hw2d&*G}PTJyw*@8@arzZWOl1jWj99_&AR^^4rvf?m-z->gd*c+);Ma?AhI ze!FV-pOYf7UH&mihfGW6etLLZyyo!(E_mx|->?GgWTQEy#F^E$g9Uj+C5bBXf4 zpB-)S;bgn5_f`>oJA>fm>r-VbR~EmNu6QY9@$&F|(fH%q@i_-KEMImjc>meudFMK6 z_q{t%zrx=9|K@wowmv`j9kef?V6|C6`2){#(2;VU`}gVAl|M+7{c)hb?%}6rzwN#l ze2~@nSIIDcUA^3a)qnQ3`j>p%&hkQDAmZ^)sgCR$DKYVf3+~SF&F`J_;MBr{Q-1uq z_~5kW;Wr$UwJ(;8P z%p~vx_vEPxJ2tjPI~|B_c&+^3Nc6_R&2<-2zWwHBdX&7hE#g4j`p|I8BU9aCnSbl` zHG0;RT`hE<_me4x*K}T22wTpN|MB1M9X-40^@>k6Gb1DYJ*VlMo)kZ+ccboh#^lG7 zG7l`P{5s$Ee$8je%f9~3=4VR2H{@YIWL4Z>687gst>ib?VIQo#ij* z>@7diCE7pv)UCqT&wQVSZrQq>&;HN@?s;wOha+AJCLESw&iiO?t@)rU@lM&h8Ryz# zYK|N}s=O?J-`+c_oLa*LN+TYTBHSth^DQ>R%HurHv4|6{%QM}k7c&cCWZg|cgrOFVmi-O!|6X&@_ybvR{Z=^)JL~7Jikrm zMc#QLa$W2Hrk6K%+AU>T?;g8f@c*y>c6OVeGoQEFUGwaI?MIPMnzKY_i0;S{ouSVV zvrXOV^Gs2TtxW8vKfix`oMC}YA2XXi)0@u^PMq&%aBu&4lc91!ud`**4ZoJBsdod&&HhL;cMYJCNcHl&7a12`^LL%Ck<<&1UDyr z^vPLOY!+4xnPxb)25B*D$XCfx!rhz+&rTY z9qD!Fbmv{TeXmT?$>SvBzFD&U&mNWXoY+$~=fp3meZe~!&K17r`q1#N*yO+cZu2LO zCguD8{XYHhVcmb({|c3UXVx58b3kanMRi|fV`QUtVB_=neZTj8u6cC)^ru7Le;a)tsMG`o}fwctYO`X89udthpV9k9ehRI#w{;x*nCD zvZHjGUL2=zgZ#O&mHsoG)Ube(b7xe^T)Q{oIES zO69CJOy8Q4ILDxDr_@)eu!WnXJ~jM&kiTZrN{Mc@y&Qf2{{$R9{X#TtH{ves#w-i1?D)fDE~qMQd8Kvdo1}L?qmR{Sg&&>tu3h!^)eU>o zi`P077vJZS`E&lqub=<+X4k|&URLuo`}5`h(U#AO!X5;LC+uS=SSkPBCoe1^yfHnl zrgG=UBbTiYR>#=>FK0hJ`QyQc-*!JHHoX3r$6)j^GW7XV3-^uJb<@(lcCU0UlxeR0 z@zmuG{~DQtEtdmlsj*Pr-KG5rPQTOm zj-S%G`JsFN7Gv9AT&kr?&a(|gV+=jSwx5}LW6jrD93LC+2T99s{_H=WL1?vWo-lL% zpXsHkm6!Z)aV^_%d5_1GltN*TKi8ABwm#W4HS*V%{a@DKsG74?js5yfiSz5MHyc** z-H;M7&f8wamEQBERWR@I35Q=MBoRn{(C=E&urTeRgjn#Fe(>fHYob@-e_MNR8@;WB|K`bR@=$Zc0D zJojSnF|)RfJH78mv7fX0EgN%$$)6|XSmyf*?>;}fz+(*h-nSRK%N0M@d3fRA!e2RW z4h2YjDfr>kG5-?R;|LS)Np;yf4Rw-&i4}y z=Ie4Euc`AsASf>&p&p^vcInZvP^ay_-|g3^amD?6vQX@j%D104jqfD$E;D;+R+1Qb zamp#iWok2bR?jy(=X}kY{o%T#NvpRoHt)=F;*+jUD*rQAKlz-RPs9g9spvNsrs#+D zE_kex|8eashTAt3(yp4=d`y2nd#a=6(J7+FbCzixoWSnlteDU&=v-2GSWK-VNLV84 zO)RJ3N*kN+bIMn2(@JONcVB7zjOW;|9#z9j;$_j!N4~9%m|CwIW_R^UznlgGqK})KZ)~KG->!<`)2t_)Arv}?Hs>uNr`ozwfx0V;Y|}K-<>AE?bJ-$9W&Us75;q9xF`a$wLsCYXZ{@~~I_{e|H{Qvd0 zo8O7}kk~N)r@R%DUVcpcwDA0WoA=ZeN5-YsW^cbgBld=_y-#;N-T>i1*3m+xQa|7t1=zm-{k6uuC z(q;9X=oQY}96m5VIVLl`nPJZV(43ntDa;Pa3f(5Ra^EaEdsNr+prNkx(*tq)ira35 zEmmaM{K@(7#NP6A3^r~BiYKzXnr-HQ@25SE_Seb( z^XKAUKYKfw4?n(I960)%`Hh=QpYxYvym34aTpKGI72;$bxH4VKmw7)yJhfD!{eran z_378|*A-X(R`?(4%DdNZZ6(9I_RABV%$(u6RpSzO|D{I}J=?ufjORX&lah8Q{MjID zER=HTnOoM}{?Y)k1erD5O0%DM)(UbiKU{O8>8+(gg1+TN7B%OSksFr=>P+nIFy6Nz z>rti@6cUzcvU{ys{d%elbfEDEunvbR_ z&eHrE@X+1(T%=Zhk?FRSr^!DIKcN)Au*bKQ~O}f4)3D^yA5W4VUsC8H#aOu5Rnq zcb%F2+^H^=?frxQ+jlHF8NcYH{g22q_MNuA|5o?!=i2_>W@F96)A6-``j)jvHbypb z2RHs?I?!4vVmu+T@pyb=^N%mpbvzG#|Nr~H>dmrN zH*dY>^m<}*5of@^<(sY2dy!FK@p`!t`m`@jniV8?O1mgmE>!17pJCSCllO19aLzDQIrb?zo>|zyK6{7cqyC!_pTh54 zT6Ci}mXF8k(Zwmo*~J_8iT7J*nLm8=Y=+J9pL+Et=hpw+s(=33t`g(aj${86;;QT0 z)&8q1E;yOK?5F+lk5BLanEUkBR_-6G{#ag1*)>me#~W}9|C_qa=h>n+LLRjKFl^Xd z!I<#4Z0laJ_gnUVJ9enrCT1n|l8-MQ+vX9~r(i>(*^9l03XY zZkzb`sIAsLhb=gSm((s6KCf^(!D7w(H$}Sk2dDlsdX%K|e(6N5yf%enUW+!ozmvN1 zBxkbtVGC2fuTBh~f;L|{(4+W%p~zW*cb_zF=;`0q*l^|7%ruRcE9Z69{BX+^3FmN| z>lfK<+2~yqSSnY{zHr(p^{_Kt=PZxz`+ep_s*>eDx8A1iuMall7AlHw``O|tqnd2* zcJj%73pbrNr{{mz81_zay8P#(ab|CJp2|BCYjM0m*Ez;eJI?cy*_RiG!paJ+N$mSo zx7d7^WZn6$wQrut|2s6rqBt?QQe)q#gA&h`-W|wImMYcgTjD+E@2u>E!|uyc%DX4_9YrI_}3*LmKO{oCtK*X{L<&%b?tPwMsA@#%}9c0r@ohd26vE)~5!w)W}S>veUFi%tcU zy_f1xzVytU>t5G+^+&QyRgMkiJa_ueJ5)W}viR9z&+chYk3IbQBc{Q8y7_&}pNFSq zWh(i|W-xoJSp9m|8Dl;FU}+fNnseV;OE*8b$;;I4{IDr~&c?{&@+Awe-Zg(D;hy)? zkJnp}W#*35_+ECA*3v&0UIpk?KJ2>v-)rWXOfMPF%X5T0b3Sj7&n}!M`e4VOZT~0c zA5q`$CsiSIdg7mbkLs z>>pfnleFg_>)g-rbz8-?#)+|yPrqvIi!cfOmmu-sn5z8N@7Byq*oCflY&Cc`xt8%@ z#r{nP%YRPT`O)yTkA?|Ap+5bMGHoU@o4WbMUH7 z$)zX0`?}aWE7lxOU$fEvA-B)ZN&U~x%beT)+A!He{Bwuyo43rh@tfZ}&zuzLyyDT5 z8KO59bA8aieg5yG>EARS@qE>&`VjrUY=h0;^X%OFyFM(4K}XIt#Q_x-fq z8C?6X&Oa5%Dj}1YvVWWULau7_&wm&eJhrSlaxdnE(fcEuK@mC|jLQrZwDl4X995Ov z)_d9P)anAqsB5Xo(P>G!)eYb7*B(-wb|8C8<%Zk*R_nXZ$JmBdq)116S+lbx_nV?u zTxY}?)?Il#@8mhI6xO&Of9`q*cJ=@B{UdR3@o$^M)z@0? z3ltdN<2}&Nwxjlu!fC7Tl^@QUDfWMyv)=OawVmb{9%tMQQ{@eadh2xJv!&TL!Quz2 zc%wd+CL8`NkpKK`!CQ?t{g#Fws^=EoxK`|`{jaC}hs4bv5p4|XW(41T#I{%NS$GcL z2D2xJ}=oX7^Rv!y@;GI z;XAEIU10Idro-1gMIL@Go!D%ed`7cx$zrjsW@3_yi=~xqViMjR?cevu(*4cOu%C~Y z9Q$C)A{zctLG0MZqj5W1;x>QQu&sEmW&PPe{F+@S_ss7(lH1DPC~i=)+>>v2S|C~1 z{ok=e-N&jHz6!JHJ8bd3Rf}iWmjg;+it@h&5-)#GIP*ANV8UI4{G+XJJO6OoK50E# zE`MqIxqUgt1-|wv)%z@dJYR8Z{)dybzwP5@=KsI{zum%khG+#ps5;uyzQ}iSWMl36 z1MPNySD(K4@!$Se=lgkTK1H{GtYqr{-&t^AZ(YS68TpvYjk6aQY!j-~2%Egas!UnZJEnzJR^8c7pVD<)cnN zCM~$hD(D zk>uZaYVxzh=dK!}=TGp+Ti0hsexLAtSE+2#ny_iS{AD-tuDx)Z@IqyUMaR*P6?qaR zA7)i2EHvRa{wQj;{iUm6w4ZNMu60h>VGD!8-p2noOgvZhq=!pPdvGMDZpV(S{i~Li zN*tTOf7^3HnQeVB_wJ2_ZpWGp`K*pEbiH%v{Dti|mXyU99=`fnz9wP8MP1w5`O{CT z-}sb%=E1>zJHm{f$Q@x<(>ogcvF~}vZ`nOX+k*%vs80dSwCZqwcu^H{xiIncs}1~o#~qSvWsVr7nzpMa9bcU6*&CpE*e=g8blJ&hM)mTX6RHf#_8$&2 z{7y9JnA80DopE+#%iMzIo*fIk_k9W9*|c#!@3P0P0X5%}<(k%cxHHLKu6@ismtFg# zO2$&-=X|l2Eqg5a_gt^Hu>Z;;$4a7qYOp+4R7%28Ek7b*DE;_^C;uB;&z^% z?3kbY_6ZNnmen-Me{~OcE=!O&w?A!(a@v=pPh;K`Pwn2>a9b|A|G_nmnDxtdZjdbc z{d&`uxVxhM=g%8HSbgn@-KYG0iks|Thd;?xj@ir_KUG?zSg|0+d1rpN$GZppi$6bi z{;PNJ$JE{Xci!1k`&=l+D0qhG4AF`kTyyTO{F$gc`v<56emtu2^rvJ7k(Zr66qbqa zd$0UCS$Fmc{>Sc&c}Kb#uJb*b%$fXR!J5hWa`l$!uh-1Ib+N|d7~`Do!$oJCI@8xM zE&e~@RX_iwH`@+dJ#kskyo3MQfm%ie!QVX0Tin+M&o!LR<@#f@c8W*DO)QlnM6x?wlyxUv^oX(R&sou;sW7=hXxdl)xHX^WWd+DR z>gr8D85`Z4^v(E#PeZcpBX60aA2#MvM>lFr zBHDFpQu1P&H$Qn{^WeNx&~XXT&j(E8{xnSVEu3-E=4-{1c)JJLGB0X9<83}gTO4oR z_uT#w_nhR$+lCckZ;oEA^_DMs*}3}1#NSds7c(>XC(M;s;;l&9@$Fi&vhPdP^;>tO zhThrce(lwgJ2}A>%f8I8bN}aizUV3c><5m|7FNtYV|9}E)|rHDd!$da-k1Axenni( zhx@6L>uYN3e*I6aoMzk~+1S4JKn>%AL~XN=o0+CBVLD&??DXk}s=w~;uQ7Nq+5VPn zko@=LMV?RHnJN?=-a7A$o4fPznia*yhjxUqXUZ=4T;RK=wCwzbqoKY@TSYrI8J=U# zIDdsbTmFpeB(_aWPKAR0+f)?W+np_6?0BkF?6^hprRn>}2jXSU*(ZDToGFxYdi2RO zM>JqTlAraph|2akrcTAn*3F#eX?pt;vpe5X5%Y)5N^iTAb&kqc{XginbIM-zt6R<1 z%x(U9CidF(D_1X;&#@4VzWz?4UB2nuf%Z!u4Q^MRV9OGpT#_KoU$)NVW8aK}NA5At zskP{9)D_qhabnfAZRP)-P0UR=(mB7czf}I%t@V$MRy|ktd9JIJBqSI6x%G_UWA}~8 z&O95Z`<`91)&01_JBd&H%brYqUKkX1`{B`}ae? zQ@O}{rX7_f%6zx7_gl%dHy0|%v%j@6ng1&0g-rzee7*~Zjz+~4`g^>)op`&wNPb7@ z7jd)Mx;xd6>^KtrF=fsNwT*9t)y^MYSSFytXJKjWf8?mblL^QDHhOl=zUB~qGQ~aS zqq?nv;`5XfJo!|mS(&)mK(&8)4-N^<`lQ!TUG9)Hc5Ic2GR=(KZ} z!y6wa#}41SaV*)*U|-TgsjO|jC(f+gaWGoEX3f&$ z+uFkJP5=9WeV)ad{=K@}W4h#XejZ$w{MP>~qd0Tkn=Qt2@kg1DRqZ&&dThsx$-E^q zo=uTSNGgc`sU>OEmURB;xwu)oMEMI%^!be*eA>W(AaK;?6Hf#!}f3-=w~VTcXTh` zlQ|o2emT4^=`@4xvR9P?{LO#-+p;!n&En;pV_L$%&;C-$aJSe=jlitMY)*c@O{r`B zEqA0(c~LyI_?@ty@Pw@M*1G}}c##>F+crPb@o;}*(JOoF*=f^FeuWw@CYZ>` z@Vtv^&y(KFa69_w_07_D&gu8gspi~tG*2qBF%)LsF;V*C!t`fawwue2oNf!WInnOZ zwd-d2ci~kjt#)Ar2O?vhi8%i>SDi6c{x$9{r%kl~*Y%ub-5=h+V&Qh}`#xyT+f-;HyZg}5wu+qx zB=#SDb9A2jzq3DNx2%8Xx`kin$=#pd{(7KReg! zw7XxI6WA-i;r2&HU-`fHEc)hWUihiZczv_xistpVnE9^D%zMr7R*%Inyf2Dja>Fx4 zX`cN82`ZcU?`Op{UvJNqbZ%WQo0@lcrZng6=85|c7$+-zo>4w=$3~AsKFpCyQ{oOg zKWW}?!Tg}M{buB^P_;)Q%Um5@Iz4;;gzeEvusXQ%jVS*DiOT6(8A^3}C%q@%J|L<2 zRqI}0^qa15nXT`BE%KhUD>|Ql?%@L;V`lYEO7#EB({ppHi2u`MQ=_B}c{irs`N(V8 zw(xV^((SxSBIpWjvLrc^FQkU|G&eZaewui_V)JE z)Bpc{zsI^j2{ewWmkVkGP5*ZB+>tcadwZFVo9FAx*S!4vp6`zFk@kNN6Dpt0_p@JP z_b#OG5i?Vz`GJdy@7X#eYAlXzannYkww7I?Ggl zJ{C1eK*P*TdBKE?+*sDo$sWI~9w>Bn+jnh$^m6j!{nO)abRCqiZI8V$SMQ;8=jFrq z!jJ9l)c&$W!RO3l$-~$Gq~^}4Kl5TojP?h^2aHQAR3q;nx%}mgY1eX_B^vKO@Yb$9 zYro;CabxB5*oTP%+|}2wKGW%zh<7yQp$gy}>ibZHG)`-5zP& zKk~L>!uol_4v#@S*8N$}KW+Bj~OGkYbm*ksOJ~+Rl;nuX9zrW;nE_LdYV3)9Wc;xji zB20Oq&kDxonQ19KF_V}7k3nCzU^Jj&$Eu@+rA@js&4w8RsUl#Z`MO)<)hLv$AZh#PxaIsDecn# z7;)}R=~2UF6K4BMJbu95vd_S#{A9<~9ZHAh>lZvL`g4QVeo?RNjvX5=&*GF7|MS%O zSC`}KLq|=LQqFF$&9=zN)_WrNNl^OF+z!h}QiT%CdTrVJpB|b1C->8Iosv(?jy%85 z-F?Tm?YW)F6Mwsc;|A)lb@%;tJb&YtwB?ej@_f4uH*dXdsNV3)^U7=c*md)NmtTEf z_m+RpoX?9YGV^V^yR?GJxWmrrm0asAb4Grq@fzMb4}5z+VPxcT{P8Tq)& zyBU9MZM-S)Byrcdhwq}5_Slp?Z@k%FGOOcq-{ZBN3L6_voa39|7`VLVt8h+o;y;Vo z>cuDS@GRIId1_T+c2M#L@q-3=cC!9IkKE~X@8J06lJjJxy^`5Wz545?|Aks48uL8K z@t#u3EA_V9&#=WI`cePI$4!qzBu#IzD){_T?pL{gt192_x7vbr!jX55gm1PyE>pBe zK-QwN#Kt#|ujWL2!jsh-BwuROMuh#lEwkk0Y^|ocX32d{>u#(ni=I<^!dxQ$sQPV& znf@Pd)hFzkUi)y%v3)hW^k!{z?b}yluz0cB8J@GL`EzPm+3J1^@XgzGV1Y4zOtOUd zjNChq4#&s-tGab8#k!Df{`X_Ir@wiyD)G()0dL;lFU$^2-Mizoc(#tJvE%K>%RifV z=yf0eIOoFq?gwj6Oy>VvogB|p_rZey(fdX5zt7&UENJeW16ort1=PijId$A>@+{Gc z4_q<(|33M2M&0iB1GegxJs)pv`ry~_S;pkQdH79x!Xs~`*(+s!bOg`Ul%m1u+7b_@Ui_`;T*AtOS*lS z7Szs&5{A zM}8=o^Ylf>%GZ0$#H-_{CB{$Wjs6{|q?{LSTKj-|Z;c60QLKpLgvk<`kDmQ7$=|Ud z>?Y5*JD>ddBgKE_%u(Jpp>(cA=ky<1ZdaG&w7TZ`%s)8iS@R61zTR3xW4E~vS9Q8tCEYxd);)F4Z{9N3q;^7qgFONIWQzPI~VBh$Y(;`?^k ze122R-!gXrsPA+-8Z>+Hw04{E=Ez3&bqw#BcC2~)vfeJ@hfl+2nJr!BeErj3vmf7m zc%##)9iI-&?bW;y`#PNcZCS+mZN{58gV$_*Ua+oPCsWc({Z|f?icO*Sf3fF!Zxah* zDi`SInlhQ5yuw~5kSBJBXU6OeM`RR|{LG46&&6JwbvfTdXiM~i5{^VRhdD3BrDhbh z=>2Q9{^_0Uci8Onx$U3%9X_x8_Y#awHz{rKX+Qnt$v!GQ`Y^=)9LoyV^6IZw#Z-W z+WRSe|1ul)!?3de@*h924&1#%@AZz~Uw?l4fBIKWdfa9ve(7*a+O0R z`%~KfhUOh_me0-o*lOYWvhsRVD6@3b--RnD%vRczcqUKeL8K}7QJF`dE8>jKKthAM;GX(Uq2Dg zKZD2q?z&Ht_uV`#zH6Dd_yMVA{cp_juP54Xo6Xq%#c;RSBh59sEyXr-7pGjC&AeN# zKfCds#A&O!r6>F@m%nL^`>&z(A^!2&|I-TK_i~O9}aNFJdF*DQ8JFOOZ*Awqe zd@EJp-SA}Mz4m`LDel4-_`iQpi9MBi=%DPpE#`~M@|bkiEVf@8_iJ$?>r7q7eGiIs zZY{|&*dS5m$$da%+qvGGcd~8VcDwD2S#CJXt6=tjD?P@i{&pRfbK9E&f4(^p98-RL z^3O^2d`kuTE>!FKUcG2>yV~9$_4vJS(elUm*3Y%RHgmi2ikUMG1=Ov!`{m@IcIfV! z^e`i}*RwOe|2Y%tbv$SG!~Pv+61lTuKF*nB|4I0((>3N-uP+$xvVX23{pH8XpDg|_ zB)*+Im#ukYGlRQ~sq&%aA95so&A$mfzusQt=BU%xt-DF$n|A$+sN!v2_oN z<0I_8{Bf7xKKrD-wt@Brc2Lp%=AiQ|#dBP9Tv_9GEZNU{V5@Z>^S$o--#*^H^m%sv zk>^Zzy0m8}KI%BU{NLrkxNP_3%>Of(ytu?7mxhGhQaZQo0*~kX-A-&A!SU8lc3nK; zek@|2L4(pP{-63*x7R1B3b^?kSv=#AUGi@5r`;>u&3CX_X7;@OMVs|| zvN@z*Ppt1={9HT!Xl``jH2Jj;H%{7_y%#M~jWa-`;o~Y1aB(`G4@A=9fQj$1lD)KUL)UgZ)SL|M~xA zi`H9zsrOQ|M|U&i9o@~i z?&z6|m)Y}e8@Y;Q)+DcgoOx#gCpTMS%opK9r}VxuKc2=E(32*%EU&fOqh&$Rx@J40 z)zSz1H^Eds$%Q;DCh<|B5>xA+$!uB>m=)Eqq?1dnrSK!W z|LqwqJkm8c`qN8zjw@UKF||6E{p0wp1G63)iZgCDGi%PT%IUaUxN)5$%PG~g;scBC zMAf{=NxgewzQx&gev6H%XYc%(>HOo+BKeKU(J?;9PM)(#G_T>l@+tpc)tN5F`WLo0 zj?cWx6U}L;LXL# zX?yOUTE6GXuaKNV3-b@(EMI83{SMYy_=V?O_OprgKV@yU3e-Qo^Xhxr|NpMb>KChT zH&@(0-QoSeYw!1pZ_YFZwXiw0K`rbS{?cV8+8^Sy3SPf_K5cni#ml`j-c3ErecwJ} z-k4#JKkFNqwej zavxMzr&q2}Q}~~Hlb5lsem#+CJ_5a{J!3zymlcpr)axU{QU3KtA zuCbnBV!(kCacRlr6C_GRKX;r=nZdq4`_0akr|a5SNwZ8^z4i2(UnaNTaa%uk^m)W^ zdRNI2-JM^b91ETEjCrxl+T-&Ct4|hBFWaUseY1^6@7l9FzdUxONbd`>NODDT=#KK)s~`r>WN^Ps7tkD$K$x*y^PeCop% zgg1BT(mGg<$sXS2PwNvYNT(fp{4-;R& zySpMcgkG+*y(OsRos;E0+2D?vyjY&tnUgwurid_@?*q5?@)9Y&Soa+iYnSzoxe|=WzI8&#;K2IkDaMzjY~o`Skiv zvqN21%#Bm`KOW5H`z^isui^&((>FHvMefTsTc2LIaP^H3sx^gW_b)Y0zIbxB#i@5o z%v66&egEj78*`pa6dxqsyM)7QpQ|~*x%M{nC9VvR`QGeJaafwLWEH%ZpwJW;jZ?%}~@?m** z&&E4b%RlukUAKeP-Dr8#+Sh7!Cf~DaE@hRM>qXucTabL)@px82zQH3d$(_7D{>#>G z|2cckoh8{3g{JY%((w;+?>uYFw9#5pnH3g!*zbp~*uJ*#yUG05vUjVn{g(O3?zdCo zN_x#q>uaYJ)1KZhJoaGn*G$7Qo7ay5dvbIS@B8Q}XZ29(`zgIA>II6yI!l??&-wV6 zQNHSVSkZNl%7#uu1JQEFVlAFB+xp`fYbuX!J-I*mfabMX7jJHRyyxC)%dV0SXJ7oO z-*-LIrr^W+Ki~d*t>=$f!#PKE2NS4Di)n1P37#Wbae?d3zX#l>h0oXiaeP?a`}jIj z-IM&{$ZFY5tE^_+ofbOhWAO!rf*IeprS<5n7ISB>e==jq zeg<|Ai@e+GoNnaUWJtB`yM3ZZ;*G`Z9d}eUwe;R|sH|-MHZG}5; z-n@DL!fn3z-u=Hf@$S^L%XwwgpB(t_g%;n>^|ODvuQ_U%^WZ&OZTy@UJ2!0f|H)Om zJ#&e%C6xXX?WPBLZ;b30}8nABkHf`L@6uy`#{ONIqidpCXzHff|vR?A|bMCJVKTR9U zAGtPsRAy|Eynb8efp&_!-qj3=RfQS84ShRv{TEe!dHT*xS@opsYo+ydiXRl6W9n|K z-Y5`$^IgYP51y4f%vuZ%E$1*uanad%!mV`%^AumseK(f{E(@L$;nh%TYUp>MCReV| zdgq0?8EKasfBM-!}T`4E97v_Iq^8^>HSYlOx8bB4|7=ewh6PZy0T=J^^F6baW-pq zhJ9>U>!|bX`Aw_0Z}+!M667vBv+u&@b&2)IPW!*Osl9K;+R2_DHU2*`ln$&CT+Aq5qEV-n6lQ*3;bueTRS8eXp2ct+w8}EdN@)_}0Ji;SXy6e|6aR z>-PV`e7Uobav%pZMr1MD%3psoQ+06TbN!#|neW(j^#8wlu;_8qKHe`M9vIk(KbTgh zqOiLsL+SCA^E+eH%hzVQWvFQxm$NEPIKlq^#$v6NVK<$2T)gozCEf1wEcI_6zPKsK zE@d*VSsn{kcHq!?bqE zxVH4%8t&TTQ9b(w({4&_pERA(=w-hi`9CwUIW@mFfd6H4f1^$KEf2My5w3MY zZ$2dFuUo}zaJ;4eptftK)44A1UmIc{dgYxtAgF&`bKi#dKefXjC&|e)MnCP(Kh9~M zmA5s`((Oc;O~QvpnRyIFYYh%dteC<6eQ!bY@(;}I_PkbwEqGg8KA6966c+zga@lNG8<^HaBhd#^fJ^GwcMyEahbw#b>#g%82b#rebDxgo-XGTvzXC%Sxr-z%mMFXMLh?M zMYy}m4@%tr+;>3Wy+y?IHLv%Sv3<&vsoNpcc#4-C{@_!OT9)A5Dnq(hTc|l`-^~2=T-ur)i zYCfc%2dcfgLK5~d6u8QL@hJ{_kf;S3Ym;Z&_x(n+y?un;zq{RYkD31UII#BVt%no+ zK3R3RpO|yI)L}id4KKg*_iGX<-U+gw*FE%_JR$Y?hRyDc67#HnRP$!+nUZjM!qK3^ zpAWRS6?$>UCyAD^2{au_+0b=7Q*>=g)9qtFC;K;)*|i;wh$h!}`7Ky{_|z#omx4?4JIr^TYi+G;lkt1*U*mcx=gbW2@`p0} zKeKhOeZ!sXx2>-4Sg2T+${ zPVKDjjy>D>?Y&Q*-K_ul;QS-n4F7HBo!Qs&ci*Eq`Ty(VpUytEnQ8hq&;mH`-x=vi zo0;D4Wzvt?D9^kg==`4#2|pj$&$B4n|6R$B?et-bBlr8xv+k%YvE~0~a`oF{*CX-m zb*VwTjeMUEef_g3lX(%d-LHu5V{;yPbp`GAzGl`aRbFt3Kxx*NRTk4Ack z1Th|1?vpX&L*U0uJ*_t`YzI{GF0=`2$|!G^mJpeCyGr|YL~_}>OV=L+=yYy6^KZ-j zA7=OWru|M7>rej^FhL?Eb553;qUpWF9#__|^AmU%SDZQ6qyM&3yyx?(n-Mur<@rhq zHT@rNWNqGaEg`J>#l>9rn+eNLZ@eL1+@m{v$&cgv4496cjd^o<)=|BE2UGXH)fPLN zCu^7WWzO$gzYGtHm)-3zRnBAKWuBibE7!@sZU>uqb$=HxPZhtwCh4XvhV9SJ1@F-N zKC4pyc)9ZP{H;-MQ-%2DzuaAa-v597k5#_ik&UP0!9!#9$3PWV`?>?c@;|Q>{hrBh zzvb>e>6$;gFDCqd^C-RW?}v!_ya&=*F7RZ$h>^?Y`_J)c!Mn%rEH+8+Hf9h@K0a}q zD%VE-KY0o^?QS}oSi()Vd^)POH-C4Tn(7bLIa`+BOrJXKlO%&dr}s{UG8L(srF;oS zSN6$R*FB4U;~d#N=Uw~Juyx^LTLTlU`$}0f@Bb>ZnO3dF|2xr)Z(3twu*K<3I_r{O zu|5uCUbAzfIeXUcN%yuY=@%GUtDilQ5@Kqae^#4E#!w`EZEBR!=SPQ~H(qi3x%r(P zZ%^fHdmG#Kb60C;_}}Qwf3CAG_a86!xyrTkE}QLZbKY9Try0;IJ3pyx--Cl!B)+bw zyWD8RAn8_CGrPynp?<_J2PEYd-9JK0W@&9q~PL z!6WHw4y-s(!L7cAIbbR`My=my1kVjFWYwA=l^hI>+REp zdn62`1P?8{neeWct9psmnZ;+1wQl2+2sd-uc}+1smg9GhR;G=_ytDe5caBV*#~f;R z%vxsa!fZw~kdmfK_sGMHf%?-WLu+QQ1=FPlc?X-CM#LU?w}cuDfP~ zK%8l{>gL{UFOm$`__W!-_^}}_4&U_D= zqo@UUSo15F6@@)`751R@eEiz;j2|Z3)zwDY3GSCnkY@|LXSZwPoi2XPC&!shUK#is zPn?vqqG)=MiVV-yI>xTfzF#}9;+Dm}Z@{IPVFe9i^o{$#1GX8eYXDJ?$PpB(iAZP%X)G|GG=W_0}d zuTAFi`$WZ!eq>F%BlEO7uUnKneaDRQ@192`mD zJ3RK+e#mTh@}m8Y#s-GDQjA|kk46-IC`n8Ud#|WCCrV|3Ym#MFA+vMDF-dQ;(2k`{ z!ZMPx%^tJ$e=_)}S$*Ki1?d^{isPjh&oRu|lOy?{y|>A0XUWp-E;i3H%AQI5dZZ}N zwt3x#!rIMlZXC;g$b7!QK0R>T-Dit`nA;s(_bBt@H1nUuF+U4VIUm*Be}lI<;z-=$ zlV=sPzs!zUGClKYe$UQHg?FAZ_8poj&+vL;{o9GF^WI25?_hU0aWS+_d(NM!b92wA z2j`tTDbp%nCC2lmQPyy?-kLugLaQGgK4Q4>-Qn|7D+^5aC+JQ;-)7wZ^%+C@8pGcv z@_$^fyvvU{S=;sJ@9|%a>GNXkKDWl#{&$(22&&qH!a!BKru=K4tgr`xAquk|oR8fr z&s^}-?$dVm)06G@T`#=7qT5*h=yS$9JIk_n3fc>8=$>_Wy}r5KZnnn5d#_gd=$)Q% zCuX;%K-uJVw`~o+y|nBsx86NT#q5!*2ZNzEe}9S)tJ;-~*L(7>J$Re%-XCUlJ6nFo zy)ztF@)FjD+Y~%llO!RtQ6PVE_>-i=$0|Jqw(IuSn9f_MU;nJwtIv01!^+0FWxCS} zrWqd3seW*1ku%@EoF~>_K1uF67-@IFGo5e4-T9SV{d@mRn|tI(uGuqVKf?w3cf9x= zUxZ0?R{TqBvTNJ1;kw$+34*IjFNwKX9=Y{Vs(GhnyF<6jc{+_iaC0p?ZTyayN`35Tje=# zOm{3U)UH!$xGyrnUZ~+nxXiQ1{bhHq%si62{92XFFXwf~mY6at-k7RnJ^OB)wv>s; zF-{duTaHT?0}d=brjq`0>k>t+s|n|C7^k(| z|GsfpGO>5#t?j!`Z2x|=S^x1;_j{9D>}j=UZrtFHsfd61u>8%9#LKnQdJR;?-8MwE z+Riz4WBwn7^e++G%lanU?)dbusv>{BgUJC={_5u`=lZ6fv6C+QkTG#5ceLS@h|3+z zTi(3Qe&BS>p)byC>-0r;4@g`;@_TE~`UzfMD?)l2ST|Wcas{b6@wCBs)!>#=6ca|5{ROD7F)u}xA@j>Iz zxd&ILyI4=!oyFmi^(1Uome_>(8EmT<8BeNC+m*^aUFL!jmtDc8#`|2&QNc=qcZ_&G z@624OW^=e-;uF8wYQbZ9Ni+OqryZ~1Z?V|0H~z3n?VTsb=Wry?F!!Fa)%^M@w#Kzn z+U_3koK*G0%1`EL@GhOGq86Doj~2;!%$AEjX)|4J|F_1s4_cMK6(~%WnJx8xR{etw zfjl3MEd6n+ZjJ2XzllN9-(>4+K#jBT3pX$fmEq`x2ZIR!l$VTt5#-B_FRI3*l&y8$!k8EtN zi2nI`{@<S@X0wUnx~GP*k{w6 zWHFCDw{1$YMe(|mlfM5hlvo~hEv+Es!>aC2T(wK5e%yJ@RC=+M>CBAo)$5-YA6EQ# zpo_WS1^c<5Q|o>-O3$+sSJMn)kCS*N@#B$GAOE!v8vl=(8>~2J@P=E)tmBoFZChou z%?+vbJGz;r?X`D2S$$M-_buzRo68^SsU8&|x}V%TvvLINbhN9#+*s=7 zRoJ|lKk=0Qr>hz}|MJZH#M=JZIPHdc^wHbjn1i`L9{I&`T&&{a_jvlQ0mz}BUJQ2Gax%aB5w!8l@yFF*;^QWep|B6-~OXe${t?}(!;vUNx#!k)GHy0*^ z?bx7f{#v&G`-Tl^3F$jigQmp1J~VGt{=TM+g0B@h$Jaa$HoYKaZ;@$tKykC|E5liD zSN>v%(wp~4_S^4mKOC>TsgHU1{qOb9kFNheYp89YZJ=GS9~5ry!smXz_|)@pO^yHh z%}nZAAKonAw?XE9)uWa_t2pESyl#H_EBp6M)BUm!n2(o>)kQO;|H)C(5}(x~u!^(S zbn|Q`b6FFQOp{*+LKRni<(52i^UU}Blu3sU9-YuXOLc*y*4Kum{yS1zX&V-`u4oSiw+)^x-B@L z<)!zkTgE5LMX&tXmYlygd5Udaa>4XF`(|7W{TIa@(eunX%#NMih~MJXrhO7U5l{7F zpUbWI$}~qmdyf1+w|^Vi+Vfr*DlGfp9QWuTTcxyZ`s09R`O?I*4eaOMI)oK{`C+i` z_vSO3AAjdr`?2r(jj7)ASZ~boKiVQ^Iivce>n8r~_1!W1B+u^e-pp@k-zJfNzcT6P zq3Qd7Nz71x3TjEFt^y6oJ+eKk{qOqqqty!Gwu_p7SbpNKf94&&m*@1u*{46P+;0=Z zci--5!G86tJB97}4&-y42vcub_e`Ph+^Gh)1?D_)((bPpihOwvmr#+aY1^p9flVB9Cm4o5^PJ zJn-A2p0B6UKQ1iR?Mh6Sw~4iX!>mzX@Iz~6jp@0AcmG&kTe$i0ffM)Y9%k;E7=F^d zwkJ`~UPyHQpNMlY)88B{DCN7}Z?WdMn%nWt?QbIbpT`;bZp*g$`}%Ug=`;6t9?W>Z zBP+irm%Y!G=XLS+^zwff=Rd3Wk6%zbfBViGc!UaWXf{H*WPzO{3>Pg}8kn#u9z^6_`^p=bGT z{i)rXm2!zOi}&+Q`=8S1-<0$Q*XGC=S3RD}{^xI2M9l%?9lO>&k}4Dl6G+lp^QThw z)72YqJ|33%xKZE!uF&3}Q{%f?=WIySp0}32a>eY*4ePzX^)c(MzgR6>^mF~*zXxCK zuvsjf{ozB!l)b5s9IqR0bbF_L^=RW>%Ve|76=&|I*?U{GbV^EJ<(&V6Ymc5o?G(SQ z?6=!?3Y7NlJQ82A-uCpe9|y9h=)czaekXV0*IJ=B_y0cdzaPi5nNP#*Jjyq_}9DOrw7FhBz$jW_DfW5dV0&~=K=ql8|CjXiS9d@c<OW^9!Gwn%ecugx|oX789AhkKXp5Sm(jRK)vw>&zJv z&Fb5mv!%0}UcWwJrzKx_!!G4q{7&|=C;9cQu}|Y;mL*Qx$9?P8&J*D$j_)(5eZTd& z+vfVy?_F#E7lnTOd-|cEYt7$dXP-{r|4F*PBli+06l0!&ib1`5m*>t;DE>I_HGu#Jmo9PKz#amMc6i#fuJ_%#ufVQda-GIj+uN$g9SEGNsn^V-rW-fy;;Yy1>Q+r1_E)Zf1DsZwr#R6A=_l-|eI zOC5W&{2_-}t*hFhGm6b&cTe4naFo_HD3a9so%FWNUDC;~h{a(z8joYt2F!H}K9gP_m8iaeXo^~kLkWIR8Vc%C(m0Typ*qX zS=*CG$L8?z-Jdt1-R1tuDr0BiDM=r4SXQce8s!u-GK3_}U|ihu|I932!QG!U_e3PK z+DJ#|J}Qd}zMT4K^|MoEXO0=nzHIckA8!A5Pq#k9 zq$}LA)#0Dl(h8aSpCviU@~@J2A28U#zn4YTJ^o*drj5YS)kl9l+oAK;)cCko8nd?E zwrz=Tf9!Q@?|+y6K-=!yt_}M48XGm*k~H+b#pYK(skVP)962rji1`Ea&okfE+Vv|m z?*9DtfB%B6$w^@kvO*O6YKmhvaGlwIR__M$%!etg73>Ete(Zg^*8G{eZs0`e^>RA; z|9>9*^lEMSwCj)LV?R};SAIRVY0tj&{~U+=Sw1KoSe?Ij-=9BguZ!Jld&1Vb>>#gw z@wUewbG>*yUamU3$lr0zGG+0fM}@QZZA_JZlV51MT&d@ho?*`9+i%xBSBbe_J^8X} zm+bwk0&_dG4_v$HCy{^Q_|vErmfY(^{ZC(x(dO&g^l;*V?Xefj_T5uWJ8rD8XGP_U zx#cG&rmAoT#fkpTP~*N_{4U#k`Jb*JkD3|Ma+q(P&Tq&g*CE*u!5;4Zq43 z{BfGC#q&g&JBQ03{4Kqk`YbUl?o8_Pnuq!`#Y(03t>Rmdv;UOllkZ0k_14C)Z+!Rj z_Vp9pGY%YccqZ|-_<@$(ZR_y&-;18z4YTj``6>Q?#{HkyXVlC5_Wq!^#{H{J-}?Iz z`+l6tx7FHp61))MBzRn>g8xSS{P;a5(pZyBTVd0eZid}u?P)GtUyXjTxj73jDqoezIJ@@byW6vK+cp{`hT6?+mYlBMHhb>n zwC0T^hg*2ov^~GG_H*$a-o-Y*>VI8Ex|!?X!P(&ukO608(BU5|q1=&rk*q*8yp>HfWf{J(2H*Xd85x4t{n_aknV+W&OJ|0e4)KJYKPIGra} zI`ppE%+v8Ze@mZUd;9bCujkgcfTtl>gW5a?UzJQ-@vr<>CEgmKUy#HP0E|OH}CIbNx_tCO^JG{?wz-40Z*7 zj(pl|UcYb8w{x#Ard~MmWa8|6zr0hCzZKrqrJGE85LK+U#=*pT;S{^Kn@ycIWk*$| zUzV0O_nwxU9${1P_}Foa*EX~J_Zl8K^?`@q>hu{+$GdI!EZg@yzP5GMGjoHki!wJh zta}#p=)>zB8gndn`&2yAwl2;pv+-RY;glEO7yIwc?w?M1Jic3!R!GY8JWrpSJau1< zyt0sAd|Lp&@Y{GAef@uBv zK79e@|54KFkHwn5{V4h)G{tt?*6%6v-|U^H&z$w{qr~pUIq#=!PuQ`uzy55!`aje8 z^B*t2Ie-75_kV3??T-Kb_q)5nTF?f`bs?ZCUHpAv{{MITD+JGVtjl4Y_xsKC>4&qA zpT3=+_s{d$sjw#xg`XEQ=N(u4@y-A4x&srl<76JlGOxeaC(iPS`B&xkhHvKQzujiK z_~Bv0@%;G9+>Cj@jH9<~d26ZsdT-AXu`6?Q^PerKUix^=l`9>FYaZL1n>TCAuKu{{ z>8eVjdsZJ^i=VRPyolbcrt>>OBE34Trkbak#U$5uzrfA;SGK0VHQaH?@4rxgZO_E| zH3vDu4NtPqIJ8OLMp2#b+oOz{LmS-pKa|>-+?V^(|H=KDgZFD^FiR&*IyYm6>4x^* zX@9eKhn`V<@4Q1z;@_D@KD}kT1TtG>W|sDK+dBW~Qf5t(|M-+I;p+C9Ce!B|xbHsb zH#MuXd*mo~xU%})j&nz&?3#`rw#&_~dp&dan)Me`S=O8@Jo38jw7;_Ye{DNo`~REH z?|-p>>p4@E;yvfNWMX%3t9g07{&(#rpUKRToRfM-;vb5yF*2Uh z9nhG4Jzwv9-KXi(>i=AuAJ zn8o==c{8TY4_v}<#31|mDRnmeR{6?d@EODS5T{9Gt=_$gnbMJRk7!SU+w>Y z-ni{$b=ZS>q7@H6mp|ub_`c@cn)~$ry{gim zKWBeroN#f?g;~aK{zsOXnyoAdKFYJ;Yevo$)5u1JFGqLBZY~$Tem!s21j8Hra~}EJ z6jQ%0E4}S--s#}qGm0kg>^xTR*>qKY?AfVz`NGS(+J9ulr*z%V{>_p6v-r#psg1Yp z?Rnv%FdzDHJJy(za!QZ-n8C&?#hB-f;Y)-q$pZ@fq z#FHCUIh7N{m)9yy;$L|xe(gouZy%dK{H%YL-ZJOQ)gsH4b0!@RXbj)?kG;RKH0T^? zP1iH7n1=Zld(S+r_1}4RzwQ0to69anhCVnvfA?+~xwF^#&ab-n{@L@R`|Tpu%|EE0 zwxhPBxboGHzFG1~i~JwXp1LnFwE2>>3NP(@QsQWLzCo zVy~-r^Z31u8?LXIam=nIYBs<8Q}&%TSLW5U$=YnwoKvDWo%dYU+FeJ)_UixC;9Kx! zt@Mva*X3$s`WSP*EAxFe(fc68?{Mp+c#UDn`mK*L>fR_%^fY?3!S}qSV&(lO`By(& z|IDviv}3n^j*7FK`1#-+6RgdiYs5Z0)B3ke;`_JTeEj@pq-7qvg zvhnbb+8gu#Tzgnl!S?+BMjgUr4mxpdpc(-19ud?pbwB zzvYj^KLQsRR~-Ckm|m3mBL3f_@M+8E)jhlP#8?;9*J>4L{|kM$_`db*{O9c#4}9=rxF>iZz<9|rmDwt{G)^w6J{hOF zZ|TR_-n_P*`wpijJ5Qcmzv?Q_fvOH|m#h-6)R`0adJ3(ZwM5T6Y}>-=de21j8zyLn zSR}=I-iR@l_$??Ow1dku%|c@C>J+`>AEuw!^LYEQ%s;nhOyT|^)_g{~|8J*$!6wCN zX^(eI)+*DjcMYj&UR|Rl_nGCG%{+IVr~P{^pD2H0mj0$Z|IwuU&&e{c1Gh(=-j{sf z=!E$bjQ^Yw$$uN^efyEvwCEEyPx@~NOut}vl%2osfoA=IOU7=m*Gv9rTld3e?I(V{ z-;?)M^t{}c^Z&*(``&Pde=j#~^ndgnlt0bx|NAd@%FH*7bzKeXxo1Zzo}T^_z`Eo5 z%m)iS)GERr6oLjRYE{o|{(SrR<24@-7uo;rKmF;T{JN;t_N5gXf7ULR{#DxO-u`hl z_ae=Ox>42VW4}3l%0%Hy>#T@!Cky>ouB;(|MfsjO1{_G?qSySY?FeXKm2;G^GA$;@q8Y@=rWn_DM&+ zzxG$0&E)+hD*bnFmwaFleenK4{O`5*YZ;!jfI8A=xYlSJ_vVso;}S^G})rq zce;12^sJmc8J{+Nyk+Kkh$=`_HN2-r+g7!WXB>YABhje-gF( zmQZp~^4q1u!PAYW^XXW`_nxmjpxW1U@n4(G7oKmQ68CRCF28G`dtUvKblFc5&*Wc! zIvpo-Or-to?zw-a&V8a=Utw};dy?aYk2#N}?p#X_j=9z>$t-Jgl=qs-EAXwc{>My{jcAV%l=N{HOUjV*~sA;@jd@4-d&(#a}D$)9v3OFz@J( z!Y3b&-_AI9=;N2d{aiKAxZ`8yResy~+i_VQWW4$cDC7C*p5DoI{(*YsdDHzb|4a&b z@bmosB2S~8HkltR8T4y^Cp^5)Iq&L%$jVQ*a{n%4sIzJ4Qu%E$qFYMAskD(kp4&p6{Id}b<_`_uF`9C<;7!+*Qn|E&iqlWzk39b2MT4FJVI~JSY+ng09 zv3{vBf8o)qxeuj3vgQ>W430=-(Oz4t;2im9P4&MUi#N+@IRERGkKLGcOwRWDHNTh+ z@B42XjC0oix#=BMWBdQp;tKBMx|?A&U97t$o*C&a%{(LZ*ZS||Z-?`a&JVcD9)431 zG?ZPEcU7?VMae?@*4tHY&KJr2xi9#+zuGPRW$mwB;jBNTcK+UafA{pNQ=q;<8tXgo zhO^q!#d@cHn!nvSrPiwU2G^aMPcxss?63WmcvkHnkJg7P{e07&*KNo$K6l{tCYht> zm)O<*Ia2ie*4a0UMGjf7;^kGkEpKsee@fn+C ze$D*9e_prKYXtB+_pokb#J)OtK9Exe{qmAdgq)Ey@@=Z zZ@M`>kN+2b^@ZWgXtyexuQg?Rl-tGbPe_z6Ix3(2Oz!*v%?lSy{%ZNX&KC3U4*vL9 zKJw?`^PKfJgoBSJ)m#co`M_{lcjHpenOiEQ3#Y`-oN~{s@3@_=ydnENn;Cu2J?7L} ztT&s@edG4WzUIW+Z_g#k&j0qGZ~ia)O zcNm!Tss5Ukd})HYfnmt*8E?xAa+3_tne|ET%n7)Yd?NngpVC0d<++yyk9REnai)Lm z-$=HUCzmV2eogrPaPH4Gy#kZ}&&}fc@9h71t*D?+aZdER>PJ7c_y62oE%cJ77&Hy2 z1YR=nabDMX7IFPek{8z;SjMz{UR_@0tJdjemv-*e5Zxhm_J8+%%l-MEw@j4pC};Yh z*!XVQCR5wUh4)x4n)I@*HT6AKvbS)q;7q9BqJb*YanpuRZst?t}Zq zpa0*Sx$D=?AJHgo|99t8<@rCJFfUTifz&lAtmodRR2WUuJjr$D`km+DhvGsXwEn!! zp!el-l~P^nb@zSm#h%qT?JX@yu2idweX#YgGHXEYH7yBK@2e&YZW2Hvi&}mY<5(r=1gq=`o@Fu zO&iS3KSuO-ZPhy-x$lUy(e2D{_dZVH{dTQe*iz!Lz%he!o4*9LEZNZ`%vRl2xyL>2 z{x?x$vuVkdhxmU<95S5FTm4jebGBaWi-#XYPs@n5=Oo$wneyvO+aHlRpT+qrkG$9z z@qYcxMd3Yt{@cRl9IpBB|Cr#D_r~9f3+!X<99ZLK&j0tHA#~ORP{;pwD5z7q|J9uS zynkn_?Mmmz-A`tH_MBnQ@k4VL-faK>E}`;O{J9GcHP`)k%W(ckKIem@4W*}NosG)x zy0XQT?|pCK<|_(OO5$d-jMHwH&XG1szFH8vIc?6{BO8TZh4!perkKR%_nny!{bY9W-zb+(RFd- z$#su6unTXRleBK@=M8Han%>Qr+kF4s`@I{}ZRP(c*!;8K{o~xN|6jf*h|aM8|39I0 z)(TJ{RD*lw?{CeSp7!r;b==hXb@zi=-_?9jJ`LKKVSCeW@#fVBy6a1K#MI|rkC{97 z`CRp=&^j5=#Bb8pZdcxL)3^n9IV!jH?3LC!f6Z|B%SD{bx8L?2UY(QqiOD#J;mCy8 zv-h}}<(s7XX0H9YWK~*l5$B_45l-p#yWcpgoano&eKt|M_V||_ve(_rYJ{9$p0Uol zo>RqlY-#43j@xxBqBUfMXN3Cc*1xPdEWUWhhV-+09vSI9scSoQ%}(v;rc(=Fvv}|r zo)+_ar1viG^&Z>gyg4hPY~CES_4)Vglg{t1)7d50PCvK2!xz^pQSrLoc3Ck;;;xzo zu00<^Qn0_*ozRpuRYHx`eG{U&H}G-ZE*d5^N>pVZ|w1u=&=R=&SkcBp-Ajo|FZ z+zW~gk8L}n9G2s~aOWd~Yny~+9_;uazWImCnN@A2t-{abB9dI!+vJ>&`B^ftK((Oe zR@2(k`*NN~ec4;n7GKzMXmjl%b-A+(+lzS~^?FBKTX><4A%Hdc;Su3o+wWDqW?46L z&fQ7!Hh+%Y=GJFAQ@Bz4!`1u$`o*meNZws@V9kNJ5Kx9VC>Jav_3Zuu@i#Ni6x`d( zbiV%IyWP!dkM3v}e7-;JY4UyPwP$x8D=sP6VI#a>^+D(3=&YtYF}cl~=H&P8>-lKz zefsa)%f`Io%{_BXtxS`HQi6|7V!X9&R^}~o;cX=BxXuWBct;*2Il7nCDb3;gc?T z_Ijg4y7XD2RngriKU#(VRGxQsf__#^(Zh#{0^DYWKP&m?pPl}BYu-^+DeohzZhJNL zKRC=D^Wsf@L_=ao&Yq|D^MCsv{dtf7U-cfQ4~IYZCRWvN;JWkom%NQc0r*hxG*)<8 z@LP5D=A$`DGxE||U2JDqvc9}8@8w;F z_AKe$ZTEEd%dT3uN^Q0BoK|VQ$1N+{4?>W>*IQ@^L-z4!t13A|F&usZ>w9ry?xz*V*Q$&O3gZE_*~w0 zrq8Sm-=hz@=bxYb@!1T~8@st|{_p&$*HBxTA^-o~!J^O4#no>;{?TxN|JZKEJQ=z5 zO-05_+camXoBQ1^WBr+NC1Z=kfBWiuF_q_qcYcSxzq>iPtVx06UBeOC85i9gHmUr& z`2AvCd4zu729dgg2*I)&VK0t8WOt3R{RrEt)P_dRjNCtK z-pe)X*4xd_mR~uEP5s2?>vx==W|dg)GBF6Tyt;npE%Dej)0-S8rpn5w+!C2G$7-wO z7S=u&-`?{l)dGQTVO- zPjC2dpZogNq#sKkD@J||KJw-V)BhclQbX=-E0*7=Ja-Mx^w`MU$9FECp1!V5(_3}= zyVs$At&U&HxIJ~^4_C9$Yf~>49bV3vku3Dn=HU;^cfb9a{>J6_)%{ET`S-qe{1-+> z0S3nD3}Os1Qgu^S&$s#Wfi1$#9R6+Y4_cp5TlAW8Se8GLwo%xf034Tem-KKxKc<-Jq z^KP%veXG1PBRSE!ZdzenR&=T+{}6*NEVOJse{_E$5u$cGnM$A3Qk z>&UL`k_~fiFJEW-Vfs^<>R0^XtL^lPAMLz&fAX(wt6W0ew*SdjU%YAKGij6W>o3SR zY>ilZ=!PAK&%7hw{?|Tm4i{ixY-bQ-kdc@t^3-^u`o06a0vWR${xkQ_I(5L~@+SeG zX%3l3XE;yKIy5gucGI=ppHeNQ8|0-=FivLkmC9H-Imn-J#?C-lb zRou2Oc51lorQ>WfQ)2F1{I{zA^-}rM8+m?b%7)KeF!yz6|K!bcZt>h&tMfXjc2WE5 zo72wj)Z5@(y6E_}mFr%mhMu~rzHgKKpQ1l$m0MSCJ7RWnrR~Q^miPVfDQoiY#Q){` z^T2Gi^Vc0kF=DdU?4~d6dmHd*lZvc;>eoNLbh*RrmA{rbuMoc2;%&Z)+{b9ivz!-~o;FD_eqEZ}5uU|^Z0 z-oV=MEb^%8$@#7m92BOq9Ghn~(QCi;)t8MgdnBKw*H=&bF|A=?(ENu-89YVSWCko- z=f&VIF0`cZz*7~zBRkxFWh_{}Q(62=QPYkE=T2n`tk}85no+Ks_1g-WAD-V#IG)5b zJLmHMdA4z-?z{89=2&sst<}4FaOb`ycV+i)Jz5pA{=@}S^|h1BCQZ$LcV2&+b7wu< z*KIMm55vUPp3)Zn8vbho!|TFYwaRz;MR%r8&V84tFldLwKt!=7%6&$+6s&3upuR6=OxwPj7$D5SDEu7h0uYcL{)4QMtYp3fS zS{cPOx#Y+EfAg+xd%y(>hbT}uT$|~7m-+wH#cDi%CkjefTS?B$)97nEaIk;Vvg&iZ z`HMDQUKRGM?%Eg8T~D_){HhO;+Og9yCLo>nW5*BX%`DAk3Bi(L4>$EiJ+V+&YZkKk zRjBENC;w*cn19O3DCvCM^)1C4Jiq>$WOhk$-v+tk{HBUKZ!L^r4$KYDzw>cL&$g9o zdb~=n+~lfBvwt&#X@5xLSy%nn3zs|&{ymF*+a0@iKX&ey-5eTXTz55j_x$I)& z(xa`yG$-?XpKfI>HuZ7Lj|khM-1?65eS3Z^xIU}6eA@DVbMAaBzWH_b>0M9bTWWtB zI`7}Vyzt6|{L7nW+Y77j-!q%zmLmg;!-7nvuk%;WddG1vgJXxh%A$=7YZU}!8ylVd zX9T@(e8J;T{DQ?u-#TYIIU=eDUt$jz)1 z$HKRI{@x&0+u6eVOU2*`7oSw_gz(G9S3Tah?rfI$CaXxRO)$z7w|V}n{VReh-TAL?mYd6b zRee{^-YwltyX4<+zg~32K0C*A?Z@|1>R(S!S^ea*zs8U6CCN>%wN8EK{30)5~3G-PtFl_K|5G>)h>vvfot>vf?#Pp|8VmqTtmxa(%S1ukoyYmkx&)0Nk zXW^Ur%h!KjknjsuM;X>s(|QZbfX$j3fwHG}6y+&+bS-7So;gYA`(rEh=Vez{J1%&( z%DuE;oz>C!>*3kmr7L?E%)hlpi-W1`+WKur#ZGVZTdn;fK7PZ$`|`4!5z>p=Z&_}X zyBcr&FKcnZgF2{&Gg&Bp_4%M_QSN>%tKP)UUlU=PJU>!? zZ^-m#N$2}KYZl4f3EQeCes$lf38^oa{@r;?m;HK}Tg|rQi)Q)T|8zH-?76P-WBKI& z6EC?wIN}{|bW_J}-SX^f4|F+x_%hT7{+TWR=IrNuzXyTbEXN9^9k{pr5q!JTyW2ci z$n59ikh$u%t$M~D>C7pbHB%DKaUQr*yso_2*!;ocUk>a1%r&RXS}=G0T0P!7t_`n3 z)Sv&EK@TDlWM%&C-4Sw~J-xT33IKf1UkA=696* zkJYx3d1P{L?Is zlao~3^{=TPRJpz7+m5(+o#UPBRU_{+?aev7Z^{0vhuLQdyTbf>B)Bj z0kUuUll@DIWYt$~SKWEV==q|{+p_nb+Wl?E;-2~3I{BrWdkmh;xt&wHz%UK}TUEc-oMiPn<#T6E{wucn6CPeZ2NesR>+ zb^h5<_UDcMSB`Z)-QllrxO7`wX-D0+=J=e`uL7g*#C|P`EInEA^8j*oqv~X$t->wzPO!r%iLet(OYjx3ZC9jxAovAl_SLq zg63q`&y=s${d{E8mrMWVWbgeJx%~L=k4z2UI|KJP)#+8f*Oa-_c0k4N|NSE!z}jx3m3w{8(R^QagJm-CmYzzT%11%I%*n zwg0}kZ;dOH4{wa+^OWMLldUc(?uuAGb>hE}YYL_pp1xRF6?IiJHE*@F^!0At*}-AH zIo_q2vR_L}yYyd4pUjB9(EqDDew+OD)w+S1wTY51lh^O`eR5^rwbxtE-kf)9N@1b> zw#4{5YE_!om(00SrF*(UMtW8I=Ma{EWfNw`2Ysjyc3XVdZJrX-({*{DPm0@5RqB7J z#nIpp1Z?WxN11yi2BP`$2l*z;I~F3aPIZ0^%5e8RPLM`RzW(4jQBJ| zj~{a_|0Ct&ZM(g&G3Y{nty-*=-?qoU^vl-pe82dSrRa{E=WlN2l`CH*RwrHlb^6zm z_1`vca$HulE%xixA1Qwyn0?bUjs90UA-wYhyOnTMr;RU5U;Q)gWdS-%Pp1ChVp5nO z#1z5sA!pGGX1;&P)n@7i>G>?jKA1G@ZT!6Adb50*Z{a77&H@=NIx@)I(aHRc~0 zPid|G+x%ea*OfZYzf5{EWv5$KWzW@Xt2(B6hP_r*-+91ndhYI5&$cDhmX4%-j6Pi^R!?b2^-x83*Dxg8aI z-u+;X+_X6N_3PJ-J{R5%wFDKIcqD>kq;><#GN)?ZWB zd5vXDv%?pbHu>sfPrRqT;63P+!cruCOvWDILqX~ z^P&tJ90 z@$r97*8i(lev&$OO;BUnsb@2V=dWFTR25W$wSe;R!vdb*?$4QOK3gxaGJO;}@r*0& z$;Ri8Ou4cb3Ts^CYj74{%^3CKlyb-o?{uaQjE#{|0uMy~BwlTp$n1K2U)}B12a5%C ztc+x?upL~=wrW*>?gl^UC7af~dNAkhYW_)^^}@HG+Q7HEdp5J=s&v~b*%!g~QZF89 zZA>lg?s&Dq^7ZBETUN(!oBO+X_lv;jE5{yBc9?nEkLyRF?AA=}=TTyXSC>}h>^7LP z?)%c#%(=4H6opnD-^ML}T<`Qo`Dmf5ojRr&?@kpwn7{1z->cux%nsSLIQ+zAuLnQ< z=b9`v61#BOs{VsCOO1L1E5rY^6$jNt|IcZ0SGd5_xG{P1*$&5J#Z2tWdLJ4yUYh2$ zbCKj)9;tn%4o&I2VwnoR+NUx5)qeb;vSKT9s-*1JE4v$)3Wu=On#?F|@?NcVLHYW& zyKj$w&GCNwlBwy$m7i~asUFn*y=a?dc}b_;yZqD}eJ6sB6|IRc4j1q4lv~}ue(Bue z)&5Pjde)u4cNE<@Wmb~7VAg7`q_<|HL5wghy9SuaswIV_;HvpwFno@Wb5v*UTgK=N|}iZ1G5V z@=@GpU4wH^)RAXPE$l@XIQg@CM4t+}VD4ef$T#KJk*~V-2`gipgU^P#HHYteW|sNf zVIs4qm)7#@LO+V6_gXBtx@GB`ZJYN@y(%ZfQ=GoK`}We_b*rb%T`)D(Vj|DQR=FF-LqCr z`~7%%_Qf-ww)w7GTldwuv}67q_rHawt~p;++P2p2oAK3y|8*W#@gCgomptm@-6SJ$qM!H4l7mXqRRfkV1Ted0K4$ZhQCYQSTe0o`h0lKdQYn?1k)`}H zW6G2My}Uu8^RG-SxZw8o)-lFyKN5bkSZ%+kf6#pU#yw}R3ZE;rnYGz&qwA(Q*)v}s zHA}q}f7^n8(l5tvCLH3o8y#{R->%BNxcsb@-R*NmNz>PdwiRdlGgWRq`}J1*>mBw} znWMMe-Rt*yTD{!5NM(`r0UzdXddzjC{Lc-i2RA#<&+-zNTfCcl6kU9_vO)Czqv1h zW{36O3SS$VUFG_0rNoO%c5fzsoq0SUdGhTW>%KkC-nnDGcjxPuM^mpJOns-xvy=bj zM5B`*e_Z7=@8r`yT$M8C+PW`~&CECQs9saIij|kOyn1@utC_|2eE*j2zpQTRDkoF_ zxjx)|rqua|FU(lf{F8qFH_Hg%Wl|6jV$@-1VKLG%{i4WH{o_y*r)`{oMD3>?C!cZ0 z{a{l6=D#3cE%}&T+2L(}U-twvSK9@=GCf=p?VvY%ul$dztoTyl_^+Z-RG7TQa`Eju8@K&fXLRS--ygSR*{@&Rx+QXcjQky= z?xIzRN}^Y#E(X@;=5j{oEuL~J_}}Vv_y1R>T~4tJ68gS6&`Z5B#x8%}|1&E~toYYE zGOz@JO4510Q|nHAJfpjVnWi*&^=E~?t%!(=-xm8fGyllfV z#Rd8IOrQJP%{D$9%e-uT$^W|B2`(}RE_qMlb-TNXWqHiO#-*n&s(ybUzWufLmJK}n za~OQjn!Zgs&9<^J)?IDG!f$JQr-}YuGvQQDaB#qR(QUdH&i-Ai$2%`Ht|+g3l1|x& zt!vY*w+e+Ou6sQF6noK~{j#o;nV-!bvLc$9NUSsRsV%lBc3FL#M|V`k@g4 zZKl+QE}QMeNwIU*oDs9VlULfgB-uaZ=d9andtN0+)~)23eL#Lp2 z%A~#CE?fR<>?=>Vb(->xz{pag9I)ccjDzj{Kc9*HxvAdxt}qg7wpJUU|U0&|E zV2bvPkM9(NPfl<363Dr&5Zs)?8)vzIm&u>cNZ0!>v!3PmQl_Nb&1|a+D;wa>0En7bv~e#e@99X7Ww9Lc>l z?Nlev>u^i0)q5B8{n}l((dw-~>s7fYj`gd%f3J!ad}Oz6{)wphPvYvi{xtt2l0>6+jl>+fw0 zzJHHd-rg!RFTQ5^UiO=fi(k)OYs>#Yjx{!0H94&7`x&jHmrjdn1+Tkqr~F8k!Fg(3 z@Hz3bXFkojdBr%=vg7H3$#eLb!ua%ZH=JbDoqpGB+VlrkCY}tiypZU4<^RESS<}*W za!{#;eNF%57h#qwSFC+*x61j?#=l3zZ~On9G;f`4c6iRsI!A_cxvNf3 zTRm;H9jl$SSJsvK5t+wt}^Qn(pSL<{9iENl`ttQJJe);t2>c2nF z-&flEV`HtzgLE;C`O5V>_vk1ubz9=6JYnI{aSi7xe=SXZ!DM&0*& z?S<@l(wdj|{;RnWzo>Kn&iRW!7A-i>Xt&qsi&1w;ZfyDA)m87VEx6;!GVjNe;%5gg zc&PJACbKdMcuZgrV-RZD-}{nHo_E_s7gLTUJqK=V^weQkl5_4+(RPP+u1~?NRTdsx zLaa+gBvnhj8QZtBpQJWRKL3te>xZH_q=spb*&{>#$?+w3@a zwmmL(U)`MgL-p2<{#R*pa}K|HST=F->tC)cU#GjQs)~`l(_dOB{d%#;jmg^UR@@S@ z-S%EgQv3CWQhkN}0rnSZM>R3%> zK6clpac-XGgWGIB<}~)p-81pNd-ios=q4v_hR1ggZ{pan>0F?^DxcqL=9FE0`)+Py zT**?W^PFYn)py^^MGB$?G`fp^B|iOGox^z|(!KW5f{5CS+1lz>JNwpOZM(Yq;NDeN z7^03Z{pS&WJO1{P{#z-vA%Zs^RLyQ`WjkIzH8{5MZq@!B{j&Ym|9;=P%Jo!bd1L+G z9WpBmx{OWkT|a3aw<2ol#g@h5QC71KbWY!Q)DEi^ zaZE{D4}R0goS+ka=UCCDq2TdFTS zd3iz4?-ToN&JNXtYuS8xYkSx(O}hOc{IvGoe`n90^5apRb@;}fQy;f}S#S2?{HnNX$)%lc*AgdxS(?4FJcViQw!XJn zvd=@$ulnXHdOC9NooBDOZ+W^k*Js=9>XEJJ{g(XUTb+sk$HW6F+y1wE2(JQ7WGM=q zS6`N9)7T^A@#Be7DYGSqi*v%C8_O33@~oBhUC{3rDF6FMor08VPtdxIW7TcE_PP$b zUIwcVt~Fh^aM6j332Q7*7VXtuoci({Ji@t8UbOh9Rd72RWd&fc3)7Q2a+fOY0 zReWGAWBmemp3YCJbhDkm6mH!X9p99_>b}z9O?U2}`t(#xGHd#hqN}TCeJsAJ_A(+m zzW=>oeY%gMvlkAZaOm(RWzBVHVR%g zuT#w(Jk581<&AW-Q;tw)4@n+{s0A zd#xt_{~Ijj9|dF+d8JMvEb_3EC+tfSn~$8Z1rDH^@Y_3_W8PX%TNO1xrW zYL~CQWb@&|L_Z0wV;oEh4?xYv3KPj|qBW;JfMy0xfF_eqI4smtDEN^SHcw3;#3km1 zX4%Y-_Y#9oTrB3vZFt4DT1aFvzmP`u?#Y|iupPQI|Mm-2D-)0MV+vp2ZJjD=8oKP4(?s-Uh}-+?y2JbEPb(cS;CULLVnlmJ=%9wbJLsty?jr$ z^c4rnZVAZ^zFRft{;$s!VHq9kFC4RZb2eGZLs58!MeF7N&Td?#3JgrX0tc8JgeN44 zaoU9+%@Kd*pkT)Ic>aN0hK&c`88}y3t$6#eSY*wyAJex?I6IsD#k7WdL0v`|P+l-HVWF$VnWl%10P`EFr;J8lM#Jk|kb+Z*$6=pQYac1qjx2;>p z^8A0E5)sGQe5YPL^!~a0Vccdp^^a4pc^Ks1c1e%juy&VAOX()P)zM#W7;W#)zY;Ih z-^`_>Za!rg*U$?{LyhJos_fADuh4(dwhTK>H4eE$@iB1{S@vo)IdHJ3`9Hk zg3N7I_8dL(N0}PrjW6ztvnf$bK6UzIzy9>ICwDS1KegkkJGqbTkXgsG2Vws%1UG0i znry$#5LKKPE-BZ@_Ug{NYsJyNvac73pYEP>RX0^|-Ko_*zYbY0m%6*e`N@)luY@&! zEwo#08!dk$)N*xm?UbX8)0V3|erQ&AQ>B|FEU$UnpU;~AJD0eAdYS(Huh$WFiQH;X zNdDG5#^87$8azgu^}<=OPwG>S-vd7rma5}?v)?UbUlho6T~(`-pAeg*AHA%|b5 zZJ#k(DH!;3GYRDNi0O;2A_T_;!@lmE3tZ|}CZj{egEEl=HCR(CjF`S4%MjXlrq_zVAj^kYpo)1S5V zYnK06Z9nI8{e$@v`d9bO&$B+IAi&f(@niF|hsXQd7I1>bJ{%U5g4{nh@`~+;mC9L% zF1+V$+_>DY;xV^Orax1v)q%GQy-)V8ouYq9mvQm3z(kxO!h+gYN?i8Ukj&h7Ge1Ou;K7s_Eqw~ zOdHbJEpC)DrTk<+@HXoG6C0*-k)Uk{H~sEcp0IG+f-}o*%=681*E<=vS7v+h+;5yu z)~^54YyVsK_OIBvF=aKA3U6e;yzDXU%L&K&!ug-_YcJ}2R_y&GWA;#!|H(zc85W(# z|GT=2&vIm7DFclj-Q_rXf8G4;kGAu4D+m}j{%h`^;q|}ZOjzPNVNRJ{F3%Zt)T}?v zOA@~;WRTRg(@EgRY55wi36{?rqT_dIJgAcSQ@r8RJEg{)gWJE}aZl0Q5cW4lozdcC z#s#^W%j^DHtVp+1{i^8I@eWnR&VgvH!jMeTvuS zFIBP!>v@G^T%5oQN#5Krp7^3I2o#1HZ46=zvlfc1_xT{HAGdh}8?CB$^W+>^L}@ZWlmd@ej;V_O@)N%4YQb2cqe6Y z$b@+RSZjIxz_%f3B%30co^&sOnD6~ycE2^-;eLCTcin0E-j~s?30t}5N0-&6+ zxy4`U-&szZt_ck6>N|K9oDKx+Z9VW~YR}7gn;G9vyH=rH^rhoKVO7Wj0a*c!PUcj; zL%9cHZ<@SM@6#xD8CG=}&f`?;J28}hG+{Ah4c|G1y! z-*Scj5+)0-mr3_{?`C^&XW4(Z9qWVj4_aK`$n$!U%G;2zoxc{uw)|+n6?E5;S#(98 zIGe@vl-w2ZYi9HRdd$j@FLbu{S?H(ZlY2St6*MYO6q;evdilRHE6-C<3q$Y#lSA-~ zB+*Ut7bI=vOjekn(Ri@Ee`Zp6{35#p73qQ+Yc5tYh_7!pK6jGC+|yzGZ0~dq>R-%0t#z1j&z=?YCjXnvwPn|@ z)k+8bdH-2{*G#SRI{3fIU+&=aGW82rFK74Jl`p#6-ubbSfn!Mn187`bWxt*3&*SWJ zTNN99)Uw^?Tit6@K0KjD?Sk`mMjMCi%s-}V`VuGcTGnW>ZL`Ss%Z!a87TxVKEvpxA z^J6YMYni~G5;2XbICF88-=AQn!hOrkbhG8px?VfA!S9-4^#8SX`<{g??tikx{`Fe3 z(~9-k`((bP*FMtu%vbqiV{oGq#}NncdXk?tZ~5j+oH+!ZQ_lg-zUg`ht-o;IuhW`| z-9f>R>G2L;1;0BRQaO9-<{17pmb&z-dFl2mxqM&yCGUwYXf$PiEVIMAi0SQv`E1(@ z71kA*X7kz-`~yhw1UIu(&PUVKr4F=u!Bm0M6s;e9sf_R-X<5WaN!zDn_T5BkyCebZQXdz z`7^car}-4#w7<`_)8?*_LE5j=dxdUzPsnYq;yt9Akbd1XW4S}j_OGAqCohO~dH28i zqAOEg`O__N%vG7&%{;fvdev0BG52l$g_r+o_V1s5@W0Z>ADXQHzZ<^jn9%I7gymH1 z-Di{a>$HNT1NfK}7?{oq9bj_Uyd-JTV*dCa8A8I@Gd^}ZwD~#9S3Y9hKc~rD)=|N` zf3dw2`1Xi>s(jDdce9^iE-`cH%jZ;Jx74^E-g{bR#pnG3=hpH(Z{%TrcjsI;OHBUL z6&3b3AH>~Sqhr3xR#e1#O?zBc^Zu{Tr%&&ERLA+xHs;00N#b+3rXJ{?|8tL~i1)%* zEk>Zi{DwB84#S(NF)n{!IEQ|S)KwA=;AXlfBw=s%a&3OWn!TJMV#d!7o|bv`YvwMl z)Z&bUJ0+s0g${T(oLhHkKhMWm%;kJ1c6P3;S5{=3zDd_OD;FPyKs_VQw#%%9VXnzsD-u3S8` z#;*DIzrV9*p8K#OLTh%qy4`ij=A#;p3@ky84BQO=d?)|@dD{N{13?ZK4+U>^m42Il zH7#Nbq*%(-3SI;q3waS&IA3@^=YidfKYAJz0vXOTTCzX-2{Dca2Psa_ zGNj1E>Y|{92uD2Q%fWBB${0V`^K)J+ z4fuAuYUh6#|l07@tha)ATQQNs__3G7ia$lAwKcCaD{Daf?iPgWgyCMSm zrXM)6a{6Oaj%?5%zy@wm#4~9$rqu*_GFbWia)wMe27EcLSaGS4#j7OXjuz^kx5>pZME$djBhf z`q%!R+ka$VIR8H(#jruT(w#wHra%@ zvuLwZ_rLL96lRipBX4TCOSOTy+H+3vhI~^te}M&6y58%$+4@rdU8|qV_xti)M!(7P zkA_OU@_+Gv#q)HFADsMgt)2?g&(9XO`~1@&)JYt?BEX4(n_*!BPiSYI8+WMYltzbH z98bJs(_Tzme))+}O>Qug%pdz2#n}H^ySqd`e|X?{+4sHjU$gtX5AGWIOaH(8&}#d< z(i~7pJ8IVx6IwoNbi6a$R-n&3G4h#dD}0PO^sm4 z*sXcA;hR&!n-@>wb}i)RXz^R{e4XLrU>UFF>Q6UWer9;fBJomk2{Y5`eeB8)vbjEd z6=PV(e{03O_n)6XxF0LvarlJs`-^>Tnt!-`566@?^$V*P{;F3$V|^iX)f+_-VI7qJ`O!P2O%+L+T8c#$zme@n>c{^4cZFSfTRN$up1h#p30x8JAKS&i$NS_w(BP zci;V=TAFxoX?_^`>-x&(OO8tHjV&BDU(Xa?HaD0tp>zf-s8utUGl4N7-EQOdOYDX< zEuC|%cz$^?Y-jvoWjyy~_`H1*^*?HkeR$ZA=^be$Qm`lgNYD3vmhCJr_DlaeW*^G_ zcjt#pJ)`xPI_pp8sh(1ppwoCVINt9XV+1?MY*|nbm)(NXbm@o1sZL80w!P)-x%`;H zxMr565J!d6f^NPU)*k)rKH0Cdzt1?#&NqweE!Tr1-+z7H{(q%!{zdcu=E1LRW%8Yw z{90B%$B#j zTyVh*{Tb}1oYl6^^1ka>Z)#lB%JMh2Cjxwetin?p`XWraudDxoam9YJ$HwPLSFW)@~ip`1%UlbUa?tv3uuuFWlLL&pm4whs6zPqMtG&h*Duq`uAtLe&P zU*LZ6<2r`>myYfAEl&TsF7if1p1`l`Rk!b6O=V+e_tl%eI`_lFuABFkUSDtK`|Qv) zS04G87JdbTLZRjVA8%)1aX3)VRQW&ljRpV5lgl12lVQHsd*H`L|C#)CKfdqoTqw&T zq-aoPn*8HvW^>Z>r(An?y_Ye^{n_h%`%n5#AN{INO%IK7|GG0V&gYy{@zUFFH~W21Ys>+>MyeXeky?Z-@3 zt@9tcXRO`)-9ur59TUIprxweK9}~Y=^7T71uvqmmh%wmA{!~51rkL|xLxVWWu@`0x zIt&$tq6JC(EJk0Z@AZgZ@p*MW^Ie71lNXzYJBCSPAcKGV^pyaj`Rp4k<;hyu9!2wAx9x|4X%4yG(IBkj*q%{o@b&_39kw8W}iPST-;SEcmdv zftR7~4D+53{SMzRFfU90;b>f+eaikhs0rke4~9Josb_3gMXkGItX&iB z_;TLP3%2QEyRI{L8gNVQs$IB!`Ee1k&ClNLF7ZB~Ggr&u{;TvG>Nz+0g-uJAPC4+S zx&7=ycRBx+-wUK!92Q*W*s^2ihRYw0*vCaNeE1;q@`vRCB@Pp%1KSxvDL>$YZ~TUE zu}skir@D52IINxabbj9KH@{2H*WVTYlDP5IoOn~)|JiqYV)bT!y1M41&tyfG|A`M< z=S>lw@#oL_zq}>; z`P$CaX{o*YelCCe?dJ7qzVGbCe9}~p)GKFy_xV<@KkJV6mpRT{KZ>_VC#+bW!?w$7 zS=D5x>Q6^kI4lrln!J3&-~FO-6F`%B(-`s?J*L$wzgDa(%3dN@sxTprX>W47@AU^W zBIoRzchB)avA_-W##F8!_ji|EKBE&55I5au`gPxT*Y@1BxS9H{_o6Z9_sMy=Gw$?@ z`=;f+KDOmu&^?h)lEvwDSJ&j{<=*d){KDF3A}AqgpCMQGCu3Gj+j<2CCRslQYlc6+ zSwWRnF;l;dlEJqtE$;UYhzfK}KX7&?>*r&?!=>2%H>!8fR{P$}I=OnToaKh;|23|t z|324s^8e|Y!w=%$iWFNK-wHR2b*-7cz-L0aTqCE|pZ@bF9hg8B$`Ds(GkaQlU9v1hdTxBl;Q3FSNHjB6&ml}_BjwShrE&p(5i zQJ`an!~cWszB}tRlC%T(nf?hxM8uiq&zWf?e|JvqUCn#P{@>SJu>Z*U*S9TCzGYUK zz4P9$&%bxQnAR}y-X_gwA2ueXpUTKpXJT)2Kl}Ljyv*f_i8=}btc*1~C0Vc5zBp3< zs`fzlB5hk%hWtqy2b|KS1y9TS$(^nC{rnf+|8swD7f4An z_x<-f#CG?|&+A_@@%(`ReFEnTs%IEyR#301ca9|zFho{fhbvzP1aMJeU zr@UiQ_f*aXH2Js-OIUnaqWP6)#?+rP`<^u%+^*g@^QY?P)%^Kk^6$kz30OWjUt51J zM^0b**Q-k@_ZL+EH9z!!d+@2}XCmT{`kY@sW9rvy`I;P+Hv-t177D+3D{tU$_t)t~ z)j|Ol2L_f!Y7Mdsfx>Nd0t}58I6oXrPWGS65vwoKcl2ipek>+fc4KUXi~`9Z#? z_tD2U*{9Wh7gzNpupHcf>!CAarTPH|S%Hqd3|_32y9FNP|1bWc!lNR<&=||vvj5bc z)t(%0To||+tS@>lmBgsPwd(f;$!m%JzY7>+9Th}bemr@4Q9a(Lb$x*liT=HSq&bXxVo7L^|x;6J_r!PO_Zk}$<=K>ns^0|%-7)OYgy&;z{CIO8Qt|WcW;qsVqwZu40!j-%zyunms@sl+k>0!>+N5jc@joc4s%rcG1#^VOGi>vD5uY}b!9mai#Uy}0@RgYOORm|mNFoihf*t&)y{$?oZiiyZgpWhBAp#O@DTlOS+6tndDY(V^%Y%bV~U2CUa1`At9fo zfcwC`9j-UISEmGCYJIm&+2GTSVzqdk*IEaHfOFwUV0Tcukto9AprFGP zY1LWzG+QPESjE%~BMJFHEWjw3Y9Mi)x@Ann& zWg+X1WC$*ESfI<1;Cp>@<`JfbbYUm;)JMLLZq7L%`=7Jtu^GdE^_7Qh_84wXi2MJb zYi8_$b5p{W?{n~He-O>ItLw&+8(P*U+`SoY|6lx+`>g^4(?@{=_17yb4s&rdIJB_1 zWw1Nw#hXaKw(zxaIumf0h0*Tj|3k+w%l@p`ulB)2chbUWCccvCPhuLZyH}U|X1R83 zb{qRa4~{3?uAl!b_jg+)?d7=OEawCM9qGb7vJCTlId(Rich-aI3-t+$jTzeHYL6A` zKaATgJR?5JG=JV)rw{UvT*5QN8Q!yLTykvmP?(_7nBG@^EM!;Jq!Pw$pp>E@0$TFH zsywBEfkVYN;m?hfOL_7;ta3d*B;%nmJtIf=J^;^*EE8G52!v)|jjE zD++KlAGq~vS>^vvyv48eBTcjD++^8~J4B9Z^bV7EIC%D@w#FV^)S3%Wy zYMy!J>Hn%#TkgO7eErO+TW-79Cp*0_F7ZAP;D3{+WGc^l9tWnz6`bIeGAksV?Kv78 zYCvr+`RoWLP(^p(MzFe%T6aF1^07M?Uri6ce)eT?@G~V{hJ?LlH{)C11Qtl9bUrcE;Afz6zXrV2xlVG0FbZi@ToCSBnJ zml>km+W7byw|@2g{B3FWGo9Su$LFW8`*?NL2s`92RbXd%R^aX6m3G$OT;1>CmwNkj zwzx(HjyIs)=HHz8YaKxg8V%}7CjB|I_duLq-*dmaGvgOE|FbjNo-S_tUF6+cw*@Zj zEPWRk8=o@eao*?^y(KMd@$u4hDf0zl;N7`9cJIs#*~pp1s9<)*SNf1x!?Hj}0TwA? zXg|{=&qbHRMBza6KmCh~|K77IKf7j4uE_+4P$|wQFQzptoVQ6vsKfQ3$+?6gJNe2- zGbC#t1QhWdFaizJxPWR}!N$AoQFqig^H;p!V)9DxDPw#gS|o5|-hofAc3IYzor+n) z%3#x3TJ4va_wYDN-?yo}^937Nn9d5!u=!eKoHT)vQQ*d0P#rlpaE(v|Lx<@1cgHej zpZWDJXaWPHz>VGm%=3SI$y3T;XDm)mYuxrH-f(ltE&HHqVT~u7ws2fXtvc2EO-{p2 zoQpB%KR>wq0CjrP_p^iZn3+c9God?-jUhrSczR~s+jw~87g;}d|Age^%X`XyF01^0 zsdTen>XLH`2hy3og?FV*$m2edWbTme^GnXH^2NmCXAd6U(VN5n;sPs+l%PY-l`3ptXS#Zr;rj)_*juv>+_}_M`S{F~oo2SzuRT8UV4Hft z!e&PImxYZ-ZTL9u%nN$H&1c?`Z~xEDt?jYNVcN1=a=UpuZ|VGB;Qhjh<;VFNHgr10 zs|z1^%wS#lZF}Eb`=*$kmf81gim&RdTazp5;Oj1UB6ahZ6$duG)E2kSx+C%C9m|Iw z`|nCRGO)aQzw)dW1E&cqi-Tchm)~Dcx zt>1#(Q>GtgT>i40-M=GaZv3q3o*sD<_jwl6o+%zE7x*xdp^TxzO#24ogPSdD)~U2?H^6GPH$l@;$3gt-~44|p!(v}TyJfN%C&KL&1ww|0lGe$36unH7I=`@i?G z`!dZoJl^mb@ju{Y4RlI!;Wy)$_%E}5}>kZ}0FOo*Fd z-r1ACep&gQ`T|f!tp;V@IaZc`Hr(09 zVwCnq(}s6}?DFaSzjhZDU)xjpZB=B{(fQ}g<|NA>o3dD#sXbG0!TOjl2W5Wjy3D@XZ+t& zaCXkzUAY~ z;`@JTcKI1d;`~v3^Wc82PQJPCI1HL?Q;Qd@PARZSSQISS^-);A_T-bIhzp#oYEv2< zWEo`&;@FQgd+RV%7;vuR*473kC?-cq7AKX4+YC-EGaFVNoof1-d)M{hvQ917{!(7G&+qG*In@u=Od=(}yJzi=1)9Laj0iSsX zemz_E^J=^Knd<5JvwD4NUeEv2bD-IcUhcCn;?s4< z3O1%(hXyf*0`7+N&k*ltW^zKkU&nH+&v)DX1-BAhE*E=0`}%$TjN9eYZzY&WFmXui z5>-e)_FJnhpV#42Kf|{Fzs=P@toX}uL6-qkwMBiDKe?H6p)AAfc{^Fk9TvRh;IaR< z=A`K9wB*vAZ;wqo#(2@P{!7OG%;KNNbawVP|GQ+-{-Ls(NzzJj0zZ4qlA{fhR@a&K zKfJxUYxB|wt=Z4j;=3-Z`$CI!*fps90K-CJ8LfX*nErm&8yR@ z92FC3oIh``)4~5dmHihqT7x;J&sMn*x`>%6)7(L^y+6)ZsXL^n`G3$(p_RNE?2G~i zZ0!uczDGCwcba$hr}}?Q;M5&-@S7 zNHd6~J0)?MDqQF|pdrh0!kW?SnqZCDBjMD$R&RgWf4-<|b5Gyv#D%xIjQdm<><^ma z&U!C0!gThZ8{A7GI6*xe8(D@97A6G&TgE7c0|)IcCg+`GYC1Emz*SA=&-dld3@u&-LYQ>wzzK7FEvO_DA{0eO8fK*7F$da~a&a zo~l)n%@9<=(r0U*S@oNJkuk$r1qLQlmJCJ)>)F{ID)o)54NsmW+*Yl-!!+T}k^@E0 z1tsjQE><@8B_1g`H{a5{^!A_Ut=Va%waRSW28>KE)hC>lFuoCN#J0RDX|=3J!k3l8YLm~FJ~**h z`qQINvnoHo5&bN9_j5n$1+a8!mbDQ-gCA{-@bH|;oMVKYlaO$*Jg-jIVzMe9bFe^T=660MU{2v!>unZ zKOdW^X}rJXxOUmA_YB8m`5J4?yxCc%`TFk^=yS?2be~^)k%yNp@-!KXL@N9lQzm=UmS`|9`#R9>4a#cb7Py$@p&% z8phklnesxD@$b|{l8bILZ3=O_CEv(7=ih$&h8Mg{)e0LnY-rFo`+h+8lwJfw$E0_1 zrOAv5{SPkivV8Ms5NG(&Yng~HqnzZ=9I!*{Yah%syk5@U>6 z!P5AaV@egXup13N}Uoj%J5GrW=X7R2TG@ zu{>xwaIMp>?2V+ciP60(_b;Hvzj))zmzhUQe=AAUrRy-XoG)1~^|g$l`8dNnQN>@4 z2kvq9$j-e}{haU3&RyFS)&I<2I{BY@;iY)?e{2aSJOy7YHD+|H)>1p(&*P9ai(%V; z^Iond1p%$b+2;94><%kjzOU!uX84`HBb-<2!|Cn+)(J&1%qU+U$i^tpG4H?+!#IWm z?61;1?$3%fSYKOk)aSgenKGZG07K&hPLWTM#<#Ag?&{j1x*&3kdfcy|&5I^5GlKRN z+|J+M%g?R&iN{$~jN#A%iFtgHA`70Uo@-q$Gz+}hAmE*}(O-7?Ot&q`sXu1_EB?~^ z-+KDG*;~K%_C0NAY;NFZsbD_P_->Qsh0sODJ~bas#LM^D6rSmktvM0T{!o%*izZ{$ zrklsU@Bd#bBk<(rpCm?wf1DTd|J^KF{Ausjty^dQxZC6^z`%HiZNuO92f&H^Oei>! z%l^Mz?8MD+#CO4J7j~9!p+1W3{Ux_I&bRscsmyl;JEK6y1O{D(GnbMX?z%JY-00sj zZDEx<(~ta(!gE`0z3446SG;V%%_QraAoHBTWX}KW%f-)njf2zbGM6S=YdJEoOj4b2 zKJ6@rtiTQD=gbPZsts@dN6%cq#_~wPVY})1V~Ol$QJ@v6*DBtI?^+qU{^!q19fmj4 zO@1{WkmD%f?VEY;;^FESH|ND(GdDZCdwSlC?|S!5q-Vcls;ll3U(D2KBK+X^ov)XL znb=-#YtVVe$+7Iyg^9<{96Y>ZuQo#y6UPychIu({Gt}JfotLI!O%4+NaD8{d>ub6?3|E{FnsOX*Vc5=i$2k0j z-T%w!>t{W^x^>2i!#o@f4l`JO2sR$JO*M94XDR!2*jLT3*3xIo1y&}73rvi=SQD1B zHpo6+D0aqSi!0Mlv8Rf1c8$9G|2#iG|NhIfLi^Y+a4;zd2)vNAOaXPM zu~VA=3H&+!_cmL#f`D$Llb!ZI<#Z>#)-RFAaMvai_rNxDnXZLvafB)U_?&tDS1{DE@Ml&6GuEff2t1J97h}u=rOENePbFT z#Q8je!J~p{o3TGPLxsqLvVs79CPM*+2S3t~>py!fx{;hwoc{~v6qd^Gv|*2@zN!~{H48)wd(`74^CLeN2+$;AKZjZ1G# z#26;c-J4tg@#RLz?UPD*uQ@Kb%$aiL#^kq!(hT?S*MlZEyNkomUV0U^++XJyKa;`( zffE<`818WzIIy$KGP`MbzWsly(T|STKE_=N6F3;Fw*2(X-eLD_<$k{-`~6J2_V=AU zTeCR6X5Hi}D-&jJhLdM`b($LlGTLu5OaqNnehxqD8pUwHvHr(VHb#LPeGcaG0ylyU zf9CwMm-3r)=;i<0XN_MuD)=$||D^x_5I2L==Zv`bhswTwE!x4?>G13Ssdg5nMANVP ze|(=deR}Cpoy6-2ax7NU4>Y#VGg>;Y@e0!p%gqka|JKjA`Sa1$KU1JKiuudc|B8dRp9PiZrdD70AtxoO9;p8g zo3ZSD&Mf!;j<~Ph^|*8?6YKH`%#8CnE>x!V?mM%lvs|Wr`|5I+0G@6JF@{4*lAGqe ze3_}k&~irPl|zC)%dvjHH%p_FYQ?67pPl`sVt;?$Bbkdw)EL_w7w~eFeAH#!D;m7y zINLH!-+5J*zV8l&3uHtyovTcfxwD(`U)j@bsSj@-xb!vru6fnN-;47nRLj@@DO?=7 z`Gc;LGDpLKJ{GqAnnNPHtIDT*T>A0WmpPS>&x9K9-}0*4`vWtJgThf3E|EX{mvtF! zpE_x|UA%fZ+wP-NwcR08QDFvk#{-HRwrzWM@Bew-ya(?+7D(baRem<)foV-k2v>%-!~LzxVCG_jlJ^zF=E^wxp25h1p+!8#3zGedAV}?|(eWHTZ)f3zMeWhF!b5?w*-VP$&;}KDg(1_rBnnx8LdN>gHbB zYka-za(9^kL*olh(9u>Wr|(*1ex-@^Uw2Wb+}rQhe)gX|b*qZoR*?&|K(|_X!s1}Y z_UhAV4g4H;W_O*anyMZ9?B(P6oj&#=%?un{+!E5#o+aNt+*=)^di&~bBMo=Z;bDRf zyxyvJtrzFd*%0Ey&jH$Nlv2!U{ObsJ^$SVg>7a>!tM40kdT=wCdUs!((HsU^eY-;O zf-gJEr<)8v*VxM|$N%`E8LTrk>(3EZjs}M!mc0A>&RR2=oLGN;axu@eUicxmm7(*0{`am?*c^+^d@* z_UImQ8MT66`^7&qs5>}t?)iMq`fFj&F{@dtH9pPY{P8i|`k7;O#<^PMg2=cn3>z-* zHJ0OJ)@yY5EhJI*CUUa&lrzVojNFTJ^6nXn+s!-n`0uUn;XKOBjaLLuT+n4?-{oDT zcRbH`njm!tucNU5>ojG*K>F=qT z@%Ii%3%pQ(bW{JuC@IH9#4SDk+gI_2e~VpD69dN^w*}pKoDvH+Gj@HsaX{tLW?}uA zK|3e4WZqz56v%K$NKSry?tJ~fKl`)=eYw5@94Q29LFYT?1EtZiy9%4_!&K0YS; zT`E<8p%JvdzW2XbL-yrjkw5XKdpH_cKvBI<=tS@b`%BS=wNI1Y==sh0RrJOzQGmrE zp_-|r?`BAISS(M*g_czooC%Byez&4?O}YLxn;+l(eqVKEw)hp$%$I8eKhv3GUwU3{ z?dAPEUwr*cP#b9W?Ae~Yl4YQ2vIUh)Uosl*c{$D#nQ->l=Q$0aqekZ2+}zVYfrU|^ zV-CaJV}BN!3LP=+yXaQxXWSJCDzmP%%)TD~KW)eN>GeOS?_t<*aSxYA69b2fr$V^S z?1%32J1@1om^U-t2-E|}JFn-K$IS+sBvr^^T3U0i=WFPboQ4gh*I!pZJCg2Oo*!Rp zy3Ub-MM!1CjvXhs8Dwr}6rGMbUKtSo{X|JP+tUdk&!sG=4iW$JY4_ikU(Rg(`n6PM z!9yoUNfseBfv33)HFeiOb6nBS?aj2mtgu%BC0q~fMjcrJ3!mpqJS9_orc^V6&WNdx zs&{`-{#$5A@pHe$v1h)1?2F63x6i>wS`xI@N}#CyKFh)GZ5K?cn14PuwP9-HQ#APX zq;T@t*8dDsQ@MWlL+AOO*fYyd@FdMpU|=c~dNGZeNxSVl2Z#037lAGMHP5&cSGU>; zFf`T*{rK^?Uw>kk!n}>Cr{2{+J}>TZQh?+C`a+|}8SJZnsjZakF6Q_3wbgTAV3`E# z`Ep$SarP$btxxqw&**(<+zIMdD_n?U;rh8oa_L=$*rNsF{{6P*iB-SZZ*hycF*D^l zJeV3DcTjwW+<9d&hDr11xHZ-Xlz)F0yQgu|eB}yI$N11L-h|@M^FSTnJ;fh)MczN) zSZ_L^kdaB@LKurtiT44-a)3mYIsfF@TwgFW7I0qJ`$zl!uWQ@aMXz=Ac^r`$^4w&x zgejvAgNyHti}B?CXW<~*zKFECG?Az0s|3vIJKkqlQKkwW- zZ+@$0(1h76j*^SIjEhSg5+;_hzEMv#iY)%KdH*(f(HGoI3IdFcSFc{>-n@BpO-H4# zPxAV^yExW3ry8?-`+j}9B=>|&7l-CeG`6sV)B9rf2!Q%7Z*Sy>E8f zTY-|SbHhA_2X&P@Rkw8>)7r8Pv?6)!g)a9naVC}j&&>B%&Mf15aO^?Fjl*0VCcdE2 z<;$D2c5K_|c>C}Anf!LUQY(Igy37n5B|40|xEE{>nxM}5p~+!&Ce!l&+q-x^DF|>g z?y8+B&R{YxarTb`!rVWf^(HVbs0<7A%KLq6N8R6FZ23z&FIkj;%56W;&Y_3ZQL}x^ zpG|+UV83SZ>vu_;V-?szMM19rf+^=u>M}lc`n)~#DbgZ0nX=Y+io zgw$UQOj}ug1RPj6ZgNae#L(_ri{JlrH8B90!JeIhg z&2_5u`I1cQPaPZyj0#E{ssp6=aOT{9Fl*Z-lPJ&I&738Si~<~!5B%8JDSK6S{kqwg zpRb$My@lZbHzPPTGces2ezA<1$-PQjZU1Rq#>wjO_u@FC92i)HlpPxX{F?v&=luAr z2M&LiE9||t$5A}Q)s=ypA#mc|`Ej+z1$FOVU0t32Z8_gwM+TNf3I^Xq^chV4?R$0X zOjG6GX6>{xM)wMrgK}1m3@o=MJ2cMYJK)dxLhIRf*$17I<8N)_8^R!{?@;lMkVA9DYh@2VPmtpBw+tk0(IN{?(!M!70z z(y?Rifj@u#lznH|3tL|KeU>QktBcY$e8?$im{V zK$R)^TrLA54{ck*diO#1|9%$rv?z2=g>;p^hk>%Em* z_$qrq6Zy(-tb%`?UTW?O(}7aR|GGsQMPUiNzB^fS%0z)=Yy9k#H% zC`-Nj?|zSBZ|MuelRwOMNhTzoN?=s*nf36+q=xw+tob})zrU`p|7#@6(lPnyp+p{C zkQ*34xfQ}t6x>nzTC6_uFev~ zUs`Nhx3KpFs7hu4t#<$wU7*#_>`cr3=hrp=SdzxH6@E~rYxFGGw(EtoWAQh) z`xbeTuQF2Ko@Key>Itg&7#I;`hDSrv?5FlWC);O!IbzhCZF246o`rHD$DA3s89o-= z>z=BcKjHhm>i+O+;H_vV7IQO|{QqCS@BhEw*-v_;KV<7H-YLHCQ0J@8&kr83H~um| zGwpSL-RIf)m${_(@5IoqP{?%o^5s3=mELcXUf;0KeQXqT2(;AYA?J;wd-g@DEO%$r z3#=-iZkNG^*0-+4F1M%vgG zKRa_?s7NvmJ-ihZ9Q?Ja|G)qD?tR({r+sVgsPg|>I6;O{he73doNo4k^_JK3f3yAC zZ2x=n{D(3OuI(~Nk;TB`prFb0Wy+@T6J?8UsA^76C>LjnV7S2=aOUix<11O`>5x?mKpN#F_f%45_62n z;mm>A2Fvq#zWXjtJz4*;JN`qMG`O9FY>Y<>sF>1^tNnV_)#%+ZtvB59*VWfv-m_2k zx*@3N;r#~pvh&134%z|C-WbC-9}eFN)f)AeG1ZQmi&B#Uly0MqN&udna_G@6_pYe!`5QqgY0Zw=X4hWuh6I1#UjKW0`HRAa<+HDGIHEg+qw&D){QYZp zPs~md30iwS=csObf~eLZg$B^7hj-5Ho0*p73!F3ESsHyRd|k}Xa#0uX_De`QfRKU0 z3l5(U|Nnsh--Gm&bnlu+H=dcP@Pjtv9roO)_j2~Ki_+e={(Y4H|FQnpYdafeczOUc zGR&Ebjg9U9J=L$@>GSxOo?qeX+qpkh%JYjc{F&p#`QhzsFWZv)9zp+pUEg2V`EKvd z1xy&_kAfGIxL(YIxBsJ)ChxK;Jvi0)+`IH#cR6EFXPjpbYw^3;skc8}T^+vvev@AZ zdNdxG#}UA=|5I;%&ExvO=eduCSI@XT(FU|dieK$$Bxmt`k5@CJw=ezU|No2s|20jR zwreq!zq|AE{{7$gzQ3FFPq(l$dGc+G%(%UW{PjU==esjAUbrkapL*@1viXnF_uuRP ze?PCUtGoAp05fR#7+QuxiZullruX~*|NHJS^_a@;r;3rSvkX8T3!hmRUl!eu5&AKC z<$`ygo}T`1Yjybo526SMMZ|@dEHA$NZ@vF}@BQC`g%>PTegs`w`Pj@?OTF~my7XN4 zc?_WPfnhJ_mn1XqZpW~)eC-o z$^3h%(A&l-YHQwmwhP8$pt^ z4Qd9areFVG+L@faBw_arm*&51J2&61Ja4xk*#gx5;5#0$#Uh{Qd+*X+cX#P(=iC3f z*e~{C-<G_J86b8-Jj;uojXT%ZeO+y@peoaD(qo@9 zTYCAM(`r`k-VEFf7eD1j{x;K2c_*E55mE%g$piB^WA;{k z{a^id>-A|KjmH#BttZT_%Dul?x1|gEwyMBL|N8XyIxBtD4E|%eDkn?q84$r#huJ)%A~0 zezpH|*#1xWq*P0cN+M(Cfw|V@bzj=+f3%CQSbR)gCi}{9`CH2>PnTU#j02^=>E*%K zzEwy&d`SCq>-xU3Z}0Bzw$D8!(Eb_ z{#jLwp=RFPwfkPqK7IBl?~8q$OaEM+|L;rw_A+-~R4*}byzyz6r&IRLy#BL!y2*N@Y!wC+nDW_XBT`nuX}9%ule2%@lg39^fEi6 z?SS?BJ)h&l!o$ryuJyWF{JS9GQoMbyG5-rkSh1Wr(mWes-ol6EoPYW*s3Mk1IH212ak?+|NqKv|Kninrjr|8YI7DEwjWdYS~+Lkp=)xWO{Dz~gkFXH zS1VqB*F5Dz+TWY!>$X+=`%^iGb;5;~gE}a&xuo&HvuDpP$^ZW-U*BHCo5ZPAyeF{! z?J|4D0jvO&5z8AseH>z%wr>NT|ZQGWW})@#Npy8r*by~}+i zG`l)~K%$|NNMRNfIX9H8*lphoB2(Ns@DR%7VwyT#n zm7JWh`JB~jzkS>a)0Xx!FmWg}K#7NE57p1NDt&cH;o)P>tJ53iWrI*+7|73bG~ zFO`t?Xkc+*j@2n&@sD$Q==+aLZ2$du{QlmC#KTJF{GluY4h)P?A|tW!gWAK~{eR!) zhqEyq6}9UNh+TRtVx{WJ-Tu0(=Wl9YQeoh#2)x91(stn^_1W`Us)AN+sf)iaVs~rv zN6@(H`b*E2^TSL7nH%N3VC~wsdoTN0znxcbtxG!OTZif)iKDV%n@m=KJh!*t-G;xy zfnT?%=Upnl66kKbBk%66>36?{2!eD&owk6LHSO%I($jM+i?6i?e=lsgT5GegG*UJG z-*t!m)+!8AFOpp6Go7enT6Nz(bM3q@>*MzBdVErS{+^|cezToj;ofs#XtWcH*kAYe zwXnKhje*bW$Ly<*7rJOpdm(rGxci}!=->vH1J|yc%a5vFSjMr18JhpP!z-eJHV+sqQF$m*JW%M~}Yd8{abm+ZrmtWH^x@QgL5)GlV6y{y1^;m7q@ ze;4iCkGZQ~cdk}xxOH*|r?8rhz(3;yb5gwP;CYBcp~2xY*O@t%#h;4L+io|Tv!I{% z(XM{=oVmK!maDEz-=n$Ga0;ua0%O1h?%=l@jOGoR`#_uFFE2lDpF^x zUwg|gvLD%Py_y31KK@_%@$qqcnZCG^!og=hvG&J6U{1l~IU6wX1FY5A|j9 z_PkpC<;c4|UoLsa*S_6)eVN%F26b2l2WL`S7AtG(>oJv2r`q#sUwXtWuf1Yj#i3`L zc1>F{?_=7MV*z3j4f>2aAC9hkdf|>Z)2q`Ap;1NZZ@C#C>$CbC>ya|eI&o)b@q1aD ziVt>M-%K|I8ww>ZNHA?qKfmwS1ZTdD{~4C)e+`a&+5KNfa)C?3BnA}*smGJj5}_ zGk@Gr=wq*m-q`HL8L?ab{lD(ThTcL9y_I*p+~|_1;>&E`6SM7eutFumOaX2=+pLf;eS?y=l8D>4$6<(pFmHhnl z8h=KWWV!l3A44DdGwSgd-?9)x&gmHrjUO)le-5(H`q0ALwRK@U0^fh{ICxz;puyQs zSb=fEvP(B=|8>54{c*DO>b)=5Y(6*ZFQ~DyOQk~FD#$aI@4$7?kz$3Oj=V#L#!(g?f?7jcCP*ZpXb$E-#lzPG)I)Z z2|4+TvL4#~&-i$sY<0cFl86KMpQm3nz2+4iQfBJ(Wq!zyiyxR&7^MCQ<=Hh(Su>|z z>GjEVc3U4`x)yzZnfAIJkG@(KKl@YjS#E_QIDR06Lm=y|+qeIhJ!}<^+gSMc*poUt zne>|V^RMaM{onqvOZ1J%+H;pum{b^My=mH|(olcsRcB7>t;u%pF7NPtxY+rB_RC94 z`^_YUmp3CE>adx!1l$&Q7+ZdKtI3=h@1Fmy-Dmf6^`&c>bEf_a+*J6{b!ETTW3CAd z8F#Y7Ur+q4zUkG!)&sBpxjMYg$w@yUS2m~YR;K-}4T*;rMU|hhKu!cVb}{Mi|MMv~ z{_m^s^tQdP>h?v=yz{>OW0&BXE0eBp{yTcnMwPSS+0JQ8jSKxl{;O>J`tRi2uT28# z%w~W8ec%5-;nWn(`~08tbl$dbAyxVuB2EcUPfh(_c_Xp?ua->DyYHLdeO-IMKhRc> zyD(11LzA!`m_(!BvaN!iw!}tHD z#}=JboxV0=FX+|RnD z@YnPPEnUVZ|Nq{1xE>apyyngRijR-7+xg|~46j5YXW#^Bt`>#8pZEQKXZ?Oku;0&M z3m(?LVsC!8|NA=s{bx=_RmKptbQh)s>9`v*;yFWL3DINaiP%ltbteV(Ou zjpl=|vwDQUMnQ>B;u+c5(Ng=qyk8_9UsD)+Bl=eDp1YgFcTMkq+;GWp`JT#%DEFn7 zEFKId$4l2#h+P!6tUL7D)j3u31pnl;y#*@8GF2}Ye%FcG@?u|?b-F5Ydi&Dpke>eh z7^qLR=yv}8zjqB1Z~d)a`oMl?`}=$6UPaZ!)=u%&I#AD;afy3&S=8Pi>lrftD;}8S zb}c{kzo4?)8-x6Nd*XSd&2oHh*}Zf|4hK8I8(&^5?vJxeJk)Z(_2wh4G0bPsxMzA(%KgdantvzPi?i(Y_~Fm+g4I>u>}1*d zJ)iw15Wgtn7Z+#4vi(P9`x{gxz3zPP{r}LyUGM$l=NA!(m z*L_(mzqjzXYD`<)V?O5QA>>D$ zlTkAI0&%9-*Va~leKOg9-;b%`aU0FApL(}*@wRuDOV_Wvw5{{~`?nrz*2jwoM~HiK zb4*~!XzZR-)^$tFxb&|-cqtmSOU{I%63cl%dgsom+-`}ua}`)kK99b!^pi2Cu!-IHPRh3=NU6+d>x z`P4+dWKF+pcwFZ5@0rHwagxtA=0<=c5K1gyYy5EZmUrEUX8Avtw`5+v$A9?kygT=s zkC)z^9R2coruE&+t9|+uRQWu9{k-@GRLT68x?a`xZP5eqy%|5mh5j=M?>}+mt#9)2 zzQ1SJL~dTkJ^5Ji@4o>kxnAMIwQJw*iH64%o;z!P|BqkBuipi$kAHvk@$Yu|^$qc< zC#QpgeiA6?D{q?5FZsf>K)hqe7k#yT@8Vl(_P?6VXZz*C%gyQM|6M;@y`&9MjWIB7 zbf~WW{pt0(-S6hF+yC!ZYvSDf^4Is4#=MHV^F7=8b@1J?)Y5aI zw`#Y%TOayr-|gv9;?q4hy#FS&{jS!kwQ3Gb3$CuKIQG-clR-L^>%s2VAGy2pU$=VH zda*=US2F%SlWu4Hbm#MVyLauDH{!-3abDI>*RFB*H2vFh>DUkPr761&laJl` zz1;rq%k;a&Elo(>rN(Qb9PvN4S6o`+8UOzwfBlcBfD3!KdEE`ZUbFwMZF}x|_a^TT z^9?>IbL5EENj5kvP}vY0;nn-$x8SRfhbOsJ@o+Sh)=yTrwzu#0JKKa@f{U>d!m7!>o{YWWmkzN1 z^IYKm;eOHnC{h0zEDo;ouEj*U3oQux@P0Cr?IebW^|Rzer>#D`Z(F`^>N!M2EC^-2 zwl@0vt()odYwvw|d3icp^Ogtq+5d5Ut6llEF1qP|*x%MwkN4;rBs(tfQ8*>^s#Qhr zZ+Bn4VbI-czYVi)+sr*KQ+%eU`u*PT*0Qp)t528tF(YERQB5pjYu42}>ucX;KmTU* z zmEifj@fAg7KORjk&t*pr6_d>eE-m$zw|=wX@VVLtjqKCfKkM8TFTY;8U*^95@9NC> z>)-4i=wA(f_k8xuX~nAp8|)cZ2(Aik-)YZ&p`7{0dzKIKZ?{+J)(bt@?zvA`;m_O4 z^RCBLzx^*^lyV|&?W;4D%8>RU0~3eI_5)3<+{@};1<#+gr|fMM>qoIQ+Ap>THSS-( z+W+>7F9-M6FV=HZ0bNeCRM*m4h#`8>b*9_9SQ_T$&SBY*V8{8$dF6q9;hqhP`|YYC zeoStd-Nq~Z&GNP&H&T7*tF$5W-=Ck`WeW~4?&o^GctQM9AqK85+N}Shmgm+!$p4z6 z_+vUt>MRD%1{2X=r&zXy2|YN#%rEojMsok(obPwb?Kiz)$UrL8E;KOBojX^0e#N8C z)qNI^I_|d~{?53Ysh;(#)>MuUOXXMZntwWF5(~q@dggzVa-KSGE6khf}3{5^{lz#KKT##^jCFzw|Q=xZ=Qee*FDSw5U^H(q3 z&fEQVj#N|qUG?Kj-%WiS^!;JjtwozwAN6O^;+Vj&qT%qWvrN(2oC0wg)_it99#|h1 zUmPRz&-uWtv+fQ^bztK$fr!m%Xa98T@B7j7`MiC-pG6Yu8@mbL5AOT=d)w8k&$nN+ z(c)b!^2h!A>ur6kjdDzXI34(fth6|DmfEv>?92EtD|?+}?Mc8jjjWeRK4Xm|9|?m>ch%wr4MI_tkIsZy-eo8b*VqMWoDiCoBrUwNW?241;zkz z@n+uz{F52JafbTDndOA8ZA1$D0FTBGd^OMS z>@3dboS*i0dqD%&m9-%s-g~qDy|%n+j>LtEvkNT`tFioVI}o&=fwSS)*&9k6PbRPg z7%gxYQegSdJg@TE%=~xz|Nnd57JIu2xme?px~j;@z@R+Q)5S4F`NF+>`~Lj<{eJ(W zE5ZJ?Rx(qVO1U41?$Ms{-LWn#Hu>+jS(ne^z*OuOwo?a)td%NCU;qv*5-tGSF`u*|kfQDG}NeuHDGM20kJ&^o% zu|Ly`yY>H5Th+iCs>AW6(_zZQkYz#o3001lx#xN9 z6$sNZy*B;i9)FvUM^*_cyZKZ-ndn}3;o`-OoBaJ+Jdv_c0n_U6^>rVQ+yDDm&-?$; z`*Q++ejm7dfBvetxBJ3go8Ehuy>{L2H0|8LlgIzu4CHRpeus+Y)qkH(kDq5$ z_U4B5G`-j?`TO4;cU=`d@;iu=DSp+v<16LPXR>>J$mi0j)pKB4aNcv~1(qxyrXMK{ ztY0G=_y4M8a`?JQrQz+bhwbuqS%+FUr}N8L6ogxgA!o2xqM(B3bnr5tpD$g-V@=jq zN3*^X+oAmif0dAlrX%nxjQ>h8*oR4wK({$2ZjN8#gt&reO&)@}ICxa*bJk>mWV zzr%i2+4ylXoLzc}@6@Z4LVqjQ?$Q*zp~NV}5PjtMv8yau9!we)+YiK-{_orWs&J)0 z!;iN)zc(FUU;p>@MqzcoJvA2=I38XttNj`=+7lJ6!18DFy4~+~&5tcUYs$S#RbeeQhl8~;KUTjzzGB_m zTA>B^#rOX>I(ND6?6QCNs^8ZZSxjrR<(t5V$i36VIQ~afy*xA1IA6-X?$3Ls7N%n3 zjk{Ri7p`out3A<%hL+#1?v-9^=-j__D^Re6h-^c#dyU*We zD*7Mr;azj()yEC-xog8;g0k{0_f64E|HFR2y>0f~n<5?^|RyBUwA|z^A9D=kI+wEm|$U z^6Aw4md)P}R>-W;?&$Ys{jHU|cYU<=Jneh_O#YyTU$QpG1cn=2C*CZIQSf4kD9Gz> zDv{rztvEsK#qS08)!85A{@JtY`|a}kwd}SZ4lw`E{r~s7{p$Mv|D1R6HO@zrA7}M8 z)cyUnU9R%U#CqF@EyDA-o@X)c)a5!8`s;x$-=D`0_I$toR-AqA{dRr!am8Q$T7Mi8 z;8rhp;Pqr+ea-dn@+mJ*hWQ*Hif%=UTiUi|R( zh=Vc5mhId1K{ft<`T9Q}%k?8RBshP!{CVKc^;!Y>>$L~ozpk6}Y?B@9kHVAte`dGE zuVJ%h7h>QoJ2>lQ!D|nuhy(McF$B!_XZcYzhb3V56owby?!5c+`MkW`KII2|Cpy)HYx{_of7!}ovsSAM&he!V3)s30$W`7Wl??B5P{>y9(-&(DsI zkGd++}|J-sZ26@o$IEH}lF* zPyU{>`~8OdkT|Pco-9%$vR6}p>3{FTx&Ny5cfDAo$#47R!l?o=t^=Yyzk@g%EFOH% zm4EZQF8+J*s=S6Uf3bh};)SN%c4}Zbuq_~czAeilmj>47+m1Ny+B%hIg(~L+Gq>Bn zcmCNosp`k(hQI#yf4^-0ej~Y`^X~D)zetv{igPf2JLcLgX8UFG{69~=X$CLbBh43n za68kd9a|Xg{i;!T|9UU)pTBj&rcM7_8s%>XG_V{nJCc6=DJVnMe3&Qp4WcCC(e}e-B&#+s}FbmHpRsyWd4g z^_^vZuy0*MyyJc^`7hCX=N-6zK2yhR`M1nmyKnO~J|stTPhhxlExh6HbWRsfhUjpv zW3je=3@c>We_s`1yyf@%L+Z_a&TKy?Kc83q?xvz(fscjT?CFSZ(EGf_+9ZQLrm#Zu4|$nqW8br%JR3a`s_<@YnE(v?)SgEn>KNP=9LOW zE!yOm0+kLh6-U=Jyn9{cZtusiV(wIq4VHhl)%jihTlc%wp8JQ#k4*UA_?nOP^NY_}{{OCD|MT>&cgg_@dA?U`UpAMs{{1?|Z2Geg zpI5E>8+CH`zfLCmE)@r+1s>te$(q6|Ca`QM(tEclrL`cay*0&fhQ0&M&uT`Pu{| zoA(}gb#-<8EaUWZ>-G11IMkkgcGlKq)oR@Paz8HRKN0%9asOA%8S9r`sff>t)6VVm zZu+k$UMCjOz~a!T&(|KNC!orhvy}D8wjT#<)@eW3UUyY{YTcYyALr~_`*->&+44IZ z|G(XS|KIn=$NTNg{zxBG{{8*E{fRK7QLPYhj`!czS3RFwp4Q?Yd9VCt>h!&DwskP) z$xk?5D*xxY@Q2@TO_qPXxoF$eZ7benwdDWy*81bH-~hrY z7whk=-v{phsz-G;Vro>LZKOEQ?ec)^1*KM@)WB^lhfR`^z2f{|X6@TE7y@=M<{?anR+h2`2^`jdYxogVk= z;|G0K^*39u$31p3dSJZp^0Op(`j{1U;NHvsx9|V^_QqR(@0TZ6!{h(HILvRq$H*#C zzT~zR>wT@Y=h^>UTfa@ecEkK@>)&qi`@DSWHvj0H*i8aIyqh*%1O?A6(Z?0l9H4^e zJ#*n!(J8CH9ITF-F#l@$?d<5Ovy9hX`FHu$68%3j4{YGp+wtH%pVf;6g}dMH`#qhV zM`FWdq~towalzibwXQ!Z{-61fTIUa1Lh9dM{_A?%XD!yt>(<%DN7=r8$Xw3)+svWxWv~0W8?+c9xs6}mPOWC)1G~hf@{2c4fmQ7e zimXmGcm9EfO?m%sdq4BIeEpx9?`z*zf4;UZc6CF{@ih-_-Qqg5`Bl_<%Qb6vG1YuM z`nS+JSMTf6xA%WdYAye(>Ui@6Xv*v7tHL+7;6xPu;f;5EKXduk+RU#za*L1ry%zpf zux-k*6?sc_t=6pz%a~tqh*RF`{hrT!obgH)YBg35xSQE{f6WLBQi2V5xC(-nCft4) z`!D_Z+1cN7eq{VN_P6`_E7@6s{etK zQt^-P$2WskZSF69e~10zwWqgNMN3|Pyp5^4H}v-O$kN-#)^|Ve`2Ms%`1P9p2ljyv z+>JRWFk~!SeRAg)ffJgbe9wB9>(lC~Tt6OP>I<(ejLp@~-B?)XdF%19-rJUc(i@xY z{=W2=|8lXv?#qD>txdo4_y7GS|BZPid|)VADdGLQ^)Wj?Ej`*TzTYDI+8RsH5YnFO zan-rW-{+(<7QguXcE`GHQww8j!?Y^CmwsJ3HM6#E-o7Qf2O$~3}|Jh&W$Hf}Z z!0-E6MQ#%vyo3(8X)O4*^s2V)C8oby>}EUvEws(9lbyUa{Ou*sh#WP)>< z&F3@5-|c=pX#Or|m~=$l{>wuCTBAQd58P8UG*H@cqY&2abLi%1k=U-w@+-ep|JCo; zn^I5Ti`Y@{Q2OEAzx)4vn!dlL_~Ihh+`ff67VqBm|Gu&=2wf{z( zU75QkxBFLZ^xu;Ve>wD|7VCMj%<^ksakv~C+0v#@79HPnuZG?-TebSk zEcd@!f9)>6x|ZJEd;QVtYvS9hd!#Lko_qtX_UM?PAn~))zb5I4yWOXWwxH@{qV?M? zmrux+-AL^JpwC%;%?*}!FIX@=jDNqD$MN5z4{v{WAF}?{eMtO^Zq?&nb1~b$UoLkq z^PT-I@B8*ouUj@T-QRJ4`{h^HmjBzb>sQT7=5Jee{dyEsydpk#YFS>P?bjO4KS!Nz za)IU-9%>c$tz;@yW6I%UeZFsv$c^8nk)>y!Ue_+TZZ*}aEy_zex%=6xtgkt%?Q?VX zf4IxktnN2w$M3q|Z@)k6umAJ7`pvf6dGdVo)ves5dh<6}#u&2l2-n0|f_Umqv?==b;OE#>d;SHE03{T^@p ziR~%>{``~<>N~*nXpL&z^s8a@OD|n}nfN>S?x`&ecfYS(XLRq__urMF-+u?+^@`5% z$yEHH9~h9S$SB01Te8veYDuG=7mJ3+b|b!L+FKu&*3H|Ir~csg6+i9GziReh-Fjos z)oEu8laKY>{5T`I?`HD)z2ANXcnI2nvK0!sos^Y_gg^HdVygkHtc;f>y z`scFq$yi*GdnnB2y!!2wuaF|uWT8WPMSERoMU8;mYA1*PjumUT82`Td5FvVRXS^uW z?@+}J@uK(k#*6ODt-spD${luQhT-GC9|Hew6}36Vbf>2pypViaPta-{#Ya*hBN>c{u+cOn5M_ z`d#M9M#hKsQW1+196VIsNC>ej)=$uJKD6ttvs2>o51q}7(;_bD9hUeV%)0rF_$038 zL&@x`+q10{ayC!Cx_kP+g>!$U^Csp*m=<)~pA>i|Dbkleq09rk9B)G^)3?5IjvJTm zon4pxNFZZ9@8^;wxhaknFSaWfMXp(^uyOI?-CUa zFP5ml1{MeHxYBbw7dhw)Gny@D-TT!hi}&AS%gf)M{tj6m@~gDac5QC$wktw6t{W}O z-u7jF^sd@pYra70sSR&gL^gc;#*uZ?F5BnM``ekI(skeYS5-GESI!GNc5v^pl&`m| z)_Tvr{e5NLQr&I2H=Q2DA9GxMl+}Y_%MX2q<@0p|R0R{HwF15_y?Xi9tMy@Ns;rx~ zT`tVno4@?()%2yh+ve(O?T)&7JM!_B^+xl;|3;?neZ2YELV55oPU9lZFC0FrWfHc2 zjC}q0-Nz`dzwe`P6;->!Ph!`@jCR=*rJyzc6f z3m?lS)*>Ia8-AjDu1BZkzW#mr)vMK@VsyJI-`Yu66AS0+h{_Kr_8 zYYb_$5@Ou8mnk~_*w-!FzJ|V9zb)>!pY-z5tyAiJ)|N`AclYgI9s18WH#RE%XBf1H zn&HF9J>{|LTL0ur*M9D+dLg~*g+uODqYr~dBy+p1Smv%`Mv+cEFjy-9Y~>nncm z(0_YpYwtC|FVj_4Cx|ltBdA~QVx*qh)aNoM0 zYtK*1dV8wK3(5t&$7Q}B&9z>;Lx1&KSjZRdjfy^=_4v`oy1CFI!aA~I zd%VyTjxTWyEDrDA-Vkc^Y1H*#yD^7r-sMwSwRN?+rEkyohsH-mu06kg`!3yCyH{WR zdind$s%sVdbxR>*Z&JPvjGOvyZ?Cn@-TU>YVdht_+NkH=@z?+CTYs&>sWt&Ly4 zvu6G)t-oede`Q6cC!6T$UH%rF>2qu2Z_6!qP}?TVX5ehNlQC1G@f4HjB*txtGB3hF z8E5sE;+6iXw|3q8@-z3*uC2KrfA`#;dQDaOGi0ixkxe{c!J5k-a^tsuEwq03`Ss;j zwHL!*%Y58;_0l%OlTH`(IlWl6&12wfSet%!p1=x?fOMw=&sK?8{4Txv>gu)cW${+= z_Vu>Knbls9sF~%cpu6H)<&>oR%YM1~t%*EzDUGY> z39gdI|FUxD#&0dG+q?eux2d^leh=2@LR7|*i zoN?Q;p5KqlZf)Dzv$Ayen#U{3tIs+^v%fM|2-mh!yN?oIqb1jO-_A_F^egM8Uarrr zm}}wN1U^hJyx_2)0Mvml_q0%8$r8SCXi39c72Ol(U#(Bixh8XMy=v~hEq1-jAqi!H zB+DhH*IVrB*G0a5{c?T!ns0Nr_Fnt0d_zAlz;Qt$sE2oE=_3ch6*>XQP7aepxVLRj zsok2JZP^y}{6Y-0u_Y3^AV?uLbFTe-ug6+zH(PEiseNg|@xxmrL?9yqw7j|cr9}Zt zmhcK=!-UD#gloQky;1dSHE%;8q){N^ydX*;&pqT_T$D3aK>S1 zOcPVsk^gM<>79xb<}+|M{K=Uq39e8OMB^cjFZyRD7AQcRj~IPcfTw7LVlb=m5yu1u zjdNQcc)$k!z`~3y0w;VMSRC@U`1C*(3PFkDc~pT4Oa!LjiJAkGf_aph8^paZ zgB%uEfQIObbuENI9)=4uFtQX0D=}Q_pu8ROJ8O?`g}7nVDr@IIL9lWiV9IJ>`CJmYV6L9h3K5Co#^3o6f1%`G|I-_n7g@dOPv3m=lV8E) zew%k2etN_it`;^aSz{rbwQ7Q_^ar^E2t`-sTd)`=mudqqqX)!H)f=oh{%XWDW?$^RU(%uE<{Q1kk zCL7_a^Vn!^M*fb12ls+_RVn%slH=t5@Bg-F*u?SMLy$ zb#e2bYOHu|$pTf!(9n->tuGyZd*ATXiucFT7HG(A;<{^;@QE>Gn$z(Y>*psI)=Gu% zy6>L+qte~FMssS!sfOEqFRx1fcr<-=M5Rr-``K%ogY{qOdnveGHdv@>r}M|OmEZmT zL4NM*cg}deU&`a5Ei&n)1#fNVi(B7YLKYYmS-+B5!JBu9^Kiq#i%&F4wgq$6)O=<> zW51(Sd++;)6$U|ByA-CMJ+Rk3(C6PrU6HAET3l~ho-aD*o;Tz7gF~DTdscM0xvx84 z-}>_RV^)LZo{{XcR^PfX!8Y;wPKFaP9lN@Zvz>9B$1WZzuxnb@qO#?3xu&*$J6g+A zWjWfEDlD&dEwdJ@`C~4*YK84*g-r^J&Ng!gY~uAx_+Y-|zQ{8Sa z-}qzpTjAeZ7cS*KE+3h(@3rIKZS#Fs+^h{?5P5J&Fpw+lXq557p5hp@3s--;d7ghi z``q~@YELW`tR2?gdd64bq!@Da(u%I^4_iYLwt5{*E92hPaqC>mM)UpY4wA|i`#S=1 zC7xID+09tOBk-P+z3CyxD*vqpUw7@TNQ*qEy+8E95_6ujs}-4LZ>ES{`>Hc#{`Y^} zyH+m#d31A!$((cSudL)=G@M9`&3=B*U%kAgWt+&BwJaa*RH^Rq?)jniUaJ1to#lNR z?$=wr{}dES$2|1zb2Z=V;myA1AJ>d3u~o~I)=KZN7e0LLnAv(SxgW+3hh``0zlrV8 za=QA$qW|7J@qfr8zV9rZA=ma^=$(0zb4`wlq~?R~>?*eU`91#{ zVu2d%uKV*GKvK<<4xNl8wx1xpWIx^zi3PHubOE#_N3dU|@_rarPfbunX$ z{!Aag&gPdI9ozEGpF6kjDa*4Ezv%V*_wO%CNOX_bTnTz6MzIyjWKk8OG zn!NGGod*vd_&Ym0-&VCcf8@VA+dsxhIbzJ}Z#0=0kPF7Vw0s5zM!TefoXp}91A{A! zOw25-Z0uYSDrKof#hLkekt&Hr$r+htsYM|wCHVyrD)~uSsmUe9LJW*;3*rU%{erzy zD^pV(p^7eYaE27+=a)1vF6ZVEmync_3suQ0&B?jU%f~MuEEet;te2fySsdjTtOqqH zOeHxdvAB4Vpil!-1M?yg(FT?V)&{nayu{qpcmc7*;^Nejmt^MWB^FgKQdExc3-$+l zfJ0D9MbpUK(P}$B13NZ!-1{Ej` Hqdoxum$21; From 0263677e1f95e7fe527ce87e4aab3714d42dac34 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu 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 @@ -