From cd74309f85bfd389b7abb180e097939b95d980c7 Mon Sep 17 00:00:00 2001 From: Matt Dainty Date: Mon, 24 Apr 2023 00:33:56 +0100 Subject: [PATCH] Add various branch converters Add support for ARM, x86/BCJ, PPC and SPARC executables. Fixes #62 Fixes #63 --- README.md | 2 +- internal/bra/arm.go | 55 ++++++++++++++++++++++ internal/bra/bcj.go | 104 +++++++++++++++++++++++++++++++++++++++++ internal/bra/bra.go | 14 ++++++ internal/bra/ppc.go | 48 +++++++++++++++++++ internal/bra/reader.go | 51 ++++++++++++++++++++ internal/bra/sparc.go | 53 +++++++++++++++++++++ reader_test.go | 16 +++++++ register.go | 9 ++++ testdata/arm.7z | Bin 0 -> 2005 bytes testdata/bcj.7z | Bin 0 -> 2100 bytes testdata/ppc.7z | Bin 0 -> 2004 bytes testdata/sparc.7z | Bin 0 -> 1895 bytes 13 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 internal/bra/arm.go create mode 100644 internal/bra/bcj.go create mode 100644 internal/bra/bra.go create mode 100644 internal/bra/ppc.go create mode 100644 internal/bra/reader.go create mode 100644 internal/bra/sparc.go create mode 100644 testdata/arm.7z create mode 100644 testdata/bcj.7z create mode 100644 testdata/ppc.7z create mode 100644 testdata/sparc.7z diff --git a/README.md b/README.md index c525036..859d561 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Current status: * Handles password-protected versions of both of the above (`7za a -mhc=on|off -mhe=on -ppassword test.7z ...`). * Handles archives split into multiple volumes, (`7za a -v100m test.7z ...`). * Validates CRC values as it parses the file. -* Supports BCJ2, Brotli, Bzip2, Copy, Deflate, Delta, LZ4, LZMA, LZMA2 and Zstandard methods. +* Supports ARM, BCJ, BCJ2, Brotli, Bzip2, Copy, Deflate, Delta, LZ4, LZMA, LZMA2, PPC, SPARC and Zstandard methods. * Implements the `fs.FS` interface so you can treat an opened 7-zip archive like a filesystem. More examples of 7-zip archives are needed to test all of the different combinations/algorithms possible. diff --git a/internal/bra/arm.go b/internal/bra/arm.go new file mode 100644 index 0000000..3916a0c --- /dev/null +++ b/internal/bra/arm.go @@ -0,0 +1,55 @@ +package bra + +import ( + "encoding/binary" + "io" +) + +const armAlignment = 4 + +type arm struct { + ip uint32 +} + +func (c *arm) Size() int { return armAlignment } + +func (c *arm) Convert(b []byte, encoding bool) int { + if len(b) < c.Size() { + return 0 + } + + if c.ip == 0 { + c.ip += armAlignment + } + + var i int + + for i = 0; i < len(b) & ^(armAlignment-1); i += armAlignment { + v := binary.LittleEndian.Uint32(b[i:]) + + c.ip += uint32(armAlignment) + + if b[i+3] == 0xeb { + v <<= 2 + + if encoding { + v += c.ip + } else { + v -= c.ip + } + + v >>= 2 + v &= 0x00ffffff + v |= 0xeb000000 + } + + binary.LittleEndian.PutUint32(b[i:], v) + } + + return i +} + +// NewARMReader returns a new ARM io.ReadCloser. +func NewARMReader(_ []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) { + return newReader(readers, new(arm)) +} diff --git a/internal/bra/bcj.go b/internal/bra/bcj.go new file mode 100644 index 0000000..40fc7c0 --- /dev/null +++ b/internal/bra/bcj.go @@ -0,0 +1,104 @@ +package bra + +import ( + "encoding/binary" + "io" +) + +const bcjLookAhead = 4 + +type bcj struct { + ip, state uint32 +} + +func (c *bcj) Size() int { return bcjLookAhead + 1 } + +func test86MSByte(b byte) bool { + return (b+1)&0xfe == 0 +} + +//nolint:cyclop,funlen,gocognit +func (c *bcj) Convert(b []byte, encoding bool) int { + if len(b) < c.Size() { + return 0 + } + + var ( + pos int + mask = c.state & 7 + ) + + for { + p := pos + for ; p < len(b)-bcjLookAhead; p++ { + if b[p]&0xfe == 0xe8 { + break + } + } + + d := p - pos + pos = p + + if p >= len(b)-bcjLookAhead { + if d > 2 { + c.state = 0 + } else { + c.state = mask >> d + } + + c.ip += uint32(pos) + + return pos + } + + if d > 2 { + mask = 0 + } else { + mask >>= d + if mask != 0 && (mask > 4 || mask == 3 || test86MSByte(b[p+int(mask>>1)+1])) { + mask = (mask >> 1) | 4 + pos++ + + continue + } + } + + //nolint:nestif + if test86MSByte(b[p+4]) { + v := binary.LittleEndian.Uint32(b[p+1:]) + cur := c.ip + uint32(c.Size()+pos) + pos += c.Size() + + if encoding { + v += cur + } else { + v -= cur + } + + if mask != 0 { + sh := mask & 6 << 2 + if test86MSByte(byte(v >> sh)) { + v ^= (uint32(0x100) << sh) - 1 + if encoding { + v += cur + } else { + v -= cur + } + } + + mask = 0 + } + + binary.LittleEndian.PutUint32(b[p+1:], v) + b[p+4] = 0 - b[p+4]&1 + } else { + mask = (mask >> 1) | 4 + pos++ + } + } +} + +// NewBCJReader returns a new BCJ io.ReadCloser. +func NewBCJReader(_ []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) { + return newReader(readers, new(bcj)) +} diff --git a/internal/bra/bra.go b/internal/bra/bra.go new file mode 100644 index 0000000..f567b5d --- /dev/null +++ b/internal/bra/bra.go @@ -0,0 +1,14 @@ +package bra + +type converter interface { + Size() int + Convert([]byte, bool) int +} + +func max(x, y int) int { + if x > y { + return x + } + + return y +} diff --git a/internal/bra/ppc.go b/internal/bra/ppc.go new file mode 100644 index 0000000..9d38243 --- /dev/null +++ b/internal/bra/ppc.go @@ -0,0 +1,48 @@ +package bra + +import ( + "encoding/binary" + "io" +) + +const ppcAlignment = 4 + +type ppc struct { + ip uint32 +} + +func (c *ppc) Size() int { return ppcAlignment } + +func (c *ppc) Convert(b []byte, encoding bool) int { + if len(b) < c.Size() { + return 0 + } + + var i int + + for i = 0; i < len(b) & ^(ppcAlignment-1); i += ppcAlignment { + v := binary.BigEndian.Uint32(b[i:]) + + if b[i+0]&0xfc == 0x48 && b[i+3]&3 == 1 { + if encoding { + v += c.ip + } else { + v -= c.ip + } + + v &= 0x03ffffff + v |= 0x48000000 + } + + c.ip += uint32(ppcAlignment) + + binary.BigEndian.PutUint32(b[i:], v) + } + + return i +} + +// NewPPCReader returns a new PPC io.ReadCloser. +func NewPPCReader(_ []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) { + return newReader(readers, new(ppc)) +} diff --git a/internal/bra/reader.go b/internal/bra/reader.go new file mode 100644 index 0000000..274fe1d --- /dev/null +++ b/internal/bra/reader.go @@ -0,0 +1,51 @@ +package bra + +import ( + "bytes" + "errors" + "io" +) + +type readCloser struct { + rc io.ReadCloser + buf bytes.Buffer + conv converter +} + +func (rc *readCloser) Close() (err error) { + if rc.rc != nil { + err = rc.rc.Close() + rc.rc = nil + } + + return +} + +func (rc *readCloser) Read(p []byte) (int, error) { + if rc.rc == nil { + return 0, errors.New("bra: Read after Close") + } + + if _, err := io.CopyN(&rc.buf, rc.rc, int64(max(len(p), rc.conv.Size())-rc.buf.Len())); err != nil { + if !errors.Is(err, io.EOF) { + return 0, err + } + } + + if n := rc.conv.Convert(rc.buf.Bytes(), false); n > 0 { + return rc.buf.Read(p[:n]) + } + + return rc.buf.Read(p) +} + +func newReader(readers []io.ReadCloser, conv converter) (io.ReadCloser, error) { + if len(readers) != 1 { + return nil, errors.New("bra: need exactly one reader") + } + + return &readCloser{ + rc: readers[0], + conv: conv, + }, nil +} diff --git a/internal/bra/sparc.go b/internal/bra/sparc.go new file mode 100644 index 0000000..8aa4553 --- /dev/null +++ b/internal/bra/sparc.go @@ -0,0 +1,53 @@ +package bra + +import ( + "encoding/binary" + "io" +) + +const sparcAlignment = 4 + +type sparc struct { + ip uint32 +} + +func (c *sparc) Size() int { return sparcAlignment } + +func (c *sparc) Convert(b []byte, encoding bool) int { + if len(b) < c.Size() { + return 0 + } + + var i int + + for i = 0; i < len(b) & ^(sparcAlignment-1); i += sparcAlignment { + v := binary.BigEndian.Uint32(b[i:]) + + if (b[i+0] == 0x40 && b[i+1]&0xc0 == 0) || (b[i+0] == 0x7f && b[i+1] >= 0xc0) { + v <<= 2 + + if encoding { + v += c.ip + } else { + v -= c.ip + } + + v &= 0x01ffffff + v -= uint32(1) << 24 + v ^= 0xff000000 + v >>= 2 + v |= 0x40000000 + } + + c.ip += uint32(sparcAlignment) + + binary.BigEndian.PutUint32(b[i:], v) + } + + return i +} + +// NewSPARCReader returns a new SPARC io.ReadCloser. +func NewSPARCReader(_ []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) { + return newReader(readers, new(sparc)) +} diff --git a/reader_test.go b/reader_test.go index 9e805a0..1a440d7 100644 --- a/reader_test.go +++ b/reader_test.go @@ -116,6 +116,22 @@ func TestOpenReader(t *testing.T) { name: "zstd", file: "zstd.7z", }, + { + name: "bcj", + file: "bcj.7z", + }, + { + name: "ppc", + file: "ppc.7z", + }, + { + name: "arm", + file: "arm.7z", + }, + { + name: "sparc", + file: "sparc.7z", + }, } for _, table := range tables { diff --git a/register.go b/register.go index bd950b1..a08a679 100644 --- a/register.go +++ b/register.go @@ -7,6 +7,7 @@ import ( "github.com/bodgit/sevenzip/internal/aes7z" "github.com/bodgit/sevenzip/internal/bcj2" + "github.com/bodgit/sevenzip/internal/bra" "github.com/bodgit/sevenzip/internal/brotli" "github.com/bodgit/sevenzip/internal/bzip2" "github.com/bodgit/sevenzip/internal/deflate" @@ -42,8 +43,16 @@ func init() { RegisterDecompressor([]byte{0x03}, Decompressor(delta.NewReader)) // LZMA RegisterDecompressor([]byte{0x03, 0x01, 0x01}, Decompressor(lzma.NewReader)) + // BCJ + RegisterDecompressor([]byte{0x03, 0x03, 0x01, 0x03}, Decompressor(bra.NewBCJReader)) // BCJ2 RegisterDecompressor([]byte{0x03, 0x03, 0x01, 0x1b}, Decompressor(bcj2.NewReader)) + // PPC + RegisterDecompressor([]byte{0x03, 0x03, 0x02, 0x05}, Decompressor(bra.NewPPCReader)) + // ARM + RegisterDecompressor([]byte{0x03, 0x03, 0x05, 0x01}, Decompressor(bra.NewARMReader)) + // SPARC + RegisterDecompressor([]byte{0x03, 0x03, 0x08, 0x05}, Decompressor(bra.NewSPARCReader)) // Deflate RegisterDecompressor([]byte{0x04, 0x01, 0x08}, Decompressor(deflate.NewReader)) // Bzip2 diff --git a/testdata/arm.7z b/testdata/arm.7z new file mode 100644 index 0000000000000000000000000000000000000000..7791b1af7261a5f9aecbe299efa1ffb5480a704f GIT binary patch literal 2005 zcmV;`2P*hCdc3bE8~_An%e8%D2LJ#70000|00000000051j_~B9v25&Z2&)!MTB4u z{7P2*_0)^WeDi3$?cXYY1`#l?zH&`{b~`g%mV#{5-0S&te~ zJL{-%OACHUflywWaD>J;!@gDjs_LvphC;|!I@pLC6{-k{#!qmMJF&_xg2D%^WF@Ai zz#}d^T%LHH9H4^&3Oae7SeV$plds zTAONpY7$*o@>nJyTI!K#0w`MJ_rv&0`ISg>6oq(Mr|sM`U`e4 zbqEl>m`ULwTLB?~3yw(B2PScb(Ll~bibGl82%tpAIb;W!*fyuhu80}+e(+EeCAFUzEBk_a0$KPRxVQ6xj*-;6Mw+uoBJyH0-eV*co3_N|quS4|YNExf%xC8Sc zKx_oVK7~dQkPpSTkJ$Ji%N#KS5c$yjgbwpXHh&xA#mGVI&6fdG8@gI>{QYL@Py{w> z_8aAebmq7;UXm}aP?cn>=PK`ty?2N5yL@A)B9p&F`dVzxp+6tJ&lingM|nWK#Q8;f z=1Pr)34hKB=51xJP%V*;?P8#Lx4L}XkL$KRofayy7wwqQBfilYf9nnSheIZj=hrKI zpE9O`7I*+~MW{MEQYm`vceQTw`M{q{c>veT*hjrXctVv!2j9`@f_9i$|F3*UG}w-g zqLQ%aPCC{)XU|Jifu>q3#U|dsDB4vAKxTm7plbZD*M<@Rxtt@z%*P{vkL_b9{FN=j zcy*WP|FlGP4`*M#CzC`phZx=lXU*yNFffC~dY;-sC=qgy0fW9BRMrX^26BB!i9DKX zp(u4gTVryj&WHc4t~njM7VgnQYebUNNn~NMFUo>UTIU6iHsbYV(e5BccnGgw4o{3MbpIkw%X;^2h#ZlP39Ej z2N4TBZrS+I!`H@Xa2ihEZegR60=t_l(!0Mucj)SoAhQphusXA`P!dUGCs!_i7j1V6 z&nwYg)ZPzwD9HsadcpKQb7QHuJ5x4`B^g@20ntN<^_e&f6yFr{Fbhv_W;=}?cMB#Q zC*2axZLt>r;5tzsU_+~U0~YK9Ovk!#wucG$*x(UeXixQtJ!gBt z5455kT_U|DM51c$S3!pKz^~bMov7wJX57D4!JY76L%59WM-_`J7^zkM>9knn;C>7$O*4 z8myJGT7zl2lp7)+T9HHJrjbeBoL|33N1dQIi7zZ<*Z|0Av3G&p{2_JEB6<;5Cb&E+ z>TbZ|T%bG6K%?(-H|i_9XN5L`48kXO6ldmPlL2V(Zr#<*nbjUvc0y{go+hR%7Tq~v zh3<0_A>52%n_nnJ86*XwViThZjNyTZ01{}%?z}HaVRqoH+Q=Qnab5XYL#_{Q!g0Sn zw0UL0kP)Dv)HG-rX``RRqBKJG8!ge>hB2{w;#lo4;{XW#&xs3ECJ!OS!IJ)|8hnmc4)Iq+8Li1(M+pxzx0VN~}T3Z7(G7fo!K-vzZPMGsiVu3l=&?C=5}%tXJq zg5+Xx+_~Mu5(!eN&B6_%dSspP^2G57OwW*H7Z%D8oTfZ$N4uGdCVAF`t+xK1pL3@XLasX`r000>R000yU0RVtGp;|R|*#Q*>0RSL??STLQES9`C literal 0 HcmV?d00001 diff --git a/testdata/bcj.7z b/testdata/bcj.7z new file mode 100644 index 0000000000000000000000000000000000000000..cd2a09053acf4408a8e50ba401357ad37218cf69 GIT binary patch literal 2100 zcmV-42+Q|3dc3bE8~_9rv;INC2LJ#70000|000000001J&BE>AA!G--T>w9kMTBTQ ziKg0$%#-3y*#!Xrge1eZz{E@L3?|VVvHg@+PS?~EyWN;URs=onK20Q7;@EgS_u*TQ}kTnpauUUQTFV<~7 z8XhZ|I<_&cP^No3`-HX&|1{?jAn`|~)6YZeVrED#%DlS(n)4(xPT}YH9|8ym&8^p- zULn}2^2(<$j(0Pk%Vv38p?R7J8Dh*yTJ>a%%0d{zkN~E~wShefdTSr;s0bSmRWiAJ zQx6?VP6dbCukAGHJMzvG78PBbWsLV-Zv7A)jmP;24#!%@r)hFnC_jR^cp_< z-@_XLR%#`^1BVNAvi;+#rRsH&Tdm5jEG!=_&+AJQT0>?x#~z)p<@lJSt}5RlATI#Y z`zDaQPvP1%H%MIIYBjHjZ@JX z+pqqOvlZjs1Tcr;(Hzdx_~GBQ-V`cwOtO!er|} zbB`AjQ~ywrB+iywVNgdk7GeP3%%6Z#C%Fl*BBkg@>4Cj*XWFua(9=2XMAW}|T=Z;N zY=J$Z{3nR@G_G9H~UC;L2$;g(67Dw$0h%8!VUu1EO7F{%^wu+KsPEnavAiIvm>Id7Mi-J zavxwQ?%CSh#m!IQmkr}?l#+KJq z!x&EtaW;U0@iv9!Ms3<@#(6TrcmaFb8hS9|R3GZ6F{^9tpuj|$pb?#QfdlI}L$A0) z!MdnJAg8K&CGOC^?Z9dd0iaWD;2mrQ_+#Q_0&3GX{Fk}fL~FY^bRi@L{IN$_Kx)hF z)6Z7p0f(Q!SMLQdPUZu*nhSD_>O6^4FN^Un{bZ``XRbcWN=wBL*Q;9WRPIJ(h59H7 zkUeq-KyuSuP{bCS3Rwi!hjzr8M?L0y=--nIvM}dGEn_;w7p%21>|G10yh}670Z7(j z53&nVw5@!~n4@k0`dwG*dJi2tO}Qr6l`PFzyI1J%^EH1f(117n4!R}45azK?wP52n zB#{Oh8ioZS;*^hP(@9nTC*ArQq$a9_`o@HDcI3k#eMoz4xupDevy~G~iR@DvZ^|r0 z6rj9dV?4~h-Yk+A8QRLiSqo=9HN*9|Zl@W1fdJE@Y8MCz?VEi<2 zCDF%=OdL9iqMT@%{yFYJ{HcYo2>~vBhM~5 zXRNiZQ=di{{NJn%2+RGam2efUq*V>PX9n*G{t5vbiYh>RJS^Rv({Xs#j~&*%i2 zsAaINQ!bQ`2|5;Ha~w4}{AE(^f0PM|L(&Icoj;BWK7QCNTi5dBGocipV0oGBr%W?X zqbJ75koZ9WL*T$waMpmF!e8Mm8Q>)ViNPl|!`gVLx*NM~i>Eh?&rFFrb!4FF5;PgT zWQ74f88FeK;Rom$P;eN;F@3bIWmt+Beeq%vM>BR#F2N!E)oky*-C)L!0G%@7BGm3v zus6A2<)c~;8gZ|9Z*Ruzm^q4W70U;-;Ur&{bMkyi#R``W|7g#G$>=)pViYsEPDc1x z)kPkfHV4;dPYam#z8dNzRPGBzUUse`0v{W9`4}4i12BSpysS={kh6G4V({|CTvism zq@QZOL_&Y4Pp#bF9{p}8{Zu6IMlX)Q36kOxB1qMOg7&_{W7abD=_uS-+4^_ScDs&4 zRjFRr`8;Au?*4``ti}-00u}j_6H^yqV=CpV+sw9u4b<)4F6Qa4swe8CyXo4~d9$1{ zP@~_UNsOWwA`N%o+nKY!J>-tnEH4CJqG`sx{}|3#k#)WSu>j(B%fEn};gc$N^i7FM0*6l)o6 zK9<6J0G;4reb*6Z8T2;SbiPks%n(wLSJg>OpxK|Dnh@NYOhegc0*Zi`VD(hm009IB z009Yy!T<*g0RRFaAprvf0|NmA0RRl4WuavN2nqpAWdJn*00jXV0RRyR0Ac`R0BQgL e02u-R02B%V0DzW+@GN%O0Tl)T03d+vfdBwG^XohS literal 0 HcmV?d00001 diff --git a/testdata/ppc.7z b/testdata/ppc.7z new file mode 100644 index 0000000000000000000000000000000000000000..444c74376aa00a17fdc000457f2591f550819407 GIT binary patch literal 2004 zcmV;_2P^nDdc3bE8~_AMy{j)$2LJ#70001D000000000@7pZdL3iAg_Z2&)!MTB4o zMn7<*@ScmlJ<*CCVV3ysQE z^3AGzwTh;iR|_O}kKBJHpZ0bj`w|fKm_8@7DGM669w|k^a9N&WFF-LOiEaNnn+& zKqI@+>83CPeftypoy;Y0UsK01Kk-&Xl6!e}ot%jRM`|+Y7!G91hziqP-!F@OUZCu| zv95?nfACL_7RbDu?%rwT?U~5+<^tLM7CyyQ-^fb^kUE(@i^c3t61cXkp+)?Nh37cN z>uOJR5#xt??&A;>R|Z1R=B6%|P9IR^{fxemF!cR-2^Dhfo3TdJr_62tKAbe#65FB+ zJy^9HrPuGr7D(7RWd90!J?5%pt9Not>%&ttFSE-@$jp>( ziFhrbG6>a%<%{}dwrq)_SsWRxH(+WC0Rt|Xj^6aPLO)|Eh@NMH|IFVyiOP6H4KZCM z+>7dZ40NBy(?oU{M>-XZzz(0h@lt{>#&Q%$D9THQB0qi7g+K}~ipF8px<{@73f+tckRKLRa z#GA_*0|VO1Bi% zzz4M~+<{*#e8=&jR>R=R4t%fal*# z{nyZ6YcemLkw^3O6B*PBe2)laIes}kva6^@M*gxOP1cIN`;ej-`7A5oNCF=|1yc=)=_xob?=cen_NtjV5bM?)%Dv-qW|+PVjuzC(-CVExjg$UVLA5sfz=6A& zv|2BbVR+0o9TCLAQ;;(|49V@!vTC=^vFO_)nR-5MBz1XZM}STt<@X1K8X{{v`W~Jt z?XoT~fo?oX#Jg7}Thw$pCR!s$=bHN}5PnMQUUT1lpq+193fe{}&qTaXysLTj(>YN@ zp&6EZO1@av3a;=pciVKok=pX=D4DYtX?D`N1A2^Ce-c=&(*A_(T`CqD%2?}3fD<5{ zqRkZpS=6l00AQ+N%fZG^QB^S14-CJm&O`5#nqLr{z~thBYc3`Icm5wl8s;qf>qwn> z7B=*N49T>(6i#h0f2s3Bzp}2nszXT%eU|HOU9iqe!6w-RPZtyVJP1-F97K|(EK-#i z6e^2CQ9dJ>J3Z z%!x-IOZ*dR%P}n8lI|;A)zXZcm$=X@bNMlwf8{~WY+S|7==~E8V9tK0zUPrOeF-M<((^+WP7lq( zQJ8r1W?Dbh-axy*Nv&`0WZjk&qoQ>s?-_TAiJ|{R{{;lB7#h_8009IB009YyQUC`F z0RRFaApr>l0|NpD0RRla^a{cB3IGTS0kPJh0{{R80T~Yf000000000000000000pQ m0B`_s0Am0E02u-R02B%V007v#Su}Rp0Tl)T03d+vfdByFQpgnm literal 0 HcmV?d00001 diff --git a/testdata/sparc.7z b/testdata/sparc.7z new file mode 100644 index 0000000000000000000000000000000000000000..9b357f09d61f3b40c27f21051112a3c6fb6b1c42 GIT binary patch literal 1895 zcmV-t2blObdc3bE8~_9-Z|olR1^@s60000|000000000OBzckGF<%DlT>w9kMTBTX zR8-e59M1xv7l5AYiKJ!_|>&%Vn*yQo?X1R?AFG{2Fye=Uk%ac8442LxLWSA>hUCPBiV z&LX1jzU?$41ZCdk>pbTF{|L636CNAxgA5Ha_xySgM9d_?-9?mBn z8sB%nZ;!WX5@q}u0Z;>u<=HT_D=IzZQ_p{<=Za}c-}Pr@FIYT;dA+vgg~3`Sm6A zJ0v;X=tQhT4kVcgK2Mu{(QPA`w8o8V6912(SraAjAy{i(b^?k?J2Vixr4M@u^JipgR4q%k@o{JdjK95|eN95`1?E}&&@iFf@ z1>9ohPoG;(LN=js0uqjM%sqhnSyqihj_Fg(R*T26d0(+t?_3(0)n74T>r!gK>~seS z7I}(S07GFW8Pww3$HdoO2`V$>xFE1_&R zDp;YDFdg&#g1S-tWy zs(36)a#9h+WSv_*_6ZuA*_14oqHUm?^UZGM&?lgut?R+<2{4Uohnj?!K86o+Gyy_jDGXT^BH@l?z4 z5ju>Q`@?Lvtnt}R2U*Bwj#1H)jgY)^#lC|1BaXS7Kz=~%op+PcxDEnL2de;4Bg4bb zw}JMM;SFv;klch?`M+Sh5|dAZC0NuV8~={g9(!I7J{@{DMv(2ZE6+Ut2yDI@!#ExK zC3N@)DAFNS55Q=fofM*xM<`3>U4po)VGA!pyQp)}?dv21Hg|~34jzBB3H!X!w7B&1<~*Oj%4ioOA3Q&R58(QIUcXOuMu+r>~j%X-y zx&>IH#f3vjg?^E+!d^?TLh@~LPuA)3cCM8UNo)p1^HvsBv-n zpywnhuiet?-Ue;Fu^Mb+1<>+?^Z?XK5F*5*!FesDlqU{ej|*SMPpmeY*iteT1SM*{4<9;z4zYbLoFo&Wm-a z{-kldX7e$53kpk#R!zYEyBo#z;SRx*)b*;Oq9lv^wyc3_CK~^ItF252YNfAVjZNz4 zZoYUbygpQ5wty*i>XcCWaYJ00JQ)0R#jC0|*5H01UBUv0wlQ3IQM#{fPho1pyfW01*uUa{zDv hVE}RfV*mgE6bb