From 546208d07055fc2799a71c350a8ec82dba0bf4f3 Mon Sep 17 00:00:00 2001 From: Aurora Date: Mon, 28 Jul 2025 19:44:54 +0100 Subject: [PATCH] Init on Icicle --- bootstrap/build.gradle.kts | 3 + .../bootstrap/BootstrapLogListener.java | 85 +++++++ .../pack/converter/bootstrap/Main.java | 217 +++++++++++++++--- bootstrap/src/main/resources/icon.png | Bin 0 -> 16469 bytes 4 files changed, 277 insertions(+), 28 deletions(-) create mode 100644 bootstrap/src/main/java/org/geysermc/pack/converter/bootstrap/BootstrapLogListener.java create mode 100644 bootstrap/src/main/resources/icon.png diff --git a/bootstrap/build.gradle.kts b/bootstrap/build.gradle.kts index 1f0590f..d3a4e63 100644 --- a/bootstrap/build.gradle.kts +++ b/bootstrap/build.gradle.kts @@ -14,6 +14,9 @@ sourceSets { dependencies { api(project(":converter")) + implementation("com.twelvemonkeys.imageio:imageio-tga:3.9.4") + implementation("com.formdev:flatlaf:3.6") + implementation("com.formdev:flatlaf-intellij-themes:3.6") } application { diff --git a/bootstrap/src/main/java/org/geysermc/pack/converter/bootstrap/BootstrapLogListener.java b/bootstrap/src/main/java/org/geysermc/pack/converter/bootstrap/BootstrapLogListener.java new file mode 100644 index 0000000..d9ac9dc --- /dev/null +++ b/bootstrap/src/main/java/org/geysermc/pack/converter/bootstrap/BootstrapLogListener.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/PackConverter + * + */ + +package org.geysermc.pack.converter.bootstrap; + +import org.geysermc.pack.converter.util.LogListener; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.concurrent.atomic.AtomicBoolean; + +public class BootstrapLogListener implements LogListener { + private final JTextArea output; + public final AtomicBoolean isDebug = new AtomicBoolean(false); + + public BootstrapLogListener(JTextArea output) { + this.output = output; + } + + @Override + public void debug(@NotNull String message) { + if (this.isDebug.get()) { + appendText("DEBUG: " + message); + } + } + + @Override + public void info(@NotNull String message) { + appendText(message); + } + + @Override + public void warn(@NotNull String message) { + appendText("WARNING: " + message); + } + + @Override + public void error(@NotNull String message) { + appendText("ERROR: " + message); + } + + @Override + public void error(@NotNull String message, @Nullable Throwable exception) { + appendText("ERROR: " + message); + } + + private void appendText(String text) { + if (output.getText().isEmpty()) { + output.setText( + text + ); + return; + } + + output.setText( + output.getText() + "\n" + text + ); + + output.setCaretPosition(output.getDocument().getLength()); + } +} diff --git a/bootstrap/src/main/java/org/geysermc/pack/converter/bootstrap/Main.java b/bootstrap/src/main/java/org/geysermc/pack/converter/bootstrap/Main.java index 99ddf6f..adb526f 100644 --- a/bootstrap/src/main/java/org/geysermc/pack/converter/bootstrap/Main.java +++ b/bootstrap/src/main/java/org/geysermc/pack/converter/bootstrap/Main.java @@ -26,42 +26,203 @@ package org.geysermc.pack.converter.bootstrap; +import com.formdev.flatlaf.intellijthemes.FlatArcDarkIJTheme; +import com.twelvemonkeys.image.BufferedImageIcon; import org.geysermc.pack.converter.PackConverter; import org.geysermc.pack.converter.converter.Converters; +import org.geysermc.pack.converter.util.ImageUtil; +import org.geysermc.pack.converter.util.ZipUtils; -import java.io.FileNotFoundException; -import java.io.IOException; +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.*; +import java.io.*; +import java.math.RoundingMode; +import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; +import java.text.DecimalFormat; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; public class Main { + private static final boolean canOpenExplorer = Desktop.getDesktop().isSupported(Desktop.Action.BROWSE_FILE_DIR); - public static void main(String[] args) throws FileNotFoundException { - if (args.length != 1) { - throw new AssertionError("Please choose a .zip file to convert"); + private JFrame frame; + private Path javaPackPath = null; + private Path output = null; + + private final AtomicBoolean converting = new AtomicBoolean(false); + private final AtomicLong startTime = new AtomicLong(0); + private final BootstrapLogListener logListener; + + public static void main(String[] arguments) throws IOException { + List args = Arrays.asList(arguments); + boolean debug = args.contains("debug"); + + if (args.contains("nogui")) { + if (!args.contains("--input")) { + throw new IllegalArgumentException("No input provided."); + } + + String inputPath = args.get(args.indexOf("--input") + 1); + + new PackConverter() + .input(Path.of(inputPath)) + .output(Path.of(inputPath.substring(0, inputPath.length() - 4))) + .converters(Converters.defaultConverters(debug)) + .convert() + .pack(); } else { - Path packFile = Paths.get(args[0]); - - // Check the file exists - if (!packFile.toFile().exists()) { - throw new FileNotFoundException(String.format("Specified pack zip file not found (%s)", packFile)); - } - - // Check its a zip - if (!packFile.toString().endsWith(".zip") && !packFile.toString().endsWith(".jar")) { - throw new AssertionError(String.format("Specified pack is not a zip (%s)", packFile)); - } - - try { - new PackConverter() - .input(packFile) - .output(Paths.get(packFile.toString().replaceFirst("[.][^.]+$", ".mcpack"))) - .converters(Converters.defaultConverters()) - .convert() - .pack(); - } catch (IOException e) { - e.printStackTrace(); - } + new Main(debug); } } + + private Main(boolean debug) throws IOException { + DecimalFormat df = new DecimalFormat("#.##"); + df.setRoundingMode(RoundingMode.CEILING); + + FlatArcDarkIJTheme.setup(); + + frame = new JFrame(); + frame.setTitle("Icicle"); + InputStream iconStream = Main.class.getResourceAsStream("/icon.png"); + if (iconStream != null) frame.setIconImage(ImageIO.read(iconStream)); + frame.setSize(800, 300); + frame.setLayout(null); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.setResizable(false); + + JLabel dataLabel = new JLabel("Input: None | Output: None"); + dataLabel.setBounds(225, 20, 535, 50); + frame.add(dataLabel); + + JTextArea outputArea = new JTextArea(""); + outputArea.setEditable(false); + outputArea.setFocusable(false); + outputArea.setDisabledTextColor(Color.BLACK); + outputArea.setLineWrap(true); + logListener = new BootstrapLogListener(outputArea); + + JScrollPane scrollArea = new JScrollPane(outputArea, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + scrollArea.setBounds(225, 90, 535, 125); + + frame.add(scrollArea); + + JCheckBox debugCheckbox; + + if (debug) { + debugCheckbox = new JCheckBox("Debug Mode"); + debugCheckbox.setBounds(650, 225, 105, 25); + debugCheckbox.addActionListener(event -> { + logListener.isDebug.set(debugCheckbox.isSelected()); + }); + frame.add(debugCheckbox); + } else { + debugCheckbox = null; + } + + JButton convertButton = new JButton("Convert"); + convertButton.setBounds(20, 225, debug ? 625 : 740, 25); + convertButton.setEnabled(false); + convertButton.addActionListener(event -> { + if (converting.get()) return; + + startTime.set(System.currentTimeMillis()); + converting.set(true); + if (debugCheckbox != null) debugCheckbox.setEnabled(false); + convertButton.setEnabled(false); + outputArea.setText(""); + dataLabel.setText("Converting %s...".formatted(javaPackPath.getFileName().toString())); + frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + try { + new Thread(() -> { + try { + new PackConverter() + .input(javaPackPath) + .output(output) + .converters(Converters.defaultConverters(logListener.isDebug.get())) + .logListener(logListener) + .convert() + .pack(); + + logListener.info("Converted pack."); + dataLabel.setText("%s Converted! Time elapsed: %ss".formatted(javaPackPath.getFileName().toString(), df.format((System.currentTimeMillis() - startTime.get()) / 1000d))); + converting.set(false); + convertButton.setEnabled(true); + if (debugCheckbox != null) debugCheckbox.setEnabled(true); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + } catch (IOException e) { + throw new RuntimeException(e); + } + }).start(); + } catch (Exception e) { + e.printStackTrace(); + converting.set(false); + convertButton.setEnabled(true); + if (debugCheckbox != null) debugCheckbox.setEnabled(true); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + } + }); + frame.add(convertButton); + + JButton javaPackButton = new JButton("Select Pack"); + javaPackButton.setBounds(20, 20, 200, 200); + javaPackButton.addActionListener(event -> { + FileDialog chooser = new FileDialog(frame); + chooser.setTitle("Select Resource Pack"); + chooser.setMultipleMode(false); + chooser.setMode(FileDialog.LOAD); + chooser.setFilenameFilter((dir, name) -> name.endsWith(".zip") || name.endsWith(".jar")); + chooser.setVisible(true); + + if (chooser.getFile() == null) return; + + javaPackPath = Path.of(chooser.getDirectory(), chooser.getFile()); + + if (!javaPackPath.toString().endsWith(".zip") && !javaPackPath.toString().endsWith(".jar")) { + dataLabel.setText("Please provide a Java Edition resource pack."); + convertButton.setEnabled(false); + } else { + String name = javaPackPath.toAbsolutePath().toString(); + output = Path.of(name.substring(0, name.length() - 4) + ".mcpack"); + + dataLabel.setText( + "Input: %s | Output: %s" + .formatted(javaPackPath.toAbsolutePath().toString(), output.toAbsolutePath().toString()) + ); + convertButton.setEnabled(true); + + try { + ZipUtils.openFileSystem(javaPackPath, true, path -> { + Path iconPath = path.resolve("pack.png"); + + if (!Files.exists(iconPath)) { + javaPackButton.setText("No Icon."); + javaPackButton.setIcon(null); + } else { + javaPackButton.setText(null); + javaPackButton.setIcon( + new BufferedImageIcon( + ImageUtil.resize(ImageIO.read(Files.newInputStream(iconPath)), 175, 175) + ) + ); + } + }); + } catch (IOException e) { + javaPackButton.setText("No Icon."); + javaPackButton.setIcon(null); + } + } + }); + frame.add(javaPackButton); + + JLabel outputLabel = new JLabel("Output:"); + outputLabel.setBounds(225, 50, 535, 50); + frame.add(outputLabel); + + + frame.setVisible(true); + } } diff --git a/bootstrap/src/main/resources/icon.png b/bootstrap/src/main/resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0d09eeed90b82ca0b445fe3d6840cf0bf83617a6 GIT binary patch literal 16469 zcmV-bK&roqP)1^@s67{VYS00001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>DKk7+DK~#8N?VSgh zTxHq*pEJGBY%kf(ZhD1~KnRHtx=8&Luy^4vc0_>yB18lMLH)2Gs0gB1!2;^1(nJsl zAR&<6H@n&1ruRAj=YG$5XU@*fZi0wAyUBfBZ%&<)*_q$m&-0e^9_5@*UPgy?SZ|h0 z`tq<2>rK&N2P>g@7p=Kuui1L_Ue(&7?-EjNWB(rgIeuL_@`H{a-Fk_>>#$gdJYaxn zQ^tB%pUtZ887(7NN-8%lXvI1?5&6Wndv3j3-#p}W$N`r5@U?26%7EC-M9HkSgs=|D z?Ggf50L-}ldSHVVCt}YI-gEO?^vx-+LjthMi?06XrnGgHzB#svp?jTry|5h$?lz?X z3%~QLYRaTLlR+uF=0e4?k(E-E)|NQO=xVP6JR@l1xtu(P{LTISFDv@)DWxd~7~HD8 z$0^(@=Ts=3Opw`WrEEMcmSYJ(*^(Lv58ULbMoO=hESrrAZJCaM@5?Ic-tEOT7VuaBqk;FE#_n)rHH{->03j_qPb z*s2{0C4YMDqyw$AH%|g}y!Yx+aM{vIzCf;I0Xl#ti*Z}ZoaF5iMIM6ymt4Nl;!l{x z5)h3AS8)Z!{=pjyZ+MbuQaTvm!V+-R_1MHdQ%a$njlVozN@3fml$F0wh7U_fx*Bpe zx2ol75;`1{fX3`;v5w_|3 z46#N5GN@ELoq!9_$t4TRVg2h;JZ28DIhuFbO)9#&#LhC+qyelLE-V38DP^pRC=#v+ z#VO=U<;zfIT0jM*xYmdwnl0For0YrNlTaG>qbT8ww&c)Nd>nAeb5I~ahROkIsaVP) zm5n4sC`7|6+RS7TjkFA!Y)4CSXjLOwa`0REvCb&#ADFBP>aE+%_DwPLI z3UKuMla%nAD9g35j3Xlolr*OVSQ1!sLd(yL&S!MIVwc2m6ogO!CAT37KF;@X_=VF# zNb2LJBn2%o;apf^8(^8O8mLNGbG`tFM3k0XE)N&Bk%I*++TZBl{eG*N-26Vz`SL9X z{EQ|-lLD~*6o(NM$gmzV#RX7+%jC9GEE^O12R$+Btw{*Cf@0PLsWP82GZYLLWOPGc zOv~q_^GUdH4XhjCl_1G;u$1dKW`sPJVP|5=$^$?`5n$=tQmS`kz9tiT30tLwVdDG? z&Q+N+lZ#!_m(#B6H7WFwtG=dfl#$B57HPTQ3UyDSM4rO(g)}IP3OnNDt0}e|bETiiC+~nX|DFisB;CPmVg{p&viU43j4u{$&gkA#(VNikuHKbZGEP%|NuE?5E}Mh%76uCiV4*-&fXjF--4Wqb()pxhIK>j77G>C`<#T`w zVHX9x04T`oa0~@7NoSLeS1bvYgshYTUbAqlX}Ln)1p%y5a6wFZ_VWMvJQi{G6Q ztm1W*f9V!;LO|tmb1jddLaB0@`NJow=#sMS%b&T(4Ak z7Oq-CA%@JwKMIG%k_W07D#!?y&cD#tuDSMe`s#$KYys={)@Q^AtA{)Y>nK$o+X$DM zSK$z41{W{?!kanILczM+4w$tsV<4tMA+P1j6`ZjcI^(*O0Lv(O4liw4{?fT%9iYh) zV|O*eCS{FgX#v*~t^8v^-eoM1kMl6NIDSMBfO7Ns5-1~B#X?q?^ZM@4wkA$x3D`AP zU$?|*Qu%{Z1xT$vnmdACq@Y|c_bI=k7Xi=q8Y@^^u0T=i3ev|<3 zb33^Lz)O^dbZp%>IH5NW54sw%iFRBIU};qjO}F20!#;g=LR6Nu$jh#~nInnTg1wD@ z~HmB@^Aizho`6jQk-r(f4;F^LcghkJ16kl1&yg7ZmN zfr~=8p48?@S^$WUihK{?K_PZGqXXjDk^F}sjMuqKC;8Jkv^V*+zt0GcQI1@;3d?XP z82`$xw}@m9U^%Ty;L}%snX4NUr?Lf1U!^Y2Ln4JAyMPKn!QCJ-G+Ab?d26~M=J;i7xOY5Gf7Q~)Nzx$clS7jTF5w+Yyw*<34>pwIV@Zg#=n zInB^MW-oA@myQ4bORu~_xqi)6PJLHi61a&{*#U+RDd`M|7E=p9pddgZYo8}KY2g_v<)Q%pN4p|g?T=rBn(GNwHgT>D*>GdHoP-VV0kBs7 zn^)F4DaGKG0;rVIiFo^9JacYch4&R~!^OmbGE&(Ac4LY58~loMDV0q81txXAuurZ_ z$$j!yZ(KLZ_`2_G7!=9BbSFrLyyewe#3=>|fZCZq0B?M69){({O~E`=c%7eJ4d+5| zTevD5Ii6ES99Sp)fBI$HSFPl=Klr`&8{=tpIFn3$+TwEmQy#;z zoK&`el>)=4bTD-3HD9~&+cF%uJuX{qz}*uw*}dX~A{Dy=oC5k+UxIqmM75meAjBDE zbUPzZMipOo>u@UnI=rT`=$iO{0XUpE#9$h=I8Zv#hx!JF&jU!YwCZ+QmHapoB=-91 zgy&nORCa*fcHMQqNjg0j8ox;6mx!=%`E}P#*EfYEWHXv-hB9VHX~0SV3uo9H(OkqB zHl^c8pvDVXxGt_EuiwTo(8DkCvYVCqKW+T~r1ab{6lW((s^r#d4V9crjwKskFBFS~ zQ7gRtrkg9V&sb|LSCt)L&~3MBE+)U&1pnmMe0j~7-!cG0GW-48ZyR9=#xFh+4Bf6+ zt?#bzxSF*+nj#rBpZ8#ia3|$pP)b1}C1Jt;gZBtnpbDQ^ybj056JEQgCrd6nhhaO1 zWjC5NwAZ9KN0aNN^ubHNIR3@UL}dxszoTNfpjvV;F?%2@Y&^L7VNh@Y*NqQ-V!}-r`Nr-cyXhMU- z?cf|t?}%R5Vi68iN>6P~(e9p%IDc-Vg^p^rP^~+$?((&3kJC@d>y|HFd#>4}tj402n}qaD2sVD=la=i(G8HMpId-(KpLllLoNQU3PV! z%kQtgW$jgCe(-NpXuup!d+v}f1{eT=1gg)*HNFt<^QrkIgmr+Hh-(qckr-__(xsr{ z>q<$$>iAsQ?K9a`mPvl`y?(Xso7IXcf3+zYJ4)YmKmo}2Tz-R!j`QnpJh(e<&~S^| zoZn@kh8hiq-AYD)1-FWYEwooji6!8wt6&(o2;jVhR{4Y@D~gnKGYHp3m_%-dysOSX zCF)sYsw^A~F#PfdJ~3>5#wlr9_sDhmUoYTt9i604pY5V6)*Rf;47j^yYcRzQktZ1pcv}IPKY|i2EunY)%<{tiln~9aQ15k=3Dv#dJag=pUQ)`_ouB zUus^!AwXKvVyA#prLW&scps>QytvQ0Vu&5Bf}QQKgZ&4EeSwfqg2*f1zfHC_qF=MmpVxg5UEZ1vH7w-jcJL96^t-&HBaHMX7;d+7_u)Y7+ZxcZ<&25xuzM@N|U zhP2z*Fbt>OXBVSk=ef8>#($@|v5}`(H?q`#G(4`>D$mDh9IL zG@%am{`mnqf2MZ?T+nsD*1`;&&F8g^7+`oH6^D{=CQBieYi+7ZJ<&7`rLr{Ehzp}q ziPY`J(G|GSw$W}CMcjxHAN|ppstQdNuBxPPFTho2(ysGHn-*r-((BGW%1V0{2gUZC z-%{zyE6Q`q0I3ND?2q5NPWk@VzBG!SQ&(H)rG_>da)-r;PtQ1`42p#>5dc(iaxX`v z0x}F&#vnfo%|UleslH32Y;O0@Fzm)tohG}(Np|b7engYiG`bO84orkRDUZn25NeUr z$tla0YBGK|oDOgRi1 zK*GIHd#x}lgmGN|sfJOs1Fo#GQ*L%lKz8!Gb9DXnnfwTpa#Ps>_LH7;@`>(*2y3U# zsi0P;goH-*n#jWZ&AbWWjQt;VRC?z{;;8c(kP6+@c6tJVjayKfD8pSMI`=c zk@tD*RE>dSe0{(L^$lvdQI*O$7rR%z!$hWZ^ug}VE-j}0hg6r1p0~2aPhC~R^y_D| ze*CBSGb=}xWlKI9NHK)CkrQ%?ghzE?A4q79fJ?0(O4FvnxVZ5GH{YV^0$~!M0YKam zhW0TE4~qmGuf@n%ug~vPE#`sFXqKMqON!S)j;vDvwzOFTHJ8j%)WkJh<}?#l=%=s6 zMJS!+_S3N~CR)l)XEx=k-i)ElSFOE8{q?2LP4XzVEDKDgl)IUL}E&|$Z*@* z#)NG}eNE**ofgY!)<9(paJ6xW#@#qaJw8JtKfVM zFr%Q|4#TF?vFN%tYe)@xH5Q=Bg~9sL(_M5#z$$)_<)%r~+5PWGhDtQDt{+YbP%v{1 zpx*P-_=k%*lNN=Y7Fgdir&1uMxNOzZpX95o6SFqX``Q%^V2u@2V^v>^~(m zZ{UNMH^)l2Aj~k>rL{ybCVI zhDWJHR{TFm{NY|`+|R~C{rc%|iqNHm|7WJ3DF$P;kVbks_90WN4JrgpL@RL===Q?rLM9&L(|x@d?z zaqEbuaL6e#C0u59un>pIO`MRS0oTs8O5FYiSBa5r)X3RGQBxi;B*SPhokLmSR5eO2 zKV2nqFypk8xZ@h2&2Ki5+2!Y2s6sU{n{X}^2wg^UepEmPy~tqgH~~;M|(C$$l?;o`0MK0n#wvP0D~|SAet56V&cID4B4osnJEe-RaGoeq@yiwQs{(M zvnU9mqr@T7s!xFq1;fc}_0>aOhYVoQFTS!yfs4&#$Q*9X!MeljIz=ufRSX&ghcl`t zl*vE96v;m{7z}++tQu`>|`bY}QYj!XrCjgY$>8l=$#G6sDt{<9GI^+OD$8)pUL{$Nsc>dzF5>to+ zG&DgNCAYDZ1?o>_#3TFC!zM8Z5i{}}NEj3epc2VMrQhX#Wtz|WH1^9`bEc9GIl!QK z4ig>2VRB)Gm0GKuWa-imumG2o5h_SN7f1^G(`}~w6V=kmoS64(tZ*koqOYdD5pY@E zu2+V#Dn&9juis_ehq=G>Q>Tokd`o^Q;a0IOt7}DPN)^Lw24X6y0sY`OlI>q<4vSJR zJf-DtL0^_mT4<(Y>s6|@s#V3KC@=&FO9@eoO#6bVteEK#4Z#?igc%a2%I#`Whopm{ z3kMz4a+pn(ODQ?VUJA!hj9^(AsA5I;Cn*;ta+)%G0hF--EEHQhb`;>+6*AGbK@060 zHq#J;u21YU<#b2_2Gy`zRFdXM75oKbE_*x!s5(`EP2mavicu0WBW&(rh5WqT6U-6j zw!kclZo6ebO~kGSR2Y73H=FlmEv9`vX7|H@Yxhuwwxv~3FfQ%io!q$Gb;tq+$*D3S zK!(QH3ZF_1&U79!tKRh^@~aXy(^OR$s*D?KapyLsc)~Cmo7Z=_$!^&fvAHfpu7ytb z%|VNRtJ`D}6$BLACx8ipr=kv7z|bL$8lRj&8p7Z~xEjXN&vd6m=qv`S7_yo<4vGd; z0ort4?=|PBo3GP_A=`s#db(So=ljg`_nrhj+GnDTkt}V@eJ0frbdU8_N8ixgbU%x>ivQYad=P};3QHDJ$((XkNVLdQEEFe+z2oU9X^ zjHg2uFwD7%Da!?1eOcNbOVakvjA%CKU{Da^AaO$n!z4gsn8aN~h5}W23$+D6CP8a; zq(m1Ix~0(fiB5KvlRvEEf$t05R&h-kcoEiF*L?PfJq-;SW@Bqq*Dd0Sm>lx)2y_z)Dzybja&2n-G z<_qE(kco~C%T#nP=#T|0l2xfEmlgLq#+^>l5-$bFkeKh_f}22gBzRwf!yNBNuTgP< z#AA*d*wwgTX=ZowI2D>5P-q@6Ynanj6=s^na4ln4PjFjkkwc*y*}3rgspwwNAqyD7 zBf4ou$geF>vxA1YmKo$ke{(b~1``6by$sp_@-+4vEFnM!cSAw@YbmE|z_gj1Z=nwq z4$V z_VfN%b=Zt<2FS3lSa39eR>zmbvy8-Zho&%G&>;z!@8u`eNRpk1b1k$@1@&1Btz1h4 zRFGUkSRdf!Zgw+(*2bZ7dn%VNuf__lb|3*RbW^prwHx{ormhYdz^4E6VfBj(?dlbE z`_*MNCQ(b=@63#7^pr|+ZX}m-T?(KX3k2ZcY7urdS>SNR1^Xd9GN$F#yE2QqwyNQu z-6e|kxxZ=$r2I zbG6P!VSP+kHOIVSz{b-;eLga{Tq&JwGtn}aLSI|jLYc7kobaQMT|lRwv5Z=dTuRBN z6X{(?EhnGLN$U9@KKTE*j5Xx}gF76*a0WfId5^w_=*aWVC)@Qu6t|kSnM`C4bJ!YU zz?#w&_FKt|7I%iMcqxVL14+J43auFD`pq<#cik|1^mJzyZv}ne!WFdoT^G`8?L&0K zUw2Ijz@~hNJng)*DV84Dg>&3D?iIB|+-hbN;1Ww6rc!W88PAczx-6x(WlIQPDi$2g z;k44=z8&^P$J2D`hu31znIC?shtN<4kVl-OR;7> zbSL>9TB>hxs;9#>V{Rj@IO{Yz{(>_p(zl;Nfh81gjNd8_P6176z)r5TEYMf!-DfYO zD!-GSeem0&_9r384aVr0?_NTeq@Sl*k6LKfdlu8l?>&Z&{Ebx`srv32)NslyT6oUF z5#SWVgr(8(jK|*hyF00N_mgz?Sqmxr%!SlBJwtzA{u7$JWG=ZYYuLS_qNL(6+P}Y> zERBQo-cLl;zQL^{2cJzsO*z2)J2(Fynj_Bd->ha$h5=qIKoDwi zbH~#szWqORjCntuI=h|*8@uWBm9uH(ikWor2Xb`Oi>v9JlNQjD1@&~y!Upl0nrHr! z2S>^X83Z_urJ|ymtejt2tv2d=_9+T4%8G|!rQ;EXDo&BX0RdNkM+Yr%_mIPpqvtoY z(UGT;a_e^j>Iw5gYBaQe$Zd{1rlbeAVI9lq+3=O(KP+QSsvHd6DnEJf+RQW0Y2)%v zZ<(9VRnVq%Lam3M+ptL-gUl8yEt+3LZuiL6`i{;4a=Pr)HyEPkI_*}(1Xl!R-S$_y z@8o%O(<6VR&zyWJoqFs{iX^o5`S~*&#p|H^zy7#bmJ)!%2v^y+iLv_2>vMF>#)~PP zPSM&OT(?;BaO@J>I?h( zauP0Mtr6fDH830zaCLNOU*^s|1Nn32M0U`7AKywFU9-iugiETau}YLn)}BR2{pMR@ zA1VoubSuEsb7V%i6bjaZ*1|+~*3aA*z{rZ8?_Ja!k0l8uG%!G2?F^N!FIYxDwQzsG zg?ffe6dbhB#y!u`3!9%4%faw49kqIh;B@+_Wu-|2*tdQ(o3^aS)BLs1(Pqo$i^pF< zkyX=8zgh6}J0qO@Yk)Yc7JBiHtHcCyzrXw< z-X7WSBSM^Bpd(-WqX?NFUwb;8`Q;Ch>4EuFF)J>d3j+)RQIJ2~$|{92)mrT_%=zAD zW?JaV8jDzuhP3z9-7!F0Hnx-7=MV+J9mgTNl-h?oXiuk!;_)m!_M}4JzVk7$|Mu@a zNoRg=uerkHkcEb{P$l8ozO9$t zpzvJBFy}yUk@lWJ`p5Ho#pJ!i&x={jM+nUbvQz~;!Ua*F>E?bqarq4DSTmoN&LRQU z=}r?l^v8g_XTAm)LM(178D>MndN>rN;eimnwyBL)p14Sy1L3->HjQj63%Ky?%NmNt z3X#xJWhIYC8;GW62PvHHBjjdcN13QB00Th!>L}qD+{?*nl+BT)mB%>g9cNYKA&V!q z4;)S=1<3w#qTnzBQwO-lSDWJ1k%(ORWp+C0|(LaXL^vf6a($?LBbjjnd(7I>; zu3v?Lb=dO-E@*TxI37xc-%bB>(L1SmW<7oKW2ex|=T4&srrT+$$3#!^^82m!{Pg1O z?OIqK6!s#j2C&p-K%1 z=~D7bhu~o2sjf=v26{G>qb-pft&3#y6QZEfff=Yb4P^wJP%@_d4X6MV3$@F!LM3$U z!j-i4)(7=1P5btZtT|7#$^tN`rgFZiGT>H!{J?RF`IO)#RIw|tcXyCZTG34H9bNQ} z^M6Zcy!97!!{slFeQ+~C)=--r5i*Mf$X+^O9@VtWq(!S%iS4@SOGPCC5c*uzqF~?q z*1T7gmPHH1Yd*PUtA3S2WkEWalx#3X?gZJ`9e2c4G5WQEVLI7o5@WwAK6@|uD$Mk| z?b^8i8Rxz|581$Af)Wfa!X;{w2-&DvLT3*z5laX)Nw-TC<;0_prz@`erGA9ypMMRI z+hL+@Ti1^p5&qjU&t1M!U!8cloz-7?{Nc{`z5ilg)vK=zaac;zy5~D+^c5dC9_#8S$*1ao5LxJl*-4X7F9c_#%-n( zrnza$K$3FSEIG^`vRhqLH?4(!yy0a!r$(E)%WkodnUnu;FeZTO>K~?4&pT1v9)f=D z$Z(i!E6yX!oMWiTq0l$B=Ll1vtnXE6=P=RYo<}LRbpz#&IF;%bETd{yjP}3w3R$ce zA2uv*rE_^~3|o?7p%|&TG^rfo&#LDiNqjG5D928xkc}Dt&Np8st3A!Bfr&pih2|f@ z>lTF`{HcpR`k6Gnye@J*K2Pz+Cs1YC4u)UgJ1?oC=bqRnwgFl+n&R9eLlIuT;q_~wD$U4TV9~=&733F;aGcibqm*l-gog^#cR=E`t93)M_+h)Gp*}S(`#C{r+9c4 zZh6DtMk(A|iTf(69cgOV^DI5_^+J0o^05VrMe~?YfJ3gYs*3989Z6s7{kd>2w1@rX zmu=MA?4YJ52W{UGB^MrHSgASUFMjN#rs{gpN4CfrxOlJC{g5~?0V*p8gId$k3(mdF z`>yrt_LJLVrP@XZ#l-wu@Cj8o+0n-}vV&ylpU-sA@KBuI{`UFQJDB8nuQ}FGP$5Jg z>1NW&wsI}2ZgV@$nA0G(rOZ~FnA#lW!$-}f`?v2B`<0wYfL2x6Ouz5kN~e1Q)WwOm zgbQkE39tiMMN)p&cm7NdY|qlZpen$^)SG|{H{{m4ax}}Y&`joghEJh-H>U**g|)tw zmc8rablm$cq2R_Bsi&)hIy(nxTBF_oth-sbw}aZ6!eq}z*vU<_a#aPj&h$_vJ8)-v zOoT@(JE6m6p;afF$+W%k`na?DykWC#i@4APsq7pK?sfcGCazU!wZd2=Lp6&3)i%vZguM7otn`r+Gl9oVjd-dqGyGHV52g zPd-bVds8EvY;ac(eI#9&K-$jmpoM?#k`}t>k=?ZVI}d44F*r{=^&5I^{hw&StTm57 z$Q!*;KZV^|ZBnkIh794o<9*{jJNU-{*LM11R9kJOnhGm5wz$de=1K=&-^CICC-=6I z+ijuNM(t_!P8XU+EaY@s#d&|eZ?X1=PoT;QFxeGE6}X*($b#F+dLjZc2p_n%)Iz3M z=ddYt?z?8uaL`H%XM1S#&Pe_XgYc#8?+DYS2! zBV?CYBl+52Ri#5*o*&=t48U)$f9pb9_dbB-J(68yzijjS9nEsxw3OG zHMg5{;JEVSLuU1J*EB1Ccvy3I37N}dp}rpNi=N(7W$F~Hjt|28{>eG(J)LNzH zol&}?okJ}2zrBx$_b7paBvi7$F`DS4m_A1$+$s~BP7nO*Y2jE900UeIjgWM&d%tVV z_e&xgOmrV*S%yd~@xcJL+J!+yhD&JoEich!&wh}8^uvFN?Xe*H>UDpoFW+#IfD8g? z#_%~1(n4n2rY#Ju<_sPVE!K1+SsKiu2?Htx7-WR(_|9i%)(qFspo;bg{q4K2k;81FxBm2P^y<3qLZ~beHsMl$tg~;3ydD?z^(XS673XLT z-Mi_uxJPu^pkY%jiCj_GqHBHuQ=mI?9ostvG9S9NenmyqoW6KUOqX5_i7ddEP z*i7p$D|F#N&2wgu^@6Gq`I*~oqu$IPx`hBTWH{bRGZG3F6*ca8l6QPej`Yw9v@!et_pc!kMfDDqLx%rFu)aO^w z^_1c0WL6lmM(!3(?5EWNpx4*OGgT z>Du_@egS>UY=u^zF++sHBjzrqO7F<_I1Ib#?AP*5xRnfrP_b)eHz^eQ^XP}U_4*pN z;{{BZb)7wYMlwQlj_r{;I1nq(c|3|=r+9tI5rWdH@;>7~8+FCd5 z*&{oM3yy|d>pdTu$%(W=k)b4IOdbIlWCYC=>!K?i^>k;_sZy0knY%y>)!Vl?1zZpS zlg;B`*V?_tFu6YqT~xN?-?n6@ zYI{}RRTe6{hSp-WJ^9#LugyvpGlvVijRG8U1`=rjS(?A&Ex+DI4*p{1tZt%h_YCA> zy}e592~8qgYA&Xb&4w?EYjswWXsCr;4hMMxR{HBBZM5WQzi60M6wYNB3cCt8}3XX?{ETUlD>`sx#n-k83ZLd2^77M?Y;R@7P$X8*aYHykXUWLP>iTpk^ zgo^)~Hf_<|2SA~#r^3&n&t>NS(?V^#qqK43UVa@PvvAn;nfY}JdHwtu@O8Wa&JX$T z>HD)*c>!3}ZYRxanNH7p8b2j=mXXTd!S>#Jr`pILpoPJ(83qA%ghq}UsGZ&PYVBJZWLOqzn_|h1hIX?@eyz3Iqq_+z)vN(!qc#B*WX!ipxmKk;oKLokllEO( zgS7tD$f)o)e4k3wKB29T=TuSv%czf>c@o_a@|9Ko1!;dt8LPUsl3X_aH5ZLAZSuK= zi%IqF>&U|e?Y-k=(PjoG(+Y#f1aK(G4ML zJ-d;kqr!nwhDrv9m+3TzH(tj7)=S>S0ouK7fSPA&ZC?m=CDS7Q!LG`or(`bGVDp&h24Q&|ipfhv_RSs%`mj2Z#{zx*i!MJEs8I zfs6=$NwQMyQ694Jk85yiT~~n z=1*sHIsW31i;Q$J2+mgFDKuuHjSL|b%UFnOl%Xv7>pawZ?+$Ui*wL(Etz8QHHaxdK zUs-_QPO@eQ!O^OGLGs!nqHPXcK!6L74E4nYSSSzvdOy{$Gok+wE;Vs`q%PzO~Ra0dPu5 zf&`a>;80?j(egjYg<1tm-aXY&RY~_hy`EY>`l-V?*uSGhHkbdhq)NFNWOTFH6$K~& z7xdzq=cr*;K;&nT%cqY>HMkn!LS!QGM4SEFt_geyFTtgKVf~FZ>;ab(+u*kWpt1R@R?{>4 z@FLMa{j}BetC&M;XDKsHDhGoO*1=K?2}-fhya883neNdB8A`#?An9aM$jM|Im&sQl zYKynuxL0>Ny}$78UJBpdLw&#N)L*5^u0MJPlIys;sM_nv7o&_Wh0q9RlakKn z4c97XO$xwF?$JYDAUI8NvhQ`-$jrZPvsn=ZkipSP2{lk-p7z=`>qbCk43&TjLRi$` zV)yVdcnnwce|rS@^IPghghXRr1-Nh*xe+)B_ie(d1W>%RT5O`od`rS)1PZS)u6OPm z33WMXQq&?NfBL05+mLKyt}kP#l#n&nXcOJ1IF$4cX^A)B${J?F5#TX^#X{}V*pZHk z1$qOJ>>GOlBzScv|%P1pk zD7A)G)td91CNyOjyPM!z)eWh96Nk-WAwRzcODRA$y}m9FoZJ`i1jyykpqKllj1WQq zPL}>~ggS?~Lc#^>5ZoKvXv#q1@2MI&^rxIODFBNl(p2g3h=QuTEKsqN!Lb0D43S0^ zyV=1Cc^TTyJkDy7bEWRwlK+1tTv(PWZqA{wl;;4hbUHl(G8sn4lFa48(YOE$AsB*N zqxc$IC-l~|@iZv_%SEH&3q_GhFA`x9j`XD3=wbj#_r-1oMUx59f$nZ7+&8&w zwU_D|Y@!Rv?c@KyT015~pp?aICMUlaEK#J4>rg4kBwR9VLI{z;h?Z|bfXwR#tP*Uk z9SuLa>eiNjh@EAqNdXuVZYi1AqJfHDW2jGAhl$V1IvzLD9#pWwO~kWW$kR>gpd71z^QE^ci77UWLC2f17OO zAFBZFpWOK6v-FpiohAif#(p+q4GNM@hHweU#J&=cLE}MIe6CSVbKN80k}`%w2@hn1 z4qyc%5sGqcuWOhS^I&L1=qq+9@wa$AtR%uQ|6d4)#R6P#CKOqNlrq`0_bDN3sICw|R zt|bL<(Om@rGDw1koU0+=ACZ$8g@#$cWt0&zSs*lJh@35+pa|z$s1TqaheO2x4X(y6 zvzMNKmXK2Dn`NQ01gz^XzgL$o*X{~BZPjwIGzyK4vQG|@VN!-AqmcMJ?3x1@9jsV# zEWgpwAaps+ZLSwZsyNS+ki~WA#**LTA(!2rhi@zhk+(^xBtSB>meSLk3sZeoH#C$L zWMv81r!V~sJ^r}XcDSRxlfL-*&x&=aM|XGA`j7?~l6;xy8$%={8zL6Pz+Ztl@d@P+`_F; zWZs6QT!(B!h3>HkR}27|Y18_$fUN8Q!>>=m1r2iU1;~u@l1jV!hXrIt*D3}9p%MaU z0HQeIw_8jH1P!5cQ(G@>X)C;k={1E`e86Rtgawje(^!ncHfow%+xF4Uj{O2G!q}pYhB@xxDWEt&26G=Z|@ezr2v;4R%{$$A)JgsgNq?_qLH-}WY~v-(1|6&D{?uM z=6bDm3l-;J5Fjh=Hj+!@d9Ur-BYqU}eF7|*&|@9>nh`job76@l4IFEjsSOo^0yWj5 zK;Spp8AWrW$U*5HxrJ8*)mkc z?r=~z7$htQ!chuyj`EDZ96!eNL&)(lCO&O|18c^y{U#GT8NcqW$1SB$NCO<>?z0RGWai^?nbj)| z5bhZO9B|HPJjr17;d)3=!^U^k*25nqM{5(5?saCYG!h{O|dXdfl;z{3HO4et2sGoM}YvX zEK+bXWB7#NT3DBzYzf-$?46w=2*{E}c zkD=U2DbU7$tXxGB|2z&CDH$a3X0Kk6ZEpXV6 zkO;}}2pxZfHuIr{kE37?3oGYO^8fE03emQ<4r=QepeC+yiqMJwlfxNqGs7cf5JI2p zi*bl08V*J2xqUsfs3ssB2@n~>ES7TLQOlRof(3Kvm`lD)yDa{@tt(GiK1gQ$u@B{^ zvT-nU*0#)=x!~DnpVjvigv2$&Rd|`e8r5i1sKTdBT9Qac*}dFiDJ2^}rJF(0#Y!pP z>d!D3h5eyeoa{Eca4;!28C2|M;aGx0-{|)XXH&Qq`q<0b%#6};_jYKNhA3YL_eoxK z?8u1aTS66}Qq6AN`~ktM01uL{g;V+LfU^)WepgF*62AG zeqdZTlW@r;@)$rSg>~chu(@=Ld9jMSk>m{5J;S4Ch%T1V#X?~%FGNTLTt}^1CAOjA z;h+FZ3OyC!aHvJ!ob)Ptz@%i@G?u%beBQP4)YC6qvSP*E(&+#hTnfu#u%JLz3w6c0 z)mQ>#GGs#X&ZFWy%$V#O6MnDZam>(+=E5y;fUA^xLc?V_I`!YtqyemyzH;j=afFiI ziiS@Zoi1w#Jw{tv)@(fol5zeDdD903xTJ$Y5-#Cp>~tI2+r@TQ z?*OUZ%29Cb@9d(-|MpkfY;r0n_@U^BCcUN{U=SVvg%B!3CYC`}`_ABE$DVioh1gzS zVtRpt5yGT#h)l7ANqH+Osd0KE-E!|A=x2X>it1;LoFnd@`|hU~6t@y=nkhoQzI#e( zQcU0UMuZ-M@t)sSqtam~pVys-3*i!QVPwOln}W(= zxC%N9*cbw6c=jSdLukZ<9poe|vKEOP3t*jc`7QL(=R1{hoTq-Gbr`S%2%yz}=o8AZ z?|=WAFew5w*%o%*P2U$_;T&zxVHImqOk-XS3YzTytXsPLD~lH{*tmYfhIe%ZnlH2; vz3Sci;lmn3hm-undQ){cL>|_gD5C!lpca%3*bVVx00000NkvXXu0mjf*{8aA literal 0 HcmV?d00001