!ua{8*gdia{Pbc|>gmZ+b7gy8^9nR{
zjLppN)SF+s_4x4zJI*pmdcy-!3gQkQWC~z4Fp4*@IMt+m7UPZoJAlI^L}z{cYyH@pA%pnB;uWBxDU27@uwO9l)6QZV!v(W(LS<
zASVmQ0G5E|f_Dgi5EMux^c9JL1YxqEPW1C9A8PmXSLEB(9LXlv;n~G=Ok|YLWZD(t
z$lP($$!xJd(^OwEUe3KnbgoTBSG#Ao;=Av!o~L%0oU+EXydv+W)rB%tKEbp1ajEB6x6@fGC&kanH-
z-MWmkT|E2EszZs%zvpx5GuUHd%GnsitK+Db0YhGUS(Lk<%Uiigpeq&^^uw5bN
zVSwt3G2%;v@uD?3sHxwm{Kg*4Kdw2Q(BQclD2>K);H~vrgeEb6Z-O4_6H6(2-gGh_
zo|M1u?n81+Ohu_XJF$_BHbfkr+%~2~uD4
z;fXTwPF6dkfr8gV=QQxZn)M_0#hni|8CnbaUo`W8AIg*2bN1qEaxV3uXqNFUNV0a%
zuBtaw2Y42FF5!R0rX;Dzdk7y@`jwY6FK%u+GIx)gx%dBNArs{HkvM5#De+t^Rh}oG
zPu5d=R3z0lgNuCuc}=J4BiVEQFsOq7BOP8YC{IE+Ol$<~i>FV?1?Poh3f8bJ*~;#5
z{m37c=3pGOGtmC3>~?0=A;if!R5`K&b`t*K(n9*7lSqimVjQLnHGI=GRA
z8fPd!+^j?LdFxt};JT|Z2mH~n7^w5>X6~JTXw(Guk0$sfX~7Ys$+K1*k#v;N&bVhL
zt@5k_GAU3o>7Om50KyGP1p_*9rH|6G{v&@4CP44ndYBo
zGvITud4>Ea;ZL&G-^{(|`vE4HE4^Yt@DAuI06r>ZR8j@NBy=)(HDY{3IS1!G`vk&J
zjCrx6PvTnCY;t_n$j5Tk*vRvn8>E(V&Q+dYA^*842J4zfbcM9JF9nU*dE^b$M_{)=
z2G+kqv*CiA8e*$a#%sQ@9$eE{4^CjdbHzo}b*9CMHfrIUY
z?1p5uIC)LepEvVB?dEmlTV510@l)=9)I2KVHl?oxFC2ag~P!j3d3L*fi{Fxy5rKTcNxvd0cFs_soBS
z%mlHo37}t=*fup0VybIx9su|ZuLX;A-~`lXkp%p}%c@t`CxXGLAM`6Sf60`He}bk5}sezUUJOHy3k%MZNi4)i4ep
zYLd581-({4Cxh36pa2XM)OApsOs)r5<-E2hWS{_i9(@*P2|(CpBoH@8+!HmV>D5*7?;zISMtczdL9ByhLpbspSSYtj-nIO(E%n90
zcC=b~&0weHRK0m!Y8XfNGqcqLfY$MOk7o$1noQn3CR-zKNMEj837J9?C@~iKqUmJ)
zMY5XTc?1>DIRwkIuQf|Pn;UB&7old8XFKFW5oYrmK-qj3#Sq59c(b?re<@%b{d%dO
zlED7^V7bQ6rAbyc*<>TWFCtozZzXJ}XUq5*kjc$WH|M1cfGfERoewn9YKYN=7z)qq
zimo#GsBK(dt2e)MhjH+q&D?+qVUUCpsMma5539)6R#-pqfhH_wB^IM=64aCylHJr@za
zn)^JPWv(IlBK>(f%J?hSTXXg*`(^W&rzelFp&0XO?aPMZXjSyL=K5Jwry{In>#f3%
zBH#1k3^lGY`Sxwc_U@`T&(&cZJIXltrYw?Q-2ADLUw)7Fj4a4*q%5W
zv{@8;NJp5=`TNDqNcSzfW&mpqtZP2E2J+FW)TuQPYpVczm`tsitkZKyt5Q!bM_P?r
zBdzA4z4GdkjCOd;N8oSG^?a7b%5Ilx2c%0*Dm<|H2FglOgf%Tf_(*9{ax%0_hHMNS*l!&vMCRc5~
z%AVg9JGIc$6&z^zg1yhwi~ReVIavc{$Y}wbJOEX(ciTymqPs8Xh#tA9Bf9^hsqwXP
z-{IZk)ydlH{TH(Q)KupFR6d&(xvA;?Bi`Am!UpeIe{VZ^V)@+Qa}n{%J-k;B+JE~
zZvcmmKe;sV^OBRy-+Vk!RFZr8su|JrXI4ZTSG*Lx{`%|Do;-H$d_CH>{*`Fef-TXD
zlh#Kwn^#8T|FR@{_S)Ie%|`}+$ZI*dQfCpF%7RckSEg~nT3qvjM&RMXUd`HF=S_*4
zZ<-fPerj2?qDO$hr
z>MJ{=1+D9%VYkeS{&@TZ9T1Q&&L`$oTSEYOH3*$kfXOndE+9Mm#bnZAar3cDr$uv~
zUlqOb@{VrJ^^VteMk{7-j@qB-jGn$`R&@KZZMjV}l-p1qgWOQ=MKcJTQED=}N6BxD
zA6Eu+qQC*rXnVMJ^U^&ild;OAaJ(}-T^S4A}`z(s?v|p-~A#Fjvg7Sf;7}61Jpofk>wkI3F
zkPsm3{Ow`+;KTjA+2^!L(dva;daUq!!g%$i9npmQ7e~K8sx_E5jcmIz%Mv+nUt8C3
zf^vhdn;pHh^|iX@+cty6lh;ShH@pykSyq%|Esng}>`JXDf6YBb{!eNo#^=!eZw?z*
z7>v8#{;9TT?Tc@nkNnk+ZR=i%hW%znO};H92>=w0ZP$2%Ol*5p6f70oMV
z;*bMi3PhYxbOe$BqFHages;ZUyvL3Ct`z*x*l}Ic
zuQjJTJ~JWOvgYM_r?T{T`7LXr2hW|NQo
z{GymQZqNK?@8ACRw`jr0)zL%eOjdKC)k3=u<FSR27hs=D#bL{Z2
z!yypl$v58qyEe9Oc{Lh%<@8)271bdm8*D5E7$}2-mOm`0cj|U|?A-BsH2&^IrJHBb
z3p6jUInDKN=J|uQXaM(m&K)kF?@%1ZH8t47LC(y%BUaVBj(hyDKOi;ock_Z^(dxut
zyMyan1t|Y`Y-=>+7qbh`I6ORZ_T-p6d&^ipV`Fsx8I%0H9X`&lK^Jpe&@k@lZt}m=
z%$;cvX8z&uvC?QxKzG17PJ&B>)A!QmSLCniZ?OCqAv)dYg
zA?FoNc}!Gr$UWoLO$RW3MtN)~e?Gpg-gVpS#-s-p34kE^vGHoPMB?8;HP>o;-xnvP
z-@keE!gkSoyk-M_teLyH&RMG6cyYqI=l`1zC5+;Se&{3vo7=THu7XYMC<
z7x_PmKu9M61RDge;~F=RJ0K~~{dju4YqeL7ov-YSo2yq7*NCY$!CHON2cn(*zB?^?
zd0X}qdy6NpQ|o@m5o5ixEx(^v$ISHHuubu)Z%@tgPWjPtTf`ZaW7teSIrbyn(Ex5t
z4ISY-=ho7zGVeTs^(xOn!7pT=zQxpbkIW0A@8UuR|v4H|(z^5Bue8k!!CGa837)pc)-PGU80iwWVfl
zzpgicBgXc=^T?K1p#w}jk8JT8K{i`Nv%qI95Iomt0JR^}9$TW;i^1sC9Khx3KmN}7
zs*SOKnxE-BVyvjY#KzcY<}=HBI0N+`_6LVX|88C-pGQKTQm(al^+DN8#^kh7+nRk6^Y_w)U_2
zYW`CnU($n-?_R@xUrj$LmD-B>s>d;XE=Ze8oar6^)tIICi}74ey4r=##f@PE4RnL7qPqjO#yApAHLGdORy%@a4+Ll
zb$=|x9C)u$@P_Bi(PJu;4QkWMv5a;G)@s!N?2i|(p1HBd=Bk++@?~aijMgsQ8UwUm
zKLlfmG3fgp`L$8j+y7Gh#-8>}rv#$IjH2`I&y0(9?9jiLpbh(dht6s1qlxz~jGBHl
zEqeOW_UQ4iOpNY3dA#cXJ-x?8Pkp038v4sw(S-XJM01*!Ma$dQ#SLP28sq*jPjaUW
z$BJr~aqw@J)73P9OFcz=&qeKu9A3?Au2BP6)UrzVBjjH-a@zWl6x)$4dP6j8cl~Ee
zH0&2Mqj@7%#toq-hiyil|E|d&+2Ymcu47xGEgN1b)VpHJ`trFoGM3PxVH>3Vm^`EZ
z(x~~rrbl-jr})~HI-2X_7fy<%^j#dSUsC_SJY_#s_5WtOPv}sLW#i4}H_JDGqsKfd
zjBA8t2T?AR_`C*D!v?Sz8$ig9P%LF)E88dvbllx*Y&7}7Me(1W>WN{=^}y%H>%J-R
zkG!eeuWH*L47&4}Kn|6)iTC=y3T$;ue`0a;!0F?4A}F}*QzrL;m(LUDPmE?fvM6pg
z_4=}5Wykt(z5+Pv6S!A!-M1|H_ce31J=8}t7AR^Tx~BIz;O|2^YT5uWK}sV-|2@BR
zCdZJMkKA+Chhr-151l$b>TKQ+e)z@cfs@7wU-Gc0&QPNWG!NYGd+vnzQ)4|E!E;x4RE@VjM@zi*IYv2iRLd&M
z?J^DEKvbG4J|=9??K_#&iKKAuIp|!nKD5T40GyBP1B0Gp2__lh6Z+|lC>jB=PvD%r
zPJ8slnuBoJlyzQx(MRY1J?5i#=>Pj`V`88AK1LWvaBqK9dEK0iQQxml4Aw^lKj)4x
z$PL7+V}Mxp>~lZth&K1|mj`R+Zpy`75LZf0IS*!jw6Fmf;Va;PS3Hqea)!?dA_4id
zrVuxPYirj4*e(~{?cMz;A|?CFjZVo6+KBbBE~Qy&cI?@Fh3A*|$p5vmpE^Q*aNJs6nO`8^uZaBnnYl?#gd+=;8D2k%o2oKEMLAYH(x!Bc^*KF7Ex5^b74fTgxWb<{wM3jphDcl3;>F=GxV-ffo#4A>wd3
z;5=dtkd)pTY5;-+pQgHf8gzWh&wHwueA4tWmit{iF?wlh_x{|*=H=U>p+B20{a~LJ
zjA$=y72lPZt`0VU2ag@yBoadg-BlEYiKUV3)4t;#89!aS2C&RFfIt#8LgK#3NjOIG
z-&-T_XlFRz8lc_t^EJwk9Z#Ouruu08xzFUFt6%%tbI*KtvKp^Sat(XhOlsrR^HV;#
z;~G+KU0p+V^O}HLAAa@JZuuwbZF8M)&;02A$=r1V*(zd+(AQEZC=g9jtHE&uZ8Jasa5tg$yQF*F{7upQ|Cx~-n*c_zC3ry#9{>nW8sixYL3<1f6Cu4%3X-yntrZFDDQdrb{vRn
zGG=Af`B*b^L-g#oCh0yXSSjLEH8?W!XsrQU?G8F}=RNaU*{0xR@Vt|+SpyimO84cs
zQNl0hM_&A)*j!GW&X6u=w@4?RM?Yuxt^R+n6CoJUWff)O1IdUdf$8zx0Yv)
zTYK?vz7SI(4Cd7()GXY@v%>#m{qZx$MLS+8_sMMgnwO$+H_X%0*irqoI?KNB-k0Gq1o4+gF>ZOvp4Fxj&1=O4{QKPRQ7`#g4QVV8i+=A
z)~z)fk;lNATc2?I3pL5n{AZV@e)grfx~l=$?+1+ipBZ_;^m7mS;3lipjxp=bxtRJY
zm`0MTwbNdJRG;Y&=l|1Ywn5BzWMTBs34z$z=i_{g3SMe2mSYtioWF{WyS)Zrl(z_@
zXXMK{OGTp+Z`Dn+I{8boCAG
zDIlm-lU$lKNiFtg&TZ{BJ3C+75e>OwvH)}nY;Mlfj9gCAoE|Idi1~BRP7Z6V){0*M
zN?irc2|EuRKRS9<`E5@7e^{1|UK#a2Yn+OI#-hL=`%E@@B98h}p0Ht=^)XrlSc8h(
zK}Tc;X&1H3BcJy+kMHO2aX#hJwCpoAReMRo;K#IEh~-7UtT$q&WA8
zZH>(yr{WT#JO?)rs(-ACriF47}IKxJ|Mn04@`I!mww(>3-X)Acc4>!6{j!>
zOdh<33draA-pY-sVFS<-9_o}@{0OX2KKLGii48>n>Nr0?d9&lLxi!hryl0k#^J1>u
z{ev%?JGVytzdSA@YXEcHa5Wrp4mpOtly|L;jAcz0NvUCZ=;f2**k0c7ay0RGvpoW8
zHP;$ah%esDQ;}zXLwGgU|BDUaXiYHB8O^y+0NB~`bl0*0u&u{GGcp)cwKH@cZS2ZK
zJ@cQ=|9gC09n&6KAmZWtY+b&s+ihB+4F7(6mvf$1w-8Q?YE~^4xW+U3%#EHnWlXo!
z9r+WA!P@U4{*J;1aB(OBMIgp~d!HxIdknIkXaHqN>E!Z!E&^!LxiY#~X5H-azms3h
zXuo|*7e3yGy$BV<3M^By<&RY@Y%G96?jzgF&deMb&RHRSyrObXA>=t0~
zM*IZOuS}vs$Q()@o@fBw5nGX*i(M;v+5BjFC;y?`pXBPw7=Fbh6+F(*=5I}?
znQb>VEZ7u1d+xZZ>R|l~y@2v?$+am{kFH>&8u>CWTz>8VGD{a=UMVLw&$J8ev%k1wr%xhD2;yt?((
zXzb5Bf=yKeb5V`7ea^R5hib8|owdH^+0VmC2vREBTH{To+6B*-n`o8)w77~dFS&;9r7uJ&4BN(n(KbH0qn05LLmqz
zHB=mX|77nGUgC?o*|?wAwgH5aR%T-32}?S30k8}=K7^9C-727O^n*uNYccDddqxS%zl(((aZ1@TgauL56?8&yy5kssK{
z{O44sBqnXZkRUWT>(+x#^_J&VJIQ*jJ^?Hrw^H~llAg?d&L>PB`IyE)dv@LJ^>g=J
zRG1vU+7y!e{-=zNHZ88*UjwpZ+;vlP>yPnlUb3a8zG%b#;L!9x&WWBnUiS}QI2xI>
zx;;ttR&m^m>caI&&h=CB=Je&&8uz@HtD;NwsnL#Sc|lA9v`lkN2E}5
zC%ue)QBS}>W1}DWrZsx4Suy@R?(~v)X8dt{?5nw6V!s5q(kb?cKuN%09GUqH0va7=
z!u6y3xB=AYO90ydI0lzs@z(Awn^cJC7#HhB-A+H+Q9%VhIQ!3?(-LhoUz^u@teL%D
z#N&L`nxn%*Eq*Adnz7k5f=Rc}iUyj-<$OlMyBIVKVr{u*DCcx)F9o#ac*3~+&eWk_o6`M8JK#Pgj@P)`tL93Zo-_Y!r<5!3;0#7ISHdB2k`1-lg
zf!=HPG5@)ll|D(&3=VDp9Gu(hM@~Q4Tb^U0wf>@Z4S@aa*IS9<1V1L>;|clkv231R
z&5!ZdO^Mde-&m6zXv2PEap0+=1gNm#t93T__G|HVGivjbfn6E)n}thTRzxGOn&{`9
zbAAGlsGuflT@@np3oZ1l&Z7OCGv24^9A0{HRj$9b4d734kS7e+fm?L1Ij4L|D7+qD
zyH5Zs$FGbAoH{ZZaLUNa)!m;$_otA|sfrA*v8<+pbXn3hUpy`vd(Gsi{dY5?&QZ(b
zw^>h*c~39$Kh0SjoG>ChlUfZvcWm_Pwx0L7Xl!1(CH_<};siv})^Mw?Ft{Z1U7i*0os8{>(?Re2Trfo7WKIN8h9nZq}&
zJ|WIwUD3Dr`NZK5?PG2RSVw}1IdOP4m4H$skYJu6M_AwZUv$r3o@tEYv2^4zzb+>Z
z_lSZZb#=yiFCN-iY-~3eFRgniTGF&68uQ~xQU4PqIXK2NZbOuTkPmx5kmBbTn%7K2
zWJH^aeI)xpt^w)L>~SMvzY?AO_c05@T~X8E+7t!fF2q4
zuhBmLt?{BJT0<8#J5l1p?LTwsh-ghmZT`K2T8&pWy%H@Q-Wj$2ygeFl^2kWwpGeN}
zGR6_>-t&o4CpVK^TYTkdb(}O@`mWS3
z%C7w=K)04&PxPH{G=QENiv}zbjU8*0)|l(Wn1E~3=_8}{v)0!;tS*gLx4#;#XjvXj
zys0C4=5r%td?)!Y#5hMMNe}QTmiVoW^&3PW8HDvef$F6Yz^*_0`H|6v
zxwZLQ1wA!(y!u+SYQl+gx2H4>)PcUZtFw>K
zLRM);X3n+^VA(+B&_J5FK8V)Dxf28O)a_E>oIEU*o%r9422jgkKl+cmdZJ%5rN(*n
z4V@i)#;9ohoISe9eF#iwT^Z{Au_<8Wsh4WPE;#b+0%HLy9i6M-B*D|K@9!7JVm
zguUm?r;EkUFRgc|-8$^gODufm#c0^YW7XUs<~dhocvO}{MU&5IFIk$KYyY>X>h?9!gkQ8r
z&wkeDi$-)UU!mp^Y7%)37}uKu`);kt_x1I(0ick(HYr8NwN&=Au=ZIUVB*|ohpBrL
z{=2>2HQuX-{Th96(gc{)O@TT$CH2A@^9w^;x97%RGqJ~hq`9ZZ))iZ$jz7$Z8vzq9
zl&2;o*LisiO+L@|-E*jUG-Y;Q-^V;`#)C}Mrq5>UT66I3KO0_Gn+MoxG=SQVEuEXA
zVdszW>g+XgrMc&Uu?gyoK(h^}b3|vi{<{LZ)7Y|XbJTJ3)TrqcnKQ(@#)Br3i<>6p
z-{TDB7Ig)AMSG-olWhRYQbpR8=gD$<&S-Mlc>tYzZvSPY0rbS!zIJ;w^3t&qq@UI6
zt~Cj-(Ff{_7-+kGa%^{R9h(+zj3)nLvYw9sA26Tl3s*a*@lCr1YDO}h`q9pUCtSL{*iz!+!IZMno?T
zS`>eWeQzD>XRnRMT`}Iv3lcHS3*=O*0j}A5!J|B0e%7}asPXK5%*I6Q3Lzux43gWO
z2{i~!mpkX2v-j=4npE%ly`_fz2P?;4K2FvS!04`w30E(^hU(2PgE1Gk#9tQJ|1G&U
z4*Si|1y9Y5hMhLtBOl@&xf9|SpTkDLwF$kPnRlRP!an9d!|^aX=MFey8u^9nsn2^s
z#?NM50}twL|K;R**X}Jh><qWd0=b(040}q_<
zZyUhFL1ni1pBo&0)59!sP&>=d?Z4{2e}=j-sv9%^JTtAe1EE%5UelgE43WMN4#s?=
zC3=RbH~nT{P$b@Iw`eQLFd-7u8f-yjtC3Gzc?(KdFQlf(~GtLn}|I*?3Y_(
zzdbtmX-IHR%Ms*QdkN&_3Z~vd#|MOFg5gyxC
zZ8dMHKvTy$=QRG%%S6M>^Pg+d$D~vBLq%?g%Tij
z@>ai!k<$i8Z9g$zA~Y?Gwy&x0UnSnvVSnpz!cWJFya}G1rst0HoaPL!%t5vBW;MKa
zA9HghD^iPVi*%YSQf%hI{qVYKZ`iaRBwL;3B~W
z5?mpAY~wVrg*ZgqeJeS
z_<0R5p@A`$JAIJEQ+hv0!A+CIU^->9Y5DGmXxCW+qh
z+Ky=Zy6rHx(Q{~x_d@5&>MLHeU+dQUC*|x9vW)rGaF1+Qs}x8M8>e$k@g*`y;)#i`
zo(W(pu;-e~=x+{-UU+0yv|--*J>q{WzPxdJG~(Q5elzQwQS%18Jw235YTWMm^g*ZW
zWB#+hDAMUzFjz|qOomS%Bpvk{hoakSES39OeF9j~x8LkR|1!))}^3Wmw!
z^-yv1*7NHv}cWoMFbDYlx-LxvWI+(pFck+>iFG+Xyc;p{mZ;-
zrmqaLk5d!E4z7LbikcDzh5-yrLB9`i*KWyOS4ZnlM^=^F5Vk_Rg&N+7p8zh7
z7X!sDL*{fXq$AK+aIYsDK%vihabT`O*F3#Nd9J1w${cm+koaxV9b?AblgjzYJ{RR7
zAQJ>`C?^GZ!WUb7Um!o~Yt7NpQ48w!e-KtrTT#B&jK%7Bdl5E(qt#-%=Py2~?^O$W
z-m`i^X3~vgYLZmj*>n9aibp#el5@Gz;>`nbHMUDOt{L~V$3Nr0D`V;C1-gFCw@}P^
zaa9BAJdB3yX}Qa*WAZ%iM?<6a^Hu?=FjmiZ69UT*`*RHAemp$fTthi$+-cmEc|0m>
z0AJ|$j+Bgf$qIon2ei!^A+&~K?rYWn+$R7|MjnV=Qn^{>fIf{?D@IW+?U_*X2o<)#
zAA6nf^U=|3uXO#l>o#|8$|q=E1K|3H8fQp0M{i+{i<(0b#^W3hh^GF2T=dfVp8aOw
zib+dCr1i0>$TjD^u(Q1V-+geHg*D9p!t@>z-K^XT&@+hn9vo_>J^Ng{PXI5bHgXb@
z`j2@C#*;@}j=AU@<-Iv4?>N=mv#fa@f6cHi|B9{sKETkk2dMe*fW|(Q@Vl9P57kM_
zbzbhZyoB(n*hasGp4~5+_wwX;@qGdlG#@mC{!&S*K%
zaukRk*2Z`Zz%1TVlpr->NEA*ugBxUpo#c7qO~Y%FOxpm^P9b6QWY~8Gy^;?;tLoPD
z!dg-8G@b0z=*yppUfNLoZ_>?vcuFu|s{ec+icP}@=1s{V%M4+A0eQBqz2Jg5Mwv67
ziWWROqvpSJu&DV3&&RwPr#^=`C!g>38^9TTul5S)ij-H}98d=L19CNQ02=rl7_?Z+
z#8yOtL}w6a-5&sPGuNpm}
z7|Y}@RL2tJclRyde>U2>dP_|(Ysc_Qp4J;#C{LV&5C_lbd$jfm;P1@bF2G8_8GOcH
zRN@A&ymkiZB^xzu0Gf{>UqUi@NiLG*y}aCLqzLVmtOeI@?d;{{^EY8(xp)JyjJmA9
z-}sfY!pAcJb7gZK%73WAaKjJPAjjU&3!aEpO<7tK+>XC?aCv@(a%
zStD7k8o;u)1-S&zB^$)CUh%A>;PRFSRaQBO=|T-wrF4zYPml
z2OEh1CfT)f!mox#ufFt3^!z!GhhGLt?ur_imzz*-1LXJ4a=ijI)nar_z`kg@bIg2j
zLK=gX6ZW-aGxNXxcf$yLLIn_kPD?f>1f7eoXWr+5oWX0awQK;+58*faObt)u!o`5=
zE=CO`njVzrH3p&em79$MRJ^LSQer~+b@PgUWO&W&WnueD!^3>edT3&-qvOue!IuhS
z&dZtRzvhqf>^1I!oD?;5%}=*Je)!nRNf?&`{9>9
zA@;l+qkklWdW0>dK5f4mT$sP*6JOM8%es|(EgtbgtYiDSEzyv#J`yH%;Jok~?c~*_
z2)|`&%e!&J1H-Rrd*grQ|CQS&S
zp|y~_xla6bjT*p&1-{>Tv4rB{$%HHdNe*y$?S-eOMsxd5^X=s|t|FfU>ucxae?xHb
z$QOK@8bK7>YhTE8FC08u_dQgPvbD$~zVns!aYGE*&5JW&bMz0MiofjH)v>f?o*rii
ztJ<23vtKUx;|6f%eOIVp*8r54xRBF1v!)FovC(u^*L*9Tlf0%_rjW^8J-svjjkF0j
zJ|8AS2nac#7V#%@!cF;~0_H#4QLe5^toTPa?9U&xT+=^HyrP_zty^BM%IGPJiyYjy
z-O#klzgp}y%(5?}a1581X#fWn5k&)$wimh&t<~&H0Mh{Sd=C1W1B9SHbEZpsiOzWc
ztM{dEkXN?8WWF4DRJG}z<7*W)(&kG!e;sFb-?r+>VdjtvAMk!+&VDH{>|1}S$~Uy@
zawKA}R+pB)5|}Mx3uR_LvTJ|g>V+pK?^1k9y*^sl0F1$X&0LMi!G!0Lh7-zbJXv2Y
z8-Pl*oI)4*8(ItHLfa34xZ|Et!vDo1=ag=yTF#4VVb>N7ofXXA#NRZRCKBF{|HZSa
zzRk-vM9*LNa4^TAwW8XEWQwq=*~n`Ed2)E)eD(uztX&<`?;97absjDQajr7geG3~v
z(gYrDIy+HtcGzGM!SjfutxvdlNSBk9H{2%x)sH}YWn#+%Q;6_+dfav6lC{Er`)9(3
zf4grGaAm+<32!+h2Hpl-Zi6{
z54Wf>EE+a@mwhRc+rD9|XLsxBja{~t_o>2;Eh{%hBd>hC7}P1I`jn4N^RdiY!3&`)
zd05fDs7tuD&7-Q?=KJ`~@(tkZKJPUL-pG{n2?vgZaraI>k6c#fY`C8D=V-$J*0uqZ
zPG%_HvuSDo)$FKw@NJC$)u7-vj_hCd8FJBsejeHHkZ+nC#kOLK|AQ>{f1#?mapz3k
z;;CR_^{mdq+Su=POt|@3Kkw`#+EsI`GzUJToy}_sdHRd$4|Civ`d5uv`@NIrzn+IR
zg;QSM`nc!6ndkSGZUB`gaNm%$z)avwG2P=c%dnGu65wRlT7LqNek%TWAPIo*ERHh(
zCL{(lI&n5RXFuYs&Y}Obss-2XOEkJ;v<|t
zx9SF9jJ(|(-0l*GNfB$9T(2qgQP0StWksxM1E8)QYHIk5gy(yy<316$Xfo6gxPdlb
z)W>}EURWv)`!%|kYp~Dt@avKvfAd9m3VZA3uk>P3b0mDJ?6q9?c$e#C|Ayh*0TZL<
z3-1!;X4c}QBP8gV^6TAUV?3Q*nsm}?)4wyxP!RV+ikai0p(U|k5v40!e#|BCRm2S#~7b}{7=
z$;eE-=lNZ(oBfh#`op8+Mo`q?^cPVdueRR#q;Z6Fq1@yw36H&?z7VgJ`-^TmC<
z_#FuOt;{+XG*|aWNAiaK(qPS;&S>VNEz$7rJ>=WOnv}sQ`pRcX_uvcv6it6{bo_rq
zcx+z2&Ld@z59a#f$l0ahT|IMg*zZ8?L_BTU
zCeAPR1NRYFMQ2=`1+iJ+&Uj=@v}(rEUHX@5+ireF_q9xYv?fh}-?FOmgDcA?&(oV<
zo*j5zHGOe+l}`-&cC>F{`}Q68KTjK5)TBc7D0(p^>x0k#Q=H@ZO;e)vi&o|SIB)!>
z{_cIUnFJcKU2ng$+?Tj*zkQ|{cUmLx<5_BE{xAQOXaYBC5`U54-d!r@MxI
zL)!+iW%Y*WrH$L7S6_am+yCvqmD3mLxli+wjN(ay&i!p>Fu9iolZkujncK+8TZQ9pw*BU*ZY|E#M?UsNxWX}ak5x~!&)8#?U0
zFtDxOsp^sO(xz?U94j-_0Oky6&26}aQ#Om8X1=rA8bI6x&b#H_AlbEf-Yu2s?(G@(
z9GHzy0QqD4`Yq)Fk(X_2t9euM_}qoRQ+2F=
zab@(}`M2t4i|V1*^`34ef7}4h{q2X0GjBMD;=%_267RVNz`?ll=ilN5*Y?{1JzTy$
zYrHsmdKeH~7tVjqIeW&dr$Tg;iJ#9`Y{LGqtL~1rt*!0{o%Xb0f0+q$?Q-EbfLs`M
zWgqYSjvYIq5kI=m&pVy<>dH+xtI^-g{s+6g0mMz(?o|?U(m-k73wqS1@_J8v-*3*|8z3f_k;s1Da
z$HPOj8lLYrW84!B_I#OhS0A(Xmzt2@4&%(hXv_lvfKLO9n96?ZEl<{=gnP!Y|1|ZW
zizN`!9Gkm=qgcs_a!kUcvLng`Q8_0#t;iNm|
z54yeZTx;~~1vh&QmYcGuGv!v;IPaD>&Gn#~H2^z|kpY@BAxDLie2#RCy!L@Qm2vkU
zi$-<$#n+l@-VDj0zMOMj^Dn49H~tB?_lsWIT>ZaJygQFk*FC6^^G21h3r9?e&)6Td
zn%;MK^>xznD%bnhl>BxWBcIIPIyXYVHwGLHtSbQz{?_evF6Zt!?AM7yF8@=xyaq{p
zqb`T*g3OA3ukMPD+y-%DH29mhMTZX_iQ}I1=Ye`{%5X)i
zPj-KJ%^N_{1cp3&E;)TJIh3qF^VMI;wR5RikRJP!Ta6*dyyl5n{M~_$}@=Ry~(ZUo+U={&F<_md7Oz_$8Y6B4v45
zo9h4Q$1XI0<5J^D;Bz6cb7?Z*NY*uqrEU3tNA9gQUf%jr)bx#Cdn8O7tGhq%M(^qo
zKgoZ*&j3th?4Fxm(>eD>e@&|?f|hHI_RSn}`K{5Sk&~kxuXf+>OZ3FB&C6@DloNdU
zjkxBnX!OtT_lXO#n0J?>R0nX#t50R`*dA*Dc0BWy>&K*k`O5VYJmxvRp7ZncK6c#7
zQE(y{vo_)ON9$eN-DcR2i(_wmI3)|`4cFp(muqao=h0rEImfkfdZD?CX9O?S9)KwP
z0Y8N<2DrICchSw!#5*2~mbJeSf5UTk9xG=oFmc=@a)>4s1=58LF?#yISY0
z9;17d{7C~inVrH!qzxcx0_b=O8h6$;iezqHxu)JV-Yv%TzJvW{X>uh`WR%TBmca&92~&AOPBTK3F;D1HKrXEUXQ>=!R(0A0LmFx6j_~n!&1>
z3!|n>;8PdY0CSG9aSidM=y>9C$+<&2xDO56)Z(O`ZvbEY)xO4wNvShP;{@lZZ*W}q3
zJ_|39K^NY*TQq|$tJg(sw>}2(T}%1CR?j8Z1oMG1S`NH=QhqA(lgupl4}Nre6Zp#Y
zy)q!01e~2DA-=-2&u3ku?EO42uZ=$nzE#J(!Q(T6F$vEm@@u(9f+9GbY+Q?LrCxUx
zXSmT^o9zj*gKZ~&HrE*RBW93DPR`R)xgT`l|3s7i)Gt~xz9ZVaVoi_y%JH`KTcR0{
zJ#R=$abVoyo^UOi1ItRxi=2g;s&D@@bs>M+1kSqV5jC*0ug&H;bdLd@eXT5XB4>-e
zsk8XsuHT!61$4&)11kiz6LaUD&%RdXj&qF-;cNwD8UPcyfoJ}gUnlkf0qSa}Hg=@}
zppWi_!0R4%ey;dhc^-7ZFQT!(x;L8n#IR`De&kn>4!wafB1VD
z55I}Em!8X!ixK0*x~NT(8Vlke%{|LYVUq(wOOs_XTYc^he>7{7;BhN+8p7WDR
zUe5SoTKqU~NOtoq07S%0iG#9m-UA!hNWld*g#F~|_ETHjbf9r~L?$_h3pFNMPknG$
zLNaOu1%I(2#{RoW%?)KauP#?27j*CK<<%+iK{RX89I2iZt9kCe-}m3uVVrrJ31(`V
zV3!a87X}@JJh^(w1kk1XW^LTB?~gVtSzd3xcFVAT6~@Lg^YJ0(ZT0g?K9X3y8~|7m
zya;A&94@!~mhp&zlj@M_D3YgUpb;XiH8u|X8gcg3gIsviMrcceti#Izv#mKy^-nGmSYC0yL`ByM`Xze);#Juhvp4t~zluiu=y%b`|N6b0*ZiR}aqY*y
z_t&&DEaRSkOnOOn#5K1^!>|5bH0-Kd%=OmTuOXNHI*Wtj!#6@v&X~j@l4xro`4HhW
z(tSy>!+TgOt{cXv>4ch=%0-?m=34Vt%SW{~Ld}nxnU-Iz-kdwE^OwZ_UG?U9a2V(Q
z*${Mt7u1)2$U#U#Yvjl)NJv+xSk^{Sft^nD!=7_z(I&2844Pj|9F0hXgIiDZp&kKXvV-T)Uj(Iy83CWz*Yt;)qi6=a=IptA0x&riJcs$>xKiC`
z{7~rOc=P=6nk3r8S`m3fJ|KS~{Q?gp%X^q-5tG9N)(3JY&QNTaCy_6R6ZL7>68duR
z2RIyW_UiK!f7K2X(4ofRopCUyUm?kiL^$m-5p=v~T~wE4$yp@3=>r3
zl^P)D9ViK+VVV1)fC=POsCG_#@U)4$m>bj4Zzi
z;^}m-f4uMRSue-0>CyJsjw3qenn{K^PNv!*xe1aDQ5ViJ<_h=oYR)8wTw~6>*cihu
zHf{=-FVG`iBpSX$GE)AEdh@yJVVvIA1lUi2$^rGXOJ%Vbv@vilmFL_2p@F
z7{DUI2!=~#v6#fHm-b`bxW>%`b<-4Q%{6^a&s-`e?j^JTDVLCMy02LdE|Y#cf7!0;
zx6*LXPdHy`8dL-J-?NkXESe6;3cc!CXxf}Rrwi?4?2IYeOMMcxK_4%Zv7?^gdB*Hz
zeoS7j=+lDMG%K<@4L**jH-Ecjm|(8-3Jw#2VM$@1MIi`04v;A4?%jC-;8QB=o~=Y4
z3G6+edP#5!GM}2+;B_4=mxBM7c|LG#NC1vCYb2M+#)1u&&LWuEM-dyxbBc_eyPkSUrVo-W_U*bRi+;F!Cr4ltv2Z->lji3s;74MEf$f3J*`nCea~PA;
ze+v1j#d-=g8Rrw%Pt%L}L>X?xiCwRmLHweo_>9ex&!0*@X!t>FL2N=7%W1Vn?4i#&
z>mKf2WQH{=`2
zvkt9Eu$<;osF|=%_CaeDcpht@`H%LoIfiP=jYr$3W)!T4mP;o0NniDI!cCD0j?D;P
z7F-7n3tG1Z1c-M48Lf+2f&y+<DG6(x88=6Z{hZMj5luU=jM5z}zJ5GumLg
zNlYRq3{1AG$a?v}28p>qADpc7!N!W@ARjTWcvfvl(0DjTkFfa0MshpsP+hojA+}WP
zvhSSx%JUR+{}1)%V_k+xz`OhP{_>)dn91Vwgl6*00x>
zx5bI+r4+LlT{XF0RM=ht9U4eN%>xV8i*#^futjG7*rz-~hJ3R3y%c?UZ%aO_<&&E&
z=aPL7HNjAQLV3^{Hor}IKQx)7p+kYcgG_PKCCa4xHKfM;L|a4Sth#6f2?Vx+mi
zv+mjMIse!uH$kmY^pd)~rt4&^4V#y|ngn9c%eiJV
zOf-{TUzYaWy(1Z(r?(I5U^z`z#DdL@W5G3z@R$d-%XMQPT^@m`qBhhJnb;!NT3k4(
z&VroImTR)kCu#~DgH|)d4(2C0`|W!3YmXQvVLx?6V6vuHH;+BlTUQHWcrY$KG7FVH=f}d;W_tA_2o}jo(;qvVz;92C-vsl9yLsIzhmYb
zqG+!0F7ZfAEEa;ROw+*PyQ!_?*-xJ$fra{!WSVbCA_hO&LOEW`i-*Y=I-@l@_ETwe
zUUTAHp?$W+@u1z1KU{aMx$|VwVokL(>_5iMI<+`BmQ1(BaO2J2m0=QjmPzW4m~03@
zOuQpv2ncrHo#%C}z_^|VE+-eVV|`*7ch1RZ`ysnU{<1F3XC^f3X0m8kC$Fs|Ih-xd
zCpTK{9LiW?hGIj%@wwVuAihF1&zo#7i+_iCKC|9D+M|a_Zf}#sZCW0-^3=VB3lAke{Q
z^`3I&<=X8Xb*wJq>?HAvy1AUX+)yJBjSFNj2BFvaljzlI&yC;16umGK`Gw}b@e_Wv
z7$(8*HVK}Z0*Q*KfdUC9iikRhlwvZ0oV=T8-vS$e>iKA=lVSaC9m#<@?ER6SmiE{m
zcLt}{`1rgd3J^$;&HAL}1#P1Z&KInwL~-$AE}R{XTWcULPsvpfA9L;G_&hc`_Sv1`
zMgqD;o-+BVX1=w5w!hZHkl-Iol1B=FBbbyoZ%iBn7ZcU)Ys5i7@tBpd=6{bM`W$g~FK&Nz9@2g_+X&|l6Ck{x5>_?=B6naG>l$7YB13)L3m
z;~a9$@R{q%jSKg&K78h=V@H_lTl2U2dU}{-|Gg>n<0$|TU^p50D2oMj>xtZxM;s+V
zr=7<&1_j$dxjZm2LD43!Gx;51aF63fS@Z|xm{drb&>3FCiDcwlgyQ2`x;+;oe_CK!
zK7)SXzKfOP!KQ;GT9dGVGSCh_~4d6k*3dw|n`7?_sqZsw9aa5mSj
zLTF(t0aChVo>iTB#34{}9HN=!>VF=+WKlXtb*6y!yX
z;gd(=nVn`{Y2@A-f5~Te9HszAnzngOoabD#KxPYR4rvjK6Uopc?hMo_H*Xm-R|jwhhotEqw+aUOg&&;87G
z@BGu&j58N7t@c5z*i5+*pl-!ZdX9)1PixENn3_e1$f2d#cS+jxb&KG;t_K~<87y7H!5$l1qV*f0r
zKJkf9)*IiAQDT@PzQfFZDQOUN4n$aJB$vDAXARs)LMO|hWBs9L)Q9J=0in+rgVTrP
z;5fOFpf1+O>+Cn{&~k@X@G$>V@Jr@=XlNPPOhFFjYy%U#deBh^XwoQ^ZZI=giR*0
z*1QzEY7X-9tJyA-x4b+R=upmB>|9~i8z1@i)L{yEsF|%EKrG>vq9ag)AabN8U8R6&
zWZhXlCv*mEqu;9UtjF2|n<$I+iwL9nrrFEOEjFOg9>yhNEXXJ3G*lbb>vG^V>x#^H
zGY_pdejB43hAHxXW`5nw^>MKe`!XpK8)@!3YbjWI%A2De{{R#3?u!EDC5W5kr6zS0IQWaYAI#0NmB?IiHXmb{!i}axdQQ
z;GdA=xX?eR7ww`QjE`)KXZ9};pW6Hszwy(F*FdoOYRwXIqvi+e=GXpBbLK9Kk$U5_
zF={+a!H+Vao9Yqa69*@NI5=DLNOf?w#vn#{E0^j&*e6~J;)7)qz{bP!8d=dU`Y+b0
zp`ni$3pXAAB8=N!SkK`$r|nyGmok_E*ql<
zhXKXg3?MEyK-iL!!A(JkgEOKC!oV8dGbuyoG=Sq-)FnO{94u`jvCtOZ=lLx3DEjlx
zdj!{-&%v3Gnhq^D*fiOvv_=#+HUc4|oU6!gF|rl|Z>u*R8>3FcfZ^R{KCBWT0nX!x
z9V9>$KO^Wh0x7_a&!Jo%DOHjyM9_$w==JR7*$L?m#8O5+&6`SG$Roa)tIb26@7SFD
zvk%+a*BgJ0v6~D7hR>KeDF)`p5A?}v)+}J%^Z>4%3+&-}r`I(ICx=ZZU9;B!G0->E
z%RZuA?OFKd&QtT!;!NYniyxaQ`Gawyea@Mx8)ZokHwGhTYy6pd>h#bfaqk9e0W7YxW;m52W>gM
zNFGNPXBYi(1Y)AF&rC{gKF$xf$1&nQlM}C)FL!migw7B9`DUWaHCwBGW*%N|{4~a1
zG7OjwHnXn*;oo9Fe*D7{#7t6+063kz12_}iBin~1QIH6@uj$5Tw1dRQhJ|{Z{2@R>
z7e6;K?TR)rR%|w@Kaf{!ZfL_bORXt<>?40O&wnxVpnBt_G4|47fcHTIw0q2Tryv1?
z_~Rdz1jNZe4-uFN<<9Y1a{jSH|0BDiFPa}_-^u$8EkI^$1Rxjd!8bKFG(L+mXcwAI
z>X*}fNES!Bhx!hQYhi2h!FuDPG2SA>fbN|JjF+1^QGhhLKK8K>Wn@giYS)l-u?($?
zb3k^XhjqAnYyjYkXdEEx?4wM)4)LVVDC5o@@?l@r?j`=laV4>#pQ3qz4$qF$&CMq}
zGttOhZst4djfci~3l9Upj~GDSXl7?h;KM(dfgcEvKD&K$MPf0b#Uvd_pwt%V6^#XD
z&@UpeXhK#8lb7tHEk2v-aMSq*J_s3#U$bZCQZsKbav!O8-5aAZ#4w=T&j7iXnfIHy
zJtO4-8Spi-hREqi|WdgkK-@iJUee^zVRTyEbQs{ztRfJJ;O5z25a~jK=8l
zFra>~0r5A@eAEzVbw)}gl|nLX5_s?SwRKh30kZj|jpYEJoI;-7>ZsrI(TZx-~{)bo(&i-q*~Jnt8dI{mk6xNb%?=KOhLi&w|KEcqV2do1QoF
z+5G{YZj=*^MQt|OClGrM^#*OAF6pn0y`Pc0+}wZE=xqFyUSqtqh5`S6W*%mUaJ9KM
znd^3L2JT+09scivOrlvRljSv1S{Y4`Bf22GlXrTlDW!YT&Ng%Yxg=H_bK~pz#%K&2
zh8X{UJ7tf9AP_|1#87hs?WP$;+F`&d&ZMZyb!XhhmZplay#PG5TOCZ8B}bg+V1BncC#KeptH&-egG3`rvXTK(v)6vV#dmlEQbVRV3U+$^00000NkvXX
Hu0mjfSrTZ)
literal 0
HcmV?d00001
diff --git a/Gizmos/BanterQuestHome Icon.png.meta b/Gizmos/BanterQuestHome Icon.png.meta
new file mode 100644
index 00000000..b6f7a289
--- /dev/null
+++ b/Gizmos/BanterQuestHome Icon.png.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: bf0b8db33631351499b50a35412163c2
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 1
+ wrapV: 1
+ wrapW: 0
+ nPOTScale: 0
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 1
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 8
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 1
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Server
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Android
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID: 5e97eb03825dee720800000000000000
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/Scripts/QuestHome/APKExtractor.cs b/Runtime/Scripts/QuestHome/APKExtractor.cs
index 22a770ee..733c839c 100644
--- a/Runtime/Scripts/QuestHome/APKExtractor.cs
+++ b/Runtime/Scripts/QuestHome/APKExtractor.cs
@@ -74,30 +74,59 @@ public static QuestHomeAssets ExtractAll(byte[] apkData)
///
private static byte[] ExtractSceneZip(byte[] apkData)
{
- using (var stream = new MemoryStream(apkData))
- using (var archive = new ZipArchive(stream, ZipArchiveMode.Read))
+ // Validate input data
+ if (apkData == null || apkData.Length == 0)
+ {
+ throw new ArgumentException("APK data is null or empty");
+ }
+
+ Debug.Log($"Extracting scene.zip from APK data ({apkData.Length} bytes, {apkData.Length / 1024.0 / 1024.0:F2} MB)");
+
+ // Validate ZIP signature
+ bool hasValidZipSignature = apkData.Length >= 4 && apkData[0] == 0x50 && apkData[1] == 0x4B;
+ if (!hasValidZipSignature)
{
- // Find assets/scene.zip
- var sceneZipEntry = archive.Entries.FirstOrDefault(e =>
- e.FullName.Equals("assets/scene.zip", StringComparison.OrdinalIgnoreCase));
+ throw new InvalidDataException(
+ $"APK data does not have valid ZIP signature. " +
+ $"First 4 bytes: {BitConverter.ToString(apkData, 0, Math.Min(4, apkData.Length))}");
+ }
- if (sceneZipEntry == null)
+ try
+ {
+ using (var stream = new MemoryStream(apkData))
+ using (var archive = new ZipArchive(stream, ZipArchiveMode.Read))
{
- Debug.LogError("assets/scene.zip not found in APK. Available entries:");
- foreach (var entry in archive.Entries.Take(20))
+ // Find assets/scene.zip
+ var sceneZipEntry = archive.Entries.FirstOrDefault(e =>
+ e.FullName.Equals("assets/scene.zip", StringComparison.OrdinalIgnoreCase));
+
+ if (sceneZipEntry == null)
{
- Debug.Log($" - {entry.FullName}");
+ Debug.LogError("assets/scene.zip not found in APK. Available entries:");
+ foreach (var entry in archive.Entries.Take(20))
+ {
+ Debug.Log($" - {entry.FullName}");
+ }
+ throw new FileNotFoundException("assets/scene.zip not found in APK");
}
- throw new FileNotFoundException("assets/scene.zip not found in APK");
- }
- using (var entryStream = sceneZipEntry.Open())
- using (var memStream = new MemoryStream())
- {
- entryStream.CopyTo(memStream);
- return memStream.ToArray();
+ using (var entryStream = sceneZipEntry.Open())
+ using (var memStream = new MemoryStream())
+ {
+ entryStream.CopyTo(memStream);
+ return memStream.ToArray();
+ }
}
}
+ catch (InvalidDataException ex)
+ {
+ // More specific error for ZIP corruption
+ throw new InvalidDataException(
+ $"APK data appears to be corrupted or incomplete. " +
+ $"Size: {apkData.Length} bytes. " +
+ $"ZIP signature valid: {hasValidZipSignature}. " +
+ $"Error: {ex.Message}", ex);
+ }
}
///
diff --git a/Runtime/Scripts/QuestHome/BanterQuestHome.cs b/Runtime/Scripts/QuestHome/BanterQuestHome.cs
index c6967993..2650d6b2 100644
--- a/Runtime/Scripts/QuestHome/BanterQuestHome.cs
+++ b/Runtime/Scripts/QuestHome/BanterQuestHome.cs
@@ -73,6 +73,12 @@ async void LoadQuestHome()
return;
}
+ // Don't load if URL is empty
+ if (string.IsNullOrEmpty(url))
+ {
+ return;
+ }
+
_loaded = false;
loadStarted = true;
@@ -189,32 +195,92 @@ async void LoadQuestHome()
}
///
- /// Download APK from URL with progress tracking
+ /// Download APK from URL with progress tracking and retry logic
///
- private async Task DownloadAPK(string apkUrl)
+ private async Task DownloadAPK(string apkUrl, int maxRetries = 3)
{
- using (UnityWebRequest request = UnityWebRequest.Get(apkUrl))
- {
- var operation = request.SendWebRequest();
+ Exception lastException = null;
- while (!operation.isDone)
+ for (int attempt = 1; attempt <= maxRetries; attempt++)
+ {
+ try
{
- float progress = request.downloadProgress;
- // Log progress every 10%
- if (progress > 0 && (int)(progress * 10) % 2 == 0)
+ if (attempt > 1)
{
- LogLine.Do($"Download progress: {progress * 100:F0}%");
+ LogLine.Do($"Download attempt {attempt}/{maxRetries}...");
}
- await Task.Yield();
- }
- if (request.result != UnityWebRequest.Result.Success)
- {
- throw new Exception($"APK download failed: {request.error}");
+ using (UnityWebRequest request = UnityWebRequest.Get(apkUrl))
+ {
+ // Set timeout for large APKs (5 minutes)
+ request.timeout = 300;
+
+ var operation = request.SendWebRequest();
+
+ while (!operation.isDone)
+ {
+ float progress = request.downloadProgress;
+ // Log progress every 10%
+ if (progress > 0 && (int)(progress * 10) % 2 == 0)
+ {
+ LogLine.Do($"Download progress: {progress * 100:F0}%");
+ }
+ await Task.Yield();
+ }
+
+ if (request.result != UnityWebRequest.Result.Success)
+ {
+ throw new Exception($"APK download failed: {request.error}");
+ }
+
+ // Get data reference before disposal
+ byte[] data = request.downloadHandler.data;
+ if (data == null || data.Length == 0)
+ {
+ throw new Exception("Downloaded data is null or empty");
+ }
+
+ // CRITICAL FIX: Copy data before disposal to prevent corruption
+ byte[] dataCopy = new byte[data.Length];
+ Buffer.BlockCopy(data, 0, dataCopy, 0, data.Length);
+
+ LogLine.Do($"Download complete: {dataCopy.Length} bytes ({dataCopy.Length / 1024.0 / 1024.0:F2} MB)");
+
+ // Validate ZIP signature (PK header: 0x50 0x4B)
+ if (dataCopy.Length < 4 || dataCopy[0] != 0x50 || dataCopy[1] != 0x4B)
+ {
+ throw new Exception($"Downloaded data is not a valid ZIP/APK file. First 4 bytes: {BitConverter.ToString(dataCopy, 0, Math.Min(4, dataCopy.Length))}");
+ }
+
+ // Validate size matches Content-Length if available
+ string contentLength = request.GetResponseHeader("Content-Length");
+ if (!string.IsNullOrEmpty(contentLength) && long.TryParse(contentLength, out long expectedSize))
+ {
+ if (dataCopy.Length != expectedSize)
+ {
+ Debug.LogWarning($"Downloaded size mismatch: expected {expectedSize} bytes, got {dataCopy.Length} bytes");
+ }
+ }
+
+ return dataCopy;
+ }
}
+ catch (Exception ex)
+ {
+ lastException = ex;
+ LogLine.Do($"Download attempt {attempt} failed: {ex.Message}");
- return request.downloadHandler.data;
+ if (attempt < maxRetries)
+ {
+ // Wait before retry (exponential backoff: 1s, 2s, 4s)
+ int delayMs = 1000 * (int)Math.Pow(2, attempt - 1);
+ LogLine.Do($"Retrying in {delayMs}ms...");
+ await Task.Delay(delayMs);
+ }
+ }
}
+
+ throw new Exception($"Download failed after {maxRetries} attempts. Last error: {lastException?.Message}", lastException);
}
///
@@ -816,7 +882,11 @@ internal override void Init(List