From 934a430e89b7c75ab07304a213fe6a49fbe8f62b Mon Sep 17 00:00:00 2001 From: KillzXGaming Date: Sun, 28 May 2023 15:06:34 -0400 Subject: [PATCH] Add support for loading decompressed TOTK bfres files --- BrawlboxHelper/app.config | 4 + .../FileFormats/BFRES/BFRES.cs | 4 + .../FileFormats/BFRES/BfresSwitch.cs | 29 +++++- .../FileFormats/MeshCodec/MeshCodec.cs | 89 ++++++++++++++++++ .../Compression/Formats/Zstb.cs | 21 ++++- Switch_Toolbox_Library/Config.cs | 4 + Switch_Toolbox_Library/Runtime.cs | 1 + Switch_Toolbox_Library/Toolbox_Library.csproj | 4 +- Switch_Toolbox_Library/app.config | 26 ++--- Switch_Toolbox_Library/packages.config | 2 +- Toolbox/App.config | 22 ++--- ...System.Runtime.CompilerServices.Unsafe.dll | Bin 22160 -> 0 bytes Toolbox/Toolbox.csproj | 3 - 13 files changed, 179 insertions(+), 30 deletions(-) create mode 100644 File_Format_Library/FileFormats/MeshCodec/MeshCodec.cs delete mode 100644 Toolbox/System.Runtime.CompilerServices.Unsafe.dll diff --git a/BrawlboxHelper/app.config b/BrawlboxHelper/app.config index f6e7dca8..996f4bcc 100644 --- a/BrawlboxHelper/app.config +++ b/BrawlboxHelper/app.config @@ -6,6 +6,10 @@ + + + + \ No newline at end of file diff --git a/File_Format_Library/FileFormats/BFRES/BFRES.cs b/File_Format_Library/FileFormats/BFRES/BFRES.cs index 19e21f60..b200e416 100644 --- a/File_Format_Library/FileFormats/BFRES/BFRES.cs +++ b/File_Format_Library/FileFormats/BFRES/BFRES.cs @@ -802,6 +802,9 @@ namespace FirstPlugin BFRESRender.ModelTransform = MarioCostumeEditor.SetTransform(FileName); BFRESRender.ResFileNode = this; + if (this.FileName.Contains("bfres.mc")) + MeshCodec.Prepare(); + if (IsWiiU) { LoadFile(new Syroot.NintenTools.Bfres.ResFile(stream)); @@ -835,6 +838,7 @@ namespace FirstPlugin } } } + public void Unload() { BFRESRender.Destroy(); diff --git a/File_Format_Library/FileFormats/BFRES/BfresSwitch.cs b/File_Format_Library/FileFormats/BFRES/BfresSwitch.cs index 1f07e891..75f20c25 100644 --- a/File_Format_Library/FileFormats/BFRES/BfresSwitch.cs +++ b/File_Format_Library/FileFormats/BFRES/BfresSwitch.cs @@ -266,7 +266,9 @@ namespace FirstPlugin Syroot.Maths.Vector4F[] vec4t0 = new Syroot.Maths.Vector4F[0]; Syroot.Maths.Vector4F[] vec4b0 = new Syroot.Maths.Vector4F[0]; Syroot.Maths.Vector4F[] vec4w0 = new Syroot.Maths.Vector4F[0]; + Syroot.Maths.Vector4F[] vec4w1 = new Syroot.Maths.Vector4F[0]; Syroot.Maths.Vector4F[] vec4i0 = new Syroot.Maths.Vector4F[0]; + Syroot.Maths.Vector4F[] vec4i1 = new Syroot.Maths.Vector4F[0]; //For shape morphing Syroot.Maths.Vector4F[] vec4Positions1 = new Syroot.Maths.Vector4F[0]; @@ -298,6 +300,10 @@ namespace FirstPlugin vec4w0 = AttributeData(att, helper, "_w0"); if (att.Name == "_i0") vec4i0 = AttributeData(att, helper, "_i0"); + if (att.Name == "_w1") + vec4w1 = AttributeData(att, helper, "_w1"); + if (att.Name == "_i1") + vec4i1 = AttributeData(att, helper, "_i1"); if (att.Name == "_p1") vec4Positions1 = AttributeData(att, helper, "_p1"); @@ -335,6 +341,17 @@ namespace FirstPlugin if (fshp.VertexSkinCount > 3) v.boneWeights.Add(vec4w0[i].W); } + if (vec4w1.Length > 0) + { + if (fshp.VertexSkinCount > 4) + v.boneWeights.Add(vec4w1[i].X); + if (fshp.VertexSkinCount > 5) + v.boneWeights.Add(vec4w1[i].Y); + if (fshp.VertexSkinCount > 6) + v.boneWeights.Add(vec4w1[i].Z); + if (fshp.VertexSkinCount > 7) + v.boneWeights.Add(vec4w1[i].W); + } if (vec4i0.Length > 0) { if (fshp.VertexSkinCount > 0) @@ -346,7 +363,17 @@ namespace FirstPlugin if (fshp.VertexSkinCount > 3) v.boneIds.Add((int)vec4i0[i].W); } - + if (vec4i1.Length > 0) + { + if (fshp.VertexSkinCount > 4) + v.boneIds.Add((int)vec4i1[i].X); + if (fshp.VertexSkinCount > 5) + v.boneIds.Add((int)vec4i1[i].Y); + if (fshp.VertexSkinCount > 6) + v.boneIds.Add((int)vec4i1[i].Z); + if (fshp.VertexSkinCount > 7) + v.boneIds.Add((int)vec4i1[i].W); + } if (vec4t0.Length > 0) v.tan = new Vector4(vec4t0[i].X, vec4t0[i].Y, vec4t0[i].Z, vec4t0[i].W); if (vec4b0.Length > 0) diff --git a/File_Format_Library/FileFormats/MeshCodec/MeshCodec.cs b/File_Format_Library/FileFormats/MeshCodec/MeshCodec.cs new file mode 100644 index 00000000..48f0914c --- /dev/null +++ b/File_Format_Library/FileFormats/MeshCodec/MeshCodec.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using Syroot.NintenTools.NSW.Bfres; +using Toolbox.Library; +using ZstdSharp.Unsafe; + +namespace FirstPlugin +{ + internal class MeshCodec + { + static ResFile ExternalStringBinary; + + public static void Prepare() + { + //Check if a valid directory exists + string path = Path.Combine(Runtime.TotkGamePath, "Shader", "ExternalBinaryString.bfres.mc"); + if (!File.Exists(path)) + { + MessageBox.Show("A game dump of TOTK is required to load this file. Please select the romfs folder path."); + + FolderSelectDialog dlg = new FolderSelectDialog(); + if (dlg.ShowDialog() == DialogResult.OK) + { + Runtime.TotkGamePath = dlg.SelectedPath; + path = Path.Combine(Runtime.TotkGamePath, "Shader", "ExternalBinaryString.bfres.mc"); + Toolbox.Library.Config.Save(); + } + } + + if (!File.Exists(path)) + { + MessageBox.Show($"Given folder was not valid! Expecting file {path}"); + return; + } + + LoadExternalStrings(); + } + + static void LoadExternalStrings() + { + if (ExternalStringBinary != null) + return; + + string path = Path.Combine(Runtime.TotkGamePath, "Shader", "ExternalBinaryString.bfres.mc"); + byte[] data = DecompressMeshCodec(path); + //Load string table into memory + //Strings are stored in a static list which will be used for opened bfres + ExternalStringBinary = new ResFile(new MemoryStream(data)); + } + + static byte[] DecompressMeshCodec(string file) + { + using (var fs = File.OpenRead(file)) + using (var reader = new BinaryReader(fs)) + { + reader.ReadUInt32(); //Magic + reader.ReadUInt32(); //Version 1.1.0.0 + var flags = reader.ReadInt32(); + var decompressed_size = (flags >> 5) << (flags & 0xf); + + reader.BaseStream.Seek(0xC, SeekOrigin.Begin); + byte[] src = reader.ReadBytes((int)reader.BaseStream.Length - 0xC); + return Decompress(src, (uint)decompressed_size); + } + } + + static unsafe byte[] Decompress(byte[] src, uint decompressed_size) + { + var dctx = Methods.ZSTD_createDCtx(); + Methods.ZSTD_DCtx_setFormat(dctx, ZSTD_format_e.ZSTD_f_zstd1_magicless); + var uncompressed = new byte[decompressed_size]; + fixed (byte* srcPtr = src) + fixed (byte* uncompressedPtr = uncompressed) + { + var decompressedLength = Methods.ZSTD_decompressDCtx(dctx, uncompressedPtr, (uint)uncompressed.Length, srcPtr, (uint)src.Length); + + byte[] arr = new byte[(uint)decompressed_size]; + Marshal.Copy((IntPtr)uncompressedPtr, arr, 0, arr.Length); + return arr; + } + } + } +} diff --git a/Switch_Toolbox_Library/Compression/Formats/Zstb.cs b/Switch_Toolbox_Library/Compression/Formats/Zstb.cs index 74c8a477..069d0dbb 100644 --- a/Switch_Toolbox_Library/Compression/Formats/Zstb.cs +++ b/Switch_Toolbox_Library/Compression/Formats/Zstb.cs @@ -82,7 +82,26 @@ namespace Toolbox.Library { byte[] dictionary = new byte[0]; - string folder = Path.Combine(Runtime.ExecutableDir, "Lib", "ZstdDictionaries"); + var userDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "SwitchToolbox"); + if (!Directory.Exists(userDir)) + Directory.CreateDirectory(userDir); + + string folder = Path.Combine(userDir, "TOTK", "ZstdDictionaries"); + + //Check if old directory exists and move it + string folderOld = Path.Combine(Runtime.ExecutableDir, "Lib", "ZstdDictionaries"); + if (Directory.Exists(folderOld)) + { + //Create folder for TOTK contents if it does not exist + if (!Directory.Exists(Path.Combine(userDir, "TOTK"))) + Directory.CreateDirectory(Path.Combine(userDir, "TOTK")); + //Remove previous folder with any old files incase it gets updated with additional content + if (Directory.Exists(folder)) + Directory.Delete(folder, true); + //Move old to new directory + Directory.Move(folderOld, folder); + } + if (Directory.Exists(folder)) { void CheckZDic(string fileName, string expectedExtension) diff --git a/Switch_Toolbox_Library/Config.cs b/Switch_Toolbox_Library/Config.cs index 873ecd70..63e6da8b 100644 --- a/Switch_Toolbox_Library/Config.cs +++ b/Switch_Toolbox_Library/Config.cs @@ -181,6 +181,9 @@ namespace Toolbox.Library case "BotwGamePath": Runtime.BotwGamePath = node.InnerText; break; + case "TotkGamePath": + Runtime.TotkGamePath = node.InnerText; + break; case "SpecularCubeMapPath": Runtime.PBR.SpecularCubeMapPath = node.InnerText; break; @@ -511,6 +514,7 @@ namespace Toolbox.Library PathsNode.AppendChild(createNode(doc, "Mk8dGamePath", Runtime.Mk8dGamePath.ToString())); PathsNode.AppendChild(createNode(doc, "TpGamePath", Runtime.TpGamePath.ToString())); PathsNode.AppendChild(createNode(doc, "BotwGamePath", Runtime.BotwGamePath.ToString())); + PathsNode.AppendChild(createNode(doc, "TotkGamePath", Runtime.TotkGamePath.ToString())); PathsNode.AppendChild(createNode(doc, "SpecularCubeMapPath", Runtime.PBR.SpecularCubeMapPath.ToString())); PathsNode.AppendChild(createNode(doc, "DiffuseCubeMapPath", Runtime.PBR.DiffuseCubeMapPath.ToString())); PathsNode.AppendChild(createNode(doc, "PkSwShGamePath", Runtime.PkSwShGamePath.ToString())); diff --git a/Switch_Toolbox_Library/Runtime.cs b/Switch_Toolbox_Library/Runtime.cs index 03edf497..f6eda5b5 100644 --- a/Switch_Toolbox_Library/Runtime.cs +++ b/Switch_Toolbox_Library/Runtime.cs @@ -39,6 +39,7 @@ namespace Toolbox.Library public static string SmoGamePath = ""; public static string TpGamePath = ""; public static string BotwGamePath = ""; + public static string TotkGamePath = ""; public static bool ShowCloseDialog = true; diff --git a/Switch_Toolbox_Library/Toolbox_Library.csproj b/Switch_Toolbox_Library/Toolbox_Library.csproj index 93c4a470..11825169 100644 --- a/Switch_Toolbox_Library/Toolbox_Library.csproj +++ b/Switch_Toolbox_Library/Toolbox_Library.csproj @@ -162,8 +162,8 @@ - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll diff --git a/Switch_Toolbox_Library/app.config b/Switch_Toolbox_Library/app.config index 32c837a6..ccfe1f06 100644 --- a/Switch_Toolbox_Library/app.config +++ b/Switch_Toolbox_Library/app.config @@ -1,24 +1,28 @@ - + - + - - + + - - + + - - + + - - + + + + + + - + diff --git a/Switch_Toolbox_Library/packages.config b/Switch_Toolbox_Library/packages.config index 36dd0209..b4f74633 100644 --- a/Switch_Toolbox_Library/packages.config +++ b/Switch_Toolbox_Library/packages.config @@ -14,7 +14,7 @@ - + \ No newline at end of file diff --git a/Toolbox/App.config b/Toolbox/App.config index 6e97d895..5e55850d 100644 --- a/Toolbox/App.config +++ b/Toolbox/App.config @@ -1,27 +1,27 @@ - + - + - - + + - - + + - - + + - - + + - + diff --git a/Toolbox/System.Runtime.CompilerServices.Unsafe.dll b/Toolbox/System.Runtime.CompilerServices.Unsafe.dll deleted file mode 100644 index 63403d72f7196cfd9b587d2c1b9800d5d0ae6558..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22160 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%0A56G~s_?EA@|o_YHn zV&m9i&L!Y8tbDNLZuzBvI(OS!O_g!a@Y!i2P&s+|_p3%FUXEM$p>ajtt}Dbb=?sZF zYpdTX>?!+jDEPw8T8(1yKz*~G6_Az=+Vn!eB^+*|B_Bp2(Qxv3{K6bg^O=_VtXzpHg-#KS5<7VF@fh%$LX` zq4w>WgINUgh{1I9bdCI(X7w5l*a;~winW2)z?KE(1*WOS8_DE|Rm#eKq)?@>1+7BK zj~J#L5fX$@mBDoya)63dUYtgREvZ}ym{JL3fnDht!J?$2V?d#>*02?9v4F$qR2^Ca zF)=Z}YJfoci<-$WH`3pgScC;>O<)+8vI|&`fL&1FBB!-;bFc?}ntP;$diw4xpZ3=r zIy*}4X_{@HE?e$BvW7PeB#$MT$=oIbuDwU2z>A7pw&l5XOhSszGFFmi!* zeX@0!xX;!Rb$O)aSg3yTrc*NQFSZz_rFT7vICXyFn)BKmvAqVVUTV`usjdC0lIMEA zZX?g4o=X@-zLFa|eXQ`}vZ%GhyLJZr(vq&514}1zyyFKp$n@ql#I)0UUQ=W$>9Le) zt$15|;ATCLU$;H;; zb!#WkT_IH5PooCM{UfqM0v4NJ^gpypprZN{aE{;z3S{G=9Mgog@OTv%kH@Re%dsCQ zoRs{J^oLHfqRjkU3}cUTa}nBNHG|0tMF$j(0Eygj#$`DiYLaY%o8reX5bx3#mL zdz_l2?^(-dy?>D;)RrnA*BZ60HZWN#wZ2nXNbi>G$#i1owEIliO~JeC0S(1O1i{IkN_naBaw}0g+r&+o@&Q(!PU18Q*=O&bGMy=tbYn&Jj^| zfemzbiYu=|2b%=#3w>5|La6H0cG*t`pE;ftbGV%HV>EGcU;}bM3^>47ien>nD>X76g+I7^(HYTBw*M((2orZF`0Mwm>KE zL_$^%KZa?Yyqg3&Ad7n$7Kow}bJ!IB)BLExD#&!vam%ZyXluc$@^~NL0BwSLfTAD4 zA68UTA!sQ2XsG)u`fIDI!`kZF0R))v2MO4=Y_PTC9G@$yMN>(YkMELo9x82LXoP8&kTh>pnW`Y`xoUTy*KJx8xVz9c9+AVI@ z^BeqiB186kBt8tfdT%wy;Ym7@j4VfA{k<^bpf2v;D=NXOmJD4#`K|Tqg6Re^;-N z^r`GUdDnXxRy_RZzr0$$>l0&qtbGc-ijcN_@!j1KuNWV1CR*#~4Mv5%Y2$Vgl6BMa zx@vmrcwXzI?9sbg_b=V1yR0yMePzQ&wr-BiMgp0)((Bzr_Oq;=dUA0f{cMe@OeDrL zq1cR)UvWVrCVB@U2_fy(gQt0p5`iN_@9g}Mv#&elqt<;2*=(>81Nme$9u*>u>P;raKK-AAI* z?w^b_NqCa<;#2)cjgl5m)0b5hx;-)*vjxgZNs-Ru%QN15SI;07bVhrL#F!?hXz{g2 zdeB`Cbl6*ZHc0vT3YOV}WZB+mWv4D4*zH@#Uf=I+Ena4L`%BW?Zw%o3Bw0=w@mrd< zx&S!H37vovF{OI1cW$Dg3ji;HsFb@|**ajfN++hwxGH|0%b67eU1A-;u>z9%Q*-eBw zuvl<#NQJrRnP}${UKEy&S~1Xn7inljW&8dBZ~IVcSLW*+@6{*p#N#@3PZi-6i%{*~M_a<7>kGu_^l!1nX9%n}CTv%t8(FjfEW z%d$m&Cjr*>1Ds8jVonQE3rH~w*bFvVz=0Ig`g>Ar-Y|tM7-YSGTK_D+z-@|~wqAmi zS=D=@LAA>H;P>px;U_G=zW0h6v0knC#JGZGwrxleUo3HFi+xU_*!t7D%GQ^QPB`cH zMO>*p_bu+cCF!f)JHv$5*R0Ef+Dh|ric>81*PZVw_E~k*MGTx^E5ejI_tfrMkQ8k@f=e#jTi4?gKiqN6n36t)g>mmaXaE!dGx%9Ps_c39w> z(3oRq;2rTfPvN^od+Mdn#`!s$6s^5GIaKWG+;tdjYNG5l@x1a8MJ0TuB$NBi`=EjH zB898BLUFsfU_i}DAeMn*7V;9EGZL7OQ)*_$u;8SR8<8VZ=%R!f|8G8!^Eimq1?-vI$ zlS?&56qT3XeoNBinjlu|1X3m)&hAy^BZ#o?>f(ImKS8`__UPgAp}6bVa}TE#xy0qR#cg~F_|@3@k(?EB%D?8k#F-CzGTTu zY4?LqnB$f6UiNL$eFAOsHGgm~t)-TKik-B#u~@eXZ4f*cbU3S*^91KPO?$@9W?h&< z&j952=z{#_+o(pB-$MU}{Dw7QHQ=hEqOJ;SBk~(hMS=GUK>xkt{(q9*#|lHMyI)uw zl-m-bwBmJrZ{LmFwG#Ff_qzCPB{@DkD0^UCL56Y5-ZMOQ%H*}o5Hvbind1pdJ%>UD zH`fpEWZ?M9j?Ed{c}KKORdUCXj}w7H3NxDr(uCg)*cKNyN;tOeoie@8)V`sr{frT| zX!3aIp};3HFU=g!q_+3Vm?=r0PPJR-!1@NGFde*qKODZ}lN)?wYFlS^&7fHJwr?HW zpBOJXhB=%w-G9sivN8`?CM_FqBKyrl`XsBO$?av!%z2n7$F`5Gi=9Q~3EMNKK%B7I z$i-I@W>>B&IvuMLi8aK>+&SF6I{8qc5Bj_?TlLJ>!)HjVS065)T0)c;#&cQDqR*}IxOF(w*aL+2`DUfhdYYlEs*B5(2tW92?|@9O6C zTBS!^MSH@0YhBQ7wm5G4+^%?oUY9FXu6aVQg;A-euf%@b-9Ee;^`ZA_Iy2qvy%xP= zj=WuVrw+au*c<#fvGHxjM|$NH%+LWjN%4s3FEejqbCuY>GW15&@!KBR7s5=+tS!{e z4^+Idmi?Wtr#@fKZk&EEgMiB99lTXEURR#Pa{FC`?pz8pcXt!B&%UuIwaeez?oPO& zF2A9;;eFj!7NgCN97$py;MOa#1Wymta%LX(j^{i%pLH$;xYj5t4@{<{-dXEBcqAet z^t871qc3p{C;8)jWk-t+%c{}G2>7<>iiC+$##nACT)A&t(?2=9^?YB+39@>v?Tt+m zTvE|2Iu3g_xtkgD)Ya6Su?f6=%xEq#PAu^#FB~vv#I-@-_EB-McH<%Wp(_&>cN88y zRY|m#lCzNXa)0MMdi+)Hk=9kj`Xp&G{jv|yVhx8WjnYmRs)KcR7DoG=3oqn8-f+@< zjEnevmrCf_+3vNs_eit^)E^Pv!R3$ERjhJLuYDudfBsCX-?>;Px<`ge_NOz>l*XQ_ zDaeWvcy@3Hca*rY$_d8s0*^f_8wy6Zw~BSX7qM%}`(WAg6-6NKWZ8N<==R(2cV*f4 z@UnC4H#|I_+6Wdtol-ukue6RYq=mcqJDwr~tgtfMfUOq>%kAGYOi-=Af3lnWFg_O~ z6(Q;x%}C9J!Lu$gJ!64=_lIek1T4t2u7>@_=-+kIYwK?2B#;2rG@!9Qw^rlonXL*7zs@u|k2kv3dFavK|%}GbpI2-IbN_j@%Yl!jjO+e;MwAMPjUB6H!bBAU`a)IWtF>_=Du~eG#65doMWk__Ehr&I(Pn zZn>%Ew{6+Gkyk0@68D{@FGeLrsBI%@J&Tn1Fxqo0u~jR?_d)r@v@^Ttf#cGe@>QC!E$@V8`*b%|Job4kU2{!_`;x$oi4CC= zp7~rYD>n}l$K&G1(@YgPY;)?R_Z1&Lsv)uYymuJ!xmkr5IlMK5{oQIwp>^i#c0Zftj{9_Sf zQFw|(KcKx{9BbrmN}>ah?g8Gxb09W!juWNY`mHd=d!j%w|tGv6_hfV|(@f%FZhJhfdS#&+l$J_*P_YcvDDQ0%J6}hjXpPqjBiAg>^B5 zdcPan_bvIi-@5mueD2KS5Z!nD&GzMuovp`&QMbNZlyM(a%@(sz`PL*^1f9L=*qR(J z>yb376`uI%$}l&}`vvVf5op(2=1mbRk&C58{3RRRgA8RV_s=RSgwIe{k&?;G?3Vt`^|; z2lI$UxPQMr-9yUB<(it}=fh64Mxir*f7$iRyC`BrB=x=vu=AoJ7U5qn1SrCqim(>d zMB=mX)xbou4t9jsF5tjKvS3U8eFF>kU4PL88E6g*mMAm~{j{7h6reKG^NGH2X|lg9 zVOO*^_Q}Y%?|17TUmKqkbRKcM9b``T_orq?npnQL^W+&y`5X>|doUR-&P zUMF!;S0dIhtn%A9EA4?>?n) zmaC6dir-F`Jf5_>TUzK%fbdQ|r8yUjO=Ea+fy(=HHOltR>*=d`_uLltyBNvZ*BLLx zA(v%Zrjo3erRQQ9Be8R~n)BAKen#H4H{{*%9@@cKCrZ*nvSf(k*DK%En=ZfOYnyc5 zNx*zxPHEV+aOs=hq(yI!;L2I5$M3P^W%LFg2}&;1coK%2O?f=mT$`=IG^@+gn8Q-m>z620y9FPiMPD`44ub(`y#TY)=#UL4K2etSlv@luKrvZsY!p<`TmMWS3YO7?hyKz7>u&JAeay~B8FIVo?-OxU)W^zEd?f`2vMQ`-_Q^i6odY{GQXcziErU<4m z!~+Ys?}GJ&eSZg4_G5lx7C0&G9}%v=!aXniWq%0&-Q%I@eY4}MBr;Xa>j~86Uju{Ecg$%jd zpRA?V62MwsPH1Ko>Cz0j$@P?FOsgrE{>*Ld$0#1W(Zii=asE5DWq3=5Rvop>AKJK~ zqx-PqS!RXis%Pc$mGMkfPqN%vy#?ro0-^_19Hh9E2Tn2C->)&bxc=#NB}~+*ji1`W zKCSXBRq&Z!^`7;EJMFd$d@NLUd$WJFVx_9ujlQxfZj;tB5lr7_yIbOCk zF!Aj!-&n7+%G;l~j8x}SL1wuZ+!`cgq0LST2>ft&LV380R(8(Y@47|W)98$sw|0GbUyKmsf z)Y6L^j3t3E#G$6nqP#9r+-yu~i_;T1u7IQvVXXVz1w zv(T*G?NfFs(;5rT+(glKNcKkc;Zl9m7!mcG9YsaQ;^W1qEHXt;O`A(3em?rGA>`tk z?B4fLu>!;IG;`zl*UWX+NNkLHTQxPa`#nqIyP(xoGw=x3dS7>URG8lZ-3LdUZS5Ko zUBnAxm#K)wj~X&p>rbD!Q?kCXFzv9*CTCj<(`!a8hoe216D>l%#~o{|4+{%!afsw* zi?_dvr|^PvBJW+Q@gVcx-niy%2){T3E;(8jBsBsrMdk$q)Cw#f7`ajZePfpL;&GS> zixLbnqcBFh{gObmTgCd7DP}QdCyw{C({KEc zBd1K?mf}j!o=R7`p~7!DPk&v`306%*rxYJeH9oc7J=KJdZW9$WEM( zOPL``%Jj&VjcXLVZZ!A%G(}(33y}91<8_qA;zbPw1u}*2F>&wP;sd)?!zLW#-f7#N zj9H)9)M_!-7TUMX)jU;Yy9n7Q@PJp6Q}z8r z)(0D}A7B5#hdW&6rod!s;+1Zn8M8)dVaY%)l7QH*sx|&Q?R~4)e~%H>9dn5~UBAo7 z#$fFOj(mE(z=Y2M*DzTo+eX{hd=Kx>Ypp)~{=kJaM)kp1p)*6dcXbAo zO)i&By!5XxK{eu-4#nP`sk_!;B5+H$-O-O>;JpXb&IMZVsn8NVGTtU zRjL~Lf55r!H+2IQw1BFDB7Q$fnQ+oUI4S)PT4)liyQoB>`0&5gLhaXB&a0rt_KwQ_ zga9A#qrJbOgWCT=2X*|rIw-^2w6$!#H+ypQlQv;=j}#Y*5o_Kbzw;S?^*&v1`RtTS z*b2C&=|(_HaQEkdvDo@sCyuV{o2(lNadKglUrRCJn!e##tS3fU2}OmVZ``OCmfgKOgeu*1a#S z@epwhY$7SHbAJR=nw9P*TX*3*_I1QD6)WlQ>#CGzFZAzIz2fVY_rk?sX#8Go!2UgR z!M+JyH>_9=NcCzDudv$WcVx3zSaW`Hky++0FA3 zZ?xYnYaiX3Job9^CZ_K7 z!CPWgZs#jsn&@QPZ+cSLabsOy&?}e52e063-|xJ&nPzKPZ#Q&AWQCS;-F;U&rBSv5 zd-dm|OwvM+YqqJ2FkXJH@#3LZ`YUppCF1b?3|s>=F1ZFcd9gfg?ww0BGj;blxm1E8 z*8u;nNMHKf6%BRO`AdNLpRyq6|H#e%KkidUpC)W}iJhXGD$cvOzbfz2;|t{I18ueU zuE^b!mmT#w7ZQ~dC6ic@lYZ&s*pZG4ht2(SZkD>8JPKrg6)FVqf}!z`8+lyngGDnIxoN zcyDZ*UB`sL)`@5>VIB?P(q}t~tv4KbM!6Mm=*$PqaW^_Xgz4xXFdMq$@T%LhH^0PY zZLo0D&>ffmiyD`gP>IA^+27-UzgMuEvS7taD{N)7(~8 z=cZs|bYR4z239&)c4(h%B5P%N9Ddm0z?MwTs)WXS5-&J%?*unrpHL8u@~0>iJY{G2 zn#WE!;hC9~zs>bS+@}L#6))~8G@MT(&C&}V@rve%R!OUL&WsMc)xZ#H;Gxx&U-(isB zdD7LAq$_Yz?LVS;|EHhu**ZB=f5NAtp`os*W~^$aXsm8xikF9F=k;~*KkDl^N9v!K zz}fhKKTkr!86$rdV(E4Rdv!5@(ptaC{3I$)K^MI2sRbrWbv0sOQ!K zW|$EvhT-e;E?kI0dRd=__R8!Hzh_R8m zI^9S4^zV}Uw4dRhPJC#NaXMZ(B+uc0>v-hjy8MK!HP}_BODQ*1_*bu>?3Lhk=qphxw0XHY&G29|Ir;k6gHji}qz-CT(T{w5 z;im7tbF-?d+(YrvZ4I|rl;m>czeuEPcqVg6#Z^**=$o2%5nq PreserveNewest - - PreserveNewest -