From d334b54846a47b82f8fa36251f9eccbcb7b83f53 Mon Sep 17 00:00:00 2001 From: coderb Date: Sat, 16 Dec 2017 12:54:32 -0500 Subject: [PATCH 01/61] rar5: add test archives --- .../TestArchives/Archives/Rar5.crc_blake2.rar | Bin 0 -> 60314 bytes .../Archives/Rar5.encrypted_filesAndHeader.rar | Bin 0 -> 60686 bytes .../Archives/Rar5.encrypted_filesOnly.rar | Bin 0 -> 60336 bytes .../Archives/Rar5.multi.part01.rar | Bin 0 -> 10240 bytes .../Archives/Rar5.multi.part02.rar | Bin 0 -> 10240 bytes .../Archives/Rar5.multi.part03.rar | Bin 0 -> 10240 bytes .../Archives/Rar5.multi.part04.rar | Bin 0 -> 10240 bytes .../Archives/Rar5.multi.part05.rar | Bin 0 -> 10240 bytes .../Archives/Rar5.multi.part06.rar | Bin 0 -> 9696 bytes tests/TestArchives/Archives/Rar5.none.rar | Bin 0 -> 101393 bytes tests/TestArchives/Archives/Rar5.rar | Bin 0 -> 60066 bytes tests/TestArchives/Archives/Rar5.solid.rar | Bin 0 -> 60046 bytes 12 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/TestArchives/Archives/Rar5.crc_blake2.rar create mode 100644 tests/TestArchives/Archives/Rar5.encrypted_filesAndHeader.rar create mode 100644 tests/TestArchives/Archives/Rar5.encrypted_filesOnly.rar create mode 100644 tests/TestArchives/Archives/Rar5.multi.part01.rar create mode 100644 tests/TestArchives/Archives/Rar5.multi.part02.rar create mode 100644 tests/TestArchives/Archives/Rar5.multi.part03.rar create mode 100644 tests/TestArchives/Archives/Rar5.multi.part04.rar create mode 100644 tests/TestArchives/Archives/Rar5.multi.part05.rar create mode 100644 tests/TestArchives/Archives/Rar5.multi.part06.rar create mode 100644 tests/TestArchives/Archives/Rar5.none.rar create mode 100644 tests/TestArchives/Archives/Rar5.rar create mode 100644 tests/TestArchives/Archives/Rar5.solid.rar diff --git a/tests/TestArchives/Archives/Rar5.crc_blake2.rar b/tests/TestArchives/Archives/Rar5.crc_blake2.rar new file mode 100644 index 0000000000000000000000000000000000000000..2e89e107c4e3f783a47caba92d7166e8c6771af3 GIT binary patch literal 60314 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 literal 0 HcmV?d00001 diff --git a/tests/TestArchives/Archives/Rar5.encrypted_filesAndHeader.rar b/tests/TestArchives/Archives/Rar5.encrypted_filesAndHeader.rar new file mode 100644 index 0000000000000000000000000000000000000000..d0bad6d438e329028acc65b2f16ae8aba513da0f GIT binary patch literal 60686 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�#g{akPlzqM^iJ^y$E@DHzovy&#mq9; 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

;yD&cI}LR z;jrlkqiagT#fn(7EZ@76djED!f3x7glr8Qp8)k~O2@6f$@87id%lVx>71O?axpc?s zd&JtnYZ0XjcL=APzLK?E=JYHXpG9vsmI>DAm>T_W`zdd!!f}v;{oRk`^X}VY*ne+k zvs^#v;N*o@K0G-7>a@hlD692;I+}~l*DYd6j#%^d=?Ruuj1Q(MY!`hJ?OLwcQY*Q# zA-aY4y400-f7SY8GkmBt$Hiiwq-?fZ--pwoq1cC`lGi9^MAVgd$xpn>g7d( z2d>&*I~2A%>C~-?OB_8KktgJN9u{c-mw0Y!=$icEy~6zHypFTG&b0*DExDYg%n&q# zGxzf&W5>4>)8{{x`klzl;}{;gXlB*!3oqhjlC}Re~FG#NO&nW@dc;_2Q=T zMh&9o>ZLk~&n=^#EuX^gG|N`vmQeAb-yTxiGmoBbNql%?%WUD>hc2u-=X0_*#(Wp! zrG|);`+nsu*n3UT?c;IP>W1>?Qq>JL)gjw-{Pl{gDgwUzsWb4BH_~w4-aUP-!EXce z*$p3;ojomKzw7hLQg)l98|T_WzORw}ck-v0%()e2wi`Jvgx-q_FLULT*RDFIQFN)& z_oSYkNucAZLkC}S$1JFm72EhCqRmk5`Ymbp3ho7(hZ(+4{bn?I@)3Eh{JTFrHp|+) zYP69%x$AeP%3Zh>&#ulBt@QL-zPw6#tseD6+7 zxDozqtIS6K;4c;>xhp>KeVFv!$0Lj7fw=hYERzr0#d_`i)z-FZdHl-$lQ6&I&!Ww_ zcMZ20Oeo)_W6{adc`tX8hM%IE*cOhR*Y1^D7joO~Gtro$+WTJa!!7@rFU$8<^xyLg z5u6ZmI&IVYEeBOLtex5QK&){R@6!&pr|vV~Exvr@%Nagl)>h}LGgd_hIMtdK%;%Nu z4xPB=aqIcEc|tuA{cBmh);(J*ptVrKVec7(r|$wMrrY>W;Ga2(Md!}S`CHF8YR>*4 z6nwew)|CTG4_JR%@QL%2z)pv$5ksn@FYLdVSM*FHg(ZE}n+w5Fz_zldB!~N`i;)VxAlXsGjDnlx1p1JrZ#QqA8Pz+&=1Tk(I&YFJD`I)%Y1mCoM$vf_7e3n)erECM`xA<1 zmB!UNux)Ppk#uq4-U)9~b*63X`5&(JM0C0A%&km?-5jTMqW(50J#>}cY8SQ5kL@u> zf%e1#~%WhJCxbqh4egngb`kHCiRX#RltYkOMEV2rL=h9J3sD7~Hj<&}3EGI& z_(=7j@cK5r6_fcGPUoFabD4f}nz&$?vd{y*6!CydnyEMQ=H;|JRb3aL{oVaPM^5lL zu`}8(2QzPlT{@ectC|03!@2A{xkXR^Sgrrh_L9;1ZJpD8p%Yt*v)!Uj&B?v_tTePr zAj30e&+M7a*Eh^M_i@V`HoY&)vMlp9`7f@oewQiQ5|zX1k+JmemRAwC|IM=b!sj$! zE{c;oq9L}?WP`#?^D@mMV-Jt8<1F_!ZLW2w+x<`Z?(v%jXBE@WZ0)nYTV_q{&%0Ii-VDFB&MkV3R>tqBh7Mc`DA8AtEwdPC0hS0wq zp)2D{8SjP6P_3vc<^FH4a$K%kJm}ovPNNU)^Ut2zKGX5~uMelJxS1I@l`F6Mp>pF? zyL*kq7vYE7Y8IWCckQ56z{7Wq;`$E%pO?tDIy|4q_M6#V>aD^vb;e#+?F#l^pSb7# z)|&EQN8FXKyvvVFpC@`+ul7Op{xyy6ZmwcAB~xwVxF&^tKfgq|R!?&8x11Qy+sAZc3tz2OBl_zTHmR&H2sr` zvts$GcF7NEm;at{@IAQcxI^Kj$tP{h8#jq?Rw%xy5smToJ33P-Sf7(|qTKdXGh!~t zyfYMyH{jM`hz{(W^#7*#6zLDkf8JeJao*g%c&gqpkJr=Bys>+g`Cs9imgG}T4ez)w zcbrA$uIu~y<+j$0BVuLD!KZFc2>s=J@j{8-w@;r|zB4qW__RjVZwn(pUT3R zUFC&_m$Y49yk60-e9wrsc00JW;tRtqwUYU}n=jsRo<3=r+2JMS zPgZWbc0XAA*|#YZ?84ewC(Tfsy8UQ_-37sW-!I>`h}x)l)@<7XK7+RV8p;s{HZwD0 z67Ph0svb&m{CZ18h9`8%T4S$^hlBY~JKh(4v16D29zMOP4)f;v_Xvb7{dR~=bgpA@ zU;ey*O3K2k_6bbCZauj*^plMIc}_p!kHXu$KK|HN#+>!Ehg~Y`?D-Yl#csENV>TD-{=7$M~{fkH()61)=vGPxBUEj%|q6eIi)X zdSTM7?7-&KG_Q*{Cp}($;zq?bll#ZKW?%d$d_6B|--puO$K%<*N&J#lU9f7WVa(~> zMqCAtoy_@!N}R7TW!b%z;96hx%Sz|x%763A{e}J}`jxG>i*x$Kd!d+;e!*> z9T7g$G*)eJKXT*Zsj8)k%}kmp>kH?1vIIuUDr2?Wkze`F)6drL`6Zf^LM#V7in z-kAM(_61eF=|ypqZdX4w|KP^r8F)bOkAAA%oN&Fn2UM*4+SaNrFVW5H4h(QQmU3~? z0}-AF+s{<}m4B!AnBOyJlJ~}~H@4>?bk%(KyYn`ms(ZDE?}E+AqSqg5MV|U{ zNIQ9DKtSIMm)(D=qJA#;ysp^p>f7IQ_C<#p`sioHw$@nZH7VQ2HklS?s_XtQI&~@J ztJbF_8z)HkcNI3>{9(FwcIf5hQ9s^_NwqQYUY&lbb6VNe{oP$H+nTk@E^vyjc;nUQ zzvSS=EuZ=q$147r_ILKGiujEuI|8^T_A4K6?yb2zrQnZL(1|-evzmTZw$E3)vH0M> z9o(liEbP7~eiod|a8=sD?k|to0=vdN`8;b5oSJ;Deb)bpuhz|6RD0|~A4_ng!qaQr zhXtCBf8@MpCnRv-M{Cb>4X@gZzW-*#9k=xB)RCR`d8^5eGT%(wy{{fTVW_$_dCST3 zE$e>HxZ8Qnm(lmwx3CT?EZ1Sda2aT(zs7?Cx zxcldxlWJT0Hvhf*MR{8JP0!nmzFaxx+9y>wbsO~ z@zWlmU?tWNmR^%WwRO?OQ}r^k*U9p|P@EN9;$u|S;}p5Vwdw!D`|V=?gjQ9s@1Nl4 z@uhvnaki(2j&oYXW%U;x?lE-IQ28(Yy)VGhPwuAI<*RN!jMvu2-CTG@Fmmy(h}7R* zu~+`OPcxeKr0eZQOV6jeoBV>p{uFG!^xWpiLJ98K4hJ2$e(z6EvQV2O7|d_F#gr%9 zF5=CSWt`mZ#$E4OC%3E!ttg8Pu9mhBYVN<2KCNeYsrmmYXXN?A9EDYnJ4voST`W!{4()p1t2B34soSIE_hTieih{ZRj}?2%l@0uW`}xLn-!nE$RXJj+{vG?#qH0=i*eifCh&8dZrP%` zVqwRjqx1R#Wwp5e>y(-PnOhXXQn=)kVn6%kuj&4KlY}D-YPua&_&2*HEcCV%(kSiO zc7MsF{I?d}_h)qHs+d+En^$$ZNV?BcXKlE9_7~>;j%t&BDE^xyr(pUjlCAe!@$57F z&%f(8_};1!n6&P+hmF{(zl*i|lv5^!T@2W`Q@CW#hUg=9Qjh!=vk2=5v5P1y=`Xif zx_H(x?Z&^1d%QFMe*coP^%c|N6HP~3xmHh!TB5U9e-?Y>+jn)#(&q3#>^oh2WxBT^ zx2f;>JI?1Xw6%VIGn3oOMt5hT?wiuo-}BdSdjuanA;O$$?ROl}Z;oExqcHz$p;Dpl&UHuj)n{@ z?pNK%J+bQA=A@|`-mNprjbY{du}PLqZP(HzyG(yq<1+v6l5-Z+v>&_R z*r~nUm^p7}`FmCGc`r1)#cnSPoiWeJ|J%;=i?Kqbf!CNO2f2O-n>1T|UF=nXfbwW* zgNTinS|tovg=^(P7Wl>H8wDg?YF``~zWKQ8T>nZphJsy8jE6EepK6=^$gYEyb56TWo#|)gG^~vUFTGxP;$*S@kGp#9Y09w=rq%Tp zEx(b#pR|E9Gx>k&Ee4o1QA^%=O~f*UZ$^98{3M|H%F@ix0bY>z(%uE6|t};(mB~ zr*%<=d&}mudlKR6*afam-k_Sh>EvnWeN#nW&DcCWVD;^@*Mx&zPHqcr(7a#qFDUh{ z!Q#b&j-Q;1xO{#X^Y-U{>p5zvWB%SP_Rxa)$Jg&(dw_-Y%lr2wO9 zjtX0NUb@63r}nWbMUQ>g#4j5^n#g-^TJN836c@v*XSQmx?X9CT(%v7k^RkiPxV=td zqszogsVWadwVqc@b+cK2x9?5-v`^o-%2w=)*~*`AUS+ek|HNcZg>6E=rra+#?drO; z`atUZB_22TYU@iaJIPv=@>wq``Zg}$iRn04}-fZ~qi`wrwCyvB7X{-GpZ%JA3Wx_tbZ$Tw5 zU-L4Z%%;ab&_3Cq5x;b9wE{=gB)jw#i&oux5a|8r@V^juV$1;ePTr4m#YVTe$G0#M{?Kd$T>mN7)R^d*EO6@TzjOPb`q-@_3Z>gq2}Z%)+kzvqe+`uGgQH5p!+u$b0>ANnr(J-=r^J zu1;le+7mP7kteckHW-HNlP*6V7{ z-*S7o)3SHAp0CdBr(n|g;%J4R?1obD4_xb)Q0zB!M07!DR?t!DX_x^erPOP;T5wWp+p zZ1&qQ>$-x2(XI7vHFal-6^rjSUC}mO+E}vSxbNP%8*IeaXr>+A#cvlGz|p6*C|Rnd zdr#Sw&oM#2xp$RCxhp&UI#80ZP}TO{7Jk#Cs|_S)is_e19X8qX>(ccz4jpd09%uD= zs~InTQTz7c0&npHkG1X2#&4IKoLyVH;J?M%rTZGK^b*cYe!ukC+*^`4d|l~((hhA{ zsHJ_5-Sn}tud_{}(bTJx<|aF+I0q^`z4>!CcTcX${X8k>usc0pYc{V-NQkl(t#neD zq-LKcSKrQ9^0=B);Q58T&Xyl;GuXAdl%y`ITz?x?b?wUL6!zt73k(@fOgnT)Ku&A@ zELLgpcMa=>wAS;nZ}Hf<=sdH`%xPMS=h>NA2Qc2&G&@`?wsCs0V0?ew`>D4N9ewhB zi{2O3E8$(i=b3)b5b{(IRQtZ~!+ggbA4ZOY2s(@Kk30;mpz`d&w6d>P!{6ce5*+9z(O8$T_IXh#_YHO(2YDa;&L5i=T@+d&)!2EGr|0W_wQ0G3V5k#np+w)>^R}tl?^(JnwJJDM$Hp6PNkv z9#?SR-E#Ed&kyqsa6J7fdMIj{#Hq#=3tcjPhu$^Ka+p%Z*8e)q{`!lcZCZ=JE=gcy zm%MQ+@QT;;?Tdvy*4>@Uy8Gj~BO(d1du(;r)%-q};daaZe8u%!3@5kWyu#Y-bNp~q zlbc+w^U=`KEh*l*meD?rGIb@4=PKSbXPnZjU-v_NU*&;Wi`(sk{TEfKFYp&tnRoQ; z%J4`&gB8DgPB%?#Y3nMuAS5oYyCGy^qn3!K@T>X>rUe2gAG=+Td~MOa&7CjJqM##V zo7WLnyKs%S9m|)f9GqDAgGcO>+f22s6Q;JWj-0pe!o2FkN>h9s+$So&Jo4z2)~6WV zHOFtAl4rig(#svf>BDzrm$5!z6#ZO8y!QGj`t^^FhP&KeUbcMuivA^02aeiwyDVysIq3H>Q8iMtO@4`e^6THmhp!%C zsnmMA?)=Z(>3k9``w!jAPp;zxh(3BLZh4XKWo=J$v%mwT4|I;Hqf zy+XLi$)jOc=PZe8y*+7SQm~NCnq3br__W$Q%42$Bd`C4a;oZwnH-;sSwPG8SPG3yz zPD}mE`D<(2?gI;set3IOs`psY`*|;8m%BQ)J+GbltYypJH?xk&s4+@!;5N4Wkv{o+ zbHwY(f)AD6ED?9j-MOaO?)Lly|54r%H6)Ptlpbyc)VbJY@QRMq$>9 z^2COT#mr&*%4?aj^qlL{nH*ZZ0jJgvI& zFs;2e#;`%^!-I;g+j$c!Urf?F8~OY8Cyom`>pqB0=06$em%4eu-`z{6DVCTXXH#v8 z5DVTQ8pX?+vNVM6?N6hG6`STSOO5%xwwF<@oM-yN+geVRyW$_``lQ$2KJuqHoBLa6 z*YpRM3vMqw{UdUH2CH9(K#jH1lbKtrU+OI`oSl?Au=lB zp~Wo5q|W#XoEmdPcTIp^i<9?_oQegD{!fkI6lGc6p4iY%n@y;I047sZ2YTVniXP)2tBg$<1er2hv9S}ObKv`qXrq%?-ZPh!< zzbh*q(LAUSJoV|ad&@kV<-hYi?rq-Z@aoHQ-N2uhBu-BW=6EDGM`VrJkMJXh=bP5? z3SZs+eCO|kk6{~|S4v#Wlf13WsxIWOx2&1L->~l*$Cz^eoY$ASRwU-9_ z9#*r*f(Q5mejHo=WW9!~tGnFfif7pz`L~?oGjFB4y-BW6>@In7v$n)pZ^Oi&BJ(U4 ziB(cHg-Xt6&s|pMl5^*zLi?{s-An_C zzTl&;3L72H>3eF)_}>z-E9sx(t+DQmNh>q!6?qL=X$7n6OEi}}yyo&EX5DK>uM_;Q z*WCCUmH2xfqfU%`rcj^A%5xHP+V{-yJHFvBQ;)&HbBiktggf7J2vmyq)?eGcBW_y4 zn#=D$Nc0Ff1~pAw@VaiXug9|WNB`cx8OHrrB~T>9|K6&tzF%9l?)eIoq^!w!YSC7^ z_4%f~uYWmiGdq!8d?u~F-(ZLJNvl`gi^^6RUa9fY`nUS5>@NPqnfY3q_gs}q{+RPz z(x=5bXZ0$*n$mUA!3%7BdpMQ$E-AjdXmxJnPKLe%?~@#_-o7ZZ`qytqi%Ulpx#cyg{bn#Po%ow4zLGxIz z?b|ISM_x-eX$#8#5It_1)RZ@;^R?vv zcU&{)x^M@5Hx1=}9V<1pEhk4OX5xYki%%c^QE+R?j7~2x$%(NMVjX2on;*AMXE$2m zS`qbO{qmW6C)Cc+O?j_y|9jr0=)g==PI(_HjNuQpzes{M=>pm{|eX8rD zTlkT*sZV!K)2(%!*OA~ekM(W(w>$oU^Cmv&6AX*F}RNbM^VccdviN`)2sL zl(B6V`jGih=*k5tAHkxdk5>Nle`slIXJ4IAB=(}kb<0h+Df5GCW*nH)^-4LQC%rD^ zWQOna6BD{Jmuy&G`0cLP<_7kFYr*nPj&H@M^Q0U<K?%8e+FjVrM>KRj>QnZ=v|ApGV#Qtz=(+Bt!Y(TxGYJL96db*&k9X^|)2> zNLTdKN=_f0s9!32=7;Vtvs);mv0ZZVp-BbXgPDAUBB$icdh8>vzEs9CBcgQcJ~Pkd zdY)^Ja(-8BKYQP~_C#cR)Z2`zHxkXqf_gR65AQg)P2{qnK-YxXlV--(i|{SF`TK=# z>VxB(R&wjKKANxU{%tm+_{Tkbi8J{_)<;^)oKwvcza7r?{pRdf+Rxnco#O9jAD{9h zSd&42p@GZ%Qn5|{+zO4@?rdlhi$3$>@}}wcyfw70+%V`|_*3GdxOlvr*p62|*A-Xx z@9|=6XPWcX=>5~Dmy@nNHrg(d?Ka)2e*>T6?(Ltpd^<5?X|To~gE?m=e?RljTV#HP z{OLVA4R4sT78|lp5=^QyVd=L2y;@7kI`6I3&IX~)ZizE^-?YCok($Tc$#D5>t51Z+ zjMD9OcQh+ZWol)l|L`4@_7hm<5cY10&BG$@&)i9Y^DGbe{Jp%3^SuB1c`l0u*hTmI z`+N(Ls9Efzrq3ExtRHaToKjXRQ{4-5(cj<tRe^y_2|9aM^ z%W~Yef2ALbe=YcXy#e1p^^@ZNoM#4IH=KU@mqyqIbU&}ye@f(0Qh}%O zJju+SKe9(9WsG%qOue2s;X?3%s;W-Th2BvfO@F`Z2!FX=Al*ApOtyZ3MOK5j$SrwDP5Az#r4GwKFnWFK$eUpB$lIT7Nr_DR9+We(a~k_Tp;_@|Nb|H4-$@^k|Av;&V2v+YU{_nxj*Mt z-jchJcla*nLJ{GM1}4k+{x_A)dBT5NAmd_n^KRdi|E5ds#y3^D)m2ocy4$Io|0koj z#7mVoZA)|MZuUj(PM4VUug)xToLi=FHfFNnze3S}a~FMG_U2gBo1oy0+m>G7@R+ee zF<|n4wl%I=GV?u*YE`qiluj3|3A!U@>u}0=&4sw)qw(@4t)Wc<2USCj9Nr&ix^w7J zR6wMWhTnK*e*4MSAg1(lIh)yj7H7QN{m3R&{;z1| zyX!A+ZZCQ4vb~(^%BPR6ugczB+Zd*EOyEYW%a!*B=Lo<09w;82Kj)`F%-6yPJ;&F^ zDLg)D($|>TcY5}Y|K<{&9h{PLJdHo6Zxnp=lS8rGYgf~kVyo{98nsh>`14Da*6;MY zxpQa03C?i+Q!5=gn`%T_-%MvQxwr7moD;na((68Zu5uG`kKmq>u&{?^on62$?(mwr z>Mql3sfUauc8f?U6zx2wb3$Ep`|}zBzY4QS`(~$@E#1z)XrY3@x*qP+2TnJg_uY81 z_?8D_^J(RyYyMYn{jFQjf387DY2U9k_amJb@=H0F3(sEP-Lc->H3yDMIB++V_dFbGGaBKR+7FArk*~`kVfr&p&wl zt+mWDU|2YZ`83by19!V_A8d*uHdio_2q{lIP)# z#nm;Byr-Ujn-*ifd)2AkpKfgUfB0{J1`pFlue;y&bSS@Hyf`Q->%@&)=Eu}N@lI^9 zRFnR3_lux$#0zduAOFoIN3Sm4bkeK2m3J}U<)HqF zin(e2U$u6LYRDe*xVH9g5uWw0mY-s}&L zo$e~!No&nHyj^$x+*&y)=xXSjw{sT$D38U|5i$;&Tm!RU>^43zqG4s z^S$p1pV|XlJd1=yA1#Zr534p`*ZN|{<^7A>_{?M8TdBrAa~2Q3U(DRQ_`8Kz-rvLC z2YwcJ+ZKMd^f>jYf;0FsXVH}$r}AwHuT|z4g}uytrP{h{f99Idj2rh9UHoJ29;kJQ z`!%uc+15p(j*FI0+;4kwo2bdrJwCHeJyMZAv0>lzw=Yk1`n+L+M8o|jjF%1{ z{lZ)CX76m~=>IxVYEs3F=e~YZAFUK~THO4Rtzy>StxgA)A9c;y{cC@y$iJ+Evujw3 zC%NVJoYGMik#U}ySiL%&uIpqv4JD&HYV@dX$RGs_`a{a#PWNei)8c@R zCV3?1hJH(6p1RoK}!fbW|-e_RMpyLq4s27V4{+_>vfMgOAA? zUvEhICKqV^rb=H-_|n_eN=I)WF%dtw;ccPn9fmJoTP}%yPxoZ}u=Ks>jX8WPry0N7 z-?jf)!kpJ$Pjt^enV#l6!JGfp?+wvfRRXBxLwq#pTk@`h+ec=44W!H%S*irY>eyD4`3`7g_4LFMArfA4==EnH_*kS}!X zP5=EzdyS`kX`U`&+Mwbu{CWSOw;AiBjVGjin)uAt@cS&0ZxZJYOkKanwWj6xvt#}p zhok?e3aL6NbhrK99lt*Il+5mf=VYGU)i%05b;FTGf$2-4KH7Cfv)$%dxcXz9#LPc+ zC!XY=aAPad;B=xb+3?(m@$P~}Y>38Fwa0|}Q>UdXCqpk3|yVt1QXJ^dz=-)3z11$ot4uD3j+Zv;X&#slwYNtm_Xh z)|3mkTl&W6Ojxh>M*-3056;%Fa&kZ>VozbF~f;pDr0UiT#+)}pukakEd1D}H=DW(Pl+&2vAkO{$unk+i_`55 zvKfx{6Y?ibNH;5soilO-fJT&z1jy``gA=GLN~xzlD`bYz%+>*&#-l%9lU z(MPVHCi!9l1^;SKua$|CdlifdKNd6Y zF<3mM|IOLVLeHI-mN{-eFn!L(7(3hWssFrF{v8YXJZ<~Eg(~dl7HJE**r*)i>Ss}x zezA+uN@bbAm;WbsEN%QG(W}+eS?Kap^4xsg*Vf1OCQb;ql31oTaj|Uw=f%0w(*rnS zF6!21eDzyx%fvGG$E7KoI#=G`Y&K29W}8WX9>*=7@+I@)6L#rBFl6G2}DxpuNe!Nh>Zjt>>`E|w*UrPi>!CjB(o4tN^Ebb~l4X8+{}q9J=U+^F zB5#G9@t*NWM@r+szn0J~Pb(%);d=SWG0?erbNqtK*`MzkM9O5yM_1fq`PXxQqf@qL zXm;s~OYb+mirpCIQEc%1$%K`s^e$B0Vq?EvB>S`P2-n9yr7JR@pOmrQfB$IZTvNj{ zOQvW9Wlh=^nZ}@hpqD#`(JgbWyZLU1uXXFEyBYo04$e^gV6-MJR**q!Q_Xi?&0XwA zj=%ZGbD+O>{rNQ;CZFSaa8Y2&stfURHd)B3w4R=}$!?C!B}U#AvzsltA4G(SJgI$s z{D*5(?T4>gjMw=4bOi3*-f`>G@ow+*{;MZqj~DVk=}}3Gi(4Fip>1>7{1v~pe0nSQ zZ(8%L2aERXnta!`*(kc#p>S?nbD3+!>{|}jx}Q*9E?rYDwDESTf^me(y*o0|e{Y3JeJ^h) z7S}m`_wXIVU7PzA!#CXK64k#Iqwt)2VKmR|fheWT%@xzA7P3%5E1hkS^X z71(>NCu^tiEUraSTaNExe`2_5%A*HUe+GK9xTWoye_!gU(oLU(mziGAD`3cKpR-W( z{VDhU&(-{r&OerV8ah*-D{_^)^rLlJ`nkO&4jt}y#Y{f zoYF$;UlnxiJIdVncg@do|6}f}yVO(F9rF0HtbDDbl!C)8aw`1}eeB`&TdNY(X}VjZ zDUf4PyV4q+UDJi#+if>rZDbGeZIpAZPc_`}-_>{9{`-L!{(kS!EBKQ6o>k~OQ?@)ZbDP5v_n*5w71&+#ND@zWATr%3cO54$G?t$MqEDUR#vaY23 zd_1F+{g_!t;nW`w{SSx+TLr$F?XWZNh_h7R6aBtpzm+wzqqH~YH!?|1@J(K|G~~Jw zhe)N(KUd>N8A{PrXRqJa{l80N{&&rs7@w1Q7yPPOZr-2w^+L&uUjiN$hAR`w8*VFo zy&HT^#jAVYhG&~U&DiWHzG1t}iZYv(G0Uog8x?nzy_;cw(R{rl{(Y16 zM51ruM%mfDy$cpU5k7yYzh!~MBGwqCb8FeZSyh@{U2Ay6kiXq+{ml7)xxRl$WK+8E z;d-gXorm7%)~sMQ^5@WyHGAK={qZ}V4RiJ{J|w2Ye5#n|h038SX?4SbV;}B*xu#Pn zeeI=> z{Ni5a-p=pZV)#mrF=q&t=NHq&FM+ zW>&mAYMF0!+wfs$Zg9^Jop0AOV~hHVH@uQxdgb+^Hvv4MSG;Dt$||4P^`_+5MZ<#@ zmPU*-xW8|ZuKAcSRd1o#uUiszCL&q_%LB@*OD~<>-0qZ6sp|Y;(TqiRrv?WJ2`=`hL3l(M{=yR<-QkTde1DT^gOms=1%+ez8 zfa&Oq*4U#Vu`M}|FNU3p|2(nQ?Ao7`r%M*Ue9Fb}uvdS%%Hm_cJN^4pl@Ez=W}Ue7 z!q(mL)WP_&fKUIWjZ?2O7yPn3aAU7Y+oSu%yXzx*;w)!n&5l_Xd-uyX_Qqna2R<(y zE>BzX=W95qtkV(mZG3q%v=js`Q_aP6uS~m`3n@S6Leg&St;rHkWENGdPU*~yv3@1RrFY%Gj;Uh#S-*r?(Sib{tA6^#U+rA$!RPeL zZ7T2aKP{;f@_CjRk66gB7f9uwpG|Z5_@j3eZ&3I+1<}VCHIQYQV@1Yov3WR<$tlSfaQm* z5S``QemUehKdmVVhnBXdl8+-}a>%cRU6zpCH9pJmz==fCl#ULo!|cAj-t-z9f7 z?E2DTC#-e&y`e{V$krH_zh8ovuk3PPv~7=yT}DOYhBMz|g3o=}5IQMQcq?ZYL#A=~ zy`Sr!z4+q4?rBpNi)=yLPKI;VkL2ywUHIV`}Zkk2|bImIsH>w((g~5)@HFnD3_U? zpSytDl+!ixe}js=h;dD34Et|`tRj!KX~r+)DGz%ZcT3{tSqn4sf(3$Sz)>E;Xjj%hGMnO`%_qrUv4XU z7tte9`RPm-#tqMYU&qx11?AX^U;oQ|-!~@Fg~*aboC_d6P9%jds1^ z$bJ5aspoxLxwHO(!tZg+4L*A`7l!W13<@ax5$WJ?MeF#L0{O`Dl1?oqrf)Bu)Bc z&zYuEev@NKCTG#rZr)V|_Q`%OpBi+I%=7NhSz`BOi|n2yhg|gpw+pujp7Oh$?xZ=H z&3wxp+hgB*O;2#lS}OZUaBAOw-Q;8SDR#x4+{eZL_}aY-vh^%?&h7vAY1IyK(SVh& zHt+Kn)wEbGc5v3_&j`smpTixx zN8jZmPqO-Klby8*3QRwFx~A-y86)z0d4S2iX&(C?o}Y91>fL46)|xHWR{HQ!E7ra@ z)%F3GrfuLMA%_UBmz({yePi6OSg?GPJuP#8mmt@dTL;B`-vH@yj7^Zt8c!X+rD-wO7A)9yYw-NDS`MlI zTz0yi-Ll-*yfZC)?`2lqEAfkaY|}1zf8v_|Fyl+w#s4C!W24Muf2}|57HzP5$DK9~ z<<~s3_UGOHxT_;zkE$?-_3|I-Hq8;Xyq7AZ_isz>ReHQzv;T~Cfof9Er=Je*t+twr zO}@EENx$vR8mCW3I4-MZ#Bg#IHu4HxxHi{9z)S7$lQ4^We-t)dvO0a_ik2bEbny$f zSpOgP-(fvz@u{g>XFjcUo2IlXT>sAFqVGmdo9&`GdX8Sui@#*GZrz#sllx!E@UEC~ zU-g^ZgOtENPdjzpr+oMI%i7Od+MkX4_Q)xPlgl<$HNtqd(LS~*-{0sjezt2`YQXba zw&+u5@25mvyqSI1o~1gsK1A#IrEb-X`R9_i^!0gpdhGf6_Fl41iS^k@(mz84>)wb5 z1XNEg$i1NaW0tgxOW5n45;>n)k2Shpd-tArafIpHMe8&bFV301tERQSyA#KusXmd( z;BFwJLH&D%cfwEh?)BPx?0&celFnyd-@ux%Y1+?4f0xbwnZaCa{lM8S`mm+* z`yWdfUhBv`oFzAVVzFOPe96-9*+(M7XIK8XZkHc;qt7q8HrygjL$O}&!KOzHtkV^% zpY%RzRC~8R*SuM0b#suusnepnzi!#<8NdI0-*P?k`X9PyI2eUYu1~!+z3FB-W6h3L z$%UFff?2GlS*usu&(VAHW$j}Vn;A3y-22*dTuO!gjX@^9+vI`#EF4~e;*e*a#na%$kM`^ooSmsD&~q^tx#|ovrpLm7s->Tx_{Gl*uip`^CclP`Anfxtf zxHE09@-vB_PZk}C%j9`=V%@ISGoNy8b3FK5dd-JU+oQ~HjQ-rV3JK2_%(<=Ew5_r6 z5UUu=f!lBQ_sohvv%9OA@k&}r)SZQ;^7=Pta0MTDbxW&4td&FO^M}C{eO1mfheCZ_I?%8jJuL&e*ITwzP^jS@Xg;- z^5-i)tX?ho@N`1jV+*4>;rB!jdA>Nn8Wrd-@;%_sn$_Jig3Yc*?+7qw;aZlfYZG4j z;u!N?(~a6uozsrwOkMcz<5#a^Yj&17i=^y4zmV_#wQatKS@UW?U*CCC)0jWo(CYmL zU1^gNj}tXBUU@mZoiBJ%de?y^Y|SFa(_S0RYS)+kyIk`9|A)t3e9d@Jx%>QOiLYPu z=P%}aC~@a`48z4$8A3(pl?uPMZT;9`$gcZI`-Rl=kDqR-hsLIcRF&9XK6*yD^{s9W z$EF7zC&V3RSpA4GI8@Z>DL%z>{^NN!r%IRq340v4(4y$E^{vmDcdD7E{CamVG~a#) z&yF@hjhpK&`nJw8_PX45$e!WC#s7?V5+4^F^hgS1*mvWm$JvGJ1s84=tI+(=^q9#+ zk>OJyYt-~}pQamJv<{ZgO#N%IqQlhM#Y|?AT=dzUlkJ)Q39w{VXqfoDR*sgwpyJ|h z`yzJB$(?ftb~WA=kMFnSu?h5}F>I*snJ4@a?y9K1x{&zw6c=&Cy^_nD#L$ z9!>r# zm{D+yORm^%`;4l4NA9{^nj9RF?0WUV&I9gDd*({bXEb@YPHSOF>Fq~DX0C* z-&?LYbeN|YrJg_kXS)3!^OmMdXR~HXJm3A`!yIpiSDT+0uS`Fx-8fZD8(xoHnq z{!zX6Pv_;&L$5W|{w?@-U+3=ZUe)Up7*ozyAg}ZqB_6bk3BWKVN+CO59JCtd>&ix|5%;{l9QwulVZYDoQbK$x{t~ z7kqnfdurD*u8!rU>f(~-slU7zawM|GFF9mBu`a3NaMHYrhdcJVRW$o)E&TD*sk!9( zqxW&?KLtc~Rv((fIHN`H@b7nmit&frXY$mjozfO(UN10zyS{^`YjLV)teD+KOJ)5- zy*v3E*h9`8+OS_zrY&vyg_Hw@b>Dx;$*Kzc@9p-lWLv2gCVna?#n!q=JnoG8=dBNa z&9ceL^Gap#-2e5lZhYHSzvr$?7Bm*Uk^L|$&wU$reKNEB!*y=c`9mkZ&rB`vAdPQSlobw`mj!nyoL}p(7n&`<9 zB(2!Yo$!8{o=(;Ir*r#U*y5dMhXqZwNEFzl&o#a9V$|F_ESgczfB%V4Zv1`z)Q285 zx6d8XPYsWRvf1^7PxF&Yo;G8=`j_-VN(b-dF!NiGH9gpAh?tY$_wb%5) z_S((5Wq;l!H7eJY&yu&8{VqXe^S;6xvCZxow?D;qw`bd~IvSaL-gxFg<7$;mo7^OC z=bHr|54vBq*X!G%puF*CL#@EI)>^%3mPyACYW6V$OTQ-0sOtEo@zZBT@+I|szlFBced>74=e~cQ>070z0Y7I@7vkBea5fKgP^B0S* zh1Ky!uB|`1Vcw0NjXciDyTvuuC2Q6`p84-(ci0-Y=`F^&3%@YzKYuiG#%sy@7CeQ^ z_;PI@3U>W^U3O@liME0lr>i_&$?a5B2+U50C zCg(d$jLz+6DqLQhJk~=_!ky(KdPF(!+%X4--M-`%aej{ zF)D37QXI4Jmrr)#;krG0%OC13zH&a`xtsW~t|CbP@{bF7pS*80oQyX4VZ)N=D{wNGCndJ_U}JL5!h37v zHnzREZ#rd3>W!m%FLeUv&HYloOf`A+^%v9Am&u4~aBe=65&iGgTkaEcUhX=0!B%xz znDdkJzDvtvisoy7TXbXoUpIZ7N54fa#L7*pr><$sDt`9LwdY}oZ1<#sPxt2Xv-jxA z&F;=Wac})%<;mgBs|2oIf3(2qPryML$tjuTr%h&tm~^Lk&geUK&NcUb?d$vd_BmLd z6Z;U=B&n;86#ieg~nC0Vscj7bcyl+Y_l_Go>tuRV7T=`M>!=gNptm^?BRO zzlI(Y>acm!WFokJ(e}MQe&UlqyIY00_{E>J-I=+;$XZ)2tm1{K*Bf#EtW`^Ay1rU> zd_B+Bi)vf}`iX3&_5M3WWWG-6-n1Y`vNt%jp)*})o_X3-mKLpA+htZ}pM_3nO^=IO zyNU5Pw}@F?b7Sv5$?%TS-@osvZkuDY!l%3>?}%u0+jCVuOE-&Cw=JJ-x7pDpCt$++ z+VkAX@@Wq})(05N`$&sDSy%G-PIJN1Gk#|;Tw;vKY0T&eXP+Tp(=ET=<;bK(lIN@r z>IzNhU9(q2P+z!VUgo5f4S(LbD2U$mDQ`E4kTWUnKXK2zrS8CNouGx{Ydeo~Ob?V$ z+3Uai+cuT^iP=0Q6aOcrFWKhY+pV@ZJmj;E@yh1)8r$v`|GLh7#^#$=4C^WXH^kg6*8?vcIu>>rLmdrgglZY{~k5Pjj)ux0x2#m=->cwW{&w zIWJ_g_~-u}owpC2*--R5th{g6%KLW~$nSh=b)O;a=H?C2wM8plBrg%qH{Q7Ls$l5O zivKp+2^sZWCdYp8Pxt*$y6#)pza4*qLiVukT+=1L%Vyf|d$Sf7W=v&_j#?fWER@xp zJYA^m_2STmQ{G!l{;!&n(b^sETDzOS;ncJ1P2r2ZHdPyK(R$SWwEod{v*WvNUH!>7 z^VahOt+0D%>gHDkpXjS)oKO*_)vSH+$=8yGS<^yQKibacc+%Q+OMUOT17}(mZj9Kl zGblWt)3a9g$oE^D7p$7Uy#42vHLfpL{N1GH8$0#!G~3!^K0R`|y0XQd(mT!sz7|X8 ze*DPm)^(La<9fGyE3_AUH9WkvV#fE<6Whx21517`-znl_yYmv;o)^4^S~5r6elWj% z#aLNsUQ!&$_pVg4Ht=wecW|tjc#3;`QDx&TFU}?5KA(BT>ny%b&*t&EXubO@N6MW| z3r`6>+$nCbBj2ET!35uPliS=|pU;`F`1yI(6Y3uvSNd}vkAKFn{&(Rd_N~T`CVjWu zIdkg1`l*2dcPCj}O%YOgU998k*&x69<1V%vpE{KGsmq!*&YawJE2!p|TBz~5BT7&TgxFA9H@%%vvsV@@U|K4{jZ`s?5C~&jjQ>ICMMx>b94gbsxQ2 z@t&a}?2}D-s_x-8FBQJNv)!|?-glqm3WI&=6WDfF$!%hJYrSsK`F*UqkK(=_UMi?v zf4-oA;b*Ljq0&M5h0|Jk_THZrx#jZ?jdsJUAHUgdeqLf0f818PC||`t?6KCxl+M6}V-y_w*_Ykr_9<&t7_}Pdz%yIi$5w2 zHQBc|>Y?$gY0LA?KNjVzn{@6};{;~y2itxLDfx-1?9-ItZ(qz->A*dap;Yhgw1|)C zn=Y?dkn45TO7e3=_{pwG&m52CZjWv{bjYz4 z|Dn`}1wy-}O>Zu#5}EgFW$n6j_4zhEuWnkdzxHVNk(ED^BF=11DG|LO{eJ4wqU96q zVjdpeAJI0kP+&&)+`HdbrEg&uGPz`bHKk!aGfVoCE1O#^d@Wut37&s|W3qPMh9rUg zUwD!tqpo?k*;yQYCX;vL&dYiH?UQVFxlDR-a{W)~l3(XH#;99NJ!G44%e7+ijL zyWj6`EaQsh<2)v?GTACS&SO&Y+#6pwO0?c|Wfk5$`P%h@?;C4f$#pMIJ`R+r=6c1W zQaJJNNB2FIZTlZu-8!-01!su=j= z*~MSD@veyP)hkyXmB07k`S`HM!zrG#tV*LM$G(qs&b?ier3C)R9^32?+cody`Q0m? zoMF?~wfd30&M8JcOwoAyj~J1CJdf_?gtr;~QDHQflvV9GT6Sm}o5#G>Leow!X5ltx zyQjAQU*r@0$&%LM7FMg5%b05Dn^lN3ToDqSo_^bRqR365Y_Fph-&2bF-tF?Ji7KDw z>(1t2bu%JMjpK$+(cBps9Xk3#52F{hN5?V8FWNInjjh-AO0fdiF4W1uTI-1^84zA+7BXgHN&kkorN3nr!I63 zFB06Qa&TSewv)3PHLt8^H;Ap2u`f2U>{4OBt|w={SL@9*gEI|wIxE7}bcF5*uuPPy zUH&3tZR01ADaG7cs}!?)oi+w^&(@o4*ukY^FC*NdcT;xLk!_(XkIHK-sSZoB{n+yM z;c50u0ogYy+>X?ITiegOJmuiK*VzH5v%RbSlqjSq?EF1_^AhuYOKx0=KCI@Rx^+iS zWzrRn-|NouFFq~*j!{*6Ws&?ranr7E95aqJ*&A_ME-`(#`p>7+8yCBM3HxwFRBq$s z1i_evN#B$%>ij(;B7XYNl@|uzHfm-rop7eV=fJG9Cr;0HI+yvHxBOr5#eUPA6qg^< z4R(4ym|&4I_2cWS=hFUVE54WEoxJy%C{tuar4xrO?@Q0KRh%&!iXXHs)^6+Q`c!$e zWY+yBCOj9!)EresHdsyB-hD!Fn#|!o>7_<*MH&|AaJI@>Y;f?8|HI2A>Mk;K@ewOW zAwKJl7e^#xZ7bG4necAUX8%2`Dvs}Mjx5vtyz5$&IZOS$&$8RKce-n&-&NC}71 z`#ZA@^Hq0Ap1)ts1jru&-TTN%^C$fYb@m6+YXxaDkuhiQ7(?8aZV_TM_5U#hzltYpTJ%Q{J2)Ofw{@Bd~Cj%KkXq}i2%;NlH#Qgmhgp~SB`4h(5_j`=yr4RgITgXb=GA! z4X-(OadODkTz|BiC0(>!!MN=}0%y-#xz>hw0iRAot0I2RmwSx`zHHo3zJZVN$I&MH zAG!ytK6KY_IU#rdZR&|Av8=ADD{YTDQ<)kWS#HhlR=9gnNM_c&6JHKz?vPy5@4^$( zz4?gmY*p`wCydeM>({SZ8hPb*OTG%bQtCnd^V*IsUA3Wyi%!cuoAJEX>+Jjcu1zNs zGnr2p-H=EW{<^(*(;1gG>8pumMTgV;&u>1QGTkRou<%h|h1F#H3{Q#cF8-T$|MplPvnF!g`$<0( zwSGKQ3i9~bB)hL5?4sDY0@vM{2B~?@%g^pKm2)n+ za;?XBN^p0g_Vk0LwTHGH`Ro_Pk)3+J(kAh&${!P@22bgtx9m4+;xEZA==!AA`mru} z*#aiJX&ZOW`W5@r_hx_Azlrxu*;CB#FLgfSJYk>Z@8oTUd%iw<_I+6(n+c0XNUXN` zmb=uiH+ur{lB*nC}f_vHm>Gp5a)7!b`pW8j_)QopYUJu`$vg^EKBX^#SVXN@- zS^v}(Cx=GzH_IG#dUbex`NFU)@B7UPLcD)t=c>+@GjQz6&1!unJyi_tUo}HR z@6Mq$U3`YRJ5AE;)e=q~Hqn1^v-8ycC)$Z04;ZaYoM~A7C*ya%`0fS2w01`|*4f(5 ze|z_qtUG^0nqr)N*lVo~2Qup&+8isBxq3Qd7iKeyZTqvyE19P?_-k*K&O}rD>$mtn z-hbs&dv)W3-^X4?P1w9iVqzVZyap2Da>F zDN`m$7b>jOR1Z`UOKyH3;&^$-U7k=YzT2MfTXxT!-rgd3@vNobuhos7YyO`2SG&g7 zFHkkg#^P)JyfyOCg4Y#i4nHRlArH6Nn@4{lwsL^3vttvk{on8M?# zBhDRuCtYsute~HbHOl`E*SXzmJY#CT!Q$ZFq78Qyj<@n8Y?YAk)%kVg^uOP^7g=Re zzTS8)#`Mga!)2S^;kQk!Qwx_r&%4>venGg+I>BZ^-L$vtmPLUfCH%}rN6znJUA{A= zP*wR+pV7;uLe7&@YbHJB`PJbwgj{m9qO;xlKpt8n=5BwVyTiuIY&;{SJJ%p{*^u!zbk z*mDOx{lGT8dEl{Nd@nGKu{VFBWgB`&R*dVJ!hrd=u#eR-!3G+t*^+VIS` zAf=4uRT8@?Lt9SNB!#8h)#h%p^`6Xe>hRm0%R)65DXo|?C8^+#pmFjhaU zTJ3z((eA}2-M%v${p51Zqb%l@p32+xuXD2AZ(G&8@28*N@SXT`c9%zHMXkmiyF}K% zK_z9onia2k?s%y@|EfcCt#-jvr^8miRc>(q`zOxV{mLk$LT^`Pw&Duz)2!+M=dWU>Vu|Wt=U1Y=EQ|lFvU|#46th-6Op^K9 ziv2(LJ9-$_ol_ECKYyCj#f@7Rc`PzK_Uyn*&t{PW|+M_*@H>{@aBL$*<0`=1%pC(2Ad6dAtM^zM)Folg#R z?1|a5CD_zZ@m-lQ!V@SYSL?oXEqY=lKU@5<)x)}otvwexS=P> z|FMX$$rQz~!y)nE6?_l$|4QGybM4p>&gjPv^QNdjWcuQr>9yqj6uLN5S&Xgrf75{^_nB8G zaL(di$NTJa%)Fp;bNB9(Y05nG`mggnd-r29cKgB(_iak9DEOLx^5Mf%;nx?~P0#k7 zsx;@X)27>zhi<=L))Lpp^JMnUE$q`~>He{q%p&~8;A5rCKmU6dm*(#gTIPM{{Y=4K zuawl6wAlXl6VH#GKIh77k@C%czh32NHnitg7MADU|CsXY#K$N-?`pT=2dlZGnoXv& zXlY9_a=JaQIkG9tb^g(gCgn>%0tGB27F)|p@VY;kyyd_ASEm#ErdekPZ{0hUGjpn{ z>nF2K@o{b|3S z`!+3?dFNAmpzFt~N8X-qJej*^SO*+9%UQ~}kwtRSzlmmTr@YVKnb8zedO3Ms(}{Pg zlV`*{Zd>*9<Fv?BTxfNfr zAj0zZON%{*1*%KBf|f1RVw2Qj)i1c-`m=hM*d-Izm+!Z1a#cLC@86MsrfSOqMJC?Y z&M9`SxODNTsi51cWzz(uJ(7CnM#{#yH|A~-d|Z;xA?<8q7p3znIYpJrqHD%~>xc8t z$cPmLBtEt-YP-!cE0i^Ndeh4Xvn?-ah-H3@;$$r`iP>_XK(|!3P{_MpO>*|ki%++7 z{C=DfAihU9=G>aEvzt!&=LjuGG0~o!Huq1{n#syoq5ckPh@Pm zJzw)wU){{D7OwODR`xAYnYeI8$M@;^(dI>)Ci}nrH^*>FPuA|oN|)1K_y2t*teY2Q znmT)>mtd>A;9fbqi(H8?Bu8`OAjBN`!!7WiG%Bt$<7`c3#(5?d=o6(dWsiH6PXOHCgs=qxdz)AD7cA_+NRf@_L|YxqemH^6$0_7M#B+ zq%?PrlAh(isx{p)_3J0>=yx!3lltzP?=)uuYrHe}`J`_vS?90c<2-rl`|0To@s=~# zx2{{Z-%KjV@ydnkR-WSGkt>tZS{}u2e|zT7`RJ)V2UIs~jQDXzdfV2Jg0)^g0Y(9{ zmt37EE&L#?;?svS*WLD5e`P6t(q6Q{iK|`r!R1x|UIb3-oFcnVTKSr?l7F@IS>sT1RK;gt#mEZk1Az=nqv+W!G;P$Q;y2*v!1}^vU~y zi6w0HA#}eU_GUeWxq`J*NM< zZ;kl0jF8ojrl@S*ZeSjake+& zy&QVN*BaS%GuwNzx2^Pg^U$+hDNpv#l=kQHYLk0HA1~zcYWnBmWINwz--)UB)`l7E z`~ISDf|kJXt@HGpU-s+IInp(K>eH88O-Yp;p;>T0GW>w~kHNr<_*XoPj z6nCxvtbX9>H@?|=_Hkv3Y-rh*DS%npwXKL=9eNV{Vev@m7`=J|ecxT-8GmXm3bvij^XEl%RwgWT6-XCI! zf0B0Z;^n>=k2+VSJo8Uqu4IYN{CLG|x(;KcNc>z)qj%HTm&kp1Tzzl-)aj~DXKWVK zH`~d~J%06s!XF*so({%aR$uD$OLBum0W9pTV~(;!N&^@YL0s4i3jw2cFi=4>dhxTDNKD z?%nUE?)ZJ=(KVmvX-5hLdZ_|RLT{x7S}xleNYx_!sV^MzAw zxhrQJTbqe}+`p49~UnUJa28 zOE@I7`jnOSfk#v056JJ?Hk146vw~BJmtG)KiSW6)M44>=!9F7p2=O{ zvk`ac(~@MpvgQ4x$1Ru?Ka!T<2llWDI#g~p>E_$G}iG{bwXT4R)oxQeGs{$t$ zCb}x9$m!gF#KZh2=i*62>9na|gKOq`a7i)m;1dnL8RuJk?KfAIV*8@5ch{|c=r^Zn zB`lw{i8Dy@rK#IByG8vcMXzyWD0B;EPnlYFTOfC~_$0m`QX+FV6^T9jp8E2OU;UanxBi|W(WvyEtN){D@9a(TL5tsa?_GVO zZbj^x30m)7hB@7gTbka|b0#z}W+@BP8>#kK?T0gB3{4IGX})_C>l3PAD&@81>02$c zQqxO67(Aby*1Q?b_UqvK53l;R_?#{K;$-P|b?tuR&|SG#9E7Vk7rhR8IDxVK$j-c3 zoTr{K2v@F?{!uHwTvy1YC(S7Ri82%K0fV@Lhju3}y*`!kV*XOcD?PQJ&ad`LZdxi@ z#TeDZB*=C2)(gXRQ&uLMH9yXgfA8?p=!5`|!PnVx9%rY`5|5QQG2{G_M}H?my%Px zU#rH!BE#+4G`6~|Ifp(MxJ=3KcY1K+&!Sm6`;LbAIbMpYDv0~X6epluv90gc+MvHz zwpA;c-v9Y*Q@+F=9`J`&z|EaN~T+ClBwXXD9(#5Pz^)T-^^sZ9>+r)YY0vhDqRO=bV%SGsFdkDqAUwsMZf)b#h0b@?lMlqF^*trI;c zXZK+9{-nCOJ6`rHZcY7V#diB1*IKun?`8ZuZylQ#ntA5V6aMc!hlLy}=P6z-cQ#rf z@o2O76T^qbKYx7N?71oJ0IzX9W4l^!dFHoWWqZsHEqa-AKJG{6rI;eOoaCLWSl4(x zPWbY3;;SbYrFfY3^G*8m%Km8AbJyLMYPRU?HkQ|a?D+oPeDj8&zN(i0y0(mOr|!;i zJalP;RrRHZy&*D{ikb7$axR=GDOS>2sj17l(M*YLjdY;53e$ zX!s!ZLPy1Gb!pY<^P6OZ!uO6A#<<*O0HDonrEZ!jGY`~%F)cWbt z(jA^jXG*upthcXj`O?O!Ir%~M711XB4Qy_K`=36^n^|gH6EG#M?@PSc_vyDSwyv1{ zZJPhYmq%wPYCq(7=RNbwlJiq-j!3pm@A^=;Xal!GQc%phw;N2SPV-2P(wgh5U;3cx z$!fj~`Oqz$oJwJ}|D`0nlvLv1)y!G5eb(Vd`Di9)n+;PLSh{{PD6H50ncKk3z;m(b z!q$t87n}4-DoP9(8JPJM{p2>Z8k9vns#KDG+nP|+q8xhetlpCahhP6o{QTfi_}4=p zR-_fyo`1#uauOFa)3W?LK7OM!j2HQ(lR7)xwn`kI(Peu24o}=|6rAKLdlO;V7|+E;L9tU#U>&dyx|t#CPYW(nLk3_7kf@?8~I})+(r0>4cpW(EGgQ>ei@#nI;@{ zf5J?oVm$u(iq6`3(}2-vx=MW?6K{#*sXc28#hKL)%wu(Xd9w1#`g2=(zgSl7MR$zq}9H@v9_yM%bIO;!8VA ztBS-lT)OjP5?-A@R_}b}L`@H`x`purh20{3t1A-OH@QT($-FJyWR`!iQuAMyyGv8S zit;68zwZ6wzkF`u^v9YOzTFcUR`=DjvdjPKOwLzbJ8S*H$S{+(HCe0g_vB4GCbr{C z9^3WPzg@0<$o-jjPP%E4=L%-QqmSpL>OYL&(r_vDIzFY0LBneI&Ip_CugW*?n1^T7 zEi^wV_*TVXO@Zc(1v~lf3ZC*^IO%Fq7+a{hp#Hj(ZUKI>3z+r4bfwCFSbvl8B=;fv zDO~qCc@3`kx0@<>9DZ)F^k~((^W2ZS9k20w9ZM;>bo)9dYp%&v<9Em6(yeqe%=W!9 z;rQsFQtMy3X7Qc!DT@B_PvyH3A|?g@TEZxCH0DnH2LG7N<^CZ>kMFmaRGmE4{AA~b z<5wL^YT~z~d3Sg%J14wxr)K@k&--n6E|)teu^|4&TB%*-A!6E#GH2a4Rhv;{@9(K} z(>48FK(4cP!5JH$gC@SgX5~eDob6W4_;`}9<@ICbnJMq|f3Hf4?u-Ar(YEQ@^+O?% zg3hnrER^lx%QHG)=9hh9;s()+cd}Q{-ysvN=waRHST)f)b6;nxQ)60hP3&YrA3e<< z&6#g*^lX)6JE3vmrpm4O=gS2?z3+SAb$V?8tIXjakRoxp z(BioIT2B+Vo6*H5`EsL0xFf3z4zviH85?JYz7bveeqHau9HY5cmwH>KhPnA)khFYp zevR$_yIa@%S$BfPq;T&COSdxLzqf<(P z)wD>VALZ|Q0{9i8r~NO!x8(&-U%=xkKArk+a{uL7WY0~?a|})~-C-lEG*cp@bC$Z6 zQhd*>kVT(b-S{uPV=A6D?~x|Q@$?(<-d3N({CG|Z1Y6#|=X1RLw^GZFi1y=0_byHk zyDa|j*5YotZI>SV${hK!;^}YIu9zLuIYO*YuQ*cU+LL=jp~AN&kWJ#;gO|~FpEq!e zFiXULRC=YkXv^!<(+sRTdU7TizPlYIYnimTreEaxYIB&|gT)DV-hWQ@piVFfQezHyZHJ5^w{Y94_;ms@Hc_hip zdufK^iUke!&3D(yGlj_w1>77?4jAjQoDou6|$x{02xRz;E z?1gg`2Tr~D<((vUUjLZ3@xMsM63s7{AI#eRc-M6w2gBSWJWqp)WKZnNUcb`Cx$%ek zQCYoG+t|0A8evi=FIMsdYaHq3dDoK^Un)?R*T3~n(kc$E(#SX6Q3AnQHmO<1ejZ+X z{yWoj%jjG6C0j3Eu6!7#u0Ofs>(yf-mvVcyIxk6B@xRhSv*mkXvdO}j$$}T=YWFA1 zpYX|Y>Y3=7ccgP7_*1{GIMDrc*Nm^l+r-a*E?jkWx7Foizt+rdVLdnD4p-mt^qQze zS-wyHuekLl>+d_~Une|wKfG<~v%)%0_1nA2*XJZ25Ndw1a;8{HNWR*K#dFtBW%cDf zwC?;Wy=@cUoaZ>XJH(*-k8$Ad(1J5rMyoi&4t?2s?!r>({}oqx3qrde zoIaJ;TsK=L_&^iOK8?KfEwg?)2edH0J9*=Vzyh&Ax$X&_3lAzyd_S#rMl;v*73*u0 zx869GcTlNGa7T~Y`Fr-3QI@m5)i-iHWc|iuKT&7#zLz1-b-L&eB?bpP`)%}cf8WVm;Ywrt=P`nWovUL7@c9D$}^yAXkPka25FH@iM&rhSs zoZXS9idbhg`Rv|XudW+^@8Rl+OAm^zPj3Amb4~fde+jAft!JZezFP3G?!ndeEj$X# z8AEX;a-fqry25BRV}HnP z-8I|Ra^8$?+hTDtz34@9viDopz1#0U&J91FVfF5J-D>;Ne_o95r6N2hruvqpZDX|# z{5oS_>XSp+=VU5xFdct!$7fc>@<2^a4F;Jjn;!J}#`N9MWSFp8(|3ndX4xg)B|Td; z*G**RbdGwYwub#p=pmE&6_fNno|tO7ef^xfTTkm9k#9Sm^djz#rPG!@6*|r?Cz<^t zmY@GE*DH8&#ct*gO`Laj2VL7OYJSq3 ze05drwtR`3I}X0@JlNa*vqZhe^n8{=gO~WC#CHa@eG?7#d@r4U_1fa7I97poHNA_C z7i4QPFJ*k~zu>jUl-#maPWv8}@VIo}GF(@)P{Z%o1&y;U9RF(qKkQp1*=mq|p zO=H&!JGT4OxvrVj@aERdA9fd)dL^0lx{5km)I9$;zvo8C^kWT^9j`n8sWMldt&yA} zqIj%R`x)D_VrF(R56$`fE6qfAOFLY!mva&hLQ6)KX_oL+)55)Y!*x+b$9U$?6+!Lm`#ts+*UDa1O)&01ZaiJ6;$ zl|g`kfq|icfq~UEx1gkwigobgO@du~NqN-s|tw`l!X8OEO(LT)j4CAu; z@L+TmSq14_%uLKi&liAHoV&1WDIYW26-I_Dj37&x0{xB7zA#EAZ|8s!WT zi_RJSKghV^l&WVc!-^A33Ax>8Zh&mMuz`tX%Wfux-;81XAe*uZ(jhiw6{KUeX^AQq ich%~iM&HI`3!*+cg``eG(2EZSgSBCyn&g4 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 literal 0 HcmV?d00001 diff --git a/tests/TestArchives/Archives/Rar5.multi.part02.rar b/tests/TestArchives/Archives/Rar5.multi.part02.rar new file mode 100644 index 0000000000000000000000000000000000000000..d23d82e4c45c79ea7d1086517504a819c66f66e5 GIT binary patch literal 10240 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 literal 0 HcmV?d00001 diff --git a/tests/TestArchives/Archives/Rar5.multi.part03.rar b/tests/TestArchives/Archives/Rar5.multi.part03.rar new file mode 100644 index 0000000000000000000000000000000000000000..159c55d807a09ae035cb67e1eedc5aaf236c8313 GIT binary patch literal 10240 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 BFK7S& literal 0 HcmV?d00001 diff --git a/tests/TestArchives/Archives/Rar5.multi.part04.rar b/tests/TestArchives/Archives/Rar5.multi.part04.rar new file mode 100644 index 0000000000000000000000000000000000000000..cb0ed618847510fb77e571abebf29a13c899b6c5 GIT binary patch literal 10240 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 vyfUWOo{yPr2Lr>vj)RV`gPxWPkwxwT=+* literal 0 HcmV?d00001 diff --git a/tests/TestArchives/Archives/Rar5.multi.part05.rar b/tests/TestArchives/Archives/Rar5.multi.part05.rar new file mode 100644 index 0000000000000000000000000000000000000000..ec132ab8e5a1f3bcb11484de8e6523c224141867 GIT binary patch literal 10240 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 literal 0 HcmV?d00001 diff --git a/tests/TestArchives/Archives/Rar5.multi.part06.rar b/tests/TestArchives/Archives/Rar5.multi.part06.rar new file mode 100644 index 0000000000000000000000000000000000000000..8b90861086cd1e39d0f0b3a1932ecb6d5d7a044b GIT binary patch literal 9696 zcmWGaEK-zWXJk;@!y3!W$jZUY%Ff8xdb**3Aza=rP>YG1yLAf#%a+|t3XerPI2xE4 zc(Mx8^-EHVOZ2h|(z%$Kf?M8ng;}3rbh?rE@aRl&`J>HW_idhKHSO_5p($}jRi&(yJ6AmP)L*}op1AOL^g6eg2#pheS=Mfhd9Ta=d4JpYPc6YFom| zdG232?M3#cMV(eNJ{^Od$cv99Pl`aSD+u4tv|$JAe^_U&-_C|7Z@^CGqSEJJ`6u}c~^(C{__1lcl@&5|Bqp* z#mbdOt(Ns0?&&gQpPc)sXZexD!}~)S{|N4J-ccYGq26i2an;u>xO$1f?TyyQ({DZ4 zaN@s(k8?Ul>mr%x1Fl!ImKwhb)=|@6_v2>E4V%5{(}h$n=uf;VaA+HgKu5*#7f0pp zRZ3l{w&qUv>0444;-T^_$>mDbRDJEt=3$_nH7{bwE(7Mdr&XKt{vU9#k} z+oH$PCC?@Z#8-#T`8w17ZT-=Qe5)*u7<0ba=wv?Sm|OZ;!T0sIDnhl=WSW`6<#ybA zQ(7Wz(^`DxL{-dzH~MSe-dw#y!Qj=zsOtMicIVF!x7k+y%W&Jm6`uBQ3wuK@pUO9_ zc_CM_R_yo5tLM`sr-yC`z7ZaDdexTQiurNcktWuMH!IatOj=p6=KK8bYO9zp&x?DM z>hj)dL(o5-DN3vTd-@BF`1+sm*RI^@)U)v9qA&Hni?+?3@XV>wDo}1|SmveW^(%fK zjb&E%IKU!vBqFty(@rg^O+M?NXk!2EQkkw9FNAyCej~lZTW*^3ql2&7Cwu=YVxIb`NnV8{ytwRr=)Vs{BGAehk33$HfSz* z^sKyj$IG{x69V5QylJVk`Ph)H^`5@5{<+mWwqo7_AFd@OZa&HyfY9{_J(p$@`YKB^B;{ zEHT%cM}|qc$J8if+O5;Sq7SWU@SAsYe`lqY#w4Ndjq5E}p2*VUNM%PspCW3KXNS+MI_siS@? zP9{DRNhk|v7Eb?UP}b6-8GT+yz)gX_kL#RxZpP;IWTu!Y%&Hc#vk+3MwW zsO#FrpiNCbHnxg}wj^e3ZPnWF?_S;CSNi+!-`jbv^m@Hzd)@43v7hI>pIiRQT8r;) z)U@SiE^wCpS|#+bT>PJ;VXQ>K)#tj0vTGyEZp$9ZaQVp`?wTR;#pvG#&Bu1E^W~>? zzWM6Evc8b>+1*$AwS~59HLJF7-BsW}b+yc_nbUakujfwsdiLdcEB)p9EB}e+eE(YF zRd6TKmj8^ex}w*rgZ~4!m_1KeZ_T5q{>sfGz0Y~o!3Pum{kZYxmIRyd>f;k09}|$M z+Lh7jxOcj80WV+DzLr0I*8dphmw%GoI=^CakDlEtqgh8@JZoI1pq)r zz}%$~n*Mfk_KIX_j%h1TZZS?V{rSl|zh1wp%2|Ek%86Xp$wqy}$=8p%>pZq>Sav%vSH9#j zx1rFsw@0^5EV62Tu35fTRC>v=U3`jr~I4Szuvw5lKrPw3qLjesSe(f`;za$YSYdw<~OrH z=uU6nCAoW5T>9Aklya&vZqxRQOTU^szv_L>`ujsM!^R_9H`Kph^f&3<-~F>R+njel zbWL1W<-_;$W?z_Wyy$bS=-nSUVx~*&sQj0}|8JfAy0DhprJ=#Wp;fB$&skfFT|e=C z-Kv}WWhI}_TKuZJv%uwM$byo}57)fazwh6m^~dPe2d(MeP2Yq#nQz<Ml?R+XF+f6W^3`dRtQFNW2RqPHm}X1De4kNKRd zy#00AU+?(+@(F@FZz)#d^At2bNBh1vxVepW_#%`>#p?O z9roz#X^|I&H~){@iuKH~e1I>;w*7W9g~Av$U#qmLwB2?0qHGk^wzzO%No3DC(GFn!#;JN4#8PS~_b2FCjdDngMq*G|kE9=Pc`keyK^IY?e z&poSk+_8Ck&5^U`>-O)P*7|E7b4k;docoOG71MndYaX^WF`WE8K3`I@`&@jLm%wW2 z55MB=k3aVeewcB4a)4{={@a^Be~viHaVz-PE{#r`i<3kipX^QkrmOLE)77J0j>-Ke zj(^HCu}@^QZb_GEu9*Db(fs!%yg`!^JdeiYY{ ze_lw$lY-kqmJOxpzXB&1XTI!o{ppysJLK1`_hyUj+vT3B&Sn+&@Og2%csZMw@#FGC zFMjyA3Qs*Z#oadS=+A0vfBA~wi~xJ7?M6z4-$QHE7fjlBIy~b(da~-@x&TIMWrVP+~wSso=|_O>$zcl zs(+fks_Nvcaas~zdG2d?mrNEq@;pehB7L9YisplVw{%^7D0gM8+^cLC^~Rse%=KC% z8m8!pPu5`iGvj@e)?L-52_N`9gY%Y8yA^RpBseK!O2NnS1ec!Bm%57DYJt(FdA8+q zx~mS&5c_`m{I|W)Te|aZHGEIDv{`=c+!WufeJ=_=y`IQ>bw_rIcz5}hSzB(Mzk6Hj z_^RDybJGZ?%YLim3)~A127`n6gI%m!PQ z&+*izYI|Ia_ zo}|6<&D1K7XwS-RhPC|;!rCGJJ$D|?p0l@M&e4gp?*)?vF%l;fY^fALxYx*xAjsBG3v0n76%HO>ab-T(_MH1g;8@{+}Jyqgx4F9Wz zqI=@mc)Ax`wMV}&{@JlKa(2{ZDXGgJBo`g}p_RY$*t)gOT-7z<)1NI&EDlwA+h^^$ zUU|8Hh0K9E&AqA5|Nbc8>-sIF8G7Z~EH*CH)!Mn5g162G%zaq4xT^hAjz;zg7m3tv zp{f6RoenK{ye?$clWR^oo0bM2`upltP3h5xze;;jAC{l*TPUU~AoVUqWzM$szwBlH z^>m)t$!KQ2EI|8~vis9}VHI=FzYkQtbMMpL4rTdmr+Z|~(sV9H@UN8L%hCEh=cvs2 zh5{8Kp1-%xbZww5NLsOa7jmTEzPtTMRK9PHbz;d8{VT8Q^S9qs66c)qx-z?8i1n8L z+4%ljmR-~Dn9UPP)^1S?4x6><$NvR$OdK?_4@s^N&Ju9X{!uO<#im>w|M+>8MZD+b zJ@*f8{d)BM?=y@ktx?bNIsNwqbLbv^{)+Qz`fY`r$4)Qm?55nXpZD;u{;_Lq>Rkz{ zVo(3N$}N?vOuPInu7T;JnoN)A0&j(NPBv*iIeM}-rJMN{^U8mCXMwP_uW{Gz zuWxUO+MbFvU34gKo|1dfH^1Kx4_ltneR|s9d(zkYhm@zE{V0Ei;mCSLAFD3c+_QNh z&S`W1mj9oPPqvT(c^LOWCoi)*i8VEtIqby!XC}9c*4cZ_omP39XTtVH%Epyb ze-{3}9e2R;yc_cljb$54-mA~g|DBlFYEZprN80lL3L8qIii&q{VNhH8@%X|3^Zo?J zbzWTz+Ll6^lLguMD^7}Lu-@j{v(SGDhw)4HMEkC|FAa%qt@3L1#fzTTdcR$P5l2hV$!k-yUy3w#@Yw8#C^G%mj zo+q4_IrTTl>EDe#jywFfH_gqQdn)PUdEM6?*S}i$UMs(RAuF~(X-!bp} z^Yay}ZN%0It2r)YuCa7FwMkegY4@RkTOo>1i?4jjaP0c^Y$}ti<`WgJ${Ky|s|U)? zYMm>-y+k$d&`qoOc@lzNP30U?P7by$%C}yc%rr?p9=`GHUg5^`E>g`Cl5L9Br1v&A zuRdrTb)@ZVSZ6{q%a23J@z);D54!(4{2nvoiJ+O+M2sxoRRk|cW>EE5|FbwH$e%g7 zc>U`QC*CdSYwY;^e1pja-_;8RSdBU+Rn02TN$_UNxw0xzOD};vaGI!Cr?=a$zMZ?3 zcZeJ{=RcDFF?9wf>w|=R-T9(!uKs3nC$AN*JJ@giq3P`k*iMMSBu|zSIw%(zmxu>f;*n z6rcA7FxM$PoeI>RyP&AKI$+g4uNC~7)q?xd&H%^@eZO^Uz$%33{dWxKD8j-^YP zY3a%sfoF|}y*FMkYj$cBU$-)NPS?K8oz=2msQ;bP-nYlS*2T-NhSPCV z`0?Uqm+&y3!(Z&yPB-y9bxpBY!%)ZDXVJXADfg${ELrXQRpW2z_Jzx=UuNmOGHVpm zpENE0**{AIF)ahTjsz)pzd#?RXIw|)Hg`|1ja>3D@NZx|OR(DCn|{?S9n@d-o{!evP1oB zzDIIQ*DOt!w=>eE*6XcdoNu&s@w)z_i*}#;er@ZG)JIFJ4yLBuJaXWmF;iRgv@N+B z{9k2v?Om?-KDy)Hick5v4Q1AABjpr7rfgxJzvfV^R>+Iu6$bNES4OHm&@`QAf9^)E z*5=zrKNc~Ymgasx>8~oecK6)weAa6rebd>NWw3@^%c!n>@X6CEXa}3Lg3G2UOCo0H zDxKxzxS&_$@yC+&64T*X|No2lP4RvA=17xbm70_Dyro&UEVgztzp1{llyTnkrB5gD zTADmazbpN2ZBpU7AmcTY*Zl3@rX=Arr$ELmKz%Wj=)vENUjw7ORnH# z+Yuu<>&s$}IwnQ6DQ}L+DqFRcG+PQ+^)YbXTKvN*Yr=^ck5a?;r!jvB=a*hOX=T}E z6TWIe(aDQ8uHAq1@cy9AFAtoa>s6&6;VfiC>w2FLjH#wNq6- zchE-l=BR(h87}^BmiGJEu(5YNT=?fy)r^Y|4hk1qKI2`#P-SY}AG6&NMeq3Lbtss` z=HJMyX65m|vQc%(ET0ug&SYAoR6#vHH!oExw%Mtr8WF zRK%F}?W=sMYi+~u%Tjj3ZSJ#I zPPlw<|EE+NnMYfFgTlGj8}+PzBJovEhE1)z>cDr0-<&Fq8(V^nt+YC`>_Q)&3Kx9% zs9$ThecbvBKco! zR_f|^y_b1$>aN~2zl~npy4O!GS6Dgu>xQJ;0cSgR-#i(b6s5Us)|=eR_f3Cy?p+iR z92{Cudq3Hak1fW!^5lbO0*;YtG0yKhmtRJzC;o8R4aj%jd;+}&9h z`AAJQGwyuQBKA3*>pUH7JwJWd`nx1JF|cK}=<&ro*$(%%Ui*0PBxh6gt=}Af*T!~4 z*nB$YnK5^Z{;Xw6wkzYi-`uY7Yf{zE*tbb@dD*9kxO)@&CkQXDo}rm3X0ZDb-)*n7 zynLSB%3*Z`mg%fF)78D(O7b2$V<~_ z#~v;=7EOB_{5nMX<209zy_?o9Ub(|zN9Nyob>A+9r{&jmJFkx8{Q2m(q5X%HBpsUy zr52OpJqyk}zx@8_{zTauR&yt=TD-!>=)bgA`qcYMk9VZZU2XUBP4Exn84>UP6#wpD zDO78IM#@$Al)%N)Dtj##+^c>Pxk_;HmWL<(>)MKI8WwsV>eFIk=3X(Cfu-vwgTk+b zi?cywo<>$#V72dxu+( zecJWSLi?A@vB&SWYjk%{3gegF@&93B|2MOZmTYdb3reb@>J{9QPW(8~P-4gYll6me z)ANIBBFt$`y<5y%7hZMWP?%V*S1RZfe9I{9xy0Ke;in#l|1^AfUcx!=>y^0bt*58< z?A2>27w5A%!1LST<6*^;76W5RZCQtUhI+jOA&0Em&s}0d^)Z@clni{5CpR#x#`*ryj)i~#yCW{?=E;UZO?ddD{ zk$nOC97dlH65o|?@WeAN4APwIp!)P+*+&B>>t~0jESbD@V&nIXnsZEe6fV_N7fSmb zpQyItThZsTHvV2uu9L?Nw)}}<(?}Bidb?x8sk;#$e+hZ7^r#F|FXrL%pR-}(WsO@B zVH5Li*}tmXKS^abhs}#6lfJ5Ll{-?Dkydx>$m6yP2Sq;B|9}3!KY9WCtxUtelN#bD z?X_!}Jy7R4?fJX)*zFt5Hg1V0XX~r+g*Az-d&(N_Z^bp;ds3_*cRg?5?c*z|EgNlm6wi}KL2KU z#ceKGuvTt=Q~E^xh+}(>A3m11J9*w#*ZBsHmG_QcdEmzXFe@VE+qo|$H;wFk|5cKr8~i?h>rU-mI~<=3LBpZwoHKY67x^ z)+wyr3)a?md-vZ}yC0WRVA^Xc_@yzgzeJ%-OzLyK0@y(WxH}t-qM@j!S1jSfb+Tz0RdP zM_5YOUd;+kYb~^FKb_jGg{7~aZhGowbKBGPR7mdI z**-Tp!q@B;T*@W2aq27Szkx-YLbp!qpZQi)o<|`?A$8u|1#B0~wC$z3b;S6;X~*-N zGXCLpVBy;-`z)E4mQCc=lwI7>yR*?f{ff!l+AVKx)yVIzHhn1{^5&A)+WyTp7RJ8z zE6>E7OSN|wsM6hb#=BIa&1mM5vr;=gEPZh?K2N!kgZrp_p7563a-oz@zK@KzY>S_C zp;7H#`~tDFPl$%IhDU7b^`yg*2sLH zKK9LacX?**F;6$Y)TS@KY>ooY!6~ncWpk!|Ie&A}q*>Mn9fS`ro?8CN%5`g_&AQ*o-T+HueS zCwZU$kg0#_=g(x;$`zkZxh{Ql`j|&-`;k@IcjKg6R&P_Etvl&SeY=2HdayCK^m0d$ z>l1os^BlULv(Hwt&b05lQFDr+c>b}Td*4r3oXaoXDROU8=&_C3W?rA0w$`s)_{8IZ zZE?xn6F)yE$hl96@wjVIJ#FG6*V$rw=3PBvaO;oNY`>Dpk)C~fzptG8ruiwW$p3Ix zXZFS7cjxD9*>h@F(22banHp79kB&4eKRF=vwDe;uzrW+WrQ9Yunm+?yJ@zXy`X0($ z6!E0uorX73NYxFfak@r8-grFy=9NIYz47jHTDmFMCswsU%e)XrpD z?%2n(Y+vQZmclpI2e;4bdi<$0;+f8tuUBf{R_)3?#xPA^*i#q#`_nGmv}W!nXzQzv^ACH&!#KCHdIPd zfBz!#=^Z{ZR`I7|Qh)p(&d^KIU|8rdJ7ss*zh%459hsPWpmnQ=h+W8LQN2f6)yDk# zJ2I<{ZB14@{Zg%VoHJv~qy=)pKRVwWd7Bfg`)Ee=>u+kO{?*++xM-!K$X($TYo;qk z+}?GP_gaf-l)zc}r9W=CZNI!>y=KsrF&I%mR{MMvTG}A z{3G=zr^}aJbyZQnwl&@9)a(C~PPg|vy>wQ)o3{4mqe-i+r>)GIwtTfg&8jn-)?Ce9 zTwPvXP#pGd%Kx**?_a#xmK>kDFX>eCw-VC;iK-X+cGoP}8Ygtmy57lelKcAZ_PO&c zep%W~v-|sUZu!|WSvNfVwGTM9CC%5m`0Q}jHD7u6C)ZcKIJKVna@tC7#hOnU8hhKA zw*|{4-Kou*Qu)+t-g5Sx_o_LoG~_2NPOCI1wV%4=Rruly>#e>#ycKEjYvGMWQ`=^$ zDs5Y~aNd*0?C;CnPVqLbQr@+1jcsz)srU=mFRqQ|=_|KkUU_6{1lJzBk7EBkR3dbi z-YDl_(|mohb>Xk$?SCUaTyt-!pSdnGO|{AE2d~%nz_{pk`$>5}oKGv=>#@65%5cc^ zwQi(REQbf{=D;;Ur!ReY$}Ar}v-Z9HyC*eD_w8$cFWLBVp5^i-fOTw%Av} z`uW(Yv&qvRIvC0`8x_=)MW*~N%ZqxaepF<2$LU8m)3oQD+pr~mi^%N*8udndZE{oZ ztH1Z`Ke=cc%W)S8zvjnzE4XKLZuDQ`mQ(B5S+UHX~% zM3(OlUtKx;+-O4^L(7qm%eG5xe0fAz=Zkx2#6u&dOLnUm%@3L|hdV!i5b;kz>6!k6 z7&mKmc_wCV237_E1_lO(1_lOJ*W7}VN-k!m=BTq9g00Umvc(^qE{m#$Ikh5{i<#;3 zK1F-b3aMrF;lb!CAZw_Yjh-(6saP4)YtP5bwu6CT2iOv(Kz|W=#^1&apS|=+T#hAM L9u~;V%EABub8Xi_ literal 0 HcmV?d00001 diff --git a/tests/TestArchives/Archives/Rar5.none.rar b/tests/TestArchives/Archives/Rar5.none.rar new file mode 100644 index 0000000000000000000000000000000000000000..276234c5f1a425b33d8d981f8eb0966408976f0f GIT binary patch literal 101393 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 q2M0)FRzW&MBg7HGEpNI&j+iIEB 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 RZ@NH^kSz}jWM*Yy004WVd&B?$ literal 0 HcmV?d00001 From c346a4ca948c5137a1adf511b243bfb4df7a708e Mon Sep 17 00:00:00 2001 From: coderb Date: Sat, 16 Dec 2017 17:35:07 -0500 Subject: [PATCH 02/61] rar5: change rar MarkHeader detection logic to support rar5 --- src/SharpCompress/Archives/Rar/RarArchive.cs | 5 +- .../Archives/Rar/RarArchiveVolumeFactory.cs | 2 +- .../Common/Rar/Headers/MarkHeader.cs | 98 +++++++++++++--- .../Common/Rar/Headers/RarHeader.cs | 12 +- .../Common/Rar/Headers/RarHeaderFactory.cs | 109 +++--------------- .../Common/Rar/RarCrcBinaryReader.cs | 7 ++ .../Common/Rar/RarCryptoBinaryReader.cs | 39 ++++++- src/SharpCompress/Common/Rar/RarVolume.cs | 2 +- src/SharpCompress/IO/MarkingBinaryReader.cs | 41 ++++++- .../SharpCompress.Test/Rar/RarArchiveTests.cs | 8 +- .../Rar/RarHeaderFactoryTest.cs | 4 - 11 files changed, 189 insertions(+), 138 deletions(-) diff --git a/src/SharpCompress/Archives/Rar/RarArchive.cs b/src/SharpCompress/Archives/Rar/RarArchive.cs index eecd27b6..adb9117c 100644 --- a/src/SharpCompress/Archives/Rar/RarArchive.cs +++ b/src/SharpCompress/Archives/Rar/RarArchive.cs @@ -134,9 +134,8 @@ namespace SharpCompress.Archives.Rar { try { - var headerFactory = new RarHeaderFactory(StreamingMode.Seekable, options ?? new ReaderOptions()); - var markHeader = headerFactory.ReadHeaders(stream).FirstOrDefault() as MarkHeader; - return markHeader != null && markHeader.IsValid(); + MarkHeader.Read(stream, true, false); + return true; } catch { diff --git a/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs b/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs index 4bf39967..7270a4bd 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs @@ -56,7 +56,7 @@ namespace SharpCompress.Archives.Rar return null; } bool oldNumbering = !ah.ArchiveHeaderFlags.HasFlag(ArchiveFlags.NEWNUMBERING) - || currentFilePart.MarkHeader.OldFormat; + || currentFilePart.MarkHeader.OldNumberingFormat; if (oldNumbering) { return FindNextFileWithOldNumbering(currentFilePart.FileInfo); diff --git a/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs b/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs index f5c61274..364202fc 100644 --- a/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs @@ -1,26 +1,92 @@ -using SharpCompress.IO; +using System; +using System.IO; namespace SharpCompress.Common.Rar.Headers { - internal class MarkHeader : RarHeader + internal class MarkHeader : IRarHeader { - protected override void ReadFromReader(MarkingBinaryReader reader) - { + private const int MAX_SFX_SIZE = 0x80000 - 16; //archive.cpp line 136 + + internal bool OldNumberingFormat { get; private set; } + public bool IsRar5 { get; private set; } + + private MarkHeader(bool isRar5) { + IsRar5 = isRar5; } - internal bool IsValid() - { - // Rar old signature: 52 45 7E 5E (not supported) - - // Rar4 signature: 52 61 72 21 1A 07 00 - return HeadCRC == 0x6152 && - HeaderType == HeaderType.MarkHeader && - Flags == 0x1A21 && - HeaderSize == 0x07; - - // Rar5 signature: 52 61 72 21 1A 07 01 00 (not supported yet) + public HeaderType HeaderType { + get { return HeaderType.MarkHeader; } } - internal bool OldFormat { get; private set; } + private static byte GetByte(Stream stream) { + var b = stream.ReadByte(); + if (b != -1) { + return (byte)b; + } + throw new EndOfStreamException(); + } + + public static MarkHeader Read(Stream stream, bool leaveStreamOpen, bool lookForHeader) { + int maxScanIndex = lookForHeader ? MAX_SFX_SIZE : 0; + try + { + int start = -1; + var b = GetByte(stream); start++; + while (start <= maxScanIndex) { + // Rar old signature: 52 45 7E 5E + // Rar4 signature: 52 61 72 21 1A 07 00 + // Rar5 signature: 52 61 72 21 1A 07 01 00 + if (b == 0x52) { + b = GetByte(stream); start++; + if (b == 0x61) { + b = GetByte(stream); start++; + if (b != 0x72) continue; + b = GetByte(stream); start++; + if (b != 0x21) continue; + b = GetByte(stream); start++; + if (b != 0x1a) continue; + b = GetByte(stream); start++; + if (b != 0x07) continue; + + b = GetByte(stream); start++; + if (b == 1) { + b = GetByte(stream); start++; + if (b != 0) continue; + return new MarkHeader(true); // Rar5 + } else if (b == 0) { + return new MarkHeader(false); // Rar4 + } else { + continue; + } + } else if (b == 0x45) { + b = GetByte(stream); start++; + if (b != 0x7e) continue; + b = GetByte(stream); start++; + if (b != 0x5e) continue; + throw new InvalidFormatException("Rar format version pre-4 is unsupported."); + //return new MarkHeader(); + } else { + continue; + } + } else { + b = GetByte(stream); start++; + continue; + } + + // unreachable + throw new InvalidOperationException(); + } + } + catch (Exception e) + { + if (!leaveStreamOpen) + { + stream.Dispose(); + } + throw new InvalidFormatException("Error trying to read rar signature.", e); + } + + throw new InvalidFormatException("Rar signature not found"); + } } } diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs index 30753aee..55434c94 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs @@ -1,11 +1,10 @@ using System; using System.IO; using SharpCompress.IO; -using System.Text; namespace SharpCompress.Common.Rar.Headers { - internal class RarHeader + internal class RarHeader : IRarHeader { internal const short BaseBlockSize = 7; internal const short LONG_BLOCK = -0x8000; @@ -25,13 +24,11 @@ namespace SharpCompress.Common.Rar.Headers { try { - RarHeader header = new RarHeader(); - + var header = new RarHeader(); header.ArchiveEncoding = archiveEncoding; reader.Mark(); header.ReadStartFromReader(reader); header.ReadBytes += reader.CurrentReadByteCount; - return header; } catch (EndOfStreamException) @@ -44,7 +41,7 @@ namespace SharpCompress.Common.Rar.Headers { HeadCRC = reader.ReadUInt16(); reader.ResetCrc(); - HeaderType = (HeaderType)(reader.ReadByte() & 0xff); + HeaderType = (HeaderType)reader.ReadByte(); Flags = reader.ReadInt16(); HeaderSize = reader.ReadInt16(); if (FlagUtility.HasFlag(Flags, LONG_BLOCK)) @@ -69,7 +66,6 @@ namespace SharpCompress.Common.Rar.Headers header.ReadBytes += reader.CurrentReadByteCount; int headerSizeDiff = header.HeaderSize - (int)header.ReadBytes; - if (headerSizeDiff > 0) { reader.ReadBytes(headerSizeDiff); @@ -102,7 +98,7 @@ namespace SharpCompress.Common.Rar.Headers protected ushort HeadCRC { get; private set; } - internal HeaderType HeaderType { get; private set; } + public HeaderType HeaderType { get; private set; } ///

/// Untyped flags. These should be typed when Promoting to another header diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs index d05840ee..66d4c024 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs @@ -8,7 +8,7 @@ namespace SharpCompress.Common.Rar.Headers { internal class RarHeaderFactory { - private const int MAX_SFX_SIZE = 0x80000 - 16; //archive.cpp line 136 + private bool isRar5; internal RarHeaderFactory(StreamingMode mode, ReaderOptions options) { @@ -20,12 +20,11 @@ namespace SharpCompress.Common.Rar.Headers internal StreamingMode StreamingMode { get; } internal bool IsEncrypted { get; private set; } - internal IEnumerable ReadHeaders(Stream stream) + internal IEnumerable ReadHeaders(Stream stream) { - if (Options.LookForHeader) - { - stream = CheckSFX(stream); - } + var markHeader = MarkHeader.Read(stream, Options.LeaveStreamOpen, Options.LookForHeader); + this.isRar5 = markHeader.IsRar5; + yield return markHeader; RarHeader header; while ((header = ReadNextHeader(stream)) != null) @@ -38,102 +37,24 @@ namespace SharpCompress.Common.Rar.Headers } } - private Stream CheckSFX(Stream stream) - { - RewindableStream rewindableStream = GetRewindableStream(stream); - stream = rewindableStream; - BinaryReader reader = new BinaryReader(rewindableStream); - try - { - int count = 0; - while (true) - { - byte firstByte = reader.ReadByte(); - if (firstByte == 0x52) - { - MemoryStream buffer = new MemoryStream(); - byte[] nextThreeBytes = reader.ReadBytes(3); - if ((nextThreeBytes[0] == 0x45) - && (nextThreeBytes[1] == 0x7E) - && (nextThreeBytes[2] == 0x5E)) - { - //old format and isvalid - buffer.WriteByte(0x52); - buffer.Write(nextThreeBytes, 0, 3); - rewindableStream.Rewind(buffer); - break; - } - byte[] secondThreeBytes = reader.ReadBytes(3); - if ((nextThreeBytes[0] == 0x61) - && (nextThreeBytes[1] == 0x72) - && (nextThreeBytes[2] == 0x21) - && (secondThreeBytes[0] == 0x1A) - && (secondThreeBytes[1] == 0x07) - && (secondThreeBytes[2] == 0x00)) - { - //new format and isvalid - buffer.WriteByte(0x52); - buffer.Write(nextThreeBytes, 0, 3); - buffer.Write(secondThreeBytes, 0, 3); - rewindableStream.Rewind(buffer); - break; - } - buffer.Write(nextThreeBytes, 0, 3); - buffer.Write(secondThreeBytes, 0, 3); - rewindableStream.Rewind(buffer); - } - if (count > MAX_SFX_SIZE) - { - break; - } - } - } - catch (Exception e) - { - if (!Options.LeaveStreamOpen) - { -#if NET35 - reader.Close(); -#else - reader.Dispose(); -#endif - } - throw new InvalidFormatException("Error trying to read rar signature.", e); - } - return stream; - } - - private RewindableStream GetRewindableStream(Stream stream) - { - RewindableStream rewindableStream = stream as RewindableStream; - if (rewindableStream == null) - { - rewindableStream = new RewindableStream(stream); - } - return rewindableStream; - } - private RarHeader ReadNextHeader(Stream stream) { + RarCrcBinaryReader reader; + if (!IsEncrypted) { + reader = new RarCrcBinaryReader(stream); + } else { #if !NO_CRYPTO - var reader = new RarCryptoBinaryReader(stream, Options.Password); - - if (IsEncrypted) - { if (Options.Password == null) { throw new CryptographicException("Encrypted Rar archive has no password specified."); } - reader.SkipQueue(); - byte[] salt = reader.ReadBytes(8); - reader.InitializeAes(salt); - } + reader = new RarCryptoBinaryReader(stream, Options.Password); #else - var reader = new RarCrcBinaryReader(stream); - + throw new CryptographicException("Rar encryption unsupported on this platform"); #endif + } - RarHeader header = RarHeader.Create(reader, Options.ArchiveEncoding); + var header = RarHeader.Create(reader, Options.ArchiveEncoding); if (header == null) { return null; @@ -146,10 +67,6 @@ namespace SharpCompress.Common.Rar.Headers IsEncrypted = ah.HasPassword; return ah; } - case HeaderType.MarkHeader: - { - return header.PromoteHeader(reader); - } case HeaderType.ProtectHeader: { diff --git a/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs b/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs index 6df617b5..aa8c73fd 100644 --- a/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs +++ b/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs @@ -30,6 +30,13 @@ namespace SharpCompress.Common.Rar { return base.ReadBytes(count); } + public override byte ReadByte() + { + var b = base.ReadByte(); + currentCrc = RarCRC.CheckCrc(currentCrc, b); + return b; + } + public override byte[] ReadBytes(int count) { var result = base.ReadBytes(count); diff --git a/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs b/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs index 9635375b..4a14a375 100644 --- a/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs +++ b/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs @@ -2,7 +2,6 @@ #if !NO_CRYPTO using System.Collections.Generic; using System.IO; -using SharpCompress.IO; namespace SharpCompress.Common.Rar { @@ -18,6 +17,10 @@ namespace SharpCompress.Common.Rar : base(stream) { this.password = password; + // coderb: not sure why this was being done at this logical point + //SkipQueue(); + byte[] salt = ReadBytes(8); + InitializeAes(salt); } // track read count ourselves rather than using the underlying stream since we buffer @@ -47,6 +50,36 @@ namespace SharpCompress.Common.Rar rijndael = RarRijndael.InitializeFrom(password, salt); } + public override byte ReadByte() { + if (UseEncryption) + { + return ReadAndDecryptBytes(1)[0]; + } + this.readCount++; + return base.ReadByte(); + } +//x +// public override byte ReadByte() { +// if (UseEncryption) +// { +// int queueSize = this.data.Count; +// if (queueSize < 1) +// { +// byte[] cipherText = ReadBytesNoCrc(16); +// var readBytes = this.rijndael.ProcessBlock(cipherText); +// foreach (var readByte in readBytes) +// this.data.Enqueue(readByte); +// } +// +// var b = this.data.Dequeue(); +// UpdateCrc(b); +// this.readCount++; +// return b; +// } +// this.readCount++; +// return base.ReadByte(); +// } + public override byte[] ReadBytes(int count) { if (UseEncryption) @@ -68,13 +101,11 @@ namespace SharpCompress.Common.Rar for (int i = 0; i < alignedSize / 16; i++) { //long ax = System.currentTimeMillis(); - byte[] cipherText = base.ReadBytesNoCrc(16); + byte[] cipherText = ReadBytesNoCrc(16); var readBytes = rijndael.ProcessBlock(cipherText); foreach (var readByte in readBytes) data.Enqueue(readByte); - } - } var decryptedBytes = new byte[count]; diff --git a/src/SharpCompress/Common/Rar/RarVolume.cs b/src/SharpCompress/Common/Rar/RarVolume.cs index 89fee688..4bed1696 100644 --- a/src/SharpCompress/Common/Rar/RarVolume.cs +++ b/src/SharpCompress/Common/Rar/RarVolume.cs @@ -30,7 +30,7 @@ namespace SharpCompress.Common.Rar internal IEnumerable GetVolumeFileParts() { MarkHeader previousMarkHeader = null; - foreach (RarHeader header in headerFactory.ReadHeaders(Stream)) + foreach (var header in headerFactory.ReadHeaders(Stream)) { switch (header.HeaderType) { diff --git a/src/SharpCompress/IO/MarkingBinaryReader.cs b/src/SharpCompress/IO/MarkingBinaryReader.cs index aa7c820b..42163d53 100644 --- a/src/SharpCompress/IO/MarkingBinaryReader.cs +++ b/src/SharpCompress/IO/MarkingBinaryReader.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Linq; using SharpCompress.Converters; namespace SharpCompress.IO @@ -36,12 +35,22 @@ namespace SharpCompress.IO public override bool ReadBoolean() { - return ReadBytes(1).Single() != 0; + return ReadByte() != 0; } + // NOTE: there is a somewhat fragile dependency on the internals of this class + // with RarCrcBinaryReader and RarCryptoBinaryReader. + // + // RarCrcBinaryReader/RarCryptoBinaryReader need to override any specific methods + // that call directly to the base BinaryReader and do not delegate to other methods + // in this class so that it can track the each byte being read. + // + // if altering this class in a way that changes the implementation be sure to + // update RarCrcBinaryReader/RarCryptoBinaryReader. public override byte ReadByte() { - return ReadBytes(1).Single(); + CurrentReadByteCount++; + return base.ReadByte(); } public override byte[] ReadBytes(int count) @@ -121,5 +130,31 @@ namespace SharpCompress.IO { return DataConverter.LittleEndian.GetUInt64(ReadBytes(8), 0); } + + // RAR5 style variable length encoded value + // maximum value of 0xffffffffffffffff (64 bits) + // implies max 10 bytes consumed + // + // Variable length integer. Can include one or more bytes, where lower 7 bits of every byte contain integer data + // and highest bit in every byte is the continuation flag. If highest bit is 0, this is the last byte in sequence. + // So first byte contains 7 least significant bits of integer and continuation flag. Second byte, if present, + // contains next 7 bits and so on. + public ulong ReadRarVInt() { + int shift = 0; + ulong result = 0; + do { + ulong n = ReadByte(); + result |= n << shift; + shift += 7; + if ((n & 0x80) == 0) { + return result; + } + // note: we're actually only allowing a max high bit of 2^56 + // to avoid an extra complex check for shift overflow due to the + // 10th byte having high bits set + } while (shift < 63); + + throw new FormatException("malformed vint"); + } } } \ No newline at end of file diff --git a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs index e227a74e..55d79994 100644 --- a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs +++ b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs @@ -14,14 +14,12 @@ namespace SharpCompress.Test.Rar public void Rar_EncryptedFileAndHeader_Archive() { ReadRarPassword("Rar.encrypted_filesAndHeader.rar", "test"); - } [Fact] public void Rar_EncryptedFileOnly_Archive() { ReadRarPassword("Rar.encrypted_filesOnly.rar", "test"); - } [Fact] @@ -209,6 +207,12 @@ namespace SharpCompress.Test.Rar ArchiveFileRead("Rar.none.rar"); } + [Fact] + public void Rar5NoneArchiveFileRead() + { + ArchiveFileRead("Rar5.none.rar"); + } + [Fact] public void Rar_ArchiveFileRead() { diff --git a/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs b/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs index 29309cff..9d5b07d2 100644 --- a/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs +++ b/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs @@ -26,11 +26,7 @@ namespace SharpCompress.Test.Rar [Fact] public void ReadHeaders_RecognizeEncryptedFlag() { - ReadEncryptedFlag("Rar.encrypted_filesAndHeader.rar", true); - - - } private void ReadEncryptedFlag(string testArchive, bool isEncrypted) From 9f549b98da822151cde78d53e2d09ff585ec4ddc Mon Sep 17 00:00:00 2001 From: coderb Date: Sat, 16 Dec 2017 20:39:53 -0500 Subject: [PATCH 03/61] rar5: support ArchiveHeader, additional rar5 header implementation --- .../Archives/Rar/RarArchiveVolumeFactory.cs | 6 +- .../Common/Rar/Headers/ArchiveHeader.cs | 82 ++++++++++++++-- .../Common/Rar/Headers/FileHeader.cs | 2 +- src/SharpCompress/Common/Rar/Headers/Flags.cs | 50 +++++++--- .../Common/Rar/Headers/MarkHeader.cs | 1 + .../Common/Rar/Headers/ProtectHeader.cs | 2 +- .../Common/Rar/Headers/RarHeader.cs | 97 ++++++++++++++----- .../Common/Rar/Headers/RarHeaderFactory.cs | 6 +- .../Common/Rar/RarCrcBinaryReader.cs | 4 +- src/SharpCompress/Common/Rar/RarVolume.cs | 7 +- src/SharpCompress/IO/MarkingBinaryReader.cs | 44 ++++++++- 11 files changed, 239 insertions(+), 62 deletions(-) diff --git a/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs b/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs index 7270a4bd..091f24ae 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs @@ -33,11 +33,11 @@ namespace SharpCompress.Archives.Rar FileInfoRarArchiveVolume part = new FileInfoRarArchiveVolume(fileInfo, options); yield return part; - if (!part.ArchiveHeader.ArchiveHeaderFlags.HasFlag(ArchiveFlags.VOLUME)) + ArchiveHeader ah = part.ArchiveHeader; + if (!ah.IsVolume) { yield break; //if file isn't volume then there is no reason to look } - ArchiveHeader ah = part.ArchiveHeader; fileInfo = GetNextFileInfo(ah, part.FileParts.FirstOrDefault() as FileInfoRarFilePart); //we use fileinfo because rar is dumb and looks at file names rather than archive info for another volume while (fileInfo != null && fileInfo.Exists) @@ -55,7 +55,7 @@ namespace SharpCompress.Archives.Rar { return null; } - bool oldNumbering = !ah.ArchiveHeaderFlags.HasFlag(ArchiveFlags.NEWNUMBERING) + bool oldNumbering = ah.OldNumberingFormat || currentFilePart.MarkHeader.OldNumberingFormat; if (oldNumbering) { diff --git a/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs b/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs index 214a25c0..82a32910 100644 --- a/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs @@ -1,3 +1,4 @@ +using System; using SharpCompress.IO; namespace SharpCompress.Common.Rar.Headers @@ -7,24 +8,91 @@ namespace SharpCompress.Common.Rar.Headers internal const short mainHeaderSizeWithEnc = 7; internal const short mainHeaderSize = 6; + public ArchiveHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader) { + } + protected override void ReadFromReader(MarkingBinaryReader reader) { - HighPosAv = reader.ReadInt16(); - PosAv = reader.ReadInt32(); - if (ArchiveHeaderFlags.HasFlag(ArchiveFlags.ENCRYPTVER)) + if (this.isRar5) { - EncryptionVersion = reader.ReadByte(); + ArchiveHeaderFlags = reader.ReadRarVIntUInt16(); + if ((ArchiveHeaderFlags & ArchiveFlagsV5.HasVolumeNumber) == ArchiveFlagsV5.HasVolumeNumber) + { + VolumeNumber = (int)reader.ReadRarVIntUInt32(); + } + if (ExtraSize != 0) { + ReadLocator(reader); + } + } else { + ArchiveHeaderFlags = Flags; + HighPosAv = reader.ReadInt16(); + PosAv = reader.ReadInt32(); + if ((ArchiveHeaderFlags & ArchiveFlagsV4.EncryptVer) == ArchiveFlagsV4.EncryptVer) + { + EncryptionVersion = reader.ReadByte(); + } } } - internal ArchiveFlags ArchiveHeaderFlags => (ArchiveFlags)Flags; + private void ReadLocator(MarkingBinaryReader reader) { + var size = reader.ReadRarVIntUInt16(); + var type = reader.ReadRarVIntUInt16(); + if (type != 1) throw new InvalidFormatException("expected locator record"); + var flags = reader.ReadRarVIntUInt16(); + const ushort HasQuickOpenOffset = 0x01; + const ushort HasRecoveryOffset = 0x02; + ulong quickOpenOffset = 0; + if ((flags & HasQuickOpenOffset) == HasQuickOpenOffset) { + quickOpenOffset = reader.ReadRarVInt(); + } + ulong recoveryOffset = 0; + if ((flags & HasRecoveryOffset) == HasRecoveryOffset) { + recoveryOffset = reader.ReadRarVInt(); + } + } + + internal ushort ArchiveHeaderFlags { get; private set; } + + internal int VolumeNumber { get; private set; } internal short HighPosAv { get; private set; } internal int PosAv { get; private set; } - internal byte EncryptionVersion { get; private set; } + private byte EncryptionVersion { get; set; } - public bool HasPassword => ArchiveHeaderFlags.HasFlag(ArchiveFlags.PASSWORD); + public bool HasPassword => (ArchiveHeaderFlags & ArchiveFlagsV4.Password) == ArchiveFlagsV4.Password; + + public bool OldNumberingFormat { + get { + return !this.isRar5 && + ((ArchiveHeaderFlags & ArchiveFlagsV4.NewNumbering) != ArchiveFlagsV4.NewNumbering); + } + } + + public bool IsVolume { + get { + return this.isRar5 + ? ((ArchiveHeaderFlags & ArchiveFlagsV5.Volume) == ArchiveFlagsV5.Volume) + : ((ArchiveHeaderFlags & ArchiveFlagsV4.Volume) == ArchiveFlagsV4.Volume); + } + } + + public bool IsFirstVolume { + get { + return this.isRar5 + ? VolumeNumber == 1 + : ((ArchiveHeaderFlags & ArchiveFlagsV4.FirstVolume) == ArchiveFlagsV4.FirstVolume); + } + } + + public bool IsSolid { + get { + return this.isRar5 + ? ((ArchiveHeaderFlags & ArchiveFlagsV5.Solid) == ArchiveFlagsV5.Solid) + : ((ArchiveHeaderFlags & ArchiveFlagsV4.Solid) == ArchiveFlagsV4.Solid); + } + } } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs index 1b539eb7..335f343c 100644 --- a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs @@ -42,7 +42,7 @@ namespace SharpCompress.Common.Rar.Headers highUncompressedkSize = int.MaxValue; } } - CompressedSize = UInt32To64(highCompressedSize, AdditionalSize); + CompressedSize = UInt32To64(highCompressedSize, AdditionalDataSize); UncompressedSize = UInt32To64(highUncompressedkSize, lowUncompressedSize); nameSize = nameSize > 4 * 1024 ? (short)(4 * 1024) : nameSize; diff --git a/src/SharpCompress/Common/Rar/Headers/Flags.cs b/src/SharpCompress/Common/Rar/Headers/Flags.cs index a2dbd550..09c58083 100644 --- a/src/SharpCompress/Common/Rar/Headers/Flags.cs +++ b/src/SharpCompress/Common/Rar/Headers/Flags.cs @@ -2,8 +2,14 @@ using System; namespace SharpCompress.Common.Rar.Headers { - internal enum HeaderType + internal enum HeaderType : byte { + Rar5ArchiveHeader = 0x01, + Rar5FileHeader = 0x02, + Rar5ServiceHeader = 0x03, + Rar5EncryptionHeader = 0x04, + Rar5EndArchiveHeader = 0x05, + MarkHeader = 0x72, ArchiveHeader = 0x73, FileHeader = 0x74, @@ -16,24 +22,38 @@ namespace SharpCompress.Common.Rar.Headers EndArchiveHeader = 0x7b } - internal enum HeaderFlags : short + internal static class HeaderFlagsV4 { - LONG_BLOCK = -0x8000 + public const ushort HasData = 0x8000; } - [Flags] - internal enum ArchiveFlags + internal static class HeaderFlagsV5 { - VOLUME = 0x0001, - COMMENT = 0x0002, - LOCK = 0x0004, - SOLID = 0x0008, - NEWNUMBERING = 0x0010, - AV = 0x0020, - PROTECT = 0x0040, - PASSWORD = 0x0080, - FIRSTVOLUME = 0x0100, - ENCRYPTVER = 0x0200 + public const ushort HasExtra = 0x01; + public const ushort HasData = 0x02; + } + + internal static class ArchiveFlagsV4 + { + public const ushort Volume = 0x0001; + public const ushort Comment = 0x0002; + public const ushort Lock = 0x0004; + public const ushort Solid = 0x0008; + public const ushort NewNumbering = 0x0010; + public const ushort AV = 0x0020; + public const ushort Protect = 0x0040; + public const ushort Password = 0x0080; + public const ushort FirstVolume = 0x0100; + public const ushort EncryptVer = 0x0200; + } + + internal static class ArchiveFlagsV5 + { + public const ushort Volume = 0x0001; + public const ushort HasVolumeNumber = 0x0002; + public const ushort Solid = 0x0004; + public const ushort Protect = 0x0008; + public const ushort Lock = 0x0010; } internal enum HostOS diff --git a/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs b/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs index 364202fc..30225bc7 100644 --- a/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs @@ -8,6 +8,7 @@ namespace SharpCompress.Common.Rar.Headers private const int MAX_SFX_SIZE = 0x80000 - 16; //archive.cpp line 136 internal bool OldNumberingFormat { get; private set; } + public bool IsRar5 { get; private set; } private MarkHeader(bool isRar5) { diff --git a/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs b/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs index e2f78c32..c47c5676 100644 --- a/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs @@ -13,7 +13,7 @@ namespace SharpCompress.Common.Rar.Headers Mark = reader.ReadBytes(8); } - internal uint DataSize => AdditionalSize; + internal uint DataSize => AdditionalDataSize; internal byte Version { get; private set; } internal ushort RecSectors { get; private set; } internal uint TotalBlocks { get; private set; } diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs index 55434c94..310e8bbf 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs @@ -6,8 +6,32 @@ namespace SharpCompress.Common.Rar.Headers { internal class RarHeader : IRarHeader { - internal const short BaseBlockSize = 7; - internal const short LONG_BLOCK = -0x8000; + protected readonly bool isRar5; + + protected RarHeader() { + //TODO + //throw new NotImplementedException(); + } + + private RarHeader(bool isRar5) + { + this.isRar5 = isRar5; + } + + protected RarHeader(RarHeader header, RarCrcBinaryReader reader) { + this.isRar5 = header.isRar5; + FillBase(header); + ReadFromReader(reader); + ReadBytes = reader.CurrentReadByteCount; + + int headerSizeDiff = HeaderSize - (int)ReadBytes; + if (headerSizeDiff > 0) + { + reader.ReadBytes(headerSizeDiff); + } + + VerifyHeaderCrc(reader.GetCrc32()); + } private void FillBase(RarHeader baseHeader) { @@ -15,18 +39,18 @@ namespace SharpCompress.Common.Rar.Headers HeaderType = baseHeader.HeaderType; Flags = baseHeader.Flags; HeaderSize = baseHeader.HeaderSize; - AdditionalSize = baseHeader.AdditionalSize; + ExtraSize = baseHeader.ExtraSize; + AdditionalDataSize = baseHeader.AdditionalDataSize; ReadBytes = baseHeader.ReadBytes; ArchiveEncoding = baseHeader.ArchiveEncoding; } - internal static RarHeader Create(RarCrcBinaryReader reader, ArchiveEncoding archiveEncoding) + internal static RarHeader ReadBase(RarCrcBinaryReader reader, ArchiveEncoding archiveEncoding, bool isRar5) { try { - var header = new RarHeader(); + var header = new RarHeader(isRar5); header.ArchiveEncoding = archiveEncoding; - reader.Mark(); header.ReadStartFromReader(reader); header.ReadBytes += reader.CurrentReadByteCount; return header; @@ -39,14 +63,33 @@ namespace SharpCompress.Common.Rar.Headers private void ReadStartFromReader(RarCrcBinaryReader reader) { - HeadCRC = reader.ReadUInt16(); - reader.ResetCrc(); - HeaderType = (HeaderType)reader.ReadByte(); - Flags = reader.ReadInt16(); - HeaderSize = reader.ReadInt16(); - if (FlagUtility.HasFlag(Flags, LONG_BLOCK)) + if (this.isRar5) { - AdditionalSize = reader.ReadUInt32(); + HeadCRC = reader.ReadUInt32(); + reader.ResetCrc(); + HeaderSize = (int)reader.ReadRarVIntUInt32(3); + reader.Mark(); + HeaderType = (HeaderType)reader.ReadRarVIntUInt32(1); + Flags = reader.ReadRarVIntUInt16(2); + if ((Flags & HeaderFlagsV5.HasExtra) == HeaderFlagsV5.HasExtra) + { + ExtraSize = reader.ReadRarVIntUInt32(); + } + if ((Flags & HeaderFlagsV5.HasData) == HeaderFlagsV5.HasData) + { + AdditionalDataSize = reader.ReadRarVIntUInt32(); + } + } else { + reader.Mark(); + HeadCRC = reader.ReadUInt16(); + reader.ResetCrc(); + HeaderType = (HeaderType)reader.ReadByte(); + Flags = reader.ReadUInt16(); + HeaderSize = reader.ReadInt16(); + if ((Flags & HeaderFlagsV4.HasData) == HeaderFlagsV4.HasData) + { + AdditionalDataSize = reader.ReadUInt32(); + } } } @@ -54,16 +97,14 @@ namespace SharpCompress.Common.Rar.Headers { throw new NotImplementedException(); } - +//x internal T PromoteHeader(RarCrcBinaryReader reader) where T : RarHeader, new() { T header = new T(); header.FillBase(this); - - reader.Mark(); header.ReadFromReader(reader); - header.ReadBytes += reader.CurrentReadByteCount; + header.ReadBytes = reader.CurrentReadByteCount; int headerSizeDiff = header.HeaderSize - (int)header.ReadBytes; if (headerSizeDiff > 0) @@ -71,16 +112,17 @@ namespace SharpCompress.Common.Rar.Headers reader.ReadBytes(headerSizeDiff); } - VerifyHeaderCrc(reader.GetCrc()); + VerifyHeaderCrc(reader.GetCrc32()); return header; } - private void VerifyHeaderCrc(ushort crc) + private void VerifyHeaderCrc(uint crc32) { if (HeaderType != HeaderType.MarkHeader) { - if (crc != HeadCRC) + var b = (this.isRar5 ? crc32 : (ushort)crc32) == HeadCRC; + if (!b) { throw new InvalidFormatException("rar header crc mismatch"); } @@ -96,22 +138,27 @@ namespace SharpCompress.Common.Rar.Headers /// protected long ReadBytes { get; private set; } - protected ushort HeadCRC { get; private set; } + protected uint HeadCRC { get; private set; } public HeaderType HeaderType { get; private set; } /// /// Untyped flags. These should be typed when Promoting to another header /// - protected short Flags { get; private set; } + protected ushort Flags { get; private set; } - protected short HeaderSize { get; private set; } + protected int HeaderSize { get; private set; } internal ArchiveEncoding ArchiveEncoding { get; private set; } /// - /// This additional size of the header could be file data + /// Extra header data. /// - protected uint AdditionalSize { get; private set; } + protected uint ExtraSize { get; private set; } + + /// + /// Additional data size, eg recovery record + /// + protected uint AdditionalDataSize { get; private set; } } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs index 66d4c024..22ac412f 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs @@ -54,16 +54,18 @@ namespace SharpCompress.Common.Rar.Headers #endif } - var header = RarHeader.Create(reader, Options.ArchiveEncoding); + var header = RarHeader.ReadBase(reader, Options.ArchiveEncoding, this.isRar5); if (header == null) { return null; } switch (header.HeaderType) { + case HeaderType.Rar5ArchiveHeader: case HeaderType.ArchiveHeader: { - var ah = header.PromoteHeader(reader); + var ah = new ArchiveHeader(header, reader); + //x var ah = header.PromoteHeader(reader); IsEncrypted = ah.HasPassword; return ah; } diff --git a/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs b/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs index aa8c73fd..3251552b 100644 --- a/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs +++ b/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs @@ -10,9 +10,9 @@ namespace SharpCompress.Common.Rar { { } - public ushort GetCrc() + public uint GetCrc32() { - return (ushort)~currentCrc; + return ~currentCrc; } public void ResetCrc() diff --git a/src/SharpCompress/Common/Rar/RarVolume.cs b/src/SharpCompress/Common/Rar/RarVolume.cs index 4bed1696..521cae19 100644 --- a/src/SharpCompress/Common/Rar/RarVolume.cs +++ b/src/SharpCompress/Common/Rar/RarVolume.cs @@ -81,7 +81,8 @@ namespace SharpCompress.Common.Rar get { EnsureArchiveHeaderLoaded(); - return ArchiveHeader.ArchiveHeaderFlags.HasFlag(ArchiveFlags.FIRSTVOLUME); + //return (ArchiveHeader.ArchiveHeaderFlags & ArchiveFlagsV4.FirstVolume) == ArchiveFlagsV4.FirstVolume; + return ArchiveHeader.IsFirstVolume; } } @@ -93,7 +94,7 @@ namespace SharpCompress.Common.Rar get { EnsureArchiveHeaderLoaded(); - return ArchiveHeader.ArchiveHeaderFlags.HasFlag(ArchiveFlags.VOLUME); + return ArchiveHeader.IsVolume; } } @@ -106,7 +107,7 @@ namespace SharpCompress.Common.Rar get { EnsureArchiveHeaderLoaded(); - return ArchiveHeader.ArchiveHeaderFlags.HasFlag(ArchiveFlags.SOLID); + return ArchiveHeader.IsSolid; } } } diff --git a/src/SharpCompress/IO/MarkingBinaryReader.cs b/src/SharpCompress/IO/MarkingBinaryReader.cs index 42163d53..ed33d8a4 100644 --- a/src/SharpCompress/IO/MarkingBinaryReader.cs +++ b/src/SharpCompress/IO/MarkingBinaryReader.cs @@ -143,18 +143,56 @@ namespace SharpCompress.IO int shift = 0; ulong result = 0; do { - ulong n = ReadByte(); + byte b0 = ReadByte(); + var b1 = b0 & 0x7f; + ulong n = (ulong)b1; result |= n << shift; - shift += 7; - if ((n & 0x80) == 0) { + if (b0 == b1) { return result; } // note: we're actually only allowing a max high bit of 2^56 // to avoid an extra complex check for shift overflow due to the // 10th byte having high bits set + shift += 7; } while (shift < 63); throw new FormatException("malformed vint"); } + + public uint ReadRarVIntUInt32(int maxBytes = 5) { + int shift = 0; + uint result = 0; + do { + byte b0 = ReadByte(); + var b1 = b0 & 0x7f; + uint n = (uint)b1; + result |= n << shift; + if (b0 == b1) { + return result; + } + shift += 7; + // NOTE: we are too strict here but handling the full range adds complexity and we don't need it + } while (shift < 28 && --maxBytes > 0); + + throw new FormatException("malformed vint"); + } + + public ushort ReadRarVIntUInt16(int maxBytes = 3) { + int shift = 0; + uint result = 0; + do { + byte b0 = ReadByte(); + var b1 = b0 & 0x7f; + uint n = (uint)b1; + result |= n << shift; + if (b0 == b1) { + return checked((ushort)result); + } + shift += 7; + // NOTE: we are too strict here but handling the full range adds complexity and we don't need it + } while (shift < 14 && --maxBytes > 0); + + throw new FormatException("malformed vint"); + } } } \ No newline at end of file From 72f52359e6238419fdb2d9e6f26e1c358807c332 Mon Sep 17 00:00:00 2001 From: coderb Date: Sun, 17 Dec 2017 01:58:31 -0500 Subject: [PATCH 04/61] rar5: wip --- .../Archives/Rar/RarArchiveEntry.cs | 13 +- .../Archives/Rar/RarArchiveEntryFactory.cs | 2 +- .../Archives/Rar/SeekableFilePart.cs | 4 +- .../Common/Rar/Headers/ArchiveHeader.cs | 82 ++--- .../Common/Rar/Headers/EndArchiveHeader.cs | 29 +- .../Common/Rar/Headers/FileHeader.cs | 311 ++++++++++++++---- src/SharpCompress/Common/Rar/Headers/Flags.cs | 136 +++++--- .../Common/Rar/Headers/IRarHeader.cs | 6 + .../Common/Rar/Headers/MarkHeader.cs | 53 ++- .../Common/Rar/Headers/ProtectHeader.cs | 2 +- .../Common/Rar/Headers/RarHeader.cs | 79 ++--- .../Common/Rar/Headers/RarHeaderFactory.cs | 43 ++- src/SharpCompress/Common/Rar/RarEntry.cs | 8 +- src/SharpCompress/Common/Rar/RarVolume.cs | 6 +- .../Rar/MultiVolumeReadOnlyStream.cs | 6 +- src/SharpCompress/Compressors/Rar/Unpack.cs | 9 +- src/SharpCompress/IO/MarkingBinaryReader.cs | 18 + src/SharpCompress/Utility.cs | 10 +- .../Rar/RarHeaderFactoryTest.cs | 2 +- 19 files changed, 550 insertions(+), 269 deletions(-) create mode 100644 src/SharpCompress/Common/Rar/Headers/IRarHeader.cs diff --git a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs index fe3d5106..8043784d 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs @@ -24,7 +24,7 @@ namespace SharpCompress.Archives.Rar public IArchive Archive => archive; - internal override IEnumerable Parts => parts.Cast(); + internal override IEnumerable Parts => this.parts; internal override FileHeader FileHeader => parts.First().FileHeader; @@ -33,8 +33,7 @@ namespace SharpCompress.Archives.Rar get { CheckIncomplete(); - return parts.Select(fp => fp.FileHeader) - .Single(fh => !fh.FileFlags.HasFlag(FileFlags.SPLIT_AFTER)).FileCRC; + return parts.Select(fp => fp.FileHeader).Single(fh => !fh.IsSplit).FileCrc; } } @@ -65,7 +64,13 @@ namespace SharpCompress.Archives.Rar return new RarStream(archive.Unpack, FileHeader, new MultiVolumeReadOnlyStream(Parts.Cast(), archive)); } - public bool IsComplete { get { return parts.Select(fp => fp.FileHeader).Any(fh => !fh.FileFlags.HasFlag(FileFlags.SPLIT_AFTER)); } } + public bool IsComplete + { + get + { + return parts.Select(fp => fp.FileHeader).Any(fh => !fh.IsSplit); + } + } private void CheckIncomplete() { diff --git a/src/SharpCompress/Archives/Rar/RarArchiveEntryFactory.cs b/src/SharpCompress/Archives/Rar/RarArchiveEntryFactory.cs index 23057c61..b5c4865a 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveEntryFactory.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveEntryFactory.cs @@ -25,7 +25,7 @@ namespace SharpCompress.Archives.Rar { groupedParts.Add(fp); - if (!FlagUtility.HasFlag((long)fp.FileHeader.FileFlags, (long)FileFlags.SPLIT_AFTER)) + if (!fp.FileHeader.IsSplit) { yield return groupedParts; groupedParts = new List(); diff --git a/src/SharpCompress/Archives/Rar/SeekableFilePart.cs b/src/SharpCompress/Archives/Rar/SeekableFilePart.cs index 1e583f07..b7d3affc 100644 --- a/src/SharpCompress/Archives/Rar/SeekableFilePart.cs +++ b/src/SharpCompress/Archives/Rar/SeekableFilePart.cs @@ -20,9 +20,9 @@ namespace SharpCompress.Archives.Rar { stream.Position = FileHeader.DataStartPosition; #if !NO_CRYPTO - if (FileHeader.Salt != null) + if (FileHeader.R4Salt != null) { - return new RarCryptoWrapper(stream, password, FileHeader.Salt); + return new RarCryptoWrapper(stream, password, FileHeader.R4Salt); } #endif return stream; diff --git a/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs b/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs index 82a32910..d9630296 100644 --- a/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs @@ -1,34 +1,32 @@ -using System; using SharpCompress.IO; namespace SharpCompress.Common.Rar.Headers { internal class ArchiveHeader : RarHeader { - internal const short mainHeaderSizeWithEnc = 7; - internal const short mainHeaderSize = 6; - public ArchiveHeader(RarHeader header, RarCrcBinaryReader reader) - : base(header, reader) { + : base(header, reader, HeaderType.Archive) { } protected override void ReadFromReader(MarkingBinaryReader reader) { - if (this.isRar5) + if (this.IsRar5) { ArchiveHeaderFlags = reader.ReadRarVIntUInt16(); - if ((ArchiveHeaderFlags & ArchiveFlagsV5.HasVolumeNumber) == ArchiveFlagsV5.HasVolumeNumber) + if (HasArchiveHeaderFlag(ArchiveFlagsV5.HasVolumeNumber)) { VolumeNumber = (int)reader.ReadRarVIntUInt32(); } if (ExtraSize != 0) { ReadLocator(reader); } - } else { - ArchiveHeaderFlags = Flags; + } + else + { + ArchiveHeaderFlags = HeaderFlags; HighPosAv = reader.ReadInt16(); PosAv = reader.ReadInt32(); - if ((ArchiveHeaderFlags & ArchiveFlagsV4.EncryptVer) == ArchiveFlagsV4.EncryptVer) + if (HasArchiveHeaderFlag(ArchiveFlagsV4.EncryptVer)) { EncryptionVersion = reader.ReadByte(); } @@ -40,59 +38,41 @@ namespace SharpCompress.Common.Rar.Headers var type = reader.ReadRarVIntUInt16(); if (type != 1) throw new InvalidFormatException("expected locator record"); var flags = reader.ReadRarVIntUInt16(); - const ushort HasQuickOpenOffset = 0x01; - const ushort HasRecoveryOffset = 0x02; + const ushort hasQuickOpenOffset = 0x01; + const ushort hasRecoveryOffset = 0x02; ulong quickOpenOffset = 0; - if ((flags & HasQuickOpenOffset) == HasQuickOpenOffset) { + if ((flags & hasQuickOpenOffset) == hasQuickOpenOffset) { quickOpenOffset = reader.ReadRarVInt(); } ulong recoveryOffset = 0; - if ((flags & HasRecoveryOffset) == HasRecoveryOffset) { + if ((flags & hasRecoveryOffset) == hasRecoveryOffset) { recoveryOffset = reader.ReadRarVInt(); } } - internal ushort ArchiveHeaderFlags { get; private set; } + private ushort ArchiveHeaderFlags { get; set; } - internal int VolumeNumber { get; private set; } - - internal short HighPosAv { get; private set; } - - internal int PosAv { get; private set; } - - private byte EncryptionVersion { get; set; } - - public bool HasPassword => (ArchiveHeaderFlags & ArchiveFlagsV4.Password) == ArchiveFlagsV4.Password; - - public bool OldNumberingFormat { - get { - return !this.isRar5 && - ((ArchiveHeaderFlags & ArchiveFlagsV4.NewNumbering) != ArchiveFlagsV4.NewNumbering); - } + private bool HasArchiveHeaderFlag(ushort flag) + { + return (ArchiveHeaderFlags & flag) == flag; } - public bool IsVolume { - get { - return this.isRar5 - ? ((ArchiveHeaderFlags & ArchiveFlagsV5.Volume) == ArchiveFlagsV5.Volume) - : ((ArchiveHeaderFlags & ArchiveFlagsV4.Volume) == ArchiveFlagsV4.Volume); - } - } + internal int? VolumeNumber { get; private set; } - public bool IsFirstVolume { - get { - return this.isRar5 - ? VolumeNumber == 1 - : ((ArchiveHeaderFlags & ArchiveFlagsV4.FirstVolume) == ArchiveFlagsV4.FirstVolume); - } - } + internal short? HighPosAv { get; private set; } - public bool IsSolid { - get { - return this.isRar5 - ? ((ArchiveHeaderFlags & ArchiveFlagsV5.Solid) == ArchiveFlagsV5.Solid) - : ((ArchiveHeaderFlags & ArchiveFlagsV4.Solid) == ArchiveFlagsV4.Solid); - } - } + internal int? PosAv { get; private set; } + + private byte? EncryptionVersion { get; set; } + + public bool? IsEncrypted => this.IsRar5 ? (bool?)null : HasArchiveHeaderFlag(ArchiveFlagsV4.Password); + + public bool OldNumberingFormat => !this.IsRar5 && !HasArchiveHeaderFlag(ArchiveFlagsV4.NewNumbering); + + public bool IsVolume => HasArchiveHeaderFlag(this.IsRar5 ? ArchiveFlagsV5.Volume : ArchiveFlagsV4.Volume); + + public bool IsFirstVolume => this.IsRar5 ? VolumeNumber == 1 : HasArchiveHeaderFlag(ArchiveFlagsV4.FirstVolume); + + public bool IsSolid => HasArchiveHeaderFlag(this.IsRar5 ? ArchiveFlagsV5.Solid : ArchiveFlagsV4.Solid); } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/EndArchiveHeader.cs b/src/SharpCompress/Common/Rar/Headers/EndArchiveHeader.cs index b2f62039..caee1a83 100644 --- a/src/SharpCompress/Common/Rar/Headers/EndArchiveHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/EndArchiveHeader.cs @@ -4,21 +4,38 @@ namespace SharpCompress.Common.Rar.Headers { internal class EndArchiveHeader : RarHeader { + public EndArchiveHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader, HeaderType.EndArchive) { + } + protected override void ReadFromReader(MarkingBinaryReader reader) { - if (EndArchiveFlags.HasFlag(EndArchiveFlags.EARC_DATACRC)) + if (this.IsRar5) { - ArchiveCRC = reader.ReadInt32(); + EndArchiveFlags = reader.ReadRarVIntUInt16(); } - if (EndArchiveFlags.HasFlag(EndArchiveFlags.EARC_VOLNUMBER)) + else { - VolumeNumber = reader.ReadInt16(); + EndArchiveFlags = HeaderFlags; + if (HasEndArchiveFlag(EndArchiveFlagsV4.DataCrc)) + { + ArchiveCrc = reader.ReadInt32(); + } + if (HasEndArchiveFlag(EndArchiveFlagsV4.VolumeNumber)) + { + VolumeNumber = reader.ReadInt16(); + } } } - internal EndArchiveFlags EndArchiveFlags => (EndArchiveFlags)Flags; + private ushort EndArchiveFlags { get; set; } - internal int? ArchiveCRC { get; private set; } + private bool HasEndArchiveFlag(ushort flag) + { + return (EndArchiveFlags & flag) == flag; + } + + internal int? ArchiveCrc { get; private set; } internal short? VolumeNumber { get; private set; } } diff --git a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs index 335f343c..dd789c6b 100644 --- a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs @@ -1,35 +1,203 @@ using SharpCompress.IO; using System; using System.IO; +using System.Text; namespace SharpCompress.Common.Rar.Headers { internal class FileHeader : RarHeader { - private const byte SALT_SIZE = 8; + private uint fileCrc; - private const byte NEWLHD_SIZE = 32; + public FileHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader, HeaderType.File) { + } - protected override void ReadFromReader(MarkingBinaryReader reader) + protected override void ReadFromReader(MarkingBinaryReader reader) { + if (this.IsRar5) + { + ReadFromReaderV5(reader); + } + else + { + ReadFromReaderV4(reader); + } + } + + private void ReadFromReaderV5(MarkingBinaryReader reader) + { + FileFlags = reader.ReadRarVIntUInt16(); + + var lvalue = checked((long)reader.ReadRarVInt()); +//!!! rar5 TODO: this may cause problems with user code, Unpack20 seems to use this field to decompress + UncompressedSize = HasFileFlag(FileFlagsV5.UnpackedSizeUnknown) ? long.MaxValue : lvalue; + + FileAttributes = reader.ReadRarVIntUInt32(); + + if (HasFileFlag(FileFlagsV5.HasModTime)) { + FileLastModifiedTime = Utility.UnixTimeToDateTime(reader.ReadUInt32()); + } + + if (HasFileFlag(FileFlagsV5.HasCrc32)) { + FileCrc = reader.ReadUInt32(); + } + + var us = reader.ReadRarVIntUInt16(); + + // Lower 6 bits (0x003f mask) contain the version of compression algorithm, resulting in possible 0 - 63 values. Current version is 0. + R5CompressionAlgorithm = (byte)(us & 0x3f); + if (R5CompressionAlgorithm != 0) throw new InvalidFormatException("unknown rar5 compression algorithm " + R5CompressionAlgorithm); + + // 7th bit (0x0040) defines the solid flag. If it is set, RAR continues to use the compression dictionary left after processing preceding files. + // It can be set only for file headers and is never set for service headers. + R5IsSolid = (us & 0x40) == 0x40; + + // Bits 8 - 10 (0x0380 mask) define the compression method. Currently only values 0 - 5 are used. 0 means no compression. + R5CompressionMethod = (byte)((us >> 7) & 0x7); + + // Bits 11 - 14 (0x3c00) define the minimum size of dictionary size required to extract data. Value 0 means 128 KB, 1 - 256 KB, ..., 14 - 2048 MB, 15 - 4096 MB. + // 2 ^ (17 + x) + R5DictSize_2_17plusX = (byte)((us >> 10) & 0xf); + + HostOs = reader.ReadRarVIntByte(); + + var nameSize = reader.ReadRarVIntUInt16(); + + // Variable length field containing Name length bytes in UTF-8 format without trailing zero. + // For file header this is a name of archived file. Forward slash character is used as the path separator both for Unix and Windows names. + // Backslashes are treated as a part of name for Unix names and as invalid character for Windows file names. Type of name is defined by Host OS field. + // + // TODO: not sure if anything needs to be done to handle the following: + // If Unix file name contains any high ASCII characters which cannot be correctly converted to Unicode and UTF-8 + // we map such characters to to 0xE080 - 0xE0FF private use Unicode area and insert 0xFFFE Unicode non-character + // to resulting string to indicate that it contains mapped characters, which need to be converted back when extracting. + // Concrete position of 0xFFFE is not defined, we need to search the entire string for it. Such mapped names are not + // portable and can be correctly unpacked only on the same system where they were created. + // + // For service header this field contains a name of service header. Now the following names are used: + // CMT Archive comment + // QO Archive quick open data + // ACL NTFS file permissions + // STM NTFS alternate data stream + // RR Recovery record + var b = reader.ReadBytes(nameSize); + FileName = ConvertPathV5(Encoding.UTF8.GetString(b, 0, b.Length)); + + if (ExtraSize != 0) { + var size = reader.ReadRarVIntUInt16(); + var type = reader.ReadRarVIntUInt16(); + switch (type) { +//TODO +// case 1: // file encryption +// { +// var version = reader.ReadRarVIntByte(); +// if (version != 0) throw new InvalidFormatException("unknown encryption algorithm "+ version); +// +// } +// break; +// case 2: // file hash +// { +// +// } +// break; + case 3: // file time + { + ushort flags = reader.ReadRarVIntUInt16(); + var isWindowsTime = (flags & 1) == 0; + if ((flags & 0x2) == 0x2) { + FileLastModifiedTime = ReadExtendedTimeV5(reader, isWindowsTime); + } + if ((flags & 0x4) == 0x4) { + FileCreatedTime = ReadExtendedTimeV5(reader, isWindowsTime); + } + if ((flags & 0x8) == 0x8) { + FileLastAccessedTime = ReadExtendedTimeV5(reader, isWindowsTime); + } + } + break; +//TODO +// case 4: // file version +// { +// +// } +// break; +// case 5: // file system redirection +// { +// +// } +// break; +// case 6: // unix owner +// { +// +// } +// break; +// case 7: // service data +// { +// +// } +// break; + + default: throw new InvalidFormatException("unexpected extra data record type " + type); + } + + } + + if (AdditionalDataSize != 0) { + CompressedSize = AdditionalDataSize; + } + } + + private static DateTime ReadExtendedTimeV5(MarkingBinaryReader reader, bool isWindowsTime) + { + if (isWindowsTime) + { + return DateTime.FromFileTime(reader.ReadInt64()); + } + else + { + return Utility.UnixTimeToDateTime(reader.ReadUInt32()); + } + } + + private static string ConvertPathV5(string path) + { +#if NO_FILE + // not sure what to do here + throw new NotImplementedException("TODO"); +#else + if (Path.DirectorySeparatorChar == '\\') + { + // replace embedded \\ with valid filename char + return path.Replace('\\', '-').Replace('/', '\\'); + } + return path; +#endif + } + + + private void ReadFromReaderV4(MarkingBinaryReader reader) + { + FileFlags = HeaderFlags; + uint lowUncompressedSize = reader.ReadUInt32(); - HostOS = (HostOS)reader.ReadByte(); + HostOs = reader.ReadByte(); - FileCRC = reader.ReadUInt32(); + FileCrc = reader.ReadUInt32(); - FileLastModifiedTime = Utility.DosDateToDateTime(reader.ReadInt32()); + FileLastModifiedTime = Utility.DosDateToDateTime(reader.ReadUInt32()); - RarVersion = reader.ReadByte(); - PackingMethod = reader.ReadByte(); + R4RarVersion = reader.ReadByte(); + R4PackingMethod = reader.ReadByte(); short nameSize = reader.ReadInt16(); - FileAttributes = reader.ReadInt32(); + FileAttributes = reader.ReadUInt32(); uint highCompressedSize = 0; uint highUncompressedkSize = 0; - if (FileFlags.HasFlag(FileFlags.LARGE)) + if (HasFileFlag(FileFlagsV4.Large)) { highCompressedSize = reader.ReadUInt32(); highUncompressedkSize = reader.ReadUInt32(); @@ -42,18 +210,21 @@ namespace SharpCompress.Common.Rar.Headers highUncompressedkSize = int.MaxValue; } } - CompressedSize = UInt32To64(highCompressedSize, AdditionalDataSize); + CompressedSize = UInt32To64(highCompressedSize, checked((uint)AdditionalDataSize)); UncompressedSize = UInt32To64(highUncompressedkSize, lowUncompressedSize); nameSize = nameSize > 4 * 1024 ? (short)(4 * 1024) : nameSize; byte[] fileNameBytes = reader.ReadBytes(nameSize); - switch (HeaderType) + const int saltSize = 8; + const int newLhdSize = 32; + + switch (HeaderCode) { - case HeaderType.FileHeader: + case HeaderCodeV.FileHeader: { - if (FileFlags.HasFlag(FileFlags.UNICODE)) + if (HasFileFlag(FileFlagsV4.Unicode)) { int length = 0; while (length < fileNameBytes.Length @@ -75,15 +246,15 @@ namespace SharpCompress.Common.Rar.Headers { FileName = ArchiveEncoding.Decode(fileNameBytes); } - FileName = ConvertPath(FileName, HostOS); + FileName = ConvertPathV4(FileName); } break; - case HeaderType.NewSubHeader: + case HeaderCodeV.NewSubHeader: { - int datasize = HeaderSize - NEWLHD_SIZE - nameSize; - if (FileFlags.HasFlag(FileFlags.SALT)) + int datasize = HeaderSize - newLhdSize - nameSize; + if (HasFileFlag(FileFlagsV4.Salt)) { - datasize -= SALT_SIZE; + datasize -= saltSize; } if (datasize > 0) { @@ -99,34 +270,33 @@ namespace SharpCompress.Common.Rar.Headers break; } - if (FileFlags.HasFlag(FileFlags.SALT)) + if (HasFileFlag(FileFlagsV4.Salt)) { - Salt = reader.ReadBytes(SALT_SIZE); + R4Salt = reader.ReadBytes(saltSize); } - if (FileFlags.HasFlag(FileFlags.EXTTIME)) + if (HasFileFlag(FileFlagsV4.ExtTime)) { // verify that the end of the header hasn't been reached before reading the Extended Time. // some tools incorrectly omit Extended Time despite specifying FileFlags.EXTTIME, which most parsers tolerate. if (ReadBytes + reader.CurrentReadByteCount <= HeaderSize - 2) { ushort extendedFlags = reader.ReadUInt16(); - FileLastModifiedTime = ProcessExtendedTime(extendedFlags, FileLastModifiedTime, reader, 0); - FileCreatedTime = ProcessExtendedTime(extendedFlags, null, reader, 1); - FileLastAccessedTime = ProcessExtendedTime(extendedFlags, null, reader, 2); - FileArchivedTime = ProcessExtendedTime(extendedFlags, null, reader, 3); + FileLastModifiedTime = ProcessExtendedTimeV4(extendedFlags, FileLastModifiedTime, reader, 0); + FileCreatedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 1); + FileLastAccessedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 2); + FileArchivedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 3); } } } - private long UInt32To64(uint x, uint y) + private static long UInt32To64(uint x, uint y) { long l = x; l <<= 32; return l + y; } - private static DateTime? ProcessExtendedTime(ushort extendedFlags, DateTime? time, MarkingBinaryReader reader, - int i) + private static DateTime? ProcessExtendedTimeV4(ushort extendedFlags, DateTime? time, MarkingBinaryReader reader, int i) { uint rmode = (uint)extendedFlags >> (3 - i) * 4; if ((rmode & 8) == 0) @@ -154,7 +324,7 @@ namespace SharpCompress.Common.Rar.Headers return time.Value.AddMilliseconds(nanosecondHundreds * Math.Pow(10, -4)); } - private static string ConvertPath(string path, HostOS os) + private static string ConvertPathV4(string path) { #if NO_FILE return path.Replace('\\', '/'); @@ -171,11 +341,61 @@ namespace SharpCompress.Common.Rar.Headers #endif } + public override string ToString() + { + return FileName; + } + + private ushort FileFlags { get; set; } + + private bool HasFileFlag(ushort flag) + { + return (FileFlags & flag) == flag; + } + + internal uint FileCrc + { + get { + if (this.IsRar5 && !HasFileFlag(FileFlagsV5.HasCrc32)) { +//!!! rar5: + throw new InvalidOperationException("TODO rar5"); + } + return this.fileCrc; + } + private set { this.fileCrc = value; } + } + + internal byte R4RarVersion { get; private set; } + internal byte R4PackingMethod { get; private set; } + internal byte[] R4Salt { get; private set; } + + internal byte R5CompressionAlgorithm { get; private set; } + internal bool R5IsSolid{ get; private set; } + internal byte R5CompressionMethod { get; private set; } + internal byte R5DictSize_2_17plusX { get; private set; } + + + private byte HostOs { get; set; } + internal uint FileAttributes { get; private set; } + internal long CompressedSize { get; private set; } + internal long UncompressedSize { get; private set; } + internal string FileName { get; private set; } + internal byte[] SubData { get; private set; } + internal int RecoverySectors { get; private set; } internal long DataStartPosition { get; set; } + public Stream PackedStream { get; set; } - internal HostOS HostOS { get; private set; } +//!!! TODO rar5 + public bool IsSplit => HasFileFlag(FileFlagsV4.SplitAfter); + + public bool IsDirectory => HasFileFlag(this.IsRar5 ? FileFlagsV5.Directory : FileFlagsV4.Directory); + +//!!! TODO rar5 + public bool IsEncrypted => HasFileFlag(FileFlagsV4.Password); + +//!!! TODO rar5; R5IsSolid + public bool IsSolid => HasFileFlag(FileFlagsV4.Solid); - internal uint FileCRC { get; private set; } internal DateTime? FileLastModifiedTime { get; private set; } @@ -184,32 +404,5 @@ namespace SharpCompress.Common.Rar.Headers internal DateTime? FileLastAccessedTime { get; private set; } internal DateTime? FileArchivedTime { get; private set; } - - internal byte RarVersion { get; private set; } - - internal byte PackingMethod { get; private set; } - - internal int FileAttributes { get; private set; } - - internal FileFlags FileFlags => (FileFlags)Flags; - - internal long CompressedSize { get; private set; } - - internal long UncompressedSize { get; private set; } - - internal string FileName { get; private set; } - - internal byte[] SubData { get; private set; } - - internal int RecoverySectors { get; private set; } - - internal byte[] Salt { get; private set; } - - public override string ToString() - { - return FileName; - } - - public Stream PackedStream { get; set; } } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/Flags.cs b/src/SharpCompress/Common/Rar/Headers/Flags.cs index 09c58083..dba0d245 100644 --- a/src/SharpCompress/Common/Rar/Headers/Flags.cs +++ b/src/SharpCompress/Common/Rar/Headers/Flags.cs @@ -4,22 +4,37 @@ namespace SharpCompress.Common.Rar.Headers { internal enum HeaderType : byte { - Rar5ArchiveHeader = 0x01, - Rar5FileHeader = 0x02, - Rar5ServiceHeader = 0x03, - Rar5EncryptionHeader = 0x04, - Rar5EndArchiveHeader = 0x05, + Null, + Mark, + Archive, + File, +// Comment, +// Av, +// Sub, +// Protect, +// Sign, +// NewSub, + EndArchive + } - MarkHeader = 0x72, - ArchiveHeader = 0x73, - FileHeader = 0x74, - CommHeader = 0x75, - AvHeader = 0x76, - SubHeader = 0x77, - ProtectHeader = 0x78, - SignHeader = 0x79, - NewSubHeader = 0x7a, - EndArchiveHeader = 0x7b + internal static class HeaderCodeV + { + public const byte Rar5ArchiveHeader = 0x01; + public const byte Rar5FileHeader = 0x02; + public const byte Rar5ServiceHeader = 0x03; + public const byte Rar5EncryptionHeader = 0x04; + public const byte Rar5EndArchiveHeader = 0x05; + + public const byte MarkHeader = 0x72; + public const byte ArchiveHeader = 0x73; + public const byte FileHeader = 0x74; + public const byte CommHeader = 0x75; + public const byte AvHeader = 0x76; + public const byte SubHeader = 0x77; + public const byte ProtectHeader = 0x78; + public const byte SignHeader = 0x79; + public const byte NewSubHeader = 0x7a; + public const byte EndArchiveHeader = 0x7b; } internal static class HeaderFlagsV4 @@ -56,49 +71,66 @@ namespace SharpCompress.Common.Rar.Headers public const ushort Lock = 0x0010; } - internal enum HostOS + internal static class HostOsV4 { - MSDOS = 0, - OS2 = 1, - Win32 = 2, - Unix = 3, - MacOS = 4, - BeOS = 5 + public const byte MsDos = 0; + public const byte Os2 = 1; + public const byte Win32 = 2; + public const byte Unix = 3; + public const byte MacOs = 4; + public const byte BeOs = 5; } - [Flags] - internal enum FileFlags : ushort + internal static class HostOsV5 { - SPLIT_BEFORE = 0x0001, - SPLIT_AFTER = 0x0002, - PASSWORD = 0x0004, - COMMENT = 0x0008, - SOLID = 0x0010, - - WINDOWMASK = 0x00e0, - WINDOW64 = 0x0000, - WINDOW128 = 0x0020, - WINDOW256 = 0x0040, - WINDOW512 = 0x0060, - WINDOW1024 = 0x0080, - WINDOW2048 = 0x00a0, - WINDOW4096 = 0x00c0, - DIRECTORY = 0x00e0, - - LARGE = 0x0100, - UNICODE = 0x0200, - SALT = 0x0400, - VERSION = 0x0800, - EXTTIME = 0x1000, - EXTFLAGS = 0x2000 + public const byte Windows = 0; + public const byte Unix = 1; } - [Flags] - internal enum EndArchiveFlags + internal static class FileFlagsV4 { - EARC_NEXT_VOLUME = 0x0001, - EARC_DATACRC = 0x0002, - EARC_REVSPACE = 0x0004, - EARC_VOLNUMBER = 0x0008 + public const ushort SplitBefore = 0x0001; + public const ushort SplitAfter = 0x0002; + public const ushort Password = 0x0004; + public const ushort Comment = 0x0008; + public const ushort Solid = 0x0010; + + public const ushort WindowMask = 0x00e0; + public const ushort Window64 = 0x0000; + public const ushort Window128 = 0x0020; + public const ushort Window256 = 0x0040; + public const ushort Window512 = 0x0060; + public const ushort Window1024 = 0x0080; + public const ushort Window2048 = 0x00a0; + public const ushort Window4096 = 0x00c0; + public const ushort Directory = 0x00e0; + + public const ushort Large = 0x0100; + public const ushort Unicode = 0x0200; + public const ushort Salt = 0x0400; + public const ushort Version = 0x0800; + public const ushort ExtTime = 0x1000; + public const ushort ExtFlags = 0x2000; + } + + internal static class FileFlagsV5 + { + public const ushort Directory = 0x0001; + public const ushort HasModTime = 0x0002; + public const ushort HasCrc32 = 0x0004; + public const ushort UnpackedSizeUnknown = 0x0008; + } + + internal static class EndArchiveFlagsV4 + { + public const ushort NextVolume = 0x0001; + public const ushort DataCrc = 0x0002; + public const ushort RevSpace = 0x0004; + public const ushort VolumeNumber = 0x0008; + } + + internal static class EndArchiveFlagsV5 + { + public const ushort HasNextVolume = 0x0001; } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/IRarHeader.cs b/src/SharpCompress/Common/Rar/Headers/IRarHeader.cs new file mode 100644 index 00000000..93f9b0ca --- /dev/null +++ b/src/SharpCompress/Common/Rar/Headers/IRarHeader.cs @@ -0,0 +1,6 @@ +namespace SharpCompress.Common.Rar.Headers +{ + internal interface IRarHeader { + HeaderType HeaderType { get; } + } +} \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs b/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs index 30225bc7..4e972913 100644 --- a/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs @@ -5,41 +5,48 @@ namespace SharpCompress.Common.Rar.Headers { internal class MarkHeader : IRarHeader { - private const int MAX_SFX_SIZE = 0x80000 - 16; //archive.cpp line 136 + private const int MaxSfxSize = 0x80000 - 16; //archive.cpp line 136 internal bool OldNumberingFormat { get; private set; } - public bool IsRar5 { get; private set; } + public bool IsRar5 { get; } - private MarkHeader(bool isRar5) { + private MarkHeader(bool isRar5) + { IsRar5 = isRar5; } public HeaderType HeaderType { - get { return HeaderType.MarkHeader; } + get { return HeaderType.Mark; } } - private static byte GetByte(Stream stream) { + private static byte GetByte(Stream stream) + { var b = stream.ReadByte(); - if (b != -1) { + if (b != -1) + { return (byte)b; } throw new EndOfStreamException(); } - public static MarkHeader Read(Stream stream, bool leaveStreamOpen, bool lookForHeader) { - int maxScanIndex = lookForHeader ? MAX_SFX_SIZE : 0; + public static MarkHeader Read(Stream stream, bool leaveStreamOpen, bool lookForHeader) + { + int maxScanIndex = lookForHeader ? MaxSfxSize : 0; try { int start = -1; var b = GetByte(stream); start++; - while (start <= maxScanIndex) { + while (start <= maxScanIndex) + { // Rar old signature: 52 45 7E 5E // Rar4 signature: 52 61 72 21 1A 07 00 // Rar5 signature: 52 61 72 21 1A 07 01 00 - if (b == 0x52) { + if (b == 0x52) + { b = GetByte(stream); start++; - if (b == 0x61) { + if (b == 0x61) + { b = GetByte(stream); start++; if (b != 0x72) continue; b = GetByte(stream); start++; @@ -50,26 +57,36 @@ namespace SharpCompress.Common.Rar.Headers if (b != 0x07) continue; b = GetByte(stream); start++; - if (b == 1) { + if (b == 1) + { b = GetByte(stream); start++; if (b != 0) continue; return new MarkHeader(true); // Rar5 - } else if (b == 0) { + } + else if (b == 0) + { return new MarkHeader(false); // Rar4 - } else { + } + else + { continue; } - } else if (b == 0x45) { + } + else if (b == 0x45) + { b = GetByte(stream); start++; if (b != 0x7e) continue; b = GetByte(stream); start++; if (b != 0x5e) continue; throw new InvalidFormatException("Rar format version pre-4 is unsupported."); - //return new MarkHeader(); - } else { + } + else + { continue; } - } else { + } + else + { b = GetByte(stream); start++; continue; } diff --git a/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs b/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs index c47c5676..75dbcb9e 100644 --- a/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs @@ -13,7 +13,7 @@ namespace SharpCompress.Common.Rar.Headers Mark = reader.ReadBytes(8); } - internal uint DataSize => AdditionalDataSize; + internal uint DataSize => checked((uint)AdditionalDataSize); internal byte Version { get; private set; } internal ushort RecSectors { get; private set; } internal uint TotalBlocks { get; private set; } diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs index 310e8bbf..e81f82f2 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs @@ -6,20 +6,23 @@ namespace SharpCompress.Common.Rar.Headers { internal class RarHeader : IRarHeader { - protected readonly bool isRar5; + private readonly HeaderType headerType; + private readonly bool isRar5; protected RarHeader() { //TODO - //throw new NotImplementedException(); + throw new NotImplementedException(); } private RarHeader(bool isRar5) { + this.headerType = HeaderType.Null; this.isRar5 = isRar5; } - protected RarHeader(RarHeader header, RarCrcBinaryReader reader) { - this.isRar5 = header.isRar5; + protected RarHeader(RarHeader header, RarCrcBinaryReader reader, HeaderType headerType) { + this.headerType = headerType; + this.isRar5 = header.IsRar5; FillBase(header); ReadFromReader(reader); ReadBytes = reader.CurrentReadByteCount; @@ -33,16 +36,16 @@ namespace SharpCompress.Common.Rar.Headers VerifyHeaderCrc(reader.GetCrc32()); } - private void FillBase(RarHeader baseHeader) + private void FillBase(RarHeader other) { - HeadCRC = baseHeader.HeadCRC; - HeaderType = baseHeader.HeaderType; - Flags = baseHeader.Flags; - HeaderSize = baseHeader.HeaderSize; - ExtraSize = baseHeader.ExtraSize; - AdditionalDataSize = baseHeader.AdditionalDataSize; - ReadBytes = baseHeader.ReadBytes; - ArchiveEncoding = baseHeader.ArchiveEncoding; + HeaderCrc = other.HeaderCrc; + HeaderCode = other.HeaderCode; + HeaderFlags = other.HeaderFlags; + HeaderSize = other.HeaderSize; + ExtraSize = other.ExtraSize; + AdditionalDataSize = other.AdditionalDataSize; + ReadBytes = other.ReadBytes; + ArchiveEncoding = other.ArchiveEncoding; } internal static RarHeader ReadBase(RarCrcBinaryReader reader, ArchiveEncoding archiveEncoding, bool isRar5) @@ -63,30 +66,30 @@ namespace SharpCompress.Common.Rar.Headers private void ReadStartFromReader(RarCrcBinaryReader reader) { - if (this.isRar5) + if (this.IsRar5) { - HeadCRC = reader.ReadUInt32(); + HeaderCrc = reader.ReadUInt32(); reader.ResetCrc(); HeaderSize = (int)reader.ReadRarVIntUInt32(3); reader.Mark(); - HeaderType = (HeaderType)reader.ReadRarVIntUInt32(1); - Flags = reader.ReadRarVIntUInt16(2); - if ((Flags & HeaderFlagsV5.HasExtra) == HeaderFlagsV5.HasExtra) + HeaderCode = reader.ReadRarVIntByte(); + HeaderFlags = reader.ReadRarVIntUInt16(2); + if ((HeaderFlags & HeaderFlagsV5.HasExtra) == HeaderFlagsV5.HasExtra) { ExtraSize = reader.ReadRarVIntUInt32(); } - if ((Flags & HeaderFlagsV5.HasData) == HeaderFlagsV5.HasData) + if ((HeaderFlags & HeaderFlagsV5.HasData) == HeaderFlagsV5.HasData) { - AdditionalDataSize = reader.ReadRarVIntUInt32(); + AdditionalDataSize = (long)reader.ReadRarVInt(); } } else { reader.Mark(); - HeadCRC = reader.ReadUInt16(); + HeaderCrc = reader.ReadUInt16(); reader.ResetCrc(); - HeaderType = (HeaderType)reader.ReadByte(); - Flags = reader.ReadUInt16(); + HeaderCode = reader.ReadByte(); + HeaderFlags = reader.ReadUInt16(); HeaderSize = reader.ReadInt16(); - if ((Flags & HeaderFlagsV4.HasData) == HeaderFlagsV4.HasData) + if ((HeaderFlags & HeaderFlagsV4.HasData) == HeaderFlagsV4.HasData) { AdditionalDataSize = reader.ReadUInt32(); } @@ -119,13 +122,10 @@ namespace SharpCompress.Common.Rar.Headers private void VerifyHeaderCrc(uint crc32) { - if (HeaderType != HeaderType.MarkHeader) + var b = (this.IsRar5 ? crc32 : (ushort)crc32) == HeaderCrc; + if (!b) { - var b = (this.isRar5 ? crc32 : (ushort)crc32) == HeadCRC; - if (!b) - { - throw new InvalidFormatException("rar header crc mismatch"); - } + throw new InvalidFormatException("rar header crc mismatch"); } } @@ -133,32 +133,33 @@ namespace SharpCompress.Common.Rar.Headers { } + public HeaderType HeaderType { get { return this.headerType; } } + + protected bool IsRar5 { get { return this.isRar5; } } + /// /// This is the number of bytes read when reading the header /// protected long ReadBytes { get; private set; } - protected uint HeadCRC { get; private set; } + protected uint HeaderCrc { get; private set; } - public HeaderType HeaderType { get; private set; } + internal byte HeaderCode { get; private set; } - /// - /// Untyped flags. These should be typed when Promoting to another header - /// - protected ushort Flags { get; private set; } + protected ushort HeaderFlags { get; private set; } protected int HeaderSize { get; private set; } internal ArchiveEncoding ArchiveEncoding { get; private set; } /// - /// Extra header data. + /// Extra header size. /// protected uint ExtraSize { get; private set; } /// - /// Additional data size, eg recovery record + /// Size of additional data (eg file contents) /// - protected uint AdditionalDataSize { get; private set; } + protected long AdditionalDataSize { get; private set; } } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs index 22ac412f..005e03ac 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs @@ -30,9 +30,10 @@ namespace SharpCompress.Common.Rar.Headers while ((header = ReadNextHeader(stream)) != null) { yield return header; - if (header.HeaderType == HeaderType.EndArchiveHeader) + if (header.HeaderType == HeaderType.EndArchive) { - yield break; // the end? + // End of archive marker. RAR does not read anything after this header letting to use third party tools to add extra information such as a digital signature to archive. + yield break; } } } @@ -59,18 +60,20 @@ namespace SharpCompress.Common.Rar.Headers { return null; } - switch (header.HeaderType) + switch (header.HeaderCode) { - case HeaderType.Rar5ArchiveHeader: - case HeaderType.ArchiveHeader: + case HeaderCodeV.Rar5ArchiveHeader: + case HeaderCodeV.ArchiveHeader: { var ah = new ArchiveHeader(header, reader); - //x var ah = header.PromoteHeader(reader); - IsEncrypted = ah.HasPassword; + if (ah.IsEncrypted == true) { +//!!! rar5 we don't know yet + IsEncrypted = true; + } return ah; } - case HeaderType.ProtectHeader: + case HeaderCodeV.ProtectHeader: { ProtectHeader ph = header.PromoteHeader(reader); @@ -96,9 +99,10 @@ namespace SharpCompress.Common.Rar.Headers return ph; } - case HeaderType.NewSubHeader: + case HeaderCodeV.Rar5ServiceHeader: + case HeaderCodeV.NewSubHeader: { - FileHeader fh = header.PromoteHeader(reader); + var fh = new FileHeader(header, reader); switch (StreamingMode) { case StreamingMode.Seekable: @@ -120,9 +124,11 @@ namespace SharpCompress.Common.Rar.Headers } return fh; } - case HeaderType.FileHeader: + + case HeaderCodeV.Rar5FileHeader: + case HeaderCodeV.FileHeader: { - FileHeader fh = header.PromoteHeader(reader); + var fh = new FileHeader(header, reader); switch (StreamingMode) { case StreamingMode.Seekable: @@ -134,16 +140,16 @@ namespace SharpCompress.Common.Rar.Headers case StreamingMode.Streaming: { var ms = new ReadOnlySubStream(reader.BaseStream, fh.CompressedSize); - if (fh.Salt == null) + if (fh.R4Salt == null) { fh.PackedStream = ms; } else { #if !NO_CRYPTO - fh.PackedStream = new RarCryptoWrapper(ms, Options.Password, fh.Salt); + fh.PackedStream = new RarCryptoWrapper(ms, Options.Password, fh.R4Salt); #else - throw new NotSupportedException("RarCrypto not supported"); + throw new NotSupportedException("RarCrypto not supported"); #endif } } @@ -155,13 +161,14 @@ namespace SharpCompress.Common.Rar.Headers } return fh; } - case HeaderType.EndArchiveHeader: + case HeaderCodeV.Rar5EndArchiveHeader: + case HeaderCodeV.EndArchiveHeader: { - return header.PromoteHeader(reader); + return new EndArchiveHeader(header, reader); } default: { - throw new InvalidFormatException("Invalid Rar Header: " + header.HeaderType); + throw new InvalidFormatException("Invalid Rar Header: " + header.HeaderCode); } } } diff --git a/src/SharpCompress/Common/Rar/RarEntry.cs b/src/SharpCompress/Common/Rar/RarEntry.cs index cddfca3e..105a306a 100644 --- a/src/SharpCompress/Common/Rar/RarEntry.cs +++ b/src/SharpCompress/Common/Rar/RarEntry.cs @@ -10,7 +10,7 @@ namespace SharpCompress.Common.Rar /// /// The File's 32 bit CRC Hash /// - public override long Crc => FileHeader.FileCRC; + public override long Crc => FileHeader.FileCrc; /// /// The path of the file internal to the Rar Archive. @@ -40,14 +40,14 @@ namespace SharpCompress.Common.Rar /// /// Entry is password protected and encrypted and cannot be extracted. /// - public override bool IsEncrypted => FileHeader.FileFlags.HasFlag(FileFlags.PASSWORD); + public override bool IsEncrypted => FileHeader.IsEncrypted; /// /// Entry is password protected and encrypted and cannot be extracted. /// - public override bool IsDirectory => FileHeader.FileFlags.HasFlag(FileFlags.DIRECTORY); + public override bool IsDirectory => FileHeader.IsDirectory; - public override bool IsSplit => FileHeader.FileFlags.HasFlag(FileFlags.SPLIT_AFTER); + public override bool IsSplit => FileHeader.IsSplit; public override string ToString() { diff --git a/src/SharpCompress/Common/Rar/RarVolume.cs b/src/SharpCompress/Common/Rar/RarVolume.cs index 521cae19..17be5a30 100644 --- a/src/SharpCompress/Common/Rar/RarVolume.cs +++ b/src/SharpCompress/Common/Rar/RarVolume.cs @@ -34,17 +34,17 @@ namespace SharpCompress.Common.Rar { switch (header.HeaderType) { - case HeaderType.ArchiveHeader: + case HeaderType.Archive: { ArchiveHeader = header as ArchiveHeader; } break; - case HeaderType.MarkHeader: + case HeaderType.Mark: { previousMarkHeader = header as MarkHeader; } break; - case HeaderType.FileHeader: + case HeaderType.File: { FileHeader fh = header as FileHeader; RarFilePart fp = CreateFilePart(fh, previousMarkHeader); diff --git a/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs b/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs index d5618d5f..235b8720 100644 --- a/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs +++ b/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs @@ -59,7 +59,7 @@ namespace SharpCompress.Compressors.Rar currentPartTotalReadBytes = 0; - CurrentCrc = filePartEnumerator.Current.FileHeader.FileCRC; + CurrentCrc = filePartEnumerator.Current.FileHeader.FileCrc; streamListener.FireFilePartExtractionBegin(filePartEnumerator.Current.FilePartName, filePartEnumerator.Current.FileHeader.CompressedSize, @@ -90,9 +90,9 @@ namespace SharpCompress.Compressors.Rar currentCount -= read; totalRead += read; if (((maxPosition - currentPosition) == 0) - && filePartEnumerator.Current.FileHeader.FileFlags.HasFlag(FileFlags.SPLIT_AFTER)) + && filePartEnumerator.Current.FileHeader.IsSplit) { - if (filePartEnumerator.Current.FileHeader.Salt != null) + if (filePartEnumerator.Current.FileHeader.R4Salt != null) { throw new InvalidFormatException("Sharpcompress currently does not support multi-volume decryption."); } diff --git a/src/SharpCompress/Compressors/Rar/Unpack.cs b/src/SharpCompress/Compressors/Rar/Unpack.cs index 197d6e51..81c053b0 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/Unpack.cs @@ -132,8 +132,7 @@ namespace SharpCompress.Compressors.Rar this.fileHeader = fileHeader; this.writeStream = writeStream; this.readStream = readStream; - bool solid = FlagUtility.HasFlag(fileHeader.FileFlags, FileFlags.SOLID); - if (!solid) + if (!fileHeader.IsSolid) { init(null); } @@ -143,13 +142,13 @@ namespace SharpCompress.Compressors.Rar public void doUnpack() { - bool solid = FlagUtility.HasFlag(fileHeader.FileFlags, FileFlags.SOLID); - if (fileHeader.PackingMethod == 0x30) + if (fileHeader.R4PackingMethod == 0x30) { unstoreFile(); return; } - switch (fileHeader.RarVersion) + var solid = fileHeader.IsSolid; + switch (fileHeader.R4RarVersion) { case 15: // rar 1.5 compression unpack15(solid); diff --git a/src/SharpCompress/IO/MarkingBinaryReader.cs b/src/SharpCompress/IO/MarkingBinaryReader.cs index ed33d8a4..1df9e7df 100644 --- a/src/SharpCompress/IO/MarkingBinaryReader.cs +++ b/src/SharpCompress/IO/MarkingBinaryReader.cs @@ -194,5 +194,23 @@ namespace SharpCompress.IO throw new FormatException("malformed vint"); } + + public byte ReadRarVIntByte(int maxBytes = 1) { + int shift = 0; + uint result = 0; + do { + byte b0 = ReadByte(); + var b1 = b0 & 0x7f; + uint n = (uint)b1; + result |= n << shift; + if (b0 == b1) { + return checked((byte)result); + } + shift += 7; + // NOTE: we are too strict here but handling the full range adds complexity and we don't need it + } while (shift < 7 && --maxBytes > 0); + + throw new FormatException("malformed vint"); + } } } \ No newline at end of file diff --git a/src/SharpCompress/Utility.cs b/src/SharpCompress/Utility.cs index 751dd29d..a77360fb 100644 --- a/src/SharpCompress/Utility.cs +++ b/src/SharpCompress/Utility.cs @@ -246,9 +246,15 @@ namespace SharpCompress (UInt16)(iTime % 65536)); } - public static DateTime DosDateToDateTime(Int32 iTime) + /// + /// Convert Unix time value to a DateTime object. + /// + /// The Unix time stamp you want to convert to DateTime. + /// Returns a DateTime object that represents value of the Unix time. + public static DateTime UnixTimeToDateTime(long unixtime) { - return DosDateToDateTime((UInt32)iTime); + DateTime sTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + return sTime.AddSeconds(unixtime); } public static long TransferTo(this Stream source, Stream destination) diff --git a/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs b/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs index 9d5b07d2..ea42b06c 100644 --- a/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs +++ b/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs @@ -35,7 +35,7 @@ namespace SharpCompress.Test.Rar { foreach (var header in rarHeaderFactory.ReadHeaders(stream)) { - if (header.HeaderType == HeaderType.ArchiveHeader) + if (header.HeaderType == HeaderType.Archive) { Assert.Equal(isEncrypted, rarHeaderFactory.IsEncrypted); break; From 5f121c5da475b719ea42b27406647acbc08a19e7 Mon Sep 17 00:00:00 2001 From: coderb Date: Sun, 17 Dec 2017 07:39:02 -0500 Subject: [PATCH 05/61] rar5 wip --- .../Common/Rar/Headers/FileHeader.cs | 4 +- src/SharpCompress/Common/Rar/Headers/Flags.cs | 4 +- .../Common/Rar/Headers/RarHeaderFactory.cs | 48 +++++----- src/SharpCompress/Compressors/Rar/Unpack.cs | 10 +-- src/SharpCompress/IO/MarkingBinaryReader.cs | 90 +++++++++---------- 5 files changed, 76 insertions(+), 80 deletions(-) diff --git a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs index dd789c6b..27fffe3b 100644 --- a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs @@ -9,8 +9,8 @@ namespace SharpCompress.Common.Rar.Headers { private uint fileCrc; - public FileHeader(RarHeader header, RarCrcBinaryReader reader) - : base(header, reader, HeaderType.File) { + public FileHeader(RarHeader header, RarCrcBinaryReader reader, HeaderType headerType) + : base(header, reader, headerType) { } protected override void ReadFromReader(MarkingBinaryReader reader) diff --git a/src/SharpCompress/Common/Rar/Headers/Flags.cs b/src/SharpCompress/Common/Rar/Headers/Flags.cs index dba0d245..0bbe87aa 100644 --- a/src/SharpCompress/Common/Rar/Headers/Flags.cs +++ b/src/SharpCompress/Common/Rar/Headers/Flags.cs @@ -8,9 +8,11 @@ namespace SharpCompress.Common.Rar.Headers Mark, Archive, File, + NewSub, + Service, // Comment, +// Sub // Av, -// Sub, // Protect, // Sign, // NewSub, diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs index 005e03ac..9db50d8e 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs @@ -100,35 +100,23 @@ namespace SharpCompress.Common.Rar.Headers } case HeaderCodeV.Rar5ServiceHeader: + { + var fh = new FileHeader(header, reader, HeaderType.Service); + SkipData(fh, reader); + return fh; + } + case HeaderCodeV.NewSubHeader: { - var fh = new FileHeader(header, reader); - switch (StreamingMode) - { - case StreamingMode.Seekable: - { - fh.DataStartPosition = reader.BaseStream.Position; - reader.BaseStream.Position += fh.CompressedSize; - } - break; - case StreamingMode.Streaming: - { - //skip the data because it's useless? - reader.BaseStream.Skip(fh.CompressedSize); - } - break; - default: - { - throw new InvalidFormatException("Invalid StreamingMode"); - } - } + var fh = new FileHeader(header, reader, HeaderType.NewSub); + SkipData(fh, reader); return fh; } case HeaderCodeV.Rar5FileHeader: case HeaderCodeV.FileHeader: { - var fh = new FileHeader(header, reader); + var fh = new FileHeader(header, reader, HeaderType.File); switch (StreamingMode) { case StreamingMode.Seekable: @@ -172,5 +160,23 @@ namespace SharpCompress.Common.Rar.Headers } } } + + private void SkipData(FileHeader fh, RarCrcBinaryReader reader) { + switch (StreamingMode) { + case StreamingMode.Seekable: { + fh.DataStartPosition = reader.BaseStream.Position; + reader.BaseStream.Position += fh.CompressedSize; + } + break; + case StreamingMode.Streaming: { + //skip the data because it's useless? + reader.BaseStream.Skip(fh.CompressedSize); + } + break; + default: { + throw new InvalidFormatException("Invalid StreamingMode"); + } + } + } } } \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/Unpack.cs b/src/SharpCompress/Compressors/Rar/Unpack.cs index 81c053b0..78450eeb 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/Unpack.cs @@ -154,19 +154,17 @@ namespace SharpCompress.Compressors.Rar unpack15(solid); break; - case 20: - - // rar 2.x compression + case 20: // rar 2.x compression case 26: // files larger than 2GB unpack20(solid); break; - case 29: - - // rar 3.x compression + case 29: // rar 3.x compression case 36: // alternative hash unpack29(solid); break; + + default: throw new InvalidFormatException("unknown rar compression version " + this.fileHeader.R4RarVersion); } } diff --git a/src/SharpCompress/IO/MarkingBinaryReader.cs b/src/SharpCompress/IO/MarkingBinaryReader.cs index 1df9e7df..e1037995 100644 --- a/src/SharpCompress/IO/MarkingBinaryReader.cs +++ b/src/SharpCompress/IO/MarkingBinaryReader.cs @@ -133,82 +133,72 @@ namespace SharpCompress.IO // RAR5 style variable length encoded value // maximum value of 0xffffffffffffffff (64 bits) - // implies max 10 bytes consumed + // technote: "implies max 10 bytes consumed" -- but not really because we could extend indefinitely using 0x80 0x80 ... 0x80 00 // // Variable length integer. Can include one or more bytes, where lower 7 bits of every byte contain integer data // and highest bit in every byte is the continuation flag. If highest bit is 0, this is the last byte in sequence. // So first byte contains 7 least significant bits of integer and continuation flag. Second byte, if present, // contains next 7 bits and so on. - public ulong ReadRarVInt() { + public ulong ReadRarVInt(int maxBytes = 10) { + // hopefully this gets inlined + return DoReadRarVInt((maxBytes - 1) * 7); + } + + private ulong DoReadRarVInt(int maxShift) { int shift = 0; ulong result = 0; do { byte b0 = ReadByte(); - var b1 = b0 & 0x7f; - ulong n = (ulong)b1; - result |= n << shift; + uint b1 = ((uint)b0) & 0x7f; + ulong n = b1; + ulong shifted = n << shift; + if (n != shifted >> shift) { + // overflow + break; + } + result |= shifted; if (b0 == b1) { return result; } - // note: we're actually only allowing a max high bit of 2^56 - // to avoid an extra complex check for shift overflow due to the - // 10th byte having high bits set shift += 7; - } while (shift < 63); + } while (shift <= maxShift); throw new FormatException("malformed vint"); } public uint ReadRarVIntUInt32(int maxBytes = 5) { + // hopefully this gets inlined + return DoReadRarVIntUInt32((maxBytes - 1) * 7); + } + + public ushort ReadRarVIntUInt16(int maxBytes = 3) { + // hopefully this gets inlined + return checked((ushort)DoReadRarVIntUInt32((maxBytes - 1) * 7)); + } + + public byte ReadRarVIntByte(int maxBytes = 2) { + // hopefully this gets inlined + return checked((byte)DoReadRarVIntUInt32((maxBytes - 1) * 7)); + } + + private uint DoReadRarVIntUInt32(int maxShift) { int shift = 0; uint result = 0; do { byte b0 = ReadByte(); - var b1 = b0 & 0x7f; - uint n = (uint)b1; - result |= n << shift; + uint b1 = ((uint)b0) & 0x7f; + uint n = b1; + uint shifted = n << shift; + if (n != shifted >> shift) { + // overflow + break; + } + result |= shifted; if (b0 == b1) { return result; } shift += 7; - // NOTE: we are too strict here but handling the full range adds complexity and we don't need it - } while (shift < 28 && --maxBytes > 0); - - throw new FormatException("malformed vint"); - } - - public ushort ReadRarVIntUInt16(int maxBytes = 3) { - int shift = 0; - uint result = 0; - do { - byte b0 = ReadByte(); - var b1 = b0 & 0x7f; - uint n = (uint)b1; - result |= n << shift; - if (b0 == b1) { - return checked((ushort)result); - } - shift += 7; - // NOTE: we are too strict here but handling the full range adds complexity and we don't need it - } while (shift < 14 && --maxBytes > 0); - - throw new FormatException("malformed vint"); - } - - public byte ReadRarVIntByte(int maxBytes = 1) { - int shift = 0; - uint result = 0; - do { - byte b0 = ReadByte(); - var b1 = b0 & 0x7f; - uint n = (uint)b1; - result |= n << shift; - if (b0 == b1) { - return checked((byte)result); - } - shift += 7; - // NOTE: we are too strict here but handling the full range adds complexity and we don't need it - } while (shift < 7 && --maxBytes > 0); + } while (shift <= maxShift); throw new FormatException("malformed vint"); } From 1c49ff63e227cadc572a2d8060b323f9ac1e0f14 Mon Sep 17 00:00:00 2001 From: coderb Date: Sun, 17 Dec 2017 08:08:02 -0500 Subject: [PATCH 06/61] rar5: replicate rar test cases --- .../SharpCompress.Test/Rar/RarArchiveTests.cs | 164 ++++++++++++++-- .../Rar/RarHeaderFactoryTest.cs | 37 ++-- .../SharpCompress.Test/Rar/RarReaderTests.cs | 176 ++++++++++++++---- ...udio_program.rar => Rar.Audio_program.rar} | Bin .../{Encrypted.rar => Rar.Encrypted.rar} | Bin ...rt01.rar => Rar.EncryptedParts.part01.rar} | Bin ...rt02.rar => Rar.EncryptedParts.part02.rar} | Bin ...rt03.rar => Rar.EncryptedParts.part03.rar} | Bin ...rt04.rar => Rar.EncryptedParts.part04.rar} | Bin ...rt05.rar => Rar.EncryptedParts.part05.rar} | Bin ...rt06.rar => Rar.EncryptedParts.part06.rar} | Bin .../Archives/{Rarjpeg.jpg => Rar.jpeg.jpg} | Bin ...ttime.rar => Rar.test_invalid_exttime.rar} | Bin 13 files changed, 305 insertions(+), 72 deletions(-) rename tests/TestArchives/Archives/{Audio_program.rar => Rar.Audio_program.rar} (100%) rename tests/TestArchives/Archives/{Encrypted.rar => Rar.Encrypted.rar} (100%) rename tests/TestArchives/Archives/{EncryptedParts.part01.rar => Rar.EncryptedParts.part01.rar} (100%) rename tests/TestArchives/Archives/{EncryptedParts.part02.rar => Rar.EncryptedParts.part02.rar} (100%) rename tests/TestArchives/Archives/{EncryptedParts.part03.rar => Rar.EncryptedParts.part03.rar} (100%) rename tests/TestArchives/Archives/{EncryptedParts.part04.rar => Rar.EncryptedParts.part04.rar} (100%) rename tests/TestArchives/Archives/{EncryptedParts.part05.rar => Rar.EncryptedParts.part05.rar} (100%) rename tests/TestArchives/Archives/{EncryptedParts.part06.rar => Rar.EncryptedParts.part06.rar} (100%) rename tests/TestArchives/Archives/{Rarjpeg.jpg => Rar.jpeg.jpg} (100%) rename tests/TestArchives/Archives/{test_invalid_exttime.rar => Rar.test_invalid_exttime.rar} (100%) diff --git a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs index 55d79994..54619c92 100644 --- a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs +++ b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs @@ -16,16 +16,34 @@ namespace SharpCompress.Test.Rar ReadRarPassword("Rar.encrypted_filesAndHeader.rar", "test"); } + [Fact] + public void Rar5_EncryptedFileAndHeader_Archive() + { + ReadRarPassword("Rar5.encrypted_filesAndHeader.rar", "test"); + } + [Fact] public void Rar_EncryptedFileOnly_Archive() { ReadRarPassword("Rar.encrypted_filesOnly.rar", "test"); } + [Fact] + public void Rar5_EncryptedFileOnly_Archive() + { + ReadRarPassword("Rar5.encrypted_filesOnly.rar", "test"); + } + [Fact] public void Rar_Encrypted_Archive() { - ReadRarPassword("Encrypted.rar", "test"); + ReadRarPassword("Rar.Encrypted.rar", "test"); + } + + [Fact] + public void Rar5_Encrypted_Archive() + { + ReadRarPassword("Rar5.Encrypted.rar", "test"); } private void ReadRarPassword(string testArchive, string password) @@ -57,7 +75,13 @@ namespace SharpCompress.Test.Rar [Fact] public void Rar_Multi_Archive_Encrypted() { - Assert.Throws(() => ArchiveFileReadPassword("EncryptedParts.part01.rar", "test")); + Assert.Throws(() => ArchiveFileReadPassword("Rar.EncryptedParts.part01.rar", "test")); + } + + [Fact] + public void Rar5_Multi_Archive_Encrypted() + { + Assert.Throws(() => ArchiveFileReadPassword("Rar5.EncryptedParts.part01.rar", "test")); } protected void ArchiveFileReadPassword(string archiveName, string password) @@ -88,6 +112,12 @@ namespace SharpCompress.Test.Rar ArchiveStreamRead("Rar.none.rar"); } + [Fact] + public void Rar5_None_ArchiveStreamRead() + { + ArchiveStreamRead("Rar5.none.rar"); + } + [Fact] public void Rar_ArchiveStreamRead() { @@ -95,10 +125,25 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_test_invalid_exttime_ArchiveStreamRead() + public void Rar5_ArchiveStreamRead() + { + ArchiveStreamRead("Rar5.rar"); + } + + [Fact] + public void Rar_test_invalid_exttime_ArchiveStreamRead() { + DoRar_test_invalid_exttime_ArchiveStreamRead("Rar.test_invalid_exttime.rar"); + } + + [Fact] + public void Rar5_test_invalid_exttime_ArchiveStreamRead() { + DoRar_test_invalid_exttime_ArchiveStreamRead("Rar5.test_invalid_exttime.rar"); + } + + private void DoRar_test_invalid_exttime_ArchiveStreamRead(string filename) { ResetScratch(); - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "test_invalid_exttime.rar"))) + using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename))) { using (var archive = ArchiveFactory.Open(stream)) { @@ -118,7 +163,7 @@ namespace SharpCompress.Test.Rar public void Rar_Jpg_ArchiveStreamRead() { ResetScratch(); - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rarjpeg.jpg"))) + using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg"))) { using (var archive = RarArchive.Open(stream, new ReaderOptions() { @@ -140,17 +185,26 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_IsSolidArchiveCheck() + public void Rar_IsSolidArchiveCheck() { + DoRar_IsSolidArchiveCheck("Rar.rar"); + } + + [Fact] + public void Rar5_IsSolidArchiveCheck() { + DoRar_IsSolidArchiveCheck("Rar5.rar"); + } + + private void DoRar_IsSolidArchiveCheck(string filename) { ResetScratch(); - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.rar"))) + using (var stream = File.OpenRead(Path.Combine(this.TEST_ARCHIVES_PATH, filename))) { using (var archive = RarArchive.Open(stream)) { Assert.False(archive.IsSolid); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - entry.WriteToDirectory(SCRATCH_FILES_PATH, + entry.WriteToDirectory(this.SCRATCH_FILES_PATH, new ExtractionOptions() { ExtractFullPath = true, @@ -168,6 +222,12 @@ namespace SharpCompress.Test.Rar Assert.Throws(() => ArchiveStreamRead("Rar.solid.rar")); } + [Fact] + public void Rar5_Solid_ArchiveStreamRead() + { + Assert.Throws(() => ArchiveStreamRead("Rar5.solid.rar")); + } + [Fact] public void Rar_Solid_StreamRead_Extract_All() { @@ -175,23 +235,42 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_Multi_ArchiveStreamRead() + public void Rar5_Solid_StreamRead_Extract_All() { - var testArchives = new string[] { "Rar.multi.part01.rar", + ArchiveStreamReadExtractAll("Rar5.solid.rar", CompressionType.Rar); + } + + [Fact] + public void Rar_Multi_ArchiveStreamRead() { + DoRar_Multi_ArchiveStreamRead(new string[] { + "Rar.multi.part01.rar", "Rar.multi.part02.rar", "Rar.multi.part03.rar", "Rar.multi.part04.rar", "Rar.multi.part05.rar", - "Rar.multi.part06.rar"}; + "Rar.multi.part06.rar"}); + } + [Fact] + public void Rar5_Multi_ArchiveStreamRead() { + DoRar_Multi_ArchiveStreamRead(new string[] { + "Rar5.multi.part01.rar", + "Rar5.multi.part02.rar", + "Rar5.multi.part03.rar", + "Rar5.multi.part04.rar", + "Rar5.multi.part05.rar", + "Rar5.multi.part06.rar"}); + } + private void DoRar_Multi_ArchiveStreamRead(string[] archives) + { ResetScratch(); - using (var archive = RarArchive.Open(testArchives.Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)) + using (var archive = RarArchive.Open(archives.Select(s => Path.Combine(this.TEST_ARCHIVES_PATH, s)) .Select(File.OpenRead))) { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - entry.WriteToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() + entry.WriteToDirectory(this.SCRATCH_FILES_PATH, new ExtractionOptions() { ExtractFullPath = true, Overwrite = true @@ -220,10 +299,25 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_ArchiveFileRead_HasDirectories() + public void Rar5_ArchiveFileRead() + { + ArchiveFileRead("Rar5.rar"); + } + + [Fact] + public void Rar_ArchiveFileRead_HasDirectories() { + DoRar_ArchiveFileRead_HasDirectories("Rar.rar"); + } + + [Fact] + public void Rar5_ArchiveFileRead_HasDirectories() { + DoRar_ArchiveFileRead_HasDirectories("Rar5.rar"); + } + + private void DoRar_ArchiveFileRead_HasDirectories(string filename) { ResetScratch(); - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.rar"))) + using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename))) { using (var archive = RarArchive.Open(stream)) { @@ -237,7 +331,7 @@ namespace SharpCompress.Test.Rar public void Rar_Jpg_ArchiveFileRead() { ResetScratch(); - using (var archive = RarArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, "Rarjpeg.jpg"), new ReaderOptions() + using (var archive = RarArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg"), new ReaderOptions() { LookForHeader = true })) @@ -260,6 +354,12 @@ namespace SharpCompress.Test.Rar Assert.Throws(() => ArchiveFileRead("Rar.solid.rar")); } + [Fact] + public void Rar5_Solid_ArchiveFileRead() + { + Assert.Throws(() => ArchiveFileRead("Rar5.solid.rar")); + } + [Fact] public void Rar_Multi_ArchiveFileRead() { @@ -267,9 +367,24 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_IsFirstVolume_True() + public void Rar5_Multi_ArchiveFileRead() { - using (var archive = RarArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, "Rar.multi.part01.rar"))) + ArchiveFileRead("Rar5.multi.part01.rar"); + } + + [Fact] + public void Rar_IsFirstVolume_True() { + DoRar_IsFirstVolume_True("Rar.multi.part01.rar"); + } + + [Fact] + public void Rar5_IsFirstVolume_True() { + DoRar_IsFirstVolume_True("Rar5.multi.part01.rar"); + } + + private void DoRar_IsFirstVolume_True(string firstFilename) + { + using (var archive = RarArchive.Open(Path.Combine(this.TEST_ARCHIVES_PATH, firstFilename))) { Assert.True(archive.IsMultipartVolume()); Assert.True(archive.IsFirstVolume()); @@ -277,9 +392,18 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_IsFirstVolume_False() + public void Rar_IsFirstVolume_False() { + DoRar_IsFirstVolume_False("Rar.multi.part03.rar"); + } + + [Fact] + public void Rar5_IsFirstVolume_False() { + DoRar_IsFirstVolume_False("Rar5.multi.part03.rar"); + } + + private void DoRar_IsFirstVolume_False(string notFirstFilename) { - using (var archive = RarArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, "Rar.multi.part03.rar"))) + using (var archive = RarArchive.Open(Path.Combine(this.TEST_ARCHIVES_PATH, notFirstFilename))) { Assert.True(archive.IsMultipartVolume()); Assert.False(archive.IsFirstVolume()); diff --git a/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs b/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs index ea42b06c..f334903b 100644 --- a/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs +++ b/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs @@ -16,19 +16,35 @@ namespace SharpCompress.Test.Rar public RarHeaderFactoryTest() { ResetScratch(); - rarHeaderFactory = new RarHeaderFactory(StreamingMode.Seekable, new ReaderOptions() - { - LeaveStreamOpen = true - }); + rarHeaderFactory = new RarHeaderFactory( + StreamingMode.Seekable, + new ReaderOptions { LeaveStreamOpen = true }); } - [Fact] - public void ReadHeaders_RecognizeEncryptedFlag() + public void Rar_ReadHeaders_RecognizeEncryptedFlag() { ReadEncryptedFlag("Rar.encrypted_filesAndHeader.rar", true); } + [Fact] + public void Rar5_ReadHeaders_RecognizeEncryptedFlag() + { + ReadEncryptedFlag("Rar5.encrypted_filesAndHeader.rar", true); + } + + [Fact] + public void Rar_ReadHeaders_RecognizeNoEncryptedFlag() + { + ReadEncryptedFlag("Rar.rar", false); + } + + [Fact] + public void Rar5_ReadHeaders_RecognizeNoEncryptedFlag() + { + ReadEncryptedFlag("Rar5.rar", false); + } + private void ReadEncryptedFlag(string testArchive, bool isEncrypted) { using (var stream = GetReaderStream(testArchive)) @@ -44,16 +60,9 @@ namespace SharpCompress.Test.Rar } } - [Fact] - public void ReadHeaders_RecognizeNoEncryptedFlag() - { - ReadEncryptedFlag("Rar.rar", false); - } - private FileStream GetReaderStream(string testArchive) { - return new FileStream(Path.Combine(TEST_ARCHIVES_PATH, testArchive), - FileMode.Open); + return new FileStream(Path.Combine(TEST_ARCHIVES_PATH, testArchive), FileMode.Open); } } } diff --git a/tests/SharpCompress.Test/Rar/RarReaderTests.cs b/tests/SharpCompress.Test/Rar/RarReaderTests.cs index aa6d5aa0..bc98dfab 100644 --- a/tests/SharpCompress.Test/Rar/RarReaderTests.cs +++ b/tests/SharpCompress.Test/Rar/RarReaderTests.cs @@ -10,23 +10,36 @@ namespace SharpCompress.Test.Rar public class RarReaderTests : ReaderTests { [Fact] - public void Rar_Multi_Reader() - { - var testArchives = new string[] { "Rar.multi.part01.rar", + public void Rar_Multi_Reader() { + DoRar_Multi_Reader(new string[] { + "Rar.multi.part01.rar", "Rar.multi.part02.rar", "Rar.multi.part03.rar", "Rar.multi.part04.rar", "Rar.multi.part05.rar", - "Rar.multi.part06.rar"}; + "Rar.multi.part06.rar"}); + } + [Fact] + public void Rar5_Multi_Reader() { + DoRar_Multi_Reader(new string[] { + "Rar5.multi.part01.rar", + "Rar5.multi.part02.rar", + "Rar5.multi.part03.rar", + "Rar5.multi.part04.rar", + "Rar5.multi.part05.rar", + "Rar5.multi.part06.rar"}); + } + private void DoRar_Multi_Reader(string[] archives) + { ResetScratch(); - using (var reader = RarReader.Open(testArchives.Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)) + using (var reader = RarReader.Open(archives.Select(s => Path.Combine(this.TEST_ARCHIVES_PATH, s)) .Select(p => File.OpenRead(p)))) { while (reader.MoveToNextEntry()) { - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() + reader.WriteEntryToDirectory(this.SCRATCH_FILES_PATH, new ExtractionOptions() { ExtractFullPath = true, Overwrite = true @@ -37,20 +50,33 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_Multi_Reader_Encrypted() + public void Rar_Multi_Reader_Encrypted() { + DoRar_Multi_Reader_Encrypted(new string[] { + "Rar.EncryptedParts.part01.rar", + "Rar.EncryptedParts.part02.rar", + "Rar.EncryptedParts.part03.rar", + "Rar.EncryptedParts.part04.rar", + "Rar.EncryptedParts.part05.rar", + "Rar.EncryptedParts.part06.rar"}); + } + + [Fact] + public void Rar5_Multi_Reader_Encrypted() { + DoRar_Multi_Reader_Encrypted(new string[] { + "Rar5.EncryptedParts.part01.rar", + "Rar5.EncryptedParts.part02.rar", + "Rar5.EncryptedParts.part03.rar", + "Rar5.EncryptedParts.part04.rar", + "Rar5.EncryptedParts.part05.rar", + "Rar5.EncryptedParts.part06.rar"}); + } + + private void DoRar_Multi_Reader_Encrypted(string[] archives) { - var testArchives = new string[] { "EncryptedParts.part01.rar", - "EncryptedParts.part02.rar", - "EncryptedParts.part03.rar", - "EncryptedParts.part04.rar", - "EncryptedParts.part05.rar", - "EncryptedParts.part06.rar"}; - - Assert.Throws(() => { ResetScratch(); - using (var reader = RarReader.Open(testArchives.Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)) + using (var reader = RarReader.Open(archives.Select(s => Path.Combine(this.TEST_ARCHIVES_PATH, s)) .Select(p => File.OpenRead(p)), new ReaderOptions() { @@ -59,7 +85,7 @@ namespace SharpCompress.Test.Rar { while (reader.MoveToNextEntry()) { - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, + reader.WriteEntryToDirectory(this.SCRATCH_FILES_PATH, new ExtractionOptions() { ExtractFullPath = true, @@ -72,28 +98,41 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_Multi_Reader_Delete_Files() - { - var testArchives = new string[] { "Rar.multi.part01.rar", + public void Rar_Multi_Reader_Delete_Files() { + DoRar_Multi_Reader_Delete_Files(new string[] { + "Rar.multi.part01.rar", "Rar.multi.part02.rar", "Rar.multi.part03.rar", "Rar.multi.part04.rar", "Rar.multi.part05.rar", - "Rar.multi.part06.rar"}; + "Rar.multi.part06.rar"}); + } + [Fact] + public void Rar5_Multi_Reader_Delete_Files() { + DoRar_Multi_Reader_Delete_Files(new string[] { + "Rar5.multi.part01.rar", + "Rar5.multi.part02.rar", + "Rar5.multi.part03.rar", + "Rar5.multi.part04.rar", + "Rar5.multi.part05.rar", + "Rar5.multi.part06.rar"}); + } + private void DoRar_Multi_Reader_Delete_Files(string[] archives) + { ResetScratch(); - foreach (var file in testArchives) + foreach (var file in archives) { - File.Copy(Path.Combine(TEST_ARCHIVES_PATH, file), Path.Combine(SCRATCH2_FILES_PATH, file)); + File.Copy(Path.Combine(this.TEST_ARCHIVES_PATH, file), Path.Combine(this.SCRATCH2_FILES_PATH, file)); } - var streams = testArchives.Select(s => Path.Combine(SCRATCH2_FILES_PATH, s)).Select(File.OpenRead).ToList(); + var streams = archives.Select(s => Path.Combine(this.SCRATCH2_FILES_PATH, s)).Select(File.OpenRead).ToList(); using (var reader = RarReader.Open(streams)) { while (reader.MoveToNextEntry()) { - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() + reader.WriteEntryToDirectory(this.SCRATCH_FILES_PATH, new ExtractionOptions() { ExtractFullPath = true, Overwrite = true @@ -106,7 +145,7 @@ namespace SharpCompress.Test.Rar } VerifyFiles(); - foreach (var file in testArchives.Select(s => Path.Combine(SCRATCH2_FILES_PATH, s))) + foreach (var file in archives.Select(s => Path.Combine(this.SCRATCH2_FILES_PATH, s))) { File.Delete(file); } @@ -118,30 +157,58 @@ namespace SharpCompress.Test.Rar Read("Rar.none.rar", CompressionType.Rar); } + [Fact] + public void Rar5_None_Reader() + { + Read("Rar5.none.rar", CompressionType.Rar); + } + [Fact] public void Rar_Reader() { Read("Rar.rar", CompressionType.Rar); } + [Fact] + public void Rar5_Reader() + { + Read("Rar5.rar", CompressionType.Rar); + } + [Fact] public void Rar_EncryptedFileAndHeader_Reader() { ReadRar("Rar.encrypted_filesAndHeader.rar", "test"); + } + [Fact] + public void Rar5_EncryptedFileAndHeader_Reader() + { + ReadRar("Rar5.encrypted_filesAndHeader.rar", "test"); } [Fact] public void Rar_EncryptedFileOnly_Reader() { ReadRar("Rar.encrypted_filesOnly.rar", "test"); + } + [Fact] + public void Rar5_EncryptedFileOnly_Reader() + { + ReadRar("Rar5.encrypted_filesOnly.rar", "test"); } [Fact] public void Rar_Encrypted_Reader() { - ReadRar("Encrypted.rar", "test"); + ReadRar("Rar.Encrypted.rar", "test"); + } + + [Fact] + public void Rar5_Encrypted_Reader() + { + ReadRar("Rar5.Encrypted.rar", "test"); } private void ReadRar(string testArchive, string password) @@ -170,10 +237,19 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_Entry_Stream() + public void Rar_Entry_Stream() { + DoRar_Entry_Stream("Rar.rar"); + } + + [Fact] + public void Rar5_Entry_Stream() { + DoRar_Entry_Stream("Rar5.rar"); + } + + private void DoRar_Entry_Stream(string filename) { ResetScratch(); - using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.rar"))) + using (Stream stream = File.OpenRead(Path.Combine(this.TEST_ARCHIVES_PATH, filename))) using (var reader = RarReader.Open(stream)) { while (reader.MoveToNextEntry()) @@ -185,7 +261,7 @@ namespace SharpCompress.Test.Rar { string file = Path.GetFileName(reader.Entry.Key); string folder = Path.GetDirectoryName(reader.Entry.Key); - string destdir = Path.Combine(SCRATCH_FILES_PATH, folder); + string destdir = Path.Combine(this.SCRATCH_FILES_PATH, folder); if (!Directory.Exists(destdir)) { Directory.CreateDirectory(destdir); @@ -207,7 +283,7 @@ namespace SharpCompress.Test.Rar public void Rar_Reader_Audio_program() { ResetScratch(); - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Audio_program.rar"))) + using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.Audio_program.rar"))) using (var reader = RarReader.Open(stream, new ReaderOptions() { LookForHeader = true @@ -231,7 +307,7 @@ namespace SharpCompress.Test.Rar public void Rar_Jpg_Reader() { ResetScratch(); - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rarjpeg.jpg"))) + using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg"))) using (var reader = RarReader.Open(stream, new ReaderOptions() { LookForHeader = true @@ -257,10 +333,25 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_Solid_Skip_Reader() + public void Rar5_Solid_Reader() + { + Read("Rar5.solid.rar", CompressionType.Rar); + } + + [Fact] + public void Rar_Solid_Skip_Reader() { + DoRar_Solid_Skip_Reader("Rar.solid.rar"); + } + + [Fact] + public void Rar5_Solid_Skip_Reader() { + DoRar_Solid_Skip_Reader("Rar5.solid.rar"); + } + + private void DoRar_Solid_Skip_Reader(string filename) { ResetScratch(); - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.solid.rar"))) + using (var stream = File.OpenRead(Path.Combine(this.TEST_ARCHIVES_PATH, filename))) using (var reader = RarReader.Open(stream, new ReaderOptions() { LookForHeader = true @@ -271,7 +362,7 @@ namespace SharpCompress.Test.Rar if (reader.Entry.Key.Contains("jpg")) { Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() + reader.WriteEntryToDirectory(this.SCRATCH_FILES_PATH, new ExtractionOptions() { ExtractFullPath = true, Overwrite = true @@ -282,10 +373,19 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_Reader_Skip() + public void Rar_Reader_Skip() { + DoRar_Reader_Skip("Rar.rar"); + } + + [Fact] + public void Rar5_Reader_Skip() { + DoRar_Reader_Skip("Rar5.rar"); + } + + private void DoRar_Reader_Skip(string filename) { ResetScratch(); - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.rar"))) + using (var stream = File.OpenRead(Path.Combine(this.TEST_ARCHIVES_PATH, filename))) using (var reader = RarReader.Open(stream, new ReaderOptions() { LookForHeader = true @@ -296,7 +396,7 @@ namespace SharpCompress.Test.Rar if (reader.Entry.Key.Contains("jpg")) { Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() + reader.WriteEntryToDirectory(this.SCRATCH_FILES_PATH, new ExtractionOptions() { ExtractFullPath = true, Overwrite = true diff --git a/tests/TestArchives/Archives/Audio_program.rar b/tests/TestArchives/Archives/Rar.Audio_program.rar similarity index 100% rename from tests/TestArchives/Archives/Audio_program.rar rename to tests/TestArchives/Archives/Rar.Audio_program.rar diff --git a/tests/TestArchives/Archives/Encrypted.rar b/tests/TestArchives/Archives/Rar.Encrypted.rar similarity index 100% rename from tests/TestArchives/Archives/Encrypted.rar rename to tests/TestArchives/Archives/Rar.Encrypted.rar diff --git a/tests/TestArchives/Archives/EncryptedParts.part01.rar b/tests/TestArchives/Archives/Rar.EncryptedParts.part01.rar similarity index 100% rename from tests/TestArchives/Archives/EncryptedParts.part01.rar rename to tests/TestArchives/Archives/Rar.EncryptedParts.part01.rar diff --git a/tests/TestArchives/Archives/EncryptedParts.part02.rar b/tests/TestArchives/Archives/Rar.EncryptedParts.part02.rar similarity index 100% rename from tests/TestArchives/Archives/EncryptedParts.part02.rar rename to tests/TestArchives/Archives/Rar.EncryptedParts.part02.rar diff --git a/tests/TestArchives/Archives/EncryptedParts.part03.rar b/tests/TestArchives/Archives/Rar.EncryptedParts.part03.rar similarity index 100% rename from tests/TestArchives/Archives/EncryptedParts.part03.rar rename to tests/TestArchives/Archives/Rar.EncryptedParts.part03.rar diff --git a/tests/TestArchives/Archives/EncryptedParts.part04.rar b/tests/TestArchives/Archives/Rar.EncryptedParts.part04.rar similarity index 100% rename from tests/TestArchives/Archives/EncryptedParts.part04.rar rename to tests/TestArchives/Archives/Rar.EncryptedParts.part04.rar diff --git a/tests/TestArchives/Archives/EncryptedParts.part05.rar b/tests/TestArchives/Archives/Rar.EncryptedParts.part05.rar similarity index 100% rename from tests/TestArchives/Archives/EncryptedParts.part05.rar rename to tests/TestArchives/Archives/Rar.EncryptedParts.part05.rar diff --git a/tests/TestArchives/Archives/EncryptedParts.part06.rar b/tests/TestArchives/Archives/Rar.EncryptedParts.part06.rar similarity index 100% rename from tests/TestArchives/Archives/EncryptedParts.part06.rar rename to tests/TestArchives/Archives/Rar.EncryptedParts.part06.rar diff --git a/tests/TestArchives/Archives/Rarjpeg.jpg b/tests/TestArchives/Archives/Rar.jpeg.jpg similarity index 100% rename from tests/TestArchives/Archives/Rarjpeg.jpg rename to tests/TestArchives/Archives/Rar.jpeg.jpg diff --git a/tests/TestArchives/Archives/test_invalid_exttime.rar b/tests/TestArchives/Archives/Rar.test_invalid_exttime.rar similarity index 100% rename from tests/TestArchives/Archives/test_invalid_exttime.rar rename to tests/TestArchives/Archives/Rar.test_invalid_exttime.rar From 85631795921de9b8aa3729866062cbc46abfa87b Mon Sep 17 00:00:00 2001 From: coderb Date: Sun, 17 Dec 2017 08:33:46 -0500 Subject: [PATCH 07/61] rar5: wip --- .../Common/Rar/Headers/AVHeader.cs | 26 ++++++++----- .../Common/Rar/Headers/CommentHeader.cs | 5 +++ src/SharpCompress/Common/Rar/Headers/Flags.cs | 14 +++---- .../{NewSubHeader.cs => NewSubHeaderType.cs} | 0 .../Common/Rar/Headers/ProtectHeader.cs | 5 +++ .../Common/Rar/Headers/RarHeader.cs | 24 ------------ .../Common/Rar/Headers/RarHeaderFactory.cs | 3 +- .../Common/Rar/Headers/SignHeader.cs | 24 ++++++------ src/SharpCompress/Compressors/Rar/Unpack.cs | 38 +++++++++++++++++++ 9 files changed, 84 insertions(+), 55 deletions(-) rename src/SharpCompress/Common/Rar/Headers/{NewSubHeader.cs => NewSubHeaderType.cs} (100%) diff --git a/src/SharpCompress/Common/Rar/Headers/AVHeader.cs b/src/SharpCompress/Common/Rar/Headers/AVHeader.cs index acb878ba..ce18fc42 100644 --- a/src/SharpCompress/Common/Rar/Headers/AVHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/AVHeader.cs @@ -2,19 +2,27 @@ namespace SharpCompress.Common.Rar.Headers { - internal class SignHeader : RarHeader + internal class AVHeader : RarHeader { - protected override void ReadFromReader(MarkingBinaryReader reader) - { - CreationTime = reader.ReadInt32(); - ArcNameSize = reader.ReadInt16(); - UserNameSize = reader.ReadInt16(); + public AVHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader, HeaderType.Av) { + if (IsRar5) throw new InvalidFormatException("unexpected rar5 record"); } - internal int CreationTime { get; private set; } + protected override void ReadFromReader(MarkingBinaryReader reader) + { + UnpackVersion = reader.ReadByte(); + Method = reader.ReadByte(); + AVVersion = reader.ReadByte(); + AVInfoCRC = reader.ReadInt32(); + } - internal short ArcNameSize { get; private set; } + internal int AVInfoCRC { get; private set; } - internal short UserNameSize { get; private set; } + internal byte UnpackVersion { get; private set; } + + internal byte Method { get; private set; } + + internal byte AVVersion { get; private set; } } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/CommentHeader.cs b/src/SharpCompress/Common/Rar/Headers/CommentHeader.cs index c9943bde..c9d71b0a 100644 --- a/src/SharpCompress/Common/Rar/Headers/CommentHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/CommentHeader.cs @@ -4,6 +4,11 @@ namespace SharpCompress.Common.Rar.Headers { internal class CommentHeader : RarHeader { + protected CommentHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader, HeaderType.Comment) { + if (IsRar5) throw new InvalidFormatException("unexpected rar5 record"); + } + protected override void ReadFromReader(MarkingBinaryReader reader) { UnpSize = reader.ReadInt16(); diff --git a/src/SharpCompress/Common/Rar/Headers/Flags.cs b/src/SharpCompress/Common/Rar/Headers/Flags.cs index 0bbe87aa..6f072396 100644 --- a/src/SharpCompress/Common/Rar/Headers/Flags.cs +++ b/src/SharpCompress/Common/Rar/Headers/Flags.cs @@ -1,5 +1,3 @@ -using System; - namespace SharpCompress.Common.Rar.Headers { internal enum HeaderType : byte @@ -8,14 +6,12 @@ namespace SharpCompress.Common.Rar.Headers Mark, Archive, File, - NewSub, Service, -// Comment, -// Sub -// Av, -// Protect, -// Sign, -// NewSub, + Comment, + Av, + Protect, + Sign, + NewSub, EndArchive } diff --git a/src/SharpCompress/Common/Rar/Headers/NewSubHeader.cs b/src/SharpCompress/Common/Rar/Headers/NewSubHeaderType.cs similarity index 100% rename from src/SharpCompress/Common/Rar/Headers/NewSubHeader.cs rename to src/SharpCompress/Common/Rar/Headers/NewSubHeaderType.cs diff --git a/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs b/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs index 75dbcb9e..19529388 100644 --- a/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs @@ -5,6 +5,11 @@ namespace SharpCompress.Common.Rar.Headers // ProtectHeader is part of the Recovery Record feature internal class ProtectHeader : RarHeader { + public ProtectHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader, HeaderType.Protect) { + if (IsRar5) throw new InvalidFormatException("unexpected rar5 record"); + } + protected override void ReadFromReader(MarkingBinaryReader reader) { Version = reader.ReadByte(); diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs index e81f82f2..ca5ca38f 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs @@ -9,11 +9,6 @@ namespace SharpCompress.Common.Rar.Headers private readonly HeaderType headerType; private readonly bool isRar5; - protected RarHeader() { - //TODO - throw new NotImplementedException(); - } - private RarHeader(bool isRar5) { this.headerType = HeaderType.Null; @@ -100,25 +95,6 @@ namespace SharpCompress.Common.Rar.Headers { throw new NotImplementedException(); } -//x - internal T PromoteHeader(RarCrcBinaryReader reader) - where T : RarHeader, new() - { - T header = new T(); - header.FillBase(this); - header.ReadFromReader(reader); - header.ReadBytes = reader.CurrentReadByteCount; - - int headerSizeDiff = header.HeaderSize - (int)header.ReadBytes; - if (headerSizeDiff > 0) - { - reader.ReadBytes(headerSizeDiff); - } - - VerifyHeaderCrc(reader.GetCrc32()); - - return header; - } private void VerifyHeaderCrc(uint crc32) { diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs index 9db50d8e..3dbb9f21 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs @@ -75,8 +75,7 @@ namespace SharpCompress.Common.Rar.Headers case HeaderCodeV.ProtectHeader: { - ProtectHeader ph = header.PromoteHeader(reader); - + var ph = new ProtectHeader(header, reader); // skip the recovery record data, we do not use it. switch (StreamingMode) { diff --git a/src/SharpCompress/Common/Rar/Headers/SignHeader.cs b/src/SharpCompress/Common/Rar/Headers/SignHeader.cs index 708241d8..2f945878 100644 --- a/src/SharpCompress/Common/Rar/Headers/SignHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/SignHeader.cs @@ -2,22 +2,24 @@ namespace SharpCompress.Common.Rar.Headers { - internal class AVHeader : RarHeader + internal class SignHeader : RarHeader { - protected override void ReadFromReader(MarkingBinaryReader reader) - { - UnpackVersion = reader.ReadByte(); - Method = reader.ReadByte(); - AVVersion = reader.ReadByte(); - AVInfoCRC = reader.ReadInt32(); + protected SignHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader, HeaderType.Sign) { + if (IsRar5) throw new InvalidFormatException("unexpected rar5 record"); } - internal int AVInfoCRC { get; private set; } + protected override void ReadFromReader(MarkingBinaryReader reader) + { + CreationTime = reader.ReadInt32(); + ArcNameSize = reader.ReadInt16(); + UserNameSize = reader.ReadInt16(); + } - internal byte UnpackVersion { get; private set; } + internal int CreationTime { get; private set; } - internal byte Method { get; private set; } + internal short ArcNameSize { get; private set; } - internal byte AVVersion { get; private set; } + internal short UserNameSize { get; private set; } } } \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/Unpack.cs b/src/SharpCompress/Compressors/Rar/Unpack.cs index 78450eeb..40d3087c 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/Unpack.cs @@ -150,6 +150,44 @@ namespace SharpCompress.Compressors.Rar var solid = fileHeader.IsSolid; switch (fileHeader.R4RarVersion) { + case 0: // no compression + unstoreFile(); + break; + + case 15: // rar 1.5 compression + unpack15(solid); + break; + + case 20: // rar 2.x compression + case 26: // files larger than 2GB + unpack20(solid); + break; + + case 29: // rar 3.x compression + case 36: // alternative hash + unpack29(solid); + break; + + default: throw new InvalidFormatException("unknown rar compression version " + this.fileHeader.R4RarVersion); + } + } + + public void doUnpackNEW() + { + var compressionMethod = fileHeader.R4RarVersion; + //??? if (fileHeader.R4PackingMethod == 0x30) +// { +// unstoreFile(); +// return; +// } + var solid = fileHeader.IsSolid; + //switch (fileHeader.R4RarVersion) + switch (compressionMethod) + { + case 0: // no compression + unstoreFile(); + break; + case 15: // rar 1.5 compression unpack15(solid); break; From c9d3563f31c473af8b7adc0af278f5c49b44ba95 Mon Sep 17 00:00:00 2001 From: coderb Date: Sun, 17 Dec 2017 09:00:11 -0500 Subject: [PATCH 08/61] rar5: some cleanup --- .../Common/Rar/Headers/AVHeader.cs | 5 ++- .../Common/Rar/Headers/ArchiveHeader.cs | 32 +++++++------- .../Common/Rar/Headers/CommentHeader.cs | 5 ++- .../Common/Rar/Headers/EndArchiveHeader.cs | 21 ++++----- .../Common/Rar/Headers/FileHeader.cs | 43 ++++++++++--------- .../Common/Rar/Headers/IRarHeader.cs | 3 +- .../Common/Rar/Headers/NewSubHeaderType.cs | 4 -- .../Common/Rar/Headers/ProtectHeader.cs | 5 ++- .../Common/Rar/Headers/RarHeader.cs | 40 +++++++---------- .../Common/Rar/Headers/RarHeaderFactory.cs | 14 +++--- .../Common/Rar/Headers/SignHeader.cs | 5 ++- .../Common/Rar/RarCrcBinaryReader.cs | 3 +- .../Common/Rar/RarCryptoBinaryReader.cs | 21 --------- src/SharpCompress/Common/Rar/RarVolume.cs | 1 - 14 files changed, 90 insertions(+), 112 deletions(-) diff --git a/src/SharpCompress/Common/Rar/Headers/AVHeader.cs b/src/SharpCompress/Common/Rar/Headers/AVHeader.cs index ce18fc42..73c1f3bd 100644 --- a/src/SharpCompress/Common/Rar/Headers/AVHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/AVHeader.cs @@ -5,11 +5,12 @@ namespace SharpCompress.Common.Rar.Headers internal class AVHeader : RarHeader { public AVHeader(RarHeader header, RarCrcBinaryReader reader) - : base(header, reader, HeaderType.Av) { + : base(header, reader, HeaderType.Av) + { if (IsRar5) throw new InvalidFormatException("unexpected rar5 record"); } - protected override void ReadFromReader(MarkingBinaryReader reader) + protected override void ReadFinish(MarkingBinaryReader reader) { UnpackVersion = reader.ReadByte(); Method = reader.ReadByte(); diff --git a/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs b/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs index d9630296..083a0bf2 100644 --- a/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs @@ -5,15 +5,16 @@ namespace SharpCompress.Common.Rar.Headers internal class ArchiveHeader : RarHeader { public ArchiveHeader(RarHeader header, RarCrcBinaryReader reader) - : base(header, reader, HeaderType.Archive) { + : base(header, reader, HeaderType.Archive) + { } - protected override void ReadFromReader(MarkingBinaryReader reader) + protected override void ReadFinish(MarkingBinaryReader reader) { - if (this.IsRar5) + if (IsRar5) { - ArchiveHeaderFlags = reader.ReadRarVIntUInt16(); - if (HasArchiveHeaderFlag(ArchiveFlagsV5.HasVolumeNumber)) + Flags = reader.ReadRarVIntUInt16(); + if (HasFlag(ArchiveFlagsV5.HasVolumeNumber)) { VolumeNumber = (int)reader.ReadRarVIntUInt32(); } @@ -23,10 +24,10 @@ namespace SharpCompress.Common.Rar.Headers } else { - ArchiveHeaderFlags = HeaderFlags; + Flags = HeaderFlags; HighPosAv = reader.ReadInt16(); PosAv = reader.ReadInt32(); - if (HasArchiveHeaderFlag(ArchiveFlagsV4.EncryptVer)) + if (HasFlag(ArchiveFlagsV4.EncryptVer)) { EncryptionVersion = reader.ReadByte(); } @@ -34,6 +35,7 @@ namespace SharpCompress.Common.Rar.Headers } private void ReadLocator(MarkingBinaryReader reader) { + // parse fields, unused var size = reader.ReadRarVIntUInt16(); var type = reader.ReadRarVIntUInt16(); if (type != 1) throw new InvalidFormatException("expected locator record"); @@ -50,11 +52,11 @@ namespace SharpCompress.Common.Rar.Headers } } - private ushort ArchiveHeaderFlags { get; set; } + private ushort Flags { get; set; } - private bool HasArchiveHeaderFlag(ushort flag) + private bool HasFlag(ushort flag) { - return (ArchiveHeaderFlags & flag) == flag; + return (Flags & flag) == flag; } internal int? VolumeNumber { get; private set; } @@ -65,14 +67,14 @@ namespace SharpCompress.Common.Rar.Headers private byte? EncryptionVersion { get; set; } - public bool? IsEncrypted => this.IsRar5 ? (bool?)null : HasArchiveHeaderFlag(ArchiveFlagsV4.Password); + public bool? IsEncrypted => IsRar5 ? (bool?)null : HasFlag(ArchiveFlagsV4.Password); - public bool OldNumberingFormat => !this.IsRar5 && !HasArchiveHeaderFlag(ArchiveFlagsV4.NewNumbering); + public bool OldNumberingFormat => !IsRar5 && !HasFlag(ArchiveFlagsV4.NewNumbering); - public bool IsVolume => HasArchiveHeaderFlag(this.IsRar5 ? ArchiveFlagsV5.Volume : ArchiveFlagsV4.Volume); + public bool IsVolume => HasFlag(IsRar5 ? ArchiveFlagsV5.Volume : ArchiveFlagsV4.Volume); - public bool IsFirstVolume => this.IsRar5 ? VolumeNumber == 1 : HasArchiveHeaderFlag(ArchiveFlagsV4.FirstVolume); + public bool IsFirstVolume => IsRar5 ? VolumeNumber == 1 : HasFlag(ArchiveFlagsV4.FirstVolume); - public bool IsSolid => HasArchiveHeaderFlag(this.IsRar5 ? ArchiveFlagsV5.Solid : ArchiveFlagsV4.Solid); + public bool IsSolid => HasFlag(IsRar5 ? ArchiveFlagsV5.Solid : ArchiveFlagsV4.Solid); } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/CommentHeader.cs b/src/SharpCompress/Common/Rar/Headers/CommentHeader.cs index c9d71b0a..d15a1b8e 100644 --- a/src/SharpCompress/Common/Rar/Headers/CommentHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/CommentHeader.cs @@ -5,11 +5,12 @@ namespace SharpCompress.Common.Rar.Headers internal class CommentHeader : RarHeader { protected CommentHeader(RarHeader header, RarCrcBinaryReader reader) - : base(header, reader, HeaderType.Comment) { + : base(header, reader, HeaderType.Comment) + { if (IsRar5) throw new InvalidFormatException("unexpected rar5 record"); } - protected override void ReadFromReader(MarkingBinaryReader reader) + protected override void ReadFinish(MarkingBinaryReader reader) { UnpSize = reader.ReadInt16(); UnpVersion = reader.ReadByte(); diff --git a/src/SharpCompress/Common/Rar/Headers/EndArchiveHeader.cs b/src/SharpCompress/Common/Rar/Headers/EndArchiveHeader.cs index caee1a83..07d1d95e 100644 --- a/src/SharpCompress/Common/Rar/Headers/EndArchiveHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/EndArchiveHeader.cs @@ -5,34 +5,35 @@ namespace SharpCompress.Common.Rar.Headers internal class EndArchiveHeader : RarHeader { public EndArchiveHeader(RarHeader header, RarCrcBinaryReader reader) - : base(header, reader, HeaderType.EndArchive) { + : base(header, reader, HeaderType.EndArchive) + { } - protected override void ReadFromReader(MarkingBinaryReader reader) + protected override void ReadFinish(MarkingBinaryReader reader) { - if (this.IsRar5) + if (IsRar5) { - EndArchiveFlags = reader.ReadRarVIntUInt16(); + Flags = reader.ReadRarVIntUInt16(); } else { - EndArchiveFlags = HeaderFlags; - if (HasEndArchiveFlag(EndArchiveFlagsV4.DataCrc)) + Flags = HeaderFlags; + if (HasFlag(EndArchiveFlagsV4.DataCrc)) { ArchiveCrc = reader.ReadInt32(); } - if (HasEndArchiveFlag(EndArchiveFlagsV4.VolumeNumber)) + if (HasFlag(EndArchiveFlagsV4.VolumeNumber)) { VolumeNumber = reader.ReadInt16(); } } } - private ushort EndArchiveFlags { get; set; } + private ushort Flags { get; set; } - private bool HasEndArchiveFlag(ushort flag) + private bool HasFlag(ushort flag) { - return (EndArchiveFlags & flag) == flag; + return (Flags & flag) == flag; } internal int? ArchiveCrc { get; private set; } diff --git a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs index 27fffe3b..49c8bb03 100644 --- a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs @@ -10,12 +10,13 @@ namespace SharpCompress.Common.Rar.Headers private uint fileCrc; public FileHeader(RarHeader header, RarCrcBinaryReader reader, HeaderType headerType) - : base(header, reader, headerType) { + : base(header, reader, headerType) + { } - protected override void ReadFromReader(MarkingBinaryReader reader) + protected override void ReadFinish(MarkingBinaryReader reader) { - if (this.IsRar5) + if (IsRar5) { ReadFromReaderV5(reader); } @@ -27,19 +28,19 @@ namespace SharpCompress.Common.Rar.Headers private void ReadFromReaderV5(MarkingBinaryReader reader) { - FileFlags = reader.ReadRarVIntUInt16(); + Flags = reader.ReadRarVIntUInt16(); var lvalue = checked((long)reader.ReadRarVInt()); //!!! rar5 TODO: this may cause problems with user code, Unpack20 seems to use this field to decompress - UncompressedSize = HasFileFlag(FileFlagsV5.UnpackedSizeUnknown) ? long.MaxValue : lvalue; + UncompressedSize = HasFlag(FileFlagsV5.UnpackedSizeUnknown) ? long.MaxValue : lvalue; FileAttributes = reader.ReadRarVIntUInt32(); - if (HasFileFlag(FileFlagsV5.HasModTime)) { + if (HasFlag(FileFlagsV5.HasModTime)) { FileLastModifiedTime = Utility.UnixTimeToDateTime(reader.ReadUInt32()); } - if (HasFileFlag(FileFlagsV5.HasCrc32)) { + if (HasFlag(FileFlagsV5.HasCrc32)) { FileCrc = reader.ReadUInt32(); } @@ -178,7 +179,7 @@ namespace SharpCompress.Common.Rar.Headers private void ReadFromReaderV4(MarkingBinaryReader reader) { - FileFlags = HeaderFlags; + Flags = HeaderFlags; uint lowUncompressedSize = reader.ReadUInt32(); @@ -197,7 +198,7 @@ namespace SharpCompress.Common.Rar.Headers uint highCompressedSize = 0; uint highUncompressedkSize = 0; - if (HasFileFlag(FileFlagsV4.Large)) + if (HasFlag(FileFlagsV4.Large)) { highCompressedSize = reader.ReadUInt32(); highUncompressedkSize = reader.ReadUInt32(); @@ -224,7 +225,7 @@ namespace SharpCompress.Common.Rar.Headers { case HeaderCodeV.FileHeader: { - if (HasFileFlag(FileFlagsV4.Unicode)) + if (HasFlag(FileFlagsV4.Unicode)) { int length = 0; while (length < fileNameBytes.Length @@ -252,7 +253,7 @@ namespace SharpCompress.Common.Rar.Headers case HeaderCodeV.NewSubHeader: { int datasize = HeaderSize - newLhdSize - nameSize; - if (HasFileFlag(FileFlagsV4.Salt)) + if (HasFlag(FileFlagsV4.Salt)) { datasize -= saltSize; } @@ -270,11 +271,11 @@ namespace SharpCompress.Common.Rar.Headers break; } - if (HasFileFlag(FileFlagsV4.Salt)) + if (HasFlag(FileFlagsV4.Salt)) { R4Salt = reader.ReadBytes(saltSize); } - if (HasFileFlag(FileFlagsV4.ExtTime)) + if (HasFlag(FileFlagsV4.ExtTime)) { // verify that the end of the header hasn't been reached before reading the Extended Time. // some tools incorrectly omit Extended Time despite specifying FileFlags.EXTTIME, which most parsers tolerate. @@ -346,17 +347,17 @@ namespace SharpCompress.Common.Rar.Headers return FileName; } - private ushort FileFlags { get; set; } + private ushort Flags { get; set; } - private bool HasFileFlag(ushort flag) + private bool HasFlag(ushort flag) { - return (FileFlags & flag) == flag; + return (Flags & flag) == flag; } internal uint FileCrc { get { - if (this.IsRar5 && !HasFileFlag(FileFlagsV5.HasCrc32)) { + if (IsRar5 && !HasFlag(FileFlagsV5.HasCrc32)) { //!!! rar5: throw new InvalidOperationException("TODO rar5"); } @@ -386,15 +387,15 @@ namespace SharpCompress.Common.Rar.Headers public Stream PackedStream { get; set; } //!!! TODO rar5 - public bool IsSplit => HasFileFlag(FileFlagsV4.SplitAfter); + public bool IsSplit => HasFlag(FileFlagsV4.SplitAfter); - public bool IsDirectory => HasFileFlag(this.IsRar5 ? FileFlagsV5.Directory : FileFlagsV4.Directory); + public bool IsDirectory => HasFlag(IsRar5 ? FileFlagsV5.Directory : FileFlagsV4.Directory); //!!! TODO rar5 - public bool IsEncrypted => HasFileFlag(FileFlagsV4.Password); + public bool IsEncrypted => HasFlag(FileFlagsV4.Password); //!!! TODO rar5; R5IsSolid - public bool IsSolid => HasFileFlag(FileFlagsV4.Solid); + public bool IsSolid => HasFlag(FileFlagsV4.Solid); internal DateTime? FileLastModifiedTime { get; private set; } diff --git a/src/SharpCompress/Common/Rar/Headers/IRarHeader.cs b/src/SharpCompress/Common/Rar/Headers/IRarHeader.cs index 93f9b0ca..bbc03593 100644 --- a/src/SharpCompress/Common/Rar/Headers/IRarHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/IRarHeader.cs @@ -1,6 +1,7 @@ namespace SharpCompress.Common.Rar.Headers { - internal interface IRarHeader { + internal interface IRarHeader + { HeaderType HeaderType { get; } } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/NewSubHeaderType.cs b/src/SharpCompress/Common/Rar/Headers/NewSubHeaderType.cs index 6218f123..d4b2a77b 100644 --- a/src/SharpCompress/Common/Rar/Headers/NewSubHeaderType.cs +++ b/src/SharpCompress/Common/Rar/Headers/NewSubHeaderType.cs @@ -47,13 +47,9 @@ namespace SharpCompress.Common.Rar.Headers return true; } - #region IEquatable Members - public bool Equals(NewSubHeaderType other) { return Equals(other.bytes); } - - #endregion } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs b/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs index 19529388..f7f0e8ba 100644 --- a/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs @@ -6,11 +6,12 @@ namespace SharpCompress.Common.Rar.Headers internal class ProtectHeader : RarHeader { public ProtectHeader(RarHeader header, RarCrcBinaryReader reader) - : base(header, reader, HeaderType.Protect) { + : base(header, reader, HeaderType.Protect) + { if (IsRar5) throw new InvalidFormatException("unexpected rar5 record"); } - protected override void ReadFromReader(MarkingBinaryReader reader) + protected override void ReadFinish(MarkingBinaryReader reader) { Version = reader.ReadByte(); RecSectors = reader.ReadUInt16(); diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs index ca5ca38f..7feb72ea 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs @@ -18,8 +18,14 @@ namespace SharpCompress.Common.Rar.Headers protected RarHeader(RarHeader header, RarCrcBinaryReader reader, HeaderType headerType) { this.headerType = headerType; this.isRar5 = header.IsRar5; - FillBase(header); - ReadFromReader(reader); + HeaderCrc = header.HeaderCrc; + HeaderCode = header.HeaderCode; + HeaderFlags = header.HeaderFlags; + HeaderSize = header.HeaderSize; + ExtraSize = header.ExtraSize; + AdditionalDataSize = header.AdditionalDataSize; + ArchiveEncoding = header.ArchiveEncoding; + ReadFinish(reader); ReadBytes = reader.CurrentReadByteCount; int headerSizeDiff = HeaderSize - (int)ReadBytes; @@ -31,25 +37,18 @@ namespace SharpCompress.Common.Rar.Headers VerifyHeaderCrc(reader.GetCrc32()); } - private void FillBase(RarHeader other) + protected virtual void ReadFinish(MarkingBinaryReader reader) { - HeaderCrc = other.HeaderCrc; - HeaderCode = other.HeaderCode; - HeaderFlags = other.HeaderFlags; - HeaderSize = other.HeaderSize; - ExtraSize = other.ExtraSize; - AdditionalDataSize = other.AdditionalDataSize; - ReadBytes = other.ReadBytes; - ArchiveEncoding = other.ArchiveEncoding; + throw new NotImplementedException(); } - internal static RarHeader ReadBase(RarCrcBinaryReader reader, ArchiveEncoding archiveEncoding, bool isRar5) + internal static RarHeader TryReadBase(RarCrcBinaryReader reader, ArchiveEncoding archiveEncoding, bool isRar5) { try { var header = new RarHeader(isRar5); header.ArchiveEncoding = archiveEncoding; - header.ReadStartFromReader(reader); + header.ReadStart(reader); header.ReadBytes += reader.CurrentReadByteCount; return header; } @@ -59,9 +58,9 @@ namespace SharpCompress.Common.Rar.Headers } } - private void ReadStartFromReader(RarCrcBinaryReader reader) + private void ReadStart(RarCrcBinaryReader reader) { - if (this.IsRar5) + if (IsRar5) { HeaderCrc = reader.ReadUInt32(); reader.ResetCrc(); @@ -91,24 +90,15 @@ namespace SharpCompress.Common.Rar.Headers } } - protected virtual void ReadFromReader(MarkingBinaryReader reader) - { - throw new NotImplementedException(); - } - private void VerifyHeaderCrc(uint crc32) { - var b = (this.IsRar5 ? crc32 : (ushort)crc32) == HeaderCrc; + var b = (IsRar5 ? crc32 : (ushort)crc32) == HeaderCrc; if (!b) { throw new InvalidFormatException("rar header crc mismatch"); } } - protected virtual void PostReadingBytes(MarkingBinaryReader reader) - { - } - public HeaderType HeaderType { get { return this.headerType; } } protected bool IsRar5 { get { return this.isRar5; } } diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs index 3dbb9f21..0a833fcf 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs @@ -32,7 +32,8 @@ namespace SharpCompress.Common.Rar.Headers yield return header; if (header.HeaderType == HeaderType.EndArchive) { - // End of archive marker. RAR does not read anything after this header letting to use third party tools to add extra information such as a digital signature to archive. + // End of archive marker. RAR does not read anything after this header letting to use third + // party tools to add extra information such as a digital signature to archive. yield break; } } @@ -41,9 +42,12 @@ namespace SharpCompress.Common.Rar.Headers private RarHeader ReadNextHeader(Stream stream) { RarCrcBinaryReader reader; - if (!IsEncrypted) { + if (!IsEncrypted) + { reader = new RarCrcBinaryReader(stream); - } else { + } + else + { #if !NO_CRYPTO if (Options.Password == null) { @@ -55,7 +59,7 @@ namespace SharpCompress.Common.Rar.Headers #endif } - var header = RarHeader.ReadBase(reader, Options.ArchiveEncoding, this.isRar5); + var header = RarHeader.TryReadBase(reader, Options.ArchiveEncoding, this.isRar5); if (header == null) { return null; @@ -155,7 +159,7 @@ namespace SharpCompress.Common.Rar.Headers } default: { - throw new InvalidFormatException("Invalid Rar Header: " + header.HeaderCode); + throw new InvalidFormatException("Unknown Rar Header: " + header.HeaderCode); } } } diff --git a/src/SharpCompress/Common/Rar/Headers/SignHeader.cs b/src/SharpCompress/Common/Rar/Headers/SignHeader.cs index 2f945878..58b3baef 100644 --- a/src/SharpCompress/Common/Rar/Headers/SignHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/SignHeader.cs @@ -5,11 +5,12 @@ namespace SharpCompress.Common.Rar.Headers internal class SignHeader : RarHeader { protected SignHeader(RarHeader header, RarCrcBinaryReader reader) - : base(header, reader, HeaderType.Sign) { + : base(header, reader, HeaderType.Sign) + { if (IsRar5) throw new InvalidFormatException("unexpected rar5 record"); } - protected override void ReadFromReader(MarkingBinaryReader reader) + protected override void ReadFinish(MarkingBinaryReader reader) { CreationTime = reader.ReadInt32(); ArcNameSize = reader.ReadInt16(); diff --git a/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs b/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs index 3251552b..6ab2923a 100644 --- a/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs +++ b/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs @@ -3,7 +3,8 @@ using SharpCompress.Compressors.Rar; using SharpCompress.IO; namespace SharpCompress.Common.Rar { - internal class RarCrcBinaryReader : MarkingBinaryReader { + internal class RarCrcBinaryReader : MarkingBinaryReader + { private uint currentCrc; public RarCrcBinaryReader(Stream stream) : base(stream) diff --git a/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs b/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs index 4a14a375..9ea3cb2c 100644 --- a/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs +++ b/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs @@ -58,27 +58,6 @@ namespace SharpCompress.Common.Rar this.readCount++; return base.ReadByte(); } -//x -// public override byte ReadByte() { -// if (UseEncryption) -// { -// int queueSize = this.data.Count; -// if (queueSize < 1) -// { -// byte[] cipherText = ReadBytesNoCrc(16); -// var readBytes = this.rijndael.ProcessBlock(cipherText); -// foreach (var readByte in readBytes) -// this.data.Enqueue(readByte); -// } -// -// var b = this.data.Dequeue(); -// UpdateCrc(b); -// this.readCount++; -// return b; -// } -// this.readCount++; -// return base.ReadByte(); -// } public override byte[] ReadBytes(int count) { diff --git a/src/SharpCompress/Common/Rar/RarVolume.cs b/src/SharpCompress/Common/Rar/RarVolume.cs index 17be5a30..201ced3b 100644 --- a/src/SharpCompress/Common/Rar/RarVolume.cs +++ b/src/SharpCompress/Common/Rar/RarVolume.cs @@ -81,7 +81,6 @@ namespace SharpCompress.Common.Rar get { EnsureArchiveHeaderLoaded(); - //return (ArchiveHeader.ArchiveHeaderFlags & ArchiveFlagsV4.FirstVolume) == ArchiveFlagsV4.FirstVolume; return ArchiveHeader.IsFirstVolume; } } From f0eac57bb96a244461e0f09532b2749a0cd27f14 Mon Sep 17 00:00:00 2001 From: coderb Date: Sun, 17 Dec 2017 09:14:08 -0500 Subject: [PATCH 09/61] rar5: more cleanup --- .../Common/Rar/Headers/RarHeader.cs | 113 +++++++++--------- .../Common/Rar/Headers/RarHeaderFactory.cs | 2 +- 2 files changed, 57 insertions(+), 58 deletions(-) diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs index 7feb72ea..5f15914f 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs @@ -9,10 +9,52 @@ namespace SharpCompress.Common.Rar.Headers private readonly HeaderType headerType; private readonly bool isRar5; - private RarHeader(bool isRar5) + internal static RarHeader TryReadBase(RarCrcBinaryReader reader, bool isRar5, ArchiveEncoding archiveEncoding) + { + try + { + return new RarHeader(reader, isRar5, archiveEncoding); + } + catch (EndOfStreamException) + { + return null; + } + } + + private RarHeader(RarCrcBinaryReader reader, bool isRar5, ArchiveEncoding archiveEncoding) { this.headerType = HeaderType.Null; this.isRar5 = isRar5; + ArchiveEncoding = archiveEncoding; + if (IsRar5) + { + HeaderCrc = reader.ReadUInt32(); + reader.ResetCrc(); + HeaderSize = (int)reader.ReadRarVIntUInt32(3); + reader.Mark(); + HeaderCode = reader.ReadRarVIntByte(); + HeaderFlags = reader.ReadRarVIntUInt16(2); + if (HasHeaderFlag(HeaderFlagsV5.HasExtra)) + { + ExtraSize = reader.ReadRarVIntUInt32(); + } + if (HasHeaderFlag(HeaderFlagsV5.HasData)) + { + AdditionalDataSize = (long)reader.ReadRarVInt(); + } + } else { + reader.Mark(); + HeaderCrc = reader.ReadUInt16(); + reader.ResetCrc(); + HeaderCode = reader.ReadByte(); + HeaderFlags = reader.ReadUInt16(); + HeaderSize = reader.ReadInt16(); + if (HasHeaderFlag(HeaderFlagsV4.HasData)) + { + AdditionalDataSize = reader.ReadUInt32(); + } + } + ReadBytes = reader.CurrentReadByteCount; } protected RarHeader(RarHeader header, RarCrcBinaryReader reader, HeaderType headerType) { @@ -42,54 +84,6 @@ namespace SharpCompress.Common.Rar.Headers throw new NotImplementedException(); } - internal static RarHeader TryReadBase(RarCrcBinaryReader reader, ArchiveEncoding archiveEncoding, bool isRar5) - { - try - { - var header = new RarHeader(isRar5); - header.ArchiveEncoding = archiveEncoding; - header.ReadStart(reader); - header.ReadBytes += reader.CurrentReadByteCount; - return header; - } - catch (EndOfStreamException) - { - return null; - } - } - - private void ReadStart(RarCrcBinaryReader reader) - { - if (IsRar5) - { - HeaderCrc = reader.ReadUInt32(); - reader.ResetCrc(); - HeaderSize = (int)reader.ReadRarVIntUInt32(3); - reader.Mark(); - HeaderCode = reader.ReadRarVIntByte(); - HeaderFlags = reader.ReadRarVIntUInt16(2); - if ((HeaderFlags & HeaderFlagsV5.HasExtra) == HeaderFlagsV5.HasExtra) - { - ExtraSize = reader.ReadRarVIntUInt32(); - } - if ((HeaderFlags & HeaderFlagsV5.HasData) == HeaderFlagsV5.HasData) - { - AdditionalDataSize = (long)reader.ReadRarVInt(); - } - } else { - reader.Mark(); - HeaderCrc = reader.ReadUInt16(); - reader.ResetCrc(); - HeaderCode = reader.ReadByte(); - HeaderFlags = reader.ReadUInt16(); - HeaderSize = reader.ReadInt16(); - if ((HeaderFlags & HeaderFlagsV4.HasData) == HeaderFlagsV4.HasData) - { - AdditionalDataSize = reader.ReadUInt32(); - } - } - } - private void VerifyHeaderCrc(uint crc32) { var b = (IsRar5 ? crc32 : (ushort)crc32) == HeaderCrc; @@ -106,26 +100,31 @@ namespace SharpCompress.Common.Rar.Headers /// /// This is the number of bytes read when reading the header /// - protected long ReadBytes { get; private set; } + protected long ReadBytes { get; } - protected uint HeaderCrc { get; private set; } + protected uint HeaderCrc { get; } - internal byte HeaderCode { get; private set; } + internal byte HeaderCode { get; } - protected ushort HeaderFlags { get; private set; } + protected ushort HeaderFlags { get; } - protected int HeaderSize { get; private set; } + private bool HasHeaderFlag(ushort flag) + { + return (HeaderFlags & flag) == flag; + } - internal ArchiveEncoding ArchiveEncoding { get; private set; } + protected int HeaderSize { get; } + + internal ArchiveEncoding ArchiveEncoding { get; } /// /// Extra header size. /// - protected uint ExtraSize { get; private set; } + protected uint ExtraSize { get; } /// /// Size of additional data (eg file contents) /// - protected long AdditionalDataSize { get; private set; } + protected long AdditionalDataSize { get; } } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs index 0a833fcf..c9d37c16 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs @@ -59,7 +59,7 @@ namespace SharpCompress.Common.Rar.Headers #endif } - var header = RarHeader.TryReadBase(reader, Options.ArchiveEncoding, this.isRar5); + var header = RarHeader.TryReadBase(reader, this.isRar5, Options.ArchiveEncoding); if (header == null) { return null; From f802b4166574224a2e04e0478321643f00c4a962 Mon Sep 17 00:00:00 2001 From: coderb Date: Sun, 17 Dec 2017 10:08:21 -0500 Subject: [PATCH 10/61] rar5: wip --- .../Archives/Rar/RarArchiveEntryFactory.cs | 2 -- .../Archives/Rar/RarArchiveVolumeFactory.cs | 1 - .../Common/Rar/Headers/ArchiveHeader.cs | 8 ++--- .../Common/Rar/Headers/FileHeader.cs | 27 ++++++++++++---- src/SharpCompress/Common/Rar/Headers/Flags.cs | 31 +++++++++++-------- .../Common/Rar/Headers/RarHeader.cs | 17 +++++----- .../Common/Rar/Headers/RarHeaderFactory.cs | 10 +++--- .../Rar/MultiVolumeReadOnlyStream.cs | 1 - .../Readers/Rar/MultiVolumeRarReader.cs | 11 ++----- src/SharpCompress/Readers/Rar/RarReader.cs | 12 ++----- .../Readers/Rar/SingleVolumeRarReader.cs | 7 ++--- 11 files changed, 64 insertions(+), 63 deletions(-) diff --git a/src/SharpCompress/Archives/Rar/RarArchiveEntryFactory.cs b/src/SharpCompress/Archives/Rar/RarArchiveEntryFactory.cs index b5c4865a..91305cfd 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveEntryFactory.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveEntryFactory.cs @@ -1,7 +1,5 @@ using System.Collections.Generic; -using SharpCompress.Common; using SharpCompress.Common.Rar; -using SharpCompress.Common.Rar.Headers; namespace SharpCompress.Archives.Rar { diff --git a/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs b/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs index 091f24ae..57eb3c69 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using SharpCompress.Common; using SharpCompress.Common.Rar; using SharpCompress.Readers; #if !NO_FILE diff --git a/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs b/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs index 083a0bf2..83eda7f1 100644 --- a/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs @@ -18,9 +18,10 @@ namespace SharpCompress.Common.Rar.Headers { VolumeNumber = (int)reader.ReadRarVIntUInt32(); } - if (ExtraSize != 0) { - ReadLocator(reader); - } + // later: we may have a locator record if we need it + //if (ExtraSize != 0) { + // ReadLocator(reader); + //} } else { @@ -35,7 +36,6 @@ namespace SharpCompress.Common.Rar.Headers } private void ReadLocator(MarkingBinaryReader reader) { - // parse fields, unused var size = reader.ReadRarVIntUInt16(); var type = reader.ReadRarVIntUInt16(); if (type != 1) throw new InvalidFormatException("expected locator record"); diff --git a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs index 49c8bb03..0a3bebde 100644 --- a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs @@ -85,8 +85,15 @@ namespace SharpCompress.Common.Rar.Headers var b = reader.ReadBytes(nameSize); FileName = ConvertPathV5(Encoding.UTF8.GetString(b, 0, b.Length)); - if (ExtraSize != 0) { + // extra size seems to be redudant since we know the total header size + if (ExtraSize != RemainingHeaderBytes(reader)) + { + throw new InvalidFormatException("rar5 header size / extra size inconsistency"); + } + + while (RemainingHeaderBytes(reader) > 0) { var size = reader.ReadRarVIntUInt16(); + int n = RemainingHeaderBytes(reader); var type = reader.ReadRarVIntUInt16(); switch (type) { //TODO @@ -139,9 +146,17 @@ namespace SharpCompress.Common.Rar.Headers // } // break; - default: throw new InvalidFormatException("unexpected extra data record type " + type); + default: + // skip unknown record types to allow new record types to be added in the future + break; + } + // drain any trailing bytes of extra record + int did = n - RemainingHeaderBytes(reader); + int drain = size - did; + if (drain > 0) + { + reader.ReadBytes(drain); } - } if (AdditionalDataSize != 0) { @@ -223,7 +238,7 @@ namespace SharpCompress.Common.Rar.Headers switch (HeaderCode) { - case HeaderCodeV.FileHeader: + case HeaderCodeV.Rar4FileHeader: { if (HasFlag(FileFlagsV4.Unicode)) { @@ -250,7 +265,7 @@ namespace SharpCompress.Common.Rar.Headers FileName = ConvertPathV4(FileName); } break; - case HeaderCodeV.NewSubHeader: + case HeaderCodeV.Rar4NewSubHeader: { int datasize = HeaderSize - newLhdSize - nameSize; if (HasFlag(FileFlagsV4.Salt)) @@ -279,7 +294,7 @@ namespace SharpCompress.Common.Rar.Headers { // verify that the end of the header hasn't been reached before reading the Extended Time. // some tools incorrectly omit Extended Time despite specifying FileFlags.EXTTIME, which most parsers tolerate. - if (ReadBytes + reader.CurrentReadByteCount <= HeaderSize - 2) + if (RemainingHeaderBytes(reader) >= 2) { ushort extendedFlags = reader.ReadUInt16(); FileLastModifiedTime = ProcessExtendedTimeV4(extendedFlags, FileLastModifiedTime, reader, 0); diff --git a/src/SharpCompress/Common/Rar/Headers/Flags.cs b/src/SharpCompress/Common/Rar/Headers/Flags.cs index 6f072396..e9048b37 100644 --- a/src/SharpCompress/Common/Rar/Headers/Flags.cs +++ b/src/SharpCompress/Common/Rar/Headers/Flags.cs @@ -17,22 +17,22 @@ namespace SharpCompress.Common.Rar.Headers internal static class HeaderCodeV { + public const byte Rar4MarkHeader = 0x72; + public const byte Rar4ArchiveHeader = 0x73; + public const byte Rar4FileHeader = 0x74; + public const byte Rar4CommentHeader = 0x75; + public const byte Rar4AvHeader = 0x76; + public const byte Rar4SubHeader = 0x77; + public const byte Rar4ProtectHeader = 0x78; + public const byte Rar4SignHeader = 0x79; + public const byte Rar4NewSubHeader = 0x7a; + public const byte Rar4EndArchiveHeader = 0x7b; + public const byte Rar5ArchiveHeader = 0x01; public const byte Rar5FileHeader = 0x02; public const byte Rar5ServiceHeader = 0x03; public const byte Rar5EncryptionHeader = 0x04; public const byte Rar5EndArchiveHeader = 0x05; - - public const byte MarkHeader = 0x72; - public const byte ArchiveHeader = 0x73; - public const byte FileHeader = 0x74; - public const byte CommHeader = 0x75; - public const byte AvHeader = 0x76; - public const byte SubHeader = 0x77; - public const byte ProtectHeader = 0x78; - public const byte SignHeader = 0x79; - public const byte NewSubHeader = 0x7a; - public const byte EndArchiveHeader = 0x7b; } internal static class HeaderFlagsV4 @@ -42,8 +42,13 @@ namespace SharpCompress.Common.Rar.Headers internal static class HeaderFlagsV5 { - public const ushort HasExtra = 0x01; - public const ushort HasData = 0x02; + public const ushort HasExtra = 0x0001; + public const ushort HasData = 0x0002; + public const ushort Lock = 0x0004; // block must be kept during an update + public const ushort VolumeContinuation = 0x0008; + public const ushort VolumeIncomplete = 0x0010; + public const ushort Solid = 0x0020; // ??? + public const ushort PreserveChild = 0x0040; } internal static class ArchiveFlagsV4 diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs index 5f15914f..4713b3ef 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs @@ -54,7 +54,6 @@ namespace SharpCompress.Common.Rar.Headers AdditionalDataSize = reader.ReadUInt32(); } } - ReadBytes = reader.CurrentReadByteCount; } protected RarHeader(RarHeader header, RarCrcBinaryReader reader, HeaderType headerType) { @@ -68,17 +67,20 @@ namespace SharpCompress.Common.Rar.Headers AdditionalDataSize = header.AdditionalDataSize; ArchiveEncoding = header.ArchiveEncoding; ReadFinish(reader); - ReadBytes = reader.CurrentReadByteCount; - int headerSizeDiff = HeaderSize - (int)ReadBytes; - if (headerSizeDiff > 0) + int n = RemainingHeaderBytes(reader); + if (n > 0) { - reader.ReadBytes(headerSizeDiff); + reader.ReadBytes(n); } VerifyHeaderCrc(reader.GetCrc32()); } + protected int RemainingHeaderBytes(MarkingBinaryReader reader) { + return checked(HeaderSize - (int)reader.CurrentReadByteCount); + } + protected virtual void ReadFinish(MarkingBinaryReader reader) { throw new NotImplementedException(); @@ -97,11 +99,6 @@ namespace SharpCompress.Common.Rar.Headers protected bool IsRar5 { get { return this.isRar5; } } - /// - /// This is the number of bytes read when reading the header - /// - protected long ReadBytes { get; } - protected uint HeaderCrc { get; } internal byte HeaderCode { get; } diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs index c9d37c16..d4ce8cd2 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs @@ -67,7 +67,7 @@ namespace SharpCompress.Common.Rar.Headers switch (header.HeaderCode) { case HeaderCodeV.Rar5ArchiveHeader: - case HeaderCodeV.ArchiveHeader: + case HeaderCodeV.Rar4ArchiveHeader: { var ah = new ArchiveHeader(header, reader); if (ah.IsEncrypted == true) { @@ -77,7 +77,7 @@ namespace SharpCompress.Common.Rar.Headers return ah; } - case HeaderCodeV.ProtectHeader: + case HeaderCodeV.Rar4ProtectHeader: { var ph = new ProtectHeader(header, reader); // skip the recovery record data, we do not use it. @@ -109,7 +109,7 @@ namespace SharpCompress.Common.Rar.Headers return fh; } - case HeaderCodeV.NewSubHeader: + case HeaderCodeV.Rar4NewSubHeader: { var fh = new FileHeader(header, reader, HeaderType.NewSub); SkipData(fh, reader); @@ -117,7 +117,7 @@ namespace SharpCompress.Common.Rar.Headers } case HeaderCodeV.Rar5FileHeader: - case HeaderCodeV.FileHeader: + case HeaderCodeV.Rar4FileHeader: { var fh = new FileHeader(header, reader, HeaderType.File); switch (StreamingMode) @@ -153,7 +153,7 @@ namespace SharpCompress.Common.Rar.Headers return fh; } case HeaderCodeV.Rar5EndArchiveHeader: - case HeaderCodeV.EndArchiveHeader: + case HeaderCodeV.Rar4EndArchiveHeader: { return new EndArchiveHeader(header, reader); } diff --git a/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs b/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs index 235b8720..69e7734d 100644 --- a/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs +++ b/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using SharpCompress.Common; using SharpCompress.Common.Rar; -using SharpCompress.Common.Rar.Headers; namespace SharpCompress.Compressors.Rar { diff --git a/src/SharpCompress/Readers/Rar/MultiVolumeRarReader.cs b/src/SharpCompress/Readers/Rar/MultiVolumeRarReader.cs index 7bc61e2e..068d7572 100644 --- a/src/SharpCompress/Readers/Rar/MultiVolumeRarReader.cs +++ b/src/SharpCompress/Readers/Rar/MultiVolumeRarReader.cs @@ -33,14 +33,9 @@ namespace SharpCompress.Readers.Rar internal override bool NextEntryForCurrentStream() { - if (!base.NextEntryForCurrentStream()) - { - //if we're got another stream to try to process then do so - if (streams.MoveNext() && LoadStreamForReading(streams.Current)) - { - return true; - } - return false; + if (!base.NextEntryForCurrentStream()) { + // if we're got another stream to try to process then do so + return this.streams.MoveNext() && LoadStreamForReading(this.streams.Current); } return true; } diff --git a/src/SharpCompress/Readers/Rar/RarReader.cs b/src/SharpCompress/Readers/Rar/RarReader.cs index 92e4d737..444f0147 100644 --- a/src/SharpCompress/Readers/Rar/RarReader.cs +++ b/src/SharpCompress/Readers/Rar/RarReader.cs @@ -24,8 +24,6 @@ namespace SharpCompress.Readers.Rar public override RarVolume Volume => volume; - #region Open - /// /// Opens a RarReader for Non-seeking usage with a single volume /// @@ -50,8 +48,6 @@ namespace SharpCompress.Readers.Rar return new MultiVolumeRarReader(streams, options ?? new ReaderOptions()); } - #endregion - internal override IEnumerable GetEntries(Stream stream) { volume = new RarReaderVolume(stream, Options); @@ -67,11 +63,9 @@ namespace SharpCompress.Readers.Rar return Entry.Parts; } - protected override EntryStream GetEntryStream() - { - return CreateEntryStream(new RarCrcStream(pack, Entry.FileHeader, - new MultiVolumeReadOnlyStream( - CreateFilePartEnumerableForCurrentEntry().Cast(), this))); + protected override EntryStream GetEntryStream() { + var stream = new MultiVolumeReadOnlyStream(CreateFilePartEnumerableForCurrentEntry().Cast(), this); + return CreateEntryStream(new RarCrcStream(pack, Entry.FileHeader, stream)); } } } diff --git a/src/SharpCompress/Readers/Rar/SingleVolumeRarReader.cs b/src/SharpCompress/Readers/Rar/SingleVolumeRarReader.cs index 3ffede69..cc7befa1 100644 --- a/src/SharpCompress/Readers/Rar/SingleVolumeRarReader.cs +++ b/src/SharpCompress/Readers/Rar/SingleVolumeRarReader.cs @@ -16,10 +16,9 @@ namespace SharpCompress.Readers.Rar internal override void ValidateArchive(RarVolume archive) { - if (archive.IsMultiVolume) - { - throw new MultiVolumeExtractionException( - "Streamed archive is a Multi-volume archive. Use different RarReader method to extract."); + if (archive.IsMultiVolume) { + var msg = "Streamed archive is a Multi-volume archive. Use different RarReader method to extract."; + throw new MultiVolumeExtractionException(msg); } } From b9e89ca64b109c6f358bd8aa2eec1cd9a8701182 Mon Sep 17 00:00:00 2001 From: coderb Date: Sun, 17 Dec 2017 10:40:41 -0500 Subject: [PATCH 11/61] rar5: wip --- .../Archives/Rar/FileInfoRarArchiveVolume.cs | 2 +- src/SharpCompress/Archives/Rar/RarArchive.cs | 1 - .../Archives/Rar/StreamRarArchiveVolume.cs | 2 +- src/SharpCompress/Common/Rar/Headers/Flags.cs | 2 +- .../Common/Rar/Headers/RarHeader.cs | 6 ++++ .../Common/Rar/Headers/RarHeaderFactory.cs | 4 +-- src/SharpCompress/Common/Rar/RarVolume.cs | 29 +++++++++---------- .../Readers/Rar/RarReaderVolume.cs | 2 +- .../SharpCompress.Test/Rar/RarArchiveTests.cs | 5 ---- 9 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/SharpCompress/Archives/Rar/FileInfoRarArchiveVolume.cs b/src/SharpCompress/Archives/Rar/FileInfoRarArchiveVolume.cs index 9555735f..7932caad 100644 --- a/src/SharpCompress/Archives/Rar/FileInfoRarArchiveVolume.cs +++ b/src/SharpCompress/Archives/Rar/FileInfoRarArchiveVolume.cs @@ -32,7 +32,7 @@ namespace SharpCompress.Archives.Rar internal FileInfo FileInfo { get; } - internal override RarFilePart CreateFilePart(FileHeader fileHeader, MarkHeader markHeader) + internal override RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader) { return new FileInfoRarFilePart(this, ReaderOptions.Password, markHeader, fileHeader, FileInfo); } diff --git a/src/SharpCompress/Archives/Rar/RarArchive.cs b/src/SharpCompress/Archives/Rar/RarArchive.cs index adb9117c..3d61dd0a 100644 --- a/src/SharpCompress/Archives/Rar/RarArchive.cs +++ b/src/SharpCompress/Archives/Rar/RarArchive.cs @@ -5,7 +5,6 @@ using SharpCompress.Common; using SharpCompress.Common.Rar; using SharpCompress.Common.Rar.Headers; using SharpCompress.Compressors.Rar; -using SharpCompress.IO; using SharpCompress.Readers; using SharpCompress.Readers.Rar; diff --git a/src/SharpCompress/Archives/Rar/StreamRarArchiveVolume.cs b/src/SharpCompress/Archives/Rar/StreamRarArchiveVolume.cs index 3da1a531..92602ae9 100644 --- a/src/SharpCompress/Archives/Rar/StreamRarArchiveVolume.cs +++ b/src/SharpCompress/Archives/Rar/StreamRarArchiveVolume.cs @@ -19,7 +19,7 @@ namespace SharpCompress.Archives.Rar return GetVolumeFileParts(); } - internal override RarFilePart CreateFilePart(FileHeader fileHeader, MarkHeader markHeader) + internal override RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader) { return new SeekableFilePart(markHeader, fileHeader, Stream, ReaderOptions.Password); } diff --git a/src/SharpCompress/Common/Rar/Headers/Flags.cs b/src/SharpCompress/Common/Rar/Headers/Flags.cs index e9048b37..a3811740 100644 --- a/src/SharpCompress/Common/Rar/Headers/Flags.cs +++ b/src/SharpCompress/Common/Rar/Headers/Flags.cs @@ -44,7 +44,7 @@ namespace SharpCompress.Common.Rar.Headers { public const ushort HasExtra = 0x0001; public const ushort HasData = 0x0002; - public const ushort Lock = 0x0004; // block must be kept during an update + public const ushort Keep = 0x0004; // block must be kept during an update public const ushort VolumeContinuation = 0x0008; public const ushort VolumeIncomplete = 0x0010; public const ushort Solid = 0x0020; // ??? diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs index 4713b3ef..8abf8670 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs @@ -34,6 +34,12 @@ namespace SharpCompress.Common.Rar.Headers reader.Mark(); HeaderCode = reader.ReadRarVIntByte(); HeaderFlags = reader.ReadRarVIntUInt16(2); + +// if (HasHeaderFlag(HeaderFlagsV5.VolumeContinuation)) throw new NotImplementedException(); +// if (HasHeaderFlag(HeaderFlagsV5.VolumeIncomplete)) throw new NotImplementedException(); +// if (HasHeaderFlag(HeaderFlagsV5.Solid)) throw new NotImplementedException(); +// if (HasHeaderFlag(HeaderFlagsV5.PreserveChild)) throw new NotImplementedException(); + if (HasHeaderFlag(HeaderFlagsV5.HasExtra)) { ExtraSize = reader.ReadRarVIntUInt32(); diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs index d4ce8cd2..05660583 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs @@ -27,7 +27,7 @@ namespace SharpCompress.Common.Rar.Headers yield return markHeader; RarHeader header; - while ((header = ReadNextHeader(stream)) != null) + while ((header = TryReadNextHeader(stream)) != null) { yield return header; if (header.HeaderType == HeaderType.EndArchive) @@ -39,7 +39,7 @@ namespace SharpCompress.Common.Rar.Headers } } - private RarHeader ReadNextHeader(Stream stream) + private RarHeader TryReadNextHeader(Stream stream) { RarCrcBinaryReader reader; if (!IsEncrypted) diff --git a/src/SharpCompress/Common/Rar/RarVolume.cs b/src/SharpCompress/Common/Rar/RarVolume.cs index 201ced3b..31bc7636 100644 --- a/src/SharpCompress/Common/Rar/RarVolume.cs +++ b/src/SharpCompress/Common/Rar/RarVolume.cs @@ -21,42 +21,41 @@ namespace SharpCompress.Common.Rar headerFactory = new RarHeaderFactory(mode, options); } - internal StreamingMode Mode => headerFactory.StreamingMode; + internal ArchiveHeader ArchiveHeader { get; private set; } + + internal StreamingMode Mode => this.headerFactory.StreamingMode; internal abstract IEnumerable ReadFileParts(); - internal abstract RarFilePart CreateFilePart(FileHeader fileHeader, MarkHeader markHeader); + internal abstract RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader); internal IEnumerable GetVolumeFileParts() { - MarkHeader previousMarkHeader = null; - foreach (var header in headerFactory.ReadHeaders(Stream)) + MarkHeader lastMarkHeader = null; + foreach (var header in this.headerFactory.ReadHeaders(Stream)) { switch (header.HeaderType) { + case HeaderType.Mark: + { + lastMarkHeader = header as MarkHeader; + } + break; case HeaderType.Archive: { ArchiveHeader = header as ArchiveHeader; } break; - case HeaderType.Mark: - { - previousMarkHeader = header as MarkHeader; - } - break; case HeaderType.File: { - FileHeader fh = header as FileHeader; - RarFilePart fp = CreateFilePart(fh, previousMarkHeader); - yield return fp; + var fh = header as FileHeader; + yield return CreateFilePart(lastMarkHeader, fh); } break; } } } - internal ArchiveHeader ArchiveHeader { get; private set; } - private void EnsureArchiveHeaderLoaded() { if (ArchiveHeader == null) @@ -66,7 +65,7 @@ namespace SharpCompress.Common.Rar throw new InvalidOperationException("ArchiveHeader should never been null in a streaming read."); } - //we only want to load the archive header to avoid overhead but have to do the nasty thing and reset the stream + // we only want to load the archive header to avoid overhead but have to do the nasty thing and reset the stream GetVolumeFileParts().First(); Stream.Position = 0; } diff --git a/src/SharpCompress/Readers/Rar/RarReaderVolume.cs b/src/SharpCompress/Readers/Rar/RarReaderVolume.cs index bf68043f..98a8c5c0 100644 --- a/src/SharpCompress/Readers/Rar/RarReaderVolume.cs +++ b/src/SharpCompress/Readers/Rar/RarReaderVolume.cs @@ -13,7 +13,7 @@ namespace SharpCompress.Readers.Rar { } - internal override RarFilePart CreateFilePart(FileHeader fileHeader, MarkHeader markHeader) + internal override RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader) { return new NonSeekableStreamFilePart(markHeader, fileHeader); } diff --git a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs index 54619c92..d320f782 100644 --- a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs +++ b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs @@ -135,11 +135,6 @@ namespace SharpCompress.Test.Rar DoRar_test_invalid_exttime_ArchiveStreamRead("Rar.test_invalid_exttime.rar"); } - [Fact] - public void Rar5_test_invalid_exttime_ArchiveStreamRead() { - DoRar_test_invalid_exttime_ArchiveStreamRead("Rar5.test_invalid_exttime.rar"); - } - private void DoRar_test_invalid_exttime_ArchiveStreamRead(string filename) { ResetScratch(); From 7969bbaac4d750fae171395c49883e0f098496f7 Mon Sep 17 00:00:00 2001 From: coderb Date: Sun, 17 Dec 2017 11:04:13 -0500 Subject: [PATCH 12/61] rar5: working on decompression --- .../Common/Rar/Headers/FileHeader.cs | 14 +++++-- src/SharpCompress/Compressors/Rar/Unpack.cs | 40 +------------------ tests/SharpCompress.Test/ArchiveTests.cs | 1 + 3 files changed, 12 insertions(+), 43 deletions(-) diff --git a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs index 0a3bebde..f8fd127e 100644 --- a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs @@ -55,7 +55,7 @@ namespace SharpCompress.Common.Rar.Headers R5IsSolid = (us & 0x40) == 0x40; // Bits 8 - 10 (0x0380 mask) define the compression method. Currently only values 0 - 5 are used. 0 means no compression. - R5CompressionMethod = (byte)((us >> 7) & 0x7); + CompressionMethod = (byte)((us >> 7) & 0x7); // Bits 11 - 14 (0x3c00) define the minimum size of dictionary size required to extract data. Value 0 means 128 KB, 1 - 256 KB, ..., 14 - 2048 MB, 15 - 4096 MB. // 2 ^ (17 + x) @@ -205,7 +205,7 @@ namespace SharpCompress.Common.Rar.Headers FileLastModifiedTime = Utility.DosDateToDateTime(reader.ReadUInt32()); R4RarVersion = reader.ReadByte(); - R4PackingMethod = reader.ReadByte(); + CompressionMethod = (byte)(reader.ReadByte() - 0x30); short nameSize = reader.ReadInt16(); @@ -381,13 +381,19 @@ namespace SharpCompress.Common.Rar.Headers private set { this.fileCrc = value; } } + // 0 - storing + // 1 - fastest compression + // 2 - fast compression + // 3 - normal compression + // 4 - good compression + // 5 - best compression + internal byte CompressionMethod { get; private set; } + internal byte R4RarVersion { get; private set; } - internal byte R4PackingMethod { get; private set; } internal byte[] R4Salt { get; private set; } internal byte R5CompressionAlgorithm { get; private set; } internal bool R5IsSolid{ get; private set; } - internal byte R5CompressionMethod { get; private set; } internal byte R5DictSize_2_17plusX { get; private set; } diff --git a/src/SharpCompress/Compressors/Rar/Unpack.cs b/src/SharpCompress/Compressors/Rar/Unpack.cs index 40d3087c..5346495a 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/Unpack.cs @@ -142,7 +142,7 @@ namespace SharpCompress.Compressors.Rar public void doUnpack() { - if (fileHeader.R4PackingMethod == 0x30) + if (fileHeader.CompressionMethod == 0) { unstoreFile(); return; @@ -150,44 +150,6 @@ namespace SharpCompress.Compressors.Rar var solid = fileHeader.IsSolid; switch (fileHeader.R4RarVersion) { - case 0: // no compression - unstoreFile(); - break; - - case 15: // rar 1.5 compression - unpack15(solid); - break; - - case 20: // rar 2.x compression - case 26: // files larger than 2GB - unpack20(solid); - break; - - case 29: // rar 3.x compression - case 36: // alternative hash - unpack29(solid); - break; - - default: throw new InvalidFormatException("unknown rar compression version " + this.fileHeader.R4RarVersion); - } - } - - public void doUnpackNEW() - { - var compressionMethod = fileHeader.R4RarVersion; - //??? if (fileHeader.R4PackingMethod == 0x30) -// { -// unstoreFile(); -// return; -// } - var solid = fileHeader.IsSolid; - //switch (fileHeader.R4RarVersion) - switch (compressionMethod) - { - case 0: // no compression - unstoreFile(); - break; - case 15: // rar 1.5 compression unpack15(solid); break; diff --git a/tests/SharpCompress.Test/ArchiveTests.cs b/tests/SharpCompress.Test/ArchiveTests.cs index 757b10a1..b6f367c7 100644 --- a/tests/SharpCompress.Test/ArchiveTests.cs +++ b/tests/SharpCompress.Test/ArchiveTests.cs @@ -88,6 +88,7 @@ namespace SharpCompress.Test testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); ArchiveFileRead(testArchive.AsEnumerable(), readerOptions); } + protected void ArchiveFileRead(IEnumerable testArchives, ReaderOptions readerOptions = null) { foreach (var path in testArchives) From d268cc768517694f8882080f0da22e1c6de88c34 Mon Sep 17 00:00:00 2001 From: coderb Date: Sun, 17 Dec 2017 11:16:42 -0500 Subject: [PATCH 13/61] rar5: wip unpack50() --- .../Common/Rar/Headers/FileHeader.cs | 27 +++++++++---------- src/SharpCompress/Compressors/Rar/Unpack.cs | 9 +++++-- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs index f8fd127e..b6181c5b 100644 --- a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs @@ -47,12 +47,15 @@ namespace SharpCompress.Common.Rar.Headers var us = reader.ReadRarVIntUInt16(); // Lower 6 bits (0x003f mask) contain the version of compression algorithm, resulting in possible 0 - 63 values. Current version is 0. - R5CompressionAlgorithm = (byte)(us & 0x3f); - if (R5CompressionAlgorithm != 0) throw new InvalidFormatException("unknown rar5 compression algorithm " + R5CompressionAlgorithm); + // "+ 50" to not mix with old RAR format algorithms. For example, + // we may need to use the compression algorithm 15 in the future, + // but it was already used in RAR 1.5 and Unpack needs to distinguish + // them. + CompressionAlgorithm = (byte)((us & 0x3f) + 50); // 7th bit (0x0040) defines the solid flag. If it is set, RAR continues to use the compression dictionary left after processing preceding files. // It can be set only for file headers and is never set for service headers. - R5IsSolid = (us & 0x40) == 0x40; + IsSolid = (us & 0x40) == 0x40; // Bits 8 - 10 (0x0380 mask) define the compression method. Currently only values 0 - 5 are used. 0 means no compression. CompressionMethod = (byte)((us >> 7) & 0x7); @@ -195,6 +198,7 @@ namespace SharpCompress.Common.Rar.Headers private void ReadFromReaderV4(MarkingBinaryReader reader) { Flags = HeaderFlags; + IsSolid = HasFlag(FileFlagsV4.Solid); uint lowUncompressedSize = reader.ReadUInt32(); @@ -204,7 +208,7 @@ namespace SharpCompress.Common.Rar.Headers FileLastModifiedTime = Utility.DosDateToDateTime(reader.ReadUInt32()); - R4RarVersion = reader.ReadByte(); + CompressionAlgorithm = reader.ReadByte(); CompressionMethod = (byte)(reader.ReadByte() - 0x30); short nameSize = reader.ReadInt16(); @@ -389,14 +393,13 @@ namespace SharpCompress.Common.Rar.Headers // 5 - best compression internal byte CompressionMethod { get; private set; } - internal byte R4RarVersion { get; private set; } + // see unpack.cs + internal byte CompressionAlgorithm { get; private set; } + public bool IsSolid { get; private set; } + internal byte[] R4Salt { get; private set; } - - internal byte R5CompressionAlgorithm { get; private set; } - internal bool R5IsSolid{ get; private set; } internal byte R5DictSize_2_17plusX { get; private set; } - private byte HostOs { get; set; } internal uint FileAttributes { get; private set; } internal long CompressedSize { get; private set; } @@ -414,11 +417,7 @@ namespace SharpCompress.Common.Rar.Headers //!!! TODO rar5 public bool IsEncrypted => HasFlag(FileFlagsV4.Password); - -//!!! TODO rar5; R5IsSolid - public bool IsSolid => HasFlag(FileFlagsV4.Solid); - - + internal DateTime? FileLastModifiedTime { get; private set; } internal DateTime? FileCreatedTime { get; private set; } diff --git a/src/SharpCompress/Compressors/Rar/Unpack.cs b/src/SharpCompress/Compressors/Rar/Unpack.cs index 5346495a..5a77beae 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/Unpack.cs @@ -148,7 +148,7 @@ namespace SharpCompress.Compressors.Rar return; } var solid = fileHeader.IsSolid; - switch (fileHeader.R4RarVersion) + switch (fileHeader.CompressionAlgorithm) { case 15: // rar 1.5 compression unpack15(solid); @@ -163,8 +163,13 @@ namespace SharpCompress.Compressors.Rar case 36: // alternative hash unpack29(solid); break; + +// case 50: // rar 5.x compression +// unpack50(solid); +// break; - default: throw new InvalidFormatException("unknown rar compression version " + this.fileHeader.R4RarVersion); + default: + throw new InvalidFormatException("unknown rar compression version " + this.fileHeader.CompressionAlgorithm); } } From 1445b0a48b1a80610aa45764979a2cb46fc3cd8a Mon Sep 17 00:00:00 2001 From: coderb Date: Sun, 17 Dec 2017 11:33:31 -0500 Subject: [PATCH 14/61] rar5: refactor unpack classes --- .../Compressors/Rar/Decode/BitDecode.cs | 2 +- .../Compressors/Rar/Decode/DistDecode.cs | 2 +- .../Compressors/Rar/Decode/LitDecode.cs | 2 +- .../Compressors/Rar/Decode/LowDistDecode.cs | 2 +- .../Compressors/Rar/Decode/MultDecode.cs | 2 +- .../Rar/Decode/{Compress.cs => PackDef.cs} | 2 +- .../Compressors/Rar/Decode/RepDecode.cs | 2 +- src/SharpCompress/Compressors/Rar/Unpack.cs | 56 +- src/SharpCompress/Compressors/Rar/Unpack15.cs | 10 +- src/SharpCompress/Compressors/Rar/Unpack20.cs | 32 +- src/SharpCompress/Compressors/Rar/Unpack50.cs | 696 ++++++++++++++++++ 11 files changed, 752 insertions(+), 56 deletions(-) rename src/SharpCompress/Compressors/Rar/Decode/{Compress.cs => PackDef.cs} (97%) create mode 100644 src/SharpCompress/Compressors/Rar/Unpack50.cs diff --git a/src/SharpCompress/Compressors/Rar/Decode/BitDecode.cs b/src/SharpCompress/Compressors/Rar/Decode/BitDecode.cs index ba13c9b8..eef2007f 100644 --- a/src/SharpCompress/Compressors/Rar/Decode/BitDecode.cs +++ b/src/SharpCompress/Compressors/Rar/Decode/BitDecode.cs @@ -3,7 +3,7 @@ namespace SharpCompress.Compressors.Rar.Decode internal class BitDecode : Decode { internal BitDecode() - : base(new int[Compress.BC]) + : base(new int[PackDef.BC]) { } } diff --git a/src/SharpCompress/Compressors/Rar/Decode/DistDecode.cs b/src/SharpCompress/Compressors/Rar/Decode/DistDecode.cs index 0c26c391..b5127e2d 100644 --- a/src/SharpCompress/Compressors/Rar/Decode/DistDecode.cs +++ b/src/SharpCompress/Compressors/Rar/Decode/DistDecode.cs @@ -3,7 +3,7 @@ namespace SharpCompress.Compressors.Rar.Decode internal class DistDecode : Decode { internal DistDecode() - : base(new int[Compress.DC]) + : base(new int[PackDef.DC]) { } } diff --git a/src/SharpCompress/Compressors/Rar/Decode/LitDecode.cs b/src/SharpCompress/Compressors/Rar/Decode/LitDecode.cs index 6084d0fe..0c58184e 100644 --- a/src/SharpCompress/Compressors/Rar/Decode/LitDecode.cs +++ b/src/SharpCompress/Compressors/Rar/Decode/LitDecode.cs @@ -3,7 +3,7 @@ namespace SharpCompress.Compressors.Rar.Decode internal class LitDecode : Decode { internal LitDecode() - : base(new int[Compress.NC]) + : base(new int[PackDef.NC]) { } } diff --git a/src/SharpCompress/Compressors/Rar/Decode/LowDistDecode.cs b/src/SharpCompress/Compressors/Rar/Decode/LowDistDecode.cs index 266bf9e1..bafb0c2f 100644 --- a/src/SharpCompress/Compressors/Rar/Decode/LowDistDecode.cs +++ b/src/SharpCompress/Compressors/Rar/Decode/LowDistDecode.cs @@ -3,7 +3,7 @@ namespace SharpCompress.Compressors.Rar.Decode internal class LowDistDecode : Decode { internal LowDistDecode() - : base(new int[Compress.LDC]) + : base(new int[PackDef.LDC]) { } } diff --git a/src/SharpCompress/Compressors/Rar/Decode/MultDecode.cs b/src/SharpCompress/Compressors/Rar/Decode/MultDecode.cs index d42a83eb..6c21dc83 100644 --- a/src/SharpCompress/Compressors/Rar/Decode/MultDecode.cs +++ b/src/SharpCompress/Compressors/Rar/Decode/MultDecode.cs @@ -3,7 +3,7 @@ namespace SharpCompress.Compressors.Rar.Decode internal class MultDecode : Decode { internal MultDecode() - : base(new int[Compress.MC20]) + : base(new int[PackDef.MC20]) { } } diff --git a/src/SharpCompress/Compressors/Rar/Decode/Compress.cs b/src/SharpCompress/Compressors/Rar/Decode/PackDef.cs similarity index 97% rename from src/SharpCompress/Compressors/Rar/Decode/Compress.cs rename to src/SharpCompress/Compressors/Rar/Decode/PackDef.cs index 3689daf0..15ecdf13 100644 --- a/src/SharpCompress/Compressors/Rar/Decode/Compress.cs +++ b/src/SharpCompress/Compressors/Rar/Decode/PackDef.cs @@ -1,6 +1,6 @@ namespace SharpCompress.Compressors.Rar.Decode { - internal class Compress + internal class PackDef { public const int CODEBUFSIZE = 0x4000; public const int MAXWINSIZE = 0x400000; diff --git a/src/SharpCompress/Compressors/Rar/Decode/RepDecode.cs b/src/SharpCompress/Compressors/Rar/Decode/RepDecode.cs index 3a59587f..1f4a42ef 100644 --- a/src/SharpCompress/Compressors/Rar/Decode/RepDecode.cs +++ b/src/SharpCompress/Compressors/Rar/Decode/RepDecode.cs @@ -3,7 +3,7 @@ namespace SharpCompress.Compressors.Rar.Decode internal class RepDecode : Decode { internal RepDecode() - : base(new int[Compress.RC]) + : base(new int[PackDef.RC]) { } } diff --git a/src/SharpCompress/Compressors/Rar/Unpack.cs b/src/SharpCompress/Compressors/Rar/Unpack.cs index 5a77beae..f0cd37b5 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/Unpack.cs @@ -82,7 +82,7 @@ namespace SharpCompress.Compressors.Rar //UPGRADE_NOTE: The initialization of 'unpOldTable' was moved to method 'InitBlock'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'" - private readonly byte[] unpOldTable = new byte[Compress.HUFF_TABLE_SIZE]; + private readonly byte[] unpOldTable = new byte[PackDef.HUFF_TABLE_SIZE]; private BlockTypes unpBlockType; @@ -114,7 +114,7 @@ namespace SharpCompress.Compressors.Rar { if (window == null) { - this.window = new byte[Compress.MAXWINSIZE]; + this.window = new byte[PackDef.MAXWINSIZE]; } else { @@ -198,8 +198,8 @@ namespace SharpCompress.Compressors.Rar private void unpack29(bool solid) { - int[] DDecode = new int[Compress.DC]; - byte[] DBits = new byte[Compress.DC]; + int[] DDecode = new int[PackDef.DC]; + byte[] DBits = new byte[PackDef.DC]; int Bits; @@ -239,7 +239,7 @@ namespace SharpCompress.Compressors.Rar while (true) { - unpPtr &= Compress.MAXWINMASK; + unpPtr &= PackDef.MAXWINMASK; if (inAddr > readBorder) { @@ -251,7 +251,7 @@ namespace SharpCompress.Compressors.Rar // System.out.println(((wrPtr - unpPtr) & // Compress.MAXWINMASK)+":"+wrPtr+":"+unpPtr); - if (((wrPtr - unpPtr) & Compress.MAXWINMASK) < 260 && wrPtr != unpPtr) + if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 260 && wrPtr != unpPtr) { UnpWriteBuf(); if (destUnpSize <= 0) @@ -378,7 +378,7 @@ namespace SharpCompress.Compressors.Rar int LowDist = this.decodeNumber(LDD); if (LowDist == 16) { - lowDistRepCount = Compress.LOW_DIST_REP_COUNT - 1; + lowDistRepCount = PackDef.LOW_DIST_REP_COUNT - 1; Distance += prevLowDist; } else @@ -474,7 +474,7 @@ namespace SharpCompress.Compressors.Rar private void UnpWriteBuf() { int WrittenBorder = wrPtr; - int WriteSize = (unpPtr - WrittenBorder) & Compress.MAXWINMASK; + int WriteSize = (unpPtr - WrittenBorder) & PackDef.MAXWINMASK; for (int I = 0; I < prgStack.Count; I++) { UnpackFilter flt = prgStack[I]; @@ -489,17 +489,17 @@ namespace SharpCompress.Compressors.Rar } int BlockStart = flt.BlockStart; // ->BlockStart; int BlockLength = flt.BlockLength; // ->BlockLength; - if (((BlockStart - WrittenBorder) & Compress.MAXWINMASK) < WriteSize) + if (((BlockStart - WrittenBorder) & PackDef.MAXWINMASK) < WriteSize) { if (WrittenBorder != BlockStart) { UnpWriteArea(WrittenBorder, BlockStart); WrittenBorder = BlockStart; - WriteSize = (unpPtr - WrittenBorder) & Compress.MAXWINMASK; + WriteSize = (unpPtr - WrittenBorder) & PackDef.MAXWINMASK; } if (BlockLength <= WriteSize) { - int BlockEnd = (BlockStart + BlockLength) & Compress.MAXWINMASK; + int BlockEnd = (BlockStart + BlockLength) & PackDef.MAXWINMASK; if (BlockStart < BlockEnd || BlockEnd == 0) { // VM.SetMemory(0,Window+BlockStart,BlockLength); @@ -507,7 +507,7 @@ namespace SharpCompress.Compressors.Rar } else { - int FirstPartLength = Compress.MAXWINSIZE - BlockStart; + int FirstPartLength = PackDef.MAXWINSIZE - BlockStart; // VM.SetMemory(0,Window+BlockStart,FirstPartLength); rarVM.setMemory(0, window, BlockStart, FirstPartLength); @@ -641,7 +641,7 @@ namespace SharpCompress.Compressors.Rar writtenFileSize += FilteredDataSize; destUnpSize -= FilteredDataSize; WrittenBorder = BlockEnd; - WriteSize = (unpPtr - WrittenBorder) & Compress.MAXWINMASK; + WriteSize = (unpPtr - WrittenBorder) & PackDef.MAXWINMASK; } else { @@ -671,7 +671,7 @@ namespace SharpCompress.Compressors.Rar } if (endPtr < startPtr) { - UnpWriteData(window, startPtr, -startPtr & Compress.MAXWINMASK); + UnpWriteData(window, startPtr, -startPtr & PackDef.MAXWINMASK); UnpWriteData(window, 0, endPtr); unpAllBuf = true; } @@ -719,7 +719,7 @@ namespace SharpCompress.Compressors.Rar int destPtr = unpPtr - distance; // System.out.println(unpPtr+":"+distance); - if (destPtr >= 0 && destPtr < Compress.MAXWINSIZE - 260 && unpPtr < Compress.MAXWINSIZE - 260) + if (destPtr >= 0 && destPtr < PackDef.MAXWINSIZE - 260 && unpPtr < PackDef.MAXWINSIZE - 260) { window[unpPtr++] = window[destPtr++]; @@ -732,8 +732,8 @@ namespace SharpCompress.Compressors.Rar { while (length-- != 0) { - window[unpPtr] = window[destPtr++ & Compress.MAXWINMASK]; - unpPtr = (unpPtr + 1) & Compress.MAXWINMASK; + window[unpPtr] = window[destPtr++ & PackDef.MAXWINMASK]; + unpPtr = (unpPtr + 1) & PackDef.MAXWINMASK; } } } @@ -796,9 +796,9 @@ namespace SharpCompress.Compressors.Rar private bool readTables() { - byte[] bitLength = new byte[Compress.BC]; + byte[] bitLength = new byte[PackDef.BC]; - byte[] table = new byte[Compress.HUFF_TABLE_SIZE]; + byte[] table = new byte[PackDef.HUFF_TABLE_SIZE]; if (inAddr > readTop - 25) { if (!unpReadBuf()) @@ -824,7 +824,7 @@ namespace SharpCompress.Compressors.Rar } AddBits(2); - for (int i = 0; i < Compress.BC; i++) + for (int i = 0; i < PackDef.BC; i++) { int length = (Utility.URShift(GetBits(), 12)) & 0xFF; AddBits(4); @@ -852,9 +852,9 @@ namespace SharpCompress.Compressors.Rar } } - UnpackUtility.makeDecodeTables(bitLength, 0, BD, Compress.BC); + UnpackUtility.makeDecodeTables(bitLength, 0, BD, PackDef.BC); - int TableSize = Compress.HUFF_TABLE_SIZE; + int TableSize = PackDef.HUFF_TABLE_SIZE; for (int i = 0; i < TableSize;) { @@ -914,10 +914,10 @@ namespace SharpCompress.Compressors.Rar { return (false); } - UnpackUtility.makeDecodeTables(table, 0, LD, Compress.NC); - UnpackUtility.makeDecodeTables(table, Compress.NC, DD, Compress.DC); - UnpackUtility.makeDecodeTables(table, Compress.NC + Compress.DC, LDD, Compress.LDC); - UnpackUtility.makeDecodeTables(table, Compress.NC + Compress.DC + Compress.LDC, RD, Compress.RC); + UnpackUtility.makeDecodeTables(table, 0, LD, PackDef.NC); + UnpackUtility.makeDecodeTables(table, PackDef.NC, DD, PackDef.DC); + UnpackUtility.makeDecodeTables(table, PackDef.NC + PackDef.DC, LDD, PackDef.LDC); + UnpackUtility.makeDecodeTables(table, PackDef.NC + PackDef.DC + PackDef.LDC, RD, PackDef.RC); // memcpy(unpOldTable,table,sizeof(unpOldTable)); @@ -1077,7 +1077,7 @@ namespace SharpCompress.Compressors.Rar { BlockStart += 258; } - StackFilter.BlockStart = ((BlockStart + unpPtr) & Compress.MAXWINMASK); + StackFilter.BlockStart = ((BlockStart + unpPtr) & PackDef.MAXWINMASK); if ((firstByte & 0x20) != 0) { StackFilter.BlockLength = RarVM.ReadData(Inp); @@ -1086,7 +1086,7 @@ namespace SharpCompress.Compressors.Rar { StackFilter.BlockLength = FiltPos < oldFilterLengths.Count ? oldFilterLengths[FiltPos] : 0; } - StackFilter.NextWindow = (wrPtr != unpPtr) && ((wrPtr - unpPtr) & Compress.MAXWINMASK) <= BlockStart; + StackFilter.NextWindow = (wrPtr != unpPtr) && ((wrPtr - unpPtr) & PackDef.MAXWINMASK) <= BlockStart; // DebugLog("\nNextWindow: UnpPtr=%08x WrPtr=%08x // BlockStart=%08x",UnpPtr,WrPtr,BlockStart); diff --git a/src/SharpCompress/Compressors/Rar/Unpack15.cs b/src/SharpCompress/Compressors/Rar/Unpack15.cs index c7c1c877..cc762635 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack15.cs +++ b/src/SharpCompress/Compressors/Rar/Unpack15.cs @@ -174,13 +174,13 @@ namespace SharpCompress.Compressors.Rar while (destUnpSize >= 0) { - unpPtr &= Compress.MAXWINMASK; + unpPtr &= PackDef.MAXWINMASK; if (inAddr > readTop - 30 && !unpReadBuf()) { break; } - if (((wrPtr - unpPtr) & Compress.MAXWINMASK) < 270 && wrPtr != unpPtr) + if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 270 && wrPtr != unpPtr) { oldUnpWriteBuf(); if (suspended) @@ -702,8 +702,8 @@ namespace SharpCompress.Compressors.Rar destUnpSize -= Length; while ((Length--) != 0) { - window[unpPtr] = window[(unpPtr - Distance) & Compress.MAXWINMASK]; - unpPtr = (unpPtr + 1) & Compress.MAXWINMASK; + window[unpPtr] = window[(unpPtr - Distance) & PackDef.MAXWINMASK]; + unpPtr = (unpPtr + 1) & PackDef.MAXWINMASK; } } @@ -726,7 +726,7 @@ namespace SharpCompress.Compressors.Rar } if (unpPtr < wrPtr) { - writeStream.Write(window, wrPtr, -wrPtr & Compress.MAXWINMASK); + writeStream.Write(window, wrPtr, -wrPtr & PackDef.MAXWINMASK); writeStream.Write(window, 0, unpPtr); unpAllBuf = true; } diff --git a/src/SharpCompress/Compressors/Rar/Unpack20.cs b/src/SharpCompress/Compressors/Rar/Unpack20.cs index 608442c1..e9a8dbdd 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack20.cs +++ b/src/SharpCompress/Compressors/Rar/Unpack20.cs @@ -37,7 +37,7 @@ namespace SharpCompress.Compressors.Rar private void InitBlock() { - UnpOldTable20 = new byte[Compress.MC20 * 4]; + UnpOldTable20 = new byte[PackDef.MC20 * 4]; } protected internal MultDecode[] MD = new MultDecode[4]; @@ -124,7 +124,7 @@ namespace SharpCompress.Compressors.Rar while (destUnpSize >= 0) { - unpPtr &= Compress.MAXWINMASK; + unpPtr &= PackDef.MAXWINMASK; if (inAddr > readTop - 30) { @@ -133,7 +133,7 @@ namespace SharpCompress.Compressors.Rar break; } } - if (((wrPtr - unpPtr) & Compress.MAXWINMASK) < 270 && wrPtr != unpPtr) + if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 270 && wrPtr != unpPtr) { oldUnpWriteBuf(); if (suspended) @@ -258,7 +258,7 @@ namespace SharpCompress.Compressors.Rar destUnpSize -= Length; int DestPtr = unpPtr - Distance; - if (DestPtr < Compress.MAXWINSIZE - 300 && unpPtr < Compress.MAXWINSIZE - 300) + if (DestPtr < PackDef.MAXWINSIZE - 300 && unpPtr < PackDef.MAXWINSIZE - 300) { window[unpPtr++] = window[DestPtr++]; window[unpPtr++] = window[DestPtr++]; @@ -272,16 +272,16 @@ namespace SharpCompress.Compressors.Rar { while ((Length--) != 0) { - window[unpPtr] = window[DestPtr++ & Compress.MAXWINMASK]; - unpPtr = (unpPtr + 1) & Compress.MAXWINMASK; + window[unpPtr] = window[DestPtr++ & PackDef.MAXWINMASK]; + unpPtr = (unpPtr + 1) & PackDef.MAXWINMASK; } } } private bool ReadTables20() { - byte[] BitLength = new byte[Compress.BC20]; - byte[] Table = new byte[Compress.MC20 * 4]; + byte[] BitLength = new byte[PackDef.BC20]; + byte[] Table = new byte[PackDef.MC20 * 4]; int TableSize, N, I; if (inAddr > readTop - 25) { @@ -308,18 +308,18 @@ namespace SharpCompress.Compressors.Rar UnpCurChannel = 0; } AddBits(2); - TableSize = Compress.MC20 * UnpChannels; + TableSize = PackDef.MC20 * UnpChannels; } else { - TableSize = Compress.NC20 + Compress.DC20 + Compress.RC20; + TableSize = PackDef.NC20 + PackDef.DC20 + PackDef.RC20; } - for (I = 0; I < Compress.BC20; I++) + for (I = 0; I < PackDef.BC20; I++) { BitLength[I] = (byte)(Utility.URShift(GetBits(), 12)); AddBits(4); } - UnpackUtility.makeDecodeTables(BitLength, 0, BD, Compress.BC20); + UnpackUtility.makeDecodeTables(BitLength, 0, BD, PackDef.BC20); I = 0; while (I < TableSize) { @@ -372,14 +372,14 @@ namespace SharpCompress.Compressors.Rar { for (I = 0; I < UnpChannels; I++) { - UnpackUtility.makeDecodeTables(Table, I * Compress.MC20, MD[I], Compress.MC20); + UnpackUtility.makeDecodeTables(Table, I * PackDef.MC20, MD[I], PackDef.MC20); } } else { - UnpackUtility.makeDecodeTables(Table, 0, LD, Compress.NC20); - UnpackUtility.makeDecodeTables(Table, Compress.NC20, DD, Compress.DC20); - UnpackUtility.makeDecodeTables(Table, Compress.NC20 + Compress.DC20, RD, Compress.RC20); + UnpackUtility.makeDecodeTables(Table, 0, LD, PackDef.NC20); + UnpackUtility.makeDecodeTables(Table, PackDef.NC20, DD, PackDef.DC20); + UnpackUtility.makeDecodeTables(Table, PackDef.NC20 + PackDef.DC20, RD, PackDef.RC20); } // memcpy(UnpOldTable20,Table,sizeof(UnpOldTable20)); diff --git a/src/SharpCompress/Compressors/Rar/Unpack50.cs b/src/SharpCompress/Compressors/Rar/Unpack50.cs new file mode 100644 index 00000000..85614d9c --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/Unpack50.cs @@ -0,0 +1,696 @@ +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; +// +//namespace SharpCompress.Compressors.Rar +//{ +// internal sealed class Unpack50 +// { +// +// public void Unpack5(bool Solid) { +// FileExtracted=true; +// +// if (!Suspended) +// { +// UnpInitData(Solid); +// if (!UnpReadBuf()) +// return; +// +// // Check TablesRead5 to be sure that we read tables at least once +// // regardless of current block header TablePresent flag. +// // So we can safefly use these tables below. +// if (!ReadBlockHeader(Inp,BlockHeader) || +// !ReadTables(Inp,BlockHeader,BlockTables) || !TablesRead5) +// return; +// } +// +// while (true) +// { +// UnpPtr&=MaxWinMask; +// +// if (Inp.InAddr>=ReadBorder) +// { +// bool FileDone=false; +// +// // We use 'while', because for empty block containing only Huffman table, +// // we'll be on the block border once again just after reading the table. +// while (Inp.InAddr>BlockHeader.BlockStart+BlockHeader.BlockSize-1 || +// Inp.InAddr==BlockHeader.BlockStart+BlockHeader.BlockSize-1 && +// Inp.InBit>=BlockHeader.BlockBitSize) +// { +// if (BlockHeader.LastBlockInFile) +// { +// FileDone=true; +// break; +// } +// if (!ReadBlockHeader(Inp,BlockHeader) || !ReadTables(Inp,BlockHeader,BlockTables)) +// return; +// } +// if (FileDone || !UnpReadBuf()) +// break; +// } +// +// if (((WriteBorder-UnpPtr) & MaxWinMask)DestUnpSize) +// return; +// if (Suspended) +// { +// FileExtracted=false; +// return; +// } +// } +// +// uint MainSlot=DecodeNumber(Inp,&BlockTables.LD); +// if (MainSlot<256) +// { +// if (Fragmented) +// FragWindow[UnpPtr++]=(byte)MainSlot; +// else +// Window[UnpPtr++]=(byte)MainSlot; +// continue; +// } +// if (MainSlot>=262) +// { +// uint Length=SlotToLength(Inp,MainSlot-262); +// +// uint DBits,Distance=1,DistSlot=DecodeNumber(Inp,&BlockTables.DD); +// if (DistSlot<4) +// { +// DBits=0; +// Distance+=DistSlot; +// } +// else +// { +// DBits=DistSlot/2 - 1; +// Distance+=(2 | (DistSlot & 1)) << DBits; +// } +// +// if (DBits>0) +// { +// if (DBits>=4) +// { +// if (DBits>4) +// { +// Distance+=((Inp.getbits32()>>(36-DBits))<<4); +// Inp.addbits(DBits-4); +// } +// uint LowDist=DecodeNumber(Inp,&BlockTables.LDD); +// Distance+=LowDist; +// } +// else +// { +// Distance+=Inp.getbits32()>>(32-DBits); +// Inp.addbits(DBits); +// } +// } +// +// if (Distance>0x100) +// { +// Length++; +// if (Distance>0x2000) +// { +// Length++; +// if (Distance>0x40000) +// Length++; +// } +// } +// +// InsertOldDist(Distance); +// LastLength=Length; +// if (Fragmented) +// FragWindow.CopyString(Length,Distance,UnpPtr,MaxWinMask); +// else +// CopyString(Length,Distance); +// continue; +// } +// if (MainSlot==256) +// { +// UnpackFilter Filter; +// if (!ReadFilter(Inp,Filter) || !AddFilter(Filter)) +// break; +// continue; +// } +// if (MainSlot==257) +// { +// if (LastLength!=0) +// if (Fragmented) +// FragWindow.CopyString(LastLength,OldDist[0],UnpPtr,MaxWinMask); +// else +// CopyString(LastLength,OldDist[0]); +// continue; +// } +// if (MainSlot<262) +// { +// uint DistNum=MainSlot-258; +// uint Distance=OldDist[DistNum]; +// for (uint I=DistNum;I>0;I--) +// OldDist[I]=OldDist[I-1]; +// OldDist[0]=Distance; +// +// uint LengthSlot=DecodeNumber(Inp,&BlockTables.RD); +// uint Length=SlotToLength(Inp,LengthSlot); +// LastLength=Length; +// if (Fragmented) +// FragWindow.CopyString(Length,Distance,UnpPtr,MaxWinMask); +// else +// CopyString(Length,Distance); +// continue; +// } +// } +// UnpWriteBuf(); +//} +// +// +//uint ReadFilterData(BitInput &Inp) +//{ +// uint ByteCount=(Inp.fgetbits()>>14)+1; +// Inp.addbits(2); +// +// uint Data=0; +// for (uint I=0;I>8)<<(I*8); +// Inp.addbits(8); +// } +// return Data; +//} +// +// +//bool ReadFilter(BitInput &Inp,UnpackFilter &Filter) +//{ +// if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-16) +// if (!UnpReadBuf()) +// return false; +// +// Filter.BlockStart=ReadFilterData(Inp); +// Filter.BlockLength=ReadFilterData(Inp); +// if (Filter.BlockLength>MAX_FILTER_BLOCK_SIZE) +// Filter.BlockLength=0; +// +// Filter.Type=Inp.fgetbits()>>13; +// Inp.faddbits(3); +// +// if (Filter.Type==FILTER_DELTA) +// { +// Filter.Channels=(Inp.fgetbits()>>11)+1; +// Inp.faddbits(5); +// } +// +// return true; +//} +// +// +//bool AddFilter(UnpackFilter &Filter) +//{ +// if (Filters.Size()>=MAX_UNPACK_FILTERS) +// { +// UnpWriteBuf(); // Write data, apply and flush filters. +// if (Filters.Size()>=MAX_UNPACK_FILTERS) +// InitFilters(); // Still too many filters, prevent excessive memory use. +// } +// +// // If distance to filter start is that large that due to circular dictionary +// // mode now it points to old not written yet data, then we set 'NextWindow' +// // flag and process this filter only after processing that older data. +// Filter.NextWindow=WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<=Filter.BlockStart; +// +// Filter.BlockStart=uint((Filter.BlockStart+UnpPtr)&MaxWinMask); +// Filters.Push(Filter); +// return true; +//} +// +// +//bool UnpReadBuf() +//{ +// int DataSize=ReadTop-Inp.InAddr; // Data left to process. +// if (DataSize<0) +// return false; +// BlockHeader.BlockSize-=Inp.InAddr-BlockHeader.BlockStart; +// if (Inp.InAddr>BitInput::MAX_SIZE/2) +// { +// // If we already processed more than half of buffer, let's move +// // remaining data into beginning to free more space for new data +// // and ensure that calling function does not cross the buffer border +// // even if we did not read anything here. Also it ensures that read size +// // is not less than CRYPT_BLOCK_SIZE, so we can align it without risk +// // to make it zero. +// if (DataSize>0) +// memmove(Inp.InBuf,Inp.InBuf+Inp.InAddr,DataSize); +// Inp.InAddr=0; +// ReadTop=DataSize; +// } +// else +// DataSize=ReadTop; +// int ReadCode=0; +// if (BitInput::MAX_SIZE!=DataSize) +// ReadCode=UnpIO->UnpRead(Inp.InBuf+DataSize,BitInput::MAX_SIZE-DataSize); +// if (ReadCode>0) // Can be also -1. +// ReadTop+=ReadCode; +// ReadBorder=ReadTop-30; +// BlockHeader.BlockStart=Inp.InAddr; +// if (BlockHeader.BlockSize!=-1) // '-1' means not defined yet. +// { +// // We may need to quit from main extraction loop and read new block header +// // and trees earlier than data in input buffer ends. +// ReadBorder=Min(ReadBorder,BlockHeader.BlockStart+BlockHeader.BlockSize-1); +// } +// return ReadCode!=-1; +//} +// +// +//void UnpWriteBuf() +//{ +// size_t WrittenBorder=WrPtr; +// size_t FullWriteSize=(UnpPtr-WrittenBorder)&MaxWinMask; +// size_t WriteSizeLeft=FullWriteSize; +// bool NotAllFiltersProcessed=false; +// for (size_t I=0;IType==FILTER_NONE) +// continue; +// if (flt->NextWindow) +// { +// // Here we skip filters which have block start in current data range +// // due to address wrap around in circular dictionary, but actually +// // belong to next dictionary block. If such filter start position +// // is included to current write range, then we reset 'NextWindow' flag. +// // In fact we can reset it even without such check, because current +// // implementation seems to guarantee 'NextWindow' flag reset after +// // buffer writing for all existing filters. But let's keep this check +// // just in case. Compressor guarantees that distance between +// // filter block start and filter storing position cannot exceed +// // the dictionary size. So if we covered the filter block start with +// // our write here, we can safely assume that filter is applicable +// // to next block on no further wrap arounds is possible. +// if (((flt->BlockStart-WrPtr)&MaxWinMask)<=FullWriteSize) +// flt->NextWindow=false; +// continue; +// } +// uint BlockStart=flt->BlockStart; +// uint BlockLength=flt->BlockLength; +// if (((BlockStart-WrittenBorder)&MaxWinMask)0) // We set it to 0 also for invalid filters. +// { +// uint BlockEnd=(BlockStart+BlockLength)&MaxWinMask; +// +// FilterSrcMemory.Alloc(BlockLength); +// byte *Mem=&FilterSrcMemory[0]; +// if (BlockStartUnpWrite(OutMem,BlockLength); +// +// UnpSomeRead=true; +// WrittenFileSize+=BlockLength; +// WrittenBorder=BlockEnd; +// WriteSizeLeft=(UnpPtr-WrittenBorder)&MaxWinMask; +// } +// } +// else +// { +// // Current filter intersects the window write border, so we adjust +// // the window border to process this filter next time, not now. +// WrPtr=WrittenBorder; +// +// // Since Filter start position can only increase, we quit processing +// // all following filters for this data block and reset 'NextWindow' +// // flag for them. +// for (size_t J=I;JType!=FILTER_NONE) +// flt->NextWindow=false; +// } +// +// // Do not write data left after current filter now. +// NotAllFiltersProcessed=true; +// break; +// } +// } +// } +// +// // Remove processed filters from queue. +// size_t EmptyCount=0; +// for (size_t I=0;I0) +// Filters[I-EmptyCount]=Filters[I]; +// if (Filters[I].Type==FILTER_NONE) +// EmptyCount++; +// } +// if (EmptyCount>0) +// Filters.Alloc(Filters.Size()-EmptyCount); +// +// if (!NotAllFiltersProcessed) // Only if all filters are processed. +// { +// // Write data left after last filter. +// UnpWriteArea(WrittenBorder,UnpPtr); +// WrPtr=UnpPtr; +// } +// +// // We prefer to write data in blocks not exceeding UNPACK_MAX_WRITE +// // instead of potentially huge MaxWinSize blocks. It also allows us +// // to keep the size of Filters array reasonable. +// WriteBorder=(UnpPtr+Min(MaxWinSize,UNPACK_MAX_WRITE))&MaxWinMask; +// +// // Choose the nearest among WriteBorder and WrPtr actual written border. +// // If border is equal to UnpPtr, it means that we have MaxWinSize data ahead. +// if (WriteBorder==UnpPtr || +// WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<((WriteBorder-UnpPtr)&MaxWinMask)) +// WriteBorder=WrPtr; +//} +// +// +//byte* ApplyFilter(byte *Data,uint DataSize,UnpackFilter *Flt) +//{ +// byte *SrcData=Data; +// switch(Flt->Type) +// { +// case FILTER_E8: +// case FILTER_E8E9: +// { +// uint FileOffset=(uint)WrittenFileSize; +// +// const uint FileSize=0x1000000; +// byte CmpByte2=Flt->Type==FILTER_E8E9 ? 0xe9:0xe8; +// // DataSize is unsigned, so we use "CurPos+4" and not "DataSize-4" +// // to avoid overflow for DataSize<4. +// for (uint CurPos=0;CurPos+4=0 +// RawPut4(Addr+FileSize,Data); +// } +// else +// if (((Addr-FileSize) & 0x80000000)!=0) // Addr>8); +// D[2]=(byte)(Offset>>16); +// } +// } +// } +// return SrcData; +// case FILTER_DELTA: +// { +// // Unlike RAR3, we do not need to reject excessive channel +// // values here, since RAR5 uses only 5 bits to store channel. +// uint Channels=Flt->Channels,SrcPos=0; +// +// FilterDstMemory.Alloc(DataSize); +// byte *DstData=&FilterDstMemory[0]; +// +// // Bytes from same channels are grouped to continual data blocks, +// // so we need to place them back to their interleaving positions. +// for (uint CurChannel=0;CurChannel0) +// { +// size_t BlockSize=FragWindow.GetBlockSize(StartPtr,SizeToWrite); +// UnpWriteData(&FragWindow[StartPtr],BlockSize); +// SizeToWrite-=BlockSize; +// StartPtr=(StartPtr+BlockSize) & MaxWinMask; +// } +// } +// else +// if (EndPtr=DestUnpSize) +// return; +// size_t WriteSize=Size; +// int64 LeftToWrite=DestUnpSize-WrittenFileSize; +// if ((int64)WriteSize>LeftToWrite) +// WriteSize=(size_t)LeftToWrite; +// UnpIO->UnpWrite(Data,WriteSize); +// WrittenFileSize+=Size; +//} +// +// +//void UnpInitData50(bool Solid) +//{ +// if (!Solid) +// TablesRead5=false; +//} +// +// +//bool ReadBlockHeader(BitInput &Inp,UnpackBlockHeader &Header) +//{ +// Header.HeaderSize=0; +// +// if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-7) +// if (!UnpReadBuf()) +// return false; +// Inp.faddbits((8-Inp.InBit)&7); +// +// byte BlockFlags=Inp.fgetbits()>>8; +// Inp.faddbits(8); +// uint ByteCount=((BlockFlags>>3)&3)+1; // Block size byte count. +// +// if (ByteCount==4) +// return false; +// +// Header.HeaderSize=2+ByteCount; +// +// Header.BlockBitSize=(BlockFlags&7)+1; +// +// byte SavedCheckSum=Inp.fgetbits()>>8; +// Inp.faddbits(8); +// +// int BlockSize=0; +// for (uint I=0;I>8)<<(I*8); +// Inp.addbits(8); +// } +// +// Header.BlockSize=BlockSize; +// byte CheckSum=byte(0x5a^BlockFlags^BlockSize^(BlockSize>>8)^(BlockSize>>16)); +// if (CheckSum!=SavedCheckSum) +// return false; +// +// Header.BlockStart=Inp.InAddr; +// ReadBorder=Min(ReadBorder,Header.BlockStart+Header.BlockSize-1); +// +// Header.LastBlockInFile=(BlockFlags & 0x40)!=0; +// Header.TablePresent=(BlockFlags & 0x80)!=0; +// return true; +//} +// +// +//bool ReadTables(BitInput &Inp,UnpackBlockHeader &Header,UnpackBlockTables &Tables) +//{ +// if (!Header.TablePresent) +// return true; +// +// if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-25) +// if (!UnpReadBuf()) +// return false; +// +// byte BitLength[BC]; +// for (uint I=0;I> 12); +// Inp.faddbits(4); +// if (Length==15) +// { +// uint ZeroCount=(byte)(Inp.fgetbits() >> 12); +// Inp.faddbits(4); +// if (ZeroCount==0) +// BitLength[I]=15; +// else +// { +// ZeroCount+=2; +// while (ZeroCount-- > 0 && IReadTop-5) +// if (!UnpReadBuf()) +// return false; +// uint Number=DecodeNumber(Inp,&Tables.BD); +// if (Number<16) +// { +// Table[I]=Number; +// I++; +// } +// else +// if (Number<18) +// { +// uint N; +// if (Number==16) +// { +// N=(Inp.fgetbits() >> 13)+3; +// Inp.faddbits(3); +// } +// else +// { +// N=(Inp.fgetbits() >> 9)+11; +// Inp.faddbits(7); +// } +// if (I==0) +// { +// // We cannot have "repeat previous" code at the first position. +// // Multiple such codes would shift Inp position without changing I, +// // which can lead to reading beyond of Inp boundary in mutithreading +// // mode, where Inp.ExternalBuffer disables bounds check and we just +// // reserve a lot of buffer space to not need such check normally. +// return false; +// } +// else +// while (N-- > 0 && I> 13)+3; +// Inp.faddbits(3); +// } +// else +// { +// N=(Inp.fgetbits() >> 9)+11; +// Inp.faddbits(7); +// } +// while (N-- > 0 && IReadTop) +// return false; +// MakeDecodeTables(&Table[0],&Tables.LD,NC); +// MakeDecodeTables(&Table[NC],&Tables.DD,DC); +// MakeDecodeTables(&Table[NC+DC],&Tables.LDD,LDC); +// MakeDecodeTables(&Table[NC+DC+LDC],&Tables.RD,RC); +// return true; +//} +// +// +//void InitFilters() +//{ +// Filters.SoftReset(); +//} +// +// } +//} From 0e57537167504f03523c32b953e65ff965eade4c Mon Sep 17 00:00:00 2001 From: coderb Date: Sun, 17 Dec 2017 11:36:33 -0500 Subject: [PATCH 15/61] rar5: unpack wip --- src/SharpCompress/Compressors/Rar/Decode/PackDef.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/Decode/PackDef.cs b/src/SharpCompress/Compressors/Rar/Decode/PackDef.cs index 15ecdf13..a9e77f12 100644 --- a/src/SharpCompress/Compressors/Rar/Decode/PackDef.cs +++ b/src/SharpCompress/Compressors/Rar/Decode/PackDef.cs @@ -1,12 +1,11 @@ namespace SharpCompress.Compressors.Rar.Decode { - internal class PackDef + internal static class PackDef { public const int CODEBUFSIZE = 0x4000; public const int MAXWINSIZE = 0x400000; - //UPGRADE_NOTE: Final was removed from the declaration of 'MAXWINMASK '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - public static readonly int MAXWINMASK = (MAXWINSIZE - 1); + public const int MAXWINMASK = (MAXWINSIZE - 1); public const int LOW_DIST_REP_COUNT = 16; @@ -15,8 +14,7 @@ namespace SharpCompress.Compressors.Rar.Decode public const int LDC = 17; public const int RC = 28; - //UPGRADE_NOTE: Final was removed from the declaration of 'HUFF_TABLE_SIZE '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - public static readonly int HUFF_TABLE_SIZE = (NC + DC + RC + LDC); + public const int HUFF_TABLE_SIZE = (NC + DC + RC + LDC); public const int BC = 20; public const int NC20 = 298; /* alphabet = {0, 1, 2, ..., NC - 1} */ From f4506080735688927b9c9de6d029ce27f803f65a Mon Sep 17 00:00:00 2001 From: coderb Date: Sun, 17 Dec 2017 11:46:17 -0500 Subject: [PATCH 16/61] rar5: unpack use partial class instead of inheritance --- src/SharpCompress/Compressors/Rar/Unpack.cs | 5 +- src/SharpCompress/Compressors/Rar/Unpack15.cs | 21 +------- src/SharpCompress/Compressors/Rar/Unpack20.cs | 49 +++---------------- 3 files changed, 12 insertions(+), 63 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/Unpack.cs b/src/SharpCompress/Compressors/Rar/Unpack.cs index f0cd37b5..66e1e40c 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/Unpack.cs @@ -10,7 +10,8 @@ using SharpCompress.Compressors.Rar.VM; namespace SharpCompress.Compressors.Rar { - internal sealed class Unpack : Unpack20 + //!!! internal sealed class Unpack : Unpack20 + internal partial class Unpack : BitInput { public bool FileExtracted { // Duplicate method // private boolean ReadEndOfBlock() throws IOException, RarException @@ -738,7 +739,7 @@ namespace SharpCompress.Compressors.Rar } } - protected internal override void unpInitData(bool solid) + protected internal void unpInitData(bool solid) { if (!solid) { diff --git a/src/SharpCompress/Compressors/Rar/Unpack15.cs b/src/SharpCompress/Compressors/Rar/Unpack15.cs index cc762635..24f098c3 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack15.cs +++ b/src/SharpCompress/Compressors/Rar/Unpack15.cs @@ -3,34 +3,17 @@ * Original author: Edmund Wagner * Creation date: 21.06.2007 * -* Source: $HeadURL$ -* Last changed: $LastChangedDate$ -* * the unrar licence applies to all junrar source and binary distributions * you are not allowed to use this source to re-create the RAR compression algorithm -* -* Here some html entities which can be used for escaping javadoc tags: -* "&": "&" or "&" -* "<": "<" or "<" -* ">": ">" or ">" -* "@": "@" */ using System; using System.IO; using SharpCompress.Compressors.Rar.Decode; -using SharpCompress.Compressors.Rar.VM; namespace SharpCompress.Compressors.Rar { - /// DOCUMENT ME - /// - /// - /// $LastChangedBy$ - /// - /// $LastChangedRevision$ - /// - internal abstract class Unpack15 : BitInput + internal partial class Unpack { protected internal int readBorder; @@ -142,8 +125,6 @@ namespace SharpCompress.Compressors.Rar , 0x9c, 0xb0 }; - protected internal abstract void unpInitData(bool solid); - protected void unpack15(bool solid) { if (suspended) diff --git a/src/SharpCompress/Compressors/Rar/Unpack20.cs b/src/SharpCompress/Compressors/Rar/Unpack20.cs index e9a8dbdd..57b7e721 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack20.cs +++ b/src/SharpCompress/Compressors/Rar/Unpack20.cs @@ -3,17 +3,8 @@ * Original author: Edmund Wagner * Creation date: 21.06.2007 * -* Source: $HeadURL$ -* Last changed: $LastChangedDate$ -* * the unrar licence applies to all junrar source and binary distributions * you are not allowed to use this source to re-create the RAR compression algorithm -* -* Here some html entities which can be used for escaping javadoc tags: -* "&": "&" or "&" -* "<": "<" or "<" -* ">": ">" or ">" -* "@": "@" */ using System; @@ -21,59 +12,38 @@ using SharpCompress.Compressors.Rar.Decode; namespace SharpCompress.Compressors.Rar { - /// DOCUMENT ME - /// - /// - /// $LastChangedBy$ - /// - /// $LastChangedRevision$ - /// - internal abstract class Unpack20 : Unpack15 + internal partial class Unpack { - public Unpack20() - { - InitBlock(); - } + protected readonly internal MultDecode[] MD = new MultDecode[4]; - private void InitBlock() - { - UnpOldTable20 = new byte[PackDef.MC20 * 4]; - } - - protected internal MultDecode[] MD = new MultDecode[4]; - - //UPGRADE_NOTE: The initialization of 'UnpOldTable20' was moved to method 'InitBlock'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'" - protected internal byte[] UnpOldTable20; + protected readonly internal byte[] UnpOldTable20 = new byte[PackDef.MC20 * 4]; protected internal int UnpAudioBlock, UnpChannels, UnpCurChannel, UnpChannelDelta; private readonly AudioVariables[] AudV = new AudioVariables[4]; - protected internal LitDecode LD = new LitDecode(); + protected readonly internal LitDecode LD = new LitDecode(); - protected internal DistDecode DD = new DistDecode(); + protected readonly internal DistDecode DD = new DistDecode(); - protected internal LowDistDecode LDD = new LowDistDecode(); + protected readonly internal LowDistDecode LDD = new LowDistDecode(); - protected internal RepDecode RD = new RepDecode(); + protected readonly internal RepDecode RD = new RepDecode(); - protected internal BitDecode BD = new BitDecode(); + protected readonly internal BitDecode BD = new BitDecode(); - //UPGRADE_NOTE: Final was removed from the declaration of 'LDecode'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" public static readonly int[] LDecode = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224 }; - //UPGRADE_NOTE: Final was removed from the declaration of 'LBits'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" public static readonly byte[] LBits = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5 }; - //UPGRADE_NOTE: Final was removed from the declaration of 'DDecode'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" public static readonly int[] DDecode = { 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, @@ -83,7 +53,6 @@ namespace SharpCompress.Compressors.Rar 917504, 983040 }; - //UPGRADE_NOTE: Final was removed from the declaration of 'DBits'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" public static readonly int[] DBits = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, @@ -91,10 +60,8 @@ namespace SharpCompress.Compressors.Rar , 16, 16, 16, 16, 16, 16, 16 }; - //UPGRADE_NOTE: Final was removed from the declaration of 'SDDecode'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" public static readonly int[] SDDecode = {0, 4, 8, 16, 32, 64, 128, 192}; - //UPGRADE_NOTE: Final was removed from the declaration of 'SDBits'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" public static readonly int[] SDBits = {2, 2, 3, 4, 5, 6, 6, 6}; internal void unpack20(bool solid) From c5b8a444f29c38e8bf004000bfbea29518deddf8 Mon Sep 17 00:00:00 2001 From: coderb Date: Sun, 17 Dec 2017 11:55:33 -0500 Subject: [PATCH 17/61] rar5: unpack tighten up access modifiers --- src/SharpCompress/Compressors/Rar/Unpack.cs | 76 +++++++------------ src/SharpCompress/Compressors/Rar/Unpack15.cs | 54 ++++++------- src/SharpCompress/Compressors/Rar/Unpack20.cs | 32 ++++---- 3 files changed, 71 insertions(+), 91 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/Unpack.cs b/src/SharpCompress/Compressors/Rar/Unpack.cs index 66e1e40c..c6545290 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/Unpack.cs @@ -10,26 +10,26 @@ using SharpCompress.Compressors.Rar.VM; namespace SharpCompress.Compressors.Rar { - //!!! internal sealed class Unpack : Unpack20 - internal partial class Unpack : BitInput + internal sealed partial class Unpack : BitInput { - public bool FileExtracted { // Duplicate method - // private boolean ReadEndOfBlock() throws IOException, RarException - // { - // int BitField = getbits(); - // boolean NewTable, NewFile = false; - // if ((BitField & 0x8000) != 0) { - // NewTable = true; - // addbits(1); - // } else { - // NewFile = true; - // NewTable = (BitField & 0x4000) != 0; - // addbits(2); - // } - // tablesRead = !NewTable; - // return !(NewFile || NewTable && !readTables()); - // } - get; private set; } + // Duplicate method + // private boolean ReadEndOfBlock() throws IOException, RarException + // { + // int BitField = getbits(); + // boolean NewTable, NewFile = false; + // if ((BitField & 0x8000) != 0) { + // NewTable = true; + // addbits(1); + // } else { + // NewFile = true; + // NewTable = (BitField & 0x4000) != 0; + // addbits(2); + // } + // tablesRead = !NewTable; + // return !(NewFile || NewTable && !readTables()); + // } + + public bool FileExtracted { get; private set; } public long DestSize { @@ -57,32 +57,24 @@ namespace SharpCompress.Compressors.Rar public int PpmEscChar { get; set; } - //UPGRADE_NOTE: Final was removed from the declaration of 'ppm '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" private readonly ModelPPM ppm = new ModelPPM(); private readonly RarVM rarVM = new RarVM(); - /* Filters code, one entry per filter */ - //UPGRADE_ISSUE: The following fragment of code could not be parsed and was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1156'" + // Filters code, one entry per filter private readonly List filters = new List(); - /* Filters stack, several entrances of same filter are possible */ - //UPGRADE_ISSUE: The following fragment of code could not be parsed and was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1156'" + // Filters stack, several entrances of same filter are possible private readonly List prgStack = new List(); - /* - * lengths of preceding blocks, one length per filter. Used to reduce size - * required to write block length if lengths are repeating - */ - //UPGRADE_ISSUE: The following fragment of code could not be parsed and was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1156'" + // lengths of preceding blocks, one length per filter. Used to reduce size + // required to write block length if lengths are repeating private readonly List oldFilterLengths = new List(); private int lastFilter; private bool tablesRead; - //UPGRADE_NOTE: The initialization of 'unpOldTable' was moved to method 'InitBlock'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'" - private readonly byte[] unpOldTable = new byte[PackDef.HUFF_TABLE_SIZE]; private BlockTypes unpBlockType; @@ -97,21 +89,11 @@ namespace SharpCompress.Compressors.Rar private int lowDistRepCount; - public static int[] DBitLengthCounts = {4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 14, 0, 12}; + private static readonly int[] DBitLengthCounts = {4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 14, 0, 12}; private FileHeader fileHeader; - public Unpack() - { - window = null; - - //externalWindow = false; - suspended = false; - unpAllBuf = false; - unpSomeRead = false; - } - - public void init(byte[] window) + private void init(byte[] window) { if (window == null) { @@ -739,7 +721,7 @@ namespace SharpCompress.Compressors.Rar } } - protected internal void unpInitData(bool solid) + private void unpInitData(bool solid) { if (!solid) { @@ -788,7 +770,7 @@ namespace SharpCompress.Compressors.Rar else { NewFile = true; - NewTable = (BitField & 0x4000) != 0 ? true : false; + NewTable = (BitField & 0x4000) != 0; AddBits(2); } tablesRead = !NewTable; @@ -942,7 +924,6 @@ namespace SharpCompress.Compressors.Rar AddBits(16); } - //UPGRADE_ISSUE: The following fragment of code could not be parsed and was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1156'" List vmCode = new List(); for (int I = 0; I < Length; I++) { @@ -988,7 +969,6 @@ namespace SharpCompress.Compressors.Rar Length = B1 * 256 + B2; } - //UPGRADE_ISSUE: The following fragment of code could not be parsed and was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1156'" List vmCode = new List(); for (int I = 0; I < Length; I++) { @@ -1241,7 +1221,7 @@ namespace SharpCompress.Compressors.Rar } } - public void cleanUp() + private void cleanUp() { if (ppm != null) { diff --git a/src/SharpCompress/Compressors/Rar/Unpack15.cs b/src/SharpCompress/Compressors/Rar/Unpack15.cs index 24f098c3..4f1d502b 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack15.cs +++ b/src/SharpCompress/Compressors/Rar/Unpack15.cs @@ -15,50 +15,50 @@ namespace SharpCompress.Compressors.Rar { internal partial class Unpack { - protected internal int readBorder; + private int readBorder; - protected internal bool suspended; + private bool suspended; - protected internal bool unpAllBuf; + internal bool unpAllBuf; - //protected internal ComprDataIO unpIO; + //private ComprDataIO unpIO; - protected Stream readStream; - protected Stream writeStream; + private Stream readStream; + private Stream writeStream; - protected internal bool unpSomeRead; + internal bool unpSomeRead; - protected internal int readTop; + private int readTop; - protected internal long destUnpSize; + private long destUnpSize; - protected internal byte[] window; + private byte[] window; - protected internal int[] oldDist = new int[4]; + private readonly int[] oldDist = new int[4]; - protected internal int unpPtr, wrPtr; + private int unpPtr, wrPtr; - protected internal int oldDistPtr; + private int oldDistPtr; - protected internal int[] ChSet = new int[256], + private readonly int[] ChSet = new int[256], ChSetA = new int[256], ChSetB = new int[256], ChSetC = new int[256]; - protected internal int[] Place = new int[256], + private readonly int[] Place = new int[256], PlaceA = new int[256], PlaceB = new int[256], PlaceC = new int[256]; - protected internal int[] NToPl = new int[256], NToPlB = new int[256], NToPlC = new int[256]; + private readonly int[] NToPl = new int[256], NToPlB = new int[256], NToPlC = new int[256]; - protected internal int FlagBuf, AvrPlc, AvrPlcB, AvrLn1, AvrLn2, AvrLn3; + private int FlagBuf, AvrPlc, AvrPlcB, AvrLn1, AvrLn2, AvrLn3; - protected internal int Buf60, NumHuf, StMode, LCount, FlagsCnt; + private int Buf60, NumHuf, StMode, LCount, FlagsCnt; - protected internal int Nhfb, Nlzb, MaxDist3; + private int Nhfb, Nlzb, MaxDist3; - protected internal int lastDist, lastLength; + private int lastDist, lastLength; private const int STARTL1 = 2; @@ -109,23 +109,23 @@ namespace SharpCompress.Compressors.Rar private static readonly int[] PosHf4 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0}; - internal static int[] ShortLen1 = {1, 3, 4, 4, 5, 6, 7, 8, 8, 4, 4, 5, 6, 6, 4, 0}; + private static readonly int[] ShortLen1 = {1, 3, 4, 4, 5, 6, 7, 8, 8, 4, 4, 5, 6, 6, 4, 0}; - internal static int[] ShortXor1 = + private static readonly int[] ShortXor1 = { 0, 0xa0, 0xd0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff, 0xc0, 0x80, 0x90, 0x98 , 0x9c, 0xb0 }; - internal static int[] ShortLen2 = {2, 3, 3, 3, 4, 4, 5, 6, 6, 4, 4, 5, 6, 6, 4, 0}; + private static readonly int[] ShortLen2 = {2, 3, 3, 3, 4, 4, 5, 6, 6, 4, 4, 5, 6, 6, 4, 0}; - internal static int[] ShortXor2 = + private static readonly int[] ShortXor2 = { 0, 0x40, 0x60, 0xa0, 0xd0, 0xe0, 0xf0, 0xf8, 0xfc, 0xc0, 0x80, 0x90, 0x98 , 0x9c, 0xb0 }; - protected void unpack15(bool solid) + private void unpack15(bool solid) { if (suspended) { @@ -223,7 +223,7 @@ namespace SharpCompress.Compressors.Rar oldUnpWriteBuf(); } - protected bool unpReadBuf() + private bool unpReadBuf() { int dataSize = readTop - inAddr; if (dataSize < 0) @@ -699,7 +699,7 @@ namespace SharpCompress.Compressors.Rar return ((Utility.URShift((Num - (I != 0 ? DecTab[I - 1] : 0)), (16 - StartPos))) + PosTab[StartPos]); } - protected void oldUnpWriteBuf() + private void oldUnpWriteBuf() { if (unpPtr != wrPtr) { diff --git a/src/SharpCompress/Compressors/Rar/Unpack20.cs b/src/SharpCompress/Compressors/Rar/Unpack20.cs index 57b7e721..f4c43af1 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack20.cs +++ b/src/SharpCompress/Compressors/Rar/Unpack20.cs @@ -14,37 +14,37 @@ namespace SharpCompress.Compressors.Rar { internal partial class Unpack { - protected readonly internal MultDecode[] MD = new MultDecode[4]; + private readonly MultDecode[] MD = new MultDecode[4]; - protected readonly internal byte[] UnpOldTable20 = new byte[PackDef.MC20 * 4]; + private readonly byte[] UnpOldTable20 = new byte[PackDef.MC20 * 4]; - protected internal int UnpAudioBlock, UnpChannels, UnpCurChannel, UnpChannelDelta; + private int UnpAudioBlock, UnpChannels, UnpCurChannel, UnpChannelDelta; private readonly AudioVariables[] AudV = new AudioVariables[4]; - protected readonly internal LitDecode LD = new LitDecode(); + private readonly LitDecode LD = new LitDecode(); - protected readonly internal DistDecode DD = new DistDecode(); + private readonly DistDecode DD = new DistDecode(); - protected readonly internal LowDistDecode LDD = new LowDistDecode(); + private readonly LowDistDecode LDD = new LowDistDecode(); - protected readonly internal RepDecode RD = new RepDecode(); + private readonly RepDecode RD = new RepDecode(); - protected readonly internal BitDecode BD = new BitDecode(); + private readonly BitDecode BD = new BitDecode(); - public static readonly int[] LDecode = + private static readonly int[] LDecode = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224 }; - public static readonly byte[] LBits = + private static readonly byte[] LBits = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5 }; - public static readonly int[] DDecode = + private static readonly int[] DDecode = { 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, @@ -53,18 +53,18 @@ namespace SharpCompress.Compressors.Rar 917504, 983040 }; - public static readonly int[] DBits = + private static readonly int[] DBits = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 16, 16, 16, 16, 16 , 16, 16, 16, 16, 16, 16, 16 }; - public static readonly int[] SDDecode = {0, 4, 8, 16, 32, 64, 128, 192}; + private static readonly int[] SDDecode = {0, 4, 8, 16, 32, 64, 128, 192}; - public static readonly int[] SDBits = {2, 2, 3, 4, 5, 6, 6, 6}; + private static readonly int[] SDBits = {2, 2, 3, 4, 5, 6, 6, 6}; - internal void unpack20(bool solid) + private void unpack20(bool solid) { int Bits; @@ -357,7 +357,7 @@ namespace SharpCompress.Compressors.Rar return (true); } - protected void unpInitData20(bool Solid) + private void unpInitData20(bool Solid) { if (!Solid) { From a2369fb53120a7d70e306348c1586e8d8e60e0a3 Mon Sep 17 00:00:00 2001 From: coderb Date: Sun, 17 Dec 2017 12:08:09 -0500 Subject: [PATCH 18/61] rar5: uppercase method names --- .../Compressors/Rar/RarStream.cs | 4 +- src/SharpCompress/Compressors/Rar/Unpack.cs | 103 ++++++++---------- src/SharpCompress/Compressors/Rar/Unpack15.cs | 2 +- src/SharpCompress/Compressors/Rar/Unpack20.cs | 2 +- src/SharpCompress/Compressors/Rar/Unpack50.cs | 10 +- 5 files changed, 52 insertions(+), 69 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/RarStream.cs b/src/SharpCompress/Compressors/Rar/RarStream.cs index c36f261c..6fdd49cd 100644 --- a/src/SharpCompress/Compressors/Rar/RarStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarStream.cs @@ -28,7 +28,7 @@ namespace SharpCompress.Compressors.Rar this.fileHeader = fileHeader; this.readStream = readStream; fetch = true; - unpack.doUnpack(fileHeader, readStream, this); + unpack.DoUnpack(fileHeader, readStream, this); fetch = false; } @@ -76,7 +76,7 @@ namespace SharpCompress.Compressors.Rar outOffset = offset; outCount = count; fetch = true; - unpack.doUnpack(); + unpack.DoUnpack(); fetch = false; } return outTotal; diff --git a/src/SharpCompress/Compressors/Rar/Unpack.cs b/src/SharpCompress/Compressors/Rar/Unpack.cs index c6545290..dc1cb17c 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/Unpack.cs @@ -12,23 +12,6 @@ namespace SharpCompress.Compressors.Rar { internal sealed partial class Unpack : BitInput { - // Duplicate method - // private boolean ReadEndOfBlock() throws IOException, RarException - // { - // int BitField = getbits(); - // boolean NewTable, NewFile = false; - // if ((BitField & 0x8000) != 0) { - // NewTable = true; - // addbits(1); - // } else { - // NewFile = true; - // NewTable = (BitField & 0x4000) != 0; - // addbits(2); - // } - // tablesRead = !NewTable; - // return !(NewFile || NewTable && !readTables()); - // } - public bool FileExtracted { get; private set; } public long DestSize @@ -93,7 +76,7 @@ namespace SharpCompress.Compressors.Rar private FileHeader fileHeader; - private void init(byte[] window) + private void Init(byte[] window) { if (window == null) { @@ -106,10 +89,10 @@ namespace SharpCompress.Compressors.Rar //externalWindow = true; } inAddr = 0; - unpInitData(false); + UnpInitData(false); } - public void doUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream) + public void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream) { destUnpSize = fileHeader.UncompressedSize; this.fileHeader = fileHeader; @@ -117,17 +100,17 @@ namespace SharpCompress.Compressors.Rar this.readStream = readStream; if (!fileHeader.IsSolid) { - init(null); + Init(null); } suspended = false; - doUnpack(); + DoUnpack(); } - public void doUnpack() + public void DoUnpack() { if (fileHeader.CompressionMethod == 0) { - unstoreFile(); + UnstoreFile(); return; } var solid = fileHeader.IsSolid; @@ -144,7 +127,7 @@ namespace SharpCompress.Compressors.Rar case 29: // rar 3.x compression case 36: // alternative hash - unpack29(solid); + Unpack29(solid); break; // case 50: // rar 5.x compression @@ -156,7 +139,7 @@ namespace SharpCompress.Compressors.Rar } } - private void unstoreFile() + private void UnstoreFile() { byte[] buffer = new byte[0x10000]; while (true) @@ -179,7 +162,7 @@ namespace SharpCompress.Compressors.Rar } } - private void unpack29(bool solid) + private void Unpack29(bool solid) { int[] DDecode = new int[PackDef.DC]; byte[] DBits = new byte[PackDef.DC]; @@ -204,12 +187,12 @@ namespace SharpCompress.Compressors.Rar if (!suspended) { - unpInitData(solid); + UnpInitData(solid); if (!unpReadBuf()) { return; } - if ((!solid || !tablesRead) && !readTables()) + if ((!solid || !tablesRead) && !ReadTables()) { return; } @@ -260,7 +243,7 @@ namespace SharpCompress.Compressors.Rar int NextCh = ppm.decodeChar(); if (NextCh == 0) { - if (!readTables()) + if (!ReadTables()) { break; } @@ -272,7 +255,7 @@ namespace SharpCompress.Compressors.Rar } if (NextCh == 3) { - if (!readVMCodePPM()) + if (!ReadVMCodePPM()) { break; } @@ -307,7 +290,7 @@ namespace SharpCompress.Compressors.Rar { break; } - copyString(Length + 32, Distance + 2); + CopyString(Length + 32, Distance + 2); continue; } if (NextCh == 5) @@ -317,7 +300,7 @@ namespace SharpCompress.Compressors.Rar { break; } - copyString(Length + 4, 1); + CopyString(Length + 4, 1); continue; } } @@ -387,15 +370,15 @@ namespace SharpCompress.Compressors.Rar } } - insertOldDist(Distance); - insertLastMatch(Length, Distance); + InsertOldDist(Distance); + InsertLastMatch(Length, Distance); - copyString(Length, Distance); + CopyString(Length, Distance); continue; } if (Number == 256) { - if (!readEndOfBlock()) + if (!ReadEndOfBlock()) { break; } @@ -403,7 +386,7 @@ namespace SharpCompress.Compressors.Rar } if (Number == 257) { - if (!readVMCode()) + if (!ReadVMCode()) { break; } @@ -413,7 +396,7 @@ namespace SharpCompress.Compressors.Rar { if (lastLength != 0) { - copyString(lastLength, lastDist); + CopyString(lastLength, lastDist); } continue; } @@ -434,8 +417,8 @@ namespace SharpCompress.Compressors.Rar Length += Utility.URShift(GetBits(), (16 - Bits)); AddBits(Bits); } - insertLastMatch(Length, Distance); - copyString(Length, Distance); + InsertLastMatch(Length, Distance); + CopyString(Length, Distance); continue; } if (Number < 272) @@ -446,9 +429,9 @@ namespace SharpCompress.Compressors.Rar Distance += Utility.URShift(GetBits(), (16 - Bits)); AddBits(Bits); } - insertOldDist(Distance); - insertLastMatch(2, Distance); - copyString(2, Distance); + InsertOldDist(Distance); + InsertLastMatch(2, Distance); + CopyString(2, Distance); } } UnpWriteBuf(); @@ -681,7 +664,7 @@ namespace SharpCompress.Compressors.Rar destUnpSize -= size; } - private void insertOldDist(int distance) + private void InsertOldDist(int distance) { oldDist[3] = oldDist[2]; oldDist[2] = oldDist[1]; @@ -689,13 +672,13 @@ namespace SharpCompress.Compressors.Rar oldDist[0] = distance; } - private void insertLastMatch(int length, int distance) + private void InsertLastMatch(int length, int distance) { lastDist = distance; lastLength = length; } - private void copyString(int length, int distance) + private void CopyString(int length, int distance) { // System.out.println("copyString(" + length + ", " + distance + ")"); @@ -721,7 +704,7 @@ namespace SharpCompress.Compressors.Rar } } - private void unpInitData(bool solid) + private void UnpInitData(bool solid) { if (!solid) { @@ -738,7 +721,7 @@ namespace SharpCompress.Compressors.Rar wrPtr = 0; PpmEscChar = 2; - initFilters(); + InitFilters(); } InitBitInput(); ppmError = false; @@ -748,7 +731,7 @@ namespace SharpCompress.Compressors.Rar unpInitData20(solid); } - private void initFilters() + private void InitFilters() { oldFilterLengths.Clear(); lastFilter = 0; @@ -758,7 +741,7 @@ namespace SharpCompress.Compressors.Rar prgStack.Clear(); } - private bool readEndOfBlock() + private bool ReadEndOfBlock() { int BitField = GetBits(); bool NewTable, NewFile = false; @@ -774,10 +757,10 @@ namespace SharpCompress.Compressors.Rar AddBits(2); } tablesRead = !NewTable; - return !(NewFile || NewTable && !readTables()); + return !(NewFile || NewTable && !ReadTables()); } - private bool readTables() + private bool ReadTables() { byte[] bitLength = new byte[PackDef.BC]; @@ -908,7 +891,7 @@ namespace SharpCompress.Compressors.Rar return (true); } - private bool readVMCode() + private bool ReadVMCode() { int FirstByte = GetBits() >> 8; AddBits(8); @@ -934,10 +917,10 @@ namespace SharpCompress.Compressors.Rar vmCode.Add((byte)(GetBits() >> 8)); AddBits(8); } - return (addVMCode(FirstByte, vmCode, Length)); + return (AddVMCode(FirstByte, vmCode, Length)); } - private bool readVMCodePPM() + private bool ReadVMCodePPM() { int FirstByte = ppm.decodeChar(); if (FirstByte == -1) @@ -979,10 +962,10 @@ namespace SharpCompress.Compressors.Rar } vmCode.Add((byte)Ch); // VMCode[I]=Ch; } - return (addVMCode(FirstByte, vmCode, Length)); + return (AddVMCode(FirstByte, vmCode, Length)); } - private bool addVMCode(int firstByte, List vmCode, int length) + private bool AddVMCode(int firstByte, List vmCode, int length) { BitInput Inp = new BitInput(); Inp.InitBitInput(); @@ -1000,7 +983,7 @@ namespace SharpCompress.Compressors.Rar FiltPos = RarVM.ReadData(Inp); if (FiltPos == 0) { - initFilters(); + InitFilters(); } else { @@ -1221,7 +1204,7 @@ namespace SharpCompress.Compressors.Rar } } - private void cleanUp() + private void CleanUp() { if (ppm != null) { diff --git a/src/SharpCompress/Compressors/Rar/Unpack15.cs b/src/SharpCompress/Compressors/Rar/Unpack15.cs index 4f1d502b..c0158746 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack15.cs +++ b/src/SharpCompress/Compressors/Rar/Unpack15.cs @@ -133,7 +133,7 @@ namespace SharpCompress.Compressors.Rar } else { - unpInitData(solid); + UnpInitData(solid); oldUnpInitData(solid); unpReadBuf(); if (!solid) diff --git a/src/SharpCompress/Compressors/Rar/Unpack20.cs b/src/SharpCompress/Compressors/Rar/Unpack20.cs index f4c43af1..12eeeed2 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack20.cs +++ b/src/SharpCompress/Compressors/Rar/Unpack20.cs @@ -74,7 +74,7 @@ namespace SharpCompress.Compressors.Rar } else { - unpInitData(solid); + UnpInitData(solid); if (!unpReadBuf()) { return; diff --git a/src/SharpCompress/Compressors/Rar/Unpack50.cs b/src/SharpCompress/Compressors/Rar/Unpack50.cs index 85614d9c..c7c55cc4 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack50.cs +++ b/src/SharpCompress/Compressors/Rar/Unpack50.cs @@ -6,15 +6,15 @@ // //namespace SharpCompress.Compressors.Rar //{ -// internal sealed class Unpack50 +// internal partial class Unpack // { // // public void Unpack5(bool Solid) { -// FileExtracted=true; +// FileExtracted=true; // -// if (!Suspended) -// { -// UnpInitData(Solid); +// if (!Suspended) +// { +// unpInitData(Solid); // if (!UnpReadBuf()) // return; // From cc175da0d8b9ab159b24ad8d706424d1b5009815 Mon Sep 17 00:00:00 2001 From: coderb Date: Sun, 17 Dec 2017 17:08:53 -0500 Subject: [PATCH 19/61] rar5: unpack wip --- .../Compressors/Rar/Decode/FilterType.cs | 16 +- .../Compressors/Rar/Decode/PackDef.cs | 42 +- src/SharpCompress/Compressors/Rar/Unpack.cs | 60 +- src/SharpCompress/Compressors/Rar/Unpack50.cs | 1498 +++++++++-------- .../Compressors/Rar/UnpackFilter.cs | 7 + .../Compressors/Rar/UnpackUtility.cs | 5 + .../Compressors/Rar/VM/BitInput.cs | 25 +- 7 files changed, 955 insertions(+), 698 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/Decode/FilterType.cs b/src/SharpCompress/Compressors/Rar/Decode/FilterType.cs index 24e2b0b5..cb64f783 100644 --- a/src/SharpCompress/Compressors/Rar/Decode/FilterType.cs +++ b/src/SharpCompress/Compressors/Rar/Decode/FilterType.cs @@ -1,16 +1,10 @@ namespace SharpCompress.Compressors.Rar.Decode { - internal enum FilterType + internal enum FilterType : byte { - FILTER_NONE, - FILTER_PPM, - FILTER_E8, - FILTER_E8E9, - FILTER_UPCASETOLOW, - FILTER_AUDIO, - FILTER_RGB, - FILTER_DELTA, - FILTER_ITANIUM, - FILTER_E8E9V2 + // These values must not be changed, because we use them directly + // in RAR5 compression and decompression code. + FILTER_DELTA=0, FILTER_E8, FILTER_E8E9, FILTER_ARM, + FILTER_AUDIO, FILTER_RGB, FILTER_ITANIUM, FILTER_PPM, FILTER_NONE } } \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/Decode/PackDef.cs b/src/SharpCompress/Compressors/Rar/Decode/PackDef.cs index a9e77f12..472e7e5b 100644 --- a/src/SharpCompress/Compressors/Rar/Decode/PackDef.cs +++ b/src/SharpCompress/Compressors/Rar/Decode/PackDef.cs @@ -2,25 +2,47 @@ namespace SharpCompress.Compressors.Rar.Decode { internal static class PackDef { - public const int CODEBUFSIZE = 0x4000; + // 20171217 NOTE: these contants are gone from unrar src code + // seems to be more dynamic public const int MAXWINSIZE = 0x400000; + public const int MAXWINMASK = MAXWINSIZE - 1; - public const int MAXWINMASK = (MAXWINSIZE - 1); - + public const uint MAX_LZ_MATCH = 0x1001; + public const uint MAX3_LZ_MATCH = 0x101; // Maximum match length for RAR v3. public const int LOW_DIST_REP_COUNT = 16; public const int NC = 299; /* alphabet = {0, 1, 2, ..., NC - 1} */ public const int DC = 60; public const int LDC = 17; public const int RC = 28; +// 20171217: NOTE: these constants seem to have been updated in the unrar src code +// at some unknown point. updating causes decompression failure, not sure why. +// public const int NC = 306; /* alphabet = {0, 1, 2, ..., NC - 1} */ +// public const int DC = 64; +// public const int LDC = 16; +// public const int RC = 44; + public const int HUFF_TABLE_SIZE = NC + DC + RC + LDC; + public const int BC = 20; - public const int HUFF_TABLE_SIZE = (NC + DC + RC + LDC); - public const int BC = 20; + public const uint NC30 = 299; /* alphabet = {0, 1, 2, ..., NC - 1} */ + public const uint DC30 = 60; + public const uint LDC30 = 17; + public const uint RC30 = 28; + public const uint BC30 = 20; + public const uint HUFF_TABLE_SIZE30 = NC30 + DC30 + RC30 + LDC30; - public const int NC20 = 298; /* alphabet = {0, 1, 2, ..., NC - 1} */ - public const int DC20 = 48; - public const int RC20 = 28; - public const int BC20 = 19; - public const int MC20 = 257; + public const int NC20 = 298; /* alphabet = {0, 1, 2, ..., NC - 1} */ + public const int DC20 = 48; + public const int RC20 = 28; + public const int BC20 = 19; + public const int MC20 = 257; + + // Largest alphabet size among all values listed above. + public const uint LARGEST_TABLE_SIZE = 306; + + //public enum { + // CODE_HUFFMAN, CODE_LZ, CODE_REPEATLZ, CODE_CACHELZ, CODE_STARTFILE, + // CODE_ENDFILE, CODE_FILTER, CODE_FILTERDATA + //} } } \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/Unpack.cs b/src/SharpCompress/Compressors/Rar/Unpack.cs index dc1cb17c..b5c9db78 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/Unpack.cs @@ -12,6 +12,14 @@ namespace SharpCompress.Compressors.Rar { internal sealed partial class Unpack : BitInput { + private readonly BitInput Inp; + + public Unpack() { + // to ease in porting Unpack50.cs + this.Inp = this; + } + + public bool FileExtracted { get; private set; } public long DestSize @@ -24,7 +32,10 @@ namespace SharpCompress.Compressors.Rar } } - public bool Suspended { set => suspended = value; } + public bool Suspended { + get => this.suspended; + set => this.suspended = value; + } public int Char { @@ -130,9 +141,9 @@ namespace SharpCompress.Compressors.Rar Unpack29(solid); break; -// case 50: // rar 5.x compression -// unpack50(solid); -// break; + case 50: // rar 5.x compression + Unpack5(solid); + break; default: throw new InvalidFormatException("unknown rar compression version " + this.fileHeader.CompressionAlgorithm); @@ -664,6 +675,11 @@ namespace SharpCompress.Compressors.Rar destUnpSize -= size; } + private void InsertOldDist(uint distance) { + // TODO uint + InsertOldDist((int)distance); + } + private void InsertOldDist(int distance) { oldDist[3] = oldDist[2]; @@ -678,6 +694,11 @@ namespace SharpCompress.Compressors.Rar lastLength = length; } + private void CopyString(uint length, uint distance) { + // TODO uint + CopyString((int)length, (int)distance) ; + } + private void CopyString(int length, int distance) { // System.out.println("copyString(" + length + ", " + distance + ")"); @@ -720,6 +741,7 @@ namespace SharpCompress.Compressors.Rar unpPtr = 0; wrPtr = 0; PpmEscChar = 2; +WriteBorder=Math.Min(MaxWinSize,UNPACK_MAX_WRITE)&MaxWinMask; InitFilters(); } @@ -731,6 +753,36 @@ namespace SharpCompress.Compressors.Rar unpInitData20(solid); } +//void Unpack::UnpInitData(bool Solid) +//{ +// if (!Solid) +// { +// memset(OldDist,0,sizeof(OldDist)); +// OldDistPtr=0; +// LastDist=LastLength=0; +//// memset(Window,0,MaxWinSize); +// memset(&BlockTables,0,sizeof(BlockTables)); +// UnpPtr=WrPtr=0; +// WriteBorder=Min(MaxWinSize,UNPACK_MAX_WRITE)&MaxWinMask; +// } +// // Filters never share several solid files, so we can safely reset them +// // even in solid archive. +// InitFilters(); +// +// Inp.InitBitInput(); +// WrittenFileSize=0; +// ReadTop=0; +// ReadBorder=0; +// +// memset(&BlockHeader,0,sizeof(BlockHeader)); +// BlockHeader.BlockSize=-1; // '-1' means not defined yet. +//#ifndef SFX_MODULE +// UnpInitData20(Solid); +//#endif +// UnpInitData30(Solid); +// UnpInitData50(Solid); +//} + private void InitFilters() { oldFilterLengths.Clear(); diff --git a/src/SharpCompress/Compressors/Rar/Unpack50.cs b/src/SharpCompress/Compressors/Rar/Unpack50.cs index c7c55cc4..fe9829b3 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack50.cs +++ b/src/SharpCompress/Compressors/Rar/Unpack50.cs @@ -1,696 +1,852 @@ -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using System.Text; -//using System.Threading.Tasks; +#if true +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SharpCompress.Compressors.Filters; +using SharpCompress.Compressors.Rar.Decode; +using SharpCompress.Compressors.Rar.VM; +using static SharpCompress.Compressors.Rar.Decode.PackDef; + +using size_t=System.UInt32; +using UnpackBlockHeader = SharpCompress.Compressors.Rar.Unpack; + +namespace SharpCompress.Compressors.Rar +{ + internal partial class Unpack + { + + // Maximum allowed number of compressed bits processed in quick mode. + private const int MAX_QUICK_DECODE_BITS = 10; + + // Maximum number of filters per entire data block. Must be at least + // twice more than MAX_PACK_FILTERS to store filters from two data blocks. + private const int MAX_UNPACK_FILTERS = 8192; + + // Maximum number of filters per entire data block for RAR3 unpack. + // Must be at least twice more than v3_MAX_PACK_FILTERS to store filters + // from two data blocks. + private const int MAX3_UNPACK_FILTERS = 8192; + + // Limit maximum number of channels in RAR3 delta filter to some reasonable + // value to prevent too slow processing of corrupt archives with invalid + // channels number. Must be equal or larger than v3_MAX_FILTER_CHANNELS. + // No need to provide it for RAR5, which uses only 5 bits to store channels. + private const int MAX3_UNPACK_CHANNELS = 1024; + + // Maximum size of single filter block. We restrict it to limit memory + // allocation. Must be equal or larger than MAX_ANALYZE_SIZE. + private const int MAX_FILTER_BLOCK_SIZE = 0x400000; + + // Write data in 4 MB or smaller blocks. Must not exceed PACK_MAX_WRITE, + // so we keep number of buffered filter in unpacker reasonable. + private const int UNPACK_MAX_WRITE = 0x400000; + + // Decode compressed bit fields to alphabet numbers. +// struct DecodeTable +// { +// // Real size of DecodeNum table. +// public uint MaxNum; // -//namespace SharpCompress.Compressors.Rar -//{ -// internal partial class Unpack -// { +// // Left aligned start and upper limit codes defining code space +// // ranges for bit lengths. DecodeLen[BitLength-1] defines the start of +// // range for bit length and DecodeLen[BitLength] defines next code +// // after the end of range or in other words the upper limit code +// // for specified bit length. +// //uint DecodeLen[16]; +// public uint [] DecodeLen = new uint[16]; // -// public void Unpack5(bool Solid) { -// FileExtracted=true; +// // Every item of this array contains the sum of all preceding items. +// // So it contains the start position in code list for every bit length. +// public uint DecodePos[16]; // -// if (!Suspended) +// // Number of compressed bits processed in quick mode. +// // Must not exceed MAX_QUICK_DECODE_BITS. +// public uint QuickBits; +// +// // Translates compressed bits (up to QuickBits length) +// // to bit length in quick mode. +// public byte QuickLen[1< Filters { get { return this.filters; } } + +// TODO: make sure these aren't already somewhere else +public int BlockSize; +public int BlockBitSize; +public int BlockStart; +public int HeaderSize; +public bool LastBlockInFile; +public bool TablePresent; + + public void Unpack5(bool Solid) { + FileExtracted=true; + + if (!Suspended) + { + UnpInitData(Solid); + if (!UnpReadBuf()) + return; + + // Check TablesRead5 to be sure that we read tables at least once + // regardless of current block header TablePresent flag. + // So we can safefly use these tables below. + if (!ReadBlockHeader() || + !ReadTables() || !TablesRead5) + return; + } + + while (true) + { + UnpPtr &= MaxWinMask; + + if (Inp.InAddr>=ReadBorder) + { + bool FileDone=false; + + // We use 'while', because for empty block containing only Huffman table, + // we'll be on the block border once again just after reading the table. + while (Inp.InAddr>BlockHeader.BlockStart+BlockHeader.BlockSize-1 || + Inp.InAddr==BlockHeader.BlockStart+BlockHeader.BlockSize-1 && + Inp.InBit>=BlockHeader.BlockBitSize) + { + if (BlockHeader.LastBlockInFile) + { + FileDone=true; + break; + } + if (!ReadBlockHeader() || !ReadTables()) + return; + } + if (FileDone || !UnpReadBuf()) + break; + } + + if (((WriteBorder-UnpPtr) & MaxWinMask)DestUnpSize) + return; + if (Suspended) + { + FileExtracted=false; + return; + } + } + + //uint MainSlot=DecodeNumber(Inp,LD); + uint MainSlot= this.DecodeNumber(LD); + if (MainSlot<256) + { +// if (Fragmented) +// FragWindow[UnpPtr++]=(byte)MainSlot; +// else + Window[UnpPtr++]=(byte)MainSlot; + continue; + } + if (MainSlot>=262) + { + uint Length=SlotToLength(MainSlot-262); + + //uint DBits,Distance=1,DistSlot=DecodeNumber(Inp,&BlockTables.DD); + int DBits; + uint Distance=1,DistSlot=this.DecodeNumber(DD); + if (DistSlot<4) + { + DBits=0; + Distance+=DistSlot; + } + else + { + //DBits=DistSlot/2 - 1; + DBits=(int)(DistSlot/2 - 1); + Distance+=(2 | (DistSlot & 1)) << DBits; + } + + if (DBits>0) + { + if (DBits>=4) + { + if (DBits>4) + { + Distance+=((Inp.getbits()>>(36-DBits))<<4); + Inp.AddBits(DBits-4); + } + //uint LowDist=DecodeNumber(Inp,&BlockTables.LDD); + uint LowDist=this.DecodeNumber(LDD); + Distance+=LowDist; + } + else + { + Distance+=Inp.getbits()>>(32-DBits); + Inp.AddBits(DBits); + } + } + + if (Distance>0x100) + { + Length++; + if (Distance>0x2000) + { + Length++; + if (Distance>0x40000) + Length++; + } + } + + InsertOldDist(Distance); + LastLength=Length; +// if (Fragmented) +// FragWindow.CopyString(Length,Distance,UnpPtr,MaxWinMask); +// else + CopyString(Length,Distance); + continue; + } + if (MainSlot==256) + { + UnpackFilter Filter = new UnpackFilter(); + if (!ReadFilter(Filter) || !AddFilter(Filter)) + break; + continue; + } + if (MainSlot==257) + { + if (LastLength!=0) +// if (Fragmented) +// FragWindow.CopyString(LastLength,OldDist[0],UnpPtr,MaxWinMask); +// else + //CopyString(LastLength,OldDist[0]); + CopyString(LastLength,OldDistN(0)); + continue; + } + if (MainSlot<262) + { + //uint DistNum=MainSlot-258; + int DistNum=(int)(MainSlot-258); + //uint Distance=OldDist[DistNum]; + uint Distance=OldDistN(DistNum); + //for (uint I=DistNum;I>0;I--) + for (int I=DistNum;I>0;I--) + //OldDistN[I]=OldDistN(I-1); + SetOldDistN(I, OldDistN(I-1)); + //OldDistN[0]=Distance; + SetOldDistN(0, Distance); + + uint LengthSlot=this.DecodeNumber(RD); + uint Length=SlotToLength(LengthSlot); + LastLength=Length; +// if (Fragmented) +// FragWindow.CopyString(Length,Distance,UnpPtr,MaxWinMask); +// else + CopyString(Length,Distance); + continue; + } + } + UnpWriteBuf(); + } + + + uint ReadFilterData() + { + uint ByteCount=(Inp.fgetbits()>>14)+1; + Inp.AddBits(2); + + uint Data=0; + //for (uint I=0;I>8)<<(I*8); + Inp.AddBits(8); + } + return Data; + } + + + bool ReadFilter(UnpackFilter Filter) + { + if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-16) + if (!UnpReadBuf()) + return false; + + Filter.uBlockStart=ReadFilterData(); + Filter.uBlockLength=ReadFilterData(); + if (Filter.BlockLength>MAX_FILTER_BLOCK_SIZE) + Filter.BlockLength=0; + + //Filter.Type=Inp.fgetbits()>>13; + Filter.Type=(byte)(this.Inp.fgetbits()>>13); + Inp.faddbits(3); + + if (Filter.Type==(byte)FilterType.FILTER_DELTA) + { + //Filter.Channels=(Inp.fgetbits()>>11)+1; + Filter.Channels=(byte)((this.Inp.fgetbits()>>11)+1); + Inp.faddbits(5); + } + + return true; + } + + + bool AddFilter(UnpackFilter Filter) + { + if (Filters.Count>=MAX_UNPACK_FILTERS) + { + UnpWriteBuf(); // Write data, apply and flush filters. + if (Filters.Count>=MAX_UNPACK_FILTERS) + InitFilters(); // Still too many filters, prevent excessive memory use. + } + + // If distance to filter start is that large that due to circular dictionary + // mode now it points to old not written yet data, then we set 'NextWindow' + // flag and process this filter only after processing that older data. + Filter.NextWindow=WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<=Filter.BlockStart; + + Filter.uBlockStart=(uint)((Filter.BlockStart+UnpPtr)&MaxWinMask); + Filters.Add(Filter); + return true; + } + + + bool UnpReadBuf() + { + int DataSize=ReadTop-Inp.InAddr; // Data left to process. + if (DataSize<0) + return false; + BlockHeader.BlockSize-=Inp.InAddr-BlockHeader.BlockStart; + if (Inp.InAddr>BitInput.MAX_SIZE/2) + { + // If we already processed more than half of buffer, let's move + // remaining data into beginning to free more space for new data + // and ensure that calling function does not cross the buffer border + // even if we did not read anything here. Also it ensures that read size + // is not less than CRYPT_BLOCK_SIZE, so we can align it without risk + // to make it zero. + if (DataSize>0) + //memmove(Inp.InBuf,Inp.InBuf+Inp.InAddr,DataSize); + Array.Copy(InBuf, inAddr, InBuf, 0, DataSize); + // TODO: perf + //Buffer.BlockCopy(InBuf, inAddr, InBuf, 0, DataSize); + + Inp.InAddr=0; + ReadTop=DataSize; + } + else + DataSize=ReadTop; + int ReadCode=0; + if (BitInput.MAX_SIZE!=DataSize) + //ReadCode=UnpIO->UnpRead(Inp.InBuf+DataSize,BitInput.MAX_SIZE-DataSize); + ReadCode = readStream.Read(InBuf, DataSize, BitInput.MAX_SIZE-DataSize); + if (ReadCode>0) // Can be also -1. + ReadTop+=ReadCode; + ReadBorder=ReadTop-30; + BlockHeader.BlockStart=Inp.InAddr; + if (BlockHeader.BlockSize!=-1) // '-1' means not defined yet. + { + // We may need to quit from main extraction loop and read new block header + // and trees earlier than data in input buffer ends. + ReadBorder=Math.Min(ReadBorder,BlockHeader.BlockStart+BlockHeader.BlockSize-1); + } + return ReadCode!=-1; + } + +//? +// void UnpWriteBuf() +// { +// size_t WrittenBorder=WrPtr; +// size_t FullWriteSize=(UnpPtr-WrittenBorder)&MaxWinMask; +// size_t WriteSizeLeft=FullWriteSize; +// bool NotAllFiltersProcessed=false; +// for (size_t I=0;IType==FilterType.FILTER_NONE) +// continue; +// if (flt->NextWindow) // { -// unpInitData(Solid); -// if (!UnpReadBuf()) -// return; -// -// // Check TablesRead5 to be sure that we read tables at least once -// // regardless of current block header TablePresent flag. -// // So we can safefly use these tables below. -// if (!ReadBlockHeader(Inp,BlockHeader) || -// !ReadTables(Inp,BlockHeader,BlockTables) || !TablesRead5) -// return; -// } -// -// while (true) -// { -// UnpPtr&=MaxWinMask; -// -// if (Inp.InAddr>=ReadBorder) -// { -// bool FileDone=false; -// -// // We use 'while', because for empty block containing only Huffman table, -// // we'll be on the block border once again just after reading the table. -// while (Inp.InAddr>BlockHeader.BlockStart+BlockHeader.BlockSize-1 || -// Inp.InAddr==BlockHeader.BlockStart+BlockHeader.BlockSize-1 && -// Inp.InBit>=BlockHeader.BlockBitSize) -// { -// if (BlockHeader.LastBlockInFile) -// { -// FileDone=true; -// break; -// } -// if (!ReadBlockHeader(Inp,BlockHeader) || !ReadTables(Inp,BlockHeader,BlockTables)) -// return; -// } -// if (FileDone || !UnpReadBuf()) -// break; -// } -// -// if (((WriteBorder-UnpPtr) & MaxWinMask)DestUnpSize) -// return; -// if (Suspended) -// { -// FileExtracted=false; -// return; -// } -// } -// -// uint MainSlot=DecodeNumber(Inp,&BlockTables.LD); -// if (MainSlot<256) -// { -// if (Fragmented) -// FragWindow[UnpPtr++]=(byte)MainSlot; -// else -// Window[UnpPtr++]=(byte)MainSlot; -// continue; -// } -// if (MainSlot>=262) -// { -// uint Length=SlotToLength(Inp,MainSlot-262); -// -// uint DBits,Distance=1,DistSlot=DecodeNumber(Inp,&BlockTables.DD); -// if (DistSlot<4) -// { -// DBits=0; -// Distance+=DistSlot; -// } -// else -// { -// DBits=DistSlot/2 - 1; -// Distance+=(2 | (DistSlot & 1)) << DBits; -// } -// -// if (DBits>0) -// { -// if (DBits>=4) -// { -// if (DBits>4) -// { -// Distance+=((Inp.getbits32()>>(36-DBits))<<4); -// Inp.addbits(DBits-4); -// } -// uint LowDist=DecodeNumber(Inp,&BlockTables.LDD); -// Distance+=LowDist; -// } -// else -// { -// Distance+=Inp.getbits32()>>(32-DBits); -// Inp.addbits(DBits); -// } -// } -// -// if (Distance>0x100) -// { -// Length++; -// if (Distance>0x2000) -// { -// Length++; -// if (Distance>0x40000) -// Length++; -// } -// } -// -// InsertOldDist(Distance); -// LastLength=Length; -// if (Fragmented) -// FragWindow.CopyString(Length,Distance,UnpPtr,MaxWinMask); -// else -// CopyString(Length,Distance); -// continue; -// } -// if (MainSlot==256) -// { -// UnpackFilter Filter; -// if (!ReadFilter(Inp,Filter) || !AddFilter(Filter)) -// break; -// continue; -// } -// if (MainSlot==257) -// { -// if (LastLength!=0) -// if (Fragmented) -// FragWindow.CopyString(LastLength,OldDist[0],UnpPtr,MaxWinMask); -// else -// CopyString(LastLength,OldDist[0]); -// continue; -// } -// if (MainSlot<262) -// { -// uint DistNum=MainSlot-258; -// uint Distance=OldDist[DistNum]; -// for (uint I=DistNum;I>0;I--) -// OldDist[I]=OldDist[I-1]; -// OldDist[0]=Distance; -// -// uint LengthSlot=DecodeNumber(Inp,&BlockTables.RD); -// uint Length=SlotToLength(Inp,LengthSlot); -// LastLength=Length; -// if (Fragmented) -// FragWindow.CopyString(Length,Distance,UnpPtr,MaxWinMask); -// else -// CopyString(Length,Distance); -// continue; -// } -// } -// UnpWriteBuf(); -//} -// -// -//uint ReadFilterData(BitInput &Inp) -//{ -// uint ByteCount=(Inp.fgetbits()>>14)+1; -// Inp.addbits(2); -// -// uint Data=0; -// for (uint I=0;I>8)<<(I*8); -// Inp.addbits(8); -// } -// return Data; -//} -// -// -//bool ReadFilter(BitInput &Inp,UnpackFilter &Filter) -//{ -// if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-16) -// if (!UnpReadBuf()) -// return false; -// -// Filter.BlockStart=ReadFilterData(Inp); -// Filter.BlockLength=ReadFilterData(Inp); -// if (Filter.BlockLength>MAX_FILTER_BLOCK_SIZE) -// Filter.BlockLength=0; -// -// Filter.Type=Inp.fgetbits()>>13; -// Inp.faddbits(3); -// -// if (Filter.Type==FILTER_DELTA) -// { -// Filter.Channels=(Inp.fgetbits()>>11)+1; -// Inp.faddbits(5); -// } -// -// return true; -//} -// -// -//bool AddFilter(UnpackFilter &Filter) -//{ -// if (Filters.Size()>=MAX_UNPACK_FILTERS) -// { -// UnpWriteBuf(); // Write data, apply and flush filters. -// if (Filters.Size()>=MAX_UNPACK_FILTERS) -// InitFilters(); // Still too many filters, prevent excessive memory use. -// } -// -// // If distance to filter start is that large that due to circular dictionary -// // mode now it points to old not written yet data, then we set 'NextWindow' -// // flag and process this filter only after processing that older data. -// Filter.NextWindow=WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<=Filter.BlockStart; -// -// Filter.BlockStart=uint((Filter.BlockStart+UnpPtr)&MaxWinMask); -// Filters.Push(Filter); -// return true; -//} -// -// -//bool UnpReadBuf() -//{ -// int DataSize=ReadTop-Inp.InAddr; // Data left to process. -// if (DataSize<0) -// return false; -// BlockHeader.BlockSize-=Inp.InAddr-BlockHeader.BlockStart; -// if (Inp.InAddr>BitInput::MAX_SIZE/2) -// { -// // If we already processed more than half of buffer, let's move -// // remaining data into beginning to free more space for new data -// // and ensure that calling function does not cross the buffer border -// // even if we did not read anything here. Also it ensures that read size -// // is not less than CRYPT_BLOCK_SIZE, so we can align it without risk -// // to make it zero. -// if (DataSize>0) -// memmove(Inp.InBuf,Inp.InBuf+Inp.InAddr,DataSize); -// Inp.InAddr=0; -// ReadTop=DataSize; -// } -// else -// DataSize=ReadTop; -// int ReadCode=0; -// if (BitInput::MAX_SIZE!=DataSize) -// ReadCode=UnpIO->UnpRead(Inp.InBuf+DataSize,BitInput::MAX_SIZE-DataSize); -// if (ReadCode>0) // Can be also -1. -// ReadTop+=ReadCode; -// ReadBorder=ReadTop-30; -// BlockHeader.BlockStart=Inp.InAddr; -// if (BlockHeader.BlockSize!=-1) // '-1' means not defined yet. -// { -// // We may need to quit from main extraction loop and read new block header -// // and trees earlier than data in input buffer ends. -// ReadBorder=Min(ReadBorder,BlockHeader.BlockStart+BlockHeader.BlockSize-1); -// } -// return ReadCode!=-1; -//} -// -// -//void UnpWriteBuf() -//{ -// size_t WrittenBorder=WrPtr; -// size_t FullWriteSize=(UnpPtr-WrittenBorder)&MaxWinMask; -// size_t WriteSizeLeft=FullWriteSize; -// bool NotAllFiltersProcessed=false; -// for (size_t I=0;IType==FILTER_NONE) -// continue; -// if (flt->NextWindow) -// { -// // Here we skip filters which have block start in current data range -// // due to address wrap around in circular dictionary, but actually -// // belong to next dictionary block. If such filter start position -// // is included to current write range, then we reset 'NextWindow' flag. -// // In fact we can reset it even without such check, because current -// // implementation seems to guarantee 'NextWindow' flag reset after -// // buffer writing for all existing filters. But let's keep this check -// // just in case. Compressor guarantees that distance between -// // filter block start and filter storing position cannot exceed -// // the dictionary size. So if we covered the filter block start with -// // our write here, we can safely assume that filter is applicable -// // to next block on no further wrap arounds is possible. -// if (((flt->BlockStart-WrPtr)&MaxWinMask)<=FullWriteSize) -// flt->NextWindow=false; -// continue; -// } -// uint BlockStart=flt->BlockStart; -// uint BlockLength=flt->BlockLength; -// if (((BlockStart-WrittenBorder)&MaxWinMask)0) // We set it to 0 also for invalid filters. -// { -// uint BlockEnd=(BlockStart+BlockLength)&MaxWinMask; -// -// FilterSrcMemory.Alloc(BlockLength); -// byte *Mem=&FilterSrcMemory[0]; -// if (BlockStartBlockStart-WrPtr)&MaxWinMask)<=FullWriteSize) +// flt->NextWindow=false; +// continue; // } -// else +// uint BlockStart=flt->BlockStart; +// uint BlockLength=flt->BlockLength; +// if (((BlockStart-WrittenBorder)&MaxWinMask)0) // We set it to 0 also for invalid filters. +// { +// uint BlockEnd=(BlockStart+BlockLength)&MaxWinMask; +// +// FilterSrcMemory.Alloc(BlockLength); +// byte *Mem=&FilterSrcMemory[0]; +// if (BlockStartUnpWrite(OutMem,BlockLength); +// +// UnpSomeRead=true; +// WrittenFileSize+=BlockLength; +// WrittenBorder=BlockEnd; +// WriteSizeLeft=(UnpPtr-WrittenBorder)&MaxWinMask; +// } +// } +// else +// { +// // Current filter intersects the window write border, so we adjust +// // the window border to process this filter next time, not now. +// WrPtr=WrittenBorder; +// +// // Since Filter start position can only increase, we quit processing +// // all following filters for this data block and reset 'NextWindow' +// // flag for them. +// for (size_t J=I;JType!=FilterType.FILTER_NONE) +// flt->NextWindow=false; +// } +// +// // Do not write data left after current filter now. +// NotAllFiltersProcessed=true; +// break; +// } // } // } // -// byte *OutMem=ApplyFilter(Mem,BlockLength,flt); -// -// Filters[I].Type=FILTER_NONE; -// -// if (OutMem!=NULL) -// UnpIO->UnpWrite(OutMem,BlockLength); -// -// UnpSomeRead=true; -// WrittenFileSize+=BlockLength; -// WrittenBorder=BlockEnd; -// WriteSizeLeft=(UnpPtr-WrittenBorder)&MaxWinMask; -// } -// } -// else -// { -// // Current filter intersects the window write border, so we adjust -// // the window border to process this filter next time, not now. -// WrPtr=WrittenBorder; -// -// // Since Filter start position can only increase, we quit processing -// // all following filters for this data block and reset 'NextWindow' -// // flag for them. -// for (size_t J=I;JType!=FILTER_NONE) -// flt->NextWindow=false; -// } -// -// // Do not write data left after current filter now. -// NotAllFiltersProcessed=true; -// break; -// } -// } -// } -// -// // Remove processed filters from queue. -// size_t EmptyCount=0; -// for (size_t I=0;I0) -// Filters[I-EmptyCount]=Filters[I]; -// if (Filters[I].Type==FILTER_NONE) -// EmptyCount++; -// } -// if (EmptyCount>0) -// Filters.Alloc(Filters.Size()-EmptyCount); -// -// if (!NotAllFiltersProcessed) // Only if all filters are processed. -// { -// // Write data left after last filter. -// UnpWriteArea(WrittenBorder,UnpPtr); -// WrPtr=UnpPtr; -// } -// -// // We prefer to write data in blocks not exceeding UNPACK_MAX_WRITE -// // instead of potentially huge MaxWinSize blocks. It also allows us -// // to keep the size of Filters array reasonable. -// WriteBorder=(UnpPtr+Min(MaxWinSize,UNPACK_MAX_WRITE))&MaxWinMask; -// -// // Choose the nearest among WriteBorder and WrPtr actual written border. -// // If border is equal to UnpPtr, it means that we have MaxWinSize data ahead. -// if (WriteBorder==UnpPtr || -// WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<((WriteBorder-UnpPtr)&MaxWinMask)) -// WriteBorder=WrPtr; -//} -// -// -//byte* ApplyFilter(byte *Data,uint DataSize,UnpackFilter *Flt) -//{ -// byte *SrcData=Data; -// switch(Flt->Type) -// { -// case FILTER_E8: -// case FILTER_E8E9: -// { -// uint FileOffset=(uint)WrittenFileSize; -// -// const uint FileSize=0x1000000; -// byte CmpByte2=Flt->Type==FILTER_E8E9 ? 0xe9:0xe8; -// // DataSize is unsigned, so we use "CurPos+4" and not "DataSize-4" -// // to avoid overflow for DataSize<4. -// for (uint CurPos=0;CurPos+40) +// Filters[I-EmptyCount]=Filters[I]; +// if (Filters[I].Type==FilterType.FILTER_NONE) +// EmptyCount++; +// } +// if (EmptyCount>0) +// Filters.Alloc(Filters.Size()-EmptyCount); // -// // We check 0x80000000 bit instead of '< 0' comparison -// // not assuming int32 presence or uint size and endianness. -// if ((Addr & 0x80000000)!=0) // Addr<0 +// if (!NotAllFiltersProcessed) // Only if all filters are processed. +// { +// // Write data left after last filter. +// UnpWriteArea(WrittenBorder,UnpPtr); +// WrPtr=UnpPtr; +// } +// +// // We prefer to write data in blocks not exceeding UNPACK_MAX_WRITE +// // instead of potentially huge MaxWinSize blocks. It also allows us +// // to keep the size of Filters array reasonable. +// WriteBorder=(UnpPtr+Min(MaxWinSize,UNPACK_MAX_WRITE))&MaxWinMask; +// +// // Choose the nearest among WriteBorder and WrPtr actual written border. +// // If border is equal to UnpPtr, it means that we have MaxWinSize data ahead. +// if (WriteBorder==UnpPtr || +// WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<((WriteBorder-UnpPtr)&MaxWinMask)) +// WriteBorder=WrPtr; +// } + + +// unused + //x byte* ApplyFilter(byte *Data,uint DataSize,UnpackFilter *Flt) +// byte[] ApplyFilter(byte []Data, uint DataSize, UnpackFilter Flt) +// { +// //x byte *SrcData=Data; +// byte []SrcData=Data; +// switch(Flt.Type) +// { +// case (byte)FilterType.FILTER_E8: +// case (byte)FilterType.FILTER_E8E9: +// { +// uint FileOffset=(uint)WrittenFileSize; +// +// const uint FileSize=0x1000000; +// byte CmpByte2=Flt.Type==(byte)FilterType.FILTER_E8E9 ? (byte)0xe9 : (byte)0xe8; +// // DataSize is unsigned, so we use "CurPos+4" and not "DataSize-4" +// // to avoid overflow for DataSize<4. +// for (uint CurPos=0;CurPos+4=0 +// RawPut4(Addr+FileSize,Data); +// } +// else +// if (((Addr-FileSize) & 0x80000000)!=0) // Addr>8); +// D[2]=(byte)(Offset>>16); +// } +// } +// } +// return SrcData; +// case (byte)FilterType.FILTER_DELTA: +// { +// // Unlike RAR3, we do not need to reject excessive channel +// // values here, since RAR5 uses only 5 bits to store channel. +// uint Channels=Flt->Channels,SrcPos=0; +// +// FilterDstMemory.Alloc(DataSize); +// byte *DstData=&FilterDstMemory[0]; +// +// // Bytes from same channels are grouped to continual data blocks, +// // so we need to place them back to their interleaving positions. +// for (uint CurChannel=0;CurChannel0) +//// { +//// size_t BlockSize=FragWindow.GetBlockSize(StartPtr,SizeToWrite); +//// UnpWriteData(&FragWindow[StartPtr],BlockSize); +//// SizeToWrite-=BlockSize; +//// StartPtr=(StartPtr+BlockSize) & MaxWinMask; +//// } +//// } +//// else +// if (EndPtr=0 -// RawPut4(Addr+FileSize,Data); +// UnpWriteData(Window+StartPtr,MaxWinSize-StartPtr); +// UnpWriteData(Window,EndPtr); // } // else -// if (((Addr-FileSize) & 0x80000000)!=0) // Addr=DestUnpSize) +// return; +// size_t WriteSize=Size; +// long LeftToWrite=DestUnpSize-WrittenFileSize; +// if ((long)WriteSize>LeftToWrite) +// WriteSize=(size_t)LeftToWrite; +// UnpIO->UnpWrite(Data,WriteSize); +// WrittenFileSize+=Size; +// } + + + void UnpInitData50(bool Solid) + { + if (!Solid) + TablesRead5=false; + } + + + bool ReadBlockHeader() + { + Header.HeaderSize=0; + + if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-7) + if (!UnpReadBuf()) + return false; + //Inp.faddbits((8-Inp.InBit)&7); + Inp.faddbits((uint)((8-this.Inp.InBit)&7)); + + byte BlockFlags=(byte)(Inp.fgetbits()>>8); + Inp.faddbits(8); + //uint ByteCount=((BlockFlags>>3)&3)+1; // Block size byte count. + uint ByteCount=(uint)(((BlockFlags>>3)&3)+1); // Block size byte count. + + if (ByteCount==4) + return false; + + //Header.HeaderSize=2+ByteCount; + Header.HeaderSize=(int)(2+ByteCount); + + Header.BlockBitSize=(BlockFlags&7)+1; + + byte SavedCheckSum=(byte)(Inp.fgetbits()>>8); + Inp.faddbits(8); + + int BlockSize=0; + //for (uint I=0;I>8)<<(I*8); + BlockSize+=(int)(Inp.fgetbits()>>8)<<(I*8); + Inp.AddBits(8); + } + + Header.BlockSize=BlockSize; + byte CheckSum=(byte)(0x5a^BlockFlags^BlockSize^(BlockSize>>8)^(BlockSize>>16)); + if (CheckSum!=SavedCheckSum) + return false; + + Header.BlockStart=Inp.InAddr; + ReadBorder=Math.Min(ReadBorder,Header.BlockStart+Header.BlockSize-1); + + Header.LastBlockInFile=(BlockFlags & 0x40)!=0; + Header.TablePresent=(BlockFlags & 0x80)!=0; + return true; + } + +//? +// bool ReadTables(BitInput Inp, ref UnpackBlockHeader Header, ref UnpackBlockTables Tables) +// { +// if (!Header.TablePresent) +// return true; +// +// if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-25) +// if (!UnpReadBuf()) +// return false; +// +// byte BitLength[BC]; +// for (uint I=0;I>8); -// D[2]=(byte)(Offset>>16); +// uint Length=(byte)(Inp.fgetbits() >> 12); +// Inp.faddbits(4); +// if (Length==15) +// { +// uint ZeroCount=(byte)(Inp.fgetbits() >> 12); +// Inp.faddbits(4); +// if (ZeroCount==0) +// BitLength[I]=15; +// else +// { +// ZeroCount+=2; +// while (ZeroCount-- > 0 && IChannels,SrcPos=0; // -// FilterDstMemory.Alloc(DataSize); -// byte *DstData=&FilterDstMemory[0]; +// MakeDecodeTables(BitLength,&Tables.BD,BC); // -// // Bytes from same channels are grouped to continual data blocks, -// // so we need to place them back to their interleaving positions. -// for (uint CurChannel=0;CurChannel0) -// { -// size_t BlockSize=FragWindow.GetBlockSize(StartPtr,SizeToWrite); -// UnpWriteData(&FragWindow[StartPtr],BlockSize); -// SizeToWrite-=BlockSize; -// StartPtr=(StartPtr+BlockSize) & MaxWinMask; -// } -// } -// else -// if (EndPtr=DestUnpSize) -// return; -// size_t WriteSize=Size; -// int64 LeftToWrite=DestUnpSize-WrittenFileSize; -// if ((int64)WriteSize>LeftToWrite) -// WriteSize=(size_t)LeftToWrite; -// UnpIO->UnpWrite(Data,WriteSize); -// WrittenFileSize+=Size; -//} -// -// -//void UnpInitData50(bool Solid) -//{ -// if (!Solid) -// TablesRead5=false; -//} -// -// -//bool ReadBlockHeader(BitInput &Inp,UnpackBlockHeader &Header) -//{ -// Header.HeaderSize=0; -// -// if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-7) -// if (!UnpReadBuf()) -// return false; -// Inp.faddbits((8-Inp.InBit)&7); -// -// byte BlockFlags=Inp.fgetbits()>>8; -// Inp.faddbits(8); -// uint ByteCount=((BlockFlags>>3)&3)+1; // Block size byte count. -// -// if (ByteCount==4) -// return false; -// -// Header.HeaderSize=2+ByteCount; -// -// Header.BlockBitSize=(BlockFlags&7)+1; -// -// byte SavedCheckSum=Inp.fgetbits()>>8; -// Inp.faddbits(8); -// -// int BlockSize=0; -// for (uint I=0;I>8)<<(I*8); -// Inp.addbits(8); -// } -// -// Header.BlockSize=BlockSize; -// byte CheckSum=byte(0x5a^BlockFlags^BlockSize^(BlockSize>>8)^(BlockSize>>16)); -// if (CheckSum!=SavedCheckSum) -// return false; -// -// Header.BlockStart=Inp.InAddr; -// ReadBorder=Min(ReadBorder,Header.BlockStart+Header.BlockSize-1); -// -// Header.LastBlockInFile=(BlockFlags & 0x40)!=0; -// Header.TablePresent=(BlockFlags & 0x80)!=0; -// return true; -//} -// -// -//bool ReadTables(BitInput &Inp,UnpackBlockHeader &Header,UnpackBlockTables &Tables) -//{ -// if (!Header.TablePresent) -// return true; -// -// if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-25) -// if (!UnpReadBuf()) -// return false; -// -// byte BitLength[BC]; -// for (uint I=0;I> 12); -// Inp.faddbits(4); -// if (Length==15) -// { -// uint ZeroCount=(byte)(Inp.fgetbits() >> 12); -// Inp.faddbits(4); -// if (ZeroCount==0) -// BitLength[I]=15; -// else -// { -// ZeroCount+=2; -// while (ZeroCount-- > 0 && IReadTop-5) -// if (!UnpReadBuf()) -// return false; -// uint Number=DecodeNumber(Inp,&Tables.BD); -// if (Number<16) -// { -// Table[I]=Number; -// I++; -// } -// else -// if (Number<18) -// { -// uint N; -// if (Number==16) -// { -// N=(Inp.fgetbits() >> 13)+3; -// Inp.faddbits(3); -// } -// else -// { -// N=(Inp.fgetbits() >> 9)+11; -// Inp.faddbits(7); -// } -// if (I==0) -// { -// // We cannot have "repeat previous" code at the first position. -// // Multiple such codes would shift Inp position without changing I, -// // which can lead to reading beyond of Inp boundary in mutithreading -// // mode, where Inp.ExternalBuffer disables bounds check and we just -// // reserve a lot of buffer space to not need such check normally. -// return false; -// } -// else -// while (N-- > 0 && IReadTop-5) +// if (!UnpReadBuf()) +// return false; +// uint Number=DecodeNumber(Inp,&Tables.BD); +// if (Number<16) +// { +// Table[I]=Number; +// I++; +// } +// else +// if (Number<18) +// { +// uint N; +// if (Number==16) +// { +// N=(Inp.fgetbits() >> 13)+3; +// Inp.faddbits(3); +// } +// else +// { +// N=(Inp.fgetbits() >> 9)+11; +// Inp.faddbits(7); +// } +// if (I==0) +// { +// // We cannot have "repeat previous" code at the first position. +// // Multiple such codes would shift Inp position without changing I, +// // which can lead to reading beyond of Inp boundary in mutithreading +// // mode, where Inp.ExternalBuffer disables bounds check and we just +// // reserve a lot of buffer space to not need such check normally. +// return false; +// } +// else +// while (N-- > 0 && I> 13)+3; +// Inp.faddbits(3); +// } +// else +// { +// N=(Inp.fgetbits() >> 9)+11; +// Inp.faddbits(7); +// } +// while (N-- > 0 && I> 13)+3; -// Inp.faddbits(3); +// TablesRead5=true; +// if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop) +// return false; +// MakeDecodeTables(&Table[0],&Tables.LD,NC); +// MakeDecodeTables(&Table[NC],&Tables.DD,DC); +// MakeDecodeTables(&Table[NC+DC],&Tables.LDD,LDC); +// MakeDecodeTables(&Table[NC+DC+LDC],&Tables.RD,RC); +// return true; // } -// else + +//? +// void InitFilters() // { -// N=(Inp.fgetbits() >> 9)+11; -// Inp.faddbits(7); +// Filters.SoftReset(); // } -// while (N-- > 0 && IReadTop) -// return false; -// MakeDecodeTables(&Table[0],&Tables.LD,NC); -// MakeDecodeTables(&Table[NC],&Tables.DD,DC); -// MakeDecodeTables(&Table[NC+DC],&Tables.LDD,LDC); -// MakeDecodeTables(&Table[NC+DC+LDC],&Tables.RD,RC); -// return true; -//} -// -// -//void InitFilters() -//{ -// Filters.SoftReset(); -//} -// -// } -//} + + } +} +#endif \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackFilter.cs b/src/SharpCompress/Compressors/Rar/UnpackFilter.cs index d199a7e6..ddf103ed 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackFilter.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackFilter.cs @@ -4,11 +4,18 @@ namespace SharpCompress.Compressors.Rar { internal class UnpackFilter { + public byte Type; + public byte Channels; + internal UnpackFilter() { Program = new VMPreparedProgram(); } + // TODO uint + internal uint uBlockStart { get { return (uint)BlockStart; } set { BlockStart = (int)value; } } + internal uint uBlockLength { get { return (uint)BlockLength; } set { BlockLength = (int)value; } } + internal int BlockStart { get; set; } internal int BlockLength { get; set; } diff --git a/src/SharpCompress/Compressors/Rar/UnpackUtility.cs b/src/SharpCompress/Compressors/Rar/UnpackUtility.cs index 8f9cfada..3c7a0692 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackUtility.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackUtility.cs @@ -4,6 +4,11 @@ namespace SharpCompress.Compressors.Rar { internal static class UnpackUtility { +//!!! TODO rename methods + internal static uint DecodeNumber(this BitInput input, Decode.Decode dec) { + return (uint)input.decodeNumber(dec); + } + internal static int decodeNumber(this BitInput input, Decode.Decode dec) { int bits; diff --git a/src/SharpCompress/Compressors/Rar/VM/BitInput.cs b/src/SharpCompress/Compressors/Rar/VM/BitInput.cs index 74c525b2..3d1fd979 100644 --- a/src/SharpCompress/Compressors/Rar/VM/BitInput.cs +++ b/src/SharpCompress/Compressors/Rar/VM/BitInput.cs @@ -5,8 +5,14 @@ namespace SharpCompress.Compressors.Rar.VM /// the max size of the input internal const int MAX_SIZE = 0x8000; - protected int inAddr; - protected int inBit; + public int inAddr; + public int inBit; + +// TODO: rename var + public int InAddr { get { return this.inAddr; } set { this.inAddr = value; } } + public int InBit { get { return this.inBit; } set { this.inBit = value; } } + public bool ExternalBuffer; + /// internal BitInput() @@ -22,6 +28,11 @@ namespace SharpCompress.Compressors.Rar.VM inBit = 0; } + internal void faddbits(uint bits) { + // TODO uint + AddBits((int)bits); + } + /// /// also named faddbits /// @@ -33,6 +44,16 @@ namespace SharpCompress.Compressors.Rar.VM inBit = bits & 7; } + internal uint fgetbits() { + // TODO uint + return (uint)GetBits(); + } + + internal uint getbits() { + // TODO uint + return (uint)GetBits(); + } + /// /// (also named fgetbits) /// From e13795651dceb14a40f2035eb54aebd629b2d57c Mon Sep 17 00:00:00 2001 From: coderb Date: Sun, 17 Dec 2017 17:52:00 -0500 Subject: [PATCH 20/61] rar: import unrarsrc-5.5.8 for reference this will allow us to diff against newer version of unrarsrc if we wish to update our port --- reference/unrar/UnRAR.vcxproj | 279 ++++++ reference/unrar/UnRARDll.vcxproj | 420 +++++++++ reference/unrar/acknow.txt | 92 ++ reference/unrar/arccmt.cpp | 166 ++++ reference/unrar/archive.cpp | 332 +++++++ reference/unrar/archive.hpp | 151 ++++ reference/unrar/arcread.cpp | 1437 ++++++++++++++++++++++++++++++ reference/unrar/array.hpp | 191 ++++ reference/unrar/blake2s.cpp | 183 ++++ reference/unrar/blake2s.hpp | 101 +++ reference/unrar/blake2s_sse.cpp | 129 +++ reference/unrar/blake2sp.cpp | 153 ++++ reference/unrar/cmddata.cpp | 1400 +++++++++++++++++++++++++++++ reference/unrar/cmddata.hpp | 64 ++ reference/unrar/coder.cpp | 48 + reference/unrar/coder.hpp | 23 + reference/unrar/compress.hpp | 50 ++ reference/unrar/consio.cpp | 352 ++++++++ reference/unrar/consio.hpp | 26 + reference/unrar/crc.cpp | 102 +++ reference/unrar/crc.hpp | 15 + reference/unrar/crypt.cpp | 134 +++ reference/unrar/crypt.hpp | 101 +++ reference/unrar/crypt1.cpp | 79 ++ reference/unrar/crypt2.cpp | 133 +++ reference/unrar/crypt3.cpp | 68 ++ reference/unrar/crypt5.cpp | 233 +++++ reference/unrar/dll.cpp | 474 ++++++++++ reference/unrar/dll.def | 12 + reference/unrar/dll.hpp | 185 ++++ reference/unrar/dll.rc | 28 + reference/unrar/encname.cpp | 69 ++ reference/unrar/encname.hpp | 20 + reference/unrar/errhnd.cpp | 391 ++++++++ reference/unrar/errhnd.hpp | 70 ++ reference/unrar/extinfo.cpp | 178 ++++ reference/unrar/extinfo.hpp | 23 + reference/unrar/extract.cpp | 1167 ++++++++++++++++++++++++ reference/unrar/extract.hpp | 62 ++ reference/unrar/filcreat.cpp | 163 ++++ reference/unrar/filcreat.hpp | 14 + reference/unrar/file.cpp | 729 +++++++++++++++ reference/unrar/file.hpp | 126 +++ reference/unrar/filefn.cpp | 510 +++++++++++ reference/unrar/filefn.hpp | 50 ++ reference/unrar/filestr.cpp | 162 ++++ reference/unrar/filestr.hpp | 15 + reference/unrar/find.cpp | 218 +++++ reference/unrar/find.hpp | 49 + reference/unrar/getbits.cpp | 52 ++ reference/unrar/getbits.hpp | 68 ++ reference/unrar/global.cpp | 7 + reference/unrar/global.hpp | 14 + reference/unrar/hardlinks.cpp | 34 + reference/unrar/hash.cpp | 135 +++ reference/unrar/hash.hpp | 52 ++ reference/unrar/headers.cpp | 61 ++ reference/unrar/headers.hpp | 380 ++++++++ reference/unrar/headers5.hpp | 100 +++ reference/unrar/isnt.cpp | 24 + reference/unrar/isnt.hpp | 13 + reference/unrar/license.txt | 42 + reference/unrar/list.cpp | 472 ++++++++++ reference/unrar/list.hpp | 6 + reference/unrar/loclang.hpp | 381 ++++++++ reference/unrar/log.cpp | 37 + reference/unrar/log.hpp | 12 + reference/unrar/makefile | 173 ++++ reference/unrar/match.cpp | 145 +++ reference/unrar/match.hpp | 34 + reference/unrar/model.cpp | 617 +++++++++++++ reference/unrar/model.hpp | 122 +++ reference/unrar/options.cpp | 35 + reference/unrar/options.hpp | 191 ++++ reference/unrar/os.hpp | 253 ++++++ reference/unrar/pathfn.cpp | 983 ++++++++++++++++++++ reference/unrar/pathfn.hpp | 76 ++ reference/unrar/qopen.cpp | 272 ++++++ reference/unrar/qopen.hpp | 61 ++ reference/unrar/rar.cpp | 105 +++ reference/unrar/rar.hpp | 94 ++ reference/unrar/rardefs.hpp | 31 + reference/unrar/rarlang.hpp | 10 + reference/unrar/raros.hpp | 36 + reference/unrar/rarpch.cpp | 2 + reference/unrar/rartypes.hpp | 32 + reference/unrar/rarvm.cpp | 364 ++++++++ reference/unrar/rarvm.hpp | 44 + reference/unrar/rawint.hpp | 122 +++ reference/unrar/rawread.cpp | 197 ++++ reference/unrar/rawread.hpp | 41 + reference/unrar/rdwrfn.cpp | 319 +++++++ reference/unrar/rdwrfn.hpp | 97 ++ reference/unrar/readme.txt | 50 ++ reference/unrar/recvol.cpp | 111 +++ reference/unrar/recvol.hpp | 87 ++ reference/unrar/recvol3.cpp | 543 +++++++++++ reference/unrar/recvol5.cpp | 517 +++++++++++ reference/unrar/resource.cpp | 11 + reference/unrar/resource.hpp | 11 + reference/unrar/rijndael.cpp | 506 +++++++++++ reference/unrar/rijndael.hpp | 44 + reference/unrar/rs.cpp | 160 ++++ reference/unrar/rs.hpp | 32 + reference/unrar/rs16.cpp | 419 +++++++++ reference/unrar/rs16.hpp | 44 + reference/unrar/savepos.hpp | 21 + reference/unrar/scantree.cpp | 486 ++++++++++ reference/unrar/scantree.hpp | 78 ++ reference/unrar/secpassword.cpp | 216 +++++ reference/unrar/secpassword.hpp | 35 + reference/unrar/sha1.cpp | 204 +++++ reference/unrar/sha1.hpp | 15 + reference/unrar/sha256.cpp | 148 +++ reference/unrar/sha256.hpp | 17 + reference/unrar/smallfn.cpp | 19 + reference/unrar/smallfn.hpp | 8 + reference/unrar/strfn.cpp | 464 ++++++++++ reference/unrar/strfn.hpp | 50 ++ reference/unrar/strlist.cpp | 151 ++++ reference/unrar/strlist.hpp | 31 + reference/unrar/suballoc.cpp | 295 ++++++ reference/unrar/suballoc.hpp | 86 ++ reference/unrar/system.cpp | 175 ++++ reference/unrar/system.hpp | 39 + reference/unrar/threadmisc.cpp | 206 +++++ reference/unrar/threadpool.cpp | 214 +++++ reference/unrar/threadpool.hpp | 107 +++ reference/unrar/timefn.cpp | 340 +++++++ reference/unrar/timefn.hpp | 65 ++ reference/unrar/ui.cpp | 14 + reference/unrar/ui.hpp | 163 ++++ reference/unrar/uicommon.cpp | 65 ++ reference/unrar/uiconsole.cpp | 398 +++++++++ reference/unrar/uisilent.cpp | 63 ++ reference/unrar/ulinks.cpp | 102 +++ reference/unrar/unicode.cpp | 612 +++++++++++++ reference/unrar/unicode.hpp | 65 ++ reference/unrar/unpack.cpp | 354 ++++++++ reference/unrar/unpack.hpp | 403 +++++++++ reference/unrar/unpack15.cpp | 489 ++++++++++ reference/unrar/unpack20.cpp | 378 ++++++++ reference/unrar/unpack30.cpp | 765 ++++++++++++++++ reference/unrar/unpack50.cpp | 683 ++++++++++++++ reference/unrar/unpack50frag.cpp | 103 +++ reference/unrar/unpack50mt.cpp | 655 ++++++++++++++ reference/unrar/unpackinline.cpp | 147 +++ reference/unrar/uowners.cpp | 141 +++ reference/unrar/version.hpp | 6 + reference/unrar/volume.cpp | 288 ++++++ reference/unrar/volume.hpp | 10 + reference/unrar/win32acl.cpp | 129 +++ reference/unrar/win32lnk.cpp | 174 ++++ reference/unrar/win32stm.cpp | 147 +++ 154 files changed, 29882 insertions(+) create mode 100644 reference/unrar/UnRAR.vcxproj create mode 100644 reference/unrar/UnRARDll.vcxproj create mode 100644 reference/unrar/acknow.txt create mode 100644 reference/unrar/arccmt.cpp create mode 100644 reference/unrar/archive.cpp create mode 100644 reference/unrar/archive.hpp create mode 100644 reference/unrar/arcread.cpp create mode 100644 reference/unrar/array.hpp create mode 100644 reference/unrar/blake2s.cpp create mode 100644 reference/unrar/blake2s.hpp create mode 100644 reference/unrar/blake2s_sse.cpp create mode 100644 reference/unrar/blake2sp.cpp create mode 100644 reference/unrar/cmddata.cpp create mode 100644 reference/unrar/cmddata.hpp create mode 100644 reference/unrar/coder.cpp create mode 100644 reference/unrar/coder.hpp create mode 100644 reference/unrar/compress.hpp create mode 100644 reference/unrar/consio.cpp create mode 100644 reference/unrar/consio.hpp create mode 100644 reference/unrar/crc.cpp create mode 100644 reference/unrar/crc.hpp create mode 100644 reference/unrar/crypt.cpp create mode 100644 reference/unrar/crypt.hpp create mode 100644 reference/unrar/crypt1.cpp create mode 100644 reference/unrar/crypt2.cpp create mode 100644 reference/unrar/crypt3.cpp create mode 100644 reference/unrar/crypt5.cpp create mode 100644 reference/unrar/dll.cpp create mode 100644 reference/unrar/dll.def create mode 100644 reference/unrar/dll.hpp create mode 100644 reference/unrar/dll.rc create mode 100644 reference/unrar/encname.cpp create mode 100644 reference/unrar/encname.hpp create mode 100644 reference/unrar/errhnd.cpp create mode 100644 reference/unrar/errhnd.hpp create mode 100644 reference/unrar/extinfo.cpp create mode 100644 reference/unrar/extinfo.hpp create mode 100644 reference/unrar/extract.cpp create mode 100644 reference/unrar/extract.hpp create mode 100644 reference/unrar/filcreat.cpp create mode 100644 reference/unrar/filcreat.hpp create mode 100644 reference/unrar/file.cpp create mode 100644 reference/unrar/file.hpp create mode 100644 reference/unrar/filefn.cpp create mode 100644 reference/unrar/filefn.hpp create mode 100644 reference/unrar/filestr.cpp create mode 100644 reference/unrar/filestr.hpp create mode 100644 reference/unrar/find.cpp create mode 100644 reference/unrar/find.hpp create mode 100644 reference/unrar/getbits.cpp create mode 100644 reference/unrar/getbits.hpp create mode 100644 reference/unrar/global.cpp create mode 100644 reference/unrar/global.hpp create mode 100644 reference/unrar/hardlinks.cpp create mode 100644 reference/unrar/hash.cpp create mode 100644 reference/unrar/hash.hpp create mode 100644 reference/unrar/headers.cpp create mode 100644 reference/unrar/headers.hpp create mode 100644 reference/unrar/headers5.hpp create mode 100644 reference/unrar/isnt.cpp create mode 100644 reference/unrar/isnt.hpp create mode 100644 reference/unrar/license.txt create mode 100644 reference/unrar/list.cpp create mode 100644 reference/unrar/list.hpp create mode 100644 reference/unrar/loclang.hpp create mode 100644 reference/unrar/log.cpp create mode 100644 reference/unrar/log.hpp create mode 100644 reference/unrar/makefile create mode 100644 reference/unrar/match.cpp create mode 100644 reference/unrar/match.hpp create mode 100644 reference/unrar/model.cpp create mode 100644 reference/unrar/model.hpp create mode 100644 reference/unrar/options.cpp create mode 100644 reference/unrar/options.hpp create mode 100644 reference/unrar/os.hpp create mode 100644 reference/unrar/pathfn.cpp create mode 100644 reference/unrar/pathfn.hpp create mode 100644 reference/unrar/qopen.cpp create mode 100644 reference/unrar/qopen.hpp create mode 100644 reference/unrar/rar.cpp create mode 100644 reference/unrar/rar.hpp create mode 100644 reference/unrar/rardefs.hpp create mode 100644 reference/unrar/rarlang.hpp create mode 100644 reference/unrar/raros.hpp create mode 100644 reference/unrar/rarpch.cpp create mode 100644 reference/unrar/rartypes.hpp create mode 100644 reference/unrar/rarvm.cpp create mode 100644 reference/unrar/rarvm.hpp create mode 100644 reference/unrar/rawint.hpp create mode 100644 reference/unrar/rawread.cpp create mode 100644 reference/unrar/rawread.hpp create mode 100644 reference/unrar/rdwrfn.cpp create mode 100644 reference/unrar/rdwrfn.hpp create mode 100644 reference/unrar/readme.txt create mode 100644 reference/unrar/recvol.cpp create mode 100644 reference/unrar/recvol.hpp create mode 100644 reference/unrar/recvol3.cpp create mode 100644 reference/unrar/recvol5.cpp create mode 100644 reference/unrar/resource.cpp create mode 100644 reference/unrar/resource.hpp create mode 100644 reference/unrar/rijndael.cpp create mode 100644 reference/unrar/rijndael.hpp create mode 100644 reference/unrar/rs.cpp create mode 100644 reference/unrar/rs.hpp create mode 100644 reference/unrar/rs16.cpp create mode 100644 reference/unrar/rs16.hpp create mode 100644 reference/unrar/savepos.hpp create mode 100644 reference/unrar/scantree.cpp create mode 100644 reference/unrar/scantree.hpp create mode 100644 reference/unrar/secpassword.cpp create mode 100644 reference/unrar/secpassword.hpp create mode 100644 reference/unrar/sha1.cpp create mode 100644 reference/unrar/sha1.hpp create mode 100644 reference/unrar/sha256.cpp create mode 100644 reference/unrar/sha256.hpp create mode 100644 reference/unrar/smallfn.cpp create mode 100644 reference/unrar/smallfn.hpp create mode 100644 reference/unrar/strfn.cpp create mode 100644 reference/unrar/strfn.hpp create mode 100644 reference/unrar/strlist.cpp create mode 100644 reference/unrar/strlist.hpp create mode 100644 reference/unrar/suballoc.cpp create mode 100644 reference/unrar/suballoc.hpp create mode 100644 reference/unrar/system.cpp create mode 100644 reference/unrar/system.hpp create mode 100644 reference/unrar/threadmisc.cpp create mode 100644 reference/unrar/threadpool.cpp create mode 100644 reference/unrar/threadpool.hpp create mode 100644 reference/unrar/timefn.cpp create mode 100644 reference/unrar/timefn.hpp create mode 100644 reference/unrar/ui.cpp create mode 100644 reference/unrar/ui.hpp create mode 100644 reference/unrar/uicommon.cpp create mode 100644 reference/unrar/uiconsole.cpp create mode 100644 reference/unrar/uisilent.cpp create mode 100644 reference/unrar/ulinks.cpp create mode 100644 reference/unrar/unicode.cpp create mode 100644 reference/unrar/unicode.hpp create mode 100644 reference/unrar/unpack.cpp create mode 100644 reference/unrar/unpack.hpp create mode 100644 reference/unrar/unpack15.cpp create mode 100644 reference/unrar/unpack20.cpp create mode 100644 reference/unrar/unpack30.cpp create mode 100644 reference/unrar/unpack50.cpp create mode 100644 reference/unrar/unpack50frag.cpp create mode 100644 reference/unrar/unpack50mt.cpp create mode 100644 reference/unrar/unpackinline.cpp create mode 100644 reference/unrar/uowners.cpp create mode 100644 reference/unrar/version.hpp create mode 100644 reference/unrar/volume.cpp create mode 100644 reference/unrar/volume.hpp create mode 100644 reference/unrar/win32acl.cpp create mode 100644 reference/unrar/win32lnk.cpp create mode 100644 reference/unrar/win32stm.cpp diff --git a/reference/unrar/UnRAR.vcxproj b/reference/unrar/UnRAR.vcxproj new file mode 100644 index 00000000..b55873e3 --- /dev/null +++ b/reference/unrar/UnRAR.vcxproj @@ -0,0 +1,279 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {95CC809B-03FC-4EDB-BB20-FD07A698C05F} + UnRAR + Win32Proj + 8.1 + + + + Application + v140_xp + MultiByte + true + + + Application + v140_xp + MultiByte + + + Application + v140_xp + MultiByte + false + + + Application + v140_xp + MultiByte + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>14.0.24720.0 + + + build\unrar32\$(Configuration)\ + build\unrar32\$(Configuration)\obj\ + true + false + + + build\unrar64\$(Configuration)\ + build\unrar64\$(Configuration)\obj\ + true + false + + + build\unrar32\$(Configuration)\ + build\unrar32\$(Configuration)\obj\ + false + false + + + build\unrar64\$(Configuration)\ + build\unrar64\$(Configuration)\obj\ + false + false + + + + /MP %(AdditionalOptions) + Disabled + UNRAR;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + false + Use + rar.hpp + Level3 + ProgramDatabase + StdCall + 4007;4996;%(DisableSpecificWarnings) + NoExtensions + + + true + Console + MachineX86 + + + + + X64 + + + /MP %(AdditionalOptions) + Disabled + UNRAR;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + false + Use + rar.hpp + Level3 + ProgramDatabase + StdCall + 4007;4996;%(DisableSpecificWarnings) + NotSet + + + true + Console + MachineX64 + + + + + /MP %(AdditionalOptions) + MaxSpeed + true + Neither + true + false + UNRAR;%(PreprocessorDefinitions) + false + MultiThreaded + Default + true + true + NoExtensions + Precise + false + Use + rar.hpp + Level3 + ProgramDatabase + StdCall + 4007;4996;%(DisableSpecificWarnings) + + + true + Console + true + true + + MachineX86 + + + + + X64 + + + /MP %(AdditionalOptions) + MinSpace + true + Neither + true + false + UNRAR;%(PreprocessorDefinitions) + false + false + MultiThreaded + true + true + false + Use + rar.hpp + Level3 + ProgramDatabase + StdCall + 4007;4996;%(DisableSpecificWarnings) + NotSet + + + true + Console + true + true + + MachineX64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/unrar/UnRARDll.vcxproj b/reference/unrar/UnRARDll.vcxproj new file mode 100644 index 00000000..ecac0db0 --- /dev/null +++ b/reference/unrar/UnRARDll.vcxproj @@ -0,0 +1,420 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + release_nocrypt + Win32 + + + release_nocrypt + x64 + + + Release + Win32 + + + Release + x64 + + + + UnRAR + {E815C46C-36C4-499F-BBC2-E772C6B17971} + UnRAR + Win32Proj + 8.1 + + + + DynamicLibrary + v140_xp + MultiByte + true + + + DynamicLibrary + v140_xp + MultiByte + true + + + DynamicLibrary + v140_xp + MultiByte + + + DynamicLibrary + v140_xp + MultiByte + false + + + DynamicLibrary + v140_xp + MultiByte + false + + + DynamicLibrary + v140_xp + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>14.0.24720.0 + + + build\unrardll32\$(Configuration)\ + build\unrardll32\$(Configuration)\obj\ + true + true + + + build\unrardll64\$(Configuration)\ + build\unrardll64\$(Configuration)\obj\ + true + true + + + build\unrardll32\$(Configuration)\ + build\unrardll32\$(Configuration)\obj\ + false + true + + + build\unrardll64\$(Configuration)\ + build\unrardll64\$(Configuration)\obj\ + false + true + + + build\unrardll32\$(Configuration)\ + build\unrardll32\$(Configuration)\obj\ + false + true + + + build\unrardll64\$(Configuration)\ + build\unrardll64\$(Configuration)\obj\ + false + true + + + + /MP %(AdditionalOptions) + Disabled + RARDLL;UNRAR;SILENT;%(PreprocessorDefinitions) + false + Sync + EnableFastChecks + MultiThreadedDebug + 4Bytes + false + Use + rar.hpp + Level3 + ProgramDatabase + Cdecl + 4007;4996;%(DisableSpecificWarnings) + NoExtensions + + + $(OutDir)unrar.dll + dll.def + true + Console + MachineX86 + + + + + X64 + + + /MP %(AdditionalOptions) + Disabled + RARDLL;UNRAR;SILENT;%(PreprocessorDefinitions) + false + Sync + EnableFastChecks + MultiThreadedDebug + 4Bytes + false + Use + rar.hpp + Level3 + ProgramDatabase + Cdecl + 4007;4996;%(DisableSpecificWarnings) + NotSet + + + $(OutDir)unrar.dll + dll.def + true + Console + MachineX64 + + + + + /MP %(AdditionalOptions) + MaxSpeed + true + Neither + true + false + RARDLL;UNRAR;SILENT;%(PreprocessorDefinitions) + false + Sync + MultiThreaded + 4Bytes + true + true + NoExtensions + Precise + false + Use + rar.hpp + Level3 + ProgramDatabase + Cdecl + 4007;4996;%(DisableSpecificWarnings) + + + /SAFESEH %(AdditionalOptions) + $(OutDir)unrar.dll + dll.def + true + Console + true + true + + MachineX86 + + + + + X64 + + + /MP %(AdditionalOptions) + MaxSpeed + true + Neither + true + false + RARDLL;UNRAR;SILENT;%(PreprocessorDefinitions) + false + false + Sync + MultiThreaded + 4Bytes + true + true + false + Use + rar.hpp + Level3 + ProgramDatabase + Cdecl + 4007;4996;%(DisableSpecificWarnings) + NotSet + + + $(OutDir)unrar.dll + dll.def + true + Console + true + true + + MachineX64 + + + + + /MP %(AdditionalOptions) + MaxSpeed + true + Neither + true + false + RARDLL;UNRAR;SILENT;RAR_NOCRYPT;%(PreprocessorDefinitions) + false + Sync + MultiThreaded + 4Bytes + true + true + NoExtensions + Precise + false + Use + rar.hpp + Level3 + ProgramDatabase + Cdecl + 4007;4996;%(DisableSpecificWarnings) + + + /SAFESEH %(AdditionalOptions) + $(OutDir)unrar.dll + dll_nocrypt.def + true + Console + true + true + + MachineX86 + + + + + X64 + + + /MP %(AdditionalOptions) + MaxSpeed + true + Neither + true + false + RARDLL;UNRAR;SILENT;RAR_NOCRYPT;%(PreprocessorDefinitions) + false + false + Sync + MultiThreaded + 4Bytes + true + true + false + Use + rar.hpp + Level3 + ProgramDatabase + StdCall + 4007;4996;%(DisableSpecificWarnings) + NotSet + + + $(OutDir)unrar.dll + dll_nocrypt.def + true + Console + true + true + + MachineX64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + Create + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/unrar/acknow.txt b/reference/unrar/acknow.txt new file mode 100644 index 00000000..b982ecee --- /dev/null +++ b/reference/unrar/acknow.txt @@ -0,0 +1,92 @@ + ACKNOWLEDGMENTS + +* We used "Screaming Fast Galois Field Arithmetic Using Intel + SIMD Instructions" paper by James S. Plank, Kevin M. Greenan + and Ethan L. Miller to improve Reed-Solomon coding performance. + Also we are grateful to Artem Drobanov and Bulat Ziganshin + for samples and ideas allowed to make Reed-Solomon coding + more efficient. + +* RAR text compression algorithm is based on Dmitry Shkarin PPMII + and Dmitry Subbotin carryless rangecoder public domain source code. + You may find it in ftp.elf.stuba.sk/pub/pc/pack. + +* RAR encryption includes parts of code from Szymon Stefanek + and Brian Gladman AES implementations also as Steve Reid SHA-1 source. + + --------------------------------------------------------------------------- + Copyright (c) 2002, Dr Brian Gladman < >, Worcester, UK. + All rights reserved. + + LICENSE TERMS + + The free distribution and use of this software in both source and binary + form is allowed (with or without changes) provided that: + + 1. distributions of this source code include the above copyright + notice, this list of conditions and the following disclaimer; + + 2. distributions in binary form include the above copyright + notice, this list of conditions and the following disclaimer + in the documentation and/or other associated materials; + + 3. the copyright holder's name is not used to endorse products + built using this software without specific written permission. + + ALTERNATIVELY, provided that this notice is retained in full, this product + may be distributed under the terms of the GNU General Public License (GPL), + in which case the provisions of the GPL apply INSTEAD OF those given above. + + DISCLAIMER + + This software is provided 'as is' with no explicit or implied warranties + in respect of its properties, including, but not limited to, correctness + and/or fitness for purpose. + --------------------------------------------------------------------------- + + Source code of this package also as other cryptographic technology + and computing project related links are available on Brian Gladman's + web site: http://www.gladman.me.uk + +* RAR uses CRC32 function based on Intel Slicing-by-8 algorithm. + Original Intel Slicing-by-8 code is available here: + + http://sourceforge.net/projects/slicing-by-8/ + + Original Intel Slicing-by-8 code is licensed under BSD License + available at http://www.opensource.org/licenses/bsd-license.html + + Copyright (c) 2004-2006 Intel Corporation. + 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. + + 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. + +* RAR archives may optionally include BLAKE2sp hash ( https://blake2.net ), + designed by Jean-Philippe Aumasson, Samuel Neves, Zooko Wilcox-O'Hearn + and Christian Winnerlein. + +* Useful hints provided by Alexander Khoroshev and Bulat Ziganshin allowed + to significantly improve RAR compression and speed. diff --git a/reference/unrar/arccmt.cpp b/reference/unrar/arccmt.cpp new file mode 100644 index 00000000..79963b27 --- /dev/null +++ b/reference/unrar/arccmt.cpp @@ -0,0 +1,166 @@ +static bool IsAnsiEscComment(const wchar *Data,size_t Size); + +bool Archive::GetComment(Array *CmtData) +{ + if (!MainComment) + return false; + SaveFilePos SavePos(*this); + +#ifndef SFX_MODULE + uint CmtLength; + if (Format==RARFMT14) + { + Seek(SFXSize+SIZEOF_MAINHEAD14,SEEK_SET); + CmtLength=GetByte(); + CmtLength+=(GetByte()<<8); + } + else +#endif + { + if (MainHead.CommentInHeader) + { + // Old style (RAR 2.9) archive comment embedded into the main + // archive header. + Seek(SFXSize+SIZEOF_MARKHEAD3+SIZEOF_MAINHEAD3,SEEK_SET); + ReadHeader(); + } + else + { + // Current (RAR 3.0+) version of archive comment. + Seek(GetStartPos(),SEEK_SET); + return SearchSubBlock(SUBHEAD_TYPE_CMT)!=0 && ReadCommentData(CmtData); + } +#ifndef SFX_MODULE + // Old style (RAR 2.9) comment header embedded into the main + // archive header. + if (BrokenHeader) + { + uiMsg(UIERROR_CMTBROKEN,FileName); + return false; + } + CmtLength=CommHead.HeadSize-SIZEOF_COMMHEAD; +#endif + } +#ifndef SFX_MODULE + if (Format==RARFMT14 && MainHead.PackComment || Format!=RARFMT14 && CommHead.Method!=0x30) + { + if (Format!=RARFMT14 && (CommHead.UnpVer < 15 || CommHead.UnpVer > VER_UNPACK || CommHead.Method > 0x35)) + return false; + ComprDataIO DataIO; + DataIO.SetTestMode(true); + uint UnpCmtLength; + if (Format==RARFMT14) + { +#ifdef RAR_NOCRYPT + return false; +#else + UnpCmtLength=GetByte(); + UnpCmtLength+=(GetByte()<<8); + CmtLength-=2; + DataIO.SetCmt13Encryption(); + CommHead.UnpVer=15; +#endif + } + else + UnpCmtLength=CommHead.UnpSize; + DataIO.SetFiles(this,NULL); + DataIO.EnableShowProgress(false); + DataIO.SetPackedSizeToRead(CmtLength); + DataIO.UnpHash.Init(HASH_CRC32,1); + + Unpack CmtUnpack(&DataIO); + CmtUnpack.Init(0x10000,false); + CmtUnpack.SetDestSize(UnpCmtLength); + CmtUnpack.DoUnpack(CommHead.UnpVer,false); + + if (Format!=RARFMT14 && (DataIO.UnpHash.GetCRC32()&0xffff)!=CommHead.CommCRC) + { + uiMsg(UIERROR_CMTBROKEN,FileName); + return false; + } + else + { + byte *UnpData; + size_t UnpDataSize; + DataIO.GetUnpackedData(&UnpData,&UnpDataSize); +#ifdef _WIN_ALL + // If we ever decide to extend it to Android, we'll need to alloc + // 4x memory for OEM to UTF-8 output here. + OemToCharBuffA((char *)UnpData,(char *)UnpData,(DWORD)UnpDataSize); +#endif + CmtData->Alloc(UnpDataSize+1); + memset(CmtData->Addr(0),0,CmtData->Size()*sizeof(wchar)); + CharToWide((char *)UnpData,CmtData->Addr(0),CmtData->Size()); + CmtData->Alloc(wcslen(CmtData->Addr(0))); + } + } + else + { + if (CmtLength==0) + return false; + Array CmtRaw(CmtLength); + Read(&CmtRaw[0],CmtLength); + + if (Format!=RARFMT14 && CommHead.CommCRC!=(~CRC32(0xffffffff,&CmtRaw[0],CmtLength)&0xffff)) + { + uiMsg(UIERROR_CMTBROKEN,FileName); + return false; + } + CmtData->Alloc(CmtLength+1); + CmtRaw.Push(0); +#ifdef _WIN_ALL + // If we ever decide to extend it to Android, we'll need to alloc + // 4x memory for OEM to UTF-8 output here. + OemToCharA((char *)&CmtRaw[0],(char *)&CmtRaw[0]); +#endif + CharToWide((char *)&CmtRaw[0],CmtData->Addr(0),CmtLength); + CmtData->Alloc(wcslen(CmtData->Addr(0))); + } +#endif + return CmtData->Size() > 0; +} + + +bool Archive::ReadCommentData(Array *CmtData) +{ + Array CmtRaw; + if (!ReadSubData(&CmtRaw,NULL)) + return false; + size_t CmtSize=CmtRaw.Size(); + CmtRaw.Push(0); + CmtData->Alloc(CmtSize+1); + if (Format==RARFMT50) + UtfToWide((char *)&CmtRaw[0],CmtData->Addr(0),CmtData->Size()); + else + if ((SubHead.SubFlags & SUBHEAD_FLAGS_CMT_UNICODE)!=0) + { + RawToWide(&CmtRaw[0],CmtData->Addr(0),CmtSize/2); + (*CmtData)[CmtSize/2]=0; + + } + else + { + CharToWide((char *)&CmtRaw[0],CmtData->Addr(0),CmtData->Size()); + } + CmtData->Alloc(wcslen(CmtData->Addr(0))); // Set buffer size to actual comment length. + return true; +} + + +void Archive::ViewComment() +{ + if (Cmd->DisableComment) + return; + Array CmtBuf; + if (GetComment(&CmtBuf)) // In GUI too, so "Test" command detects broken comments. + { + size_t CmtSize=CmtBuf.Size(); + wchar *ChPtr=wcschr(&CmtBuf[0],0x1A); + if (ChPtr!=NULL) + CmtSize=ChPtr-&CmtBuf[0]; + mprintf(L"\n"); + OutComment(&CmtBuf[0],CmtSize); + } +} + + diff --git a/reference/unrar/archive.cpp b/reference/unrar/archive.cpp new file mode 100644 index 00000000..e66bddb9 --- /dev/null +++ b/reference/unrar/archive.cpp @@ -0,0 +1,332 @@ +#include "rar.hpp" + +#include "arccmt.cpp" + + +Archive::Archive(RAROptions *InitCmd) +{ + Cmd=NULL; // Just in case we'll have an exception in 'new' below. + + DummyCmd=(InitCmd==NULL); + Cmd=DummyCmd ? (new RAROptions):InitCmd; + + OpenShared=Cmd->OpenShared; + Format=RARFMT15; + Solid=false; + Volume=false; + MainComment=false; + Locked=false; + Signed=false; + FirstVolume=false; + NewNumbering=false; + SFXSize=0; + LatestTime.Reset(); + Protected=false; + Encrypted=false; + FailedHeaderDecryption=false; + BrokenHeader=false; + LastReadBlock=0; + + CurBlockPos=0; + NextBlockPos=0; + + RecoverySize=-1; + RecoveryPercent=-1; + + memset(&MainHead,0,sizeof(MainHead)); + memset(&CryptHead,0,sizeof(CryptHead)); + memset(&EndArcHead,0,sizeof(EndArcHead)); + + VolNumber=0; + VolWrite=0; + AddingFilesSize=0; + AddingHeadersSize=0; + *FirstVolumeName=0; + + Splitting=false; + NewArchive=false; + + SilentOpen=false; + +} + + +Archive::~Archive() +{ + if (DummyCmd) + delete Cmd; +} + + +void Archive::CheckArc(bool EnableBroken) +{ + if (!IsArchive(EnableBroken)) + { + // If FailedHeaderDecryption is set, we already reported that archive + // password is incorrect. + if (!FailedHeaderDecryption) + uiMsg(UIERROR_BADARCHIVE,FileName); + ErrHandler.Exit(RARX_FATAL); + } +} + + +#if !defined(SFX_MODULE) +void Archive::CheckOpen(const wchar *Name) +{ + TOpen(Name); + CheckArc(false); +} +#endif + + +bool Archive::WCheckOpen(const wchar *Name) +{ + if (!WOpen(Name)) + return false; + if (!IsArchive(false)) + { + uiMsg(UIERROR_BADARCHIVE,FileName); + Close(); + return false; + } + return true; +} + + +RARFORMAT Archive::IsSignature(const byte *D,size_t Size) +{ + RARFORMAT Type=RARFMT_NONE; + if (Size>=1 && D[0]==0x52) +#ifndef SFX_MODULE + if (Size>=4 && D[1]==0x45 && D[2]==0x7e && D[3]==0x5e) + Type=RARFMT14; + else +#endif + if (Size>=7 && D[1]==0x61 && D[2]==0x72 && D[3]==0x21 && D[4]==0x1a && D[5]==0x07) + { + // We check the last signature byte, so we can return a sensible + // warning in case we'll want to change the archive format + // sometimes in the future. + if (D[6]==0) + Type=RARFMT15; + else + if (D[6]==1) + Type=RARFMT50; + else + if (D[6]==2) + Type=RARFMT_FUTURE; + } + return Type; +} + + +bool Archive::IsArchive(bool EnableBroken) +{ + Encrypted=false; + BrokenHeader=false; // Might be left from previous volume. + +#ifndef SFX_MODULE + if (IsDevice()) + { + uiMsg(UIERROR_INVALIDNAME,FileName,FileName); + return false; + } +#endif + if (Read(MarkHead.Mark,SIZEOF_MARKHEAD3)!=SIZEOF_MARKHEAD3) + return false; + SFXSize=0; + + RARFORMAT Type; + if ((Type=IsSignature(MarkHead.Mark,SIZEOF_MARKHEAD3))!=RARFMT_NONE) + { + Format=Type; + if (Format==RARFMT14) + Seek(Tell()-SIZEOF_MARKHEAD3,SEEK_SET); + } + else + { + Array Buffer(MAXSFXSIZE); + long CurPos=(long)Tell(); + int ReadSize=Read(&Buffer[0],Buffer.Size()-16); + for (int I=0;I0 && CurPos<28 && ReadSize>31) + { + char *D=&Buffer[28-CurPos]; + if (D[0]!=0x52 || D[1]!=0x53 || D[2]!=0x46 || D[3]!=0x58) + continue; + } + SFXSize=CurPos+I; + Seek(SFXSize,SEEK_SET); + if (Format==RARFMT15 || Format==RARFMT50) + Read(MarkHead.Mark,SIZEOF_MARKHEAD3); + break; + } + if (SFXSize==0) + return false; + } + if (Format==RARFMT_FUTURE) + { + uiMsg(UIERROR_NEWRARFORMAT,FileName); + return false; + } + if (Format==RARFMT50) // RAR 5.0 signature is by one byte longer. + { + Read(MarkHead.Mark+SIZEOF_MARKHEAD3,1); + if (MarkHead.Mark[SIZEOF_MARKHEAD3]!=0) + return false; + MarkHead.HeadSize=SIZEOF_MARKHEAD5; + } + else + MarkHead.HeadSize=SIZEOF_MARKHEAD3; + +#ifdef RARDLL + // If callback function is not set, we cannot get the password, + // so we skip the initial header processing for encrypted header archive. + // It leads to skipped archive comment, but the rest of archive data + // is processed correctly. + if (Cmd->Callback==NULL) + SilentOpen=true; +#endif + + // Skip the archive encryption header if any and read the main header. + while (ReadHeader()!=0) + { + HEADER_TYPE Type=GetHeaderType(); + // In RAR 5.0 we need to quit after reading HEAD_CRYPT if we wish to + // avoid the password prompt. + if (Type==HEAD_MAIN || SilentOpen && Type==HEAD_CRYPT) + break; + SeekToNext(); + } + + // This check allows to make RS based recovery even if password is incorrect. + // But we should not do it for EnableBroken or we'll get 'not RAR archive' + // messages when extracting encrypted archives with wrong password. + if (FailedHeaderDecryption && !EnableBroken) + return false; + + SeekToNext(); + if (BrokenHeader) // Main archive header is corrupt. + { + uiMsg(UIERROR_MHEADERBROKEN,FileName); + if (!EnableBroken) + return false; + } + + MainComment=MainHead.CommentInHeader; + + // If we process non-encrypted archive or can request a password, + // we set 'first volume' flag based on file attributes below. + // It is necessary for RAR 2.x archives, which did not have 'first volume' + // flag in main header. Also for all RAR formats we need to scan until + // first file header to set "comment" flag when reading service header. + // Unless we are in silent mode, we need to know about presence of comment + // immediately after IsArchive call. + if (!SilentOpen || !Encrypted) + { + SaveFilePos SavePos(*this); + int64 SaveCurBlockPos=CurBlockPos,SaveNextBlockPos=NextBlockPos; + HEADER_TYPE SaveCurHeaderType=CurHeaderType; + + while (ReadHeader()!=0) + { + HEADER_TYPE HeaderType=GetHeaderType(); + if (HeaderType==HEAD_SERVICE) + { + // If we have a split service headers, it surely indicates non-first + // volume. But not split service header does not guarantee the first + // volume, because we can have split file after non-split archive + // comment. So we do not quit from loop here. + FirstVolume=Volume && !SubHead.SplitBefore; + } + else + if (HeaderType==HEAD_FILE) + { + FirstVolume=Volume && !FileHead.SplitBefore; + break; + } + else + if (HeaderType==HEAD_ENDARC) // Might happen if archive contains only a split service header. + break; + SeekToNext(); + } + CurBlockPos=SaveCurBlockPos; + NextBlockPos=SaveNextBlockPos; + CurHeaderType=SaveCurHeaderType; + } + if (!Volume || FirstVolume) + wcscpy(FirstVolumeName,FileName); + + return true; +} + + + + +void Archive::SeekToNext() +{ + Seek(NextBlockPos,SEEK_SET); +} + + + + + + +// Calculate the block size including encryption fields and padding if any. +uint Archive::FullHeaderSize(size_t Size) +{ + if (Encrypted) + { + Size = ALIGN_VALUE(Size, CRYPT_BLOCK_SIZE); // Align to encryption block size. + if (Format == RARFMT50) + Size += SIZE_INITV; + else + Size += SIZE_SALT30; + } + return uint(Size); +} + + + + +#ifdef USE_QOPEN +bool Archive::Open(const wchar *Name,uint Mode) +{ + // Important if we reuse Archive object and it has virtual QOpen + // file position not matching real. For example, for 'l -v volname'. + QOpen.Unload(); + + return File::Open(Name,Mode); +} + + +int Archive::Read(void *Data,size_t Size) +{ + size_t Result; + if (QOpen.Read(Data,Size,Result)) + return (int)Result; + return File::Read(Data,Size); +} + + +void Archive::Seek(int64 Offset,int Method) +{ + if (!QOpen.Seek(Offset,Method)) + File::Seek(Offset,Method); +} + + +int64 Archive::Tell() +{ + int64 QPos; + if (QOpen.Tell(&QPos)) + return QPos; + return File::Tell(); +} +#endif + diff --git a/reference/unrar/archive.hpp b/reference/unrar/archive.hpp new file mode 100644 index 00000000..6e85b9ff --- /dev/null +++ b/reference/unrar/archive.hpp @@ -0,0 +1,151 @@ +#ifndef _RAR_ARCHIVE_ +#define _RAR_ARCHIVE_ + +class PPack; +class RawRead; +class RawWrite; + +enum NOMODIFY_FLAGS +{ + NMDF_ALLOWLOCK=1,NMDF_ALLOWANYVOLUME=2,NMDF_ALLOWFIRSTVOLUME=4 +}; + +enum RARFORMAT {RARFMT_NONE,RARFMT14,RARFMT15,RARFMT50,RARFMT_FUTURE}; + +enum ADDSUBDATA_FLAGS +{ + ASDF_SPLIT = 1, // Allow to split archive just before header if necessary. + ASDF_COMPRESS = 2, // Allow to compress data following subheader. + ASDF_CRYPT = 4, // Encrypt data after subheader if password is set. + ASDF_CRYPTIFHEADERS = 8 // Encrypt data after subheader only in -hp mode. +}; + +class Archive:public File +{ + private: + void UpdateLatestTime(FileHeader *CurBlock); + void ConvertNameCase(wchar *Name); + void ConvertFileHeader(FileHeader *hd); + void WriteBlock50(HEADER_TYPE HeaderType,BaseBlock *wb,bool OnlySetSize,bool NonFinalWrite); + size_t ReadHeader14(); + size_t ReadHeader15(); + size_t ReadHeader50(); + void ProcessExtra50(RawRead *Raw,size_t ExtraSize,BaseBlock *bb); + void RequestArcPassword(); + void UnexpEndArcMsg(); + void BrokenHeaderMsg(); + void UnkEncVerMsg(const wchar *Name); + void UnkEncVerMsg(); + bool ReadCommentData(Array *CmtData); + +#if !defined(RAR_NOCRYPT) + CryptData HeadersCrypt; +#endif + ComprDataIO SubDataIO; + bool DummyCmd; + RAROptions *Cmd; + + int64 RecoverySize; + int RecoveryPercent; + + RarTime LatestTime; + int LastReadBlock; + HEADER_TYPE CurHeaderType; + + bool SilentOpen; +#ifdef USE_QOPEN + QuickOpen QOpen; +#endif + public: + Archive(RAROptions *InitCmd=NULL); + ~Archive(); + static RARFORMAT IsSignature(const byte *D,size_t Size); + bool IsArchive(bool EnableBroken); + size_t SearchBlock(HEADER_TYPE HeaderType); + size_t SearchSubBlock(const wchar *Type); + size_t SearchRR(); + void WriteBlock(HEADER_TYPE HeaderType,BaseBlock *wb=NULL,bool OnlySetSize=false,bool NonFinalWrite=false); + void SetBlockSize(HEADER_TYPE HeaderType,BaseBlock *wb=NULL) {WriteBlock(HeaderType,wb,true);} + size_t ReadHeader(); + void CheckArc(bool EnableBroken); + void CheckOpen(const wchar *Name); + bool WCheckOpen(const wchar *Name); + bool GetComment(Array *CmtData); + void ViewComment(); + void SetLatestTime(RarTime *NewTime); + void SeekToNext(); + bool CheckAccess(); + bool IsArcDir(); + void ConvertAttributes(); + void VolSubtractHeaderSize(size_t SubSize); + uint FullHeaderSize(size_t Size); + int64 GetStartPos(); + void AddSubData(byte *SrcData,uint64 DataSize,File *SrcFile, + const wchar *Name,uint Flags); + bool ReadSubData(Array *UnpData,File *DestFile); + HEADER_TYPE GetHeaderType() {return CurHeaderType;}; + RAROptions* GetRAROptions() {return Cmd;} + void SetSilentOpen(bool Mode) {SilentOpen=Mode;} +#if 0 + void GetRecoveryInfo(bool Required,int64 *Size,int *Percent); +#endif +#ifdef USE_QOPEN + bool Open(const wchar *Name,uint Mode=FMF_READ); + int Read(void *Data,size_t Size); + void Seek(int64 Offset,int Method); + int64 Tell(); + void QOpenUnload() {QOpen.Unload();} +#endif + + BaseBlock ShortBlock; + MarkHeader MarkHead; + MainHeader MainHead; + CryptHeader CryptHead; + FileHeader FileHead; + EndArcHeader EndArcHead; + SubBlockHeader SubBlockHead; + FileHeader SubHead; + CommentHeader CommHead; + ProtectHeader ProtectHead; + AVHeader AVHead; + SignHeader SignHead; + UnixOwnersHeader UOHead; + MacFInfoHeader MACHead; + EAHeader EAHead; + StreamHeader StreamHead; + + int64 CurBlockPos; + int64 NextBlockPos; + + RARFORMAT Format; + bool Solid; + bool Volume; + bool MainComment; + bool Locked; + bool Signed; + bool FirstVolume; + bool NewNumbering; + bool Protected; + bool Encrypted; + size_t SFXSize; + bool BrokenHeader; + bool FailedHeaderDecryption; + +#if !defined(RAR_NOCRYPT) + byte ArcSalt[SIZE_SALT50]; +#endif + + bool Splitting; + + uint VolNumber; + int64 VolWrite; + uint64 AddingFilesSize; + uint64 AddingHeadersSize; + + bool NewArchive; + + wchar FirstVolumeName[NM]; +}; + + +#endif diff --git a/reference/unrar/arcread.cpp b/reference/unrar/arcread.cpp new file mode 100644 index 00000000..1120f682 --- /dev/null +++ b/reference/unrar/arcread.cpp @@ -0,0 +1,1437 @@ +#include "rar.hpp" + +size_t Archive::ReadHeader() +{ + // Once we failed to decrypt an encrypted block, there is no reason to + // attempt to do it further. We'll never be successful and only generate + // endless errors. + if (FailedHeaderDecryption) + return 0; + + CurBlockPos=Tell(); + + size_t ReadSize; + switch(Format) + { +#ifndef SFX_MODULE + case RARFMT14: + ReadSize=ReadHeader14(); + break; +#endif + case RARFMT15: + ReadSize=ReadHeader15(); + break; + case RARFMT50: + ReadSize=ReadHeader50(); + break; + } + + if (ReadSize>0 && NextBlockPos<=CurBlockPos) + { + BrokenHeaderMsg(); + return 0; + } + return ReadSize; +} + + +size_t Archive::SearchBlock(HEADER_TYPE HeaderType) +{ + size_t Size,Count=0; + while ((Size=ReadHeader())!=0 && + (HeaderType==HEAD_ENDARC || GetHeaderType()!=HEAD_ENDARC)) + { + if ((++Count & 127)==0) + Wait(); + if (GetHeaderType()==HeaderType) + return Size; + SeekToNext(); + } + return 0; +} + + +size_t Archive::SearchSubBlock(const wchar *Type) +{ + size_t Size,Count=0; + while ((Size=ReadHeader())!=0 && GetHeaderType()!=HEAD_ENDARC) + { + if ((++Count & 127)==0) + Wait(); + if (GetHeaderType()==HEAD_SERVICE && SubHead.CmpName(Type)) + return Size; + SeekToNext(); + } + return 0; +} + + +size_t Archive::SearchRR() +{ + // If locator extra field is available for recovery record, let's utilize it. + if (MainHead.Locator && MainHead.RROffset!=0) + { + uint64 CurPos=Tell(); + Seek(MainHead.RROffset,SEEK_SET); + size_t Size=ReadHeader(); + if (Size!=0 && !BrokenHeader && GetHeaderType()==HEAD_SERVICE && SubHead.CmpName(SUBHEAD_TYPE_RR)) + return Size; + Seek(CurPos,SEEK_SET); + } + // Otherwise scan the entire archive to find the recovery record. + return SearchSubBlock(SUBHEAD_TYPE_RR); +} + + +void Archive::UnexpEndArcMsg() +{ + int64 ArcSize=FileLength(); + + // If block positions are equal to file size, this is not an error. + // It can happen when we reached the end of older RAR 1.5 archive, + // which did not have the end of archive block. + if (CurBlockPos!=ArcSize || NextBlockPos!=ArcSize) + { + uiMsg(UIERROR_UNEXPEOF,FileName); + ErrHandler.SetErrorCode(RARX_WARNING); + } +} + + +void Archive::BrokenHeaderMsg() +{ + uiMsg(UIERROR_HEADERBROKEN,FileName); + BrokenHeader=true; + ErrHandler.SetErrorCode(RARX_CRC); +} + + +void Archive::UnkEncVerMsg(const wchar *Name) +{ + uiMsg(UIERROR_UNKNOWNENCMETHOD,FileName,Name); + ErrHandler.SetErrorCode(RARX_WARNING); +} + + +size_t Archive::ReadHeader15() +{ + RawRead Raw(this); + + bool Decrypt=Encrypted && CurBlockPos>(int64)SFXSize+SIZEOF_MARKHEAD3; + + if (Decrypt) + { +#ifdef RAR_NOCRYPT // For rarext.dll and unrar_nocrypt.dll. + return 0; +#else + RequestArcPassword(); + + byte Salt[SIZE_SALT30]; + if (Read(Salt,SIZE_SALT30)!=SIZE_SALT30) + { + UnexpEndArcMsg(); + return 0; + } + HeadersCrypt.SetCryptKeys(false,CRYPT_RAR30,&Cmd->Password,Salt,NULL,0,NULL,NULL); + Raw.SetCrypt(&HeadersCrypt); +#endif + } + + Raw.Read(SIZEOF_SHORTBLOCKHEAD); + if (Raw.Size()==0) + { + UnexpEndArcMsg(); + return 0; + } + + ShortBlock.HeadCRC=Raw.Get2(); + + ShortBlock.Reset(); + + uint HeaderType=Raw.Get1(); + ShortBlock.Flags=Raw.Get2(); + ShortBlock.SkipIfUnknown=(ShortBlock.Flags & SKIP_IF_UNKNOWN)!=0; + ShortBlock.HeadSize=Raw.Get2(); + + ShortBlock.HeaderType=(HEADER_TYPE)HeaderType; + if (ShortBlock.HeadSizeReset(); + + *(BaseBlock *)hd=ShortBlock; + + hd->SplitBefore=(hd->Flags & LHD_SPLIT_BEFORE)!=0; + hd->SplitAfter=(hd->Flags & LHD_SPLIT_AFTER)!=0; + hd->Encrypted=(hd->Flags & LHD_PASSWORD)!=0; + hd->SaltSet=(hd->Flags & LHD_SALT)!=0; + hd->Solid=FileBlock && (hd->Flags & LHD_SOLID)!=0; + hd->SubBlock=!FileBlock && (hd->Flags & LHD_SOLID)!=0; + hd->Dir=(hd->Flags & LHD_WINDOWMASK)==LHD_DIRECTORY; + hd->WinSize=hd->Dir ? 0:0x10000<<((hd->Flags & LHD_WINDOWMASK)>>5); + hd->CommentInHeader=(hd->Flags & LHD_COMMENT)!=0; + hd->Version=(hd->Flags & LHD_VERSION)!=0; + + hd->DataSize=Raw.Get4(); + uint LowUnpSize=Raw.Get4(); + hd->HostOS=Raw.Get1(); + + hd->FileHash.Type=HASH_CRC32; + hd->FileHash.CRC32=Raw.Get4(); + + uint FileTime=Raw.Get4(); + hd->UnpVer=Raw.Get1(); + hd->Method=Raw.Get1()-0x30; + size_t NameSize=Raw.Get2(); + hd->FileAttr=Raw.Get4(); + + hd->CryptMethod=CRYPT_NONE; + if (hd->Encrypted) + switch(hd->UnpVer) + { + case 13: hd->CryptMethod=CRYPT_RAR13; break; + case 15: hd->CryptMethod=CRYPT_RAR15; break; + case 20: + case 26: hd->CryptMethod=CRYPT_RAR20; break; + default: hd->CryptMethod=CRYPT_RAR30; break; + } + + hd->HSType=HSYS_UNKNOWN; + if (hd->HostOS==HOST_UNIX || hd->HostOS==HOST_BEOS) + hd->HSType=HSYS_UNIX; + else + if (hd->HostOSHSType=HSYS_WINDOWS; + + hd->RedirType=FSREDIR_NONE; + + // RAR 4.x Unix symlink. + if (hd->HostOS==HOST_UNIX && (hd->FileAttr & 0xF000)==0xA000) + { + hd->RedirType=FSREDIR_UNIXSYMLINK; + *hd->RedirName=0; + } + + hd->Inherited=!FileBlock && (hd->SubFlags & SUBHEAD_FLAGS_INHERITED)!=0; + + hd->LargeFile=(hd->Flags & LHD_LARGE)!=0; + + uint HighPackSize,HighUnpSize; + if (hd->LargeFile) + { + HighPackSize=Raw.Get4(); + HighUnpSize=Raw.Get4(); + hd->UnknownUnpSize=(LowUnpSize==0xffffffff && HighUnpSize==0xffffffff); + } + else + { + HighPackSize=HighUnpSize=0; + // UnpSize equal to 0xffffffff without LHD_LARGE flag indicates + // that we do not know the unpacked file size and must unpack it + // until we find the end of file marker in compressed data. + hd->UnknownUnpSize=(LowUnpSize==0xffffffff); + } + hd->PackSize=INT32TO64(HighPackSize,hd->DataSize); + hd->UnpSize=INT32TO64(HighUnpSize,LowUnpSize); + if (hd->UnknownUnpSize) + hd->UnpSize=INT64NDF; + + char FileName[NM*4]; + size_t ReadNameSize=Min(NameSize,ASIZE(FileName)-1); + Raw.GetB((byte *)FileName,ReadNameSize); + FileName[ReadNameSize]=0; + + if (FileBlock) + { + *hd->FileName=0; + if ((hd->Flags & LHD_UNICODE)!=0) + { + EncodeFileName NameCoder; + size_t Length=strlen(FileName); + Length++; + if (ReadNameSize>Length) + NameCoder.Decode(FileName,(byte *)FileName+Length, + ReadNameSize-Length,hd->FileName, + ASIZE(hd->FileName)); + } + + if (*hd->FileName==0) + ArcCharToWide(FileName,hd->FileName,ASIZE(hd->FileName),ACTW_OEM); + +#ifndef SFX_MODULE + ConvertNameCase(hd->FileName); +#endif + ConvertFileHeader(hd); + } + else + { + CharToWide(FileName,hd->FileName,ASIZE(hd->FileName)); + + // Calculate the size of optional data. + int DataSize=int(hd->HeadSize-NameSize-SIZEOF_FILEHEAD3); + if ((hd->Flags & LHD_SALT)!=0) + DataSize-=SIZE_SALT30; + + if (DataSize>0) + { + // Here we read optional additional fields for subheaders. + // They are stored after the file name and before salt. + hd->SubData.Alloc(DataSize); + Raw.GetB(&hd->SubData[0],DataSize); + if (hd->CmpName(SUBHEAD_TYPE_RR)) + { + byte *D=&hd->SubData[8]; + RecoverySize=D[0]+((uint)D[1]<<8)+((uint)D[2]<<16)+((uint)D[3]<<24); + RecoverySize*=512; // Sectors to size. + int64 CurPos=Tell(); + RecoveryPercent=ToPercent(RecoverySize,CurPos); + // Round fractional percent exceeding .5 to upper value. + if (ToPercent(RecoverySize+CurPos/200,CurPos)>RecoveryPercent) + RecoveryPercent++; + } + } + + if (hd->CmpName(SUBHEAD_TYPE_CMT)) + MainComment=true; + } + if ((hd->Flags & LHD_SALT)!=0) + Raw.GetB(hd->Salt,SIZE_SALT30); + hd->mtime.SetDos(FileTime); + if ((hd->Flags & LHD_EXTTIME)!=0) + { + ushort Flags=Raw.Get2(); + RarTime *tbl[4]; + tbl[0]=&FileHead.mtime; + tbl[1]=&FileHead.ctime; + tbl[2]=&FileHead.atime; + tbl[3]=NULL; // Archive time is not used now. + for (int I=0;I<4;I++) + { + RarTime *CurTime=tbl[I]; + uint rmode=Flags>>(3-I)*4; + if ((rmode & 8)==0 || CurTime==NULL) + continue; + if (I!=0) + { + uint DosTime=Raw.Get4(); + CurTime->SetDos(DosTime); + } + RarLocalTime rlt; + CurTime->GetLocal(&rlt); + if (rmode & 4) + rlt.Second++; + rlt.Reminder=0; + int count=rmode&3; + for (int J=0;JSetLocal(&rlt); + } + } + NextBlockPos+=hd->PackSize; + bool CRCProcessedOnly=hd->CommentInHeader; + ushort HeaderCRC=Raw.GetCRC15(CRCProcessedOnly); + if (hd->HeadCRC!=HeaderCRC) + { + BrokenHeader=true; + ErrHandler.SetErrorCode(RARX_WARNING); + + // If we have a broken encrypted header, we do not need to display + // the error message here, because it will be displayed for such + // headers later in this function. Also such headers are unlikely + // to have anything sensible in file name field, so it is useless + // to display the file name. + if (!Decrypt) + uiMsg(UIERROR_FHEADERBROKEN,Archive::FileName,hd->FileName); + } + } + break; + case HEAD_ENDARC: + *(BaseBlock *)&EndArcHead=ShortBlock; + EndArcHead.NextVolume=(EndArcHead.Flags & EARC_NEXT_VOLUME)!=0; + EndArcHead.DataCRC=(EndArcHead.Flags & EARC_DATACRC)!=0; + EndArcHead.RevSpace=(EndArcHead.Flags & EARC_REVSPACE)!=0; + EndArcHead.StoreVolNumber=(EndArcHead.Flags & EARC_VOLNUMBER)!=0; + if (EndArcHead.DataCRC) + EndArcHead.ArcDataCRC=Raw.Get4(); + if (EndArcHead.StoreVolNumber) + VolNumber=EndArcHead.VolNumber=Raw.Get2(); + break; +#ifndef SFX_MODULE + case HEAD3_CMT: + *(BaseBlock *)&CommHead=ShortBlock; + CommHead.UnpSize=Raw.Get2(); + CommHead.UnpVer=Raw.Get1(); + CommHead.Method=Raw.Get1(); + CommHead.CommCRC=Raw.Get2(); + break; + case HEAD3_SIGN: + *(BaseBlock *)&SignHead=ShortBlock; + SignHead.CreationTime=Raw.Get4(); + SignHead.ArcNameSize=Raw.Get2(); + SignHead.UserNameSize=Raw.Get2(); + break; + case HEAD3_AV: + *(BaseBlock *)&AVHead=ShortBlock; + AVHead.UnpVer=Raw.Get1(); + AVHead.Method=Raw.Get1(); + AVHead.AVVer=Raw.Get1(); + AVHead.AVInfoCRC=Raw.Get4(); + break; + case HEAD3_PROTECT: + *(BaseBlock *)&ProtectHead=ShortBlock; + ProtectHead.DataSize=Raw.Get4(); + ProtectHead.Version=Raw.Get1(); + ProtectHead.RecSectors=Raw.Get2(); + ProtectHead.TotalBlocks=Raw.Get4(); + Raw.GetB(ProtectHead.Mark,8); + NextBlockPos+=ProtectHead.DataSize; + RecoverySize=ProtectHead.RecSectors*512; + break; + case HEAD3_OLDSERVICE: + *(BaseBlock *)&SubBlockHead=ShortBlock; + SubBlockHead.DataSize=Raw.Get4(); + NextBlockPos+=SubBlockHead.DataSize; + SubBlockHead.SubType=Raw.Get2(); + SubBlockHead.Level=Raw.Get1(); + switch(SubBlockHead.SubType) + { + case UO_HEAD: + *(SubBlockHeader *)&UOHead=SubBlockHead; + UOHead.OwnerNameSize=Raw.Get2(); + UOHead.GroupNameSize=Raw.Get2(); + if (UOHead.OwnerNameSize>=ASIZE(UOHead.OwnerName)) + UOHead.OwnerNameSize=ASIZE(UOHead.OwnerName)-1; + if (UOHead.GroupNameSize>=ASIZE(UOHead.GroupName)) + UOHead.GroupNameSize=ASIZE(UOHead.GroupName)-1; + Raw.GetB(UOHead.OwnerName,UOHead.OwnerNameSize); + Raw.GetB(UOHead.GroupName,UOHead.GroupNameSize); + UOHead.OwnerName[UOHead.OwnerNameSize]=0; + UOHead.GroupName[UOHead.GroupNameSize]=0; + break; + case MAC_HEAD: + *(SubBlockHeader *)&MACHead=SubBlockHead; + MACHead.fileType=Raw.Get4(); + MACHead.fileCreator=Raw.Get4(); + break; + case EA_HEAD: + case BEEA_HEAD: + case NTACL_HEAD: + *(SubBlockHeader *)&EAHead=SubBlockHead; + EAHead.UnpSize=Raw.Get4(); + EAHead.UnpVer=Raw.Get1(); + EAHead.Method=Raw.Get1(); + EAHead.EACRC=Raw.Get4(); + break; + case STREAM_HEAD: + *(SubBlockHeader *)&StreamHead=SubBlockHead; + StreamHead.UnpSize=Raw.Get4(); + StreamHead.UnpVer=Raw.Get1(); + StreamHead.Method=Raw.Get1(); + StreamHead.StreamCRC=Raw.Get4(); + StreamHead.StreamNameSize=Raw.Get2(); + if (StreamHead.StreamNameSize>=ASIZE(StreamHead.StreamName)) + StreamHead.StreamNameSize=ASIZE(StreamHead.StreamName)-1; + Raw.GetB(StreamHead.StreamName,StreamHead.StreamNameSize); + StreamHead.StreamName[StreamHead.StreamNameSize]=0; + break; + } + break; +#endif + default: + if (ShortBlock.Flags & LONG_BLOCK) + NextBlockPos+=Raw.Get4(); + break; + } + + ushort HeaderCRC=Raw.GetCRC15(false); + + // Old AV header does not have header CRC properly set. + if (ShortBlock.HeadCRC!=HeaderCRC && ShortBlock.HeaderType!=HEAD3_SIGN && + ShortBlock.HeaderType!=HEAD3_AV) + { + bool Recovered=false; + if (ShortBlock.HeaderType==HEAD_ENDARC && EndArcHead.RevSpace) + { + // Last 7 bytes of recovered volume can contain zeroes, because + // REV files store its own information (volume number, etc.) here. + SaveFilePos SavePos(*this); + int64 Length=Tell(); + Seek(Length-7,SEEK_SET); + Recovered=true; + for (int J=0;J<7;J++) + if (GetByte()!=0) + Recovered=false; + } + if (!Recovered) + { + BrokenHeader=true; + ErrHandler.SetErrorCode(RARX_CRC); + + if (Decrypt) + { + uiMsg(UIERROR_CHECKSUMENC,FileName,FileName); + FailedHeaderDecryption=true; + return 0; + } + } + } + + if (NextBlockPos<=CurBlockPos) + { + BrokenHeaderMsg(); + return 0; + } + + return Raw.Size(); +} + + +size_t Archive::ReadHeader50() +{ + RawRead Raw(this); + + bool Decrypt=Encrypted && CurBlockPos>(int64)SFXSize+SIZEOF_MARKHEAD5; + + if (Decrypt) + { +#if defined(RAR_NOCRYPT) + return 0; +#else + RequestArcPassword(); + + byte HeadersInitV[SIZE_INITV]; + if (Read(HeadersInitV,SIZE_INITV)!=SIZE_INITV) + { + UnexpEndArcMsg(); + return 0; + } + + byte PswCheck[SIZE_PSWCHECK]; + HeadersCrypt.SetCryptKeys(false,CRYPT_RAR50,&Cmd->Password,CryptHead.Salt,HeadersInitV,CryptHead.Lg2Count,NULL,PswCheck); + // Verify password validity. + if (CryptHead.UsePswCheck && memcmp(PswCheck,CryptHead.PswCheck,SIZE_PSWCHECK)!=0) + { + uiMsg(UIERROR_BADPSW,FileName); + FailedHeaderDecryption=true; + ErrHandler.SetErrorCode(RARX_BADPWD); + return 0; + } + + Raw.SetCrypt(&HeadersCrypt); +#endif + } + + // Header size must not occupy more than 3 variable length integer bytes + // resulting in 2 MB maximum header size, so here we read 4 byte CRC32 + // followed by 3 bytes or less of header size. + const size_t FirstReadSize=7; // Smallest possible block size. + if (Raw.Read(FirstReadSize)=ShortBlock.HeadSize) + { + BrokenHeaderMsg(); + return 0; + } + } + + uint64 DataSize=0; + if ((ShortBlock.Flags & HFL_DATA)!=0) + DataSize=Raw.GetV(); + + NextBlockPos=CurBlockPos+FullHeaderSize(ShortBlock.HeadSize)+DataSize; + + switch(ShortBlock.HeaderType) + { + case HEAD_CRYPT: + { + *(BaseBlock *)&CryptHead=ShortBlock; + uint CryptVersion=(uint)Raw.GetV(); + if (CryptVersion>CRYPT_VERSION) + { + UnkEncVerMsg(FileName); + return 0; + } + uint EncFlags=(uint)Raw.GetV(); + CryptHead.UsePswCheck=(EncFlags & CHFL_CRYPT_PSWCHECK)!=0; + CryptHead.Lg2Count=Raw.Get1(); + if (CryptHead.Lg2Count>CRYPT5_KDF_LG2_COUNT_MAX) + { + UnkEncVerMsg(FileName); + return 0; + } + Raw.GetB(CryptHead.Salt,SIZE_SALT50); + if (CryptHead.UsePswCheck) + { + Raw.GetB(CryptHead.PswCheck,SIZE_PSWCHECK); + + byte csum[SIZE_PSWCHECK_CSUM]; + Raw.GetB(csum,SIZE_PSWCHECK_CSUM); + + sha256_context ctx; + sha256_init(&ctx); + sha256_process(&ctx, CryptHead.PswCheck, SIZE_PSWCHECK); + + byte Digest[SHA256_DIGEST_SIZE]; + sha256_done(&ctx, Digest); + + CryptHead.UsePswCheck=memcmp(csum,Digest,SIZE_PSWCHECK_CSUM)==0; + } + Encrypted=true; + } + break; + case HEAD_MAIN: + { + MainHead.Reset(); + *(BaseBlock *)&MainHead=ShortBlock; + uint ArcFlags=(uint)Raw.GetV(); + + Volume=(ArcFlags & MHFL_VOLUME)!=0; + Solid=(ArcFlags & MHFL_SOLID)!=0; + Locked=(ArcFlags & MHFL_LOCK)!=0; + Protected=(ArcFlags & MHFL_PROTECT)!=0; + Signed=false; + NewNumbering=true; + + if ((ArcFlags & MHFL_VOLNUMBER)!=0) + VolNumber=(uint)Raw.GetV(); + else + VolNumber=0; + FirstVolume=Volume && VolNumber==0; + + if (ExtraSize!=0) + ProcessExtra50(&Raw,(size_t)ExtraSize,&MainHead); + +#ifdef USE_QOPEN + if (MainHead.Locator && MainHead.QOpenOffset>0 && Cmd->QOpenMode!=QOPEN_NONE) + { + // We seek to QO block in the end of archive when processing + // QOpen.Load, so we need to preserve current block positions + // to not break normal archive processing by calling function. + int64 SaveCurBlockPos=CurBlockPos,SaveNextBlockPos=NextBlockPos; + HEADER_TYPE SaveCurHeaderType=CurHeaderType; + + QOpen.Init(this,false); + QOpen.Load(MainHead.QOpenOffset); + + CurBlockPos=SaveCurBlockPos; + NextBlockPos=SaveNextBlockPos; + CurHeaderType=SaveCurHeaderType; + } +#endif + } + break; + case HEAD_FILE: + case HEAD_SERVICE: + { + FileHeader *hd=ShortBlock.HeaderType==HEAD_FILE ? &FileHead:&SubHead; + hd->Reset(); + *(BaseBlock *)hd=ShortBlock; + + bool FileBlock=ShortBlock.HeaderType==HEAD_FILE; + + hd->LargeFile=true; + + hd->PackSize=DataSize; + hd->FileFlags=(uint)Raw.GetV(); + hd->UnpSize=Raw.GetV(); + + hd->UnknownUnpSize=(hd->FileFlags & FHFL_UNPUNKNOWN)!=0; + if (hd->UnknownUnpSize) + hd->UnpSize=INT64NDF; + + hd->MaxSize=Max(hd->PackSize,hd->UnpSize); + hd->FileAttr=(uint)Raw.GetV(); + if ((hd->FileFlags & FHFL_UTIME)!=0) + hd->mtime.SetUnix((time_t)Raw.Get4()); + + hd->FileHash.Type=HASH_NONE; + if ((hd->FileFlags & FHFL_CRC32)!=0) + { + hd->FileHash.Type=HASH_CRC32; + hd->FileHash.CRC32=Raw.Get4(); + } + + hd->RedirType=FSREDIR_NONE; + + uint CompInfo=(uint)Raw.GetV(); + hd->Method=(CompInfo>>7) & 7; + + // "+ 50" to not mix with old RAR format algorithms. For example, + // we may need to use the compression algorithm 15 in the future, + // but it was already used in RAR 1.5 and Unpack needs to distinguish + // them. + hd->UnpVer=(CompInfo & 0x3f) + 50; + + hd->HostOS=(byte)Raw.GetV(); + size_t NameSize=(size_t)Raw.GetV(); + hd->Inherited=(ShortBlock.Flags & HFL_INHERITED)!=0; + + hd->HSType=HSYS_UNKNOWN; + if (hd->HostOS==HOST5_UNIX) + hd->HSType=HSYS_UNIX; + else + if (hd->HostOS==HOST5_WINDOWS) + hd->HSType=HSYS_WINDOWS; + + hd->SplitBefore=(hd->Flags & HFL_SPLITBEFORE)!=0; + hd->SplitAfter=(hd->Flags & HFL_SPLITAFTER)!=0; + hd->SubBlock=(hd->Flags & HFL_CHILD)!=0; + hd->Solid=FileBlock && (CompInfo & FCI_SOLID)!=0; + hd->Dir=(hd->FileFlags & FHFL_DIRECTORY)!=0; + hd->WinSize=hd->Dir ? 0:size_t(0x20000)<<((CompInfo>>10)&0xf); + + hd->CryptMethod=hd->Encrypted ? CRYPT_RAR50:CRYPT_NONE; + + char FileName[NM*4]; + size_t ReadNameSize=Min(NameSize,ASIZE(FileName)-1); + Raw.GetB((byte *)FileName,ReadNameSize); + FileName[ReadNameSize]=0; + + UtfToWide(FileName,hd->FileName,ASIZE(hd->FileName)); + + // Should do it before converting names, because extra fields can + // affect name processing, like in case of NTFS streams. + if (ExtraSize!=0) + ProcessExtra50(&Raw,(size_t)ExtraSize,hd); + + if (FileBlock) + { +#ifndef SFX_MODULE + ConvertNameCase(hd->FileName); +#endif + ConvertFileHeader(hd); + } + + if (!FileBlock && hd->CmpName(SUBHEAD_TYPE_CMT)) + MainComment=true; + +#if 0 + // For RAR5 format we read the user specified recovery percent here. + // It would be useful to do it for shell extension too, so we display + // the correct recovery record size in archive properties. But then + // we would need to include the entire recovery record processing + // code to shell extension, which is not done now. + if (!FileBlock && hd->CmpName(SUBHEAD_TYPE_RR) && hd->SubData.Size()>0) + { + RecoveryPercent=hd->SubData[0]; + RSBlockHeader Header; + GetRRInfo(this,&Header); + RecoverySize=Header.RecSectionSize*Header.RecCount; + } +#endif + + if (BadCRC) // Add the file name to broken header message displayed above. + uiMsg(UIERROR_FHEADERBROKEN,Archive::FileName,hd->FileName); + } + break; + case HEAD_ENDARC: + { + *(BaseBlock *)&EndArcHead=ShortBlock; + uint ArcFlags=(uint)Raw.GetV(); + EndArcHead.NextVolume=(ArcFlags & EHFL_NEXTVOLUME)!=0; + EndArcHead.StoreVolNumber=false; + EndArcHead.DataCRC=false; + EndArcHead.RevSpace=false; + } + break; + } + + if (NextBlockPos<=CurBlockPos) + { + BrokenHeaderMsg(); + return 0; + } + return Raw.Size(); +} + + +#if !defined(RAR_NOCRYPT) +void Archive::RequestArcPassword() +{ + if (!Cmd->Password.IsSet()) + { +#ifdef RARDLL + if (Cmd->Callback!=NULL) + { + wchar PasswordW[MAXPASSWORD]; + *PasswordW=0; + if (Cmd->Callback(UCM_NEEDPASSWORDW,Cmd->UserData,(LPARAM)PasswordW,ASIZE(PasswordW))==-1) + *PasswordW=0; + if (*PasswordW==0) + { + char PasswordA[MAXPASSWORD]; + *PasswordA=0; + if (Cmd->Callback(UCM_NEEDPASSWORD,Cmd->UserData,(LPARAM)PasswordA,ASIZE(PasswordA))==-1) + *PasswordA=0; + GetWideName(PasswordA,NULL,PasswordW,ASIZE(PasswordW)); + cleandata(PasswordA,sizeof(PasswordA)); + } + Cmd->Password.Set(PasswordW); + cleandata(PasswordW,sizeof(PasswordW)); + } + if (!Cmd->Password.IsSet()) + { + Close(); + Cmd->DllError=ERAR_MISSING_PASSWORD; + ErrHandler.Exit(RARX_USERBREAK); + } +#else + if (!uiGetPassword(UIPASSWORD_ARCHIVE,FileName,&Cmd->Password) || + !Cmd->Password.IsSet()) + { + Close(); + uiMsg(UIERROR_INCERRCOUNT); + ErrHandler.Exit(RARX_USERBREAK); + } +#endif + Cmd->ManualPassword=true; + } +} +#endif + + +void Archive::ProcessExtra50(RawRead *Raw,size_t ExtraSize,BaseBlock *bb) +{ + // Read extra data from the end of block skipping any fields before it. + size_t ExtraStart=Raw->Size()-ExtraSize; + if (ExtraStartGetPos()) + return; + Raw->SetPos(ExtraStart); + while (Raw->DataLeft()>=2) + { + int64 FieldSize=Raw->GetV(); + if (FieldSize==0 || Raw->DataLeft()==0 || FieldSize>(int64)Raw->DataLeft()) + break; + size_t NextPos=size_t(Raw->GetPos()+FieldSize); + uint64 FieldType=Raw->GetV(); + + FieldSize=int64(NextPos-Raw->GetPos()); // Field size without size and type fields. + + if (bb->HeaderType==HEAD_MAIN) + { + MainHeader *hd=(MainHeader *)bb; + if (FieldType==MHEXTRA_LOCATOR) + { + hd->Locator=true; + uint Flags=(uint)Raw->GetV(); + if ((Flags & MHEXTRA_LOCATOR_QLIST)!=0) + { + uint64 Offset=Raw->GetV(); + if (Offset!=0) // 0 means that reserved space was not enough to write the offset. + hd->QOpenOffset=Offset+CurBlockPos; + } + if ((Flags & MHEXTRA_LOCATOR_RR)!=0) + { + uint64 Offset=Raw->GetV(); + if (Offset!=0) // 0 means that reserved space was not enough to write the offset. + hd->RROffset=Offset+CurBlockPos; + } + } + } + + if (bb->HeaderType==HEAD_FILE || bb->HeaderType==HEAD_SERVICE) + { + FileHeader *hd=(FileHeader *)bb; + switch(FieldType) + { + case FHEXTRA_CRYPT: + { + FileHeader *hd=(FileHeader *)bb; + uint EncVersion=(uint)Raw->GetV(); + if (EncVersion > CRYPT_VERSION) + UnkEncVerMsg(hd->FileName); + else + { + uint Flags=(uint)Raw->GetV(); + hd->UsePswCheck=(Flags & FHEXTRA_CRYPT_PSWCHECK)!=0; + hd->UseHashKey=(Flags & FHEXTRA_CRYPT_HASHMAC)!=0; + hd->Lg2Count=Raw->Get1(); + if (hd->Lg2Count>CRYPT5_KDF_LG2_COUNT_MAX) + UnkEncVerMsg(hd->FileName); + Raw->GetB(hd->Salt,SIZE_SALT50); + Raw->GetB(hd->InitV,SIZE_INITV); + if (hd->UsePswCheck) + { + Raw->GetB(hd->PswCheck,SIZE_PSWCHECK); + + // It is important to know if password check data is valid. + // If it is damaged and header CRC32 fails to detect it, + // archiver would refuse to decompress a possibly valid file. + // Since we want to be sure distinguishing a wrong password + // or corrupt file data, we use 64-bit password check data + // and to control its validity we use 32 bits of password + // check data SHA-256 additionally to 32-bit header CRC32. + byte csum[SIZE_PSWCHECK_CSUM]; + Raw->GetB(csum,SIZE_PSWCHECK_CSUM); + + sha256_context ctx; + sha256_init(&ctx); + sha256_process(&ctx, hd->PswCheck, SIZE_PSWCHECK); + + byte Digest[SHA256_DIGEST_SIZE]; + sha256_done(&ctx, Digest); + + hd->UsePswCheck=memcmp(csum,Digest,SIZE_PSWCHECK_CSUM)==0; + + // RAR 5.21 and earlier set PswCheck field in service records to 0 + // even if UsePswCheck was present. + if (bb->HeaderType==HEAD_SERVICE && memcmp(hd->PswCheck,"\0\0\0\0\0\0\0\0",SIZE_PSWCHECK)==0) + hd->UsePswCheck=0; + } + hd->SaltSet=true; + hd->CryptMethod=CRYPT_RAR50; + hd->Encrypted=true; + } + } + break; + case FHEXTRA_HASH: + { + FileHeader *hd=(FileHeader *)bb; + uint Type=(uint)Raw->GetV(); + if (Type==FHEXTRA_HASH_BLAKE2) + { + hd->FileHash.Type=HASH_BLAKE2; + Raw->GetB(hd->FileHash.Digest,BLAKE2_DIGEST_SIZE); + } + } + break; + case FHEXTRA_HTIME: + if (FieldSize>=5) + { + byte Flags=(byte)Raw->GetV(); + bool UnixTime=(Flags & FHEXTRA_HTIME_UNIXTIME)!=0; + if ((Flags & FHEXTRA_HTIME_MTIME)!=0) + if (UnixTime) + hd->mtime.SetUnix(Raw->Get4()); + else + hd->mtime.SetWin(Raw->Get8()); + if ((Flags & FHEXTRA_HTIME_CTIME)!=0) + if (UnixTime) + hd->ctime.SetUnix(Raw->Get4()); + else + hd->ctime.SetWin(Raw->Get8()); + if ((Flags & FHEXTRA_HTIME_ATIME)!=0) + if (UnixTime) + hd->atime.SetUnix((time_t)Raw->Get4()); + else + hd->atime.SetWin(Raw->Get8()); + if (UnixTime && (Flags & FHEXTRA_HTIME_UNIX_NS)!=0) // Add nanoseconds. + { + uint ns; + if ((Flags & FHEXTRA_HTIME_MTIME)!=0 && (ns=(Raw->Get4() & 0x3fffffff))<1000000000) + hd->mtime.Adjust(ns); + if ((Flags & FHEXTRA_HTIME_CTIME)!=0 && (ns=(Raw->Get4() & 0x3fffffff))<1000000000) + hd->ctime.Adjust(ns); + if ((Flags & FHEXTRA_HTIME_ATIME)!=0 && (ns=(Raw->Get4() & 0x3fffffff))<1000000000) + hd->atime.Adjust(ns); + } + } + break; + case FHEXTRA_VERSION: + if (FieldSize>=1) + { + Raw->GetV(); // Skip flags field. + uint Version=(uint)Raw->GetV(); + if (Version!=0) + { + hd->Version=true; + + wchar VerText[20]; + swprintf(VerText,ASIZE(VerText),L";%u",Version); + wcsncatz(FileHead.FileName,VerText,ASIZE(FileHead.FileName)); + } + } + break; + case FHEXTRA_REDIR: + { + hd->RedirType=(FILE_SYSTEM_REDIRECT)Raw->GetV(); + uint Flags=(uint)Raw->GetV(); + hd->DirTarget=(Flags & FHEXTRA_REDIR_DIR)!=0; + size_t NameSize=(size_t)Raw->GetV(); + + char UtfName[NM*4]; + *UtfName=0; + if (NameSizeGetB(UtfName,NameSize); + UtfName[NameSize]=0; + } +#ifdef _WIN_ALL + UnixSlashToDos(UtfName,UtfName,ASIZE(UtfName)); +#endif + UtfToWide(UtfName,hd->RedirName,ASIZE(hd->RedirName)); + } + break; + case FHEXTRA_UOWNER: + { + uint Flags=(uint)Raw->GetV(); + hd->UnixOwnerNumeric=(Flags & FHEXTRA_UOWNER_NUMUID)!=0; + hd->UnixGroupNumeric=(Flags & FHEXTRA_UOWNER_NUMGID)!=0; + *hd->UnixOwnerName=*hd->UnixGroupName=0; + if ((Flags & FHEXTRA_UOWNER_UNAME)!=0) + { + size_t Length=(size_t)Raw->GetV(); + Length=Min(Length,ASIZE(hd->UnixOwnerName)-1); + Raw->GetB(hd->UnixOwnerName,Length); + hd->UnixOwnerName[Length]=0; + } + if ((Flags & FHEXTRA_UOWNER_GNAME)!=0) + { + size_t Length=(size_t)Raw->GetV(); + Length=Min(Length,ASIZE(hd->UnixGroupName)-1); + Raw->GetB(hd->UnixGroupName,Length); + hd->UnixGroupName[Length]=0; + } +#ifdef _UNIX + if (hd->UnixOwnerNumeric) + hd->UnixOwnerID=(uid_t)Raw->GetV(); + if (hd->UnixGroupNumeric) + hd->UnixGroupID=(gid_t)Raw->GetV(); +#else + // Need these fields in Windows too for 'list' command, + // but uid_t and gid_t are not defined. + if (hd->UnixOwnerNumeric) + hd->UnixOwnerID=(uint)Raw->GetV(); + if (hd->UnixGroupNumeric) + hd->UnixGroupID=(uint)Raw->GetV(); +#endif + hd->UnixOwnerSet=true; + } + break; + case FHEXTRA_SUBDATA: + { + // RAR 5.21 and earlier set FHEXTRA_SUBDATA size to 1 less than + // required. It did not hurt extraction, because UnRAR 5.21 + // and earlier ignored this field and set FieldSize as data left + // in entire extra area. But now we set the correct field size + // and set FieldSize based on actual extra record size, + // so we need to adjust it for those older archives here. + // FHEXTRA_SUBDATA in those archives always belongs to HEAD_SERVICE + // and always is last in extra area. So since its size is by 1 + // less than needed, we always have 1 byte left in extra area, + // which fact we use here to detect such archives. + if (bb->HeaderType==HEAD_SERVICE && Raw->Size()-NextPos==1) + FieldSize++; + + hd->SubData.Alloc((size_t)FieldSize); + Raw->GetB(hd->SubData.Addr(0),(size_t)FieldSize); + } + break; + } + } + + Raw->SetPos(NextPos); + } +} + + +#ifndef SFX_MODULE +size_t Archive::ReadHeader14() +{ + RawRead Raw(this); + if (CurBlockPos<=(int64)SFXSize) + { + Raw.Read(SIZEOF_MAINHEAD14); + MainHead.Reset(); + byte Mark[4]; + Raw.GetB(Mark,4); + uint HeadSize=Raw.Get2(); + byte Flags=Raw.Get1(); + NextBlockPos=CurBlockPos+HeadSize; + CurHeaderType=HEAD_MAIN; + + Volume=(Flags & MHD_VOLUME)!=0; + Solid=(Flags & MHD_SOLID)!=0; + Locked=(Flags & MHD_LOCK)!=0; + MainHead.CommentInHeader=(Flags & MHD_COMMENT)!=0; + MainHead.PackComment=(Flags & MHD_PACK_COMMENT)!=0; + } + else + { + Raw.Read(SIZEOF_FILEHEAD14); + FileHead.Reset(); + + FileHead.HeaderType=HEAD_FILE; + FileHead.DataSize=Raw.Get4(); + FileHead.UnpSize=Raw.Get4(); + FileHead.FileHash.Type=HASH_RAR14; + FileHead.FileHash.CRC32=Raw.Get2(); + FileHead.HeadSize=Raw.Get2(); + uint FileTime=Raw.Get4(); + FileHead.FileAttr=Raw.Get1(); + FileHead.Flags=Raw.Get1()|LONG_BLOCK; + FileHead.UnpVer=(Raw.Get1()==2) ? 13 : 10; + size_t NameSize=Raw.Get1(); + FileHead.Method=Raw.Get1(); + + FileHead.SplitBefore=(FileHead.Flags & LHD_SPLIT_BEFORE)!=0; + FileHead.SplitAfter=(FileHead.Flags & LHD_SPLIT_AFTER)!=0; + FileHead.Encrypted=(FileHead.Flags & LHD_PASSWORD)!=0; + FileHead.CryptMethod=FileHead.Encrypted ? CRYPT_RAR13:CRYPT_NONE; + + FileHead.PackSize=FileHead.DataSize; + FileHead.WinSize=0x10000; + + FileHead.mtime.SetDos(FileTime); + + Raw.Read(NameSize); + + char FileName[NM]; + Raw.GetB((byte *)FileName,Min(NameSize,ASIZE(FileName))); + FileName[NameSize]=0; + IntToExt(FileName,FileName,ASIZE(FileName)); + CharToWide(FileName,FileHead.FileName,ASIZE(FileHead.FileName)); + ConvertNameCase(FileHead.FileName); + + if (Raw.Size()!=0) + NextBlockPos=CurBlockPos+FileHead.HeadSize+FileHead.PackSize; + CurHeaderType=HEAD_FILE; + } + return NextBlockPos>CurBlockPos ? Raw.Size() : 0; +} +#endif + + +#ifndef SFX_MODULE +void Archive::ConvertNameCase(wchar *Name) +{ + if (Cmd->ConvertNames==NAMES_UPPERCASE) + wcsupper(Name); + if (Cmd->ConvertNames==NAMES_LOWERCASE) + wcslower(Name); +} +#endif + + +bool Archive::IsArcDir() +{ + return FileHead.Dir; +} + + +void Archive::ConvertAttributes() +{ +#if defined(_WIN_ALL) || defined(_EMX) + if (FileHead.HSType!=HSYS_WINDOWS) + FileHead.FileAttr=FileHead.Dir ? 0x10 : 0x20; +#endif +#ifdef _UNIX + // umask defines which permission bits must not be set by default + // when creating a file or directory. The typical default value + // for the process umask is S_IWGRP | S_IWOTH (octal 022), + // resulting in 0644 mode for new files. + // Normally umask is applied automatically when creating a file, + // but we set attributes with chmod later, so we need to calculate + // resulting attributes here. We do it only for non-Unix archives. + // We restore native Unix attributes as is, because it can be backup. + static mode_t mask = (mode_t) -1; + + if (mask == (mode_t) -1) + { + // umask call returns the current umask value. Argument (022) is not + // really important here. + mask = umask(022); + + // Restore the original umask value, which was changed to 022 above. + umask(mask); + } + + switch(FileHead.HSType) + { + case HSYS_WINDOWS: + { + // Mapping MSDOS, OS/2 and Windows file attributes to Unix. + + if (FileHead.FileAttr & 0x10) // FILE_ATTRIBUTE_DIRECTORY + { + // For directories we use 0777 mask. + FileHead.FileAttr=0777 & ~mask; + } + else + if (FileHead.FileAttr & 1) // FILE_ATTRIBUTE_READONLY + { + // For read only files we use 0444 mask with 'w' bits turned off. + FileHead.FileAttr=0444 & ~mask; + } + else + { + // umask does not set +x for regular files, so we use 0666 + // instead of 0777 as for directories. + FileHead.FileAttr=0666 & ~mask; + } + } + break; + case HSYS_UNIX: + break; + default: + if (FileHead.Dir) + FileHead.FileAttr=0x41ff & ~mask; + else + FileHead.FileAttr=0x81b6 & ~mask; + break; + } +#endif +} + + +void Archive::ConvertFileHeader(FileHeader *hd) +{ + if (Format==RARFMT15 && hd->UnpVer<20 && (hd->FileAttr & 0x10)) + hd->Dir=true; + if (hd->HSType==HSYS_UNKNOWN) + if (hd->Dir) + hd->FileAttr=0x10; + else + hd->FileAttr=0x20; + +#ifdef _WIN_ALL + if (hd->HSType==HSYS_UNIX) // Convert Unix, OS X and Android decomposed chracters to Windows precomposed. + ConvertToPrecomposed(hd->FileName,ASIZE(hd->FileName)); +#endif + + for (wchar *s=hd->FileName;*s!=0;s++) + { +#ifdef _UNIX + // Backslash is the invalid character for Windows file headers, + // but it can present in Unix file names extracted in Unix. + if (*s=='\\' && Format==RARFMT50 && hd->HSType==HSYS_WINDOWS) + *s='_'; +#endif + +#if defined(_WIN_ALL) || defined(_EMX) + // RAR 5.0 archives do not use '\' as path separator, so if we see it, + // it means that it is a part of Unix file name, which we cannot + // extract in Windows. + if (*s=='\\' && Format==RARFMT50) + *s='_'; + + // ':' in file names is allowed in Unix, but not in Windows. + // Even worse, file data will be written to NTFS stream on NTFS, + // so automatic name correction on file create error in extraction + // routine does not work. In Windows and DOS versions we better + // replace ':' now. + if (*s==':') + *s='_'; +#endif + + // This code must be performed only after other path separator checks, + // because it produces backslashes illegal for some of checks above. + // Backslash is allowed in file names in Unix, but not in Windows. + // Still, RAR 4.x uses backslashes as path separator even in Unix. + // Forward slash is not allowed in both systems. In RAR 5.0 we use + // the forward slash as universal path separator. + if (*s=='/' || *s=='\\' && Format!=RARFMT50) + *s=CPATHDIVIDER; + } +} + + +int64 Archive::GetStartPos() +{ + int64 StartPos=SFXSize+MarkHead.HeadSize; + if (Format==RARFMT15) + StartPos+=MainHead.HeadSize; + else // RAR 5.0. + StartPos+=CryptHead.HeadSize+FullHeaderSize(MainHead.HeadSize); + return StartPos; +} + + +bool Archive::ReadSubData(Array *UnpData,File *DestFile) +{ + if (BrokenHeader) + { + uiMsg(UIERROR_SUBHEADERBROKEN,FileName); + ErrHandler.SetErrorCode(RARX_CRC); + return false; + } + if (SubHead.Method>5 || SubHead.UnpVer>(Format==RARFMT50 ? VER_UNPACK5:VER_UNPACK)) + { + uiMsg(UIERROR_SUBHEADERUNKNOWN,FileName); + return false; + } + + if (SubHead.PackSize==0 && !SubHead.SplitAfter) + return true; + + SubDataIO.Init(); + Unpack Unpack(&SubDataIO); + Unpack.Init(SubHead.WinSize,false); + + if (DestFile==NULL) + { + if (SubHead.UnpSize>0x1000000) + { + // So huge allocation must never happen in valid archives. + uiMsg(UIERROR_SUBHEADERUNKNOWN,FileName); + return false; + } + if (UnpData==NULL) + SubDataIO.SetTestMode(true); + else + { + UnpData->Alloc((size_t)SubHead.UnpSize); + SubDataIO.SetUnpackToMemory(&(*UnpData)[0],(uint)SubHead.UnpSize); + } + } + if (SubHead.Encrypted) + if (Cmd->Password.IsSet()) + SubDataIO.SetEncryption(false,SubHead.CryptMethod,&Cmd->Password, + SubHead.SaltSet ? SubHead.Salt:NULL,SubHead.InitV, + SubHead.Lg2Count,SubHead.HashKey,SubHead.PswCheck); + else + return false; + SubDataIO.UnpHash.Init(SubHead.FileHash.Type,1); + SubDataIO.SetPackedSizeToRead(SubHead.PackSize); + SubDataIO.EnableShowProgress(false); + SubDataIO.SetFiles(this,DestFile); + SubDataIO.UnpVolume=SubHead.SplitAfter; + SubDataIO.SetSubHeader(&SubHead,NULL); + Unpack.SetDestSize(SubHead.UnpSize); + if (SubHead.Method==0) + CmdExtract::UnstoreFile(SubDataIO,SubHead.UnpSize); + else + Unpack.DoUnpack(SubHead.UnpVer,false); + + if (!SubDataIO.UnpHash.Cmp(&SubHead.FileHash,SubHead.UseHashKey ? SubHead.HashKey:NULL)) + { + uiMsg(UIERROR_SUBHEADERDATABROKEN,FileName,SubHead.FileName); + ErrHandler.SetErrorCode(RARX_CRC); + if (UnpData!=NULL) + UnpData->Reset(); + return false; + } + return true; +} diff --git a/reference/unrar/array.hpp b/reference/unrar/array.hpp new file mode 100644 index 00000000..20d258d5 --- /dev/null +++ b/reference/unrar/array.hpp @@ -0,0 +1,191 @@ +#ifndef _RAR_ARRAY_ +#define _RAR_ARRAY_ + +extern ErrorHandler ErrHandler; + +template class Array +{ + private: + T *Buffer; + size_t BufSize; + size_t AllocSize; + size_t MaxSize; + bool Secure; // Clean memory if true. + public: + Array(); + Array(size_t Size); + Array(const Array &Src); // Copy constructor. + ~Array(); + inline void CleanData(); + inline T& operator [](size_t Item) const; + inline T* operator + (size_t Pos); + inline size_t Size(); // Returns the size in items, not in bytes. + void Add(size_t Items); + void Alloc(size_t Items); + void Reset(); + void SoftReset(); + void operator = (Array &Src); + void Push(T Item); + void Append(T *Item,size_t Count); + T* Addr(size_t Item) {return Buffer+Item;} + void SetMaxSize(size_t Size) {MaxSize=Size;} + T* Begin() {return Buffer;} + T* End() {return Buffer==NULL ? NULL:Buffer+BufSize;} + void SetSecure() {Secure=true;} +}; + + +template void Array::CleanData() +{ + Buffer=NULL; + BufSize=0; + AllocSize=0; + MaxSize=0; + Secure=false; +} + + +template Array::Array() +{ + CleanData(); +} + + +template Array::Array(size_t Size) +{ + CleanData(); + Add(Size); +} + + +// Copy constructor in case we need to pass an object as value. +template Array::Array(const Array &Src) +{ + CleanData(); + Alloc(Src.BufSize); + if (Src.BufSize!=0) + memcpy((void *)Buffer,(void *)Src.Buffer,Src.BufSize*sizeof(T)); +} + + +template Array::~Array() +{ + if (Buffer!=NULL) + { + if (Secure) + cleandata(Buffer,AllocSize*sizeof(T)); + free(Buffer); + } +} + + +template inline T& Array::operator [](size_t Item) const +{ + return Buffer[Item]; +} + + +template inline T* Array::operator +(size_t Pos) +{ + return Buffer+Pos; +} + + +template inline size_t Array::Size() +{ + return BufSize; +} + + +template void Array::Add(size_t Items) +{ + BufSize+=Items; + if (BufSize>AllocSize) + { + if (MaxSize!=0 && BufSize>MaxSize) + { + ErrHandler.GeneralErrMsg(L"Maximum allowed array size (%u) is exceeded",MaxSize); + ErrHandler.MemoryError(); + } + + size_t Suggested=AllocSize+AllocSize/4+32; + size_t NewSize=Max(BufSize,Suggested); + + T *NewBuffer; + if (Secure) + { + NewBuffer=(T *)malloc(NewSize*sizeof(T)); + if (NewBuffer==NULL) + ErrHandler.MemoryError(); + if (Buffer!=NULL) + { + memcpy(NewBuffer,Buffer,AllocSize*sizeof(T)); + cleandata(Buffer,AllocSize*sizeof(T)); + free(Buffer); + } + } + else + { + NewBuffer=(T *)realloc(Buffer,NewSize*sizeof(T)); + if (NewBuffer==NULL) + ErrHandler.MemoryError(); + } + Buffer=NewBuffer; + AllocSize=NewSize; + } +} + + +template void Array::Alloc(size_t Items) +{ + if (Items>AllocSize) + Add(Items-BufSize); + else + BufSize=Items; +} + + +template void Array::Reset() +{ + if (Buffer!=NULL) + { + free(Buffer); + Buffer=NULL; + } + BufSize=0; + AllocSize=0; +} + + +// Reset buffer size, but preserve already allocated memory if any, +// so we can reuse it without wasting time to allocation. +template void Array::SoftReset() +{ + BufSize=0; +} + + +template void Array::operator =(Array &Src) +{ + Reset(); + Alloc(Src.BufSize); + if (Src.BufSize!=0) + memcpy((void *)Buffer,(void *)Src.Buffer,Src.BufSize*sizeof(T)); +} + + +template void Array::Push(T Item) +{ + Add(1); + (*this)[Size()-1]=Item; +} + + +template void Array::Append(T *Items,size_t Count) +{ + size_t CurSize=Size(); + Add(Count); + memcpy(Buffer+CurSize,Items,Count*sizeof(T)); +} + +#endif diff --git a/reference/unrar/blake2s.cpp b/reference/unrar/blake2s.cpp new file mode 100644 index 00000000..317603da --- /dev/null +++ b/reference/unrar/blake2s.cpp @@ -0,0 +1,183 @@ +// Based on public domain code written in 2012 by Samuel Neves + +#include "rar.hpp" + +#ifdef USE_SSE +#include "blake2s_sse.cpp" +#endif + +static void blake2s_init_param( blake2s_state *S, uint32 node_offset, uint32 node_depth); +static void blake2s_update( blake2s_state *S, const byte *in, size_t inlen ); +static void blake2s_final( blake2s_state *S, byte *digest ); + +#include "blake2sp.cpp" + +static const uint32 blake2s_IV[8] = +{ + 0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL, + 0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL +}; + +static const byte blake2s_sigma[10][16] = +{ + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } , + { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 } , + { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 } , + { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 } , + { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 } , + { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 } , + { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 } , + { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 } , + { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0 } , +}; + +static inline void blake2s_set_lastnode( blake2s_state *S ) +{ + S->f[1] = ~0U; +} + + +/* Some helper functions, not necessarily useful */ +static inline void blake2s_set_lastblock( blake2s_state *S ) +{ + if( S->last_node ) blake2s_set_lastnode( S ); + + S->f[0] = ~0U; +} + + +static inline void blake2s_increment_counter( blake2s_state *S, const uint32 inc ) +{ + S->t[0] += inc; + S->t[1] += ( S->t[0] < inc ); +} + + +/* init2 xors IV with input parameter block */ +void blake2s_init_param( blake2s_state *S, uint32 node_offset, uint32 node_depth) +{ +#ifdef USE_SSE + if (_SSE_Version>=SSE_SSE2) + blake2s_init_sse(); +#endif + + S->init(); // Clean data. + for( int i = 0; i < 8; ++i ) + S->h[i] = blake2s_IV[i]; + + S->h[0] ^= 0x02080020; // We use BLAKE2sp parameters block. + S->h[2] ^= node_offset; + S->h[3] ^= (node_depth<<16)|0x20000000; +} + + +#define G(r,i,m,a,b,c,d) \ + a = a + b + m[blake2s_sigma[r][2*i+0]]; \ + d = rotr32(d ^ a, 16); \ + c = c + d; \ + b = rotr32(b ^ c, 12); \ + a = a + b + m[blake2s_sigma[r][2*i+1]]; \ + d = rotr32(d ^ a, 8); \ + c = c + d; \ + b = rotr32(b ^ c, 7); + + +static void blake2s_compress( blake2s_state *S, const byte block[BLAKE2S_BLOCKBYTES] ) +{ + uint32 m[16]; + uint32 v[16]; + + for( size_t i = 0; i < 16; ++i ) + m[i] = RawGet4( block + i * 4 ); + + for( size_t i = 0; i < 8; ++i ) + v[i] = S->h[i]; + + v[ 8] = blake2s_IV[0]; + v[ 9] = blake2s_IV[1]; + v[10] = blake2s_IV[2]; + v[11] = blake2s_IV[3]; + v[12] = S->t[0] ^ blake2s_IV[4]; + v[13] = S->t[1] ^ blake2s_IV[5]; + v[14] = S->f[0] ^ blake2s_IV[6]; + v[15] = S->f[1] ^ blake2s_IV[7]; + + for ( uint r = 0; r <= 9; ++r ) // No gain on i7 if unrolled, but exe size grows. + { + G(r,0,m,v[ 0],v[ 4],v[ 8],v[12]); + G(r,1,m,v[ 1],v[ 5],v[ 9],v[13]); + G(r,2,m,v[ 2],v[ 6],v[10],v[14]); + G(r,3,m,v[ 3],v[ 7],v[11],v[15]); + G(r,4,m,v[ 0],v[ 5],v[10],v[15]); + G(r,5,m,v[ 1],v[ 6],v[11],v[12]); + G(r,6,m,v[ 2],v[ 7],v[ 8],v[13]); + G(r,7,m,v[ 3],v[ 4],v[ 9],v[14]); + } + + for( size_t i = 0; i < 8; ++i ) + S->h[i] = S->h[i] ^ v[i] ^ v[i + 8]; +} + + +void blake2s_update( blake2s_state *S, const byte *in, size_t inlen ) +{ + while( inlen > 0 ) + { + size_t left = S->buflen; + size_t fill = 2 * BLAKE2S_BLOCKBYTES - left; + + if( inlen > fill ) + { + memcpy( S->buf + left, in, fill ); // Fill buffer + S->buflen += fill; + blake2s_increment_counter( S, BLAKE2S_BLOCKBYTES ); + +#ifdef USE_SSE +#ifdef _WIN_32 // We use SSSE3 _mm_shuffle_epi8 only in x64 mode. + if (_SSE_Version>=SSE_SSE2) +#else + if (_SSE_Version>=SSE_SSSE3) +#endif + blake2s_compress_sse( S, S->buf ); + else + blake2s_compress( S, S->buf ); // Compress +#else + blake2s_compress( S, S->buf ); // Compress +#endif + + memcpy( S->buf, S->buf + BLAKE2S_BLOCKBYTES, BLAKE2S_BLOCKBYTES ); // Shift buffer left + S->buflen -= BLAKE2S_BLOCKBYTES; + in += fill; + inlen -= fill; + } + else // inlen <= fill + { + memcpy( S->buf + left, in, (size_t)inlen ); + S->buflen += (size_t)inlen; // Be lazy, do not compress + in += inlen; + inlen = 0; + } + } +} + + +void blake2s_final( blake2s_state *S, byte *digest ) +{ + if( S->buflen > BLAKE2S_BLOCKBYTES ) + { + blake2s_increment_counter( S, BLAKE2S_BLOCKBYTES ); + blake2s_compress( S, S->buf ); + S->buflen -= BLAKE2S_BLOCKBYTES; + memcpy( S->buf, S->buf + BLAKE2S_BLOCKBYTES, S->buflen ); + } + + blake2s_increment_counter( S, ( uint32 )S->buflen ); + blake2s_set_lastblock( S ); + memset( S->buf + S->buflen, 0, 2 * BLAKE2S_BLOCKBYTES - S->buflen ); /* Padding */ + blake2s_compress( S, S->buf ); + + for( int i = 0; i < 8; ++i ) /* Output full hash */ + RawPut4( S->h[i], digest + 4 * i ); +} + diff --git a/reference/unrar/blake2s.hpp b/reference/unrar/blake2s.hpp new file mode 100644 index 00000000..7dd71571 --- /dev/null +++ b/reference/unrar/blake2s.hpp @@ -0,0 +1,101 @@ +// Based on public domain code written in 2012 by Samuel Neves +#ifndef _RAR_BLAKE2_ +#define _RAR_BLAKE2_ + +#define BLAKE2_DIGEST_SIZE 32 + +enum blake2s_constant +{ + BLAKE2S_BLOCKBYTES = 64, + BLAKE2S_OUTBYTES = 32 +}; + + +// Alignment to 64 improves performance of both SSE and non-SSE versions. +// Alignment to n*16 is required for SSE version, so we selected 64. +// We use the custom alignment scheme instead of __declspec(align(x)), +// because it is less compiler dependent. Also the compiler directive +// does not help if structure is a member of class allocated through +// 'new' operator. +struct blake2s_state +{ + enum { BLAKE_ALIGNMENT = 64 }; + + // buffer and uint32 h[8], t[2], f[2]; + enum { BLAKE_DATA_SIZE = 48 + 2 * BLAKE2S_BLOCKBYTES }; + + byte ubuf[BLAKE_DATA_SIZE + BLAKE_ALIGNMENT]; + + byte *buf; // byte buf[2 * BLAKE2S_BLOCKBYTES]. + uint32 *h, *t, *f; // uint32 h[8], t[2], f[2]. + + size_t buflen; + byte last_node; + + blake2s_state() + { + set_pointers(); + } + + // Required when we declare and assign in the same command. + blake2s_state(blake2s_state &st) + { + set_pointers(); + *this=st; + } + + void set_pointers() + { + // Set aligned pointers. Must be done in constructor, not in Init(), + // so assignments like 'blake2sp_state res=blake2ctx' work correctly + // even if blake2sp_init is not called for 'res'. + buf = (byte *) ALIGN_VALUE(ubuf, BLAKE_ALIGNMENT); + h = (uint32 *) (buf + 2 * BLAKE2S_BLOCKBYTES); + t = h + 8; + f = t + 2; + } + + void init() + { + memset( ubuf, 0, sizeof( ubuf ) ); + buflen = 0; + last_node = 0; + } + + // Since we use pointers, the default = would work incorrectly. + blake2s_state& operator = (blake2s_state &st) + { + if (this != &st) + { + memcpy(buf, st.buf, BLAKE_DATA_SIZE); + buflen = st.buflen; + last_node = st.last_node; + } + return *this; + } +}; + + +#ifdef RAR_SMP +class ThreadPool; +#endif + +struct blake2sp_state +{ + blake2s_state S[8]; + blake2s_state R; + byte buf[8 * BLAKE2S_BLOCKBYTES]; + size_t buflen; + +#ifdef RAR_SMP + ThreadPool *ThPool; + uint MaxThreads; +#endif +}; + +void blake2sp_init( blake2sp_state *S ); +void blake2sp_update( blake2sp_state *S, const byte *in, size_t inlen ); +void blake2sp_final( blake2sp_state *S, byte *digest ); + +#endif + diff --git a/reference/unrar/blake2s_sse.cpp b/reference/unrar/blake2s_sse.cpp new file mode 100644 index 00000000..1a02f210 --- /dev/null +++ b/reference/unrar/blake2s_sse.cpp @@ -0,0 +1,129 @@ +// Based on public domain code written in 2012 by Samuel Neves + +extern const byte blake2s_sigma[10][16]; + +// Initialization vector. +static __m128i blake2s_IV_0_3, blake2s_IV_4_7; + +#ifdef _WIN_64 +// Constants for cyclic rotation. Used in 64-bit mode in mm_rotr_epi32 macro. +static __m128i crotr8, crotr16; +#endif + +static void blake2s_init_sse() +{ + // We cannot initialize these 128 bit variables in place when declaring + // them globally, because global scope initialization is performed before + // our SSE check and it would make code incompatible with older non-SSE2 + // CPUs. Also we cannot initialize them as static inside of function + // using these variables, because SSE static initialization is not thread + // safe: first thread starts initialization and sets "init done" flag even + // if it is not done yet, second thread can attempt to access half-init + // SSE data. So we moved init code here. + + blake2s_IV_0_3 = _mm_setr_epi32( 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A ); + blake2s_IV_4_7 = _mm_setr_epi32( 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 ); + +#ifdef _WIN_64 + crotr8 = _mm_set_epi8( 12, 15, 14, 13, 8, 11, 10, 9, 4, 7, 6, 5, 0, 3, 2, 1 ); + crotr16 = _mm_set_epi8( 13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2 ); +#endif +} + + +#define LOAD(p) _mm_load_si128( (__m128i *)(p) ) +#define STORE(p,r) _mm_store_si128((__m128i *)(p), r) + +#ifdef _WIN_32 +// 32-bit mode has less SSE2 registers and in MSVC2008 it is more efficient +// to not use _mm_shuffle_epi8 here. +#define mm_rotr_epi32(r, c) ( \ + _mm_xor_si128(_mm_srli_epi32( (r), c ),_mm_slli_epi32( (r), 32-c )) ) +#else +#define mm_rotr_epi32(r, c) ( \ + c==8 ? _mm_shuffle_epi8(r,crotr8) \ + : c==16 ? _mm_shuffle_epi8(r,crotr16) \ + : _mm_xor_si128(_mm_srli_epi32( (r), c ),_mm_slli_epi32( (r), 32-c )) ) +#endif + + +#define G1(row1,row2,row3,row4,buf) \ + row1 = _mm_add_epi32( _mm_add_epi32( row1, buf), row2 ); \ + row4 = _mm_xor_si128( row4, row1 ); \ + row4 = mm_rotr_epi32(row4, 16); \ + row3 = _mm_add_epi32( row3, row4 ); \ + row2 = _mm_xor_si128( row2, row3 ); \ + row2 = mm_rotr_epi32(row2, 12); + +#define G2(row1,row2,row3,row4,buf) \ + row1 = _mm_add_epi32( _mm_add_epi32( row1, buf), row2 ); \ + row4 = _mm_xor_si128( row4, row1 ); \ + row4 = mm_rotr_epi32(row4, 8); \ + row3 = _mm_add_epi32( row3, row4 ); \ + row2 = _mm_xor_si128( row2, row3 ); \ + row2 = mm_rotr_epi32(row2, 7); + +#define DIAGONALIZE(row1,row2,row3,row4) \ + row4 = _mm_shuffle_epi32( row4, _MM_SHUFFLE(2,1,0,3) ); \ + row3 = _mm_shuffle_epi32( row3, _MM_SHUFFLE(1,0,3,2) ); \ + row2 = _mm_shuffle_epi32( row2, _MM_SHUFFLE(0,3,2,1) ); + +#define UNDIAGONALIZE(row1,row2,row3,row4) \ + row4 = _mm_shuffle_epi32( row4, _MM_SHUFFLE(0,3,2,1) ); \ + row3 = _mm_shuffle_epi32( row3, _MM_SHUFFLE(1,0,3,2) ); \ + row2 = _mm_shuffle_epi32( row2, _MM_SHUFFLE(2,1,0,3) ); + +#ifdef _WIN_64 + // MSVC 2008 in x64 mode expands _mm_set_epi32 to store to stack and load + // from stack operations, which are slower than this code. + #define _mm_set_epi32(i3,i2,i1,i0) \ + _mm_unpacklo_epi32(_mm_unpacklo_epi32(_mm_cvtsi32_si128(i0),_mm_cvtsi32_si128(i2)), \ + _mm_unpacklo_epi32(_mm_cvtsi32_si128(i1),_mm_cvtsi32_si128(i3))) +#endif + +// Original BLAKE2 SSE4.1 message loading code was a little slower in x86 mode +// and about the same in x64 mode in our test. Perhaps depends on compiler. +// We also tried _mm_i32gather_epi32 and _mm256_i32gather_epi32 AVX2 gather +// instructions here, but they did not show any speed gain on i7-6700K. +#define SSE_ROUND(m,row,r) \ +{ \ + __m128i buf; \ + buf=_mm_set_epi32(m[blake2s_sigma[r][6]],m[blake2s_sigma[r][4]],m[blake2s_sigma[r][2]],m[blake2s_sigma[r][0]]); \ + G1(row[0],row[1],row[2],row[3],buf); \ + buf=_mm_set_epi32(m[blake2s_sigma[r][7]],m[blake2s_sigma[r][5]],m[blake2s_sigma[r][3]],m[blake2s_sigma[r][1]]); \ + G2(row[0],row[1],row[2],row[3],buf); \ + DIAGONALIZE(row[0],row[1],row[2],row[3]); \ + buf=_mm_set_epi32(m[blake2s_sigma[r][14]],m[blake2s_sigma[r][12]],m[blake2s_sigma[r][10]],m[blake2s_sigma[r][8]]); \ + G1(row[0],row[1],row[2],row[3],buf); \ + buf=_mm_set_epi32(m[blake2s_sigma[r][15]],m[blake2s_sigma[r][13]],m[blake2s_sigma[r][11]],m[blake2s_sigma[r][9]]); \ + G2(row[0],row[1],row[2],row[3],buf); \ + UNDIAGONALIZE(row[0],row[1],row[2],row[3]); \ +} + + +static int blake2s_compress_sse( blake2s_state *S, const byte block[BLAKE2S_BLOCKBYTES] ) +{ + __m128i row[4]; + __m128i ff0, ff1; + + const uint32 *m = ( uint32 * )block; + + row[0] = ff0 = LOAD( &S->h[0] ); + row[1] = ff1 = LOAD( &S->h[4] ); + + row[2] = blake2s_IV_0_3; + row[3] = _mm_xor_si128( blake2s_IV_4_7, LOAD( &S->t[0] ) ); + SSE_ROUND( m, row, 0 ); + SSE_ROUND( m, row, 1 ); + SSE_ROUND( m, row, 2 ); + SSE_ROUND( m, row, 3 ); + SSE_ROUND( m, row, 4 ); + SSE_ROUND( m, row, 5 ); + SSE_ROUND( m, row, 6 ); + SSE_ROUND( m, row, 7 ); + SSE_ROUND( m, row, 8 ); + SSE_ROUND( m, row, 9 ); + STORE( &S->h[0], _mm_xor_si128( ff0, _mm_xor_si128( row[0], row[2] ) ) ); + STORE( &S->h[4], _mm_xor_si128( ff1, _mm_xor_si128( row[1], row[3] ) ) ); + return 0; +} diff --git a/reference/unrar/blake2sp.cpp b/reference/unrar/blake2sp.cpp new file mode 100644 index 00000000..da645883 --- /dev/null +++ b/reference/unrar/blake2sp.cpp @@ -0,0 +1,153 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Written in 2012 by Samuel Neves + + To the extent possible under law, the author(s) have dedicated all copyright + and related and neighboring rights to this software to the public domain + worldwide. This software is distributed without any warranty. + + You should have received a copy of the CC0 Public Domain Dedication along with + this software. If not, see . +*/ + +#define PARALLELISM_DEGREE 8 + +void blake2sp_init( blake2sp_state *S ) +{ + memset( S->buf, 0, sizeof( S->buf ) ); + S->buflen = 0; + + blake2s_init_param( &S->R, 0, 1 ); // Init root. + + for( uint i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2s_init_param( &S->S[i], i, 0 ); // Init leaf. + + S->R.last_node = 1; + S->S[PARALLELISM_DEGREE - 1].last_node = 1; +} + + +struct Blake2ThreadData +{ + void Update(); + blake2s_state *S; + const byte *in; + size_t inlen; +}; + + +void Blake2ThreadData::Update() +{ + size_t inlen__ = inlen; + const byte *in__ = ( const byte * )in; + + while( inlen__ >= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES ) + { +#ifdef USE_SSE + // We gain 5% in i7 SSE mode by prefetching next data block. + if (_SSE_Version>=SSE_SSE && inlen__ >= 2 * PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES) + _mm_prefetch((char*)(in__ + PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES), _MM_HINT_T0); +#endif + blake2s_update( S, in__, BLAKE2S_BLOCKBYTES ); + in__ += PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES; + inlen__ -= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES; + } +} + +#ifdef RAR_SMP +THREAD_PROC(Blake2Thread) +{ + Blake2ThreadData *td=(Blake2ThreadData *)Data; + td->Update(); +} +#endif + + +void blake2sp_update( blake2sp_state *S, const byte *in, size_t inlen ) +{ + size_t left = S->buflen; + size_t fill = sizeof( S->buf ) - left; + + if( left && inlen >= fill ) + { + memcpy( S->buf + left, in, fill ); + + for( size_t i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2s_update( &S->S[i], S->buf + i * BLAKE2S_BLOCKBYTES, BLAKE2S_BLOCKBYTES ); + + in += fill; + inlen -= fill; + left = 0; + } + + Blake2ThreadData btd_array[PARALLELISM_DEGREE]; + +#ifdef RAR_SMP + uint ThreadNumber = inlen < 0x1000 ? 1 : S->MaxThreads; + + if (ThreadNumber==6 || ThreadNumber==7) // 6 and 7 threads work slower than 4 here. + ThreadNumber=4; +#else + uint ThreadNumber=1; +#endif + + for (size_t id__=0;id__inlen = inlen; + btd->in = in + id__ * BLAKE2S_BLOCKBYTES; + btd->S = &S->S[id__]; + +#ifdef RAR_SMP + if (ThreadNumber>1) + S->ThPool->AddTask(Blake2Thread,(void*)btd); + else + btd->Update(); +#else + btd->Update(); +#endif + id__++; + } +#ifdef RAR_SMP + if (S->ThPool!=NULL) // Can be NULL in -mt1 mode. + S->ThPool->WaitDone(); +#endif // RAR_SMP + } + + in += inlen - inlen % ( PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES ); + inlen %= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES; + + if( inlen > 0 ) + memcpy( S->buf + left, in, (size_t)inlen ); + + S->buflen = left + (size_t)inlen; +} + + +void blake2sp_final( blake2sp_state *S, byte *digest ) +{ + byte hash[PARALLELISM_DEGREE][BLAKE2S_OUTBYTES]; + + for( size_t i = 0; i < PARALLELISM_DEGREE; ++i ) + { + if( S->buflen > i * BLAKE2S_BLOCKBYTES ) + { + size_t left = S->buflen - i * BLAKE2S_BLOCKBYTES; + + if( left > BLAKE2S_BLOCKBYTES ) left = BLAKE2S_BLOCKBYTES; + + blake2s_update( &S->S[i], S->buf + i * BLAKE2S_BLOCKBYTES, left ); + } + + blake2s_final( &S->S[i], hash[i] ); + } + + for( size_t i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2s_update( &S->R, hash[i], BLAKE2S_OUTBYTES ); + + blake2s_final( &S->R, digest ); +} diff --git a/reference/unrar/cmddata.cpp b/reference/unrar/cmddata.cpp new file mode 100644 index 00000000..758cd171 --- /dev/null +++ b/reference/unrar/cmddata.cpp @@ -0,0 +1,1400 @@ +#include "rar.hpp" + +CommandData::CommandData() +{ + Init(); +} + + +void CommandData::Init() +{ + RAROptions::Init(); + + *Command=0; + *ArcName=0; + FileLists=false; + + ListMode=RCLM_AUTO; + + BareOutput=false; + + + FileArgs.Reset(); + ExclArgs.Reset(); + InclArgs.Reset(); + StoreArgs.Reset(); + ArcNames.Reset(); + NextVolSizes.Reset(); +} + + +// Return the pointer to next position in the string and store dynamically +// allocated command line parameter in Par. +static const wchar *AllocCmdParam(const wchar *CmdLine,wchar **Par) +{ + const wchar *NextCmd=GetCmdParam(CmdLine,NULL,0); + if (NextCmd==NULL) + return NULL; + size_t ParSize=NextCmd-CmdLine+2; // Parameter size including the trailing zero. + *Par=(wchar *)malloc(ParSize*sizeof(wchar)); + if (*Par==NULL) + return NULL; + return GetCmdParam(CmdLine,*Par,ParSize); +} + + +#if !defined(SFX_MODULE) +void CommandData::ParseCommandLine(bool Preprocess,int argc, char *argv[]) +{ + *Command=0; + NoMoreSwitches=false; +#ifdef CUSTOM_CMDLINE_PARSER + // In Windows we may prefer to implement our own command line parser + // to avoid replacing \" by " in standard parser. Such replacing corrupts + // destination paths like "dest path\" in extraction commands. + // Also our own parser is Unicode compatible. + const wchar *CmdLine=GetCommandLine(); + + wchar *Par; + for (bool FirstParam=true;;FirstParam=false) + { + if ((CmdLine=AllocCmdParam(CmdLine,&Par))==NULL) + break; + if (!FirstParam) // First parameter is the executable name. + if (Preprocess) + PreprocessArg(Par); + else + ParseArg(Par); + free(Par); + } +#else + Array Arg; + for (int I=1;I EnvStrW(strlen(EnvStr)+1); + CharToWide(EnvStr,&EnvStrW[0],EnvStrW.Size()); + ProcessSwitchesString(&EnvStrW[0]); + } +} +#endif + + + +#if !defined(SFX_MODULE) +// Preprocess those parameters, which must be processed before the rest of +// command line. Return 'false' to stop further processing. +void CommandData::PreprocessArg(const wchar *Arg) +{ + if (IsSwitch(Arg[0]) && !NoMoreSwitches) + { + Arg++; + if (Arg[0]=='-' && Arg[1]==0) // Switch "--". + NoMoreSwitches=true; + if (wcsicomp(Arg,L"cfg-")==0) + ConfigDisabled=true; + if (wcsnicomp(Arg,L"ilog",4)==0) + { + // Ensure that correct log file name is already set + // if we need to report an error when processing the command line. + ProcessSwitch(Arg); + InitLogOptions(LogName,ErrlogCharset); + } + if (wcsnicomp(Arg,L"sc",2)==0) + { + // Process -sc before reading any file lists. + ProcessSwitch(Arg); + if (*LogName!=0) + InitLogOptions(LogName,ErrlogCharset); + } + } + else + if (*Command==0) + wcsncpy(Command,Arg,ASIZE(Command)); // Need for rar.ini. +} +#endif + + +#if !defined(SFX_MODULE) +void CommandData::ReadConfig() +{ + StringList List; + if (ReadTextFile(DefConfigName,&List,true)) + { + wchar *Str; + while ((Str=List.GetString())!=NULL) + { + while (IsSpace(*Str)) + Str++; + if (wcsnicomp(Str,L"switches=",9)==0) + ProcessSwitchesString(Str+9); + if (*Command!=0) + { + wchar Cmd[16]; + wcsncpyz(Cmd,Command,ASIZE(Cmd)); + wchar C0=toupperw(Cmd[0]); + wchar C1=toupperw(Cmd[1]); + if (C0=='I' || C0=='L' || C0=='M' || C0=='S' || C0=='V') + Cmd[1]=0; + if (C0=='R' && (C1=='R' || C1=='V')) + Cmd[2]=0; + wchar SwName[16+ASIZE(Cmd)]; + swprintf(SwName,ASIZE(SwName),L"switches_%s=",Cmd); + size_t Length=wcslen(SwName); + if (wcsnicomp(Str,SwName,Length)==0) + ProcessSwitchesString(Str+Length); + } + } + } +} +#endif + + +#if !defined(SFX_MODULE) +void CommandData::ProcessSwitchesString(const wchar *Str) +{ + wchar *Par; + while ((Str=AllocCmdParam(Str,&Par))!=NULL) + { + if (IsSwitch(*Par)) + ProcessSwitch(Par+1); + free(Par); + } +} +#endif + + +#if !defined(SFX_MODULE) +void CommandData::ProcessSwitch(const wchar *Switch) +{ + + switch(toupperw(Switch[0])) + { + case '@': + ListMode=Switch[1]=='+' ? RCLM_ACCEPT_LISTS:RCLM_REJECT_LISTS; + break; + case 'A': + switch(toupperw(Switch[1])) + { + case 'C': + ClearArc=true; + break; + case 'D': + AppendArcNameToPath=true; + break; +#ifndef SFX_MODULE + case 'G': + if (Switch[2]=='-' && Switch[3]==0) + GenerateArcName=0; + else + { + GenerateArcName=true; + wcsncpyz(GenerateMask,Switch+2,ASIZE(GenerateMask)); + } + break; +#endif + case 'I': + IgnoreGeneralAttr=true; + break; + case 'N': // Reserved for archive name. + break; + case 'O': + AddArcOnly=true; + break; + case 'P': + wcscpy(ArcPath,Switch+2); + break; + case 'S': + SyncFiles=true; + break; + default: + BadSwitch(Switch); + break; + } + break; + case 'C': + if (Switch[2]==0) + switch(toupperw(Switch[1])) + { + case '-': + DisableComment=true; + break; + case 'U': + ConvertNames=NAMES_UPPERCASE; + break; + case 'L': + ConvertNames=NAMES_LOWERCASE; + break; + } + break; + case 'D': + if (Switch[2]==0) + switch(toupperw(Switch[1])) + { + case 'S': + DisableSortSolid=true; + break; + case 'H': + OpenShared=true; + break; + case 'F': + DeleteFiles=true; + break; + } + break; + case 'E': + switch(toupperw(Switch[1])) + { + case 'P': + switch(Switch[2]) + { + case 0: + ExclPath=EXCL_SKIPWHOLEPATH; + break; + case '1': + ExclPath=EXCL_BASEPATH; + break; + case '2': + ExclPath=EXCL_SAVEFULLPATH; + break; + case '3': + ExclPath=EXCL_ABSPATH; + break; + } + break; + default: + if (Switch[1]=='+') + { + InclFileAttr|=GetExclAttr(Switch+2); + InclAttrSet=true; + } + else + ExclFileAttr|=GetExclAttr(Switch+1); + break; + } + break; + case 'F': + if (Switch[1]==0) + FreshFiles=true; + else + BadSwitch(Switch); + break; + case 'H': + switch (toupperw(Switch[1])) + { + case 'P': + EncryptHeaders=true; + if (Switch[2]!=0) + { + Password.Set(Switch+2); + cleandata((void *)Switch,wcslen(Switch)*sizeof(Switch[0])); + } + else + if (!Password.IsSet()) + { + uiGetPassword(UIPASSWORD_GLOBAL,NULL,&Password); + eprintf(L"\n"); + } + break; + default : + BadSwitch(Switch); + break; + } + break; + case 'I': + if (wcsnicomp(Switch+1,L"LOG",3)==0) + { + wcsncpyz(LogName,Switch[4]!=0 ? Switch+4:DefLogName,ASIZE(LogName)); + break; + } + if (wcsicomp(Switch+1,L"SND")==0) + { + Sound=true; + break; + } + if (wcsicomp(Switch+1,L"ERR")==0) + { + MsgStream=MSG_STDERR; + break; + } + if (wcsnicomp(Switch+1,L"EML",3)==0) + { + wcsncpyz(EmailTo,Switch[4]!=0 ? Switch+4:L"@",ASIZE(EmailTo)); + break; + } + if (wcsicomp(Switch+1,L"NUL")==0) + { + MsgStream=MSG_NULL; + break; + } + if (toupperw(Switch[1])=='D') + { + for (uint I=2;Switch[I]!=0;I++) + switch(toupperw(Switch[I])) + { + case 'Q': + MsgStream=MSG_ERRONLY; + break; + case 'C': + DisableCopyright=true; + break; + case 'D': + DisableDone=true; + break; + case 'P': + DisablePercentage=true; + break; + } + break; + } + if (wcsicomp(Switch+1,L"OFF")==0) + { + Shutdown=true; + break; + } + if (wcsicomp(Switch+1,L"VER")==0) + { + PrintVersion=true; + break; + } + break; + case 'K': + switch(toupperw(Switch[1])) + { + case 'B': + KeepBroken=true; + break; + case 0: + Lock=true; + break; + } + break; + case 'M': + switch(toupperw(Switch[1])) + { + case 'C': + { + const wchar *Str=Switch+2; + if (*Str=='-') + for (uint I=0;IMaxPoolThreads || Threads<1) + BadSwitch(Switch); + else + { + } + break; +#endif + default: + Method=Switch[1]-'0'; + if (Method>5 || Method<0) + BadSwitch(Switch); + break; + } + break; + case 'N': + case 'X': + if (Switch[1]!=0) + { + StringList *Args=toupperw(Switch[0])=='N' ? &InclArgs:&ExclArgs; + if (Switch[1]=='@' && !IsWildcard(Switch)) + { + RAR_CHARSET Charset=FilelistCharset; + +#if defined(_WIN_ALL) + // for compatibility reasons we use OEM encoding + // in Win32 console version by default + +// if (Charset==RCH_DEFAULT) +// Charset=RCH_OEM; +#endif + + ReadTextFile(Switch+2,Args,false,true,Charset,true,true,true); + } + else + Args->AddString(Switch+1); + } + break; + case 'O': + switch(toupperw(Switch[1])) + { + case '+': + Overwrite=OVERWRITE_ALL; + break; + case '-': + Overwrite=OVERWRITE_NONE; + break; + case 0: + Overwrite=OVERWRITE_FORCE_ASK; + break; +#ifdef _WIN_ALL + case 'C': + SetCompressedAttr=true; + break; +#endif + case 'H': + SaveHardLinks=true; + break; + + +#ifdef SAVE_LINKS + case 'L': + SaveSymLinks=true; + if (toupperw(Switch[2])=='A') + AbsoluteLinks=true; + break; +#endif +#ifdef _WIN_ALL + case 'N': + if (toupperw(Switch[2])=='I') + AllowIncompatNames=true; + break; +#endif + case 'R': + Overwrite=OVERWRITE_AUTORENAME; + break; +#ifdef _WIN_ALL + case 'S': + SaveStreams=true; + break; +#endif + case 'W': + ProcessOwners=true; + break; + default : + BadSwitch(Switch); + break; + } + break; + case 'P': + if (Switch[1]==0) + { + uiGetPassword(UIPASSWORD_GLOBAL,NULL,&Password); + eprintf(L"\n"); + } + else + { + Password.Set(Switch+1); + cleandata((void *)Switch,wcslen(Switch)*sizeof(Switch[0])); + } + break; +#ifndef SFX_MODULE + case 'Q': + if (toupperw(Switch[1])=='O') + switch(toupperw(Switch[2])) + { + case 0: + QOpenMode=QOPEN_AUTO; + break; + case '-': + QOpenMode=QOPEN_NONE; + break; + case '+': + QOpenMode=QOPEN_ALWAYS; + break; + default: + BadSwitch(Switch); + break; + } + else + BadSwitch(Switch); + break; +#endif + case 'R': + switch(toupperw(Switch[1])) + { + case 0: + Recurse=RECURSE_ALWAYS; + break; + case '-': + Recurse=RECURSE_DISABLE; + break; + case '0': + Recurse=RECURSE_WILDCARDS; + break; + case 'I': + { + Priority=atoiw(Switch+2); + if (Priority<0 || Priority>15) + BadSwitch(Switch); + const wchar *ChPtr=wcschr(Switch+2,':'); + if (ChPtr!=NULL) + { + SleepTime=atoiw(ChPtr+1); + if (SleepTime>1000) + BadSwitch(Switch); + InitSystemOptions(SleepTime); + } + SetPriority(Priority); + } + break; + } + break; + case 'S': + if (IsDigit(Switch[1])) + { + Solid|=SOLID_COUNT; + SolidCount=atoiw(&Switch[1]); + } + else + switch(toupperw(Switch[1])) + { + case 0: + Solid|=SOLID_NORMAL; + break; + case '-': + Solid=SOLID_NONE; + break; + case 'E': + Solid|=SOLID_FILEEXT; + break; + case 'V': + Solid|=Switch[2]=='-' ? SOLID_VOLUME_DEPENDENT:SOLID_VOLUME_INDEPENDENT; + break; + case 'D': + Solid|=SOLID_VOLUME_DEPENDENT; + break; + case 'L': + if (IsDigit(Switch[2])) + FileSizeLess=atoilw(Switch+2); + break; + case 'M': + if (IsDigit(Switch[2])) + FileSizeMore=atoilw(Switch+2); + break; + case 'C': + { + bool AlreadyBad=false; // Avoid reporting "bad switch" several times. + + RAR_CHARSET rch=RCH_DEFAULT; + switch(toupperw(Switch[2])) + { + case 'A': + rch=RCH_ANSI; + break; + case 'O': + rch=RCH_OEM; + break; + case 'U': + rch=RCH_UNICODE; + break; + case 'F': + rch=RCH_UTF8; + break; + default : + BadSwitch(Switch); + AlreadyBad=true; + break; + }; + if (!AlreadyBad) + if (Switch[3]==0) + CommentCharset=FilelistCharset=ErrlogCharset=RedirectCharset=rch; + else + for (uint I=3;Switch[I]!=0 && !AlreadyBad;I++) + switch(toupperw(Switch[I])) + { + case 'C': + CommentCharset=rch; + break; + case 'L': + FilelistCharset=rch; + break; + case 'R': + RedirectCharset=rch; + break; + default: + BadSwitch(Switch); + AlreadyBad=true; + break; + } + } + break; + + } + break; + case 'T': + switch(toupperw(Switch[1])) + { + case 'K': + ArcTime=ARCTIME_KEEP; + break; + case 'L': + ArcTime=ARCTIME_LATEST; + break; + case 'O': + FileTimeBefore.SetAgeText(Switch+2); + break; + case 'N': + FileTimeAfter.SetAgeText(Switch+2); + break; + case 'B': + FileTimeBefore.SetIsoText(Switch+2); + break; + case 'A': + FileTimeAfter.SetIsoText(Switch+2); + break; + case 'S': + { + EXTTIME_MODE Mode=EXTTIME_HIGH3; + bool CommonMode=Switch[2]>='0' && Switch[2]<='4'; + if (CommonMode) + Mode=(EXTTIME_MODE)(Switch[2]-'0'); + if (Mode==EXTTIME_HIGH1 || Mode==EXTTIME_HIGH2) // '2' and '3' not supported anymore. + Mode=EXTTIME_HIGH3; + if (Switch[2]=='-') + Mode=EXTTIME_NONE; + if (CommonMode || Switch[2]=='-' || Switch[2]=='+' || Switch[2]==0) + xmtime=xctime=xatime=Mode; + else + { + if (Switch[3]>='0' && Switch[3]<='4') + Mode=(EXTTIME_MODE)(Switch[3]-'0'); + if (Mode==EXTTIME_HIGH1 || Mode==EXTTIME_HIGH2) // '2' and '3' not supported anymore. + Mode=EXTTIME_HIGH3; + if (Switch[3]=='-') + Mode=EXTTIME_NONE; + switch(toupperw(Switch[2])) + { + case 'M': + xmtime=Mode; + break; + case 'C': + xctime=Mode; + break; + case 'A': + xatime=Mode; + break; + } + } + } + break; + case '-': + Test=false; + break; + case 0: + Test=true; + break; + default: + BadSwitch(Switch); + break; + } + break; + case 'U': + if (Switch[1]==0) + UpdateFiles=true; + else + BadSwitch(Switch); + break; + case 'V': + switch(toupperw(Switch[1])) + { + case 'P': + VolumePause=true; + break; + case 'E': + if (toupperw(Switch[2])=='R') + VersionControl=atoiw(Switch+3)+1; + break; + case '-': + VolSize=0; + break; + default: + VolSize=VOLSIZE_AUTO; // UnRAR -v switch for list command. + break; + } + break; + case 'W': + wcsncpyz(TempPath,Switch+1,ASIZE(TempPath)); + AddEndSlash(TempPath,ASIZE(TempPath)); + break; + case 'Y': + AllYes=true; + break; + case 'Z': + if (Switch[1]==0) + { + // If comment file is not specified, we read data from stdin. + wcscpy(CommentFile,L"stdin"); + } + else + wcsncpyz(CommentFile,Switch+1,ASIZE(CommentFile)); + break; + case '?' : + OutHelp(RARX_SUCCESS); + break; + default : + BadSwitch(Switch); + break; + } +} +#endif + + +#if !defined(SFX_MODULE) +void CommandData::BadSwitch(const wchar *Switch) +{ + mprintf(St(MUnknownOption),Switch); + ErrHandler.Exit(RARX_USERERROR); +} +#endif + + +void CommandData::OutTitle() +{ + if (BareOutput || DisableCopyright) + return; +#if defined(__GNUC__) && defined(SFX_MODULE) + mprintf(St(MCopyrightS)); +#else +#ifndef SILENT + static bool TitleShown=false; + if (TitleShown) + return; + TitleShown=true; + + wchar Version[80]; + if (RARVER_BETA!=0) + swprintf(Version,ASIZE(Version),L"%d.%02d %ls %d",RARVER_MAJOR,RARVER_MINOR,St(MBeta),RARVER_BETA); + else + swprintf(Version,ASIZE(Version),L"%d.%02d",RARVER_MAJOR,RARVER_MINOR); +#if defined(_WIN_32) || defined(_WIN_64) + wcsncatz(Version,L" ",ASIZE(Version)); +#endif +#ifdef _WIN_32 + wcsncatz(Version,St(Mx86),ASIZE(Version)); +#endif +#ifdef _WIN_64 + wcsncatz(Version,St(Mx64),ASIZE(Version)); +#endif + if (PrintVersion) + { + mprintf(L"%s",Version); + exit(0); + } + mprintf(St(MUCopyright),Version,RARVER_YEAR); +#endif +#endif +} + + +inline bool CmpMSGID(MSGID i1,MSGID i2) +{ +#ifdef MSGID_INT + return i1==i2; +#else + // If MSGID is const char*, we cannot compare pointers only. + // Pointers to different instances of same string can differ, + // so we need to compare complete strings. + return wcscmp(i1,i2)==0; +#endif +} + +void CommandData::OutHelp(RAR_EXIT ExitCode) +{ +#if !defined(SILENT) + OutTitle(); + static MSGID Help[]={ +#ifdef SFX_MODULE + // Console SFX switches definition. + MCHelpCmd,MSHelpCmdE,MSHelpCmdT,MSHelpCmdV +#else + // UnRAR switches definition. + MUNRARTitle1,MRARTitle2,MCHelpCmd,MCHelpCmdE,MCHelpCmdL, + MCHelpCmdP,MCHelpCmdT,MCHelpCmdV,MCHelpCmdX,MCHelpSw,MCHelpSwm, + MCHelpSwAT,MCHelpSwAC,MCHelpSwAD,MCHelpSwAG,MCHelpSwAI,MCHelpSwAP, + MCHelpSwCm,MCHelpSwCFGm,MCHelpSwCL,MCHelpSwCU, + MCHelpSwDH,MCHelpSwEP,MCHelpSwEP3,MCHelpSwF,MCHelpSwIDP,MCHelpSwIERR, + MCHelpSwINUL,MCHelpSwIOFF,MCHelpSwKB,MCHelpSwN,MCHelpSwNa,MCHelpSwNal, + MCHelpSwO,MCHelpSwOC,MCHelpSwOL,MCHelpSwOR,MCHelpSwOW,MCHelpSwP, + MCHelpSwPm,MCHelpSwR,MCHelpSwRI,MCHelpSwSC,MCHelpSwSL,MCHelpSwSM, + MCHelpSwTA,MCHelpSwTB,MCHelpSwTN,MCHelpSwTO,MCHelpSwTS,MCHelpSwU, + MCHelpSwVUnr,MCHelpSwVER,MCHelpSwVP,MCHelpSwX,MCHelpSwXa,MCHelpSwXal, + MCHelpSwY +#endif + }; + + for (uint I=0;IRewind(); + while (Args->GetString(CurMask,ASIZE(CurMask))) + { + wchar *LastMaskChar=PointToLastChar(CurMask); + bool DirMask=IsPathDiv(*LastMaskChar); // Mask for directories only. + + if (Dir) + { + // CheckName is a directory. + if (DirMask) + { + // We process the directory and have the directory exclusion mask. + // So let's convert "mask\" to "mask" and process it normally. + + *LastMaskChar=0; + } + else + { + // REMOVED, we want -npath\* to match empty folders too. + // If mask has wildcards in name part and does not have the trailing + // '\' character, we cannot use it for directories. + + // if (IsWildcard(PointToName(CurMask))) + // continue; + } + } + else + { + // If we process a file inside of directory excluded by "dirmask\". + // we want to exclude such file too. So we convert "dirmask\" to + // "dirmask\*". It is important for operations other than archiving + // with -x. When archiving with -x, directory matched by "dirmask\" + // is excluded from further scanning. + + if (DirMask) + wcsncatz(CurMask,L"*",ASIZE(CurMask)); + } + +#ifndef SFX_MODULE + if (CheckFullPath && IsFullPath(CurMask)) + { + // We do not need to do the special "*\" processing here, because + // unlike the "else" part of this "if", now we convert names to full + // format, so they all include the path, which is matched by "*\" + // correctly. Moreover, removing "*\" from mask would break + // the comparison, because now all names have the path. + + if (*FullName==0) + ConvertNameToFull(CheckName,FullName,ASIZE(FullName)); + if (CmpName(CurMask,FullName,MatchMode)) + return true; + } + else +#endif + { + wchar NewName[NM+2],*CurName=Name; + + // Important to convert before "*\" check below, so masks like + // d:*\something are processed properly. + wchar *CmpMask=ConvertPath(CurMask,NULL); + + if (CmpMask[0]=='*' && IsPathDiv(CmpMask[1])) + { + // We want "*\name" to match 'name' not only in subdirectories, + // but also in the current directory. We convert the name + // from 'name' to '.\name' to be matched by "*\" part even if it is + // in current directory. + NewName[0]='.'; + NewName[1]=CPATHDIVIDER; + wcsncpyz(NewName+2,Name,ASIZE(NewName)-2); + CurName=NewName; + } + + if (CmpName(CmpMask,CurName,MatchMode)) + return true; + } + } + return false; +} + + +#ifndef SFX_MODULE +// Now this function performs only one task and only in Windows version: +// it skips symlinks to directories if -e1024 switch is specified. +// Symlinks are skipped in ScanTree class, so their entire contents +// is skipped too. Without this function we would check the attribute +// only directly before archiving, so we would skip the symlink record, +// but not the contents of symlinked directory. +bool CommandData::ExclDirByAttr(uint FileAttr) +{ +#ifdef _WIN_ALL + if ((FileAttr & FILE_ATTRIBUTE_REPARSE_POINT)!=0 && + (ExclFileAttr & FILE_ATTRIBUTE_REPARSE_POINT)!=0) + return true; +#endif + return false; +} +#endif + + + + +#ifndef SFX_MODULE +// Return 'true' if we need to exclude the file from processing. +bool CommandData::TimeCheck(RarTime &ft) +{ + if (FileTimeBefore.IsSet() && ft>=FileTimeBefore) + return true; + if (FileTimeAfter.IsSet() && ft<=FileTimeAfter) + return true; + return false; +} +#endif + + +#ifndef SFX_MODULE +// Return 'true' if we need to exclude the file from processing. +bool CommandData::SizeCheck(int64 Size) +{ + if (FileSizeLess!=INT64NDF && Size>=FileSizeLess) + return(true); + if (FileSizeMore!=INT64NDF && Size<=FileSizeMore) + return(true); + return(false); +} +#endif + + + + +int CommandData::IsProcessFile(FileHeader &FileHead,bool *ExactMatch,int MatchType, + wchar *MatchedArg,uint MatchedArgSize) +{ + if (MatchedArg!=NULL && MatchedArgSize>0) + *MatchedArg=0; + if (wcslen(FileHead.FileName)>=NM) + return 0; + bool Dir=FileHead.Dir; + if (ExclCheck(FileHead.FileName,Dir,false,true)) + return 0; +#ifndef SFX_MODULE + if (TimeCheck(FileHead.mtime)) + return 0; + if ((FileHead.FileAttr & ExclFileAttr)!=0 || InclAttrSet && (FileHead.FileAttr & InclFileAttr)==0) + return 0; + if (!Dir && SizeCheck(FileHead.UnpSize)) + return 0; +#endif + wchar *ArgName; + FileArgs.Rewind(); + for (int StringCount=1;(ArgName=FileArgs.GetString())!=NULL;StringCount++) + if (CmpName(ArgName,FileHead.FileName,MatchType)) + { + if (ExactMatch!=NULL) + *ExactMatch=wcsicompc(ArgName,FileHead.FileName)==0; + if (MatchedArg!=NULL) + wcsncpyz(MatchedArg,ArgName,MatchedArgSize); + return StringCount; + } + return 0; +} + + +void CommandData::ProcessCommand() +{ +#ifndef SFX_MODULE + + const wchar *SingleCharCommands=L"FUADPXETK"; + if (Command[0]!=0 && Command[1]!=0 && wcschr(SingleCharCommands,Command[0])!=NULL || *ArcName==0) + OutHelp(*Command==0 ? RARX_SUCCESS:RARX_USERERROR); // Return 'success' for 'rar' without parameters. + + const wchar *ArcExt=GetExt(ArcName); +#ifdef _UNIX + if (ArcExt==NULL && (!FileExist(ArcName) || IsDir(GetFileAttr(ArcName)))) + wcsncatz(ArcName,L".rar",ASIZE(ArcName)); +#else + if (ArcExt==NULL) + wcsncatz(ArcName,L".rar",ASIZE(ArcName)); +#endif + // Treat arcname.part1 as arcname.part1.rar. + if (ArcExt!=NULL && wcsnicomp(ArcExt,L".part",5)==0 && IsDigit(ArcExt[5]) && + !FileExist(ArcName)) + { + wchar Name[NM]; + wcsncpyz(Name,ArcName,ASIZE(Name)); + wcsncatz(Name,L".rar",ASIZE(Name)); + if (FileExist(Name)) + wcsncpyz(ArcName,Name,ASIZE(ArcName)); + } + + if (wcschr(L"AFUMD",*Command)==NULL) + { + if (GenerateArcName) + GenerateArchiveName(ArcName,ASIZE(ArcName),GenerateMask,false); + + StringList ArcMasks; + ArcMasks.AddString(ArcName); + ScanTree Scan(&ArcMasks,Recurse,SaveSymLinks,SCAN_SKIPDIRS); + FindData FindData; + while (Scan.GetNext(&FindData)==SCAN_SUCCESS) + AddArcName(FindData.Name); + } + else + AddArcName(ArcName); +#endif + + switch(Command[0]) + { + case 'P': + case 'X': + case 'E': + case 'T': + case 'I': + { + CmdExtract Extract(this); + Extract.DoExtract(); + } + break; +#ifndef SILENT + case 'V': + case 'L': + ListArchive(this); + break; + default: + OutHelp(RARX_USERERROR); +#endif + } + if (!BareOutput) + mprintf(L"\n"); +} + + +void CommandData::AddArcName(const wchar *Name) +{ + ArcNames.AddString(Name); +} + + +bool CommandData::GetArcName(wchar *Name,int MaxSize) +{ + return ArcNames.GetString(Name,MaxSize); +} + + +bool CommandData::IsSwitch(int Ch) +{ +#if defined(_WIN_ALL) || defined(_EMX) + return Ch=='-' || Ch=='/'; +#else + return Ch=='-'; +#endif +} + + +#ifndef SFX_MODULE +uint CommandData::GetExclAttr(const wchar *Str) +{ + if (IsDigit(*Str)) + return wcstol(Str,NULL,0); + + uint Attr=0; + while (*Str!=0) + { + switch(toupperw(*Str)) + { +#ifdef _UNIX + case 'D': + Attr|=S_IFDIR; + break; + case 'V': + Attr|=S_IFCHR; + break; +#elif defined(_WIN_ALL) || defined(_EMX) + case 'R': + Attr|=0x1; + break; + case 'H': + Attr|=0x2; + break; + case 'S': + Attr|=0x4; + break; + case 'D': + Attr|=0x10; + break; + case 'A': + Attr|=0x20; + break; +#endif + } + Str++; + } + return Attr; +} +#endif + + + + +#ifndef SFX_MODULE +bool CommandData::CheckWinSize() +{ + // Define 0x100000000 as macro to avoid troubles with older compilers. + const uint64 MaxDictSize=INT32TO64(1,0); + // Limit the dictionary size to 4 GB. + for (uint64 I=0x10000;I<=MaxDictSize;I*=2) + if (WinSize==I) + return true; + WinSize=0x400000; + return false; +} +#endif + + +#ifndef SFX_MODULE +void CommandData::ReportWrongSwitches(RARFORMAT Format) +{ + if (Format==RARFMT15) + { + if (HashType!=HASH_CRC32) + uiMsg(UIERROR_INCOMPATSWITCH,L"-ht",4); +#ifdef _WIN_ALL + if (SaveSymLinks) + uiMsg(UIERROR_INCOMPATSWITCH,L"-ol",4); +#endif + if (SaveHardLinks) + uiMsg(UIERROR_INCOMPATSWITCH,L"-oh",4); + +#ifdef _WIN_ALL + // Do not report a wrong dictionary size here, because we are not sure + // yet about archive format. We can switch to RAR5 mode later + // if we update RAR5 archive. + + +#endif + if (QOpenMode!=QOPEN_AUTO) + uiMsg(UIERROR_INCOMPATSWITCH,L"-qo",4); + } + if (Format==RARFMT50) + { + } +} +#endif diff --git a/reference/unrar/cmddata.hpp b/reference/unrar/cmddata.hpp new file mode 100644 index 00000000..e12d4fbc --- /dev/null +++ b/reference/unrar/cmddata.hpp @@ -0,0 +1,64 @@ +#ifndef _RAR_CMDDATA_ +#define _RAR_CMDDATA_ + + +#define DefaultStoreList L"7z;ace;arj;bz2;cab;gz;jpeg;jpg;lha;lz;lzh;mp3;rar;taz;tgz;xz;z;zip;zipx" + +enum RAR_CMD_LIST_MODE {RCLM_AUTO,RCLM_REJECT_LISTS,RCLM_ACCEPT_LISTS}; + +class CommandData:public RAROptions +{ + private: + void ProcessSwitchesString(const wchar *Str); + void ProcessSwitch(const wchar *Switch); + void BadSwitch(const wchar *Switch); + uint GetExclAttr(const wchar *Str); + + bool FileLists; + bool NoMoreSwitches; + RAR_CMD_LIST_MODE ListMode; + bool BareOutput; + public: + CommandData(); + void Init(); + + void ParseCommandLine(bool Preprocess,int argc, char *argv[]); + void ParseArg(wchar *ArgW); + void ParseDone(); + void ParseEnvVar(); + void ReadConfig(); + void PreprocessArg(const wchar *Arg); + void OutTitle(); + void OutHelp(RAR_EXIT ExitCode); + bool IsSwitch(int Ch); + bool ExclCheck(const wchar *CheckName,bool Dir,bool CheckFullPath,bool CheckInclList); + static bool CheckArgs(StringList *Args,bool Dir,const wchar *CheckName,bool CheckFullPath,int MatchMode); + bool ExclDirByAttr(uint FileAttr); + bool TimeCheck(RarTime &ft); + bool SizeCheck(int64 Size); + bool AnyFiltersActive(); + int IsProcessFile(FileHeader &FileHead,bool *ExactMatch=NULL,int MatchType=MATCH_WILDSUBPATH, + wchar *MatchedArg=NULL,uint MatchedArgSize=0); + void ProcessCommand(); + void AddArcName(const wchar *Name); + bool GetArcName(wchar *Name,int MaxSize); + bool CheckWinSize(); + + int GetRecoverySize(const wchar *Str,int DefSize); + +#ifndef SFX_MODULE + void ReportWrongSwitches(RARFORMAT Format); +#endif + + wchar Command[NM+16]; + + wchar ArcName[NM]; + + StringList FileArgs; + StringList ExclArgs; + StringList InclArgs; + StringList ArcNames; + StringList StoreArgs; +}; + +#endif diff --git a/reference/unrar/coder.cpp b/reference/unrar/coder.cpp new file mode 100644 index 00000000..9d971a8a --- /dev/null +++ b/reference/unrar/coder.cpp @@ -0,0 +1,48 @@ + + +inline unsigned int RangeCoder::GetChar() +{ + return(UnpackRead->GetChar()); +} + + +void RangeCoder::InitDecoder(Unpack *UnpackRead) +{ + RangeCoder::UnpackRead=UnpackRead; + + low=code=0; + range=uint(-1); + for (int i=0;i < 4;i++) + code=(code << 8) | GetChar(); +} + + +// (int) cast before "low" added only to suppress compiler warnings. +#define ARI_DEC_NORMALIZE(code,low,range,read) \ +{ \ + while ((low^(low+range))GetChar(); \ + range <<= 8; \ + low <<= 8; \ + } \ +} + + +inline int RangeCoder::GetCurrentCount() +{ + return (code-low)/(range /= SubRange.scale); +} + + +inline uint RangeCoder::GetCurrentShiftCount(uint SHIFT) +{ + return (code-low)/(range >>= SHIFT); +} + + +inline void RangeCoder::Decode() +{ + low += range*SubRange.LowCount; + range *= SubRange.HighCount-SubRange.LowCount; +} diff --git a/reference/unrar/coder.hpp b/reference/unrar/coder.hpp new file mode 100644 index 00000000..7b36ff21 --- /dev/null +++ b/reference/unrar/coder.hpp @@ -0,0 +1,23 @@ +/**************************************************************************** + * Contents: 'Carryless rangecoder' by Dmitry Subbotin * + ****************************************************************************/ + + +class RangeCoder +{ + public: + void InitDecoder(Unpack *UnpackRead); + inline int GetCurrentCount(); + inline uint GetCurrentShiftCount(uint SHIFT); + inline void Decode(); + inline void PutChar(unsigned int c); + inline unsigned int GetChar(); + + uint low, code, range; + struct SUBRANGE + { + uint LowCount, HighCount, scale; + } SubRange; + + Unpack *UnpackRead; +}; diff --git a/reference/unrar/compress.hpp b/reference/unrar/compress.hpp new file mode 100644 index 00000000..8e4f1ed3 --- /dev/null +++ b/reference/unrar/compress.hpp @@ -0,0 +1,50 @@ +#ifndef _RAR_COMPRESS_ +#define _RAR_COMPRESS_ + +// Combine pack and unpack constants to class to avoid polluting global +// namespace with numerous short names. +class PackDef +{ + public: + static const uint MAX_LZ_MATCH = 0x1001; + static const uint MAX3_LZ_MATCH = 0x101; // Maximum match length for RAR v3. + static const uint LOW_DIST_REP_COUNT = 16; + + static const uint NC = 306; /* alphabet = {0, 1, 2, ..., NC - 1} */ + static const uint DC = 64; + static const uint LDC = 16; + static const uint RC = 44; + static const uint HUFF_TABLE_SIZE = NC + DC + RC + LDC; + static const uint BC = 20; + + static const uint NC30 = 299; /* alphabet = {0, 1, 2, ..., NC - 1} */ + static const uint DC30 = 60; + static const uint LDC30 = 17; + static const uint RC30 = 28; + static const uint BC30 = 20; + static const uint HUFF_TABLE_SIZE30 = NC30 + DC30 + RC30 + LDC30; + + static const uint NC20 = 298; /* alphabet = {0, 1, 2, ..., NC - 1} */ + static const uint DC20 = 48; + static const uint RC20 = 28; + static const uint BC20 = 19; + static const uint MC20 = 257; + + // Largest alphabet size among all values listed above. + static const uint LARGEST_TABLE_SIZE = 306; + + enum { + CODE_HUFFMAN, CODE_LZ, CODE_REPEATLZ, CODE_CACHELZ, CODE_STARTFILE, + CODE_ENDFILE, CODE_FILTER, CODE_FILTERDATA + }; +}; + + +enum FilterType { + // These values must not be changed, because we use them directly + // in RAR5 compression and decompression code. + FILTER_DELTA=0, FILTER_E8, FILTER_E8E9, FILTER_ARM, + FILTER_AUDIO, FILTER_RGB, FILTER_ITANIUM, FILTER_PPM, FILTER_NONE +}; + +#endif diff --git a/reference/unrar/consio.cpp b/reference/unrar/consio.cpp new file mode 100644 index 00000000..2f71a991 --- /dev/null +++ b/reference/unrar/consio.cpp @@ -0,0 +1,352 @@ +#include "rar.hpp" +#include "log.cpp" + +static MESSAGE_TYPE MsgStream=MSG_STDOUT; +static RAR_CHARSET RedirectCharset=RCH_DEFAULT; + +const int MaxMsgSize=2*NM+2048; + +static bool StdoutRedirected=false,StderrRedirected=false,StdinRedirected=false; + +#ifdef _WIN_ALL +static bool IsRedirected(DWORD nStdHandle) +{ + HANDLE hStd=GetStdHandle(nStdHandle); + DWORD Mode; + return GetFileType(hStd)!=FILE_TYPE_CHAR || GetConsoleMode(hStd,&Mode)==0; +} +#endif + + +void InitConsole() +{ +#ifdef _WIN_ALL + // We want messages like file names or progress percent to be printed + // immediately. Use only in Windows, in Unix they can cause wprintf %ls + // to fail with non-English strings. + setbuf(stdout,NULL); + setbuf(stderr,NULL); + + // Detect if output is redirected and set output mode properly. + // We do not want to send Unicode output to files and especially to pipes + // like '|more', which cannot handle them correctly in Windows. + // In Unix console output is UTF-8 and it is handled correctly + // when redirecting, so no need to perform any adjustments. + StdoutRedirected=IsRedirected(STD_OUTPUT_HANDLE); + StderrRedirected=IsRedirected(STD_ERROR_HANDLE); + StdinRedirected=IsRedirected(STD_INPUT_HANDLE); +#ifdef _MSC_VER + if (!StdoutRedirected) + _setmode(_fileno(stdout), _O_U16TEXT); + if (!StderrRedirected) + _setmode(_fileno(stderr), _O_U16TEXT); +#endif +#elif defined(_UNIX) + StdoutRedirected=!isatty(fileno(stdout)); + StderrRedirected=!isatty(fileno(stderr)); + StdinRedirected=!isatty(fileno(stdin)); +#endif +} + + +void InitConsoleOptions(MESSAGE_TYPE MsgStream,RAR_CHARSET RedirectCharset) +{ + ::MsgStream=MsgStream; + ::RedirectCharset=RedirectCharset; +} + + +#ifndef SILENT +static void cvt_wprintf(FILE *dest,const wchar *fmt,va_list arglist) +{ + // This buffer is for format string only, not for entire output, + // so it can be short enough. + wchar fmtw[1024]; + PrintfPrepareFmt(fmt,fmtw,ASIZE(fmtw)); +#ifdef _WIN_ALL + safebuf wchar Msg[MaxMsgSize]; + if (dest==stdout && StdoutRedirected || dest==stderr && StderrRedirected) + { + HANDLE hOut=GetStdHandle(dest==stdout ? STD_OUTPUT_HANDLE:STD_ERROR_HANDLE); + vswprintf(Msg,ASIZE(Msg),fmtw,arglist); + DWORD Written; + if (RedirectCharset==RCH_UNICODE) + WriteFile(hOut,Msg,(DWORD)wcslen(Msg)*sizeof(*Msg),&Written,NULL); + else + { + // Avoid Unicode for redirect in Windows, it does not work with pipes. + safebuf char MsgA[MaxMsgSize]; + if (RedirectCharset==RCH_UTF8) + WideToUtf(Msg,MsgA,ASIZE(MsgA)); + else + WideToChar(Msg,MsgA,ASIZE(MsgA)); + if (RedirectCharset==RCH_DEFAULT || RedirectCharset==RCH_OEM) + CharToOemA(MsgA,MsgA); // Console tools like 'more' expect OEM encoding. + + // We already converted \n to \r\n above, so we use WriteFile instead + // of C library to avoid unnecessary additional conversion. + WriteFile(hOut,MsgA,(DWORD)strlen(MsgA),&Written,NULL); + } + return; + } + // MSVC2008 vfwprintf writes every character to console separately + // and it is too slow. We use direct WriteConsole call instead. + vswprintf(Msg,ASIZE(Msg),fmtw,arglist); + HANDLE hOut=GetStdHandle(dest==stderr ? STD_ERROR_HANDLE:STD_OUTPUT_HANDLE); + DWORD Written; + WriteConsole(hOut,Msg,(DWORD)wcslen(Msg),&Written,NULL); +#else + vfwprintf(dest,fmtw,arglist); + // We do not use setbuf(NULL) in Unix (see comments in InitConsole). + fflush(dest); +#endif +} + + +void mprintf(const wchar *fmt,...) +{ + if (MsgStream==MSG_NULL || MsgStream==MSG_ERRONLY) + return; + + fflush(stderr); // Ensure proper message order. + + va_list arglist; + va_start(arglist,fmt); + FILE *dest=MsgStream==MSG_STDERR ? stderr:stdout; + cvt_wprintf(dest,fmt,arglist); + va_end(arglist); +} +#endif + + +#ifndef SILENT +void eprintf(const wchar *fmt,...) +{ + if (MsgStream==MSG_NULL) + return; + + fflush(stdout); // Ensure proper message order. + + va_list arglist; + va_start(arglist,fmt); + cvt_wprintf(stderr,fmt,arglist); + va_end(arglist); +} +#endif + + +#ifndef SILENT +static void GetPasswordText(wchar *Str,uint MaxLength) +{ + if (MaxLength==0) + return; + if (StdinRedirected) + getwstr(Str,MaxLength); // Read from pipe or redirected file. + else + { +#ifdef _WIN_ALL + HANDLE hConIn=GetStdHandle(STD_INPUT_HANDLE); + HANDLE hConOut=GetStdHandle(STD_OUTPUT_HANDLE); + DWORD ConInMode,ConOutMode; + DWORD Read=0; + GetConsoleMode(hConIn,&ConInMode); + GetConsoleMode(hConOut,&ConOutMode); + SetConsoleMode(hConIn,ENABLE_LINE_INPUT); + SetConsoleMode(hConOut,ENABLE_PROCESSED_OUTPUT|ENABLE_WRAP_AT_EOL_OUTPUT); + + ReadConsole(hConIn,Str,MaxLength-1,&Read,NULL); + Str[Read]=0; + SetConsoleMode(hConIn,ConInMode); + SetConsoleMode(hConOut,ConOutMode); +#else + char StrA[MAXPASSWORD]; +#if defined(_EMX) || defined (__VMS) + fgets(StrA,ASIZE(StrA)-1,stdin); +#elif defined(__sun) + strncpyz(StrA,getpassphrase(""),ASIZE(StrA)); +#else + strncpyz(StrA,getpass(""),ASIZE(StrA)); +#endif + CharToWide(StrA,Str,MaxLength); + cleandata(StrA,sizeof(StrA)); +#endif + } + Str[MaxLength-1]=0; + RemoveLF(Str); +} +#endif + + +#ifndef SILENT +bool GetConsolePassword(UIPASSWORD_TYPE Type,const wchar *FileName,SecPassword *Password) +{ + if (!StdinRedirected) + uiAlarm(UIALARM_QUESTION); + + while (true) + { + if (!StdinRedirected) + if (Type==UIPASSWORD_GLOBAL) + eprintf(L"\n%s: ",St(MAskPsw)); + else + eprintf(St(MAskPswFor),FileName); + + wchar PlainPsw[MAXPASSWORD]; + GetPasswordText(PlainPsw,ASIZE(PlainPsw)); + if (*PlainPsw==0 && Type==UIPASSWORD_GLOBAL) + return false; + if (!StdinRedirected && Type==UIPASSWORD_GLOBAL) + { + eprintf(St(MReAskPsw)); + wchar CmpStr[MAXPASSWORD]; + GetPasswordText(CmpStr,ASIZE(CmpStr)); + if (*CmpStr==0 || wcscmp(PlainPsw,CmpStr)!=0) + { + eprintf(St(MNotMatchPsw)); + cleandata(PlainPsw,sizeof(PlainPsw)); + cleandata(CmpStr,sizeof(CmpStr)); + continue; + } + cleandata(CmpStr,sizeof(CmpStr)); + } + Password->Set(PlainPsw); + cleandata(PlainPsw,sizeof(PlainPsw)); + break; + } + return true; +} +#endif + + +#ifndef SILENT +bool getwstr(wchar *str,size_t n) +{ + // Print buffered prompt title function before waiting for input. + fflush(stderr); + + *str=0; +#if defined(_WIN_ALL) + // fgetws does not work well with non-English text in Windows, + // so we do not use it. + if (StdinRedirected) // ReadConsole does not work if redirected. + { + // fgets does not work well with pipes in Windows in our test. + // Let's use files. + Array StrA(n*4); // Up to 4 UTF-8 characters per wchar_t. + File SrcFile; + SrcFile.SetHandleType(FILE_HANDLESTD); + int ReadSize=SrcFile.Read(&StrA[0],StrA.Size()-1); + if (ReadSize<=0) + { + // Looks like stdin is a null device. We can enter to infinite loop + // calling Ask(), so let's better exit. + ErrHandler.Exit(RARX_USERBREAK); + } + StrA[ReadSize]=0; + CharToWide(&StrA[0],str,n); + cleandata(&StrA[0],StrA.Size()); // We can use this function to enter passwords. + } + else + { + DWORD ReadSize=0; + if (ReadConsole(GetStdHandle(STD_INPUT_HANDLE),str,DWORD(n-1),&ReadSize,NULL)==0) + return false; + str[ReadSize]=0; + } +#else + if (fgetws(str,n,stdin)==NULL) + ErrHandler.Exit(RARX_USERBREAK); // Avoid infinite Ask() loop. +#endif + RemoveLF(str); + return true; +} +#endif + + +#ifndef SILENT +// We allow this function to return 0 in case of invalid input, +// because it might be convenient to press Enter to some not dangerous +// prompts like "insert disk with next volume". We should call this function +// again in case of 0 in dangerous prompt such as overwriting file. +int Ask(const wchar *AskStr) +{ + uiAlarm(UIALARM_QUESTION); + + const int MaxItems=10; + wchar Item[MaxItems][40]; + int ItemKeyPos[MaxItems],NumItems=0; + + for (const wchar *NextItem=AskStr;NextItem!=NULL;NextItem=wcschr(NextItem+1,'_')) + { + wchar *CurItem=Item[NumItems]; + wcsncpyz(CurItem,NextItem+1,ASIZE(Item[0])); + wchar *EndItem=wcschr(CurItem,'_'); + if (EndItem!=NULL) + *EndItem=0; + int KeyPos=0,CurKey; + while ((CurKey=CurItem[KeyPos])!=0) + { + bool Found=false; + for (int I=0;I4 ? L"\n":L" "):L", "); + int KeyPos=ItemKeyPos[I]; + for (int J=0;J[{key};"{string}"p used to redefine + // a keyboard key on some terminals. + if (Data[J]=='\"') + return true; + if (!IsDigit(Data[J]) && Data[J]!=';') + break; + } + return false; +} + + +void OutComment(const wchar *Comment,size_t Size) +{ + if (IsCommentUnsafe(Comment,Size)) + return; + const size_t MaxOutSize=0x400; + for (size_t I=0;I>1)^0xEDB88320 : (C>>1); + CRCTab[I]=C; + } +} + + +static void InitTables() +{ + InitCRC32(crc_tables[0]); + + for (uint I=0;I<256;I++) // Build additional lookup tables. + { + uint C=crc_tables[0][I]; + for (uint J=1;J<8;J++) + { + C=crc_tables[0][(byte)C]^(C>>8); + crc_tables[J][I]=C; + } + } +} + + +struct CallInitCRC {CallInitCRC() {InitTables();}} static CallInit32; + +uint CRC32(uint StartCRC,const void *Addr,size_t Size) +{ + byte *Data=(byte *)Addr; + + // Align Data to 8 for better performance. + for (;Size>0 && ((size_t)Data & 7);Size--,Data++) + StartCRC=crc_tables[0][(byte)(StartCRC^Data[0])]^(StartCRC>>8); + + for (;Size>=8;Size-=8,Data+=8) + { +#ifdef BIG_ENDIAN + StartCRC ^= Data[0]|(Data[1] << 8)|(Data[2] << 16)|(Data[3] << 24); + uint NextData = Data[4]|(Data[5] << 8)|(Data[6] << 16)|(Data[7] << 24); +#else + StartCRC ^= *(uint32 *) Data; + uint NextData = *(uint32 *) (Data +4); +#endif + StartCRC = crc_tables[7][(byte) StartCRC ] ^ + crc_tables[6][(byte)(StartCRC >> 8) ] ^ + crc_tables[5][(byte)(StartCRC >> 16)] ^ + crc_tables[4][(byte)(StartCRC >> 24)] ^ + crc_tables[3][(byte) NextData ] ^ + crc_tables[2][(byte)(NextData >>8 ) ] ^ + crc_tables[1][(byte)(NextData >> 16)] ^ + crc_tables[0][(byte)(NextData >> 24)]; + } + + for (;Size>0;Size--,Data++) // Process left data. + StartCRC=crc_tables[0][(byte)(StartCRC^Data[0])]^(StartCRC>>8); + + return StartCRC; +} + + +#ifndef SFX_MODULE +// For RAR 1.4 archives in case somebody still has them. +ushort Checksum14(ushort StartCRC,const void *Addr,size_t Size) +{ + byte *Data=(byte *)Addr; + for (size_t I=0;I>15))&0xffff; + } + return StartCRC; +} +#endif + + diff --git a/reference/unrar/crc.hpp b/reference/unrar/crc.hpp new file mode 100644 index 00000000..d8fea281 --- /dev/null +++ b/reference/unrar/crc.hpp @@ -0,0 +1,15 @@ +#ifndef _RAR_CRC_ +#define _RAR_CRC_ + +// This function is only to intialize external CRC tables. We do not need to +// call it before calculating CRC32. +void InitCRC32(uint *CRCTab); + +uint CRC32(uint StartCRC,const void *Addr,size_t Size); + +#ifndef SFX_MODULE +ushort Checksum14(ushort StartCRC,const void *Addr,size_t Size); +#endif + + +#endif diff --git a/reference/unrar/crypt.cpp b/reference/unrar/crypt.cpp new file mode 100644 index 00000000..fc2126d4 --- /dev/null +++ b/reference/unrar/crypt.cpp @@ -0,0 +1,134 @@ +#include "rar.hpp" + +#ifndef SFX_MODULE +#include "crypt1.cpp" +#include "crypt2.cpp" +#endif +#include "crypt3.cpp" +#include "crypt5.cpp" + + +CryptData::CryptData() +{ + Method=CRYPT_NONE; + memset(KDF3Cache,0,sizeof(KDF3Cache)); + memset(KDF5Cache,0,sizeof(KDF5Cache)); + KDF3CachePos=0; + KDF5CachePos=0; + memset(CRCTab,0,sizeof(CRCTab)); +} + + +CryptData::~CryptData() +{ + cleandata(KDF3Cache,sizeof(KDF3Cache)); + cleandata(KDF5Cache,sizeof(KDF5Cache)); +} + + + + +void CryptData::DecryptBlock(byte *Buf,size_t Size) +{ + switch(Method) + { +#ifndef SFX_MODULE + case CRYPT_RAR13: + Decrypt13(Buf,Size); + break; + case CRYPT_RAR15: + Crypt15(Buf,Size); + break; + case CRYPT_RAR20: + for (size_t I=0;IIsSet() || Method==CRYPT_NONE) + return false; + + CryptData::Method=Method; + + wchar PwdW[MAXPASSWORD]; + Password->Get(PwdW,ASIZE(PwdW)); + char PwdA[MAXPASSWORD]; + WideToChar(PwdW,PwdA,ASIZE(PwdA)); + + switch(Method) + { +#ifndef SFX_MODULE + case CRYPT_RAR13: + SetKey13(PwdA); + break; + case CRYPT_RAR15: + SetKey15(PwdA); + break; + case CRYPT_RAR20: + SetKey20(PwdA); + break; +#endif + case CRYPT_RAR30: + SetKey30(Encrypt,Password,PwdW,Salt); + break; + case CRYPT_RAR50: + SetKey50(Encrypt,Password,PwdW,Salt,InitV,Lg2Cnt,HashKey,PswCheck); + break; + } + cleandata(PwdA,sizeof(PwdA)); + cleandata(PwdW,sizeof(PwdW)); + return true; +} + + +// Use the current system time to additionally randomize data. +static void TimeRandomize(byte *RndBuf,size_t BufSize) +{ + static uint Count=0; + RarTime CurTime; + CurTime.SetCurrentTime(); + uint64 Random=CurTime.GetWin()+clock(); + for (size_t I=0;I> ( (I & 7) * 8 )); + RndBuf[I]=byte( (RndByte ^ I) + Count++); + } +} + + + + +// Fill buffer with random data. +void GetRnd(byte *RndBuf,size_t BufSize) +{ + bool Success=false; +#if defined(_WIN_ALL) + HCRYPTPROV hProvider = 0; + if (CryptAcquireContext(&hProvider, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) + { + Success=CryptGenRandom(hProvider, (DWORD)BufSize, RndBuf) == TRUE; + CryptReleaseContext(hProvider, 0); + } +#elif defined(_UNIX) + FILE *rndf = fopen("/dev/urandom", "r"); + if (rndf!=NULL) + { + Success=fread(RndBuf, BufSize, 1, rndf) == BufSize; + fclose(rndf); + } +#endif + // We use this code only as the last resort if code above failed. + if (!Success) + TimeRandomize(RndBuf,BufSize); +} diff --git a/reference/unrar/crypt.hpp b/reference/unrar/crypt.hpp new file mode 100644 index 00000000..f6382ef5 --- /dev/null +++ b/reference/unrar/crypt.hpp @@ -0,0 +1,101 @@ +#ifndef _RAR_CRYPT_ +#define _RAR_CRYPT_ + + +enum CRYPT_METHOD { + CRYPT_NONE,CRYPT_RAR13,CRYPT_RAR15,CRYPT_RAR20,CRYPT_RAR30,CRYPT_RAR50 +}; + +#define SIZE_SALT50 16 +#define SIZE_SALT30 8 +#define SIZE_INITV 16 +#define SIZE_PSWCHECK 8 +#define SIZE_PSWCHECK_CSUM 4 + +#define CRYPT_BLOCK_SIZE 16 +#define CRYPT_BLOCK_MASK (CRYPT_BLOCK_SIZE-1) // 0xf + +#define CRYPT5_KDF_LG2_COUNT 15 // LOG2 of PDKDF2 iteration count. +#define CRYPT5_KDF_LG2_COUNT_MAX 24 // LOG2 of maximum accepted iteration count. +#define CRYPT_VERSION 0 // Supported encryption version. + + +class CryptData +{ + struct KDF5CacheItem + { + SecPassword Pwd; + byte Salt[SIZE_SALT50]; + byte Key[32]; + uint Lg2Count; // Log2 of PBKDF2 repetition count. + byte PswCheckValue[SHA256_DIGEST_SIZE]; + byte HashKeyValue[SHA256_DIGEST_SIZE]; + }; + + struct KDF3CacheItem + { + SecPassword Pwd; + byte Salt[SIZE_SALT30]; + byte Key[16]; + byte Init[16]; + bool SaltPresent; + }; + + + private: + void SetKey13(const char *Password); + void Decrypt13(byte *Data,size_t Count); + + void SetKey15(const char *Password); + void Crypt15(byte *Data,size_t Count); + + void SetKey20(const char *Password); + void Swap20(byte *Ch1,byte *Ch2); + void UpdKeys20(byte *Buf); + void EncryptBlock20(byte *Buf); + void DecryptBlock20(byte *Buf); + + void SetKey30(bool Encrypt,SecPassword *Password,const wchar *PwdW,const byte *Salt); + void SetKey50(bool Encrypt,SecPassword *Password,const wchar *PwdW,const byte *Salt,const byte *InitV,uint Lg2Cnt,byte *HashKey,byte *PswCheck); + + KDF3CacheItem KDF3Cache[4]; + uint KDF3CachePos; + + KDF5CacheItem KDF5Cache[4]; + uint KDF5CachePos; + + CRYPT_METHOD Method; + + Rijndael rin; + + uint CRCTab[256]; // For RAR 1.5 and RAR 2.0 encryption. + + byte SubstTable20[256]; + uint Key20[4]; + + byte Key13[3]; + ushort Key15[4]; + public: + CryptData(); + ~CryptData(); + bool SetCryptKeys(bool Encrypt,CRYPT_METHOD Method,SecPassword *Password, + const byte *Salt,const byte *InitV,uint Lg2Cnt, + byte *HashKey,byte *PswCheck); + void SetAV15Encryption(); + void SetCmt13Encryption(); + void EncryptBlock(byte *Buf,size_t Size); + void DecryptBlock(byte *Buf,size_t Size); + static void SetSalt(byte *Salt,size_t SaltSize); +}; + +void GetRnd(byte *RndBuf,size_t BufSize); + +void hmac_sha256(const byte *Key,size_t KeyLength,const byte *Data, + size_t DataLength,byte *ResDigest); +void pbkdf2(const byte *pass, size_t pass_len, const byte *salt, + size_t salt_len,byte *key, byte *Value1, byte *Value2, + uint rounds); + +void ConvertHashToMAC(HashValue *Value,byte *Key); + +#endif diff --git a/reference/unrar/crypt1.cpp b/reference/unrar/crypt1.cpp new file mode 100644 index 00000000..14263937 --- /dev/null +++ b/reference/unrar/crypt1.cpp @@ -0,0 +1,79 @@ +extern uint CRCTab[256]; + +void CryptData::SetKey13(const char *Password) +{ + Key13[0]=Key13[1]=Key13[2]=0; + for (size_t I=0;Password[I]!=0;I++) + { + byte P=Password[I]; + Key13[0]+=P; + Key13[1]^=P; + Key13[2]+=P; + Key13[2]=(byte)rotls(Key13[2],1,8); + } +} + + +void CryptData::SetKey15(const char *Password) +{ + InitCRC32(CRCTab); + uint PswCRC=CRC32(0xffffffff,Password,strlen(Password)); + Key15[0]=PswCRC&0xffff; + Key15[1]=(PswCRC>>16)&0xffff; + Key15[2]=Key15[3]=0; + for (size_t I=0;Password[I]!=0;I++) + { + byte P=Password[I]; + Key15[2]^=P^CRCTab[P]; + Key15[3]+=P+(CRCTab[P]>>16); + } +} + + +void CryptData::SetAV15Encryption() +{ + InitCRC32(CRCTab); + Method=CRYPT_RAR15; + Key15[0]=0x4765; + Key15[1]=0x9021; + Key15[2]=0x7382; + Key15[3]=0x5215; +} + + +void CryptData::SetCmt13Encryption() +{ + Method=CRYPT_RAR13; + Key13[0]=0; + Key13[1]=7; + Key13[2]=77; +} + + +void CryptData::Decrypt13(byte *Data,size_t Count) +{ + while (Count--) + { + Key13[1]+=Key13[2]; + Key13[0]+=Key13[1]; + *Data-=Key13[0]; + Data++; + } +} + + +void CryptData::Crypt15(byte *Data,size_t Count) +{ + while (Count--) + { + Key15[0]+=0x1234; + Key15[1]^=CRCTab[(Key15[0] & 0x1fe)>>1]; + Key15[2]-=CRCTab[(Key15[0] & 0x1fe)>>1]>>16; + Key15[0]^=Key15[2]; + Key15[3]=rotrs(Key15[3]&0xffff,1,16)^Key15[1]; + Key15[3]=rotrs(Key15[3]&0xffff,1,16); + Key15[0]^=Key15[3]; + *Data^=(byte)(Key15[0]>>8); + Data++; + } +} diff --git a/reference/unrar/crypt2.cpp b/reference/unrar/crypt2.cpp new file mode 100644 index 00000000..5fa4a97d --- /dev/null +++ b/reference/unrar/crypt2.cpp @@ -0,0 +1,133 @@ +#define NROUNDS 32 + +#define substLong(t) ( (uint)SubstTable20[(uint)t&255] | \ + ((uint)SubstTable20[(int)(t>> 8)&255]<< 8) | \ + ((uint)SubstTable20[(int)(t>>16)&255]<<16) | \ + ((uint)SubstTable20[(int)(t>>24)&255]<<24) ) + + +static byte InitSubstTable20[256]={ + 215, 19,149, 35, 73,197,192,205,249, 28, 16,119, 48,221, 2, 42, + 232, 1,177,233, 14, 88,219, 25,223,195,244, 90, 87,239,153,137, + 255,199,147, 70, 92, 66,246, 13,216, 40, 62, 29,217,230, 86, 6, + 71, 24,171,196,101,113,218,123, 93, 91,163,178,202, 67, 44,235, + 107,250, 75,234, 49,167,125,211, 83,114,157,144, 32,193,143, 36, + 158,124,247,187, 89,214,141, 47,121,228, 61,130,213,194,174,251, + 97,110, 54,229,115, 57,152, 94,105,243,212, 55,209,245, 63, 11, + 164,200, 31,156, 81,176,227, 21, 76, 99,139,188,127, 17,248, 51, + 207,120,189,210, 8,226, 41, 72,183,203,135,165,166, 60, 98, 7, + 122, 38,155,170, 69,172,252,238, 39,134, 59,128,236, 27,240, 80, + 131, 3, 85,206,145, 79,154,142,159,220,201,133, 74, 64, 20,129, + 224,185,138,103,173,182, 43, 34,254, 82,198,151,231,180, 58, 10, + 118, 26,102, 12, 50,132, 22,191,136,111,162,179, 45, 4,148,108, + 161, 56, 78,126,242,222, 15,175,146, 23, 33,241,181,190, 77,225, + 0, 46,169,186, 68, 95,237, 65, 53,208,253,168, 9, 18,100, 52, + 116,184,160, 96,109, 37, 30,106,140,104,150, 5,204,117,112, 84 +}; + + +void CryptData::SetKey20(const char *Password) +{ + InitCRC32(CRCTab); + + char Psw[MAXPASSWORD]; + strncpyz(Psw,Password,ASIZE(Psw)); // We'll need to modify it below. + size_t PswLength=strlen(Psw); + + Key20[0]=0xD3A3B879L; + Key20[1]=0x3F6D12F7L; + Key20[2]=0x7515A235L; + Key20[3]=0xA4E7F123L; + + memcpy(SubstTable20,InitSubstTable20,sizeof(SubstTable20)); + for (uint J=0;J<256;J++) + for (size_t I=0;I=0;I--) + { + T=((C+rotls(D,11,32))^Key20[I&3]); + TA=A^substLong(T); + T=((D^rotls(C,17,32))+Key20[I&3]); + TB=B^substLong(T); + A=C; + B=D; + C=TA; + D=TB; + } + RawPut4(C^Key20[0],Buf+0); + RawPut4(D^Key20[1],Buf+4); + RawPut4(A^Key20[2],Buf+8); + RawPut4(B^Key20[3],Buf+12); + UpdKeys20(InBuf); +} + + +void CryptData::UpdKeys20(byte *Buf) +{ + for (int I=0;I<16;I+=4) + { + Key20[0]^=CRCTab[Buf[I]]; + Key20[1]^=CRCTab[Buf[I+1]]; + Key20[2]^=CRCTab[Buf[I+2]]; + Key20[3]^=CRCTab[Buf[I+3]]; + } +} + + +void CryptData::Swap20(byte *Ch1,byte *Ch2) +{ + byte Ch=*Ch1; + *Ch1=*Ch2; + *Ch2=Ch; +} diff --git a/reference/unrar/crypt3.cpp b/reference/unrar/crypt3.cpp new file mode 100644 index 00000000..48406487 --- /dev/null +++ b/reference/unrar/crypt3.cpp @@ -0,0 +1,68 @@ +void CryptData::SetKey30(bool Encrypt,SecPassword *Password,const wchar *PwdW,const byte *Salt) +{ + byte AESKey[16],AESInit[16]; + + bool Cached=false; + for (uint I=0;I>8); + PswNum[2]=(byte)(I>>16); + sha1_process(&c, PswNum, 3); + if (I%(HashRounds/16)==0) + { + sha1_context tempc=c; + uint32 digest[5]; + sha1_done( &tempc, digest ); + AESInit[I/(HashRounds/16)]=(byte)digest[4]; + } + } + uint32 digest[5]; + sha1_done( &c, digest ); + for (int I=0;I<4;I++) + for (int J=0;J<4;J++) + AESKey[I*4+J]=(byte)(digest[I]>>(J*8)); + + KDF3Cache[KDF3CachePos].Pwd=*Password; + if ((KDF3Cache[KDF3CachePos].SaltPresent=(Salt!=NULL))==true) + memcpy(KDF3Cache[KDF3CachePos].Salt,Salt,SIZE_SALT30); + memcpy(KDF3Cache[KDF3CachePos].Key,AESKey,sizeof(AESKey)); + SecHideData(KDF3Cache[KDF3CachePos].Key,sizeof(KDF3Cache[KDF3CachePos].Key),true,false); + memcpy(KDF3Cache[KDF3CachePos].Init,AESInit,sizeof(AESInit)); + KDF3CachePos=(KDF3CachePos+1)%ASIZE(KDF3Cache); + + cleandata(RawPsw,sizeof(RawPsw)); + } + rin.Init(Encrypt, AESKey, 128, AESInit); + cleandata(AESKey,sizeof(AESKey)); + cleandata(AESInit,sizeof(AESInit)); +} + diff --git a/reference/unrar/crypt5.cpp b/reference/unrar/crypt5.cpp new file mode 100644 index 00000000..7562469f --- /dev/null +++ b/reference/unrar/crypt5.cpp @@ -0,0 +1,233 @@ +static void hmac_sha256(const byte *Key,size_t KeyLength,const byte *Data, + size_t DataLength,byte *ResDigest, + sha256_context *ICtxOpt,bool *SetIOpt, + sha256_context *RCtxOpt,bool *SetROpt) +{ + const size_t Sha256BlockSize=64; // As defined in RFC 4868. + + byte KeyHash[SHA256_DIGEST_SIZE]; + if (KeyLength > Sha256BlockSize) // Convert longer keys to key hash. + { + sha256_context KCtx; + sha256_init(&KCtx); + sha256_process(&KCtx, Key, KeyLength); + sha256_done(&KCtx, KeyHash); + + Key = KeyHash; + KeyLength = SHA256_DIGEST_SIZE; + } + + byte KeyBuf[Sha256BlockSize]; // Store the padded key here. + sha256_context ICtx; + + if (ICtxOpt!=NULL && *SetIOpt) + ICtx=*ICtxOpt; // Use already calculated first block context. + else + { + // This calculation is the same for all iterations with same password. + // So for PBKDF2 we can calculate it only for first block and then reuse + // to improve performance. + + for (size_t I = 0; I < KeyLength; I++) // Use 0x36 padding for inner digest. + KeyBuf[I] = Key[I] ^ 0x36; + for (size_t I = KeyLength; I < Sha256BlockSize; I++) + KeyBuf[I] = 0x36; + + sha256_init(&ICtx); + sha256_process(&ICtx, KeyBuf, Sha256BlockSize); // Hash padded key. + } + + if (ICtxOpt!=NULL && !*SetIOpt) // Store constant context for further reuse. + { + *ICtxOpt=ICtx; + *SetIOpt=true; + } + + sha256_process(&ICtx, Data, DataLength); // Hash data. + + byte IDig[SHA256_DIGEST_SIZE]; // Internal digest for padded key and data. + sha256_done(&ICtx, IDig); + + sha256_context RCtx; + + if (RCtxOpt!=NULL && *SetROpt) + RCtx=*RCtxOpt; // Use already calculated first block context. + else + { + // This calculation is the same for all iterations with same password. + // So for PBKDF2 we can calculate it only for first block and then reuse + // to improve performance. + + for (size_t I = 0; I < KeyLength; I++) // Use 0x5c for outer key padding. + KeyBuf[I] = Key[I] ^ 0x5c; + for (size_t I = KeyLength; I < Sha256BlockSize; I++) + KeyBuf[I] = 0x5c; + + sha256_init(&RCtx); + sha256_process(&RCtx, KeyBuf, Sha256BlockSize); // Hash padded key. + } + + if (RCtxOpt!=NULL && !*SetROpt) // Store constant context for further reuse. + { + *RCtxOpt=RCtx; + *SetROpt=true; + } + + sha256_process(&RCtx, IDig, SHA256_DIGEST_SIZE); // Hash internal digest. + + sha256_done(&RCtx, ResDigest); +} + + +// PBKDF2 for 32 byte key length. We generate the key for specified number +// of iteration count also as two supplementary values (key for checksums +// and password verification) for iterations+16 and iterations+32. +void pbkdf2(const byte *Pwd, size_t PwdLength, + const byte *Salt, size_t SaltLength, + byte *Key, byte *V1, byte *V2, uint Count) +{ + const size_t MaxSalt=64; + byte SaltData[MaxSalt+4]; + memcpy(SaltData, Salt, Min(SaltLength,MaxSalt)); + + SaltData[SaltLength + 0] = 0; // Salt concatenated to 1. + SaltData[SaltLength + 1] = 0; + SaltData[SaltLength + 2] = 0; + SaltData[SaltLength + 3] = 1; + + // First iteration: HMAC of password, salt and block index (1). + byte U1[SHA256_DIGEST_SIZE]; + hmac_sha256(Pwd, PwdLength, SaltData, SaltLength + 4, U1, NULL, NULL, NULL, NULL); + byte Fn[SHA256_DIGEST_SIZE]; // Current function value. + memcpy(Fn, U1, sizeof(Fn)); // Function at first iteration. + + uint CurCount[] = { Count-1, 16, 16 }; + byte *CurValue[] = { Key , V1, V2 }; + + sha256_context ICtxOpt,RCtxOpt; + bool SetIOpt=false,SetROpt=false; + + byte U2[SHA256_DIGEST_SIZE]; + for (uint I = 0; I < 3; I++) // For output key and 2 supplementary values. + { + for (uint J = 0; J < CurCount[I]; J++) + { + // U2 = PRF (P, U1). + hmac_sha256(Pwd, PwdLength, U1, sizeof(U1), U2, &ICtxOpt, &SetIOpt, &RCtxOpt, &SetROpt); + memcpy(U1, U2, sizeof(U1)); + for (uint K = 0; K < sizeof(Fn); K++) // Function ^= U. + Fn[K] ^= U1[K]; + } + memcpy(CurValue[I], Fn, SHA256_DIGEST_SIZE); + } + + cleandata(SaltData, sizeof(SaltData)); + cleandata(Fn, sizeof(Fn)); + cleandata(U1, sizeof(U1)); + cleandata(U2, sizeof(U2)); +} + + +void CryptData::SetKey50(bool Encrypt,SecPassword *Password,const wchar *PwdW, + const byte *Salt,const byte *InitV,uint Lg2Cnt,byte *HashKey, + byte *PswCheck) +{ + if (Lg2Cnt>CRYPT5_KDF_LG2_COUNT_MAX) + return; + + byte Key[32],PswCheckValue[SHA256_DIGEST_SIZE],HashKeyValue[SHA256_DIGEST_SIZE]; + bool Found=false; + for (uint I=0;ILg2Count==Lg2Cnt && Item->Pwd==*Password && + memcmp(Item->Salt,Salt,SIZE_SALT50)==0) + { + memcpy(Key,Item->Key,sizeof(Key)); + SecHideData(Key,sizeof(Key),false,false); + + memcpy(PswCheckValue,Item->PswCheckValue,sizeof(PswCheckValue)); + memcpy(HashKeyValue,Item->HashKeyValue,sizeof(HashKeyValue)); + Found=true; + break; + } + } + + if (!Found) + { + char PwdUtf[MAXPASSWORD*4]; + WideToUtf(PwdW,PwdUtf,ASIZE(PwdUtf)); + + pbkdf2((byte *)PwdUtf,strlen(PwdUtf),Salt,SIZE_SALT50,Key,HashKeyValue,PswCheckValue,(1<Lg2Count=Lg2Cnt; + Item->Pwd=*Password; + memcpy(Item->Salt,Salt,SIZE_SALT50); + memcpy(Item->Key,Key,sizeof(Item->Key)); + memcpy(Item->PswCheckValue,PswCheckValue,sizeof(PswCheckValue)); + memcpy(Item->HashKeyValue,HashKeyValue,sizeof(HashKeyValue)); + SecHideData(Item->Key,sizeof(Item->Key),true,false); + } + if (HashKey!=NULL) + memcpy(HashKey,HashKeyValue,SHA256_DIGEST_SIZE); + if (PswCheck!=NULL) + { + memset(PswCheck,0,SIZE_PSWCHECK); + for (uint I=0;IType==HASH_CRC32) + { + byte RawCRC[4]; + RawPut4(Value->CRC32,RawCRC); + byte Digest[SHA256_DIGEST_SIZE]; + hmac_sha256(Key,SHA256_DIGEST_SIZE,RawCRC,sizeof(RawCRC),Digest,NULL,NULL,NULL,NULL); + Value->CRC32=0; + for (uint I=0;ICRC32^=Digest[I] << ((I & 3) * 8); + } + if (Value->Type==HASH_BLAKE2) + { + byte Digest[BLAKE2_DIGEST_SIZE]; + hmac_sha256(Key,BLAKE2_DIGEST_SIZE,Value->Digest,sizeof(Value->Digest),Digest,NULL,NULL,NULL,NULL); + memcpy(Value->Digest,Digest,sizeof(Value->Digest)); + } +} + + +#if 0 +static void TestPBKDF2(); +struct TestKDF {TestKDF() {TestPBKDF2();exit(0);}} GlobalTestKDF; + +void TestPBKDF2() // Test PBKDF2 HMAC-SHA256 +{ + byte Key[32],V1[32],V2[32]; + + pbkdf2((byte *)"password", 8, (byte *)"salt", 4, Key, V1, V2, 1); + byte Res1[32]={0x12, 0x0f, 0xb6, 0xcf, 0xfc, 0xf8, 0xb3, 0x2c, 0x43, 0xe7, 0x22, 0x52, 0x56, 0xc4, 0xf8, 0x37, 0xa8, 0x65, 0x48, 0xc9, 0x2c, 0xcc, 0x35, 0x48, 0x08, 0x05, 0x98, 0x7c, 0xb7, 0x0b, 0xe1, 0x7b }; + mprintf(L"\nPBKDF2 test1: %s", memcmp(Key,Res1,32)==0 ? L"OK":L"Failed"); + + pbkdf2((byte *)"password", 8, (byte *)"salt", 4, Key, V1, V2, 4096); + byte Res2[32]={0xc5, 0xe4, 0x78, 0xd5, 0x92, 0x88, 0xc8, 0x41, 0xaa, 0x53, 0x0d, 0xb6, 0x84, 0x5c, 0x4c, 0x8d, 0x96, 0x28, 0x93, 0xa0, 0x01, 0xce, 0x4e, 0x11, 0xa4, 0x96, 0x38, 0x73, 0xaa, 0x98, 0x13, 0x4a }; + mprintf(L"\nPBKDF2 test2: %s", memcmp(Key,Res2,32)==0 ? L"OK":L"Failed"); + + pbkdf2((byte *)"just some long string pretending to be a password", 49, (byte *)"salt, salt, salt, a lot of salt", 31, Key, V1, V2, 65536); + byte Res3[32]={0x08, 0x0f, 0xa3, 0x1d, 0x42, 0x2d, 0xb0, 0x47, 0x83, 0x9b, 0xce, 0x3a, 0x3b, 0xce, 0x49, 0x51, 0xe2, 0x62, 0xb9, 0xff, 0x76, 0x2f, 0x57, 0xe9, 0xc4, 0x71, 0x96, 0xce, 0x4b, 0x6b, 0x6e, 0xbf}; + mprintf(L"\nPBKDF2 test3: %s", memcmp(Key,Res3,32)==0 ? L"OK":L"Failed"); +} +#endif diff --git a/reference/unrar/dll.cpp b/reference/unrar/dll.cpp new file mode 100644 index 00000000..d12c0b17 --- /dev/null +++ b/reference/unrar/dll.cpp @@ -0,0 +1,474 @@ +#include "rar.hpp" + +static int RarErrorToDll(RAR_EXIT ErrCode); + +struct DataSet +{ + CommandData Cmd; + Archive Arc; + CmdExtract Extract; + int OpenMode; + int HeaderSize; + + DataSet():Arc(&Cmd),Extract(&Cmd) {}; +}; + + +HANDLE PASCAL RAROpenArchive(struct RAROpenArchiveData *r) +{ + RAROpenArchiveDataEx rx; + memset(&rx,0,sizeof(rx)); + rx.ArcName=r->ArcName; + rx.OpenMode=r->OpenMode; + rx.CmtBuf=r->CmtBuf; + rx.CmtBufSize=r->CmtBufSize; + HANDLE hArc=RAROpenArchiveEx(&rx); + r->OpenResult=rx.OpenResult; + r->CmtSize=rx.CmtSize; + r->CmtState=rx.CmtState; + return hArc; +} + + +HANDLE PASCAL RAROpenArchiveEx(struct RAROpenArchiveDataEx *r) +{ + DataSet *Data=NULL; + try + { + r->OpenResult=0; + Data=new DataSet; + Data->Cmd.DllError=0; + Data->OpenMode=r->OpenMode; + Data->Cmd.FileArgs.AddString(L"*"); + + char AnsiArcName[NM]; + *AnsiArcName=0; + if (r->ArcName!=NULL) + { + strncpyz(AnsiArcName,r->ArcName,ASIZE(AnsiArcName)); +#ifdef _WIN_ALL + if (!AreFileApisANSI()) + { + OemToCharBuffA(r->ArcName,AnsiArcName,ASIZE(AnsiArcName)); + AnsiArcName[ASIZE(AnsiArcName)-1]=0; + } +#endif + } + + wchar ArcName[NM]; + GetWideName(AnsiArcName,r->ArcNameW,ArcName,ASIZE(ArcName)); + + Data->Cmd.AddArcName(ArcName); + Data->Cmd.Overwrite=OVERWRITE_ALL; + Data->Cmd.VersionControl=1; + + Data->Cmd.Callback=r->Callback; + Data->Cmd.UserData=r->UserData; + + // Open shared mode is added by request of dll users, who need to + // browse and unpack archives while downloading. + Data->Cmd.OpenShared = true; + if (!Data->Arc.Open(ArcName,FMF_OPENSHARED)) + { + r->OpenResult=ERAR_EOPEN; + delete Data; + return NULL; + } + if (!Data->Arc.IsArchive(true)) + { + if (Data->Cmd.DllError!=0) + r->OpenResult=Data->Cmd.DllError; + else + { + RAR_EXIT ErrCode=ErrHandler.GetErrorCode(); + if (ErrCode!=RARX_SUCCESS && ErrCode!=RARX_WARNING) + r->OpenResult=RarErrorToDll(ErrCode); + else + r->OpenResult=ERAR_BAD_ARCHIVE; + } + delete Data; + return NULL; + } + r->Flags=0; + + if (Data->Arc.Volume) + r->Flags|=0x01; + if (Data->Arc.Locked) + r->Flags|=0x04; + if (Data->Arc.Solid) + r->Flags|=0x08; + if (Data->Arc.NewNumbering) + r->Flags|=0x10; + if (Data->Arc.Signed) + r->Flags|=0x20; + if (Data->Arc.Protected) + r->Flags|=0x40; + if (Data->Arc.Encrypted) + r->Flags|=0x80; + if (Data->Arc.FirstVolume) + r->Flags|=0x100; + + Array CmtDataW; + if (r->CmtBufSize!=0 && Data->Arc.GetComment(&CmtDataW)) + { + Array CmtData(CmtDataW.Size()*4+1); + memset(&CmtData[0],0,CmtData.Size()); + WideToChar(&CmtDataW[0],&CmtData[0],CmtData.Size()-1); + size_t Size=strlen(&CmtData[0])+1; + + r->Flags|=2; + r->CmtState=Size>r->CmtBufSize ? ERAR_SMALL_BUF:1; + r->CmtSize=(uint)Min(Size,r->CmtBufSize); + memcpy(r->CmtBuf,&CmtData[0],r->CmtSize-1); + if (Size<=r->CmtBufSize) + r->CmtBuf[r->CmtSize-1]=0; + } + else + r->CmtState=r->CmtSize=0; + Data->Extract.ExtractArchiveInit(Data->Arc); + return (HANDLE)Data; + } + catch (RAR_EXIT ErrCode) + { + if (Data!=NULL && Data->Cmd.DllError!=0) + r->OpenResult=Data->Cmd.DllError; + else + r->OpenResult=RarErrorToDll(ErrCode); + if (Data != NULL) + delete Data; + return NULL; + } + catch (std::bad_alloc&) // Catch 'new' exception. + { + r->OpenResult=ERAR_NO_MEMORY; + if (Data != NULL) + delete Data; + } + return NULL; // To make compilers happy. +} + + +int PASCAL RARCloseArchive(HANDLE hArcData) +{ + DataSet *Data=(DataSet *)hArcData; + bool Success=Data==NULL ? false:Data->Arc.Close(); + delete Data; + return Success ? ERAR_SUCCESS : ERAR_ECLOSE; +} + + +int PASCAL RARReadHeader(HANDLE hArcData,struct RARHeaderData *D) +{ + struct RARHeaderDataEx X; + memset(&X,0,sizeof(X)); + + int Code=RARReadHeaderEx(hArcData,&X); + + strncpyz(D->ArcName,X.ArcName,ASIZE(D->ArcName)); + strncpyz(D->FileName,X.FileName,ASIZE(D->FileName)); + D->Flags=X.Flags; + D->PackSize=X.PackSize; + D->UnpSize=X.UnpSize; + D->HostOS=X.HostOS; + D->FileCRC=X.FileCRC; + D->FileTime=X.FileTime; + D->UnpVer=X.UnpVer; + D->Method=X.Method; + D->FileAttr=X.FileAttr; + D->CmtSize=0; + D->CmtState=0; + + return Code; +} + + +int PASCAL RARReadHeaderEx(HANDLE hArcData,struct RARHeaderDataEx *D) +{ + DataSet *Data=(DataSet *)hArcData; + try + { + if ((Data->HeaderSize=(int)Data->Arc.SearchBlock(HEAD_FILE))<=0) + { + if (Data->Arc.Volume && Data->Arc.GetHeaderType()==HEAD_ENDARC && + Data->Arc.EndArcHead.NextVolume) + if (MergeArchive(Data->Arc,NULL,false,'L')) + { + Data->Arc.Seek(Data->Arc.CurBlockPos,SEEK_SET); + return RARReadHeaderEx(hArcData,D); + } + else + return ERAR_EOPEN; + + if (Data->Arc.BrokenHeader) + return ERAR_BAD_DATA; + + // Might be necessary if RARSetPassword is still called instead of + // open callback for RAR5 archives and if password is invalid. + if (Data->Arc.FailedHeaderDecryption) + return ERAR_BAD_PASSWORD; + + return ERAR_END_ARCHIVE; + } + FileHeader *hd=&Data->Arc.FileHead; + if (Data->OpenMode==RAR_OM_LIST && hd->SplitBefore) + { + int Code=RARProcessFile(hArcData,RAR_SKIP,NULL,NULL); + if (Code==0) + return RARReadHeaderEx(hArcData,D); + else + return Code; + } + wcsncpy(D->ArcNameW,Data->Arc.FileName,ASIZE(D->ArcNameW)); + WideToChar(D->ArcNameW,D->ArcName,ASIZE(D->ArcName)); + + wcsncpy(D->FileNameW,hd->FileName,ASIZE(D->FileNameW)); + WideToChar(D->FileNameW,D->FileName,ASIZE(D->FileName)); +#ifdef _WIN_ALL + CharToOemA(D->FileName,D->FileName); +#endif + + D->Flags=0; + if (hd->SplitBefore) + D->Flags|=RHDF_SPLITBEFORE; + if (hd->SplitAfter) + D->Flags|=RHDF_SPLITAFTER; + if (hd->Encrypted) + D->Flags|=RHDF_ENCRYPTED; + if (hd->Solid) + D->Flags|=RHDF_SOLID; + if (hd->Dir) + D->Flags|=RHDF_DIRECTORY; + + D->PackSize=uint(hd->PackSize & 0xffffffff); + D->PackSizeHigh=uint(hd->PackSize>>32); + D->UnpSize=uint(hd->UnpSize & 0xffffffff); + D->UnpSizeHigh=uint(hd->UnpSize>>32); + D->HostOS=hd->HSType==HSYS_WINDOWS ? HOST_WIN32:HOST_UNIX; + if (Data->Arc.Format==RARFMT50) + D->UnpVer=Data->Arc.FileHead.UnpVer==0 ? 50 : 200; // If it is not 0, just set it to something big. + else + D->UnpVer=Data->Arc.FileHead.UnpVer; + D->FileCRC=hd->FileHash.CRC32; + D->FileTime=hd->mtime.GetDos(); + + uint64 MRaw=hd->mtime.GetWin(); + D->MtimeLow=(uint)MRaw; + D->MtimeHigh=(uint)(MRaw>>32); + uint64 CRaw=hd->ctime.GetWin(); + D->CtimeLow=(uint)CRaw; + D->CtimeHigh=(uint)(CRaw>>32); + uint64 ARaw=hd->atime.GetWin(); + D->AtimeLow=(uint)ARaw; + D->AtimeHigh=(uint)(ARaw>>32); + + D->Method=hd->Method+0x30; + D->FileAttr=hd->FileAttr; + D->CmtSize=0; + D->CmtState=0; + + D->DictSize=uint(hd->WinSize/1024); + + switch (hd->FileHash.Type) + { + case HASH_RAR14: + case HASH_CRC32: + D->HashType=RAR_HASH_CRC32; + break; + case HASH_BLAKE2: + D->HashType=RAR_HASH_BLAKE2; + memcpy(D->Hash,hd->FileHash.Digest,BLAKE2_DIGEST_SIZE); + break; + default: + D->HashType=RAR_HASH_NONE; + break; + } + + D->RedirType=hd->RedirType; + // RedirNameSize sanity check is useful in case some developer + // did not initialize Reserved area with 0 as required in docs. + // We have taken 'Redir*' fields from Reserved area. We may remove + // this RedirNameSize check sometimes later. + if (hd->RedirType!=FSREDIR_NONE && D->RedirName!=NULL && + D->RedirNameSize>0 && D->RedirNameSize<100000) + wcsncpyz(D->RedirName,hd->RedirName,D->RedirNameSize); + D->DirTarget=hd->DirTarget; + } + catch (RAR_EXIT ErrCode) + { + return Data->Cmd.DllError!=0 ? Data->Cmd.DllError : RarErrorToDll(ErrCode); + } + return ERAR_SUCCESS; +} + + +int PASCAL ProcessFile(HANDLE hArcData,int Operation,char *DestPath,char *DestName,wchar *DestPathW,wchar *DestNameW) +{ + DataSet *Data=(DataSet *)hArcData; + try + { + Data->Cmd.DllError=0; + if (Data->OpenMode==RAR_OM_LIST || Data->OpenMode==RAR_OM_LIST_INCSPLIT || + Operation==RAR_SKIP && !Data->Arc.Solid) + { + if (Data->Arc.Volume && Data->Arc.GetHeaderType()==HEAD_FILE && + Data->Arc.FileHead.SplitAfter) + if (MergeArchive(Data->Arc,NULL,false,'L')) + { + Data->Arc.Seek(Data->Arc.CurBlockPos,SEEK_SET); + return ERAR_SUCCESS; + } + else + return ERAR_EOPEN; + Data->Arc.SeekToNext(); + } + else + { + Data->Cmd.DllOpMode=Operation; + + *Data->Cmd.ExtrPath=0; + *Data->Cmd.DllDestName=0; + + if (DestPath!=NULL) + { + char ExtrPathA[NM]; + strncpyz(ExtrPathA,DestPath,ASIZE(ExtrPathA)-2); +#ifdef _WIN_ALL + // We must not apply OemToCharBuffA directly to DestPath, + // because we do not know DestPath length and OemToCharBuffA + // does not stop at 0. + OemToCharA(ExtrPathA,ExtrPathA); +#endif + CharToWide(ExtrPathA,Data->Cmd.ExtrPath,ASIZE(Data->Cmd.ExtrPath)); + AddEndSlash(Data->Cmd.ExtrPath,ASIZE(Data->Cmd.ExtrPath)); + } + if (DestName!=NULL) + { + char DestNameA[NM]; + strncpyz(DestNameA,DestName,ASIZE(DestNameA)-2); +#ifdef _WIN_ALL + // We must not apply OemToCharBuffA directly to DestName, + // because we do not know DestName length and OemToCharBuffA + // does not stop at 0. + OemToCharA(DestNameA,DestNameA); +#endif + CharToWide(DestNameA,Data->Cmd.DllDestName,ASIZE(Data->Cmd.DllDestName)); + } + + if (DestPathW!=NULL) + { + wcsncpy(Data->Cmd.ExtrPath,DestPathW,ASIZE(Data->Cmd.ExtrPath)); + AddEndSlash(Data->Cmd.ExtrPath,ASIZE(Data->Cmd.ExtrPath)); + } + + if (DestNameW!=NULL) + wcsncpyz(Data->Cmd.DllDestName,DestNameW,ASIZE(Data->Cmd.DllDestName)); + + wcscpy(Data->Cmd.Command,Operation==RAR_EXTRACT ? L"X":L"T"); + Data->Cmd.Test=Operation!=RAR_EXTRACT; + bool Repeat=false; + Data->Extract.ExtractCurrentFile(Data->Arc,Data->HeaderSize,Repeat); + + // Now we process extra file information if any. + // + // Archive can be closed if we process volumes, next volume is missing + // and current one is already removed or deleted. So we need to check + // if archive is still open to avoid calling file operations on + // the invalid file handle. Some of our file operations like Seek() + // process such invalid handle correctly, some not. + while (Data->Arc.IsOpened() && Data->Arc.ReadHeader()!=0 && + Data->Arc.GetHeaderType()==HEAD_SERVICE) + { + Data->Extract.ExtractCurrentFile(Data->Arc,Data->HeaderSize,Repeat); + Data->Arc.SeekToNext(); + } + Data->Arc.Seek(Data->Arc.CurBlockPos,SEEK_SET); + } + } + catch (std::bad_alloc&) + { + return ERAR_NO_MEMORY; + } + catch (RAR_EXIT ErrCode) + { + return Data->Cmd.DllError!=0 ? Data->Cmd.DllError : RarErrorToDll(ErrCode); + } + return Data->Cmd.DllError; +} + + +int PASCAL RARProcessFile(HANDLE hArcData,int Operation,char *DestPath,char *DestName) +{ + return(ProcessFile(hArcData,Operation,DestPath,DestName,NULL,NULL)); +} + + +int PASCAL RARProcessFileW(HANDLE hArcData,int Operation,wchar *DestPath,wchar *DestName) +{ + return(ProcessFile(hArcData,Operation,NULL,NULL,DestPath,DestName)); +} + + +void PASCAL RARSetChangeVolProc(HANDLE hArcData,CHANGEVOLPROC ChangeVolProc) +{ + DataSet *Data=(DataSet *)hArcData; + Data->Cmd.ChangeVolProc=ChangeVolProc; +} + + +void PASCAL RARSetCallback(HANDLE hArcData,UNRARCALLBACK Callback,LPARAM UserData) +{ + DataSet *Data=(DataSet *)hArcData; + Data->Cmd.Callback=Callback; + Data->Cmd.UserData=UserData; +} + + +void PASCAL RARSetProcessDataProc(HANDLE hArcData,PROCESSDATAPROC ProcessDataProc) +{ + DataSet *Data=(DataSet *)hArcData; + Data->Cmd.ProcessDataProc=ProcessDataProc; +} + + +#ifndef RAR_NOCRYPT +void PASCAL RARSetPassword(HANDLE hArcData,char *Password) +{ + DataSet *Data=(DataSet *)hArcData; + wchar PasswordW[MAXPASSWORD]; + GetWideName(Password,NULL,PasswordW,ASIZE(PasswordW)); + Data->Cmd.Password.Set(PasswordW); + cleandata(PasswordW,sizeof(PasswordW)); +} +#endif + + +int PASCAL RARGetDllVersion() +{ + return RAR_DLL_VERSION; +} + + +static int RarErrorToDll(RAR_EXIT ErrCode) +{ + switch(ErrCode) + { + case RARX_FATAL: + return ERAR_EREAD; + case RARX_CRC: + return ERAR_BAD_DATA; + case RARX_WRITE: + return ERAR_EWRITE; + case RARX_OPEN: + return ERAR_EOPEN; + case RARX_CREATE: + return ERAR_ECREATE; + case RARX_MEMORY: + return ERAR_NO_MEMORY; + case RARX_BADPWD: + return ERAR_BAD_PASSWORD; + case RARX_SUCCESS: + return ERAR_SUCCESS; // 0. + default: + return ERAR_UNKNOWN; + } +} diff --git a/reference/unrar/dll.def b/reference/unrar/dll.def new file mode 100644 index 00000000..54420e2a --- /dev/null +++ b/reference/unrar/dll.def @@ -0,0 +1,12 @@ +EXPORTS + RAROpenArchive + RAROpenArchiveEx + RARCloseArchive + RARReadHeader + RARReadHeaderEx + RARProcessFile + RARSetCallback + RARSetChangeVolProc + RARSetProcessDataProc + RARSetPassword + RARGetDllVersion diff --git a/reference/unrar/dll.hpp b/reference/unrar/dll.hpp new file mode 100644 index 00000000..7f829066 --- /dev/null +++ b/reference/unrar/dll.hpp @@ -0,0 +1,185 @@ +#ifndef _UNRAR_DLL_ +#define _UNRAR_DLL_ + +#pragma pack(1) + +#define ERAR_SUCCESS 0 +#define ERAR_END_ARCHIVE 10 +#define ERAR_NO_MEMORY 11 +#define ERAR_BAD_DATA 12 +#define ERAR_BAD_ARCHIVE 13 +#define ERAR_UNKNOWN_FORMAT 14 +#define ERAR_EOPEN 15 +#define ERAR_ECREATE 16 +#define ERAR_ECLOSE 17 +#define ERAR_EREAD 18 +#define ERAR_EWRITE 19 +#define ERAR_SMALL_BUF 20 +#define ERAR_UNKNOWN 21 +#define ERAR_MISSING_PASSWORD 22 +#define ERAR_EREFERENCE 23 +#define ERAR_BAD_PASSWORD 24 + +#define RAR_OM_LIST 0 +#define RAR_OM_EXTRACT 1 +#define RAR_OM_LIST_INCSPLIT 2 + +#define RAR_SKIP 0 +#define RAR_TEST 1 +#define RAR_EXTRACT 2 + +#define RAR_VOL_ASK 0 +#define RAR_VOL_NOTIFY 1 + +#define RAR_DLL_VERSION 8 + +#define RAR_HASH_NONE 0 +#define RAR_HASH_CRC32 1 +#define RAR_HASH_BLAKE2 2 + + +#ifdef _UNIX +#define CALLBACK +#define PASCAL +#define LONG long +#define HANDLE void * +#define LPARAM long +#define UINT unsigned int +#endif + +#define RHDF_SPLITBEFORE 0x01 +#define RHDF_SPLITAFTER 0x02 +#define RHDF_ENCRYPTED 0x04 +#define RHDF_SOLID 0x10 +#define RHDF_DIRECTORY 0x20 + + +struct RARHeaderData +{ + char ArcName[260]; + char FileName[260]; + unsigned int Flags; + unsigned int PackSize; + unsigned int UnpSize; + unsigned int HostOS; + unsigned int FileCRC; + unsigned int FileTime; + unsigned int UnpVer; + unsigned int Method; + unsigned int FileAttr; + char *CmtBuf; + unsigned int CmtBufSize; + unsigned int CmtSize; + unsigned int CmtState; +}; + + +struct RARHeaderDataEx +{ + char ArcName[1024]; + wchar_t ArcNameW[1024]; + char FileName[1024]; + wchar_t FileNameW[1024]; + unsigned int Flags; + unsigned int PackSize; + unsigned int PackSizeHigh; + unsigned int UnpSize; + unsigned int UnpSizeHigh; + unsigned int HostOS; + unsigned int FileCRC; + unsigned int FileTime; + unsigned int UnpVer; + unsigned int Method; + unsigned int FileAttr; + char *CmtBuf; + unsigned int CmtBufSize; + unsigned int CmtSize; + unsigned int CmtState; + unsigned int DictSize; + unsigned int HashType; + char Hash[32]; + unsigned int RedirType; + wchar_t *RedirName; + unsigned int RedirNameSize; + unsigned int DirTarget; + unsigned int MtimeLow; + unsigned int MtimeHigh; + unsigned int CtimeLow; + unsigned int CtimeHigh; + unsigned int AtimeLow; + unsigned int AtimeHigh; + unsigned int Reserved[988]; +}; + + +struct RAROpenArchiveData +{ + char *ArcName; + unsigned int OpenMode; + unsigned int OpenResult; + char *CmtBuf; + unsigned int CmtBufSize; + unsigned int CmtSize; + unsigned int CmtState; +}; + +typedef int (CALLBACK *UNRARCALLBACK)(UINT msg,LPARAM UserData,LPARAM P1,LPARAM P2); + +#define ROADF_VOLUME 0x0001 +#define ROADF_COMMENT 0x0002 +#define ROADF_LOCK 0x0004 +#define ROADF_SOLID 0x0008 +#define ROADF_NEWNUMBERING 0x0010 +#define ROADF_SIGNED 0x0020 +#define ROADF_RECOVERY 0x0040 +#define ROADF_ENCHEADERS 0x0080 +#define ROADF_FIRSTVOLUME 0x0100 + +struct RAROpenArchiveDataEx +{ + char *ArcName; + wchar_t *ArcNameW; + unsigned int OpenMode; + unsigned int OpenResult; + char *CmtBuf; + unsigned int CmtBufSize; + unsigned int CmtSize; + unsigned int CmtState; + unsigned int Flags; + UNRARCALLBACK Callback; + LPARAM UserData; + unsigned int Reserved[28]; +}; + +enum UNRARCALLBACK_MESSAGES { + UCM_CHANGEVOLUME,UCM_PROCESSDATA,UCM_NEEDPASSWORD,UCM_CHANGEVOLUMEW, + UCM_NEEDPASSWORDW +}; + +typedef int (PASCAL *CHANGEVOLPROC)(char *ArcName,int Mode); +typedef int (PASCAL *PROCESSDATAPROC)(unsigned char *Addr,int Size); + +#ifdef __cplusplus +extern "C" { +#endif + +HANDLE PASCAL RAROpenArchive(struct RAROpenArchiveData *ArchiveData); +HANDLE PASCAL RAROpenArchiveEx(struct RAROpenArchiveDataEx *ArchiveData); +int PASCAL RARCloseArchive(HANDLE hArcData); +int PASCAL RARReadHeader(HANDLE hArcData,struct RARHeaderData *HeaderData); +int PASCAL RARReadHeaderEx(HANDLE hArcData,struct RARHeaderDataEx *HeaderData); +int PASCAL RARProcessFile(HANDLE hArcData,int Operation,char *DestPath,char *DestName); +int PASCAL RARProcessFileW(HANDLE hArcData,int Operation,wchar_t *DestPath,wchar_t *DestName); +void PASCAL RARSetCallback(HANDLE hArcData,UNRARCALLBACK Callback,LPARAM UserData); +void PASCAL RARSetChangeVolProc(HANDLE hArcData,CHANGEVOLPROC ChangeVolProc); +void PASCAL RARSetProcessDataProc(HANDLE hArcData,PROCESSDATAPROC ProcessDataProc); +void PASCAL RARSetPassword(HANDLE hArcData,char *Password); +int PASCAL RARGetDllVersion(); + +#ifdef __cplusplus +} +#endif + +#pragma pack() + +#endif diff --git a/reference/unrar/dll.rc b/reference/unrar/dll.rc new file mode 100644 index 00000000..7aa83145 --- /dev/null +++ b/reference/unrar/dll.rc @@ -0,0 +1,28 @@ +#include +#include + +VS_VERSION_INFO VERSIONINFO +FILEVERSION 5, 50, 100, 2418 +PRODUCTVERSION 5, 50, 100, 2418 +FILEOS VOS__WINDOWS32 +FILETYPE VFT_APP +{ + BLOCK "StringFileInfo" + { + BLOCK "040904E4" + { + VALUE "CompanyName", "Alexander Roshal\0" + VALUE "ProductName", "RAR decompression library\0" + VALUE "FileDescription", "RAR decompression library\0" + VALUE "FileVersion", "5.50.0\0" + VALUE "ProductVersion", "5.50.0\0" + VALUE "LegalCopyright", "Copyright © Alexander Roshal 1993-2017\0" + VALUE "OriginalFilename", "Unrar.dll\0" + } + } + BLOCK "VarFileInfo" + { + VALUE "Translation", 0x0409, 0x04E4 + } +} + diff --git a/reference/unrar/encname.cpp b/reference/unrar/encname.cpp new file mode 100644 index 00000000..5556af3e --- /dev/null +++ b/reference/unrar/encname.cpp @@ -0,0 +1,69 @@ +#include "rar.hpp" + +EncodeFileName::EncodeFileName() +{ + Flags=0; + FlagBits=0; + FlagsPos=0; + DestSize=0; +} + + + + +void EncodeFileName::Decode(char *Name,byte *EncName,size_t EncSize,wchar *NameW, + size_t MaxDecSize) +{ + size_t EncPos=0,DecPos=0; + byte HighByte=EncPos=EncSize) + break; + Flags=EncName[EncPos++]; + FlagBits=8; + } + switch(Flags>>6) + { + case 0: + if (EncPos>=EncSize) + break; + NameW[DecPos++]=EncName[EncPos++]; + break; + case 1: + if (EncPos>=EncSize) + break; + NameW[DecPos++]=EncName[EncPos++]+(HighByte<<8); + break; + case 2: + if (EncPos+1>=EncSize) + break; + NameW[DecPos++]=EncName[EncPos]+(EncName[EncPos+1]<<8); + EncPos+=2; + break; + case 3: + { + if (EncPos>=EncSize) + break; + int Length=EncName[EncPos++]; + if ((Length & 0x80)!=0) + { + if (EncPos>=EncSize) + break; + byte Correction=EncName[EncPos++]; + for (Length=(Length&0x7f)+2;Length>0 && DecPos0 && DecPos1) + exit(RARX_USERBREAK); + // Otherwise return from signal handler and let Wait() function to close + // files and quit. We cannot use the same approach as in Windows, + // because Unix signal handler can block execution of our main code. +#endif + +#if defined(_WIN_ALL) && !defined(_MSC_VER) + // never reached, just to avoid a compiler warning + return TRUE; +#endif +} + + +void ErrorHandler::SetSignalHandlers(bool Enable) +{ + EnableBreak=Enable; +#ifdef _WIN_ALL + SetConsoleCtrlHandler(Enable ? ProcessSignal:NULL,TRUE); +#else + signal(SIGINT,Enable ? ProcessSignal:SIG_IGN); + signal(SIGTERM,Enable ? ProcessSignal:SIG_IGN); +#endif +} + + +void ErrorHandler::Throw(RAR_EXIT Code) +{ + if (Code==RARX_USERBREAK && !EnableBreak) + return; +#if !defined(SILENT) + // Do not write "aborted" when just displaying online help. + if (Code!=RARX_SUCCESS && Code!=RARX_USERERROR) + mprintf(L"\n%s\n",St(MProgAborted)); +#endif + SetErrorCode(Code); + throw Code; +} + + +void ErrorHandler::SysErrMsg() +{ +#if !defined(SFX_MODULE) && !defined(SILENT) +#ifdef _WIN_ALL + wchar *lpMsgBuf=NULL; + int ErrType=GetLastError(); + if (ErrType!=0 && FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM, + NULL,ErrType,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&lpMsgBuf,0,NULL)) + { + wchar *CurMsg=lpMsgBuf; + while (CurMsg!=NULL) + { + while (*CurMsg=='\r' || *CurMsg=='\n') + CurMsg++; + if (*CurMsg==0) + break; + wchar *EndMsg=wcschr(CurMsg,'\r'); + if (EndMsg==NULL) + EndMsg=wcschr(CurMsg,'\n'); + if (EndMsg!=NULL) + { + *EndMsg=0; + EndMsg++; + } + uiMsg(UIERROR_SYSERRMSG,CurMsg); + CurMsg=EndMsg; + } + } + LocalFree( lpMsgBuf ); +#endif + +#if defined(_UNIX) || defined(_EMX) + if (errno!=0) + { + char *err=strerror(errno); + if (err!=NULL) + { + wchar Msg[1024]; + CharToWide(err,Msg,ASIZE(Msg)); + uiMsg(UIERROR_SYSERRMSG,Msg); + } + } +#endif + +#endif +} + + +int ErrorHandler::GetSystemErrorCode() +{ +#ifdef _WIN_ALL + return GetLastError(); +#else + return errno; +#endif +} + + +void ErrorHandler::SetSystemErrorCode(int Code) +{ +#ifdef _WIN_ALL + SetLastError(Code); +#else + errno=Code; +#endif +} diff --git a/reference/unrar/errhnd.hpp b/reference/unrar/errhnd.hpp new file mode 100644 index 00000000..26df96d5 --- /dev/null +++ b/reference/unrar/errhnd.hpp @@ -0,0 +1,70 @@ +#ifndef _RAR_ERRHANDLER_ +#define _RAR_ERRHANDLER_ + +enum RAR_EXIT // RAR exit code. +{ + RARX_SUCCESS = 0, + RARX_WARNING = 1, + RARX_FATAL = 2, + RARX_CRC = 3, + RARX_LOCK = 4, + RARX_WRITE = 5, + RARX_OPEN = 6, + RARX_USERERROR = 7, + RARX_MEMORY = 8, + RARX_CREATE = 9, + RARX_NOFILES = 10, + RARX_BADPWD = 11, + RARX_USERBREAK = 255 +}; + +class ErrorHandler +{ + private: + RAR_EXIT ExitCode; + uint ErrCount; + bool EnableBreak; + bool Silent; + bool DisableShutdown; // Shutdown is not suitable after last error. + public: + ErrorHandler(); + void Clean(); + void MemoryError(); + void OpenError(const wchar *FileName); + void CloseError(const wchar *FileName); + void ReadError(const wchar *FileName); + bool AskRepeatRead(const wchar *FileName); + void WriteError(const wchar *ArcName,const wchar *FileName); + void WriteErrorFAT(const wchar *FileName); + bool AskRepeatWrite(const wchar *FileName,bool DiskFull); + void SeekError(const wchar *FileName); + void GeneralErrMsg(const wchar *fmt,...); + void MemoryErrorMsg(); + void OpenErrorMsg(const wchar *FileName); + void OpenErrorMsg(const wchar *ArcName,const wchar *FileName); + void CreateErrorMsg(const wchar *FileName); + void CreateErrorMsg(const wchar *ArcName,const wchar *FileName); + void ReadErrorMsg(const wchar *FileName); + void ReadErrorMsg(const wchar *ArcName,const wchar *FileName); + void WriteErrorMsg(const wchar *ArcName,const wchar *FileName); + void ArcBrokenMsg(const wchar *ArcName); + void ChecksumFailedMsg(const wchar *ArcName,const wchar *FileName); + void UnknownMethodMsg(const wchar *ArcName,const wchar *FileName); + void Exit(RAR_EXIT ExitCode); + void SetErrorCode(RAR_EXIT Code); + RAR_EXIT GetErrorCode() {return ExitCode;} + uint GetErrorCount() {return ErrCount;} + void SetSignalHandlers(bool Enable); + void Throw(RAR_EXIT Code); + void SetSilent(bool Mode) {Silent=Mode;}; + void SysErrMsg(); + int GetSystemErrorCode(); + void SetSystemErrorCode(int Code); + bool IsShutdownEnabled() {return !DisableShutdown;} + + bool UserBreak; // Ctrl+Break is pressed. + bool MainExit; // main() is completed. +}; + + +#endif diff --git a/reference/unrar/extinfo.cpp b/reference/unrar/extinfo.cpp new file mode 100644 index 00000000..5cb90a40 --- /dev/null +++ b/reference/unrar/extinfo.cpp @@ -0,0 +1,178 @@ +#include "rar.hpp" + +#include "hardlinks.cpp" +#include "win32stm.cpp" + +#ifdef _WIN_ALL +#include "win32acl.cpp" +#include "win32lnk.cpp" +#endif + +#ifdef _UNIX +#include "uowners.cpp" +#ifdef SAVE_LINKS +#include "ulinks.cpp" +#endif +#endif + + + +// RAR2 service header extra records. +#ifndef SFX_MODULE +void SetExtraInfo20(CommandData *Cmd,Archive &Arc,wchar *Name) +{ + if (Cmd->Test) + return; + switch(Arc.SubBlockHead.SubType) + { +#ifdef _UNIX + case UO_HEAD: + if (Cmd->ProcessOwners) + ExtractUnixOwner20(Arc,Name); + break; +#endif +#ifdef _WIN_ALL + case NTACL_HEAD: + if (Cmd->ProcessOwners) + ExtractACL20(Arc,Name); + break; + case STREAM_HEAD: + ExtractStreams20(Arc,Name); + break; +#endif + } +} +#endif + + +// RAR3 and RAR5 service header extra records. +void SetExtraInfo(CommandData *Cmd,Archive &Arc,wchar *Name) +{ +#ifdef _UNIX + if (!Cmd->Test && Cmd->ProcessOwners && Arc.Format==RARFMT15 && + Arc.SubHead.CmpName(SUBHEAD_TYPE_UOWNER)) + ExtractUnixOwner30(Arc,Name); +#endif +#ifdef _WIN_ALL + if (!Cmd->Test && Cmd->ProcessOwners && Arc.SubHead.CmpName(SUBHEAD_TYPE_ACL)) + ExtractACL(Arc,Name); + if (Arc.SubHead.CmpName(SUBHEAD_TYPE_STREAM)) + ExtractStreams(Arc,Name,Cmd->Test); +#endif +} + + +// Extra data stored directly in file header. +void SetFileHeaderExtra(CommandData *Cmd,Archive &Arc,wchar *Name) +{ +#ifdef _UNIX + if (Cmd->ProcessOwners && Arc.Format==RARFMT50 && Arc.FileHead.UnixOwnerSet) + SetUnixOwner(Arc,Name); +#endif +} + + + + +// Calculate a number of path components except \. and \.. +static int CalcAllowedDepth(const wchar *Name) +{ + int AllowedDepth=0; + while (*Name!=0) + { + if (IsPathDiv(Name[0]) && Name[1]!=0 && !IsPathDiv(Name[1])) + { + bool Dot=Name[1]=='.' && (IsPathDiv(Name[2]) || Name[2]==0); + bool Dot2=Name[1]=='.' && Name[2]=='.' && (IsPathDiv(Name[3]) || Name[3]==0); + if (!Dot && !Dot2) + AllowedDepth++; + } + Name++; + } + return AllowedDepth; +} + + +// Check if all existing path components are directories and not links. +static bool LinkInPath(const wchar *Name) +{ + wchar Path[NM]; + if (wcslen(Name)>=ASIZE(Path)) + return true; // It should not be that long, skip. + wcsncpyz(Path,Name,ASIZE(Path)); + for (wchar *s=Path+wcslen(Path)-1;s>Path;s--) + if (IsPathDiv(*s)) + { + *s=0; + FindData FD; + if (FindFile::FastFind(Path,&FD,true) && (FD.IsLink || !FD.IsDir)) + return true; + } + return false; +} + + +bool IsRelativeSymlinkSafe(CommandData *Cmd,const wchar *SrcName,const wchar *PrepSrcName,const wchar *TargetName) +{ + // Catch root dir based /path/file paths also as stuff like \\?\. + // Do not check PrepSrcName here, it can be root based if destination path + // is a root based. + if (IsFullRootPath(SrcName) || IsFullRootPath(TargetName)) + return false; + + // Number of ".." in link target. + int UpLevels=0; + for (int Pos=0;*TargetName!=0;Pos++) + { + bool Dot2=TargetName[0]=='.' && TargetName[1]=='.' && + (IsPathDiv(TargetName[2]) || TargetName[2]==0) && + (Pos==0 || IsPathDiv(*(TargetName-1))); + if (Dot2) + UpLevels++; + TargetName++; + } + // If link target includes "..", it must not have another links + // in the path, because they can bypass our safety check. For example, + // suppose we extracted "lnk1" -> "." first and "lnk1/lnk2" -> ".." next + // or "dir/lnk1" -> ".." first and "dir/lnk1/lnk2" -> ".." next. + if (UpLevels>0 && LinkInPath(PrepSrcName)) + return false; + + // We could check just prepared src name, but for extra safety + // we check both original (as from archive header) and prepared + // (after applying the destination path and -ep switches) names. + + int AllowedDepth=CalcAllowedDepth(SrcName); // Original name depth. + + // Remove the destination path from prepared name if any. We should not + // count the destination path depth, because the link target must point + // inside of this path, not outside of it. + size_t ExtrPathLength=wcslen(Cmd->ExtrPath); + if (ExtrPathLength>0 && wcsncmp(PrepSrcName,Cmd->ExtrPath,ExtrPathLength)==0) + { + PrepSrcName+=ExtrPathLength; + while (IsPathDiv(*PrepSrcName)) + PrepSrcName++; + } + int PrepAllowedDepth=CalcAllowedDepth(PrepSrcName); + + return AllowedDepth>=UpLevels && PrepAllowedDepth>=UpLevels; +} + + +bool ExtractSymlink(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const wchar *LinkName) +{ +#if defined(SAVE_LINKS) && defined(_UNIX) + // For RAR 3.x archives we process links even in test mode to skip link data. + if (Arc.Format==RARFMT15) + return ExtractUnixLink30(Cmd,DataIO,Arc,LinkName); + if (Arc.Format==RARFMT50) + return ExtractUnixLink50(Cmd,LinkName,&Arc.FileHead); +#elif defined _WIN_ALL + // RAR 5.0 archives store link information in file header, so there is + // no need to additionally test it if we do not create a file. + if (Arc.Format==RARFMT50) + return CreateReparsePoint(Cmd,LinkName,&Arc.FileHead); +#endif + return false; +} diff --git a/reference/unrar/extinfo.hpp b/reference/unrar/extinfo.hpp new file mode 100644 index 00000000..2b0005da --- /dev/null +++ b/reference/unrar/extinfo.hpp @@ -0,0 +1,23 @@ +#ifndef _RAR_EXTINFO_ +#define _RAR_EXTINFO_ + +bool IsRelativeSymlinkSafe(CommandData *Cmd,const wchar *SrcName,const wchar *PrepSrcName,const wchar *TargetName); +bool ExtractSymlink(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const wchar *LinkName); +#ifdef _UNIX +void SetUnixOwner(Archive &Arc,const wchar *FileName); +#endif + +bool ExtractHardlink(wchar *NameNew,wchar *NameExisting,size_t NameExistingSize); + +void GetStreamNameNTFS(Archive &Arc,wchar *StreamName,size_t MaxSize); + +#ifdef _WIN_ALL +bool SetPrivilege(LPCTSTR PrivName); +#endif + +void SetExtraInfo20(CommandData *Cmd,Archive &Arc,wchar *Name); +void SetExtraInfo(CommandData *Cmd,Archive &Arc,wchar *Name); +void SetFileHeaderExtra(CommandData *Cmd,Archive &Arc,wchar *Name); + + +#endif diff --git a/reference/unrar/extract.cpp b/reference/unrar/extract.cpp new file mode 100644 index 00000000..f9461136 --- /dev/null +++ b/reference/unrar/extract.cpp @@ -0,0 +1,1167 @@ +#include "rar.hpp" + +CmdExtract::CmdExtract(CommandData *Cmd) +{ + CmdExtract::Cmd=Cmd; + + *ArcName=0; + + *DestFileName=0; + + TotalFileCount=0; + Unp=new Unpack(&DataIO); +#ifdef RAR_SMP + Unp->SetThreads(Cmd->Threads); +#endif +} + + +CmdExtract::~CmdExtract() +{ + delete Unp; +} + + +void CmdExtract::DoExtract() +{ +#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT) + Fat32=NotFat32=false; +#endif + PasswordCancelled=false; + DataIO.SetCurrentCommand(Cmd->Command[0]); + + FindData FD; + while (Cmd->GetArcName(ArcName,ASIZE(ArcName))) + if (FindFile::FastFind(ArcName,&FD)) + DataIO.TotalArcSize+=FD.Size; + + Cmd->ArcNames.Rewind(); + while (Cmd->GetArcName(ArcName,ASIZE(ArcName))) + { + if (Cmd->ManualPassword) + Cmd->Password.Clean(); // Clean user entered password before processing next archive. + while (true) + { + EXTRACT_ARC_CODE Code=ExtractArchive(); + if (Code!=EXTRACT_ARC_REPEAT) + break; + } + if (FindFile::FastFind(ArcName,&FD)) + DataIO.ProcessedArcSize+=FD.Size; + } + + // Clean user entered password. Not really required, just for extra safety. + if (Cmd->ManualPassword) + Cmd->Password.Clean(); + + if (TotalFileCount==0 && Cmd->Command[0]!='I' && + ErrHandler.GetErrorCode()!=RARX_BADPWD) // Not in case of wrong archive password. + { + if (!PasswordCancelled) + uiMsg(UIERROR_NOFILESTOEXTRACT,ArcName); + ErrHandler.SetErrorCode(RARX_NOFILES); + } + else + if (!Cmd->DisableDone) + if (Cmd->Command[0]=='I') + mprintf(St(MDone)); + else + if (ErrHandler.GetErrorCount()==0) + mprintf(St(MExtrAllOk)); + else + mprintf(St(MExtrTotalErr),ErrHandler.GetErrorCount()); +} + + +void CmdExtract::ExtractArchiveInit(Archive &Arc) +{ + DataIO.UnpArcSize=Arc.FileLength(); + + FileCount=0; + MatchedArgs=0; +#ifndef SFX_MODULE + FirstFile=true; +#endif + + PasswordAll=(Cmd->Password.IsSet()); + + DataIO.UnpVolume=false; + + PrevProcessed=false; + AllMatchesExact=true; + ReconstructDone=false; + AnySolidDataUnpackedWell=false; + + StartTime.SetCurrentTime(); +} + + +EXTRACT_ARC_CODE CmdExtract::ExtractArchive() +{ + Archive Arc(Cmd); + if (!Arc.WOpen(ArcName)) + return EXTRACT_ARC_NEXT; + + if (!Arc.IsArchive(true)) + { +#if !defined(SFX_MODULE) && !defined(RARDLL) + if (CmpExt(ArcName,L"rev")) + { + wchar FirstVolName[NM]; + VolNameToFirstName(ArcName,FirstVolName,ASIZE(FirstVolName),true); + + // If several volume names from same volume set are specified + // and current volume is not first in set and first volume is present + // and specified too, let's skip the current volume. + if (wcsicomp(ArcName,FirstVolName)!=0 && FileExist(FirstVolName) && + Cmd->ArcNames.Search(FirstVolName,false)) + return EXTRACT_ARC_NEXT; + RecVolumesTest(Cmd,NULL,ArcName); + TotalFileCount++; // Suppress "No files to extract" message. + return EXTRACT_ARC_NEXT; + } +#endif + + mprintf(St(MNotRAR),ArcName); + +#ifndef SFX_MODULE + if (CmpExt(ArcName,L"rar")) +#endif + ErrHandler.SetErrorCode(RARX_WARNING); + return EXTRACT_ARC_NEXT; + } + + if (Arc.FailedHeaderDecryption) // Bad archive password. + return EXTRACT_ARC_NEXT; + +#ifndef SFX_MODULE + if (Arc.Volume && !Arc.FirstVolume) + { + wchar FirstVolName[NM]; + VolNameToFirstName(ArcName,FirstVolName,ASIZE(FirstVolName),Arc.NewNumbering); + + // If several volume names from same volume set are specified + // and current volume is not first in set and first volume is present + // and specified too, let's skip the current volume. + if (wcsicomp(ArcName,FirstVolName)!=0 && FileExist(FirstVolName) && + Cmd->ArcNames.Search(FirstVolName,false)) + return EXTRACT_ARC_NEXT; + } +#endif + + int64 VolumeSetSize=0; // Total size of volumes after the current volume. + + if (Arc.Volume) + { + // Calculate the total size of all accessible volumes. + // This size is necessary to display the correct total progress indicator. + + wchar NextName[NM]; + wcscpy(NextName,Arc.FileName); + + while (true) + { + // First volume is already added to DataIO.TotalArcSize + // in initial TotalArcSize calculation in DoExtract. + // So we skip it and start from second volume. + NextVolumeName(NextName,ASIZE(NextName),!Arc.NewNumbering); + FindData FD; + if (FindFile::FastFind(NextName,&FD)) + VolumeSetSize+=FD.Size; + else + break; + } + DataIO.TotalArcSize+=VolumeSetSize; + } + + ExtractArchiveInit(Arc); + + if (*Cmd->Command=='T' || *Cmd->Command=='I') + Cmd->Test=true; + + + if (*Cmd->Command=='I') + { + Cmd->DisablePercentage=true; + } + else + uiStartArchiveExtract(!Cmd->Test,ArcName); + + Arc.ViewComment(); + + + while (1) + { + size_t Size=Arc.ReadHeader(); + + + bool Repeat=false; + if (!ExtractCurrentFile(Arc,Size,Repeat)) + if (Repeat) + { + // If we started extraction from not first volume and need to + // restart it from first, we must correct DataIO.TotalArcSize + // for correct total progress display. We subtract the size + // of current volume and all volumes after it and add the size + // of new (first) volume. + FindData OldArc,NewArc; + if (FindFile::FastFind(Arc.FileName,&OldArc) && + FindFile::FastFind(ArcName,&NewArc)) + DataIO.TotalArcSize-=VolumeSetSize+OldArc.Size-NewArc.Size; + return EXTRACT_ARC_REPEAT; + } + else + break; + } + + +#if !defined(SFX_MODULE) && !defined(RARDLL) + if (Cmd->Test && Arc.Volume) + RecVolumesTest(Cmd,&Arc,ArcName); +#endif + + return EXTRACT_ARC_NEXT; +} + + +bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) +{ + // We can get negative sizes in corrupt archive and it is unacceptable + // for size comparisons in CmdExtract::UnstoreFile and ComprDataIO::UnpRead, + // where we cast sizes to size_t and can exceed another read or available + // size. We could fix it when reading an archive. But we prefer to do it + // here, because this function is called directly in unrar.dll, so we fix + // bad parameters passed to dll. Also we want to see real negative sizes + // in the listing of corrupt archive. + if (Arc.FileHead.PackSize<0) + Arc.FileHead.PackSize=0; + if (Arc.FileHead.UnpSize<0) + Arc.FileHead.UnpSize=0; + + wchar Command=Cmd->Command[0]; + if (HeaderSize==0) + if (DataIO.UnpVolume) + { +#ifdef NOVOLUME + return false; +#else + // Supposing we unpack an old RAR volume without end of archive record + // and last file is not split between volumes. + if (!MergeArchive(Arc,&DataIO,false,Command)) + { + ErrHandler.SetErrorCode(RARX_WARNING); + return false; + } +#endif + } + else + return false; + HEADER_TYPE HeaderType=Arc.GetHeaderType(); + if (HeaderType!=HEAD_FILE) + { +#ifndef SFX_MODULE + if (HeaderType==HEAD3_OLDSERVICE && PrevProcessed) + SetExtraInfo20(Cmd,Arc,DestFileName); +#endif + if (HeaderType==HEAD_SERVICE && PrevProcessed) + SetExtraInfo(Cmd,Arc,DestFileName); + if (HeaderType==HEAD_ENDARC) + if (Arc.EndArcHead.NextVolume) + { +#ifndef NOVOLUME + if (!MergeArchive(Arc,&DataIO,false,Command)) + { + ErrHandler.SetErrorCode(RARX_WARNING); + return false; + } +#endif + Arc.Seek(Arc.CurBlockPos,SEEK_SET); + return true; + } + else + return false; + Arc.SeekToNext(); + return true; + } + PrevProcessed=false; + + if (!Cmd->Recurse && MatchedArgs>=Cmd->FileArgs.ItemsCount() && AllMatchesExact) + return false; + + int MatchType=MATCH_WILDSUBPATH; + + bool EqualNames=false; + wchar MatchedArg[NM]; + int MatchNumber=Cmd->IsProcessFile(Arc.FileHead,&EqualNames,MatchType,MatchedArg,ASIZE(MatchedArg)); + bool MatchFound=MatchNumber!=0; +#ifndef SFX_MODULE + if (Cmd->ExclPath==EXCL_BASEPATH) + { + wcsncpyz(Cmd->ArcPath,MatchedArg,ASIZE(Cmd->ArcPath)); + *PointToName(Cmd->ArcPath)=0; + if (IsWildcard(Cmd->ArcPath)) // Cannot correctly process path*\* masks here. + *Cmd->ArcPath=0; + } +#endif + if (MatchFound && !EqualNames) + AllMatchesExact=false; + + Arc.ConvertAttributes(); + +#if !defined(SFX_MODULE) && !defined(RARDLL) + if (Arc.FileHead.SplitBefore && FirstFile) + { + wchar CurVolName[NM]; + wcsncpyz(CurVolName,ArcName,ASIZE(CurVolName)); + VolNameToFirstName(ArcName,ArcName,ASIZE(ArcName),Arc.NewNumbering); + + if (wcsicomp(ArcName,CurVolName)!=0 && FileExist(ArcName)) + { + // If first volume name does not match the current name and if such + // volume name really exists, let's unpack from this first volume. + Repeat=true; + return false; + } +#ifndef RARDLL + if (!ReconstructDone) + { + ReconstructDone=true; + if (RecVolumesRestore(Cmd,Arc.FileName,true)) + { + Repeat=true; + return false; + } + } +#endif + wcsncpyz(ArcName,CurVolName,ASIZE(ArcName)); + } +#endif + + wchar ArcFileName[NM]; + ConvertPath(Arc.FileHead.FileName,ArcFileName); + + if (Arc.FileHead.Version) + { + if (Cmd->VersionControl!=1 && !EqualNames) + { + if (Cmd->VersionControl==0) + MatchFound=false; + int Version=ParseVersionFileName(ArcFileName,false); + if (Cmd->VersionControl-1==Version) + ParseVersionFileName(ArcFileName,true); + else + MatchFound=false; + } + } + else + if (!Arc.IsArcDir() && Cmd->VersionControl>1) + MatchFound=false; + + DataIO.UnpVolume=Arc.FileHead.SplitAfter; + DataIO.NextVolumeMissing=false; + + Arc.Seek(Arc.NextBlockPos-Arc.FileHead.PackSize,SEEK_SET); + + bool ExtrFile=false; + bool SkipSolid=false; + +#ifndef SFX_MODULE + if (FirstFile && (MatchFound || Arc.Solid) && Arc.FileHead.SplitBefore) + { + if (MatchFound) + { + uiMsg(UIERROR_NEEDPREVVOL,Arc.FileName,ArcFileName); +#ifdef RARDLL + Cmd->DllError=ERAR_BAD_DATA; +#endif + ErrHandler.SetErrorCode(RARX_OPEN); + } + MatchFound=false; + } + + FirstFile=false; +#endif + + if (MatchFound || (SkipSolid=Arc.Solid)!=0) + { + // First common call of uiStartFileExtract. It is done before overwrite + // prompts, so if SkipSolid state is changed below, we'll need to make + // additional uiStartFileExtract calls with updated parameters. + if (!uiStartFileExtract(ArcFileName,!Cmd->Test,Cmd->Test && Command!='I',SkipSolid)) + return false; + + ExtrPrepareName(Arc,ArcFileName,DestFileName,ASIZE(DestFileName)); + + // DestFileName can be set empty in case of excessive -ap switch. + ExtrFile=!SkipSolid && *DestFileName!=0 && !Arc.FileHead.SplitBefore; + + if ((Cmd->FreshFiles || Cmd->UpdateFiles) && (Command=='E' || Command=='X')) + { + FindData FD; + if (FindFile::FastFind(DestFileName,&FD)) + { + if (FD.mtime >= Arc.FileHead.mtime) + { + // If directory already exists and its modification time is newer + // than start of extraction, it is likely it was created + // when creating a path to one of already extracted items. + // In such case we'll better update its time even if archived + // directory is older. + + if (!FD.IsDir || FD.mtimeFreshFiles) + ExtrFile=false; + } + + if (!CheckUnpVer(Arc,ArcFileName)) + { + ErrHandler.SetErrorCode(RARX_FATAL); +#ifdef RARDLL + Cmd->DllError=ERAR_UNKNOWN_FORMAT; +#endif + Arc.SeekToNext(); + return !Arc.Solid; // Can try extracting next file only in non-solid archive. + } + + while (true) // Repeat password prompt in case of wrong password here. + { + if (Arc.FileHead.Encrypted) + { + // Stop archive extracting if user cancelled a password prompt. +#ifdef RARDLL + if (!ExtrDllGetPassword()) + { + Cmd->DllError=ERAR_MISSING_PASSWORD; + return false; + } +#else + if (!ExtrGetPassword(Arc,ArcFileName)) + { + PasswordCancelled=true; + return false; + } +#endif + // Skip only the current encrypted file if empty password is entered. + // Actually our "cancel" code above intercepts empty passwords too now, + // so we keep the code below just in case we'll decide process empty + // and cancelled passwords differently sometimes. + if (!Cmd->Password.IsSet()) + { + ErrHandler.SetErrorCode(RARX_WARNING); +#ifdef RARDLL + Cmd->DllError=ERAR_MISSING_PASSWORD; +#endif + ExtrFile=false; + } + } + + + // Set a password before creating the file, so we can skip creating + // in case of wrong password. + SecPassword FilePassword=Cmd->Password; +#if defined(_WIN_ALL) && !defined(SFX_MODULE) + ConvertDosPassword(Arc,FilePassword); +#endif + + byte PswCheck[SIZE_PSWCHECK]; + DataIO.SetEncryption(false,Arc.FileHead.CryptMethod,&FilePassword, + Arc.FileHead.SaltSet ? Arc.FileHead.Salt:NULL, + Arc.FileHead.InitV,Arc.FileHead.Lg2Count, + Arc.FileHead.HashKey,PswCheck); + + // If header is damaged, we cannot rely on password check value, + // because it can be damaged too. + if (Arc.FileHead.Encrypted && Arc.FileHead.UsePswCheck && + memcmp(Arc.FileHead.PswCheck,PswCheck,SIZE_PSWCHECK)!=0 && + !Arc.BrokenHeader) + { + uiMsg(UIWAIT_BADPSW,ArcFileName); + + if (!PasswordAll) // If entered manually and not through -p. + { + Cmd->Password.Clean(); + continue; // Request a password again. + } +#ifdef RARDLL + // If we already have ERAR_EOPEN as result of missing volume, + // we should not replace it with less precise ERAR_BAD_PASSWORD. + if (Cmd->DllError!=ERAR_EOPEN) + Cmd->DllError=ERAR_BAD_PASSWORD; +#endif + ErrHandler.SetErrorCode(RARX_BADPWD); + ExtrFile=false; + } + break; + } + +#ifdef RARDLL + if (*Cmd->DllDestName!=0) + wcsncpyz(DestFileName,Cmd->DllDestName,ASIZE(DestFileName)); +#endif + + File CurFile; + + bool LinkEntry=Arc.FileHead.RedirType!=FSREDIR_NONE; + if (LinkEntry && Arc.FileHead.RedirType!=FSREDIR_FILECOPY) + { + if (ExtrFile && Command!='P' && !Cmd->Test) + { + // Overwrite prompt for symbolic and hard links. + bool UserReject=false; + if (FileExist(DestFileName) && !UserReject) + FileCreate(Cmd,NULL,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime); + if (UserReject) + ExtrFile=false; + } + } + else + if (Arc.IsArcDir()) + { + if (!ExtrFile || Command=='P' || Command=='I' || Command=='E' || Cmd->ExclPath==EXCL_SKIPWHOLEPATH) + return true; + TotalFileCount++; + ExtrCreateDir(Arc,ArcFileName); + // It is important to not increment MatchedArgs here, so we extract + // dir with its entire contents and not dir record only even if + // dir record precedes files. + return true; + } + else + if (ExtrFile) // Create files and file copies (FSREDIR_FILECOPY). + ExtrFile=ExtrCreateFile(Arc,CurFile); + + if (!ExtrFile && Arc.Solid) + { + SkipSolid=true; + ExtrFile=true; + + // We changed SkipSolid, so we need to call uiStartFileExtract + // with "Skip" parameter to change the operation status + // from "extracting" to "skipping". For example, it can be necessary + // if user answered "No" to overwrite prompt when unpacking + // a solid archive. + if (!uiStartFileExtract(ArcFileName,false,false,true)) + return false; + } + if (ExtrFile) + { + // Set it in test mode, so we also test subheaders such as NTFS streams + // after tested file. + if (Cmd->Test) + PrevProcessed=true; + + bool TestMode=Cmd->Test || SkipSolid; // Unpack to memory, not to disk. + + if (!SkipSolid) + { + if (!TestMode && Command!='P' && CurFile.IsDevice()) + { + uiMsg(UIERROR_INVALIDNAME,Arc.FileName,DestFileName); + ErrHandler.WriteError(Arc.FileName,DestFileName); + } + TotalFileCount++; + } + FileCount++; + if (Command!='I') + if (SkipSolid) + mprintf(St(MExtrSkipFile),ArcFileName); + else + switch(Cmd->Test ? 'T':Command) // "Test" can be also enabled by -t switch. + { + case 'T': + mprintf(St(MExtrTestFile),ArcFileName); + break; +#ifndef SFX_MODULE + case 'P': + mprintf(St(MExtrPrinting),ArcFileName); + break; +#endif + case 'X': + case 'E': + mprintf(St(MExtrFile),DestFileName); + break; + } + if (!Cmd->DisablePercentage) + mprintf(L" "); + + DataIO.CurUnpRead=0; + DataIO.CurUnpWrite=0; + DataIO.UnpHash.Init(Arc.FileHead.FileHash.Type,Cmd->Threads); + DataIO.PackedDataHash.Init(Arc.FileHead.FileHash.Type,Cmd->Threads); + DataIO.SetPackedSizeToRead(Arc.FileHead.PackSize); + DataIO.SetFiles(&Arc,&CurFile); + DataIO.SetTestMode(TestMode); + DataIO.SetSkipUnpCRC(SkipSolid); + +#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT) + if (!TestMode && !Arc.BrokenHeader && + Arc.FileHead.UnpSize>0xffffffff && (Fat32 || !NotFat32)) + { + if (!Fat32) // Not detected yet. + NotFat32=!(Fat32=IsFAT(Cmd->ExtrPath)); + if (Fat32) + uiMsg(UIMSG_FAT32SIZE); // Inform user about FAT32 size limit. + } +#endif + + if (!TestMode && !Arc.BrokenHeader && + (Arc.FileHead.PackSize<<11)>Arc.FileHead.UnpSize && + (Arc.FileHead.UnpSize<100000000 || Arc.FileLength()>Arc.FileHead.PackSize)) + CurFile.Prealloc(Arc.FileHead.UnpSize); + + CurFile.SetAllowDelete(!Cmd->KeepBroken); + + bool FileCreateMode=!TestMode && !SkipSolid && Command!='P'; + bool ShowChecksum=true; // Display checksum verification result. + + bool LinkSuccess=true; // Assume success for test mode. + if (LinkEntry) + { + FILE_SYSTEM_REDIRECT Type=Arc.FileHead.RedirType; + + if (Type==FSREDIR_HARDLINK || Type==FSREDIR_FILECOPY) + { + wchar NameExisting[NM]; + ExtrPrepareName(Arc,Arc.FileHead.RedirName,NameExisting,ASIZE(NameExisting)); + if (FileCreateMode && *NameExisting!=0) // *NameExisting can be 0 in case of excessive -ap switch. + if (Type==FSREDIR_HARDLINK) + LinkSuccess=ExtractHardlink(DestFileName,NameExisting,ASIZE(NameExisting)); + else + LinkSuccess=ExtractFileCopy(CurFile,Arc.FileName,DestFileName,NameExisting,ASIZE(NameExisting)); + } + else + if (Type==FSREDIR_UNIXSYMLINK || Type==FSREDIR_WINSYMLINK || Type==FSREDIR_JUNCTION) + { + if (FileCreateMode) + LinkSuccess=ExtractSymlink(Cmd,DataIO,Arc,DestFileName); + } + else + { + uiMsg(UIERROR_UNKNOWNEXTRA, Arc.FileName, DestFileName); + LinkSuccess=false; + } + + if (!LinkSuccess || Arc.Format==RARFMT15 && !FileCreateMode) + { + // RAR 5.x links have a valid data checksum even in case of + // failure, because they do not store any data. + // We do not want to display "OK" in this case. + // For 4.x symlinks we verify the checksum only when extracting, + // but not when testing an archive. + ShowChecksum=false; + } + PrevProcessed=FileCreateMode && LinkSuccess; + } + else + if (!Arc.FileHead.SplitBefore) + if (Arc.FileHead.Method==0) + UnstoreFile(DataIO,Arc.FileHead.UnpSize); + else + { + Unp->Init(Arc.FileHead.WinSize,Arc.FileHead.Solid); + Unp->SetDestSize(Arc.FileHead.UnpSize); +#ifndef SFX_MODULE + if (Arc.Format!=RARFMT50 && Arc.FileHead.UnpVer<=15) + Unp->DoUnpack(15,FileCount>1 && Arc.Solid); + else +#endif + Unp->DoUnpack(Arc.FileHead.UnpVer,Arc.FileHead.Solid); + } + + Arc.SeekToNext(); + + // We check for "split after" flag to detect partially extracted files + // from incomplete volume sets. For them file header contains packed + // data hash, which must not be compared against unpacked data hash + // to prevent accidental match. Moreover, for -m0 volumes packed data + // hash would match truncated unpacked data hash and lead to fake "OK" + // in incomplete volume set. + bool ValidCRC=!Arc.FileHead.SplitAfter && DataIO.UnpHash.Cmp(&Arc.FileHead.FileHash,Arc.FileHead.UseHashKey ? Arc.FileHead.HashKey:NULL); + + // We set AnySolidDataUnpackedWell to true if we found at least one + // valid non-zero solid file in preceding solid stream. If it is true + // and if current encrypted file is broken, we do not need to hint + // about a wrong password and can report CRC error only. + if (!Arc.FileHead.Solid) + AnySolidDataUnpackedWell=false; // Reset the flag, because non-solid file is found. + else + if (Arc.FileHead.Method!=0 && Arc.FileHead.UnpSize>0 && ValidCRC) + AnySolidDataUnpackedWell=true; + + bool BrokenFile=false; + + // Checksum is not calculated in skip solid mode for performance reason. + if (!SkipSolid && ShowChecksum) + { + if (ValidCRC) + { + if (Command!='P' && Command!='I') + mprintf(L"%s%s ",Cmd->DisablePercentage ? L" ":L"\b\b\b\b\b ", + Arc.FileHead.FileHash.Type==HASH_NONE ? L" ?":St(MOk)); + } + else + { + if (Arc.FileHead.Encrypted && (!Arc.FileHead.UsePswCheck || + Arc.BrokenHeader) && !AnySolidDataUnpackedWell) + uiMsg(UIERROR_CHECKSUMENC,Arc.FileName,ArcFileName); + else + uiMsg(UIERROR_CHECKSUM,Arc.FileName,ArcFileName); + BrokenFile=true; + ErrHandler.SetErrorCode(RARX_CRC); +#ifdef RARDLL + // If we already have ERAR_EOPEN as result of missing volume + // or ERAR_BAD_PASSWORD for RAR5 wrong password, + // we should not replace it with less precise ERAR_BAD_DATA. + if (Cmd->DllError!=ERAR_EOPEN && Cmd->DllError!=ERAR_BAD_PASSWORD) + Cmd->DllError=ERAR_BAD_DATA; +#endif + } + } + else + mprintf(L"\b\b\b\b\b "); + + if (!TestMode && (Command=='X' || Command=='E') && + (!LinkEntry || Arc.FileHead.RedirType==FSREDIR_FILECOPY && LinkSuccess) && + (!BrokenFile || Cmd->KeepBroken)) + { + // We could preallocate more space that really written to broken file. + if (BrokenFile) + CurFile.Truncate(); + +#if defined(_WIN_ALL) || defined(_EMX) + if (Cmd->ClearArc) + Arc.FileHead.FileAttr&=~FILE_ATTRIBUTE_ARCHIVE; +#endif + + + CurFile.SetOpenFileTime( + Cmd->xmtime==EXTTIME_NONE ? NULL:&Arc.FileHead.mtime, + Cmd->xctime==EXTTIME_NONE ? NULL:&Arc.FileHead.ctime, + Cmd->xatime==EXTTIME_NONE ? NULL:&Arc.FileHead.atime); + CurFile.Close(); +#if defined(_WIN_ALL) && !defined(SFX_MODULE) + if (Cmd->SetCompressedAttr && + (Arc.FileHead.FileAttr & FILE_ATTRIBUTE_COMPRESSED)!=0) + SetFileCompression(CurFile.FileName,true); +#endif + SetFileHeaderExtra(Cmd,Arc,CurFile.FileName); + + CurFile.SetCloseFileTime( + Cmd->xmtime==EXTTIME_NONE ? NULL:&Arc.FileHead.mtime, + Cmd->xatime==EXTTIME_NONE ? NULL:&Arc.FileHead.atime); + if (!Cmd->IgnoreGeneralAttr && !SetFileAttr(CurFile.FileName,Arc.FileHead.FileAttr)) + uiMsg(UIERROR_FILEATTR,Arc.FileName,CurFile.FileName); + + PrevProcessed=true; + } + } + } + // It is important to increment it for files, but not dirs. So we extract + // dir with its entire contents, not just dir record only even if dir + // record precedes files. + if (MatchFound) + MatchedArgs++; + if (DataIO.NextVolumeMissing) + return false; + if (!ExtrFile) + if (!Arc.Solid) + Arc.SeekToNext(); + else + if (!SkipSolid) + return false; + return true; +} + + +void CmdExtract::UnstoreFile(ComprDataIO &DataIO,int64 DestUnpSize) +{ + // 512 KB and larger buffer reported to reduce performance on old XP + // computers with WDC WD2000JD HDD. According to test made by user + // 256 KB buffer is optimal. + Array Buffer(0x40000); + while (1) + { + uint Code=DataIO.UnpRead(&Buffer[0],Buffer.Size()); + if (Code==0 || (int)Code==-1) + break; + Code=Code=0) + DestUnpSize-=Code; + } +} + + +bool CmdExtract::ExtractFileCopy(File &New,wchar *ArcName,wchar *NameNew,wchar *NameExisting,size_t NameExistingSize) +{ + SlashToNative(NameExisting,NameExisting,NameExistingSize); // Not needed for RAR 5.1+ archives. + + File Existing; + if (!Existing.WOpen(NameExisting)) + { + uiMsg(UIERROR_FILECOPY,ArcName,NameExisting,NameNew); + uiMsg(UIERROR_FILECOPYHINT,ArcName); +#ifdef RARDLL + Cmd->DllError=ERAR_EREFERENCE; +#endif + return false; + } + + Array Buffer(0x100000); + int64 CopySize=0; + + while (true) + { + Wait(); + int ReadSize=Existing.Read(&Buffer[0],Buffer.Size()); + if (ReadSize==0) + break; + New.Write(&Buffer[0],ReadSize); + CopySize+=ReadSize; + } + + return true; +} + + +void CmdExtract::ExtrPrepareName(Archive &Arc,const wchar *ArcFileName,wchar *DestName,size_t DestSize) +{ + wcsncpyz(DestName,Cmd->ExtrPath,DestSize); + + if (*Cmd->ExtrPath!=0) + { + wchar LastChar=*PointToLastChar(Cmd->ExtrPath); + // We need IsPathDiv check here to correctly handle Unix forward slash + // in the end of destination path in Windows: rar x arc dest/ + // IsDriveDiv is needed for current drive dir: rar x arc d: + if (!IsPathDiv(LastChar) && !IsDriveDiv(LastChar)) + { + // Destination path can be without trailing slash if it come from GUI shell. + AddEndSlash(DestName,DestSize); + } + } + +#ifndef SFX_MODULE + if (Cmd->AppendArcNameToPath) + { + wcsncatz(DestName,PointToName(Arc.FirstVolumeName),DestSize); + SetExt(DestName,NULL,DestSize); + AddEndSlash(DestName,DestSize); + } +#endif + +#ifndef SFX_MODULE + size_t ArcPathLength=wcslen(Cmd->ArcPath); + if (ArcPathLength>0) + { + size_t NameLength=wcslen(ArcFileName); + + // Earlier we compared lengths only here, but then noticed a cosmetic bug + // in WinRAR. When extracting a file reference from subfolder with + // "Extract relative paths", so WinRAR sets ArcPath, if reference target + // is missing, error message removed ArcPath both from reference and target + // names. If target was stored in another folder, its name looked wrong. + if (NameLength>=ArcPathLength && + wcsnicompc(Cmd->ArcPath,ArcFileName,ArcPathLength)==0 && + (IsPathDiv(Cmd->ArcPath[ArcPathLength-1]) || + IsPathDiv(ArcFileName[ArcPathLength]) || ArcFileName[ArcPathLength]==0)) + { + ArcFileName+=Min(ArcPathLength,NameLength); + while (IsPathDiv(*ArcFileName)) + ArcFileName++; + if (*ArcFileName==0) // Excessive -ap switch. + { + *DestName=0; + return; + } + } + } +#endif + + wchar Command=Cmd->Command[0]; + // Use -ep3 only in systems, where disk letters are exist, not in Unix. + bool AbsPaths=Cmd->ExclPath==EXCL_ABSPATH && Command=='X' && IsDriveDiv(':'); + + // We do not use any user specified destination paths when extracting + // absolute paths in -ep3 mode. + if (AbsPaths) + *DestName=0; + + if (Command=='E' || Cmd->ExclPath==EXCL_SKIPWHOLEPATH) + wcsncatz(DestName,PointToName(ArcFileName),DestSize); + else + wcsncatz(DestName,ArcFileName,DestSize); + +#ifdef _WIN_ALL + // Must do after Cmd->ArcPath processing above, so file name and arc path + // trailing spaces are in sync. + if (!Cmd->AllowIncompatNames) + MakeNameCompatible(DestName); +#endif + + wchar DiskLetter=toupperw(DestName[0]); + + if (AbsPaths) + { + if (DestName[1]=='_' && IsPathDiv(DestName[2]) && + DiskLetter>='A' && DiskLetter<='Z') + DestName[1]=':'; + else + if (DestName[0]=='_' && DestName[1]=='_') + { + // Convert __server\share to \\server\share. + DestName[0]=CPATHDIVIDER; + DestName[1]=CPATHDIVIDER; + } + } +} + + +#ifdef RARDLL +bool CmdExtract::ExtrDllGetPassword() +{ + if (!Cmd->Password.IsSet()) + { + if (Cmd->Callback!=NULL) + { + wchar PasswordW[MAXPASSWORD]; + *PasswordW=0; + if (Cmd->Callback(UCM_NEEDPASSWORDW,Cmd->UserData,(LPARAM)PasswordW,ASIZE(PasswordW))==-1) + *PasswordW=0; + if (*PasswordW==0) + { + char PasswordA[MAXPASSWORD]; + *PasswordA=0; + if (Cmd->Callback(UCM_NEEDPASSWORD,Cmd->UserData,(LPARAM)PasswordA,ASIZE(PasswordA))==-1) + *PasswordA=0; + GetWideName(PasswordA,NULL,PasswordW,ASIZE(PasswordW)); + cleandata(PasswordA,sizeof(PasswordA)); + } + Cmd->Password.Set(PasswordW); + cleandata(PasswordW,sizeof(PasswordW)); + Cmd->ManualPassword=true; + } + if (!Cmd->Password.IsSet()) + return false; + } + return true; +} +#endif + + +#ifndef RARDLL +bool CmdExtract::ExtrGetPassword(Archive &Arc,const wchar *ArcFileName) +{ + if (!Cmd->Password.IsSet()) + { + if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password) || !Cmd->Password.IsSet()) + { + // Suppress "test is ok" message in GUI if user entered + // an empty password or cancelled a password prompt. + uiMsg(UIERROR_INCERRCOUNT); + + return false; + } + Cmd->ManualPassword=true; + } +#if !defined(SILENT) + else + if (!PasswordAll && !Arc.FileHead.Solid) + { + eprintf(St(MUseCurPsw),ArcFileName); + switch(Cmd->AllYes ? 1 : Ask(St(MYesNoAll))) + { + case -1: + ErrHandler.Exit(RARX_USERBREAK); + case 2: + if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password)) + return false; + break; + case 3: + PasswordAll=true; + break; + } + } +#endif + return true; +} +#endif + + +#if defined(_WIN_ALL) && !defined(SFX_MODULE) +void CmdExtract::ConvertDosPassword(Archive &Arc,SecPassword &DestPwd) +{ + if (Arc.Format==RARFMT15 && Arc.FileHead.HostOS==HOST_MSDOS) + { + // We need the password in OEM encoding if file was encrypted by + // native RAR/DOS (not extender based). Let's make the conversion. + wchar PlainPsw[MAXPASSWORD]; + Cmd->Password.Get(PlainPsw,ASIZE(PlainPsw)); + char PswA[MAXPASSWORD]; + CharToOemBuffW(PlainPsw,PswA,ASIZE(PswA)); + PswA[ASIZE(PswA)-1]=0; + CharToWide(PswA,PlainPsw,ASIZE(PlainPsw)); + DestPwd.Set(PlainPsw); + cleandata(PlainPsw,sizeof(PlainPsw)); + cleandata(PswA,sizeof(PswA)); + } +} +#endif + + +void CmdExtract::ExtrCreateDir(Archive &Arc,const wchar *ArcFileName) +{ + if (Cmd->Test) + { + mprintf(St(MExtrTestFile),ArcFileName); + mprintf(L" %s",St(MOk)); + return; + } + + MKDIR_CODE MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr); + bool DirExist=false; + if (MDCode!=MKDIR_SUCCESS) + { + DirExist=FileExist(DestFileName); + if (DirExist && !IsDir(GetFileAttr(DestFileName))) + { + // File with name same as this directory exists. Propose user + // to overwrite it. + bool UserReject; + FileCreate(Cmd,NULL,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime); + DirExist=false; + } + if (!DirExist) + { + CreatePath(DestFileName,true); + MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr); + if (MDCode!=MKDIR_SUCCESS) + { + wchar OrigName[ASIZE(DestFileName)]; + wcsncpyz(OrigName,DestFileName,ASIZE(OrigName)); + MakeNameUsable(DestFileName,true); + CreatePath(DestFileName,true); + MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr); +#ifndef SFX_MODULE + if (MDCode==MKDIR_SUCCESS) + uiMsg(UIERROR_RENAMING,Arc.FileName,OrigName,DestFileName); +#endif + } + } + } + if (MDCode==MKDIR_SUCCESS) + { + mprintf(St(MCreatDir),DestFileName); + mprintf(L" %s",St(MOk)); + PrevProcessed=true; + } + else + if (DirExist) + { + if (!Cmd->IgnoreGeneralAttr) + SetFileAttr(DestFileName,Arc.FileHead.FileAttr); + PrevProcessed=true; + } + else + { + uiMsg(UIERROR_DIRCREATE,Arc.FileName,DestFileName); + ErrHandler.SysErrMsg(); +#ifdef RARDLL + Cmd->DllError=ERAR_ECREATE; +#endif + ErrHandler.SetErrorCode(RARX_CREATE); + } + if (PrevProcessed) + { +#if defined(_WIN_ALL) && !defined(SFX_MODULE) + if (Cmd->SetCompressedAttr && + (Arc.FileHead.FileAttr & FILE_ATTRIBUTE_COMPRESSED)!=0 && WinNT()) + SetFileCompression(DestFileName,true); +#endif + SetFileHeaderExtra(Cmd,Arc,DestFileName); + SetDirTime(DestFileName, + Cmd->xmtime==EXTTIME_NONE ? NULL:&Arc.FileHead.mtime, + Cmd->xctime==EXTTIME_NONE ? NULL:&Arc.FileHead.ctime, + Cmd->xatime==EXTTIME_NONE ? NULL:&Arc.FileHead.atime); + } +} + + +bool CmdExtract::ExtrCreateFile(Archive &Arc,File &CurFile) +{ + bool Success=true; + wchar Command=Cmd->Command[0]; +#if !defined(SFX_MODULE) + if (Command=='P') + CurFile.SetHandleType(FILE_HANDLESTD); +#endif + if ((Command=='E' || Command=='X') && !Cmd->Test) + { + bool UserReject; + // Specify "write only" mode to avoid OpenIndiana NAS problems + // with SetFileTime and read+write files. + if (!FileCreate(Cmd,&CurFile,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,true)) + { + Success=false; + if (!UserReject) + { + ErrHandler.CreateErrorMsg(Arc.FileName,DestFileName); +#ifdef RARDLL + Cmd->DllError=ERAR_ECREATE; +#endif + if (!IsNameUsable(DestFileName)) + { + uiMsg(UIMSG_CORRECTINGNAME,Arc.FileName); + + wchar OrigName[ASIZE(DestFileName)]; + wcsncpyz(OrigName,DestFileName,ASIZE(OrigName)); + + MakeNameUsable(DestFileName,true); + + CreatePath(DestFileName,true); + if (FileCreate(Cmd,&CurFile,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,true)) + { +#ifndef SFX_MODULE + uiMsg(UIERROR_RENAMING,Arc.FileName,OrigName,DestFileName); +#endif + Success=true; + } + else + ErrHandler.CreateErrorMsg(Arc.FileName,DestFileName); + } + } + } + } + return Success; +} + + +bool CmdExtract::CheckUnpVer(Archive &Arc,const wchar *ArcFileName) +{ + bool WrongVer; + if (Arc.Format==RARFMT50) // Both SFX and RAR can unpack RAR 5.0 archives. + WrongVer=Arc.FileHead.UnpVer>VER_UNPACK5; + else + { +#ifdef SFX_MODULE // SFX can unpack only RAR 2.9 archives. + WrongVer=Arc.FileHead.UnpVer!=VER_UNPACK; +#else // All formats since 1.3 for RAR. + WrongVer=Arc.FileHead.UnpVer<13 || Arc.FileHead.UnpVer>VER_UNPACK; +#endif + } + + // We can unpack stored files regardless of compression version field. + if (Arc.FileHead.Method==0) + WrongVer=false; + + if (WrongVer) + { + ErrHandler.UnknownMethodMsg(Arc.FileName,ArcFileName); + uiMsg(UIERROR_NEWERRAR,Arc.FileName); + } + return !WrongVer; +} diff --git a/reference/unrar/extract.hpp b/reference/unrar/extract.hpp new file mode 100644 index 00000000..85a21f53 --- /dev/null +++ b/reference/unrar/extract.hpp @@ -0,0 +1,62 @@ +#ifndef _RAR_EXTRACT_ +#define _RAR_EXTRACT_ + +enum EXTRACT_ARC_CODE {EXTRACT_ARC_NEXT,EXTRACT_ARC_REPEAT}; + +class CmdExtract +{ + private: + EXTRACT_ARC_CODE ExtractArchive(); + bool ExtractFileCopy(File &New,wchar *ArcName,wchar *NameNew,wchar *NameExisting,size_t NameExistingSize); + void ExtrPrepareName(Archive &Arc,const wchar *ArcFileName,wchar *DestName,size_t DestSize); +#ifdef RARDLL + bool ExtrDllGetPassword(); +#else + bool ExtrGetPassword(Archive &Arc,const wchar *ArcFileName); +#endif +#if defined(_WIN_ALL) && !defined(SFX_MODULE) + void ConvertDosPassword(Archive &Arc,SecPassword &DestPwd); +#endif + void ExtrCreateDir(Archive &Arc,const wchar *ArcFileName); + bool ExtrCreateFile(Archive &Arc,File &CurFile); + bool CheckUnpVer(Archive &Arc,const wchar *ArcFileName); + + RarTime StartTime; // time when extraction started + + CommandData *Cmd; + + ComprDataIO DataIO; + Unpack *Unp; + unsigned long TotalFileCount; + + unsigned long FileCount; + unsigned long MatchedArgs; + bool FirstFile; + bool AllMatchesExact; + bool ReconstructDone; + + // If any non-zero solid file was successfully unpacked before current. + // If true and if current encrypted file is broken, obviously + // the password is correct and we can report broken CRC without + // any wrong password hints. + bool AnySolidDataUnpackedWell; + + wchar ArcName[NM]; + + bool PasswordAll; + bool PrevProcessed; // If previous file was successfully extracted or tested. + wchar DestFileName[NM]; + bool PasswordCancelled; +#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT) + bool Fat32,NotFat32; +#endif + public: + CmdExtract(CommandData *Cmd); + ~CmdExtract(); + void DoExtract(); + void ExtractArchiveInit(Archive &Arc); + bool ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat); + static void UnstoreFile(ComprDataIO &DataIO,int64 DestUnpSize); +}; + +#endif diff --git a/reference/unrar/filcreat.cpp b/reference/unrar/filcreat.cpp new file mode 100644 index 00000000..a64a7d4d --- /dev/null +++ b/reference/unrar/filcreat.cpp @@ -0,0 +1,163 @@ +#include "rar.hpp" + +// If NewFile==NULL, we delete created file after user confirmation. +// It is useful we we need to overwrite an existing folder or file, +// but need user confirmation for that. +bool FileCreate(RAROptions *Cmd,File *NewFile,wchar *Name,size_t MaxNameSize, + bool *UserReject,int64 FileSize,RarTime *FileTime,bool WriteOnly) +{ + if (UserReject!=NULL) + *UserReject=false; +#ifdef _WIN_ALL + bool ShortNameChanged=false; +#endif + while (FileExist(Name)) + { +#if defined(_WIN_ALL) + if (!ShortNameChanged) + { + // Avoid the infinite loop if UpdateExistingShortName returns + // the same name. + ShortNameChanged=true; + + // Maybe our long name matches the short name of existing file. + // Let's check if we can change the short name. + if (UpdateExistingShortName(Name)) + continue; + } + // Allow short name check again. It is necessary, because rename and + // autorename below can change the name, so we need to check it again. + ShortNameChanged=false; +#endif + UIASKREP_RESULT Choice=uiAskReplaceEx(Cmd,Name,MaxNameSize,FileSize,FileTime,(NewFile==NULL ? UIASKREP_F_NORENAME:0)); + + if (Choice==UIASKREP_R_REPLACE) + break; + if (Choice==UIASKREP_R_SKIP) + { + if (UserReject!=NULL) + *UserReject=true; + return false; + } + if (Choice==UIASKREP_R_CANCEL) + ErrHandler.Exit(RARX_USERBREAK); + } + + // Try to truncate the existing file first instead of delete, + // so we preserve existing file permissions such as NTFS permissions. + uint FileMode=WriteOnly ? FMF_WRITE|FMF_SHAREREAD:FMF_UPDATE|FMF_SHAREREAD; + if (NewFile!=NULL && NewFile->Create(Name,FileMode)) + return true; + + CreatePath(Name,true); + return NewFile!=NULL ? NewFile->Create(Name,FileMode):DelFile(Name); +} + + +bool GetAutoRenamedName(wchar *Name,size_t MaxNameSize) +{ + wchar NewName[NM]; + size_t NameLength=wcslen(Name); + wchar *Ext=GetExt(Name); + if (Ext==NULL) + Ext=Name+NameLength; + for (uint FileVer=1;;FileVer++) + { + swprintf(NewName,ASIZE(NewName),L"%.*ls(%u)%ls",uint(Ext-Name),Name,FileVer,Ext); + if (!FileExist(NewName)) + { + wcsncpyz(Name,NewName,MaxNameSize); + break; + } + if (FileVer>=1000000) + return false; + } + return true; +} + + +#if defined(_WIN_ALL) +// If we find a file, which short name is equal to 'Name', we try to change +// its short name, while preserving the long name. It helps when unpacking +// an archived file, which long name is equal to short name of already +// existing file. Otherwise we would overwrite the already existing file, +// even though its long name does not match the name of unpacking file. +bool UpdateExistingShortName(const wchar *Name) +{ + wchar LongPathName[NM]; + DWORD Res=GetLongPathName(Name,LongPathName,ASIZE(LongPathName)); + if (Res==0 || Res>=ASIZE(LongPathName)) + return false; + wchar ShortPathName[NM]; + Res=GetShortPathName(Name,ShortPathName,ASIZE(ShortPathName)); + if (Res==0 || Res>=ASIZE(ShortPathName)) + return false; + wchar *LongName=PointToName(LongPathName); + wchar *ShortName=PointToName(ShortPathName); + + // We continue only if file has a short name, which does not match its + // long name, and this short name is equal to name of file which we need + // to create. + if (*ShortName==0 || wcsicomp(LongName,ShortName)==0 || + wcsicomp(PointToName(Name),ShortName)!=0) + return false; + + // Generate the temporary new name for existing file. + wchar NewName[NM]; + *NewName=0; + for (int I=0;I<10000 && *NewName==0;I+=123) + { + // Here we copy the path part of file to create. We'll make the temporary + // file in the same folder. + wcsncpyz(NewName,Name,ASIZE(NewName)); + + // Here we set the random name part. + swprintf(PointToName(NewName),ASIZE(NewName),L"rtmp%d",I); + + // If such file is already exist, try next random name. + if (FileExist(NewName)) + *NewName=0; + } + + // If we could not generate the name not used by any other file, we return. + if (*NewName==0) + return false; + + // FastFind returns the name without path, but we need the fully qualified + // name for renaming, so we use the path from file to create and long name + // from existing file. + wchar FullName[NM]; + wcsncpyz(FullName,Name,ASIZE(FullName)); + SetName(FullName,LongName,ASIZE(FullName)); + + // Rename the existing file to randomly generated name. Normally it changes + // the short name too. + if (!MoveFile(FullName,NewName)) + return false; + + // Now we need to create the temporary empty file with same name as + // short name of our already existing file. We do it to occupy its previous + // short name and not allow to use it again when renaming the file back to + // its original long name. + File KeepShortFile; + bool Created=false; + if (!FileExist(Name)) + Created=KeepShortFile.Create(Name,FMF_WRITE|FMF_SHAREREAD); + + // Now we rename the existing file from temporary name to original long name. + // Since its previous short name is occupied by another file, it should + // get another short name. + MoveFile(NewName,FullName); + + if (Created) + { + // Delete the temporary zero length file occupying the short name, + KeepShortFile.Close(); + KeepShortFile.Delete(); + } + // We successfully changed the short name. Maybe sometimes we'll simplify + // this function by use of SetFileShortName Windows API call. + // But SetFileShortName is not available in older Windows. + return true; +} +#endif diff --git a/reference/unrar/filcreat.hpp b/reference/unrar/filcreat.hpp new file mode 100644 index 00000000..44f801d4 --- /dev/null +++ b/reference/unrar/filcreat.hpp @@ -0,0 +1,14 @@ +#ifndef _RAR_FILECREATE_ +#define _RAR_FILECREATE_ + +bool FileCreate(RAROptions *Cmd,File *NewFile,wchar *Name,size_t MaxNameSize, + bool *UserReject,int64 FileSize=INT64NDF, + RarTime *FileTime=NULL,bool WriteOnly=false); + +bool GetAutoRenamedName(wchar *Name,size_t MaxNameSize); + +#if defined(_WIN_ALL) +bool UpdateExistingShortName(const wchar *Name); +#endif + +#endif diff --git a/reference/unrar/file.cpp b/reference/unrar/file.cpp new file mode 100644 index 00000000..5e6235a7 --- /dev/null +++ b/reference/unrar/file.cpp @@ -0,0 +1,729 @@ +#include "rar.hpp" + +File::File() +{ + hFile=FILE_BAD_HANDLE; + *FileName=0; + NewFile=false; + LastWrite=false; + HandleType=FILE_HANDLENORMAL; + SkipClose=false; + IgnoreReadErrors=false; + ErrorType=FILE_SUCCESS; + OpenShared=false; + AllowDelete=true; + AllowExceptions=true; +#ifdef _WIN_ALL + NoSequentialRead=false; + CreateMode=FMF_UNDEFINED; +#endif +} + + +File::~File() +{ + if (hFile!=FILE_BAD_HANDLE && !SkipClose) + if (NewFile) + Delete(); + else + Close(); +} + + +void File::operator = (File &SrcFile) +{ + hFile=SrcFile.hFile; + NewFile=SrcFile.NewFile; + LastWrite=SrcFile.LastWrite; + HandleType=SrcFile.HandleType; + wcsncpyz(FileName,SrcFile.FileName,ASIZE(FileName)); + SrcFile.SkipClose=true; +} + + +bool File::Open(const wchar *Name,uint Mode) +{ + ErrorType=FILE_SUCCESS; + FileHandle hNewFile; + bool OpenShared=File::OpenShared || (Mode & FMF_OPENSHARED)!=0; + bool UpdateMode=(Mode & FMF_UPDATE)!=0; + bool WriteMode=(Mode & FMF_WRITE)!=0; +#ifdef _WIN_ALL + uint Access=WriteMode ? GENERIC_WRITE:GENERIC_READ; + if (UpdateMode) + Access|=GENERIC_WRITE; + uint ShareMode=(Mode & FMF_OPENEXCLUSIVE) ? 0 : FILE_SHARE_READ; + if (OpenShared) + ShareMode|=FILE_SHARE_WRITE; + uint Flags=NoSequentialRead ? 0:FILE_FLAG_SEQUENTIAL_SCAN; + hNewFile=CreateFile(Name,Access,ShareMode,NULL,OPEN_EXISTING,Flags,NULL); + + DWORD LastError; + if (hNewFile==FILE_BAD_HANDLE) + { + LastError=GetLastError(); + + wchar LongName[NM]; + if (GetWinLongPath(Name,LongName,ASIZE(LongName))) + { + hNewFile=CreateFile(LongName,Access,ShareMode,NULL,OPEN_EXISTING,Flags,NULL); + + // For archive names longer than 260 characters first CreateFile + // (without \\?\) fails and sets LastError to 3 (access denied). + // We need the correct "file not found" error code to decide + // if we create a new archive or quit with "cannot create" error. + // So we need to check the error code after \\?\ CreateFile again, + // otherwise we'll fail to create new archives with long names. + // But we cannot simply assign the new code to LastError, + // because it would break "..\arcname.rar" relative names processing. + // First CreateFile returns the correct "file not found" code for such + // names, but "\\?\" CreateFile returns ERROR_INVALID_NAME treating + // dots as a directory name. So we check only for "file not found" + // error here and for other errors use the first CreateFile result. + if (GetLastError()==ERROR_FILE_NOT_FOUND) + LastError=ERROR_FILE_NOT_FOUND; + } + } + if (hNewFile==FILE_BAD_HANDLE && LastError==ERROR_FILE_NOT_FOUND) + ErrorType=FILE_NOTFOUND; + +#else + int flags=UpdateMode ? O_RDWR:(WriteMode ? O_WRONLY:O_RDONLY); +#ifdef O_BINARY + flags|=O_BINARY; +#if defined(_AIX) && defined(_LARGE_FILE_API) + flags|=O_LARGEFILE; +#endif +#endif + char NameA[NM]; + WideToChar(Name,NameA,ASIZE(NameA)); + + int handle=open(NameA,flags); +#ifdef LOCK_EX + +#ifdef _OSF_SOURCE + extern "C" int flock(int, int); +#endif + + if (!OpenShared && UpdateMode && handle>=0 && flock(handle,LOCK_EX|LOCK_NB)==-1) + { + close(handle); + return false; + } +#endif + if (handle==-1) + hNewFile=FILE_BAD_HANDLE; + else + { +#ifdef FILE_USE_OPEN + hNewFile=handle; +#else + hNewFile=fdopen(handle,UpdateMode ? UPDATEBINARY:READBINARY); +#endif + } + if (hNewFile==FILE_BAD_HANDLE && errno==ENOENT) + ErrorType=FILE_NOTFOUND; +#endif + NewFile=false; + HandleType=FILE_HANDLENORMAL; + SkipClose=false; + bool Success=hNewFile!=FILE_BAD_HANDLE; + if (Success) + { + hFile=hNewFile; + wcsncpyz(FileName,Name,ASIZE(FileName)); + } + return Success; +} + + +#if !defined(SFX_MODULE) +void File::TOpen(const wchar *Name) +{ + if (!WOpen(Name)) + ErrHandler.Exit(RARX_OPEN); +} +#endif + + +bool File::WOpen(const wchar *Name) +{ + if (Open(Name)) + return true; + ErrHandler.OpenErrorMsg(Name); + return false; +} + + +bool File::Create(const wchar *Name,uint Mode) +{ + // OpenIndiana based NAS and CIFS shares fail to set the file time if file + // was created in read+write mode and some data was written and not flushed + // before SetFileTime call. So we should use the write only mode if we plan + // SetFileTime call and do not need to read from file. + bool WriteMode=(Mode & FMF_WRITE)!=0; + bool ShareRead=(Mode & FMF_SHAREREAD)!=0 || File::OpenShared; +#ifdef _WIN_ALL + CreateMode=Mode; + uint Access=WriteMode ? GENERIC_WRITE:GENERIC_READ|GENERIC_WRITE; + DWORD ShareMode=ShareRead ? FILE_SHARE_READ:0; + + // Windows automatically removes dots and spaces in the end of file name, + // So we detect such names and process them with \\?\ prefix. + wchar *LastChar=PointToLastChar(Name); + bool Special=*LastChar=='.' || *LastChar==' '; + + if (Special && (Mode & FMF_STANDARDNAMES)==0) + hFile=FILE_BAD_HANDLE; + else + hFile=CreateFile(Name,Access,ShareMode,NULL,CREATE_ALWAYS,0,NULL); + + if (hFile==FILE_BAD_HANDLE) + { + wchar LongName[NM]; + if (GetWinLongPath(Name,LongName,ASIZE(LongName))) + hFile=CreateFile(LongName,Access,ShareMode,NULL,CREATE_ALWAYS,0,NULL); + } + +#else + char NameA[NM]; + WideToChar(Name,NameA,ASIZE(NameA)); +#ifdef FILE_USE_OPEN + hFile=open(NameA,(O_CREAT|O_TRUNC) | (WriteMode ? O_WRONLY : O_RDWR),0666); +#else + hFile=fopen(NameA,WriteMode ? WRITEBINARY:CREATEBINARY); +#endif +#endif + NewFile=true; + HandleType=FILE_HANDLENORMAL; + SkipClose=false; + wcsncpyz(FileName,Name,ASIZE(FileName)); + return hFile!=FILE_BAD_HANDLE; +} + + +#if !defined(SFX_MODULE) +void File::TCreate(const wchar *Name,uint Mode) +{ + if (!WCreate(Name,Mode)) + ErrHandler.Exit(RARX_FATAL); +} +#endif + + +bool File::WCreate(const wchar *Name,uint Mode) +{ + if (Create(Name,Mode)) + return true; + ErrHandler.CreateErrorMsg(Name); + return false; +} + + +bool File::Close() +{ + bool Success=true; + + if (hFile!=FILE_BAD_HANDLE) + { + if (!SkipClose) + { +#ifdef _WIN_ALL + // We use the standard system handle for stdout in Windows + // and it must not be closed here. + if (HandleType==FILE_HANDLENORMAL) + Success=CloseHandle(hFile)==TRUE; +#else +#ifdef FILE_USE_OPEN + Success=close(hFile)!=-1; +#else + Success=fclose(hFile)!=EOF; +#endif +#endif + } + hFile=FILE_BAD_HANDLE; + } + HandleType=FILE_HANDLENORMAL; + if (!Success && AllowExceptions) + ErrHandler.CloseError(FileName); + return Success; +} + + +bool File::Delete() +{ + if (HandleType!=FILE_HANDLENORMAL) + return false; + if (hFile!=FILE_BAD_HANDLE) + Close(); + if (!AllowDelete) + return false; + return DelFile(FileName); +} + + +bool File::Rename(const wchar *NewName) +{ + // No need to rename if names are already same. + bool Success=wcscmp(FileName,NewName)==0; + + if (!Success) + Success=RenameFile(FileName,NewName); + + if (Success) + wcscpy(FileName,NewName); + + return Success; +} + + +bool File::Write(const void *Data,size_t Size) +{ + if (Size==0) + return true; + if (HandleType==FILE_HANDLESTD) + { +#ifdef _WIN_ALL + hFile=GetStdHandle(STD_OUTPUT_HANDLE); +#else + // Cannot use the standard stdout here, because it already has wide orientation. + if (hFile==FILE_BAD_HANDLE) + { +#ifdef FILE_USE_OPEN + hFile=dup(STDOUT_FILENO); // Open new stdout stream. +#else + hFile=fdopen(dup(STDOUT_FILENO),"w"); // Open new stdout stream. +#endif + } +#endif + } + bool Success; + while (1) + { + Success=false; +#ifdef _WIN_ALL + DWORD Written=0; + if (HandleType!=FILE_HANDLENORMAL) + { + // writing to stdout can fail in old Windows if data block is too large + const size_t MaxSize=0x4000; + for (size_t I=0;ISize && FilePos-Size<=0xffffffff && FilePos+Size>0xffffffff) + ErrHandler.WriteErrorFAT(FileName); +#endif + if (ErrHandler.AskRepeatWrite(FileName,false)) + { +#if !defined(_WIN_ALL) && !defined(FILE_USE_OPEN) + clearerr(hFile); +#endif + if (Written0) + Seek(Tell()-Written,SEEK_SET); + continue; + } + ErrHandler.WriteError(NULL,FileName); + } + break; + } + LastWrite=true; + return Success; // It can return false only if AllowExceptions is disabled. +} + + +int File::Read(void *Data,size_t Size) +{ + int64 FilePos=0; // Initialized only to suppress some compilers warning. + + if (IgnoreReadErrors) + FilePos=Tell(); + int ReadSize; + while (true) + { + ReadSize=DirectRead(Data,Size); + if (ReadSize==-1) + { + ErrorType=FILE_READERROR; + if (AllowExceptions) + if (IgnoreReadErrors) + { + ReadSize=0; + for (size_t I=0;IMaxDeviceRead) +// Size=MaxDeviceRead; + hFile=GetStdHandle(STD_INPUT_HANDLE); +#else +#ifdef FILE_USE_OPEN + hFile=STDIN_FILENO; +#else + hFile=stdin; +#endif +#endif + } +#ifdef _WIN_ALL + // For pipes like 'type file.txt | rar -si arcname' ReadFile may return + // data in small ~4KB blocks. It may slightly reduce the compression ratio. + DWORD Read; + if (!ReadFile(hFile,Data,(DWORD)Size,&Read,NULL)) + { + if (IsDevice() && Size>MaxDeviceRead) + return DirectRead(Data,MaxDeviceRead); + if (HandleType==FILE_HANDLESTD && GetLastError()==ERROR_BROKEN_PIPE) + return 0; + + // We had a bug report about failure to archive 1C database lock file + // 1Cv8tmp.1CL, which is a zero length file with a region above 200 KB + // permanently locked. If our first read request uses too large buffer + // and if we are in -dh mode, so we were able to open the file, + // we'll fail with "Read error". So now we use try a smaller buffer size + // in case of lock error. + if (HandleType==FILE_HANDLENORMAL && Size>MaxLockedRead && + GetLastError()==ERROR_LOCK_VIOLATION) + return DirectRead(Data,MaxLockedRead); + + return -1; + } + return Read; +#else +#ifdef FILE_USE_OPEN + ssize_t ReadSize=read(hFile,Data,Size); + if (ReadSize==-1) + return -1; + return (int)ReadSize; +#else + if (LastWrite) + { + fflush(hFile); + LastWrite=false; + } + clearerr(hFile); + size_t ReadSize=fread(Data,1,Size,hFile); + if (ferror(hFile)) + return -1; + return (int)ReadSize; +#endif +#endif +} + + +void File::Seek(int64 Offset,int Method) +{ + if (!RawSeek(Offset,Method) && AllowExceptions) + ErrHandler.SeekError(FileName); +} + + +bool File::RawSeek(int64 Offset,int Method) +{ + if (hFile==FILE_BAD_HANDLE) + return true; + if (Offset<0 && Method!=SEEK_SET) + { + Offset=(Method==SEEK_CUR ? Tell():FileLength())+Offset; + Method=SEEK_SET; + } +#ifdef _WIN_ALL + LONG HighDist=(LONG)(Offset>>32); + if (SetFilePointer(hFile,(LONG)Offset,&HighDist,Method)==0xffffffff && + GetLastError()!=NO_ERROR) + return false; +#else + LastWrite=false; +#ifdef FILE_USE_OPEN + if (lseek(hFile,(off_t)Offset,Method)==-1) + return false; +#elif defined(_LARGEFILE_SOURCE) && !defined(_OSF_SOURCE) && !defined(__VMS) + if (fseeko(hFile,Offset,Method)!=0) + return false; +#else + if (fseek(hFile,(long)Offset,Method)!=0) + return false; +#endif +#endif + return true; +} + + +int64 File::Tell() +{ + if (hFile==FILE_BAD_HANDLE) + if (AllowExceptions) + ErrHandler.SeekError(FileName); + else + return -1; +#ifdef _WIN_ALL + LONG HighDist=0; + uint LowDist=SetFilePointer(hFile,0,&HighDist,FILE_CURRENT); + if (LowDist==0xffffffff && GetLastError()!=NO_ERROR) + if (AllowExceptions) + ErrHandler.SeekError(FileName); + else + return -1; + return INT32TO64(HighDist,LowDist); +#else +#ifdef FILE_USE_OPEN + return lseek(hFile,0,SEEK_CUR); +#elif defined(_LARGEFILE_SOURCE) && !defined(_OSF_SOURCE) + return ftello(hFile); +#else + return ftell(hFile); +#endif +#endif +} + + +void File::Prealloc(int64 Size) +{ +#ifdef _WIN_ALL + if (RawSeek(Size,SEEK_SET)) + { + Truncate(); + Seek(0,SEEK_SET); + } +#endif + +#if defined(_UNIX) && defined(USE_FALLOCATE) + // fallocate is rather new call. Only latest kernels support it. + // So we are not using it by default yet. + int fd = GetFD(); + if (fd >= 0) + fallocate(fd, 0, 0, Size); +#endif +} + + +byte File::GetByte() +{ + byte Byte=0; + Read(&Byte,1); + return Byte; +} + + +void File::PutByte(byte Byte) +{ + Write(&Byte,1); +} + + +bool File::Truncate() +{ +#ifdef _WIN_ALL + return SetEndOfFile(hFile)==TRUE; +#else + return ftruncate(GetFD(),(off_t)Tell())==0; +#endif +} + + +void File::Flush() +{ +#ifdef _WIN_ALL + FlushFileBuffers(hFile); +#else +#ifndef FILE_USE_OPEN + fflush(hFile); +#endif + fsync(GetFD()); +#endif +} + + +void File::SetOpenFileTime(RarTime *ftm,RarTime *ftc,RarTime *fta) +{ +#ifdef _WIN_ALL + // Workaround for OpenIndiana NAS time bug. If we cannot create a file + // in write only mode, we need to flush the write buffer before calling + // SetFileTime or file time will not be changed. + if (CreateMode!=FMF_UNDEFINED && (CreateMode & FMF_WRITE)==0) + FlushFileBuffers(hFile); + + bool sm=ftm!=NULL && ftm->IsSet(); + bool sc=ftc!=NULL && ftc->IsSet(); + bool sa=fta!=NULL && fta->IsSet(); + FILETIME fm,fc,fa; + if (sm) + ftm->GetWinFT(&fm); + if (sc) + ftc->GetWinFT(&fc); + if (sa) + fta->GetWinFT(&fa); + SetFileTime(hFile,sc ? &fc:NULL,sa ? &fa:NULL,sm ? &fm:NULL); +#endif +} + + +void File::SetCloseFileTime(RarTime *ftm,RarTime *fta) +{ +// Android APP_PLATFORM := android-14 does not support futimens and futimes. +// Newer platforms support futimens, but fail on Android 4.2. +// We have to use utime for Android. +// Also we noticed futimens fail to set timestamps on NTFS partition +// mounted to virtual Linux x86 machine, but utimensat worked correctly. +// So we set timestamps for already closed files in Unix. +#ifdef _UNIX + SetCloseFileTimeByName(FileName,ftm,fta); +#endif +} + + +void File::SetCloseFileTimeByName(const wchar *Name,RarTime *ftm,RarTime *fta) +{ +#ifdef _UNIX + bool setm=ftm!=NULL && ftm->IsSet(); + bool seta=fta!=NULL && fta->IsSet(); + if (setm || seta) + { + char NameA[NM]; + WideToChar(Name,NameA,ASIZE(NameA)); + +#ifdef UNIX_TIME_NS + timespec times[2]; + times[0].tv_sec=seta ? fta->GetUnix() : 0; + times[0].tv_nsec=seta ? long(fta->GetUnixNS()%1000000000) : UTIME_NOW; + times[1].tv_sec=setm ? ftm->GetUnix() : 0; + times[1].tv_nsec=setm ? long(ftm->GetUnixNS()%1000000000) : UTIME_NOW; + utimensat(AT_FDCWD,NameA,times,0); +#else + utimbuf ut; + if (setm) + ut.modtime=ftm->GetUnix(); + else + ut.modtime=fta->GetUnix(); // Need to set something, cannot left it 0. + if (seta) + ut.actime=fta->GetUnix(); + else + ut.actime=ut.modtime; // Need to set something, cannot left it 0. + utime(NameA,&ut); +#endif + } +#endif +} + + +void File::GetOpenFileTime(RarTime *ft) +{ +#ifdef _WIN_ALL + FILETIME FileTime; + GetFileTime(hFile,NULL,NULL,&FileTime); + ft->SetWinFT(&FileTime); +#endif +#if defined(_UNIX) || defined(_EMX) + struct stat st; + fstat(GetFD(),&st); + ft->SetUnix(st.st_mtime); +#endif +} + + +int64 File::FileLength() +{ + SaveFilePos SavePos(*this); + Seek(0,SEEK_END); + return Tell(); +} + + +bool File::IsDevice() +{ + if (hFile==FILE_BAD_HANDLE) + return false; +#ifdef _WIN_ALL + uint Type=GetFileType(hFile); + return Type==FILE_TYPE_CHAR || Type==FILE_TYPE_PIPE; +#else + return isatty(GetFD()); +#endif +} + + +#ifndef SFX_MODULE +int64 File::Copy(File &Dest,int64 Length) +{ + Array Buffer(0x40000); + int64 CopySize=0; + bool CopyAll=(Length==INT64NDF); + + while (CopyAll || Length>0) + { + Wait(); + size_t SizeToRead=(!CopyAll && Length<(int64)Buffer.Size()) ? (size_t)Length:Buffer.Size(); + char *Buf=&Buffer[0]; + int ReadSize=Read(Buf,SizeToRead); + if (ReadSize==0) + break; + size_t WriteSize=ReadSize; +#ifdef _WIN_ALL + // For FAT32 USB flash drives in Windows if first write is 4 KB or more, + // write caching is disabled and "write through" is enabled, resulting + // in bad performance, especially for many small files. It happens when + // we create SFX archive on USB drive, because SFX module is written first. + // So we split the first write to small 1 KB followed by rest of data. + if (CopySize==0 && WriteSize>=4096) + { + const size_t FirstWrite=1024; + Dest.Write(Buf,FirstWrite); + Buf+=FirstWrite; + WriteSize-=FirstWrite; + } +#endif + Dest.Write(Buf,WriteSize); + CopySize+=ReadSize; + if (!CopyAll) + Length-=ReadSize; + } + return CopySize; +} +#endif diff --git a/reference/unrar/file.hpp b/reference/unrar/file.hpp new file mode 100644 index 00000000..8a7631e3 --- /dev/null +++ b/reference/unrar/file.hpp @@ -0,0 +1,126 @@ +#ifndef _RAR_FILE_ +#define _RAR_FILE_ + +#define FILE_USE_OPEN + +#ifdef _WIN_ALL + typedef HANDLE FileHandle; + #define FILE_BAD_HANDLE INVALID_HANDLE_VALUE +#elif defined(FILE_USE_OPEN) + typedef off_t FileHandle; + #define FILE_BAD_HANDLE -1 +#else + typedef FILE* FileHandle; + #define FILE_BAD_HANDLE NULL +#endif + +class RAROptions; + +enum FILE_HANDLETYPE {FILE_HANDLENORMAL,FILE_HANDLESTD}; + +enum FILE_ERRORTYPE {FILE_SUCCESS,FILE_NOTFOUND,FILE_READERROR}; + +enum FILE_MODE_FLAGS { + // Request read only access to file. Default for Open. + FMF_READ=0, + + // Request both read and write access to file. Default for Create. + FMF_UPDATE=1, + + // Request write only access to file. + FMF_WRITE=2, + + // Open files which are already opened for write by other programs. + FMF_OPENSHARED=4, + + // Open files only if no other program is opened it even in shared mode. + FMF_OPENEXCLUSIVE=8, + + // Provide read access to created file for other programs. + FMF_SHAREREAD=16, + + // Use standard NTFS names without trailing dots and spaces. + FMF_STANDARDNAMES=32, + + // Mode flags are not defined yet. + FMF_UNDEFINED=256 +}; + + +class File +{ + private: + FileHandle hFile; + bool LastWrite; + FILE_HANDLETYPE HandleType; + bool SkipClose; + bool IgnoreReadErrors; + bool NewFile; + bool AllowDelete; + bool AllowExceptions; +#ifdef _WIN_ALL + bool NoSequentialRead; + uint CreateMode; +#endif + protected: + bool OpenShared; // Set by 'Archive' class. + public: + wchar FileName[NM]; + + FILE_ERRORTYPE ErrorType; + public: + File(); + virtual ~File(); + void operator = (File &SrcFile); + virtual bool Open(const wchar *Name,uint Mode=FMF_READ); + void TOpen(const wchar *Name); + bool WOpen(const wchar *Name); + bool Create(const wchar *Name,uint Mode=FMF_UPDATE|FMF_SHAREREAD); + void TCreate(const wchar *Name,uint Mode=FMF_UPDATE|FMF_SHAREREAD); + bool WCreate(const wchar *Name,uint Mode=FMF_UPDATE|FMF_SHAREREAD); + bool Close(); + bool Delete(); + bool Rename(const wchar *NewName); + bool Write(const void *Data,size_t Size); + virtual int Read(void *Data,size_t Size); + int DirectRead(void *Data,size_t Size); + virtual void Seek(int64 Offset,int Method); + bool RawSeek(int64 Offset,int Method); + virtual int64 Tell(); + void Prealloc(int64 Size); + byte GetByte(); + void PutByte(byte Byte); + bool Truncate(); + void Flush(); + void SetOpenFileTime(RarTime *ftm,RarTime *ftc=NULL,RarTime *fta=NULL); + void SetCloseFileTime(RarTime *ftm,RarTime *fta=NULL); + static void SetCloseFileTimeByName(const wchar *Name,RarTime *ftm,RarTime *fta); + void GetOpenFileTime(RarTime *ft); + bool IsOpened() {return hFile!=FILE_BAD_HANDLE;}; + int64 FileLength(); + void SetHandleType(FILE_HANDLETYPE Type) {HandleType=Type;} + FILE_HANDLETYPE GetHandleType() {return HandleType;} + bool IsDevice(); + static bool RemoveCreated(); + FileHandle GetHandle() {return hFile;} + void SetHandle(FileHandle Handle) {Close();hFile=Handle;} + void SetIgnoreReadErrors(bool Mode) {IgnoreReadErrors=Mode;} + int64 Copy(File &Dest,int64 Length=INT64NDF); + void SetAllowDelete(bool Allow) {AllowDelete=Allow;} + void SetExceptions(bool Allow) {AllowExceptions=Allow;} +#ifdef _WIN_ALL + void RemoveSequentialFlag() {NoSequentialRead=true;} +#endif +#ifdef _UNIX + int GetFD() + { +#ifdef FILE_USE_OPEN + return hFile; +#else + return fileno(hFile); +#endif + } +#endif +}; + +#endif diff --git a/reference/unrar/filefn.cpp b/reference/unrar/filefn.cpp new file mode 100644 index 00000000..4eb7b9b2 --- /dev/null +++ b/reference/unrar/filefn.cpp @@ -0,0 +1,510 @@ +#include "rar.hpp" + +MKDIR_CODE MakeDir(const wchar *Name,bool SetAttr,uint Attr) +{ +#ifdef _WIN_ALL + // Windows automatically removes dots and spaces in the end of directory + // name. So we detect such names and process them with \\?\ prefix. + wchar *LastChar=PointToLastChar(Name); + bool Special=*LastChar=='.' || *LastChar==' '; + BOOL RetCode=Special ? FALSE : CreateDirectory(Name,NULL); + if (RetCode==0 && !FileExist(Name)) + { + wchar LongName[NM]; + if (GetWinLongPath(Name,LongName,ASIZE(LongName))) + RetCode=CreateDirectory(LongName,NULL); + } + if (RetCode!=0) // Non-zero return code means success for CreateDirectory. + { + if (SetAttr) + SetFileAttr(Name,Attr); + return MKDIR_SUCCESS; + } + int ErrCode=GetLastError(); + if (ErrCode==ERROR_FILE_NOT_FOUND || ErrCode==ERROR_PATH_NOT_FOUND) + return MKDIR_BADPATH; + return MKDIR_ERROR; +#elif defined(_UNIX) + char NameA[NM]; + WideToChar(Name,NameA,ASIZE(NameA)); + mode_t uattr=SetAttr ? (mode_t)Attr:0777; + int ErrCode=mkdir(NameA,uattr); + if (ErrCode==-1) + return errno==ENOENT ? MKDIR_BADPATH:MKDIR_ERROR; + return MKDIR_SUCCESS; +#else + return MKDIR_ERROR; +#endif +} + + +bool CreatePath(const wchar *Path,bool SkipLastName) +{ + if (Path==NULL || *Path==0) + return false; + +#if defined(_WIN_ALL) || defined(_EMX) + uint DirAttr=0; +#else + uint DirAttr=0777; +#endif + + bool Success=true; + + for (const wchar *s=Path;*s!=0;s++) + { + wchar DirName[NM]; + if (s-Path>=ASIZE(DirName)) + break; + + // Process all kinds of path separators, so user can enter Unix style + // path in Windows or Windows in Unix. s>Path check avoids attempting + // creating an empty directory for paths starting from path separator. + if (IsPathDiv(*s) && s>Path) + { +#ifdef _WIN_ALL + // We must not attempt to create "D:" directory, because first + // CreateDirectory will fail, so we'll use \\?\D:, which forces Wine + // to create "D:" directory. + if (s==Path+2 && Path[1]==':') + continue; +#endif + wcsncpy(DirName,Path,s-Path); + DirName[s-Path]=0; + + Success=MakeDir(DirName,true,DirAttr)==MKDIR_SUCCESS; + if (Success) + { + mprintf(St(MCreatDir),DirName); + mprintf(L" %s",St(MOk)); + } + } + } + if (!SkipLastName && !IsPathDiv(*PointToLastChar(Path))) + Success=MakeDir(Path,true,DirAttr)==MKDIR_SUCCESS; + return Success; +} + + +void SetDirTime(const wchar *Name,RarTime *ftm,RarTime *ftc,RarTime *fta) +{ +#if defined(_WIN_ALL) + bool sm=ftm!=NULL && ftm->IsSet(); + bool sc=ftc!=NULL && ftc->IsSet(); + bool sa=fta!=NULL && fta->IsSet(); + + uint DirAttr=GetFileAttr(Name); + bool ResetAttr=(DirAttr!=0xffffffff && (DirAttr & FILE_ATTRIBUTE_READONLY)!=0); + if (ResetAttr) + SetFileAttr(Name,0); + + HANDLE hFile=CreateFile(Name,GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE, + NULL,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,NULL); + if (hFile==INVALID_HANDLE_VALUE) + { + wchar LongName[NM]; + if (GetWinLongPath(Name,LongName,ASIZE(LongName))) + hFile=CreateFile(LongName,GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE, + NULL,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,NULL); + } + + if (hFile==INVALID_HANDLE_VALUE) + return; + FILETIME fm,fc,fa; + if (sm) + ftm->GetWinFT(&fm); + if (sc) + ftc->GetWinFT(&fc); + if (sa) + fta->GetWinFT(&fa); + SetFileTime(hFile,sc ? &fc:NULL,sa ? &fa:NULL,sm ? &fm:NULL); + CloseHandle(hFile); + if (ResetAttr) + SetFileAttr(Name,DirAttr); +#endif +#if defined(_UNIX) || defined(_EMX) + File::SetCloseFileTimeByName(Name,ftm,fta); +#endif +} + + +bool IsRemovable(const wchar *Name) +{ +#if defined(_WIN_ALL) + wchar Root[NM]; + GetPathRoot(Name,Root,ASIZE(Root)); + int Type=GetDriveType(*Root!=0 ? Root:NULL); + return Type==DRIVE_REMOVABLE || Type==DRIVE_CDROM; +#else + return false; +#endif +} + + +#ifndef SFX_MODULE +int64 GetFreeDisk(const wchar *Name) +{ +#ifdef _WIN_ALL + wchar Root[NM]; + GetFilePath(Name,Root,ASIZE(Root)); + + ULARGE_INTEGER uiTotalSize,uiTotalFree,uiUserFree; + uiUserFree.u.LowPart=uiUserFree.u.HighPart=0; + if (GetDiskFreeSpaceEx(*Root!=0 ? Root:NULL,&uiUserFree,&uiTotalSize,&uiTotalFree) && + uiUserFree.u.HighPart<=uiTotalFree.u.HighPart) + return INT32TO64(uiUserFree.u.HighPart,uiUserFree.u.LowPart); + return 0; +#elif defined(_UNIX) + wchar Root[NM]; + GetFilePath(Name,Root,ASIZE(Root)); + char RootA[NM]; + WideToChar(Root,RootA,ASIZE(RootA)); + struct statvfs sfs; + if (statvfs(*RootA!=0 ? RootA:".",&sfs)!=0) + return 0; + int64 FreeSize=sfs.f_bsize; + FreeSize=FreeSize*sfs.f_bavail; + return FreeSize; +#else + return 0; +#endif +} +#endif + + +#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT) +// Return 'true' for FAT and FAT32, so we can adjust the maximum supported +// file size to 4 GB for these file systems. +bool IsFAT(const wchar *Name) +{ + wchar Root[NM]; + GetPathRoot(Name,Root,ASIZE(Root)); + wchar FileSystem[MAX_PATH+1]; + if (GetVolumeInformation(Root,NULL,0,NULL,NULL,NULL,FileSystem,ASIZE(FileSystem))) + return wcscmp(FileSystem,L"FAT")==0 || wcscmp(FileSystem,L"FAT32")==0; + return false; +} +#endif + + +bool FileExist(const wchar *Name) +{ +#ifdef _WIN_ALL + return GetFileAttr(Name)!=0xffffffff; +#elif defined(ENABLE_ACCESS) + char NameA[NM]; + WideToChar(Name,NameA,ASIZE(NameA)); + return access(NameA,0)==0; +#else + FindData FD; + return FindFile::FastFind(Name,&FD); +#endif +} + + +bool WildFileExist(const wchar *Name) +{ + if (IsWildcard(Name)) + { + FindFile Find; + Find.SetMask(Name); + FindData fd; + return Find.Next(&fd); + } + return FileExist(Name); +} + + +bool IsDir(uint Attr) +{ +#ifdef _WIN_ALL + return Attr!=0xffffffff && (Attr & FILE_ATTRIBUTE_DIRECTORY)!=0; +#endif +#if defined(_UNIX) + return (Attr & 0xF000)==0x4000; +#endif +} + + +bool IsUnreadable(uint Attr) +{ +#if defined(_UNIX) && defined(S_ISFIFO) && defined(S_ISSOCK) && defined(S_ISCHR) + return S_ISFIFO(Attr) || S_ISSOCK(Attr) || S_ISCHR(Attr); +#endif + return false; +} + + +bool IsLink(uint Attr) +{ +#ifdef _UNIX + return (Attr & 0xF000)==0xA000; +#elif defined(_WIN_ALL) + return (Attr & FILE_ATTRIBUTE_REPARSE_POINT)!=0; +#else + return false; +#endif +} + + + + + + +bool IsDeleteAllowed(uint FileAttr) +{ +#ifdef _WIN_ALL + return (FileAttr & (FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_HIDDEN))==0; +#else + return (FileAttr & (S_IRUSR|S_IWUSR))==(S_IRUSR|S_IWUSR); +#endif +} + + +void PrepareToDelete(const wchar *Name) +{ +#if defined(_WIN_ALL) || defined(_EMX) + SetFileAttr(Name,0); +#endif +#ifdef _UNIX + if (Name!=NULL) + { + char NameA[NM]; + WideToChar(Name,NameA,ASIZE(NameA)); + chmod(NameA,S_IRUSR|S_IWUSR|S_IXUSR); + } +#endif +} + + +uint GetFileAttr(const wchar *Name) +{ +#ifdef _WIN_ALL + DWORD Attr=GetFileAttributes(Name); + if (Attr==0xffffffff) + { + wchar LongName[NM]; + if (GetWinLongPath(Name,LongName,ASIZE(LongName))) + Attr=GetFileAttributes(LongName); + } + return Attr; +#else + char NameA[NM]; + WideToChar(Name,NameA,ASIZE(NameA)); + struct stat st; + if (stat(NameA,&st)!=0) + return 0; + return st.st_mode; +#endif +} + + +bool SetFileAttr(const wchar *Name,uint Attr) +{ +#ifdef _WIN_ALL + bool Success=SetFileAttributes(Name,Attr)!=0; + if (!Success) + { + wchar LongName[NM]; + if (GetWinLongPath(Name,LongName,ASIZE(LongName))) + Success=SetFileAttributes(LongName,Attr)!=0; + } + return Success; +#elif defined(_UNIX) + char NameA[NM]; + WideToChar(Name,NameA,ASIZE(NameA)); + return chmod(NameA,(mode_t)Attr)==0; +#else + return false; +#endif +} + + +#if 0 +wchar *MkTemp(wchar *Name,size_t MaxSize) +{ + size_t Length=wcslen(Name); + + RarTime CurTime; + CurTime.SetCurrentTime(); + + // We cannot use CurTime.GetWin() as is, because its lowest bits can + // have low informational value, like being a zero or few fixed numbers. + uint Random=(uint)(CurTime.GetWin()/100000); + + // Using PID we guarantee that different RAR copies use different temp names + // even if started in exactly the same time. + uint PID=0; +#ifdef _WIN_ALL + PID=(uint)GetCurrentProcessId(); +#elif defined(_UNIX) + PID=(uint)getpid(); +#endif + + for (uint Attempt=0;;Attempt++) + { + uint Ext=Random%50000+Attempt; + wchar RndText[50]; + swprintf(RndText,ASIZE(RndText),L"%u.%03u",PID,Ext); + if (Length+wcslen(RndText)>=MaxSize || Attempt==1000) + return NULL; + wcscpy(Name+Length,RndText); + if (!FileExist(Name)) + break; + } + return Name; +} +#endif + + +#if !defined(SFX_MODULE) +void CalcFileSum(File *SrcFile,uint *CRC32,byte *Blake2,uint Threads,int64 Size,uint Flags) +{ + SaveFilePos SavePos(*SrcFile); +#ifndef SILENT + int64 FileLength=Size==INT64NDF ? SrcFile->FileLength() : Size; +#endif + + if ((Flags & (CALCFSUM_SHOWTEXT|CALCFSUM_SHOWPERCENT))!=0) + uiMsg(UIEVENT_FILESUMSTART); + + if ((Flags & CALCFSUM_CURPOS)==0) + SrcFile->Seek(0,SEEK_SET); + + const size_t BufSize=0x100000; + Array Data(BufSize); + + + DataHash HashCRC,HashBlake2; + HashCRC.Init(HASH_CRC32,Threads); + HashBlake2.Init(HASH_BLAKE2,Threads); + + int64 BlockCount=0; + int64 TotalRead=0; + while (true) + { + size_t SizeToRead; + if (Size==INT64NDF) // If we process the entire file. + SizeToRead=BufSize; // Then always attempt to read the entire buffer. + else + SizeToRead=(size_t)Min((int64)BufSize,Size); + int ReadSize=SrcFile->Read(&Data[0],SizeToRead); + if (ReadSize==0) + break; + TotalRead+=ReadSize; + + if ((++BlockCount & 0xf)==0) + { +#ifndef SILENT + if ((Flags & CALCFSUM_SHOWPROGRESS)!=0) + uiExtractProgress(TotalRead,FileLength,TotalRead,FileLength); + else + { + if ((Flags & CALCFSUM_SHOWPERCENT)!=0) + uiMsg(UIEVENT_FILESUMPROGRESS,ToPercent(TotalRead,FileLength)); + } +#endif + Wait(); + } + + if (CRC32!=NULL) + HashCRC.Update(&Data[0],ReadSize); + if (Blake2!=NULL) + HashBlake2.Update(&Data[0],ReadSize); + + if (Size!=INT64NDF) + Size-=ReadSize; + } + if ((Flags & CALCFSUM_SHOWPERCENT)!=0) + uiMsg(UIEVENT_FILESUMEND); + + if (CRC32!=NULL) + *CRC32=HashCRC.GetCRC32(); + if (Blake2!=NULL) + { + HashValue Result; + HashBlake2.Result(&Result); + memcpy(Blake2,Result.Digest,sizeof(Result.Digest)); + } +} +#endif + + +bool RenameFile(const wchar *SrcName,const wchar *DestName) +{ +#ifdef _WIN_ALL + bool Success=MoveFile(SrcName,DestName)!=0; + if (!Success) + { + wchar LongName1[NM],LongName2[NM]; + if (GetWinLongPath(SrcName,LongName1,ASIZE(LongName1)) && + GetWinLongPath(DestName,LongName2,ASIZE(LongName2))) + Success=MoveFile(LongName1,LongName2)!=0; + } + return Success; +#else + char SrcNameA[NM],DestNameA[NM]; + WideToChar(SrcName,SrcNameA,ASIZE(SrcNameA)); + WideToChar(DestName,DestNameA,ASIZE(DestNameA)); + bool Success=rename(SrcNameA,DestNameA)==0; + return Success; +#endif +} + + +bool DelFile(const wchar *Name) +{ +#ifdef _WIN_ALL + bool Success=DeleteFile(Name)!=0; + if (!Success) + { + wchar LongName[NM]; + if (GetWinLongPath(Name,LongName,ASIZE(LongName))) + Success=DeleteFile(LongName)!=0; + } + return Success; +#else + char NameA[NM]; + WideToChar(Name,NameA,ASIZE(NameA)); + bool Success=remove(NameA)==0; + return Success; +#endif +} + + + + +#if defined(_WIN_ALL) && !defined(SFX_MODULE) +bool SetFileCompression(const wchar *Name,bool State) +{ + HANDLE hFile=CreateFile(Name,FILE_READ_DATA|FILE_WRITE_DATA, + FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_SEQUENTIAL_SCAN,NULL); + if (hFile==INVALID_HANDLE_VALUE) + { + wchar LongName[NM]; + if (GetWinLongPath(Name,LongName,ASIZE(LongName))) + hFile=CreateFile(LongName,FILE_READ_DATA|FILE_WRITE_DATA, + FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_SEQUENTIAL_SCAN,NULL); + } + if (hFile==INVALID_HANDLE_VALUE) + return false; + SHORT NewState=State ? COMPRESSION_FORMAT_DEFAULT:COMPRESSION_FORMAT_NONE; + DWORD Result; + int RetCode=DeviceIoControl(hFile,FSCTL_SET_COMPRESSION,&NewState, + sizeof(NewState),NULL,0,&Result,NULL); + CloseHandle(hFile); + return RetCode!=0; +} +#endif + + + + + + + + + + diff --git a/reference/unrar/filefn.hpp b/reference/unrar/filefn.hpp new file mode 100644 index 00000000..1bda9b58 --- /dev/null +++ b/reference/unrar/filefn.hpp @@ -0,0 +1,50 @@ +#ifndef _RAR_FILEFN_ +#define _RAR_FILEFN_ + +enum MKDIR_CODE {MKDIR_SUCCESS,MKDIR_ERROR,MKDIR_BADPATH}; + +MKDIR_CODE MakeDir(const wchar *Name,bool SetAttr,uint Attr); +bool CreatePath(const wchar *Path,bool SkipLastName); +void SetDirTime(const wchar *Name,RarTime *ftm,RarTime *ftc,RarTime *fta); +bool IsRemovable(const wchar *Name); + +#ifndef SFX_MODULE +int64 GetFreeDisk(const wchar *Name); +#endif + +#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT) +bool IsFAT(const wchar *Root); +#endif + +bool FileExist(const wchar *Name); +bool WildFileExist(const wchar *Name); +bool IsDir(uint Attr); +bool IsUnreadable(uint Attr); +bool IsLink(uint Attr); +void SetSFXMode(const wchar *FileName); +void EraseDiskContents(const wchar *FileName); +bool IsDeleteAllowed(uint FileAttr); +void PrepareToDelete(const wchar *Name); +uint GetFileAttr(const wchar *Name); +bool SetFileAttr(const wchar *Name,uint Attr); +#if 0 +wchar* MkTemp(wchar *Name,size_t MaxSize); +#endif + +enum CALCFSUM_FLAGS {CALCFSUM_SHOWTEXT=1,CALCFSUM_SHOWPERCENT=2,CALCFSUM_SHOWPROGRESS=4,CALCFSUM_CURPOS=8}; + +void CalcFileSum(File *SrcFile,uint *CRC32,byte *Blake2,uint Threads,int64 Size=INT64NDF,uint Flags=0); + +bool RenameFile(const wchar *SrcName,const wchar *DestName); +bool DelFile(const wchar *Name); +bool DelDir(const wchar *Name); + +#if defined(_WIN_ALL) && !defined(SFX_MODULE) +bool SetFileCompression(const wchar *Name,bool State); +#endif + + + + + +#endif diff --git a/reference/unrar/filestr.cpp b/reference/unrar/filestr.cpp new file mode 100644 index 00000000..4a53132f --- /dev/null +++ b/reference/unrar/filestr.cpp @@ -0,0 +1,162 @@ +#include "rar.hpp" + +bool ReadTextFile( + const wchar *Name, + StringList *List, + bool Config, + bool AbortOnError, + RAR_CHARSET SrcCharset, + bool Unquote, + bool SkipComments, + bool ExpandEnvStr) +{ + wchar FileName[NM]; + *FileName=0; + + if (Name!=NULL) + if (Config) + GetConfigName(Name,FileName,ASIZE(FileName),true,false); + else + wcsncpyz(FileName,Name,ASIZE(FileName)); + + File SrcFile; + if (*FileName!=0) + { + bool OpenCode=AbortOnError ? SrcFile.WOpen(FileName):SrcFile.Open(FileName,0); + + if (!OpenCode) + { + if (AbortOnError) + ErrHandler.Exit(RARX_OPEN); + return false; + } + } + else + SrcFile.SetHandleType(FILE_HANDLESTD); + + uint DataSize=0,ReadSize; + const int ReadBlock=4096; + + Array Data(ReadBlock); + while ((ReadSize=SrcFile.Read(&Data[DataSize],ReadBlock))!=0) + { + DataSize+=ReadSize; + Data.Add(ReadSize); // Always have ReadBlock available for next data. + } + // Set to really read size, so we can zero terminate it correctly. + Data.Alloc(DataSize); + + int LowEndian=DataSize>=2 && Data[0]==255 && Data[1]==254 ? 1:0; + int BigEndian=DataSize>=2 && Data[0]==254 && Data[1]==255 ? 1:0; + bool Utf8=DataSize>=3 && Data[0]==0xef && Data[1]==0xbb && Data[2]==0xbf; + + if (SrcCharset==RCH_DEFAULT) + { + if (LowEndian || BigEndian) + for (size_t I=2;I DataW; + + if (SrcCharset==RCH_DEFAULT || SrcCharset==RCH_OEM || SrcCharset==RCH_ANSI) + { + Data.Push(0); // Zero terminate. +#if defined(_WIN_ALL) + if (SrcCharset==RCH_OEM) + OemToCharA((char *)&Data[0],(char *)&Data[0]); +#endif + DataW.Alloc(Data.Size()); + CharToWide((char *)&Data[0],&DataW[0],DataW.Size()); + } + + if (SrcCharset==RCH_UNICODE) + { + size_t Start=2; // Skip byte order mark. + if (!LowEndian && !BigEndian) // No byte order mask. + { + Start=0; + LowEndian=1; + } + + DataW.Alloc(Data.Size()/2+1); + size_t End=Data.Size() & ~1; // We need even bytes number for UTF-16. + for (size_t I=Start;I=CurStr;SpacePtr--) + { + if (*SpacePtr!=' ' && *SpacePtr!='\t') + break; + *SpacePtr=0; + } + + if (Unquote && *CurStr=='\"') + { + size_t Length=wcslen(CurStr); + if (CurStr[Length-1]=='\"') + { + CurStr[Length-1]=0; + CurStr++; + } + } + + bool Expanded=false; +#if defined(_WIN_ALL) + if (ExpandEnvStr && *CurStr=='%') // Expand environment variables in Windows. + { + wchar ExpName[NM]; + *ExpName=0; + DWORD Result=ExpandEnvironmentStrings(CurStr,ExpName,ASIZE(ExpName)); + Expanded=Result!=0 && ResultAddString(ExpName); + } +#endif + if (!Expanded && *CurStr!=0) + List->AddString(CurStr); + + if (Done) + break; + CurStr=NextStr+1; + while (*CurStr=='\r' || *CurStr=='\n') + CurStr++; + } + return true; +} diff --git a/reference/unrar/filestr.hpp b/reference/unrar/filestr.hpp new file mode 100644 index 00000000..8dfdf64b --- /dev/null +++ b/reference/unrar/filestr.hpp @@ -0,0 +1,15 @@ +#ifndef _RAR_FILESTR_ +#define _RAR_FILESTR_ + +bool ReadTextFile( + const wchar *Name, + StringList *List, + bool Config, + bool AbortOnError=false, + RAR_CHARSET SrcCharset=RCH_DEFAULT, + bool Unquote=false, + bool SkipComments=false, + bool ExpandEnvStr=false +); + +#endif diff --git a/reference/unrar/find.cpp b/reference/unrar/find.cpp new file mode 100644 index 00000000..812af6b3 --- /dev/null +++ b/reference/unrar/find.cpp @@ -0,0 +1,218 @@ +#include "rar.hpp" + +FindFile::FindFile() +{ + *FindMask=0; + FirstCall=true; +#ifdef _WIN_ALL + hFind=INVALID_HANDLE_VALUE; +#else + dirp=NULL; +#endif +} + + +FindFile::~FindFile() +{ +#ifdef _WIN_ALL + if (hFind!=INVALID_HANDLE_VALUE) + FindClose(hFind); +#else + if (dirp!=NULL) + closedir(dirp); +#endif +} + + +void FindFile::SetMask(const wchar *Mask) +{ + wcscpy(FindMask,Mask); + FirstCall=true; +} + + +bool FindFile::Next(FindData *fd,bool GetSymLink) +{ + fd->Error=false; + if (*FindMask==0) + return false; +#ifdef _WIN_ALL + if (FirstCall) + { + if ((hFind=Win32Find(INVALID_HANDLE_VALUE,FindMask,fd))==INVALID_HANDLE_VALUE) + return false; + } + else + if (Win32Find(hFind,FindMask,fd)==INVALID_HANDLE_VALUE) + return false; +#else + if (FirstCall) + { + wchar DirName[NM]; + wcsncpyz(DirName,FindMask,ASIZE(DirName)); + RemoveNameFromPath(DirName); + if (*DirName==0) + wcscpy(DirName,L"."); + char DirNameA[NM]; + WideToChar(DirName,DirNameA,ASIZE(DirNameA)); + if ((dirp=opendir(DirNameA))==NULL) + { + fd->Error=(errno!=ENOENT); + return false; + } + } + while (1) + { + struct dirent *ent=readdir(dirp); + if (ent==NULL) + return false; + if (strcmp(ent->d_name,".")==0 || strcmp(ent->d_name,"..")==0) + continue; + wchar Name[NM]; + if (!CharToWide(ent->d_name,Name,ASIZE(Name))) + uiMsg(UIERROR_INVALIDNAME,UINULL,Name); + + if (CmpName(FindMask,Name,MATCH_NAMES)) + { + wchar FullName[NM]; + wcscpy(FullName,FindMask); + *PointToName(FullName)=0; + if (wcslen(FullName)+wcslen(Name)>=ASIZE(FullName)-1) + { + uiMsg(UIERROR_PATHTOOLONG,FullName,L"",Name); + return false; + } + wcscat(FullName,Name); + if (!FastFind(FullName,fd,GetSymLink)) + { + ErrHandler.OpenErrorMsg(FullName); + continue; + } + wcscpy(fd->Name,FullName); + break; + } + } +#endif + fd->Flags=0; + fd->IsDir=IsDir(fd->FileAttr); + fd->IsLink=IsLink(fd->FileAttr); + + FirstCall=false; + wchar *NameOnly=PointToName(fd->Name); + if (wcscmp(NameOnly,L".")==0 || wcscmp(NameOnly,L"..")==0) + return Next(fd); + return true; +} + + +bool FindFile::FastFind(const wchar *FindMask,FindData *fd,bool GetSymLink) +{ + fd->Error=false; +#ifndef _UNIX + if (IsWildcard(FindMask)) + return false; +#endif +#ifdef _WIN_ALL + HANDLE hFind=Win32Find(INVALID_HANDLE_VALUE,FindMask,fd); + if (hFind==INVALID_HANDLE_VALUE) + return false; + FindClose(hFind); +#else + char FindMaskA[NM]; + WideToChar(FindMask,FindMaskA,ASIZE(FindMaskA)); + + struct stat st; + if (GetSymLink) + { +#ifdef SAVE_LINKS + if (lstat(FindMaskA,&st)!=0) +#else + if (stat(FindMaskA,&st)!=0) +#endif + { + fd->Error=(errno!=ENOENT); + return false; + } + } + else + if (stat(FindMaskA,&st)!=0) + { + fd->Error=(errno!=ENOENT); + return false; + } + fd->FileAttr=st.st_mode; + fd->Size=st.st_size; + +#ifdef UNIX_TIME_NS + fd->mtime.SetUnixNS(st.st_mtim.tv_sec*(uint64)1000000000+st.st_mtim.tv_nsec); + fd->atime.SetUnixNS(st.st_atim.tv_sec*(uint64)1000000000+st.st_atim.tv_nsec); + fd->ctime.SetUnixNS(st.st_ctim.tv_sec*(uint64)1000000000+st.st_ctim.tv_nsec); +#else + fd->mtime.SetUnix(st.st_mtime); + fd->atime.SetUnix(st.st_atime); + fd->ctime.SetUnix(st.st_ctime); +#endif + + wcsncpyz(fd->Name,FindMask,ASIZE(fd->Name)); +#endif + fd->Flags=0; + fd->IsDir=IsDir(fd->FileAttr); + fd->IsLink=IsLink(fd->FileAttr); + + return true; +} + + +#ifdef _WIN_ALL +HANDLE FindFile::Win32Find(HANDLE hFind,const wchar *Mask,FindData *fd) +{ + WIN32_FIND_DATA FindData; + if (hFind==INVALID_HANDLE_VALUE) + { + hFind=FindFirstFile(Mask,&FindData); + if (hFind==INVALID_HANDLE_VALUE) + { + wchar LongMask[NM]; + if (GetWinLongPath(Mask,LongMask,ASIZE(LongMask))) + hFind=FindFirstFile(LongMask,&FindData); + } + if (hFind==INVALID_HANDLE_VALUE) + { + int SysErr=GetLastError(); + // We must not issue an error for "file not found" and "path not found", + // because it is normal to not find anything for wildcard mask when + // archiving. Also searching for non-existent file is normal in some + // other modules, like WinRAR scanning for winrar_theme_description.txt + // to check if any themes are available. + fd->Error=SysErr!=ERROR_FILE_NOT_FOUND && + SysErr!=ERROR_PATH_NOT_FOUND && + SysErr!=ERROR_NO_MORE_FILES; + } + } + else + if (!FindNextFile(hFind,&FindData)) + { + hFind=INVALID_HANDLE_VALUE; + fd->Error=GetLastError()!=ERROR_NO_MORE_FILES; + } + + if (hFind!=INVALID_HANDLE_VALUE) + { + wcsncpyz(fd->Name,Mask,ASIZE(fd->Name)); + SetName(fd->Name,FindData.cFileName,ASIZE(fd->Name)); + fd->Size=INT32TO64(FindData.nFileSizeHigh,FindData.nFileSizeLow); + fd->FileAttr=FindData.dwFileAttributes; + fd->ftCreationTime=FindData.ftCreationTime; + fd->ftLastAccessTime=FindData.ftLastAccessTime; + fd->ftLastWriteTime=FindData.ftLastWriteTime; + fd->mtime.SetWinFT(&FindData.ftLastWriteTime); + fd->ctime.SetWinFT(&FindData.ftCreationTime); + fd->atime.SetWinFT(&FindData.ftLastAccessTime); + + + } + fd->Flags=0; + return hFind; +} +#endif + diff --git a/reference/unrar/find.hpp b/reference/unrar/find.hpp new file mode 100644 index 00000000..250637f8 --- /dev/null +++ b/reference/unrar/find.hpp @@ -0,0 +1,49 @@ +#ifndef _RAR_FINDDATA_ +#define _RAR_FINDDATA_ + +enum FINDDATA_FLAGS { + FDDF_SECONDDIR=1 // Second encounter of same directory in SCAN_GETDIRSTWICE ScanTree mode. +}; + +struct FindData +{ + wchar Name[NM]; + uint64 Size; + uint FileAttr; + bool IsDir; + bool IsLink; + RarTime mtime; + RarTime ctime; + RarTime atime; +#ifdef _WIN_ALL + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; +#endif + uint Flags; + bool Error; +}; + +class FindFile +{ + private: +#ifdef _WIN_ALL + static HANDLE Win32Find(HANDLE hFind,const wchar *Mask,FindData *fd); +#endif + + wchar FindMask[NM]; + bool FirstCall; +#ifdef _WIN_ALL + HANDLE hFind; +#else + DIR *dirp; +#endif + public: + FindFile(); + ~FindFile(); + void SetMask(const wchar *Mask); + bool Next(FindData *fd,bool GetSymLink=false); + static bool FastFind(const wchar *FindMask,FindData *fd,bool GetSymLink=false); +}; + +#endif diff --git a/reference/unrar/getbits.cpp b/reference/unrar/getbits.cpp new file mode 100644 index 00000000..e4db2695 --- /dev/null +++ b/reference/unrar/getbits.cpp @@ -0,0 +1,52 @@ +#include "rar.hpp" + +BitInput::BitInput(bool AllocBuffer) +{ + ExternalBuffer=false; + if (AllocBuffer) + { + // getbits32 attempts to read data from InAddr, ... InAddr+3 positions. + // So let's allocate 3 additional bytes for situation, when we need to + // read only 1 byte from the last position of buffer and avoid a crash + // from access to next 3 bytes, which contents we do not need. + size_t BufSize=MAX_SIZE+3; + InBuf=new byte[BufSize]; + + // Ensure that we get predictable results when accessing bytes in area + // not filled with read data. + memset(InBuf,0,BufSize); + } + else + InBuf=NULL; +} + + +BitInput::~BitInput() +{ + if (!ExternalBuffer) + delete[] InBuf; +} + + +void BitInput::faddbits(uint Bits) +{ + // Function wrapped version of inline addbits to save code size. + addbits(Bits); +} + + +uint BitInput::fgetbits() +{ + // Function wrapped version of inline getbits to save code size. + return getbits(); +} + + +void BitInput::SetExternalBuffer(byte *Buf) +{ + if (InBuf!=NULL && !ExternalBuffer) + delete[] InBuf; + InBuf=Buf; + ExternalBuffer=true; +} + diff --git a/reference/unrar/getbits.hpp b/reference/unrar/getbits.hpp new file mode 100644 index 00000000..2e151da9 --- /dev/null +++ b/reference/unrar/getbits.hpp @@ -0,0 +1,68 @@ +#ifndef _RAR_GETBITS_ +#define _RAR_GETBITS_ + +class BitInput +{ + public: + enum BufferSize {MAX_SIZE=0x8000}; // Size of input buffer. + + int InAddr; // Curent byte position in the buffer. + int InBit; // Current bit position in the current byte. + + bool ExternalBuffer; + public: + BitInput(bool AllocBuffer); + ~BitInput(); + + byte *InBuf; // Dynamically allocated input buffer. + + void InitBitInput() + { + InAddr=InBit=0; + } + + // Move forward by 'Bits' bits. + void addbits(uint Bits) + { + Bits+=InBit; + InAddr+=Bits>>3; + InBit=Bits&7; + } + + // Return 16 bits from current position in the buffer. + // Bit at (InAddr,InBit) has the highest position in returning data. + uint getbits() + { + uint BitField=(uint)InBuf[InAddr] << 16; + BitField|=(uint)InBuf[InAddr+1] << 8; + BitField|=(uint)InBuf[InAddr+2]; + BitField >>= (8-InBit); + return BitField & 0xffff; + } + + // Return 32 bits from current position in the buffer. + // Bit at (InAddr,InBit) has the highest position in returning data. + uint getbits32() + { + uint BitField=(uint)InBuf[InAddr] << 24; + BitField|=(uint)InBuf[InAddr+1] << 16; + BitField|=(uint)InBuf[InAddr+2] << 8; + BitField|=(uint)InBuf[InAddr+3]; + BitField <<= InBit; + BitField|=(uint)InBuf[InAddr+4] >> (8-InBit); + return BitField & 0xffffffff; + } + + void faddbits(uint Bits); + uint fgetbits(); + + // Check if buffer has enough space for IncPtr bytes. Returns 'true' + // if buffer will be overflown. + bool Overflow(uint IncPtr) + { + return InAddr+IncPtr>=MAX_SIZE; + } + + void SetExternalBuffer(byte *Buf); +}; +#endif diff --git a/reference/unrar/global.cpp b/reference/unrar/global.cpp new file mode 100644 index 00000000..3975813a --- /dev/null +++ b/reference/unrar/global.cpp @@ -0,0 +1,7 @@ +#define INCLUDEGLOBAL + +#if defined(__BORLANDC__) || defined(_MSC_VER) +#pragma hdrstop +#endif + +#include "rar.hpp" diff --git a/reference/unrar/global.hpp b/reference/unrar/global.hpp new file mode 100644 index 00000000..35c6cf91 --- /dev/null +++ b/reference/unrar/global.hpp @@ -0,0 +1,14 @@ +#ifndef _RAR_GLOBAL_ +#define _RAR_GLOBAL_ + +#ifdef INCLUDEGLOBAL + #define EXTVAR +#else + #define EXTVAR extern +#endif + +EXTVAR ErrorHandler ErrHandler; + + + +#endif diff --git a/reference/unrar/hardlinks.cpp b/reference/unrar/hardlinks.cpp new file mode 100644 index 00000000..cf0b25a9 --- /dev/null +++ b/reference/unrar/hardlinks.cpp @@ -0,0 +1,34 @@ +bool ExtractHardlink(wchar *NameNew,wchar *NameExisting,size_t NameExistingSize) +{ + SlashToNative(NameExisting,NameExisting,NameExistingSize); // Not needed for RAR 5.1+ archives. + + if (!FileExist(NameExisting)) + return false; + CreatePath(NameNew,true); + +#ifdef _WIN_ALL + bool Success=CreateHardLink(NameNew,NameExisting,NULL)!=0; + if (!Success) + { + uiMsg(UIERROR_HLINKCREATE,NameNew); + ErrHandler.SysErrMsg(); + ErrHandler.SetErrorCode(RARX_CREATE); + } + return Success; +#elif defined(_UNIX) + char NameExistingA[NM],NameNewA[NM]; + WideToChar(NameExisting,NameExistingA,ASIZE(NameExistingA)); + WideToChar(NameNew,NameNewA,ASIZE(NameNewA)); + bool Success=link(NameExistingA,NameNewA)==0; + if (!Success) + { + uiMsg(UIERROR_HLINKCREATE,NameNew); + ErrHandler.SysErrMsg(); + ErrHandler.SetErrorCode(RARX_CREATE); + } + return Success; +#else + return false; +#endif +} + diff --git a/reference/unrar/hash.cpp b/reference/unrar/hash.cpp new file mode 100644 index 00000000..42791f4f --- /dev/null +++ b/reference/unrar/hash.cpp @@ -0,0 +1,135 @@ +#include "rar.hpp" + +void HashValue::Init(HASH_TYPE Type) +{ + HashValue::Type=Type; + + // Zero length data CRC32 is 0. It is important to set it when creating + // headers with no following data like directories or symlinks. + if (Type==HASH_RAR14 || Type==HASH_CRC32) + CRC32=0; + if (Type==HASH_BLAKE2) + { + // dd0e891776933f43c7d032b08a917e25741f8aa9a12c12e1cac8801500f2ca4f + // is BLAKE2sp hash of empty data. We init the structure to this value, + // so if we create a file or service header with no following data like + // "file copy" or "symlink", we set the checksum to proper value avoiding + // additional header type or size checks when extracting. + static byte EmptyHash[32]={ + 0xdd, 0x0e, 0x89, 0x17, 0x76, 0x93, 0x3f, 0x43, + 0xc7, 0xd0, 0x32, 0xb0, 0x8a, 0x91, 0x7e, 0x25, + 0x74, 0x1f, 0x8a, 0xa9, 0xa1, 0x2c, 0x12, 0xe1, + 0xca, 0xc8, 0x80, 0x15, 0x00, 0xf2, 0xca, 0x4f + }; + memcpy(Digest,EmptyHash,sizeof(Digest)); + } +} + + +bool HashValue::operator == (const HashValue &cmp) +{ + if (Type==HASH_NONE || cmp.Type==HASH_NONE) + return true; + if (Type==HASH_RAR14 && cmp.Type==HASH_RAR14 || + Type==HASH_CRC32 && cmp.Type==HASH_CRC32) + return CRC32==cmp.CRC32; + if (Type==HASH_BLAKE2 && cmp.Type==HASH_BLAKE2) + return memcmp(Digest,cmp.Digest,sizeof(Digest))==0; + return false; +} + + +DataHash::DataHash() +{ + blake2ctx=NULL; + HashType=HASH_NONE; +#ifdef RAR_SMP + ThPool=NULL; + MaxThreads=0; +#endif +} + + +DataHash::~DataHash() +{ +#ifdef RAR_SMP + DestroyThreadPool(ThPool); +#endif + cleandata(&CurCRC32, sizeof(CurCRC32)); + if (blake2ctx!=NULL) + { + cleandata(blake2ctx, sizeof(blake2sp_state)); + delete blake2ctx; + } +} + + +void DataHash::Init(HASH_TYPE Type,uint MaxThreads) +{ + if (blake2ctx==NULL) + blake2ctx=new blake2sp_state; + HashType=Type; + if (Type==HASH_RAR14) + CurCRC32=0; + if (Type==HASH_CRC32) + CurCRC32=0xffffffff; // Initial CRC32 value. + if (Type==HASH_BLAKE2) + blake2sp_init(blake2ctx); +#ifdef RAR_SMP + DataHash::MaxThreads=Min(MaxThreads,MaxHashThreads); +#endif +} + + +void DataHash::Update(const void *Data,size_t DataSize) +{ +#ifndef SFX_MODULE + if (HashType==HASH_RAR14) + CurCRC32=Checksum14((ushort)CurCRC32,Data,DataSize); +#endif + if (HashType==HASH_CRC32) + CurCRC32=CRC32(CurCRC32,Data,DataSize); + + if (HashType==HASH_BLAKE2) + { +#ifdef RAR_SMP + if (MaxThreads>1 && ThPool==NULL) + ThPool=CreateThreadPool(); + blake2ctx->ThPool=ThPool; + blake2ctx->MaxThreads=MaxThreads; +#endif + blake2sp_update( blake2ctx, (byte *)Data, DataSize); + } +} + + +void DataHash::Result(HashValue *Result) +{ + Result->Type=HashType; + if (HashType==HASH_RAR14) + Result->CRC32=CurCRC32; + if (HashType==HASH_CRC32) + Result->CRC32=CurCRC32^0xffffffff; + if (HashType==HASH_BLAKE2) + { + // Preserve the original context, so we can continue hashing if necessary. + blake2sp_state res=*blake2ctx; + blake2sp_final(&res,Result->Digest); + } +} + + +uint DataHash::GetCRC32() +{ + return HashType==HASH_CRC32 ? CurCRC32^0xffffffff : 0; +} + + +bool DataHash::Cmp(HashValue *CmpValue,byte *Key) +{ + HashValue Final; + Result(&Final); + if (Key!=NULL) + ConvertHashToMAC(&Final,Key); + return Final==*CmpValue; +} diff --git a/reference/unrar/hash.hpp b/reference/unrar/hash.hpp new file mode 100644 index 00000000..b7d879f6 --- /dev/null +++ b/reference/unrar/hash.hpp @@ -0,0 +1,52 @@ +#ifndef _RAR_DATAHASH_ +#define _RAR_DATAHASH_ + +enum HASH_TYPE {HASH_NONE,HASH_RAR14,HASH_CRC32,HASH_BLAKE2}; + +struct HashValue +{ + void Init(HASH_TYPE Type); + bool operator == (const HashValue &cmp); + bool operator != (const HashValue &cmp) {return !(*this==cmp);} + + HASH_TYPE Type; + union + { + uint CRC32; + byte Digest[SHA256_DIGEST_SIZE]; + }; +}; + + +#ifdef RAR_SMP +class ThreadPool; +class DataHash; +#endif + + +class DataHash +{ + private: + HASH_TYPE HashType; + uint CurCRC32; + blake2sp_state *blake2ctx; + +#ifdef RAR_SMP + ThreadPool *ThPool; + + uint MaxThreads; + // Upper limit for maximum threads to prevent wasting threads in pool. + static const uint MaxHashThreads=8; +#endif + public: + DataHash(); + ~DataHash(); + void Init(HASH_TYPE Type,uint MaxThreads); + void Update(const void *Data,size_t DataSize); + void Result(HashValue *Result); + uint GetCRC32(); + bool Cmp(HashValue *CmpValue,byte *Key); + HASH_TYPE Type() {return HashType;} +}; + +#endif diff --git a/reference/unrar/headers.cpp b/reference/unrar/headers.cpp new file mode 100644 index 00000000..b042dc39 --- /dev/null +++ b/reference/unrar/headers.cpp @@ -0,0 +1,61 @@ +#include "rar.hpp" + +void FileHeader::Reset(size_t SubDataSize) +{ + SubData.Alloc(SubDataSize); + BaseBlock::Reset(); + FileHash.Init(HASH_NONE); + mtime.Reset(); + atime.Reset(); + ctime.Reset(); + SplitBefore=false; + SplitAfter=false; + + UnknownUnpSize=0; + + SubFlags=0; // Important for RAR 3.0 subhead. + + CryptMethod=CRYPT_NONE; + Encrypted=false; + SaltSet=false; + UsePswCheck=false; + UseHashKey=false; + Lg2Count=0; + + Solid=false; + Dir=false; + WinSize=0; + Inherited=false; + SubBlock=false; + CommentInHeader=false; + Version=false; + LargeFile=false; + + RedirType=FSREDIR_NONE; + DirTarget=false; + UnixOwnerSet=false; +} + + +FileHeader& FileHeader::operator = (FileHeader &hd) +{ + SubData.Reset(); + memcpy(this,&hd,sizeof(*this)); + SubData.CleanData(); + SubData=hd.SubData; + return *this; +} + + +void MainHeader::Reset() +{ + HighPosAV=0; + PosAV=0; + CommentInHeader=false; + PackComment=false; + Locator=false; + QOpenOffset=0; + QOpenMaxSize=0; + RROffset=0; + RRMaxSize=0; +} diff --git a/reference/unrar/headers.hpp b/reference/unrar/headers.hpp new file mode 100644 index 00000000..94864789 --- /dev/null +++ b/reference/unrar/headers.hpp @@ -0,0 +1,380 @@ +#ifndef _RAR_HEADERS_ +#define _RAR_HEADERS_ + +#define SIZEOF_MARKHEAD3 7 // Size of RAR 4.x archive mark header. +#define SIZEOF_MAINHEAD14 7 // Size of RAR 1.4 main archive header. +#define SIZEOF_MAINHEAD3 13 // Size of RAR 4.x main archive header. +#define SIZEOF_FILEHEAD14 21 // Size of RAR 1.4 file header. +#define SIZEOF_FILEHEAD3 32 // Size of RAR 3.0 file header. +#define SIZEOF_SHORTBLOCKHEAD 7 +#define SIZEOF_LONGBLOCKHEAD 11 +#define SIZEOF_SUBBLOCKHEAD 14 +#define SIZEOF_COMMHEAD 13 +#define SIZEOF_PROTECTHEAD 26 +#define SIZEOF_AVHEAD 14 +#define SIZEOF_SIGNHEAD 15 +#define SIZEOF_UOHEAD 18 +#define SIZEOF_MACHEAD 22 +#define SIZEOF_EAHEAD 24 +#define SIZEOF_BEEAHEAD 24 +#define SIZEOF_STREAMHEAD 26 + +#define VER_PACK 29 +#define VER_PACK5 50 // It is stored as 0, but we subtract 50 when saving an archive. +#define VER_UNPACK 29 +#define VER_UNPACK5 50 // It is stored as 0, but we add 50 when reading an archive. + +#define MHD_VOLUME 0x0001U + +// Old style main archive comment embed into main archive header. Must not +// be used in new archives anymore. +#define MHD_COMMENT 0x0002U + +#define MHD_LOCK 0x0004U +#define MHD_SOLID 0x0008U +#define MHD_PACK_COMMENT 0x0010U +#define MHD_NEWNUMBERING 0x0010U +#define MHD_AV 0x0020U +#define MHD_PROTECT 0x0040U +#define MHD_PASSWORD 0x0080U +#define MHD_FIRSTVOLUME 0x0100U + +#define LHD_SPLIT_BEFORE 0x0001U +#define LHD_SPLIT_AFTER 0x0002U +#define LHD_PASSWORD 0x0004U + +// Old style file comment embed into file header. Must not be used +// in new archives anymore. +#define LHD_COMMENT 0x0008U + +// For non-file subheaders it denotes 'subblock having a parent file' flag. +#define LHD_SOLID 0x0010U + + +#define LHD_WINDOWMASK 0x00e0U +#define LHD_WINDOW64 0x0000U +#define LHD_WINDOW128 0x0020U +#define LHD_WINDOW256 0x0040U +#define LHD_WINDOW512 0x0060U +#define LHD_WINDOW1024 0x0080U +#define LHD_WINDOW2048 0x00a0U +#define LHD_WINDOW4096 0x00c0U +#define LHD_DIRECTORY 0x00e0U + +#define LHD_LARGE 0x0100U +#define LHD_UNICODE 0x0200U +#define LHD_SALT 0x0400U +#define LHD_VERSION 0x0800U +#define LHD_EXTTIME 0x1000U + +#define SKIP_IF_UNKNOWN 0x4000U +#define LONG_BLOCK 0x8000U + +#define EARC_NEXT_VOLUME 0x0001U // Not last volume. +#define EARC_DATACRC 0x0002U // Store CRC32 of RAR archive (now is used only in volumes). +#define EARC_REVSPACE 0x0004U // Reserve space for end of REV file 7 byte record. +#define EARC_VOLNUMBER 0x0008U // Store a number of current volume. + +enum HEADER_TYPE { + // RAR 5.0 header types. + HEAD_MARK=0x00, HEAD_MAIN=0x01, HEAD_FILE=0x02, HEAD_SERVICE=0x03, + HEAD_CRYPT=0x04, HEAD_ENDARC=0x05, HEAD_UNKNOWN=0xff, + + // RAR 1.5 - 4.x header types. + HEAD3_MARK=0x72,HEAD3_MAIN=0x73,HEAD3_FILE=0x74,HEAD3_CMT=0x75, + HEAD3_AV=0x76,HEAD3_OLDSERVICE=0x77,HEAD3_PROTECT=0x78,HEAD3_SIGN=0x79, + HEAD3_SERVICE=0x7a,HEAD3_ENDARC=0x7b +}; + +enum { EA_HEAD=0x100,UO_HEAD=0x101,MAC_HEAD=0x102,BEEA_HEAD=0x103, + NTACL_HEAD=0x104,STREAM_HEAD=0x105 }; + + +// Internal implementation, depends on archive format version. +enum HOST_SYSTEM { + // RAR 5.0 host OS + HOST5_WINDOWS=0,HOST5_UNIX=1, + + // RAR 3.0 host OS. + HOST_MSDOS=0,HOST_OS2=1,HOST_WIN32=2,HOST_UNIX=3,HOST_MACOS=4, + HOST_BEOS=5,HOST_MAX +}; + +// Unified archive format independent implementation. +enum HOST_SYSTEM_TYPE { + HSYS_WINDOWS, HSYS_UNIX, HSYS_UNKNOWN +}; + + +// We also use these values in extra field, so do not modify them. +enum FILE_SYSTEM_REDIRECT { + FSREDIR_NONE=0, FSREDIR_UNIXSYMLINK, FSREDIR_WINSYMLINK, FSREDIR_JUNCTION, + FSREDIR_HARDLINK, FSREDIR_FILECOPY +}; + + +#define SUBHEAD_TYPE_CMT L"CMT" +#define SUBHEAD_TYPE_QOPEN L"QO" +#define SUBHEAD_TYPE_ACL L"ACL" +#define SUBHEAD_TYPE_STREAM L"STM" +#define SUBHEAD_TYPE_UOWNER L"UOW" +#define SUBHEAD_TYPE_AV L"AV" +#define SUBHEAD_TYPE_RR L"RR" +#define SUBHEAD_TYPE_OS2EA L"EA2" + +/* new file inherits a subblock when updating a host file */ +#define SUBHEAD_FLAGS_INHERITED 0x80000000 + +#define SUBHEAD_FLAGS_CMT_UNICODE 0x00000001 + + +struct MarkHeader +{ + byte Mark[8]; + + // Following fields are virtual and not present in real blocks. + uint HeadSize; +}; + + +struct BaseBlock +{ + uint HeadCRC; // 'ushort' for RAR 1.5. + HEADER_TYPE HeaderType; // 1 byte for RAR 1.5. + uint Flags; // 'ushort' for RAR 1.5. + uint HeadSize; // 'ushort' for RAR 1.5, up to 2 MB for RAR 5.0. + + bool SkipIfUnknown; + + void Reset() + { + SkipIfUnknown=false; + } +}; + + +struct BlockHeader:BaseBlock +{ + uint DataSize; +}; + + +struct MainHeader:BaseBlock +{ + ushort HighPosAV; + uint PosAV; + bool CommentInHeader; + bool PackComment; // For RAR 1.4 archive format only. + bool Locator; + uint64 QOpenOffset; // Offset of quick list record. + uint64 QOpenMaxSize; // Maximum size of QOpen offset in locator extra field. + uint64 RROffset; // Offset of recovery record. + uint64 RRMaxSize; // Maximum size of RR offset in locator extra field. + void Reset(); +}; + + +struct FileHeader:BlockHeader +{ + byte HostOS; + byte UnpVer; + byte Method; + union { + uint FileAttr; + uint SubFlags; + }; + wchar FileName[NM]; + + Array SubData; + + RarTime mtime; + RarTime ctime; + RarTime atime; + + int64 PackSize; + int64 UnpSize; + int64 MaxSize; // Reserve size bytes for vint of this size. + + HashValue FileHash; + + uint FileFlags; + + bool SplitBefore; + bool SplitAfter; + + bool UnknownUnpSize; + + bool Encrypted; + CRYPT_METHOD CryptMethod; + bool SaltSet; + byte Salt[SIZE_SALT50]; + byte InitV[SIZE_INITV]; + bool UsePswCheck; + byte PswCheck[SIZE_PSWCHECK]; + + // Use HMAC calculated from HashKey and checksum instead of plain checksum. + bool UseHashKey; + + // Key to convert checksum to HMAC. Derived from password with PBKDF2 + // using additional iterations. + byte HashKey[SHA256_DIGEST_SIZE]; + + uint Lg2Count; // Log2 of PBKDF2 repetition count. + + bool Solid; + bool Dir; + bool CommentInHeader; // RAR 2.0 file comment. + bool Version; // name.ext;ver file name containing the version number. + size_t WinSize; + bool Inherited; // New file inherits a subblock when updating a host file (for subblocks only). + + // 'true' if file sizes use 8 bytes instead of 4. Not used in RAR 5.0. + bool LargeFile; + + // 'true' for HEAD_SERVICE block, which is a child of preceding file block. + // RAR 4.x uses 'solid' flag to indicate child subheader blocks in archives. + bool SubBlock; + + HOST_SYSTEM_TYPE HSType; + + FILE_SYSTEM_REDIRECT RedirType; + wchar RedirName[NM]; + bool DirTarget; + + bool UnixOwnerSet,UnixOwnerNumeric,UnixGroupNumeric; + char UnixOwnerName[256],UnixGroupName[256]; +#ifdef _UNIX + uid_t UnixOwnerID; + gid_t UnixGroupID; +#else // Need these Unix fields in Windows too for 'list' command. + uint UnixOwnerID; + uint UnixGroupID; +#endif + + void Reset(size_t SubDataSize=0); + + bool CmpName(const wchar *Name) + { + return(wcscmp(FileName,Name)==0); + } + + FileHeader& operator = (FileHeader &hd); +}; + + +struct EndArcHeader:BaseBlock +{ + // Optional CRC32 of entire archive up to start of EndArcHeader block. + // Present in RAR 4.x archives if EARC_DATACRC flag is set. + uint ArcDataCRC; + + uint VolNumber; // Optional number of current volume. + + // 7 additional zero bytes can be stored here if EARC_REVSPACE is set. + + bool NextVolume; // Not last volume. + bool DataCRC; + bool RevSpace; + bool StoreVolNumber; + void Reset() + { + BaseBlock::Reset(); + NextVolume=false; + DataCRC=false; + RevSpace=false; + StoreVolNumber=false; + } +}; + + +struct CryptHeader:BaseBlock +{ + bool UsePswCheck; + uint Lg2Count; // Log2 of PBKDF2 repetition count. + byte Salt[SIZE_SALT50]; + byte PswCheck[SIZE_PSWCHECK]; +}; + + +// SubBlockHeader and its successors were used in RAR 2.x format. +// RAR 4.x uses FileHeader with HEAD_SERVICE HeaderType for subblocks. +struct SubBlockHeader:BlockHeader +{ + ushort SubType; + byte Level; +}; + + +struct CommentHeader:BaseBlock +{ + ushort UnpSize; + byte UnpVer; + byte Method; + ushort CommCRC; +}; + + +struct ProtectHeader:BlockHeader +{ + byte Version; + ushort RecSectors; + uint TotalBlocks; + byte Mark[8]; +}; + + +struct AVHeader:BaseBlock +{ + byte UnpVer; + byte Method; + byte AVVer; + uint AVInfoCRC; +}; + + +struct SignHeader:BaseBlock +{ + uint CreationTime; + ushort ArcNameSize; + ushort UserNameSize; +}; + + +struct UnixOwnersHeader:SubBlockHeader +{ + ushort OwnerNameSize; + ushort GroupNameSize; +/* dummy */ + char OwnerName[256]; + char GroupName[256]; +}; + + +struct EAHeader:SubBlockHeader +{ + uint UnpSize; + byte UnpVer; + byte Method; + uint EACRC; +}; + + +struct StreamHeader:SubBlockHeader +{ + uint UnpSize; + byte UnpVer; + byte Method; + uint StreamCRC; + ushort StreamNameSize; + char StreamName[260]; +}; + + +struct MacFInfoHeader:SubBlockHeader +{ + uint fileType; + uint fileCreator; +}; + + +#endif diff --git a/reference/unrar/headers5.hpp b/reference/unrar/headers5.hpp new file mode 100644 index 00000000..9ea8d979 --- /dev/null +++ b/reference/unrar/headers5.hpp @@ -0,0 +1,100 @@ +#ifndef _RAR_HEADERS5_ +#define _RAR_HEADERS5_ + +#define SIZEOF_MARKHEAD5 8 // RAR 5.0 signature length. +#define SIZEOF_SHORTBLOCKHEAD5 7 // Smallest RAR 5.0 block size. + +// RAR 5.0 block flags common for all blocks. + +// Additional extra area is present in the end of block header. +#define HFL_EXTRA 0x0001 +// Additional data area is present in the end of block header. +#define HFL_DATA 0x0002 +// Unknown blocks with this flag must be skipped when updating an archive. +#define HFL_SKIPIFUNKNOWN 0x0004 +// Data area of this block is continuing from previous volume. +#define HFL_SPLITBEFORE 0x0008 +// Data area of this block is continuing in next volume. +#define HFL_SPLITAFTER 0x0010 +// Block depends on preceding file block. +#define HFL_CHILD 0x0020 +// Preserve a child block if host is modified. +#define HFL_INHERITED 0x0040 + +// RAR 5.0 main archive header specific flags. +#define MHFL_VOLUME 0x0001 // Volume. +#define MHFL_VOLNUMBER 0x0002 // Volume number field is present. True for all volumes except first. +#define MHFL_SOLID 0x0004 // Solid archive. +#define MHFL_PROTECT 0x0008 // Recovery record is present. +#define MHFL_LOCK 0x0010 // Locked archive. + +// RAR 5.0 file header specific flags. +#define FHFL_DIRECTORY 0x0001 // Directory. +#define FHFL_UTIME 0x0002 // Time field in Unix format is present. +#define FHFL_CRC32 0x0004 // CRC32 field is present. +#define FHFL_UNPUNKNOWN 0x0008 // Unknown unpacked size. + +// RAR 5.0 end of archive header specific flags. +#define EHFL_NEXTVOLUME 0x0001 // Not last volume. + +// RAR 5.0 archive encryption header specific flags. +#define CHFL_CRYPT_PSWCHECK 0x0001 // Password check data is present. + + +// RAR 5.0 file compression flags. +#define FCI_ALGO_BIT0 0x0001 // Version of compression algorithm. +#define FCI_ALGO_BIT1 0x0002 // 0 .. 63. +#define FCI_ALGO_BIT2 0x0004 +#define FCI_ALGO_BIT3 0x0008 +#define FCI_ALGO_BIT4 0x0010 +#define FCI_ALGO_BIT5 0x0020 +#define FCI_SOLID 0x0040 // Solid flag. +#define FCI_METHOD_BIT0 0x0080 // Compression method. +#define FCI_METHOD_BIT1 0x0100 // 0 .. 5 (6 and 7 are not used). +#define FCI_METHOD_BIT2 0x0200 +#define FCI_DICT_BIT0 0x0400 // Dictionary size. +#define FCI_DICT_BIT1 0x0800 // 128 KB .. 4 GB. +#define FCI_DICT_BIT2 0x1000 +#define FCI_DICT_BIT3 0x2000 + +// Main header extra field values. +#define MHEXTRA_LOCATOR 0x01 // Position of quick list and other blocks. + +// Flags for MHEXTRA_LOCATOR. +#define MHEXTRA_LOCATOR_QLIST 0x01 // Quick open offset is present. +#define MHEXTRA_LOCATOR_RR 0x02 // Recovery record offset is present. + +// File and service header extra field values. +#define FHEXTRA_CRYPT 0x01 // Encryption parameters. +#define FHEXTRA_HASH 0x02 // File hash. +#define FHEXTRA_HTIME 0x03 // High precision file time. +#define FHEXTRA_VERSION 0x04 // File version information. +#define FHEXTRA_REDIR 0x05 // File system redirection (links, etc.). +#define FHEXTRA_UOWNER 0x06 // Unix owner and group information. +#define FHEXTRA_SUBDATA 0x07 // Service header subdata array. + + +// Hash type values for FHEXTRA_HASH. +#define FHEXTRA_HASH_BLAKE2 0x00 + +// Flags for FHEXTRA_HTIME. +#define FHEXTRA_HTIME_UNIXTIME 0x01 // Use Unix time_t format. +#define FHEXTRA_HTIME_MTIME 0x02 // mtime is present. +#define FHEXTRA_HTIME_CTIME 0x04 // ctime is present. +#define FHEXTRA_HTIME_ATIME 0x08 // atime is present. +#define FHEXTRA_HTIME_UNIX_NS 0x10 // Unix format with nanosecond precision. + +// Flags for FHEXTRA_CRYPT. +#define FHEXTRA_CRYPT_PSWCHECK 0x01 // Store password check data. +#define FHEXTRA_CRYPT_HASHMAC 0x02 // Use MAC for unpacked data checksums. + +// Flags for FHEXTRA_REDIR. +#define FHEXTRA_REDIR_DIR 0x01 // Link target is directory. + +// Flags for FHEXTRA_UOWNER. +#define FHEXTRA_UOWNER_UNAME 0x01 // User name string is present. +#define FHEXTRA_UOWNER_GNAME 0x02 // Group name string is present. +#define FHEXTRA_UOWNER_NUMUID 0x04 // Numeric user ID is present. +#define FHEXTRA_UOWNER_NUMGID 0x08 // Numeric group ID is present. + +#endif diff --git a/reference/unrar/isnt.cpp b/reference/unrar/isnt.cpp new file mode 100644 index 00000000..6fadec04 --- /dev/null +++ b/reference/unrar/isnt.cpp @@ -0,0 +1,24 @@ +#include "rar.hpp" + +#ifdef _WIN_ALL +DWORD WinNT() +{ + static int dwPlatformId=-1; + static DWORD dwMajorVersion,dwMinorVersion; + if (dwPlatformId==-1) + { + OSVERSIONINFO WinVer; + WinVer.dwOSVersionInfoSize=sizeof(WinVer); + GetVersionEx(&WinVer); + dwPlatformId=WinVer.dwPlatformId; + dwMajorVersion=WinVer.dwMajorVersion; + dwMinorVersion=WinVer.dwMinorVersion; + } + DWORD Result=0; + if (dwPlatformId==VER_PLATFORM_WIN32_NT) + Result=dwMajorVersion*0x100+dwMinorVersion; + + + return Result; +} +#endif diff --git a/reference/unrar/isnt.hpp b/reference/unrar/isnt.hpp new file mode 100644 index 00000000..85790da4 --- /dev/null +++ b/reference/unrar/isnt.hpp @@ -0,0 +1,13 @@ +#ifndef _RAR_ISNT_ +#define _RAR_ISNT_ + +enum WINNT_VERSION { + WNT_NONE=0,WNT_NT351=0x0333,WNT_NT4=0x0400,WNT_W2000=0x0500, + WNT_WXP=0x0501,WNT_W2003=0x0502,WNT_VISTA=0x0600,WNT_W7=0x0601, + WNT_W8=0x0602,WNT_W81=0x0603,WNT_W10=0x0a00 +}; + +DWORD WinNT(); + + +#endif diff --git a/reference/unrar/license.txt b/reference/unrar/license.txt new file mode 100644 index 00000000..0811276a --- /dev/null +++ b/reference/unrar/license.txt @@ -0,0 +1,42 @@ + ****** ***** ****** UnRAR - free utility for RAR archives + ** ** ** ** ** ** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ****** ******* ****** License for use and distribution of + ** ** ** ** ** ** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ** ** ** ** ** ** FREE portable version + ~~~~~~~~~~~~~~~~~~~~~ + + The source code of UnRAR utility is freeware. This means: + + 1. All copyrights to RAR and the utility UnRAR are exclusively + owned by the author - Alexander Roshal. + + 2. UnRAR source code may be used in any software to handle + RAR archives without limitations free of charge, but cannot be + used to develop RAR (WinRAR) compatible archiver and to + re-create RAR compression algorithm, which is proprietary. + Distribution of modified UnRAR source code in separate form + or as a part of other software is permitted, provided that + full text of this paragraph, starting from "UnRAR source code" + words, is included in license, or in documentation if license + is not available, and in source code comments of resulting package. + + 3. The UnRAR utility may be freely distributed. It is allowed + to distribute UnRAR inside of other software packages. + + 4. THE RAR ARCHIVER AND THE UnRAR UTILITY ARE DISTRIBUTED "AS IS". + NO WARRANTY OF ANY KIND IS EXPRESSED OR IMPLIED. YOU USE AT + YOUR OWN RISK. THE AUTHOR WILL NOT BE LIABLE FOR DATA LOSS, + DAMAGES, LOSS OF PROFITS OR ANY OTHER KIND OF LOSS WHILE USING + OR MISUSING THIS SOFTWARE. + + 5. Installing and using the UnRAR utility signifies acceptance of + these terms and conditions of the license. + + 6. If you don't agree with terms of the license you must remove + UnRAR files from your storage devices and cease to use the + utility. + + Thank you for your interest in RAR and UnRAR. + + + Alexander L. Roshal diff --git a/reference/unrar/list.cpp b/reference/unrar/list.cpp new file mode 100644 index 00000000..561122b4 --- /dev/null +++ b/reference/unrar/list.cpp @@ -0,0 +1,472 @@ +#include "rar.hpp" + +static void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bool Technical,bool Bare); +static void ListSymLink(Archive &Arc); +static void ListFileAttr(uint A,HOST_SYSTEM_TYPE HostType,wchar *AttrStr,size_t AttrSize); +static void ListOldSubHeader(Archive &Arc); +static void ListNewSubHeader(CommandData *Cmd,Archive &Arc); + +void ListArchive(CommandData *Cmd) +{ + int64 SumPackSize=0,SumUnpSize=0; + uint ArcCount=0,SumFileCount=0; + bool Technical=(Cmd->Command[1]=='T'); + bool ShowService=Technical && Cmd->Command[2]=='A'; + bool Bare=(Cmd->Command[1]=='B'); + bool Verbose=(Cmd->Command[0]=='V'); + + wchar ArcName[NM]; + while (Cmd->GetArcName(ArcName,ASIZE(ArcName))) + { + if (Cmd->ManualPassword) + Cmd->Password.Clean(); // Clean user entered password before processing next archive. + + Archive Arc(Cmd); +#ifdef _WIN_ALL + Arc.RemoveSequentialFlag(); +#endif + if (!Arc.WOpen(ArcName)) + continue; + bool FileMatched=true; + while (1) + { + int64 TotalPackSize=0,TotalUnpSize=0; + uint FileCount=0; + if (Arc.IsArchive(true)) + { + bool TitleShown=false; + if (!Bare) + { + Arc.ViewComment(); + mprintf(L"\n%s: %s",St(MListArchive),Arc.FileName); + mprintf(L"\n%s: ",St(MListDetails)); + uint SetCount=0; + const wchar *Fmt=Arc.Format==RARFMT14 ? L"RAR 1.4":(Arc.Format==RARFMT15 ? L"RAR 4":L"RAR 5"); + mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", Fmt); + if (Arc.Solid) + mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListSolid)); + if (Arc.SFXSize>0) + mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListSFX)); + if (Arc.Volume) + if (Arc.Format==RARFMT50) + { + // RAR 5.0 archives store the volume number in main header, + // so it is already available now. + if (SetCount++ > 0) + mprintf(L", "); + mprintf(St(MVolumeNumber),Arc.VolNumber+1); + } + else + mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListVolume)); + if (Arc.Protected) + mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListRR)); + if (Arc.Locked) + mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListLock)); + if (Arc.Encrypted) + mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListEncHead)); + mprintf(L"\n"); + } + + wchar VolNumText[50]; + *VolNumText=0; + while(Arc.ReadHeader()>0) + { + HEADER_TYPE HeaderType=Arc.GetHeaderType(); + if (HeaderType==HEAD_ENDARC) + { +#ifndef SFX_MODULE + // Only RAR 1.5 archives store the volume number in end record. + if (Arc.EndArcHead.StoreVolNumber && Arc.Format==RARFMT15) + swprintf(VolNumText,ASIZE(VolNumText),L"%.10ls %u",St(MListVolume),Arc.VolNumber+1); +#endif + if (Technical && ShowService) + { + mprintf(L"\n%12ls: %ls",St(MListService),L"EOF"); + if (*VolNumText!=0) + mprintf(L"\n%12ls: %ls",St(MListFlags),VolNumText); + mprintf(L"\n"); + } + break; + } + switch(HeaderType) + { + case HEAD_FILE: + FileMatched=Cmd->IsProcessFile(Arc.FileHead)!=0; + if (FileMatched) + { + ListFileHeader(Arc,Arc.FileHead,TitleShown,Verbose,Technical,Bare); + if (!Arc.FileHead.SplitBefore) + { + TotalUnpSize+=Arc.FileHead.UnpSize; + FileCount++; + } + TotalPackSize+=Arc.FileHead.PackSize; + } + break; + case HEAD_SERVICE: + if (FileMatched && !Bare) + { + if (Technical && ShowService) + ListFileHeader(Arc,Arc.SubHead,TitleShown,Verbose,true,false); + } + break; + } + Arc.SeekToNext(); + } + if (!Bare && !Technical) + if (TitleShown) + { + wchar UnpSizeText[20]; + itoa(TotalUnpSize,UnpSizeText,ASIZE(UnpSizeText)); + + wchar PackSizeText[20]; + itoa(TotalPackSize,PackSizeText,ASIZE(PackSizeText)); + + if (Verbose) + { + mprintf(L"\n----------- --------- -------- ----- ---------- ----- -------- ----"); + mprintf(L"\n%21ls %9ls %3d%% %-27ls %u",UnpSizeText, + PackSizeText,ToPercentUnlim(TotalPackSize,TotalUnpSize), + VolNumText,FileCount); + } + else + { + mprintf(L"\n----------- --------- ---------- ----- ----"); + mprintf(L"\n%21ls %-16ls %u",UnpSizeText,VolNumText,FileCount); + } + + SumFileCount+=FileCount; + SumUnpSize+=TotalUnpSize; + SumPackSize+=TotalPackSize; + mprintf(L"\n"); + } + else + mprintf(St(MListNoFiles)); + + ArcCount++; + +#ifndef NOVOLUME + if (Cmd->VolSize!=0 && (Arc.FileHead.SplitAfter || + Arc.GetHeaderType()==HEAD_ENDARC && Arc.EndArcHead.NextVolume) && + MergeArchive(Arc,NULL,false,Cmd->Command[0])) + Arc.Seek(0,SEEK_SET); + else +#endif + break; + } + else + { + if (Cmd->ArcNames.ItemsCount()<2 && !Bare) + mprintf(St(MNotRAR),Arc.FileName); + break; + } + } + } + + // Clean user entered password. Not really required, just for extra safety. + if (Cmd->ManualPassword) + Cmd->Password.Clean(); + + if (ArcCount>1 && !Bare && !Technical) + { + wchar UnpSizeText[20],PackSizeText[20]; + itoa(SumUnpSize,UnpSizeText,ASIZE(UnpSizeText)); + itoa(SumPackSize,PackSizeText,ASIZE(PackSizeText)); + + if (Verbose) + mprintf(L"%21ls %9ls %3d%% %28ls %u",UnpSizeText,PackSizeText, + ToPercentUnlim(SumPackSize,SumUnpSize),L"",SumFileCount); + else + mprintf(L"%21ls %18s %lu",UnpSizeText,L"",SumFileCount); + } +} + + +enum LISTCOL_TYPE { + LCOL_NAME,LCOL_ATTR,LCOL_SIZE,LCOL_PACKED,LCOL_RATIO,LCOL_CSUM,LCOL_ENCR +}; + + +void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bool Technical,bool Bare) +{ + wchar *Name=hd.FileName; + RARFORMAT Format=Arc.Format; + + if (Bare) + { + mprintf(L"%s\n",Name); + return; + } + + if (!TitleShown && !Technical) + { + if (Verbose) + { + mprintf(L"\n%ls",St(MListTitleV)); + mprintf(L"\n----------- --------- -------- ----- ---------- ----- -------- ----"); + } + else + { + mprintf(L"\n%ls",St(MListTitleL)); + mprintf(L"\n----------- --------- ---------- ----- ----"); + } + TitleShown=true; + } + + wchar UnpSizeText[30],PackSizeText[30]; + if (hd.UnpSize==INT64NDF) + wcscpy(UnpSizeText,L"?"); + else + itoa(hd.UnpSize,UnpSizeText,ASIZE(UnpSizeText)); + itoa(hd.PackSize,PackSizeText,ASIZE(PackSizeText)); + + wchar AttrStr[30]; + if (hd.HeaderType==HEAD_SERVICE) + swprintf(AttrStr,ASIZE(AttrStr),L"%cB",hd.Inherited ? 'I' : '.'); + else + ListFileAttr(hd.FileAttr,hd.HSType,AttrStr,ASIZE(AttrStr)); + + wchar RatioStr[10]; + + if (hd.SplitBefore && hd.SplitAfter) + wcscpy(RatioStr,L"<->"); + else + if (hd.SplitBefore) + wcscpy(RatioStr,L"<--"); + else + if (hd.SplitAfter) + wcscpy(RatioStr,L"-->"); + else + swprintf(RatioStr,ASIZE(RatioStr),L"%d%%",ToPercentUnlim(hd.PackSize,hd.UnpSize)); + + wchar DateStr[50]; + hd.mtime.GetText(DateStr,ASIZE(DateStr),Technical); + + if (Technical) + { + mprintf(L"\n%12s: %s",St(MListName),Name); + + bool FileBlock=hd.HeaderType==HEAD_FILE; + + if (!FileBlock && Arc.SubHead.CmpName(SUBHEAD_TYPE_STREAM)) + { + mprintf(L"\n%12ls: %ls",St(MListType),St(MListStream)); + wchar StreamName[NM]; + GetStreamNameNTFS(Arc,StreamName,ASIZE(StreamName)); + mprintf(L"\n%12ls: %ls",St(MListTarget),StreamName); + } + else + { + const wchar *Type=St(FileBlock ? (hd.Dir ? MListDir:MListFile):MListService); + + if (hd.RedirType!=FSREDIR_NONE) + switch(hd.RedirType) + { + case FSREDIR_UNIXSYMLINK: + Type=St(MListUSymlink); break; + case FSREDIR_WINSYMLINK: + Type=St(MListWSymlink); break; + case FSREDIR_JUNCTION: + Type=St(MListJunction); break; + case FSREDIR_HARDLINK: + Type=St(MListHardlink); break; + case FSREDIR_FILECOPY: + Type=St(MListCopy); break; + } + mprintf(L"\n%12ls: %ls",St(MListType),Type); + if (hd.RedirType!=FSREDIR_NONE) + if (Format==RARFMT15) + { + char LinkTargetA[NM]; + if (Arc.FileHead.Encrypted) + { + // Link data are encrypted. We would need to ask for password + // and initialize decryption routine to display the link target. + strncpyz(LinkTargetA,"*<-?->",ASIZE(LinkTargetA)); + } + else + { + int DataSize=(int)Min(hd.PackSize,ASIZE(LinkTargetA)-1); + Arc.Read(LinkTargetA,DataSize); + LinkTargetA[DataSize > 0 ? DataSize : 0] = 0; + } + wchar LinkTarget[NM]; + CharToWide(LinkTargetA,LinkTarget,ASIZE(LinkTarget)); + mprintf(L"\n%12ls: %ls",St(MListTarget),LinkTarget); + } + else + mprintf(L"\n%12ls: %ls",St(MListTarget),hd.RedirName); + } + if (!hd.Dir) + { + mprintf(L"\n%12ls: %ls",St(MListSize),UnpSizeText); + mprintf(L"\n%12ls: %ls",St(MListPacked),PackSizeText); + mprintf(L"\n%12ls: %ls",St(MListRatio),RatioStr); + } + if (hd.mtime.IsSet()) + mprintf(L"\n%12ls: %ls",St(MListMtime),DateStr); + if (hd.ctime.IsSet()) + { + hd.ctime.GetText(DateStr,ASIZE(DateStr),true); + mprintf(L"\n%12ls: %ls",St(MListCtime),DateStr); + } + if (hd.atime.IsSet()) + { + hd.atime.GetText(DateStr,ASIZE(DateStr),true); + mprintf(L"\n%12ls: %ls",St(MListAtime),DateStr); + } + mprintf(L"\n%12ls: %ls",St(MListAttr),AttrStr); + if (hd.FileHash.Type==HASH_CRC32) + mprintf(L"\n%12ls: %8.8X", + hd.UseHashKey ? L"CRC32 MAC":hd.SplitAfter ? L"Pack-CRC32":L"CRC32", + hd.FileHash.CRC32); + if (hd.FileHash.Type==HASH_BLAKE2) + { + wchar BlakeStr[BLAKE2_DIGEST_SIZE*2+1]; + BinToHex(hd.FileHash.Digest,BLAKE2_DIGEST_SIZE,NULL,BlakeStr,ASIZE(BlakeStr)); + mprintf(L"\n%12ls: %ls", + hd.UseHashKey ? L"BLAKE2 MAC":hd.SplitAfter ? L"Pack-BLAKE2":L"BLAKE2", + BlakeStr); + } + + const wchar *HostOS=L""; + if (Format==RARFMT50 && hd.HSType!=HSYS_UNKNOWN) + HostOS=hd.HSType==HSYS_WINDOWS ? L"Windows":L"Unix"; + if (Format==RARFMT15) + { + static const wchar *RarOS[]={ + L"DOS",L"OS/2",L"Windows",L"Unix",L"Mac OS",L"BeOS",L"WinCE",L"",L"",L"" + }; + if (hd.HostOS=0x100000 ? hd.WinSize/0x100000:hd.WinSize/0x400, + hd.WinSize>=0x100000 ? L"M":L"K"); + + if (hd.Solid || hd.Encrypted) + { + mprintf(L"\n%12ls: ",St(MListFlags)); + if (hd.Solid) + mprintf(L"%ls ",St(MListSolid)); + if (hd.Encrypted) + mprintf(L"%ls ",St(MListEnc)); + } + + if (hd.Version) + { + uint Version=ParseVersionFileName(Name,false); + if (Version!=0) + mprintf(L"\n%12ls: %u",St(MListFileVer),Version); + } + + if (hd.UnixOwnerSet) + { + mprintf(L"\n%12ls: ",L"Unix owner"); + if (*hd.UnixOwnerName!=0) + mprintf(L"%ls:",GetWide(hd.UnixOwnerName)); + if (*hd.UnixGroupName!=0) + mprintf(L"%ls",GetWide(hd.UnixGroupName)); + if ((*hd.UnixOwnerName!=0 || *hd.UnixGroupName!=0) && (hd.UnixOwnerNumeric || hd.UnixGroupNumeric)) + mprintf(L" "); + if (hd.UnixOwnerNumeric) + mprintf(L"#%d:",hd.UnixOwnerID); + if (hd.UnixGroupNumeric) + mprintf(L"#%d:",hd.UnixGroupID); + } + + mprintf(L"\n"); + return; + } + + mprintf(L"\n%c%10ls %9ls ",hd.Encrypted ? '*' : ' ',AttrStr,UnpSizeText); + + if (Verbose) + mprintf(L"%9ls %4ls ",PackSizeText,RatioStr); + + mprintf(L" %ls ",DateStr); + + if (Verbose) + { + if (hd.FileHash.Type==HASH_CRC32) + mprintf(L"%8.8X ",hd.FileHash.CRC32); + else + if (hd.FileHash.Type==HASH_BLAKE2) + { + byte *S=hd.FileHash.Digest; + mprintf(L"%02x%02x..%02x ",S[0],S[1],S[31]); + } + else + mprintf(L"???????? "); + } + mprintf(L"%ls",Name); +} + +/* +void ListSymLink(Archive &Arc) +{ + if (Arc.FileHead.HSType==HSYS_UNIX && (Arc.FileHead.FileAttr & 0xF000)==0xA000) + if (Arc.FileHead.Encrypted) + { + // Link data are encrypted. We would need to ask for password + // and initialize decryption routine to display the link target. + mprintf(L"\n%22ls %ls",L"-->",L"*<-?->"); + } + else + { + char FileName[NM]; + uint DataSize=(uint)Min(Arc.FileHead.PackSize,sizeof(FileName)-1); + Arc.Read(FileName,DataSize); + FileName[DataSize]=0; + mprintf(L"\n%22ls %ls",L"-->",GetWide(FileName)); + } +} +*/ + +void ListFileAttr(uint A,HOST_SYSTEM_TYPE HostType,wchar *AttrStr,size_t AttrSize) +{ + switch(HostType) + { + case HSYS_WINDOWS: + swprintf(AttrStr,AttrSize,L"%c%c%c%c%c%c%c", + (A & 0x2000)!=0 ? 'I' : '.', // Not content indexed. + (A & 0x0800)!=0 ? 'C' : '.', // Compressed. + (A & 0x0020)!=0 ? 'A' : '.', // Archive. + (A & 0x0010)!=0 ? 'D' : '.', // Directory. + (A & 0x0004)!=0 ? 'S' : '.', // System. + (A & 0x0002)!=0 ? 'H' : '.', // Hidden. + (A & 0x0001)!=0 ? 'R' : '.'); // Read-only. + break; + case HSYS_UNIX: + switch (A & 0xF000) + { + case 0x4000: + AttrStr[0]='d'; + break; + case 0xA000: + AttrStr[0]='l'; + break; + default: + AttrStr[0]='-'; + break; + } + swprintf(AttrStr+1,AttrSize-1,L"%c%c%c%c%c%c%c%c%c", + (A & 0x0100) ? 'r' : '-', + (A & 0x0080) ? 'w' : '-', + (A & 0x0040) ? ((A & 0x0800)!=0 ? 's':'x'):((A & 0x0800)!=0 ? 'S':'-'), + (A & 0x0020) ? 'r' : '-', + (A & 0x0010) ? 'w' : '-', + (A & 0x0008) ? ((A & 0x0400)!=0 ? 's':'x'):((A & 0x0400)!=0 ? 'S':'-'), + (A & 0x0004) ? 'r' : '-', + (A & 0x0002) ? 'w' : '-', + (A & 0x0001) ? ((A & 0x200)!=0 ? 't' : 'x') : '-'); + break; + case HSYS_UNKNOWN: + wcscpy(AttrStr,L"?"); + break; + } +} diff --git a/reference/unrar/list.hpp b/reference/unrar/list.hpp new file mode 100644 index 00000000..7721ae52 --- /dev/null +++ b/reference/unrar/list.hpp @@ -0,0 +1,6 @@ +#ifndef _RAR_LIST_ +#define _RAR_LIST_ + +void ListArchive(CommandData *Cmd); + +#endif diff --git a/reference/unrar/loclang.hpp b/reference/unrar/loclang.hpp new file mode 100644 index 00000000..427128b4 --- /dev/null +++ b/reference/unrar/loclang.hpp @@ -0,0 +1,381 @@ +#define MYesNo L"_Yes_No" +#define MYesNoAll L"_Yes_No_All" +#define MYesNoAllQ L"_Yes_No_All_nEver_Quit" +#define MYesNoAllRenQ L"_Yes_No_All_nEver_Rename_Quit" +#define MContinueQuit L"_Continue_Quit" +#define MRetryAbort L"_Retry_Abort" +#define MCopyright L"\nRAR %s Copyright (c) 1993-%d Alexander Roshal %d %s %d" +#define MRegTo L"\nRegistered to %s\n" +#define MShare L"\nTrial version Type 'rar -?' for help\n" +#define MRegKeyWarning L"\nAvailable license key is valid only for %s\n" +#define MUCopyright L"\nUNRAR %s freeware Copyright (c) 1993-%d Alexander Roshal\n" +#define MBeta L"beta" +#define Mx86 L"x86" +#define Mx64 L"x64" +#define MMonthJan L"Jan" +#define MMonthFeb L"Feb" +#define MMonthMar L"Mar" +#define MMonthApr L"Apr" +#define MMonthMay L"May" +#define MMonthJun L"Jun" +#define MMonthJul L"Jul" +#define MMonthAug L"Aug" +#define MMonthSep L"Sep" +#define MMonthOct L"Oct" +#define MMonthNov L"Nov" +#define MMonthDec L"Dec" +#define MRARTitle1 L"\nUsage: rar - - " +#define MUNRARTitle1 L"\nUsage: unrar - - " +#define MRARTitle2 L"\n <@listfiles...> " +#define MCHelpCmd L"\n\n" +#define MCHelpCmdA L"\n a Add files to archive" +#define MCHelpCmdC L"\n c Add archive comment" +#define MCHelpCmdCH L"\n ch Change archive parameters" +#define MCHelpCmdCW L"\n cw Write archive comment to file" +#define MCHelpCmdD L"\n d Delete files from archive" +#define MCHelpCmdE L"\n e Extract files without archived paths" +#define MCHelpCmdF L"\n f Freshen files in archive" +#define MCHelpCmdI L"\n i[par]= Find string in archives" +#define MCHelpCmdK L"\n k Lock archive" +#define MCHelpCmdL L"\n l[t[a],b] List archive contents [technical[all], bare]" +#define MCHelpCmdM L"\n m[f] Move to archive [files only]" +#define MCHelpCmdP L"\n p Print file to stdout" +#define MCHelpCmdR L"\n r Repair archive" +#define MCHelpCmdRC L"\n rc Reconstruct missing volumes" +#define MCHelpCmdRN L"\n rn Rename archived files" +#define MCHelpCmdRR L"\n rr[N] Add data recovery record" +#define MCHelpCmdRV L"\n rv[N] Create recovery volumes" +#define MCHelpCmdS L"\n s[name|-] Convert archive to or from SFX" +#define MCHelpCmdT L"\n t Test archive files" +#define MCHelpCmdU L"\n u Update files in archive" +#define MCHelpCmdV L"\n v[t[a],b] Verbosely list archive contents [technical[all],bare]" +#define MCHelpCmdX L"\n x Extract files with full path" +#define MCHelpSw L"\n\n" +#define MCHelpSwm L"\n - Stop switches scanning" +#define MCHelpSwAT L"\n @[+] Disable [enable] file lists" +#define MCHelpSwAC L"\n ac Clear Archive attribute after compression or extraction" +#define MCHelpSwAD L"\n ad Append archive name to destination path" +#define MCHelpSwAG L"\n ag[format] Generate archive name using the current date" +#define MCHelpSwAI L"\n ai Ignore file attributes" +#define MCHelpSwAO L"\n ao Add files with Archive attribute set" +#define MCHelpSwAP L"\n ap Set path inside archive" +#define MCHelpSwAS L"\n as Synchronize archive contents" +#define MCHelpSwCm L"\n c- Disable comments show" +#define MCHelpSwCFGm L"\n cfg- Disable read configuration" +#define MCHelpSwCL L"\n cl Convert names to lower case" +#define MCHelpSwCU L"\n cu Convert names to upper case" +#define MCHelpSwDF L"\n df Delete files after archiving" +#define MCHelpSwDH L"\n dh Open shared files" +#define MCHelpSwDR L"\n dr Delete files to Recycle Bin" +#define MCHelpSwDS L"\n ds Disable name sort for solid archive" +#define MCHelpSwDW L"\n dw Wipe files after archiving" +#define MCHelpSwEa L"\n e[+] Set file exclude and include attributes" +#define MCHelpSwED L"\n ed Do not add empty directories" +#define MCHelpSwEN L"\n en Do not put 'end of archive' block" +#define MCHelpSwEP L"\n ep Exclude paths from names" +#define MCHelpSwEP1 L"\n ep1 Exclude base directory from names" +#define MCHelpSwEP2 L"\n ep2 Expand paths to full" +#define MCHelpSwEP3 L"\n ep3 Expand paths to full including the drive letter" +#define MCHelpSwF L"\n f Freshen files" +#define MCHelpSwHP L"\n hp[password] Encrypt both file data and headers" +#define MCHelpSwHT L"\n ht[b|c] Select hash type [BLAKE2,CRC32] for file checksum" +#define MCHelpSwIDP L"\n id[c,d,p,q] Disable messages" +#define MCHelpSwIEML L"\n ieml[addr] Send archive by email" +#define MCHelpSwIERR L"\n ierr Send all messages to stderr" +#define MCHelpSwILOG L"\n ilog[name] Log errors to file (registered versions only)" +#define MCHelpSwINUL L"\n inul Disable all messages" +#define MCHelpSwIOFF L"\n ioff Turn PC off after completing an operation" +#define MCHelpSwISND L"\n isnd Enable sound" +#define MCHelpSwIVER L"\n iver Display the version number" +#define MCHelpSwK L"\n k Lock archive" +#define MCHelpSwKB L"\n kb Keep broken extracted files" +#define MCHelpSwLog L"\n log[f][=name] Write names to log file" +#define MCHelpSwMn L"\n m<0..5> Set compression level (0-store...3-default...5-maximal)" +#define MCHelpSwMA L"\n ma[4|5] Specify a version of archiving format" +#define MCHelpSwMC L"\n mc Set advanced compression parameters" +#define MCHelpSwMD L"\n md[k,m,g] Dictionary size in KB, MB or GB" +#define MCHelpSwMS L"\n ms[ext;ext] Specify file types to store" +#define MCHelpSwMT L"\n mt Set the number of threads" +#define MCHelpSwN L"\n n Additionally filter included files" +#define MCHelpSwNa L"\n n@ Read additional filter masks from stdin" +#define MCHelpSwNal L"\n n@ Read additional filter masks from list file" +#define MCHelpSwO L"\n o[+|-] Set the overwrite mode" +#define MCHelpSwOC L"\n oc Set NTFS Compressed attribute" +#define MCHelpSwOH L"\n oh Save hard links as the link instead of the file" +#define MCHelpSwOI L"\n oi[0-4][:min] Save identical files as references" +#define MCHelpSwOL L"\n ol[a] Process symbolic links as the link [absolute paths]" +#define MCHelpSwONI L"\n oni Allow potentially incompatible names" +#define MCHelpSwOR L"\n or Rename files automatically" +#define MCHelpSwOS L"\n os Save NTFS streams" +#define MCHelpSwOW L"\n ow Save or restore file owner and group" +#define MCHelpSwP L"\n p[password] Set password" +#define MCHelpSwPm L"\n p- Do not query password" +#define MCHelpSwQO L"\n qo[-|+] Add quick open information [none|force]" +#define MCHelpSwR L"\n r Recurse subdirectories" +#define MCHelpSwRm L"\n r- Disable recursion" +#define MCHelpSwR0 L"\n r0 Recurse subdirectories for wildcard names only" +#define MCHelpSwRI L"\n ri

[:] Set priority (0-default,1-min..15-max) and sleep time in ms" +#define MCHelpSwRR L"\n rr[N] Add data recovery record" +#define MCHelpSwRV L"\n rv[N] Create recovery volumes" +#define MCHelpSwS L"\n s[,v[-],e] Create solid archive" +#define MCHelpSwSm L"\n s- Disable solid archiving" +#define MCHelpSwSC L"\n sc[obj] Specify the character set" +#define MCHelpSwSFX L"\n sfx[name] Create SFX archive" +#define MCHelpSwSI L"\n si[name] Read data from standard input (stdin)" +#define MCHelpSwSL L"\n sl Process files with size less than specified" +#define MCHelpSwSM L"\n sm Process files with size more than specified" +#define MCHelpSwT L"\n t Test files after archiving" +#define MCHelpSwTK L"\n tk Keep original archive time" +#define MCHelpSwTL L"\n tl Set archive time to latest file" +#define MCHelpSwTN L"\n tn

/// Entry is split among multiple volumes /// - public abstract bool IsSplit { get; } + public abstract bool IsSplitAfter { get; } internal abstract IEnumerable Parts { get; } internal bool IsSolid { get; set; } diff --git a/src/SharpCompress/Common/GZip/GZipEntry.cs b/src/SharpCompress/Common/GZip/GZipEntry.cs index 6c80ddff..849d4741 100644 --- a/src/SharpCompress/Common/GZip/GZipEntry.cs +++ b/src/SharpCompress/Common/GZip/GZipEntry.cs @@ -36,7 +36,7 @@ namespace SharpCompress.Common.GZip public override bool IsDirectory => false; - public override bool IsSplit => false; + public override bool IsSplitAfter => false; internal override IEnumerable Parts => filePart.AsEnumerable(); diff --git a/src/SharpCompress/Common/IEntry.cs b/src/SharpCompress/Common/IEntry.cs index ac15828c..e8f13a30 100644 --- a/src/SharpCompress/Common/IEntry.cs +++ b/src/SharpCompress/Common/IEntry.cs @@ -12,7 +12,7 @@ namespace SharpCompress.Common string Key { get; } bool IsDirectory { get; } bool IsEncrypted { get; } - bool IsSplit { get; } + bool IsSplitAfter { get; } DateTime? LastAccessedTime { get; } DateTime? LastModifiedTime { get; } long Size { get; } diff --git a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs index b6181c5b..0d5524c8 100644 --- a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs @@ -31,7 +31,8 @@ namespace SharpCompress.Common.Rar.Headers Flags = reader.ReadRarVIntUInt16(); var lvalue = checked((long)reader.ReadRarVInt()); -//!!! rar5 TODO: this may cause problems with user code, Unpack20 seems to use this field to decompress + + // long.MaxValue causes the unpack code to finish when the input stream is exhausted UncompressedSize = HasFlag(FileFlagsV5.UnpackedSizeUnknown) ? long.MaxValue : lvalue; FileAttributes = reader.ReadRarVIntUInt32(); @@ -56,6 +57,7 @@ namespace SharpCompress.Common.Rar.Headers // 7th bit (0x0040) defines the solid flag. If it is set, RAR continues to use the compression dictionary left after processing preceding files. // It can be set only for file headers and is never set for service headers. IsSolid = (us & 0x40) == 0x40; + if (IsSolid != HasHeaderFlag(HeaderFlagsV5.Solid_TESTME)) throw new InvalidFormatException("rar solid flag base header != file header"); // Bits 8 - 10 (0x0380 mask) define the compression method. Currently only values 0 - 5 are used. 0 means no compression. CompressionMethod = (byte)((us >> 7) & 0x7); @@ -63,6 +65,7 @@ namespace SharpCompress.Common.Rar.Headers // Bits 11 - 14 (0x3c00) define the minimum size of dictionary size required to extract data. Value 0 means 128 KB, 1 - 256 KB, ..., 14 - 2048 MB, 15 - 4096 MB. // 2 ^ (17 + x) R5DictSize_2_17plusX = (byte)((us >> 10) & 0xf); + //WindowSize = hd->Dir ? 0:0x10000<<((hd->Flags & LHD_WINDOWMASK)>>5) HostOs = reader.ReadRarVIntByte(); @@ -393,8 +396,14 @@ namespace SharpCompress.Common.Rar.Headers // 5 - best compression internal byte CompressionMethod { get; private set; } - // see unpack.cs + // eg (see DoUnpack()) + //case 15: // rar 1.5 compression + //case 20: // rar 2.x compression + //case 26: // files larger than 2GB + //case 29: // rar 3.x compression + //case 50: // RAR 5.0 compression algorithm. internal byte CompressionAlgorithm { get; private set; } + public bool IsSolid { get; private set; } internal byte[] R4Salt { get; private set; } @@ -410,8 +419,7 @@ namespace SharpCompress.Common.Rar.Headers internal long DataStartPosition { get; set; } public Stream PackedStream { get; set; } -//!!! TODO rar5 - public bool IsSplit => HasFlag(FileFlagsV4.SplitAfter); + public bool IsSplitAfter => IsRar5 ? HasHeaderFlag(HeaderFlagsV5.SplitAfter) : HasFlag(FileFlagsV4.SplitAfter); public bool IsDirectory => HasFlag(IsRar5 ? FileFlagsV5.Directory : FileFlagsV4.Directory); diff --git a/src/SharpCompress/Common/Rar/Headers/Flags.cs b/src/SharpCompress/Common/Rar/Headers/Flags.cs index a3811740..746b9018 100644 --- a/src/SharpCompress/Common/Rar/Headers/Flags.cs +++ b/src/SharpCompress/Common/Rar/Headers/Flags.cs @@ -45,10 +45,10 @@ namespace SharpCompress.Common.Rar.Headers public const ushort HasExtra = 0x0001; public const ushort HasData = 0x0002; public const ushort Keep = 0x0004; // block must be kept during an update - public const ushort VolumeContinuation = 0x0008; - public const ushort VolumeIncomplete = 0x0010; - public const ushort Solid = 0x0020; // ??? - public const ushort PreserveChild = 0x0040; + public const ushort SplitBefore = 0x0008; + public const ushort SplitAfter = 0x0010; + public const ushort Solid_TESTME = 0x0020; // ??? Block depends on preceding file block. + public const ushort PreserveChild = 0x0040; // ???? Preserve a child block if host block is modified } internal static class ArchiveFlagsV4 diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs index 8abf8670..d4bead44 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs @@ -35,11 +35,6 @@ namespace SharpCompress.Common.Rar.Headers HeaderCode = reader.ReadRarVIntByte(); HeaderFlags = reader.ReadRarVIntUInt16(2); -// if (HasHeaderFlag(HeaderFlagsV5.VolumeContinuation)) throw new NotImplementedException(); -// if (HasHeaderFlag(HeaderFlagsV5.VolumeIncomplete)) throw new NotImplementedException(); -// if (HasHeaderFlag(HeaderFlagsV5.Solid)) throw new NotImplementedException(); -// if (HasHeaderFlag(HeaderFlagsV5.PreserveChild)) throw new NotImplementedException(); - if (HasHeaderFlag(HeaderFlagsV5.HasExtra)) { ExtraSize = reader.ReadRarVIntUInt32(); @@ -111,7 +106,7 @@ namespace SharpCompress.Common.Rar.Headers protected ushort HeaderFlags { get; } - private bool HasHeaderFlag(ushort flag) + protected bool HasHeaderFlag(ushort flag) { return (HeaderFlags & flag) == flag; } diff --git a/src/SharpCompress/Common/Rar/RarEntry.cs b/src/SharpCompress/Common/Rar/RarEntry.cs index 105a306a..cec1eb44 100644 --- a/src/SharpCompress/Common/Rar/RarEntry.cs +++ b/src/SharpCompress/Common/Rar/RarEntry.cs @@ -47,7 +47,7 @@ namespace SharpCompress.Common.Rar ///
public override bool IsDirectory => FileHeader.IsDirectory; - public override bool IsSplit => FileHeader.IsSplit; + public override bool IsSplitAfter => FileHeader.IsSplitAfter; public override string ToString() { diff --git a/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs b/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs index dd23d629..584bfb95 100644 --- a/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs +++ b/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs @@ -34,7 +34,7 @@ namespace SharpCompress.Common.SevenZip public override bool IsDirectory => FilePart.Header.IsDir; - public override bool IsSplit => false; + public override bool IsSplitAfter => false; public override int? Attrib => (int)FilePart.Header.Attrib; diff --git a/src/SharpCompress/Common/Tar/TarEntry.cs b/src/SharpCompress/Common/Tar/TarEntry.cs index 5208bce1..6ff6e19f 100644 --- a/src/SharpCompress/Common/Tar/TarEntry.cs +++ b/src/SharpCompress/Common/Tar/TarEntry.cs @@ -39,7 +39,7 @@ namespace SharpCompress.Common.Tar public override bool IsDirectory => filePart.Header.EntryType == EntryType.Directory; - public override bool IsSplit => false; + public override bool IsSplitAfter => false; internal override IEnumerable Parts => filePart.AsEnumerable(); diff --git a/src/SharpCompress/Common/Zip/ZipEntry.cs b/src/SharpCompress/Common/Zip/ZipEntry.cs index 8b8c7337..b721d57f 100644 --- a/src/SharpCompress/Common/Zip/ZipEntry.cs +++ b/src/SharpCompress/Common/Zip/ZipEntry.cs @@ -72,7 +72,7 @@ namespace SharpCompress.Common.Zip public override bool IsDirectory => filePart.Header.IsDirectory; - public override bool IsSplit => false; + public override bool IsSplitAfter => false; internal override IEnumerable Parts => filePart.AsEnumerable(); } diff --git a/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs b/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs index 69e7734d..57e9fd6f 100644 --- a/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs +++ b/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs @@ -89,7 +89,7 @@ namespace SharpCompress.Compressors.Rar currentCount -= read; totalRead += read; if (((maxPosition - currentPosition) == 0) - && filePartEnumerator.Current.FileHeader.IsSplit) + && filePartEnumerator.Current.FileHeader.IsSplitAfter) { if (filePartEnumerator.Current.FileHeader.R4Salt != null) { diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_hpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_hpp.cs index 0fd6bd48..0eb0b64a 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_hpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_hpp.cs @@ -5,19 +5,15 @@ internal partial class BitInput { public const int MAX_SIZE=0x8000; // Size of input buffer. - public - int InAddr; // Curent byte position in the buffer. - public - int InBit; // Current bit position in the current byte. + public int InAddr; // Curent byte position in the buffer. + public int InBit; // Current bit position in the current byte. - public - bool ExternalBuffer; + public bool ExternalBuffer; //BitInput(bool AllocBuffer); //~BitInput(); - public - byte[] InBuf; // Dynamically allocated input buffer. + public byte[] InBuf; // Dynamically allocated input buffer. public void InitBitInput() @@ -26,8 +22,7 @@ internal partial class BitInput } // Move forward by 'Bits' bits. - public - void addbits(uint _Bits) + public void addbits(uint _Bits) { var Bits = checked((int)_Bits); Bits+=InBit; @@ -37,8 +32,7 @@ internal partial class BitInput // Return 16 bits from current position in the buffer. // Bit at (InAddr,InBit) has the highest position in returning data. - public - uint getbits() + public uint getbits() { uint BitField=(uint)InBuf[InAddr] << 16; BitField|=(uint)InBuf[InAddr+1] << 8; @@ -49,8 +43,7 @@ internal partial class BitInput // Return 32 bits from current position in the buffer. // Bit at (InAddr,InBit) has the highest position in returning data. - public - uint getbits32() + public uint getbits32() { uint BitField=(uint)InBuf[InAddr] << 24; BitField|=(uint)InBuf[InAddr+1] << 16; diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs index 65ec8690..f7c8f536 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs @@ -13,15 +13,17 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 public void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream) { + // may be long.MaxValue which could indicate unknown size (not present in header) DestUnpSize = fileHeader.UncompressedSize; this.fileHeader = fileHeader; this.readStream = readStream; this.writeStream = writeStream; - if (!fileHeader.IsSolid) - { - Init(null); - Init(size_t WinSize,bool Solid) - } +// if (!fileHeader.IsSolid) +// { +// Init(null); +// } +fileHeader. + Init(size_t WinSize, fileHeader.IsSolid) Suspended = false; DoUnpack(); } @@ -31,23 +33,53 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 if (this.fileHeader.CompressionMethod == 0) { UnstoreFile(); - return; + } else { + DoUnpack(uint Method,bool Solid); } - DoUnpack(uint Method,bool Solid); } + private void UnstoreFile() + { + var b = new byte[0x10000]; + while (true) + { + int n = this.readStream.Read(b, 0, (int)Math.Min(b.Length, DestUnpSize)); + if (n == 0) + { + break; + } + + n = n < DestUnpSize ? n : (int)DestUnpSize; + this.writeStream.Write(b, 0, n); + if (DestUnpSize >= 0) + { + DestUnpSize -= n; + } + if (Suspended) + { + return; + } + } + } + public bool Suspended { get; set; } public long DestSize { get => DestUnpSize; } - public int Char { - get { throw new NotImplementedException(); } + public int Char + { + get + { + // TODO: coderb: not sure where the "MAXSIZE-30" comes from, ported from V1 code + if (InAddr > MAX_SIZE - 30) + { + UnpReadBuf(); + } + return InBuf[InAddr++]; + } } - public int PpmEscChar { - get { throw new NotImplementedException(); } - set { throw new NotImplementedException(); } - } + public int PpmEscChar { get => this.PPMEscChar; set => this.PPMEscChar = value; } } } #endif \ No newline at end of file diff --git a/src/SharpCompress/Readers/Rar/MultiVolumeRarReader.cs b/src/SharpCompress/Readers/Rar/MultiVolumeRarReader.cs index 068d7572..cfbdc5c0 100644 --- a/src/SharpCompress/Readers/Rar/MultiVolumeRarReader.cs +++ b/src/SharpCompress/Readers/Rar/MultiVolumeRarReader.cs @@ -89,7 +89,7 @@ namespace SharpCompress.Readers.Rar return true; } - if (!reader.Entry.IsSplit) + if (!reader.Entry.IsSplitAfter) { return false; } From 093a5fdf31b6cead6dfe1047968870247d5a74e0 Mon Sep 17 00:00:00 2001 From: coderb Date: Mon, 18 Dec 2017 09:25:55 -0500 Subject: [PATCH 26/61] rar5: oops --- src/SharpCompress/Archives/Rar/RarArchive.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SharpCompress/Archives/Rar/RarArchive.cs b/src/SharpCompress/Archives/Rar/RarArchive.cs index ad41cb55..ab341e0c 100644 --- a/src/SharpCompress/Archives/Rar/RarArchive.cs +++ b/src/SharpCompress/Archives/Rar/RarArchive.cs @@ -41,6 +41,7 @@ namespace SharpCompress.Archives.Rar internal RarArchive(IEnumerable streams, ReaderOptions options) : base(ArchiveType.Rar, streams, options) { + Unpack = new SharpCompress.Compressors.Rar.UnpackV1.Unpack(); } protected override IEnumerable LoadEntries(IEnumerable volumes) From c7700944259cdf9e344ccd14b6712a31b5281cdf Mon Sep 17 00:00:00 2001 From: coderb Date: Mon, 18 Dec 2017 09:46:18 -0500 Subject: [PATCH 27/61] rar5: fileheader fixes --- .../Common/Rar/Headers/FileHeader.cs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs index 0d5524c8..b1c4137f 100644 --- a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs @@ -1,3 +1,13 @@ +#if !Rar2017_64bit +using nint = System.Int32; +using nuint = System.UInt32; +using size_t = System.UInt32; +#else +using nint = System.Int64; +using nuint = System.UInt64; +using size_t = System.UInt64; +#endif + using SharpCompress.IO; using System; using System.IO; @@ -63,9 +73,7 @@ namespace SharpCompress.Common.Rar.Headers CompressionMethod = (byte)((us >> 7) & 0x7); // Bits 11 - 14 (0x3c00) define the minimum size of dictionary size required to extract data. Value 0 means 128 KB, 1 - 256 KB, ..., 14 - 2048 MB, 15 - 4096 MB. - // 2 ^ (17 + x) - R5DictSize_2_17plusX = (byte)((us >> 10) & 0xf); - //WindowSize = hd->Dir ? 0:0x10000<<((hd->Flags & LHD_WINDOWMASK)>>5) + WindowSize = IsDirectory ? 0 : ((size_t)0x20000) << ((us>>10) & 0xf); HostOs = reader.ReadRarVIntByte(); @@ -170,6 +178,7 @@ namespace SharpCompress.Common.Rar.Headers } } + private static DateTime ReadExtendedTimeV5(MarkingBinaryReader reader, bool isWindowsTime) { if (isWindowsTime) @@ -202,6 +211,7 @@ namespace SharpCompress.Common.Rar.Headers { Flags = HeaderFlags; IsSolid = HasFlag(FileFlagsV4.Solid); + WindowSize = IsDirectory ? 0U : ((size_t)0x10000) << ((Flags & FileFlagsV4.WindowMask) >> 5); uint lowUncompressedSize = reader.ReadUInt32(); @@ -406,8 +416,10 @@ namespace SharpCompress.Common.Rar.Headers public bool IsSolid { get; private set; } + // unused for UnpackV1 implementation (limitation) + internal size_t WindowSize { get; private set; } + internal byte[] R4Salt { get; private set; } - internal byte R5DictSize_2_17plusX { get; private set; } private byte HostOs { get; set; } internal uint FileAttributes { get; private set; } From 22b008f72380b5853dadb353871df01fc62b09e7 Mon Sep 17 00:00:00 2001 From: coderb Date: Mon, 18 Dec 2017 09:51:24 -0500 Subject: [PATCH 28/61] rar5: unpack wip --- src/SharpCompress/Common/Rar/Headers/FileHeader.cs | 1 + .../Compressors/Rar/UnpackV2017/Unpack.cs | 13 +++++-------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs index b1c4137f..48d2f3e0 100644 --- a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs @@ -405,6 +405,7 @@ namespace SharpCompress.Common.Rar.Headers // 4 - good compression // 5 - best compression internal byte CompressionMethod { get; private set; } + internal bool IsStored { get { return CompressionMethod == 0; } } // eg (see DoUnpack()) //case 15: // rar 1.5 compression diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs index f7c8f536..bbba9d8a 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs @@ -18,23 +18,20 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 this.fileHeader = fileHeader; this.readStream = readStream; this.writeStream = writeStream; -// if (!fileHeader.IsSolid) -// { -// Init(null); -// } -fileHeader. - Init(size_t WinSize, fileHeader.IsSolid) + if (!fileHeader.IsStored) { + Init(fileHeader.WindowSize, fileHeader.IsSolid); + } Suspended = false; DoUnpack(); } public void DoUnpack() { - if (this.fileHeader.CompressionMethod == 0) + if (this.fileHeader.IsStored) { UnstoreFile(); } else { - DoUnpack(uint Method,bool Solid); + DoUnpack(this.fileHeader.CompressionMethod, this.fileHeader.IsSolid); } } From 59e4383209754182fa334b6fb7183f9322440cf8 Mon Sep 17 00:00:00 2001 From: coderb Date: Mon, 18 Dec 2017 10:02:34 -0500 Subject: [PATCH 29/61] rar5: unpack wip --- src/SharpCompress/Compressors/Rar/IRarUnpack.cs | 3 +++ .../Compressors/Rar/UnpackV2017/Unpack.cs | 16 +++------------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/IRarUnpack.cs b/src/SharpCompress/Compressors/Rar/IRarUnpack.cs index 256fa08f..ecd70a47 100644 --- a/src/SharpCompress/Compressors/Rar/IRarUnpack.cs +++ b/src/SharpCompress/Compressors/Rar/IRarUnpack.cs @@ -7,7 +7,10 @@ namespace SharpCompress.Compressors.Rar { void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream); void DoUnpack(); + + // eg u/i pause/resume button bool Suspended { get; set; } + long DestSize { get; } int Char { get; } int PpmEscChar { get; set; } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs index bbba9d8a..7079e0d9 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs @@ -38,25 +38,15 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 private void UnstoreFile() { var b = new byte[0x10000]; - while (true) - { + do { int n = this.readStream.Read(b, 0, (int)Math.Min(b.Length, DestUnpSize)); if (n == 0) { break; } - - n = n < DestUnpSize ? n : (int)DestUnpSize; this.writeStream.Write(b, 0, n); - if (DestUnpSize >= 0) - { - DestUnpSize -= n; - } - if (Suspended) - { - return; - } - } + DestUnpSize -= n; + } while (!Suspended); } public bool Suspended { get; set; } From 122a732206fb623e6bca71b7f188ae7b6446846e Mon Sep 17 00:00:00 2001 From: coderb Date: Mon, 18 Dec 2017 10:18:17 -0500 Subject: [PATCH 30/61] unrar5: pull in fast memset code --- .../Rar/UnpackV2017/Unpack.unpack_cpp.cs | 15 ++++--- src/SharpCompress/Utility.cs | 42 +++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs index 54309de4..334bde62 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs @@ -14,6 +14,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using SharpCompress.Common; using static SharpCompress.Compressors.Rar.UnpackV2017.UnpackGlobal; using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; @@ -73,7 +74,8 @@ void Init(size_t WinSize,bool Solid) // If 32-bit RAR unpacks an archive with 4 GB dictionary, the window size // will be 0 because of size_t overflow. Let's issue the memory error. if (WinSize==0) - ErrHandler.MemoryError(); + //ErrHandler.MemoryError(); + throw new InvalidFormatException("invalid window size (possibly due to a rar file with a 4GB being unpacked on a 32-bit platform)"); // Minimum window size must be at least twice more than maximum possible // size of filter block, which is 0x10000 in RAR now. If window size is @@ -98,22 +100,24 @@ void Init(size_t WinSize,bool Solid) // We do not handle growth for existing fragmented window. if (Grow && Fragmented) - throw std::bad_alloc(); + //throw std::bad_alloc(); + throw new InvalidFormatException("Grow && Fragmented"); - byte *NewWindow=Fragmented ? null : (byte *)malloc(WinSize); + byte[] NewWindow=Fragmented ? null : new byte[WinSize]; if (NewWindow==null) if (Grow || WinSize<0x1000000) { // We do not support growth for new fragmented window. // Also exclude RAR4 and small dictionaries. - throw std::bad_alloc(); + //throw std::bad_alloc(); + throw new InvalidFormatException("Grow || WinSize<0x1000000"); } else { if (Window!=null) // If allocated by preceding files. { - free(Window); + //free(Window); Window=null; } FragWindow.Init(WinSize); @@ -126,6 +130,7 @@ void Init(size_t WinSize,bool Solid) // RAR files, which may access unused areas of sliding dictionary. memset(NewWindow,0,WinSize); + // If Window is not NULL, it means that window size has grown. // In solid streams we need to copy data to a new window in such case. // RAR archiving code does not allow it in solid streams now, diff --git a/src/SharpCompress/Utility.cs b/src/SharpCompress/Utility.cs index a77360fb..8700d4af 100644 --- a/src/SharpCompress/Utility.cs +++ b/src/SharpCompress/Utility.cs @@ -2,6 +2,9 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.InteropServices; #if NETCORE using SharpCompress.Buffers; #endif @@ -73,6 +76,45 @@ namespace SharpCompress } } +#if NET45 + // super fast memset, up to 40x faster than for loop on large arrays + // see https://stackoverflow.com/questions/1897555/what-is-the-equivalent-of-memset-in-c + private static readonly Action MemsetDelegate = CreateMemsetDelegate(); + + private static Action CreateMemsetDelegate() { + var dynamicMethod = new DynamicMethod( + "Memset", + MethodAttributes.Public | MethodAttributes.Static, + CallingConventions.Standard, + null, + new[] { typeof(IntPtr), typeof(byte), typeof(int) }, + typeof(Utility), + true); + var generator = dynamicMethod.GetILGenerator(); + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldarg_2); + generator.Emit(OpCodes.Initblk); + generator.Emit(OpCodes.Ret); + return (Action)dynamicMethod.CreateDelegate(typeof(Action)); + } + + public static void Memset(byte[] array, byte what, int length) + { + var gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned); + MemsetDelegate(gcHandle.AddrOfPinnedObject(), what, length); + gcHandle.Free(); + } +#else + public static void Memset(byte[] array, byte what, int length) + { + for(var i = 0; i < length; i++) + { + array[i] = what; + } + } +#endif + /// /// Fills the array with an specific value. /// From b8a308012ff940f3d4638215918c7aee5c27f62a Mon Sep 17 00:00:00 2001 From: coderb Date: Mon, 18 Dec 2017 11:01:37 -0500 Subject: [PATCH 31/61] rar5: unpack work --- .../FragmentedWindow.unpack50frag_cpp.cs | 11 ++++++----- .../Compressors/Rar/UnpackV2017/Unpack.cs | 5 +++++ .../Rar/UnpackV2017/Unpack.unpack20_cpp.cs | 2 +- .../Rar/UnpackV2017/Unpack.unpack30_cpp.cs | 4 ++-- .../Rar/UnpackV2017/Unpack.unpack_cpp.cs | 7 ++++--- .../Compressors/Rar/UnpackV2017/notes.txt | 14 ++++++++++++++ .../Compressors/Rar/UnpackV2017/unpack_hpp.cs | 2 +- src/SharpCompress/Utility.cs | 11 ++++++----- 8 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs index 8c425713..6bff0202 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs @@ -20,10 +20,11 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 { partial class FragmentedWindow { + public FragmentedWindow() { - memset(Mem,0,sizeof(Mem)); - memset(MemSize,0,sizeof(MemSize)); + //memset(Mem,0,sizeof(Mem)); + //memset(MemSize,0,sizeof(MemSize)); } @@ -60,10 +61,10 @@ public void Init(size_t WinSize) // smaller than some arbitrary constant. size_t MinSize=Math.Max(Size/(ASIZE(Mem)-BlockNum), 0x400000); - byte *NewMem=null; + byte[] NewMem=null; while (Size>=MinSize) { - NewMem=(byte *)malloc(Size); + NewMem=new byte[Size]; if (NewMem!=null) break; Size-=Size/32; @@ -73,7 +74,7 @@ public void Init(size_t WinSize) // Clean the window to generate the same output when unpacking corrupt // RAR files, which may access to unused areas of sliding dictionary. - memset(NewMem,0,Size); + Utility.Memset(NewMem,0,Size); Mem[BlockNum]=NewMem; TotalSize+=Size; diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs index 7079e0d9..e50efcfa 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs @@ -13,6 +13,11 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 public void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream) { + // as of 12/2017 .NET limits array indexing to using a signed integer + // MaxWinSize causes unpack to use a fragmented window when the file + // window size exceeds MaxWinSize + MaxWinSize = ((uint)int.MaxValue) + 1; + // may be long.MaxValue which could indicate unknown size (not present in header) DestUnpSize = fileHeader.UncompressedSize; this.fileHeader = fileHeader; diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs index 8d389d70..6e54b90c 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs @@ -198,7 +198,7 @@ bool ReadTables20() UnpAudioBlock=(BitField & 0x8000)!=0; if (!(BitField & 0x4000)) - memset(UnpOldTable20,0,sizeof(UnpOldTable20)); + Utility.Memset(UnpOldTable20,0,UnpOldTable20.Length); Inp.addbits(2); uint TableSize; diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs index 6e915734..ff4252fb 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs @@ -671,7 +671,7 @@ bool ReadTables30() LowDistRepCount=0; if ((BitField & 0x4000) == 0) - memset(UnpOldTable,0,sizeof(UnpOldTable)); + Utility.Memset(UnpOldTable,0,UnpOldTable.Length); Inp.faddbits(2); for (uint I=0;I ulong (x64) [size_t] -> uint (x86) + +size_t is an unsigned data type defined by several C/C++ standards, e.g. the C99 ISO/IEC 9899 standard, that is defined +in stddef.h.1 It can be further imported by inclusion of stdlib.h as this file internally sub includes stddef.h. +This type is used to represent the size of an object. Library functions that take or return sizes expect them to be of type or +have the return type of size_t. Further, the most frequently used compiler-based operator sizeof should evaluate to a constant +value that is compatible with size_t. + + +20171218 +urggh, this allows things like new int[int.MaxValue] but NOT new byte[uint.MaxValue] +currently arrays are limited to being indexed by an int hence int.MaxValue entries. weak. +To get arrays > 2GB on x64 we need to configure + +https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/gcallowverylargeobjects-element \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs index 35a3de44..3873835a 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs @@ -366,7 +366,7 @@ internal partial class Unpack ModelPPM PPM; int PPMEscChar; - byte UnpOldTable[HUFF_TABLE_SIZE30]; + readonly byte [] UnpOldTable = new byte[HUFF_TABLE_SIZE30]; int UnpBlockType; // If we already read decoding tables for Unpack v2,v3,v5. diff --git a/src/SharpCompress/Utility.cs b/src/SharpCompress/Utility.cs index 8700d4af..f24528c8 100644 --- a/src/SharpCompress/Utility.cs +++ b/src/SharpCompress/Utility.cs @@ -79,15 +79,15 @@ namespace SharpCompress #if NET45 // super fast memset, up to 40x faster than for loop on large arrays // see https://stackoverflow.com/questions/1897555/what-is-the-equivalent-of-memset-in-c - private static readonly Action MemsetDelegate = CreateMemsetDelegate(); + private static readonly Action MemsetDelegate = CreateMemsetDelegate(); - private static Action CreateMemsetDelegate() { + private static Action CreateMemsetDelegate() { var dynamicMethod = new DynamicMethod( "Memset", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, null, - new[] { typeof(IntPtr), typeof(byte), typeof(int) }, + new[] { typeof(IntPtr), typeof(byte), typeof(uint) }, typeof(Utility), true); var generator = dynamicMethod.GetILGenerator(); @@ -96,13 +96,13 @@ namespace SharpCompress generator.Emit(OpCodes.Ldarg_2); generator.Emit(OpCodes.Initblk); generator.Emit(OpCodes.Ret); - return (Action)dynamicMethod.CreateDelegate(typeof(Action)); + return (Action)dynamicMethod.CreateDelegate(typeof(Action)); } public static void Memset(byte[] array, byte what, int length) { var gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned); - MemsetDelegate(gcHandle.AddrOfPinnedObject(), what, length); + MemsetDelegate(gcHandle.AddrOfPinnedObject(), what, (uint)length); gcHandle.Free(); } #else @@ -115,6 +115,7 @@ namespace SharpCompress } #endif + /// /// Fills the array with an specific value. /// From 11c1b45335c1ed421ac55ffe234d701621f0316d Mon Sep 17 00:00:00 2001 From: coderb Date: Tue, 19 Dec 2017 13:52:57 -0500 Subject: [PATCH 32/61] rar5: unpack wip --- .../Common/Rar/Headers/RarHeader.cs | 2 ++ .../FragmentedWindow.unpack50frag_cpp.cs | 17 ++++++++++------- .../Compressors/Rar/UnpackV2017/unpack_hpp.cs | 6 +++--- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs index d4bead44..a3fe6a91 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs @@ -4,6 +4,8 @@ using SharpCompress.IO; namespace SharpCompress.Common.Rar.Headers { + // http://www.forensicswiki.org/w/images/5/5b/RARFileStructure.txt + // https://www.rarlab.com/technote.htm internal class RarHeader : IRarHeader { private readonly HeaderType headerType; diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs index 6bff0202..bbd10c84 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs @@ -36,10 +36,10 @@ public FragmentedWindow() void Reset() { - for (uint I=0;I=MinSize) @@ -70,11 +70,13 @@ public void Init(size_t WinSize) Size-=Size/32; } if (NewMem==null) - throw std::bad_alloc(); + //throw std::bad_alloc(); + throw new InvalidOperationException(); // Clean the window to generate the same output when unpacking corrupt // RAR files, which may access to unused areas of sliding dictionary. - Utility.Memset(NewMem,0,Size); + // sharpcompress: don't need this, freshly allocated above + //Utility.Memset(NewMem,0,Size); Mem[BlockNum]=NewMem; TotalSize+=Size; @@ -82,7 +84,8 @@ public void Init(size_t WinSize) BlockNum++; } if (TotalSize Date: Tue, 19 Dec 2017 14:03:53 -0500 Subject: [PATCH 33/61] rar5: unpack wip --- .../Rar/UnpackV2017/Unpack.unpack20_cpp.cs | 2 +- .../Rar/UnpackV2017/Unpack.unpack30_cpp.cs | 19 ++++++++++------- .../Rar/UnpackV2017/Unpack.unpack50_cpp.cs | 3 ++- .../Rar/UnpackV2017/Unpack.unpack_cpp.cs | 3 +-- .../Compressors/Rar/UnpackV2017/unpack_hpp.cs | 21 ++++++++++--------- src/SharpCompress/Utility.cs | 8 +++++++ 6 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs index 6e54b90c..7ed33a4b 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs @@ -303,7 +303,7 @@ void UnpInitData20(bool Solid) UnpChannels=1; memset(AudV,0,sizeof(AudV)); - memset(UnpOldTable20,0,sizeof(UnpOldTable20)); + Utility.Memset(UnpOldTable20, 0, UnpOldTable20.Length); memset(MD,0,sizeof(MD)); } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs index ff4252fb..6ccdeb6a 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs @@ -766,7 +766,7 @@ void UnpInitData30(bool Solid) if (!Solid) { TablesRead3=false; - Utility.Memset(UnpOldTable,0,(uint)UnpOldTable.Length); + Utility.Memset(UnpOldTable, 0, UnpOldTable.Length); PPMEscChar=2; UnpBlockType=BLOCK_LZ; } @@ -778,16 +778,19 @@ void InitFilters30(bool Solid) { if (!Solid) { - OldFilterLengths.SoftReset(); + //OldFilterLengths.SoftReset(); + OldFilterLengths.Clear(); LastFilter=0; - for (size_t I=0;I(this.OldDist, 0, this.OldDist.Length); OldDistPtr=0; LastDist=LastLength=0; // memset(Window,0,MaxWinSize); diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs index 498847eb..909830c9 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs @@ -264,13 +264,14 @@ internal partial class Unpack byte *ReadBufMT; #endif - List FilterSrcMemory = new List(); - List FilterDstMemory = new List(); + readonly List FilterSrcMemory = new List(); + readonly List FilterDstMemory = new List(); // Filters code, one entry per filter. - List Filters = new List(); + readonly List Filters = new List(); - uint OldDist[4],OldDistPtr; + readonly uint[] OldDist = new uint[4]; + uint OldDistPtr; uint LastLength; // LastDist is necessary only for RAR2 and older with circular OldDist @@ -294,7 +295,7 @@ internal partial class Unpack byte[] Window; - FragmentedWindow FragWindow = new FragmentedWindow(); + readonly FragmentedWindow FragWindow = new FragmentedWindow(); bool Fragmented; @@ -331,7 +332,7 @@ internal partial class Unpack DecodeTable MD[4]; // Decode multimedia data, up to 4 channels. - byte UnpOldTable20[MC20*4]; + readonly byte[] UnpOldTable20 = new byte[MC20*4]; bool UnpAudioBlock; uint UnpChannels,UnpCurChannel; int UnpChannelDelta; @@ -381,18 +382,18 @@ internal partial class Unpack // Buffer to read VM filters code. We moved it here from AddVMCode // function to reduce time spent in BitInput constructor. - BitInput VMCodeInp; + readonly BitInput VMCodeInp = new BitInput(true); // Filters code, one entry per filter. - List Filters30 = new List(); + readonly List Filters30 = new List(); // Filters stack, several entrances of same filter are possible. - List PrgStack = new List(); + readonly List PrgStack = new List(); // Lengths of preceding data blocks, one length of one last block // for every filter. Used to reduce the size required to write // the data block length if lengths are repeating. - List OldFilterLengths = new List(); + readonly List OldFilterLengths = new List(); int LastFilter; /***************************** Unpack v 3.0 *********************************/ diff --git a/src/SharpCompress/Utility.cs b/src/SharpCompress/Utility.cs index f24528c8..a334af06 100644 --- a/src/SharpCompress/Utility.cs +++ b/src/SharpCompress/Utility.cs @@ -115,6 +115,14 @@ namespace SharpCompress } #endif + public static void Memset(T[] array, T what, int length) + { + for(var i = 0; i < length; i++) + { + array[i] = what; + } + } + /// /// Fills the array with an specific value. From c3c2fcf4d0d5acc345a3ed749b42ed4679626859 Mon Sep 17 00:00:00 2001 From: coderb Date: Tue, 19 Dec 2017 21:41:36 -0500 Subject: [PATCH 34/61] rar5: unpack wip --- .../Compressors/Rar/Decode/AudioVariables.cs | 26 --- .../Compressors/Rar/Decode/BitDecode.cs | 10 - .../Compressors/Rar/Decode/CodeType.cs | 15 -- .../Compressors/Rar/Decode/Decode.cs | 34 --- .../Compressors/Rar/Decode/DistDecode.cs | 10 - .../Compressors/Rar/Decode/FilterType.cs | 10 - .../Compressors/Rar/Decode/LitDecode.cs | 10 - .../Compressors/Rar/Decode/LowDistDecode.cs | 10 - .../Compressors/Rar/Decode/MultDecode.cs | 10 - .../Compressors/Rar/Decode/PackDef.cs | 48 ---- .../Compressors/Rar/Decode/RepDecode.cs | 10 - .../Compressors/Rar/PPM/BlockTypes.cs | 8 - .../Compressors/Rar/UnpackUtility.cs | 220 ------------------ .../Compressors/Rar/UnpackV1/Unpack.cs | 4 +- .../Compressors/Rar/UnpackV1/Unpack15.cs | 2 +- .../Compressors/Rar/UnpackV1/Unpack20.cs | 2 +- .../Compressors/Rar/UnpackV1/Unpack50.cs | 9 +- .../Compressors/Rar/UnpackV2017/unpack_hpp.cs | 15 +- 18 files changed, 14 insertions(+), 439 deletions(-) delete mode 100644 src/SharpCompress/Compressors/Rar/Decode/AudioVariables.cs delete mode 100644 src/SharpCompress/Compressors/Rar/Decode/BitDecode.cs delete mode 100644 src/SharpCompress/Compressors/Rar/Decode/CodeType.cs delete mode 100644 src/SharpCompress/Compressors/Rar/Decode/Decode.cs delete mode 100644 src/SharpCompress/Compressors/Rar/Decode/DistDecode.cs delete mode 100644 src/SharpCompress/Compressors/Rar/Decode/FilterType.cs delete mode 100644 src/SharpCompress/Compressors/Rar/Decode/LitDecode.cs delete mode 100644 src/SharpCompress/Compressors/Rar/Decode/LowDistDecode.cs delete mode 100644 src/SharpCompress/Compressors/Rar/Decode/MultDecode.cs delete mode 100644 src/SharpCompress/Compressors/Rar/Decode/PackDef.cs delete mode 100644 src/SharpCompress/Compressors/Rar/Decode/RepDecode.cs delete mode 100644 src/SharpCompress/Compressors/Rar/PPM/BlockTypes.cs delete mode 100644 src/SharpCompress/Compressors/Rar/UnpackUtility.cs diff --git a/src/SharpCompress/Compressors/Rar/Decode/AudioVariables.cs b/src/SharpCompress/Compressors/Rar/Decode/AudioVariables.cs deleted file mode 100644 index cb70b1fd..00000000 --- a/src/SharpCompress/Compressors/Rar/Decode/AudioVariables.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace SharpCompress.Compressors.Rar.Decode -{ - internal class AudioVariables - { - internal AudioVariables() - { - Dif = new int[11]; - } - - internal int[] Dif { get; } - internal int ByteCount { get; set; } - internal int D1 { get; set; } - - internal int D2 { get; set; } - internal int D3 { get; set; } - internal int D4 { get; set; } - - internal int K1 { get; set; } - internal int K2 { get; set; } - internal int K3 { get; set; } - internal int K4 { get; set; } - internal int K5 { get; set; } - internal int LastChar { get; set; } - internal int LastDelta { get; set; } - } -} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/Decode/BitDecode.cs b/src/SharpCompress/Compressors/Rar/Decode/BitDecode.cs deleted file mode 100644 index eef2007f..00000000 --- a/src/SharpCompress/Compressors/Rar/Decode/BitDecode.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace SharpCompress.Compressors.Rar.Decode -{ - internal class BitDecode : Decode - { - internal BitDecode() - : base(new int[PackDef.BC]) - { - } - } -} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/Decode/CodeType.cs b/src/SharpCompress/Compressors/Rar/Decode/CodeType.cs deleted file mode 100644 index acbd4aac..00000000 --- a/src/SharpCompress/Compressors/Rar/Decode/CodeType.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace SharpCompress.Compressors.Rar.Decode -{ - internal enum CodeType - { - CODE_HUFFMAN, - CODE_LZ, - CODE_LZ2, - CODE_REPEATLZ, - CODE_CACHELZ, - CODE_STARTFILE, - CODE_ENDFILE, - CODE_VM, - CODE_VMDATA - } -} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/Decode/Decode.cs b/src/SharpCompress/Compressors/Rar/Decode/Decode.cs deleted file mode 100644 index dc5dacee..00000000 --- a/src/SharpCompress/Compressors/Rar/Decode/Decode.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace SharpCompress.Compressors.Rar.Decode -{ - internal class Decode - { - internal Decode() - : this(new int[2]) - { - } - - protected Decode(int[] customDecodeNum) - { - DecodeLen = new int[16]; - DecodePos = new int[16]; - DecodeNum = customDecodeNum; - } - - /// returns the decode Length array - /// decodeLength - /// - internal int[] DecodeLen { get; } - - /// returns the decode num array - /// decodeNum - /// - internal int[] DecodeNum { get; } - - /// returns the decodePos array - /// decodePos - /// - internal int[] DecodePos { get; } - - internal int MaxNum { get; set; } - } -} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/Decode/DistDecode.cs b/src/SharpCompress/Compressors/Rar/Decode/DistDecode.cs deleted file mode 100644 index b5127e2d..00000000 --- a/src/SharpCompress/Compressors/Rar/Decode/DistDecode.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace SharpCompress.Compressors.Rar.Decode -{ - internal class DistDecode : Decode - { - internal DistDecode() - : base(new int[PackDef.DC]) - { - } - } -} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/Decode/FilterType.cs b/src/SharpCompress/Compressors/Rar/Decode/FilterType.cs deleted file mode 100644 index cb64f783..00000000 --- a/src/SharpCompress/Compressors/Rar/Decode/FilterType.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace SharpCompress.Compressors.Rar.Decode -{ - internal enum FilterType : byte - { - // These values must not be changed, because we use them directly - // in RAR5 compression and decompression code. - FILTER_DELTA=0, FILTER_E8, FILTER_E8E9, FILTER_ARM, - FILTER_AUDIO, FILTER_RGB, FILTER_ITANIUM, FILTER_PPM, FILTER_NONE - } -} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/Decode/LitDecode.cs b/src/SharpCompress/Compressors/Rar/Decode/LitDecode.cs deleted file mode 100644 index 0c58184e..00000000 --- a/src/SharpCompress/Compressors/Rar/Decode/LitDecode.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace SharpCompress.Compressors.Rar.Decode -{ - internal class LitDecode : Decode - { - internal LitDecode() - : base(new int[PackDef.NC]) - { - } - } -} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/Decode/LowDistDecode.cs b/src/SharpCompress/Compressors/Rar/Decode/LowDistDecode.cs deleted file mode 100644 index bafb0c2f..00000000 --- a/src/SharpCompress/Compressors/Rar/Decode/LowDistDecode.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace SharpCompress.Compressors.Rar.Decode -{ - internal class LowDistDecode : Decode - { - internal LowDistDecode() - : base(new int[PackDef.LDC]) - { - } - } -} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/Decode/MultDecode.cs b/src/SharpCompress/Compressors/Rar/Decode/MultDecode.cs deleted file mode 100644 index 6c21dc83..00000000 --- a/src/SharpCompress/Compressors/Rar/Decode/MultDecode.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace SharpCompress.Compressors.Rar.Decode -{ - internal class MultDecode : Decode - { - internal MultDecode() - : base(new int[PackDef.MC20]) - { - } - } -} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/Decode/PackDef.cs b/src/SharpCompress/Compressors/Rar/Decode/PackDef.cs deleted file mode 100644 index 472e7e5b..00000000 --- a/src/SharpCompress/Compressors/Rar/Decode/PackDef.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace SharpCompress.Compressors.Rar.Decode -{ - internal static class PackDef - { - // 20171217 NOTE: these contants are gone from unrar src code - // seems to be more dynamic - public const int MAXWINSIZE = 0x400000; - public const int MAXWINMASK = MAXWINSIZE - 1; - - public const uint MAX_LZ_MATCH = 0x1001; - public const uint MAX3_LZ_MATCH = 0x101; // Maximum match length for RAR v3. - public const int LOW_DIST_REP_COUNT = 16; - - public const int NC = 299; /* alphabet = {0, 1, 2, ..., NC - 1} */ - public const int DC = 60; - public const int LDC = 17; - public const int RC = 28; -// 20171217: NOTE: these constants seem to have been updated in the unrar src code -// at some unknown point. updating causes decompression failure, not sure why. -// public const int NC = 306; /* alphabet = {0, 1, 2, ..., NC - 1} */ -// public const int DC = 64; -// public const int LDC = 16; -// public const int RC = 44; - public const int HUFF_TABLE_SIZE = NC + DC + RC + LDC; - public const int BC = 20; - - public const uint NC30 = 299; /* alphabet = {0, 1, 2, ..., NC - 1} */ - public const uint DC30 = 60; - public const uint LDC30 = 17; - public const uint RC30 = 28; - public const uint BC30 = 20; - public const uint HUFF_TABLE_SIZE30 = NC30 + DC30 + RC30 + LDC30; - - public const int NC20 = 298; /* alphabet = {0, 1, 2, ..., NC - 1} */ - public const int DC20 = 48; - public const int RC20 = 28; - public const int BC20 = 19; - public const int MC20 = 257; - - // Largest alphabet size among all values listed above. - public const uint LARGEST_TABLE_SIZE = 306; - - //public enum { - // CODE_HUFFMAN, CODE_LZ, CODE_REPEATLZ, CODE_CACHELZ, CODE_STARTFILE, - // CODE_ENDFILE, CODE_FILTER, CODE_FILTERDATA - //} - } -} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/Decode/RepDecode.cs b/src/SharpCompress/Compressors/Rar/Decode/RepDecode.cs deleted file mode 100644 index 1f4a42ef..00000000 --- a/src/SharpCompress/Compressors/Rar/Decode/RepDecode.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace SharpCompress.Compressors.Rar.Decode -{ - internal class RepDecode : Decode - { - internal RepDecode() - : base(new int[PackDef.RC]) - { - } - } -} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/PPM/BlockTypes.cs b/src/SharpCompress/Compressors/Rar/PPM/BlockTypes.cs deleted file mode 100644 index 2550c47a..00000000 --- a/src/SharpCompress/Compressors/Rar/PPM/BlockTypes.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace SharpCompress.Compressors.Rar.PPM -{ - internal enum BlockTypes - { - BLOCK_LZ = 0, - BLOCK_PPM = 1 - } -} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackUtility.cs b/src/SharpCompress/Compressors/Rar/UnpackUtility.cs deleted file mode 100644 index 3c7a0692..00000000 --- a/src/SharpCompress/Compressors/Rar/UnpackUtility.cs +++ /dev/null @@ -1,220 +0,0 @@ -using SharpCompress.Compressors.Rar.VM; - -namespace SharpCompress.Compressors.Rar -{ - internal static class UnpackUtility - { -//!!! TODO rename methods - internal static uint DecodeNumber(this BitInput input, Decode.Decode dec) { - return (uint)input.decodeNumber(dec); - } - - internal static int decodeNumber(this BitInput input, Decode.Decode dec) - { - int bits; - long bitField = input.GetBits() & 0xfffe; - - // if (bitField < dec.getDecodeLen()[8]) { - // if (bitField < dec.getDecodeLen()[4]) { - // if (bitField < dec.getDecodeLen()[2]) { - // if (bitField < dec.getDecodeLen()[1]) { - // bits = 1; - // } else { - // bits = 2; - // } - // } else { - // if (bitField < dec.getDecodeLen()[3]) { - // bits = 3; - // } else { - // bits = 4; - // } - // } - // } else { - // if (bitField < dec.getDecodeLen()[6]) { - // if (bitField < dec.getDecodeLen()[5]) - // bits = 5; - // else - // bits = 6; - // } else { - // if (bitField < dec.getDecodeLen()[7]) { - // bits = 7; - // } else { - // bits = 8; - // } - // } - // } - // } else { - // if (bitField < dec.getDecodeLen()[12]) { - // if (bitField < dec.getDecodeLen()[10]) - // if (bitField < dec.getDecodeLen()[9]) - // bits = 9; - // else - // bits = 10; - // else if (bitField < dec.getDecodeLen()[11]) - // bits = 11; - // else - // bits = 12; - // } else { - // if (bitField < dec.getDecodeLen()[14]) { - // if (bitField < dec.getDecodeLen()[13]) { - // bits = 13; - // } else { - // bits = 14; - // } - // } else { - // bits = 15; - // } - // } - // } - // addbits(bits); - // int N = dec.getDecodePos()[bits] - // + (((int) bitField - dec.getDecodeLen()[bits - 1]) >>> (16 - bits)); - // if (N >= dec.getMaxNum()) { - // N = 0; - // } - // return (dec.getDecodeNum()[N]); - int[] decodeLen = dec.DecodeLen; - if (bitField < decodeLen[8]) - { - if (bitField < decodeLen[4]) - { - if (bitField < decodeLen[2]) - { - if (bitField < decodeLen[1]) - { - bits = 1; - } - else - { - bits = 2; - } - } - else - { - if (bitField < decodeLen[3]) - { - bits = 3; - } - else - { - bits = 4; - } - } - } - else - { - if (bitField < decodeLen[6]) - { - if (bitField < decodeLen[5]) - { - bits = 5; - } - else - { - bits = 6; - } - } - else - { - if (bitField < decodeLen[7]) - { - bits = 7; - } - else - { - bits = 8; - } - } - } - } - else - { - if (bitField < decodeLen[12]) - { - if (bitField < decodeLen[10]) - { - if (bitField < decodeLen[9]) - { - bits = 9; - } - else - { - bits = 10; - } - } - else if (bitField < decodeLen[11]) - { - bits = 11; - } - else - { - bits = 12; - } - } - else - { - if (bitField < decodeLen[14]) - { - if (bitField < decodeLen[13]) - { - bits = 13; - } - else - { - bits = 14; - } - } - else - { - bits = 15; - } - } - } - input.AddBits(bits); - int N = dec.DecodePos[bits] + (Utility.URShift(((int)bitField - decodeLen[bits - 1]), (16 - bits))); - if (N >= dec.MaxNum) - { - N = 0; - } - return (dec.DecodeNum[N]); - } - - internal static void makeDecodeTables(byte[] lenTab, int offset, Decode.Decode dec, int size) - { - int[] lenCount = new int[16]; - int[] tmpPos = new int[16]; - int i; - long M, N; - - Utility.Fill(lenCount, 0); // memset(LenCount,0,sizeof(LenCount)); - - Utility.Fill(dec.DecodeNum, 0); // memset(Dec->DecodeNum,0,Size*sizeof(*Dec->DecodeNum)); - - for (i = 0; i < size; i++) - { - lenCount[lenTab[offset + i] & 0xF]++; - } - lenCount[0] = 0; - for (tmpPos[0] = 0, dec.DecodePos[0] = 0, dec.DecodeLen[0] = 0, N = 0, i = 1; i < 16; i++) - { - N = 2 * (N + lenCount[i]); - M = N << (15 - i); - if (M > 0xFFFF) - { - M = 0xFFFF; - } - dec.DecodeLen[i] = (int)M; - tmpPos[i] = dec.DecodePos[i] = dec.DecodePos[i - 1] + lenCount[i - 1]; - } - - for (i = 0; i < size; i++) - { - if (lenTab[offset + i] != 0) - { - dec.DecodeNum[tmpPos[lenTab[offset + i] & 0xF]++] = i; - } - } - dec.MaxNum = size; - } - } -} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs index 9d11fe67..b576361a 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs @@ -4,8 +4,8 @@ using System.IO; using SharpCompress.Common; using SharpCompress.Common.Rar.Headers; using SharpCompress.Compressors.PPMd.H; -using SharpCompress.Compressors.Rar.Decode; -using SharpCompress.Compressors.Rar.PPM; +using SharpCompress.Compressors.Rar.UnpackV1.Decode; +using SharpCompress.Compressors.Rar.UnpackV1.PPM; using SharpCompress.Compressors.Rar.VM; namespace SharpCompress.Compressors.Rar.UnpackV1 diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.cs index 1aa00556..6c0e09f3 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.cs @@ -9,7 +9,7 @@ using System; using System.IO; -using SharpCompress.Compressors.Rar.Decode; +using SharpCompress.Compressors.Rar.UnpackV1.Decode; namespace SharpCompress.Compressors.Rar.UnpackV1 { diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.cs index cde11395..db8e8cd7 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.cs @@ -8,7 +8,7 @@ */ using System; -using SharpCompress.Compressors.Rar.Decode; +using SharpCompress.Compressors.Rar.UnpackV1.Decode; namespace SharpCompress.Compressors.Rar.UnpackV1 { diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.cs index e4104197..01d3604f 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.cs @@ -1,13 +1,8 @@ #if true using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using SharpCompress.Compressors.Filters; -using SharpCompress.Compressors.Rar.Decode; +using SharpCompress.Compressors.Rar.UnpackV1.Decode; using SharpCompress.Compressors.Rar.VM; -using static SharpCompress.Compressors.Rar.Decode.PackDef; using size_t=System.UInt32; using UnpackBlockHeader = SharpCompress.Compressors.Rar.UnpackV1; @@ -180,7 +175,7 @@ public bool TablePresent; break; } - if (((WriteBorder-UnpPtr) & MaxWinMask)DestUnpSize) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs index 909830c9..8db5c9bc 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs @@ -15,7 +15,8 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using static SharpCompress.Compressors.Rar.Decode.PackDef; +using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; +using static SharpCompress.Compressors.Rar.UnpackV2017.UnpackGlobal; namespace SharpCompress.Compressors.Rar.UnpackV2017 { @@ -51,7 +52,7 @@ public const int UNPACK_MAX_WRITE =0x400000; } // Decode compressed bit fields to alphabet numbers. -struct DecodeTable +sealed class DecodeTable { // Real size of DecodeNum table. uint MaxNum; @@ -61,11 +62,11 @@ struct DecodeTable // range for bit length and DecodeLen[BitLength] defines next code // after the end of range or in other words the upper limit code // for specified bit length. - uint DecodeLen[16]; + readonly uint[] DecodeLen = new uint[16]; // Every item of this array contains the sum of all preceding items. // So it contains the start position in code list for every bit length. - uint DecodePos[16]; + readonly uint[] DecodePos = new uint[16]; // Number of compressed bits processed in quick mode. // Must not exceed MAX_QUICK_DECODE_BITS. @@ -73,13 +74,13 @@ struct DecodeTable // Translates compressed bits (up to QuickBits length) // to bit length in quick mode. - byte QuickLen[1< Date: Wed, 20 Dec 2017 00:17:47 -0500 Subject: [PATCH 35/61] rar5: wip --- .../Compressors/Rar/UnpackV2017/Unpack.cs | 7 ++ .../Rar/UnpackV2017/Unpack.unpack20_cpp.cs | 118 +++++++++--------- .../Rar/UnpackV2017/Unpack.unpack30_cpp.cs | 10 +- .../Rar/UnpackV2017/Unpack.unpack50_cpp.cs | 62 ++++----- .../Rar/UnpackV2017/Unpack.unpack_cpp.cs | 24 ++-- .../UnpackV2017/Unpack.unpackinline_cpp.cs | 22 ++-- .../Compressors/Rar/UnpackV2017/unpack_hpp.cs | 52 ++++---- 7 files changed, 153 insertions(+), 142 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs index e50efcfa..029a8e5c 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs @@ -11,6 +11,13 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 private Stream readStream; private Stream writeStream; + private void _UnpackCtor() { + BlockTables.Init(); + for (int i = 0; i < this.AudV.Length; i++) { + this.AudV[i] = new AudioVariables(); + } + } + public void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream) { // as of 12/2017 .NET limits array indexing to using a signed integer diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs index 7ed33a4b..648385b2 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs @@ -71,7 +71,7 @@ void Unpack20(bool Solid) } if (UnpAudioBlock) { - uint AudioNumber=DecodeNumber(Inp,&MD[UnpCurChannel]); + uint AudioNumber=DecodeNumber(Inp,MD[UnpCurChannel]); if (AudioNumber==256) { @@ -86,7 +86,7 @@ void Unpack20(bool Solid) continue; } - uint Number=DecodeNumber(Inp,&BlockTables.LD); + uint Number=DecodeNumber(Inp,BlockTables.LD); if (Number<256) { Window[UnpPtr++]=(byte)Number; @@ -102,7 +102,7 @@ void Unpack20(bool Solid) Inp.addbits(Bits); } - uint DistNumber=DecodeNumber(Inp,&BlockTables.DD); + uint DistNumber=DecodeNumber(Inp,BlockTables.DD); uint Distance=DDecode[DistNumber]+1; if ((Bits=DBits[DistNumber])>0) { @@ -134,7 +134,7 @@ void Unpack20(bool Solid) if (Number<261) { uint Distance=OldDist[(OldDistPtr-(Number-256)) & 3]; - uint LengthNumber=DecodeNumber(Inp,&BlockTables.RD); + uint LengthNumber=DecodeNumber(Inp,BlockTables.RD); uint Length=LDecode[LengthNumber]+2; if ((Bits=LBits[LengthNumber])>0) { @@ -197,7 +197,7 @@ bool ReadTables20() uint BitField=Inp.getbits(); UnpAudioBlock=(BitField & 0x8000)!=0; - if (!(BitField & 0x4000)) + if ((BitField & 0x4000) != 0) Utility.Memset(UnpOldTable20,0,UnpOldTable20.Length); Inp.addbits(2); @@ -218,13 +218,13 @@ bool ReadTables20() BitLength[I]=(byte)(Inp.getbits() >> 12); Inp.addbits(4); } - MakeDecodeTables(BitLength,&BlockTables.BD,BC20); + MakeDecodeTables(BitLength,BlockTables.BD,BC20); for (uint I=0;IReadTop-5) if (!UnpReadBuf()) return false; - uint Number=DecodeNumber(Inp,&BlockTables.BD); + uint Number=DecodeNumber(Inp,BlockTables.BD); if (Number<16) { Table[I]=(Number+UnpOldTable20[I]) & 0xf; @@ -269,9 +269,9 @@ bool ReadTables20() MakeDecodeTables(&Table[I*MC20],&MD[I],MC20); else { - MakeDecodeTables(&Table[0],&BlockTables.LD,NC20); - MakeDecodeTables(&Table[NC20],&BlockTables.DD,DC20); - MakeDecodeTables(&Table[NC20+DC20],&BlockTables.RD,RC20); + MakeDecodeTables(&Table[0],BlockTables.LD,NC20); + MakeDecodeTables(&Table[NC20],BlockTables.DD,DC20); + MakeDecodeTables(&Table[NC20+DC20],BlockTables.RD,RC20); } memcpy(UnpOldTable20,Table,sizeof(UnpOldTable20)); return true; @@ -283,11 +283,11 @@ void ReadLastTables() if (ReadTop>=Inp.InAddr+5) if (UnpAudioBlock) { - if (DecodeNumber(Inp,&MD[UnpCurChannel])==256) + if (DecodeNumber(Inp,MD[UnpCurChannel])==256) ReadTables20(); } else - if (DecodeNumber(Inp,&BlockTables.LD)==269) + if (DecodeNumber(Inp,BlockTables.LD)==269) ReadTables20(); } @@ -311,13 +311,13 @@ void UnpInitData20(bool Solid) byte DecodeAudio(int Delta) { - struct AudioVariables *V=&AudV[UnpCurChannel]; - V->ByteCount++; - V->D4=V->D3; - V->D3=V->D2; - V->D2=V->LastDelta-V->D1; - V->D1=V->LastDelta; - int PCh=8*V->LastChar+V->K1*V->D1+V->K2*V->D2+V->K3*V->D3+V->K4*V->D4+V->K5*UnpChannelDelta; + AudioVariables V=AudV[UnpCurChannel]; + V.ByteCount++; + V.D4=V.D3; + V.D3=V.D2; + V.D2=V.LastDelta-V.D1; + V.D1=V.LastDelta; + int PCh=8*V.LastChar+V.K1*V.D1+V.K2*V.D2+V.K3*V.D3+V.K4*V.D4+V.K5*UnpChannelDelta; PCh=(PCh>>3) & 0xFF; uint Ch=PCh-Delta; @@ -327,75 +327,75 @@ byte DecodeAudio(int Delta) // so we cast it to unsigned to follow the standard. D=(uint)D<<3; - V->Dif[0]+=abs(D); - V->Dif[1]+=abs(D-V->D1); - V->Dif[2]+=abs(D+V->D1); - V->Dif[3]+=abs(D-V->D2); - V->Dif[4]+=abs(D+V->D2); - V->Dif[5]+=abs(D-V->D3); - V->Dif[6]+=abs(D+V->D3); - V->Dif[7]+=abs(D-V->D4); - V->Dif[8]+=abs(D+V->D4); - V->Dif[9]+=abs(D-UnpChannelDelta); - V->Dif[10]+=abs(D+UnpChannelDelta); + V.Dif[0]+=Math.Abs(D); + V.Dif[1]+=Math.Abs(D-V.D1); + V.Dif[2]+=Math.Abs(D+V.D1); + V.Dif[3]+=Math.Abs(D-V.D2); + V.Dif[4]+=Math.Abs(D+V.D2); + V.Dif[5]+=Math.Abs(D-V.D3); + V.Dif[6]+=Math.Abs(D+V.D3); + V.Dif[7]+=Math.Abs(D-V.D4); + V.Dif[8]+=Math.Abs(D+V.D4); + V.Dif[9]+=Math.Abs(D-UnpChannelDelta); + V.Dif[10]+=Math.Abs(D+UnpChannelDelta); - UnpChannelDelta=V->LastDelta=(sbyte)(Ch-V->LastChar); - V->LastChar=Ch; + UnpChannelDelta=V.LastDelta=(sbyte)(Ch-V.LastChar); + V.LastChar=Ch; - if ((V->ByteCount & 0x1F)==0) + if ((V.ByteCount & 0x1F)==0) { - uint MinDif=V->Dif[0],NumMinDif=0; - V->Dif[0]=0; - for (uint I=1;IDif);I++) + uint MinDif=V.Dif[0],NumMinDif=0; + V.Dif[0]=0; + for (uint I=1;IDif[I]Dif[I]; + MinDif=V.Dif[I]; NumMinDif=I; } - V->Dif[I]=0; + V.Dif[I]=0; } switch(NumMinDif) { case 1: - if (V->K1>=-16) - V->K1--; + if (V.K1>=-16) + V.K1--; break; case 2: - if (V->K1<16) - V->K1++; + if (V.K1<16) + V.K1++; break; case 3: - if (V->K2>=-16) - V->K2--; + if (V.K2>=-16) + V.K2--; break; case 4: - if (V->K2<16) - V->K2++; + if (V.K2<16) + V.K2++; break; case 5: - if (V->K3>=-16) - V->K3--; + if (V.K3>=-16) + V.K3--; break; case 6: - if (V->K3<16) - V->K3++; + if (V.K3<16) + V.K3++; break; case 7: - if (V->K4>=-16) - V->K4--; + if (V.K4>=-16) + V.K4--; break; case 8: - if (V->K4<16) - V->K4++; + if (V.K4<16) + V.K4++; break; case 9: - if (V->K5>=-16) - V->K5--; + if (V.K5>=-16) + V.K5--; break; case 10: - if (V->K5<16) - V->K5++; + if (V.K5<16) + V.K5++; break; } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs index 6ccdeb6a..43291ffc 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs @@ -159,7 +159,7 @@ void Unpack29(bool Solid) continue; } - uint Number=DecodeNumber(Inp,&BlockTables.LD); + uint Number=DecodeNumber(Inp,BlockTables.LD); if (Number<256) { Window[UnpPtr++]=(byte)Number; @@ -174,7 +174,7 @@ void Unpack29(bool Solid) Inp.addbits(Bits); } - uint DistNumber=DecodeNumber(Inp,&BlockTables.DD); + uint DistNumber=DecodeNumber(Inp,BlockTables.DD); uint Distance=DDecode[DistNumber]+1; if ((Bits=DBits[DistNumber])>0) { @@ -192,7 +192,7 @@ void Unpack29(bool Solid) } else { - uint LowDist=DecodeNumber(Inp,&BlockTables.LDD); + uint LowDist=DecodeNumber(Inp,BlockTables.LDD); if (LowDist==16) { LowDistRepCount=LOW_DIST_REP_COUNT-1; @@ -250,7 +250,7 @@ void Unpack29(bool Solid) OldDist[I]=OldDist[I-1]; OldDist[0]=Distance; - uint LengthNumber=DecodeNumber(Inp,&BlockTables.RD); + uint LengthNumber=DecodeNumber(Inp,BlockTables.RD); int Length=LDecode[LengthNumber]+2; if ((Bits=LBits[LengthNumber])>0) { @@ -703,7 +703,7 @@ bool ReadTables30() if (Inp.InAddr>ReadTop-5) if (!UnpReadBuf30()) return(false); - uint Number=DecodeNumber(Inp,&BlockTables.BD); + uint Number=DecodeNumber(Inp,BlockTables.BD); if (Number<16) { Table[I]=(Number+UnpOldTable[I]) & 0xf; diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs index 5fce0695..42b0f82e 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs @@ -11,10 +11,6 @@ using size_t = System.UInt64; using int64 = System.Int64; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; using static SharpCompress.Compressors.Rar.UnpackV2017.UnpackGlobal; @@ -35,8 +31,8 @@ void Unpack5(bool Solid) // Check TablesRead5 to be sure that we read tables at least once // regardless of current block header TablePresent flag. // So we can safefly use these tables below. - if (!ReadBlockHeader(Inp,BlockHeader) || - !ReadTables(Inp,BlockHeader,BlockTables) || !TablesRead5) + if (!ReadBlockHeader(Inp,ref BlockHeader) || + !ReadTables(Inp,ref BlockHeader, ref BlockTables) || !TablesRead5) return; } @@ -59,7 +55,7 @@ void Unpack5(bool Solid) FileDone=true; break; } - if (!ReadBlockHeader(Inp,BlockHeader) || !ReadTables(Inp,BlockHeader,BlockTables)) + if (!ReadBlockHeader(Inp,ref BlockHeader) || !ReadTables(Inp, ref BlockHeader, ref BlockTables)) return; } if (FileDone || !UnpReadBuf()) @@ -78,7 +74,7 @@ void Unpack5(bool Solid) } } - uint MainSlot=DecodeNumber(Inp,&BlockTables.LD); + uint MainSlot=DecodeNumber(Inp,BlockTables.LD); if (MainSlot<256) { if (Fragmented) @@ -91,7 +87,7 @@ void Unpack5(bool Solid) { uint Length=SlotToLength(Inp,MainSlot-262); - uint DBits,Distance=1,DistSlot=DecodeNumber(Inp,&BlockTables.DD); + uint DBits,Distance=1,DistSlot=DecodeNumber(Inp,BlockTables.DD); if (DistSlot<4) { DBits=0; @@ -112,7 +108,7 @@ void Unpack5(bool Solid) Distance+=((Inp.getbits32()>>(36-DBits))<<4); Inp.addbits(DBits-4); } - uint LowDist=DecodeNumber(Inp,&BlockTables.LDD); + uint LowDist=DecodeNumber(Inp,BlockTables.LDD); Distance+=LowDist; } else @@ -165,7 +161,7 @@ void Unpack5(bool Solid) OldDist[I]=OldDist[I-1]; OldDist[0]=Distance; - uint LengthSlot=DecodeNumber(Inp,&BlockTables.RD); + uint LengthSlot=DecodeNumber(Inp,BlockTables.RD); uint Length=SlotToLength(Inp,LengthSlot); LastLength=Length; if (Fragmented) @@ -232,7 +228,7 @@ bool AddFilter(UnpackFilter Filter) // flag and process this filter only after processing that older data. Filter.NextWindow=WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<=Filter.BlockStart; - Filter.BlockStart=uint((Filter.BlockStart+UnpPtr)&MaxWinMask); + Filter.BlockStart=(uint)((Filter.BlockStart+UnpPtr)&MaxWinMask); Filters.Push(Filter); return true; } @@ -282,7 +278,9 @@ void UnpWriteBuf() size_t FullWriteSize=(UnpPtr-WrittenBorder)&MaxWinMask; size_t WriteSizeLeft=FullWriteSize; bool NotAllFiltersProcessed=false; - for (size_t I=0;I int + for (int I=0;I int + for (int J=I;J>8); @@ -552,7 +552,7 @@ void UnpInitData50(bool Solid) } -bool ReadBlockHeader(BitInput Inp,UnpackBlockHeader &Header) +bool ReadBlockHeader(BitInput Inp,ref UnpackBlockHeader Header) { Header.HeaderSize=0; @@ -583,12 +583,12 @@ bool ReadBlockHeader(BitInput Inp,UnpackBlockHeader &Header) } Header.BlockSize=BlockSize; - byte CheckSum=byte(0x5a^BlockFlags^BlockSize^(BlockSize>>8)^(BlockSize>>16)); + byte CheckSum=(byte)(0x5a^BlockFlags^BlockSize^(BlockSize>>8)^(BlockSize>>16)); if (CheckSum!=SavedCheckSum) return false; Header.BlockStart=Inp.InAddr; - ReadBorder=Min(ReadBorder,Header.BlockStart+Header.BlockSize-1); + ReadBorder=Math.Min(ReadBorder,Header.BlockStart+Header.BlockSize-1); Header.LastBlockInFile=(BlockFlags & 0x40)!=0; Header.TablePresent=(BlockFlags & 0x80)!=0; @@ -596,7 +596,7 @@ bool ReadBlockHeader(BitInput Inp,UnpackBlockHeader &Header) } -bool ReadTables(BitInput Inp,UnpackBlockHeader &Header,UnpackBlockTables &Tables) +bool ReadTables(BitInput Inp,ref UnpackBlockHeader Header, ref UnpackBlockTables Tables) { if (!Header.TablePresent) return true; @@ -605,7 +605,7 @@ bool ReadTables(BitInput Inp,UnpackBlockHeader &Header,UnpackBlockTables &Tables if (!UnpReadBuf()) return false; - byte BitLength[BC]; + byte[] BitLength = new byte[BC]; for (uint I=0;I> 12); @@ -619,7 +619,7 @@ bool ReadTables(BitInput Inp,UnpackBlockHeader &Header,UnpackBlockTables &Tables else { ZeroCount+=2; - while (ZeroCount-- > 0 && I 0 && IReadTop-5) if (!UnpReadBuf()) return false; - uint Number=DecodeNumber(Inp,&Tables.BD); + uint Number=DecodeNumber(Inp,Tables.BD); if (Number<16) { Table[I]=Number; @@ -693,10 +693,10 @@ bool ReadTables(BitInput Inp,UnpackBlockHeader &Header,UnpackBlockTables &Tables TablesRead5=true; if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop) return false; - MakeDecodeTables(&Table[0],&Tables.LD,NC); - MakeDecodeTables(&Table[NC],&Tables.DD,DC); - MakeDecodeTables(&Table[NC+DC],&Tables.LDD,LDC); - MakeDecodeTables(&Table[NC+DC+LDC],&Tables.RD,RC); + MakeDecodeTables(&Table[0],Tables.LD,NC); + MakeDecodeTables(&Table[NC],Tables.DD,DC); + MakeDecodeTables(&Table[NC+DC],Tables.LDD,LDC); + MakeDecodeTables(&Table[NC+DC+LDC],Tables.RD,RC); return true; } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs index 86953ccc..f6c92bc9 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs @@ -27,6 +27,8 @@ public Unpack(/* ComprDataIO *DataIO */) //:Inp(true),VMCodeInp(true) : base(true) { + _UnpackCtor(); + UnpIO=DataIO; Window=null; Fragmented=false; @@ -227,14 +229,14 @@ void UnpInitData(bool Solid) // LengthTable contains the length in bits for every element of alphabet. // Dec is the structure to decode Huffman code/ // Size is size of length table and DecodeNum field in Dec structure, -void MakeDecodeTables(byte *LengthTable,DecodeTable *Dec,uint Size) +void MakeDecodeTables(byte *LengthTable,DecodeTable Dec,uint Size) { // Size of alphabet and DecodePos array. - Dec->MaxNum=Size; + Dec.MaxNum=Size; // Calculate how many entries for every bit length in LengthTable we have. - uint LengthCount[16]; - memset(LengthCount,0,sizeof(LengthCount)); + uint[] LengthCount = new uint[16]; + //memset(LengthCount,0,sizeof(LengthCount)); for (size_t I=0;IDecodeNum,0,Size*sizeof(*Dec->DecodeNum)); // Initialize not really used entry for zero length code. - Dec->DecodePos[0]=0; + Dec.DecodePos[0]=0; // Start code for bit length 1 is 0. - Dec->DecodeLen[0]=0; + Dec.DecodeLen[0]=0; // Right aligned upper limit code for current bit length. uint UpperLimit=0; @@ -265,17 +267,17 @@ void MakeDecodeTables(byte *LengthTable,DecodeTable *Dec,uint Size) UpperLimit*=2; // Store the left aligned upper limit code. - Dec->DecodeLen[I]=(uint)LeftAligned; + Dec.DecodeLen[I]=(uint)LeftAligned; // Every item of this array contains the sum of all preceding items. // So it contains the start position in code list for every bit length. - Dec->DecodePos[I]=Dec->DecodePos[I-1]+LengthCount[I-1]; + Dec.DecodePos[I]=Dec.DecodePos[I-1]+LengthCount[I-1]; } // Prepare the copy of DecodePos. We'll modify this copy below, // so we cannot use the original DecodePos. - uint CopyDecodePos[ASIZE(Dec->DecodePos)]; - memcpy(CopyDecodePos,Dec->DecodePos,sizeof(CopyDecodePos)); + uint[] CopyDecodePos = new uint[Dec.DecodePos.Length]; + //memcpy(CopyDecodePos,Dec->DecodePos,sizeof(CopyDecodePos)); // For every bit length in the bit length table and so for every item // of alphabet. @@ -291,7 +293,7 @@ void MakeDecodeTables(byte *LengthTable,DecodeTable *Dec,uint Size) // Prepare the decode table, so this position in code list will be // decoded to current alphabet item number. - Dec->DecodeNum[LastPos]=(ushort)I; + Dec.DecodeNum[LastPos]=(ushort)I; // We'll use next position number for this bit length next time. // So we pass through the entire range of positions available diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpackinline_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpackinline_cpp.cs index f3af2602..31f3dbce 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpackinline_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpackinline_cpp.cs @@ -103,22 +103,22 @@ void CopyString(uint Length,uint Distance) } -uint DecodeNumber(BitInput Inp,DecodeTable *Dec) +uint DecodeNumber(BitInput Inp,DecodeTable Dec) { // Left aligned 15 bit length raw bit field. uint BitField=Inp.getbits() & 0xfffe; - if (BitFieldDecodeLen[Dec->QuickBits]) + if (BitField>(16-Dec->QuickBits); - Inp.addbits(Dec->QuickLen[Code]); - return Dec->QuickNum[Code]; + uint Code=BitField>>(16-Dec.QuickBits); + Inp.addbits(Dec.QuickLen[Code]); + return Dec.QuickNum[Code]; } // Detect the real bit length for current code. uint Bits=15; - for (uint I=Dec->QuickBits+1;I<15;I++) - if (BitFieldDecodeLen[I]) + for (uint I=Dec.QuickBits+1;I<15;I++) + if (BitFieldDecodeLen[Bits-1]; + uint Dist=BitField-Dec.DecodeLen[Bits-1]; // Start codes are left aligned, but we need the normal right aligned // number. So we shift the distance to the right. @@ -136,15 +136,15 @@ uint DecodeNumber(BitInput Inp,DecodeTable *Dec) // Now we can calculate the position in the code list. It is the sum // of first position for current bit length and right aligned distance // between our bit field and start code for current bit length. - uint Pos=Dec->DecodePos[Bits]+Dist; + uint Pos=Dec.DecodePos[Bits]+Dist; // Out of bounds safety check required for damaged archives. - if (Pos>=Dec->MaxNum) + if (Pos>=Dec.MaxNum) Pos=0; // Convert the position in the code list to position in alphabet // and return it. - return Dec->DecodeNum[Pos]; + return Dec.DecodeNum[Pos]; } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs index 8db5c9bc..ce35a154 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs @@ -10,11 +10,7 @@ using size_t = System.UInt64; #endif using int64 = System.Int64; -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; using static SharpCompress.Compressors.Rar.UnpackV2017.UnpackGlobal; @@ -55,32 +51,32 @@ public const int UNPACK_MAX_WRITE =0x400000; sealed class DecodeTable { // Real size of DecodeNum table. - uint MaxNum; + public uint MaxNum; // Left aligned start and upper limit codes defining code space // ranges for bit lengths. DecodeLen[BitLength-1] defines the start of // range for bit length and DecodeLen[BitLength] defines next code // after the end of range or in other words the upper limit code // for specified bit length. - readonly uint[] DecodeLen = new uint[16]; + public readonly uint[] DecodeLen = new uint[16]; // Every item of this array contains the sum of all preceding items. // So it contains the start position in code list for every bit length. - readonly uint[] DecodePos = new uint[16]; + public readonly uint[] DecodePos = new uint[16]; // Number of compressed bits processed in quick mode. // Must not exceed MAX_QUICK_DECODE_BITS. - uint QuickBits; + public uint QuickBits; // Translates compressed bits (up to QuickBits length) // to bit length in quick mode. - readonly byte[] QuickLen = new byte[1< Date: Wed, 20 Dec 2017 00:29:07 -0500 Subject: [PATCH 36/61] rar5: wip --- .../Rar/UnpackV2017/Unpack.unpack_cpp.cs | 39 ++++++++++--------- src/SharpCompress/Utility.cs | 17 ++++++++ 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs index f6c92bc9..96897814 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs @@ -29,7 +29,7 @@ public Unpack(/* ComprDataIO *DataIO */) { _UnpackCtor(); - UnpIO=DataIO; + //UnpIO=DataIO; Window=null; Fragmented=false; Suspended=false; @@ -203,9 +203,10 @@ void UnpInitData(bool Solid) OldDistPtr=0; LastDist=LastLength=0; // memset(Window,0,MaxWinSize); - memset(&BlockTables,0,sizeof(BlockTables)); + //memset(&BlockTables,0,sizeof(BlockTables)); + BlockTables = new UnpackBlockTables(); UnpPtr=WrPtr=0; - WriteBorder=Min(MaxWinSize,UNPACK_MAX_WRITE)&MaxWinMask; + WriteBorder=Math.Min(MaxWinSize,UNPACK_MAX_WRITE)&MaxWinMask; } // Filters never share several solid files, so we can safely reset them // even in solid archive. @@ -216,7 +217,8 @@ void UnpInitData(bool Solid) ReadTop=0; ReadBorder=0; - memset(&BlockHeader,0,sizeof(BlockHeader)); + //memset(&BlockHeader,0,sizeof(BlockHeader)); + BlockHeader = new UnpackBlockHeader(); BlockHeader.BlockSize=-1; // '-1' means not defined yet. #if !RarV2017_SFX_MODULE UnpInitData20(Solid); @@ -229,7 +231,7 @@ void UnpInitData(bool Solid) // LengthTable contains the length in bits for every element of alphabet. // Dec is the structure to decode Huffman code/ // Size is size of length table and DecodeNum field in Dec structure, -void MakeDecodeTables(byte *LengthTable,DecodeTable Dec,uint Size) +void MakeDecodeTables(byte[] LengthTable,DecodeTable Dec,uint Size) { // Size of alphabet and DecodePos array. Dec.MaxNum=Size; @@ -244,7 +246,8 @@ void MakeDecodeTables(byte *LengthTable,DecodeTable Dec,uint Size) LengthCount[0]=0; // Set the entire DecodeNum to zero. - memset(Dec->DecodeNum,0,Size*sizeof(*Dec->DecodeNum)); + //memset(Dec->DecodeNum,0,Size*sizeof(*Dec->DecodeNum)); + Utility.FillFast(Dec.DecodeNum, 0); // Initialize not really used entry for zero length code. Dec.DecodePos[0]=0; @@ -284,7 +287,7 @@ void MakeDecodeTables(byte *LengthTable,DecodeTable Dec,uint Size) for (uint I=0;IQuickBits=MAX_QUICK_DECODE_BITS; + Dec.QuickBits=MAX_QUICK_DECODE_BITS; break; default: - Dec->QuickBits=MAX_QUICK_DECODE_BITS-3; + Dec.QuickBits=MAX_QUICK_DECODE_BITS-3; break; } // Size of tables for quick mode. - uint QuickDataSize=1<QuickBits; + uint QuickDataSize=1<QuickBits); + uint BitField=Code<<(16-Dec.QuickBits); // Prepare the table for quick decoding of bit lengths. // Find the upper limit for current bit field and adjust the bit length // accordingly if necessary. - while (CurBitLengthDecodeLen) && BitField>=Dec->DecodeLen[CurBitLength]) + while (CurBitLength=Dec.DecodeLen[CurBitLength]) CurBitLength++; // Translation of right aligned bit string to bit length. - Dec->QuickLen[Code]=CurBitLength; + Dec.QuickLen[Code]=CurBitLength; // Prepare the table for quick translation of position in code list // to position in alphabet. // Calculate the distance from the start code for current bit length. - uint Dist=BitField-Dec->DecodeLen[CurBitLength-1]; + uint Dist=BitField-Dec.DecodeLen[CurBitLength-1]; // Right align the distance. Dist>>=(16-CurBitLength); @@ -355,16 +358,16 @@ void MakeDecodeTables(byte *LengthTable,DecodeTable Dec,uint Size) // of first position for current bit length and right aligned distance // between our bit field and start code for current bit length. uint Pos; - if (CurBitLengthDecodePos) && - (Pos=Dec->DecodePos[CurBitLength]+Dist)QuickNum[Code]=Dec->DecodeNum[Pos]; + Dec.QuickNum[Code]=Dec.DecodeNum[Pos]; } else { // Can be here for length table filled with zeroes only (empty). - Dec->QuickNum[Code]=0; + Dec.QuickNum[Code]=0; } } } diff --git a/src/SharpCompress/Utility.cs b/src/SharpCompress/Utility.cs index a334af06..d5dd62d3 100644 --- a/src/SharpCompress/Utility.cs +++ b/src/SharpCompress/Utility.cs @@ -123,6 +123,23 @@ namespace SharpCompress } } + public static void FillFast(T[] array, T val) where T : struct + { + for (int i = 0; i < array.Length; i++) + { + array[i] = val; + } + } + + public static void FillFast(T[] array, int start, int length, T val) where T : struct + { + int toIndex = start + length; + for (int i = start; i < toIndex; i++) + { + array[i] = val; + } + } + /// /// Fills the array with an specific value. From bdaa060003c5b367df59a65b73d46c45a728b968 Mon Sep 17 00:00:00 2001 From: coderb Date: Wed, 20 Dec 2017 01:21:53 -0500 Subject: [PATCH 37/61] rar5: unpack wip --- .../Rar/UnpackV2017/Unpack.unpack50_cpp.cs | 4 ++-- .../Rar/UnpackV2017/Unpack.unpack_cpp.cs | 21 ++++++++----------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs index 42b0f82e..388916ea 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs @@ -139,7 +139,7 @@ void Unpack5(bool Solid) } if (MainSlot==256) { - UnpackFilter Filter; + UnpackFilter Filter = new UnpackFilter(); if (!ReadFilter(Inp,Filter) || !AddFilter(Filter)) break; continue; @@ -229,7 +229,7 @@ bool AddFilter(UnpackFilter Filter) Filter.NextWindow=WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<=Filter.BlockStart; Filter.BlockStart=(uint)((Filter.BlockStart+UnpPtr)&MaxWinMask); - Filters.Push(Filter); + Filters.Add(Filter); return true; } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs index 96897814..f7c9217f 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs @@ -10,10 +10,6 @@ using size_t = System.UInt64; #endif using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using SharpCompress.Common; using static SharpCompress.Compressors.Rar.UnpackV2017.UnpackGlobal; using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; @@ -258,7 +254,7 @@ void MakeDecodeTables(byte[] LengthTable,DecodeTable Dec,uint Size) // Right aligned upper limit code for current bit length. uint UpperLimit=0; - for (size_t I=1;I<16;I++) + for (int I=1;I<16;I++) { // Adjust the upper limit code. UpperLimit+=LengthCount[I]; @@ -287,12 +283,12 @@ void MakeDecodeTables(byte[] LengthTable,DecodeTable Dec,uint Size) for (uint I=0;I Date: Wed, 20 Dec 2017 01:39:13 -0500 Subject: [PATCH 38/61] rar5: unpack wip --- .../Compressors/Rar/UnpackV2017/Unpack.cs | 12 ++ .../UnpackV2017/Unpack.unpackinline_cpp.cs | 117 +++++++++--------- 2 files changed, 72 insertions(+), 57 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs index 029a8e5c..8508b784 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs @@ -1,7 +1,19 @@ #if RARWIP +#if !Rar2017_64bit +using nint = System.Int32; +using nuint = System.UInt32; +using size_t = System.UInt32; +#else +using nint = System.Int64; +using nuint = System.UInt64; +using size_t = System.UInt64; +#endif +using int64 = System.Int64; + using System; using System.IO; using SharpCompress.Common.Rar.Headers; +using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; namespace SharpCompress.Compressors.Rar.UnpackV2017 { diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpackinline_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpackinline_cpp.cs index 31f3dbce..0f839584 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpackinline_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpackinline_cpp.cs @@ -10,11 +10,6 @@ using size_t = System.UInt64; #endif using int64 = System.Int64; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; namespace SharpCompress.Compressors.Rar.UnpackV2017 @@ -37,60 +32,68 @@ void InsertOldDist(uint Distance) void CopyString(uint Length,uint Distance) { size_t SrcPtr=UnpPtr-Distance; + if (SrcPtr 0) + { + Window[UnpPtr++] = Window[SrcPtr++]; + } -#if FAST_MEMCPY - if (Distance=8) - { - Dest[0]=Src[0]; - Dest[1]=Src[1]; - Dest[2]=Src[2]; - Dest[3]=Src[3]; - Dest[4]=Src[4]; - Dest[5]=Src[5]; - Dest[6]=Src[6]; - Dest[7]=Src[7]; - - Src+=8; - Dest+=8; - Length-=8; - } -#if FAST_MEMCPY - else - while (Length>=8) - { - // In theory we still could overlap here. - // Supposing Distance == MaxWinSize - 1 we have memcpy(Src, Src + 1, 8). - // But for real RAR archives Distance <= MaxWinSize - MAX_LZ_MATCH - // always, so overlap here is impossible. - - // This memcpy expanded inline by MSVC. We could also use uint64 - // assignment, which seems to provide about the same speed. - memcpy(Dest,Src,8); - - Src+=8; - Dest+=8; - Length-=8; - } -#endif - - // Unroll the loop for 0 - 7 bytes left. Note that we use nested "if"s. - if (Length>0) { Dest[0]=Src[0]; - if (Length>1) { Dest[1]=Src[1]; - if (Length>2) { Dest[2]=Src[2]; - if (Length>3) { Dest[3]=Src[3]; - if (Length>4) { Dest[4]=Src[4]; - if (Length>5) { Dest[5]=Src[5]; - if (Length>6) { Dest[6]=Src[6]; } } } } } } } // Close all nested "if"s. +// byte *Src=Window+SrcPtr; +// byte *Dest=Window+UnpPtr; +// UnpPtr+=Length; +// +//#if FAST_MEMCPY +// if (Distance=8) +// { +// Dest[0]=Src[0]; +// Dest[1]=Src[1]; +// Dest[2]=Src[2]; +// Dest[3]=Src[3]; +// Dest[4]=Src[4]; +// Dest[5]=Src[5]; +// Dest[6]=Src[6]; +// Dest[7]=Src[7]; +// +// Src+=8; +// Dest+=8; +// Length-=8; +// } +//#if FAST_MEMCPY +// else +// while (Length>=8) +// { +// // In theory we still could overlap here. +// // Supposing Distance == MaxWinSize - 1 we have memcpy(Src, Src + 1, 8). +// // But for real RAR archives Distance <= MaxWinSize - MAX_LZ_MATCH +// // always, so overlap here is impossible. +// +// // This memcpy expanded inline by MSVC. We could also use uint64 +// // assignment, which seems to provide about the same speed. +// memcpy(Dest,Src,8); +// +// Src+=8; +// Dest+=8; +// Length-=8; +// } +//#endif +// +// // Unroll the loop for 0 - 7 bytes left. Note that we use nested "if"s. +// if (Length>0) { Dest[0]=Src[0]; +// if (Length>1) { Dest[1]=Src[1]; +// if (Length>2) { Dest[2]=Src[2]; +// if (Length>3) { Dest[3]=Src[3]; +// if (Length>4) { Dest[4]=Src[4]; +// if (Length>5) { Dest[5]=Src[5]; +// if (Length>6) { Dest[6]=Src[6]; } } } } } } } // Close all nested "if"s. } else while (Length-- > 0) // Slow copying with all possible precautions. @@ -110,7 +113,7 @@ uint DecodeNumber(BitInput Inp,DecodeTable Dec) if (BitField>(16-Dec.QuickBits); + uint Code=BitField>>(int)(16-Dec.QuickBits); Inp.addbits(Dec.QuickLen[Code]); return Dec.QuickNum[Code]; } @@ -131,7 +134,7 @@ uint DecodeNumber(BitInput Inp,DecodeTable Dec) // Start codes are left aligned, but we need the normal right aligned // number. So we shift the distance to the right. - Dist>>=(16-Bits); + Dist>>=(int)(16-Bits); // Now we can calculate the position in the code list. It is the sum // of first position for current bit length and right aligned distance @@ -159,12 +162,12 @@ uint SlotToLength(BitInput Inp,uint Slot) else { LBits=Slot/4-1; - Length+=(4 | (Slot & 3)) << LBits; + Length+=(4 | (Slot & 3)) << (int)LBits; } if (LBits>0) { - Length+=Inp.getbits()>>(16-LBits); + Length+=Inp.getbits()>>(int)(16-LBits); Inp.addbits(LBits); } return Length; From aaeaa44c0bf1a65a53f4c2bf7e7f116aa6d58218 Mon Sep 17 00:00:00 2001 From: coderb Date: Wed, 20 Dec 2017 01:55:54 -0500 Subject: [PATCH 39/61] rar5: unpack wip --- .../FragmentedWindow.unpack50frag_cpp.cs | 27 ++++++++++++++----- .../Rar/UnpackV2017/Unpack.unpack50_cpp.cs | 6 ++--- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs index bbd10c84..f8f3d6dc 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs @@ -89,23 +89,36 @@ public void Init(size_t WinSize) } -byte& operator [](size_t Item) -{ +byte this[size_t Item] { +get { if (Item 0) { - (*this)[UnpPtr]=(*this)[SrcPtr++ & MaxWinMask]; + this[UnpPtr]=this[SrcPtr++ & MaxWinMask]; // We need to have masked UnpPtr after quit from loop, so it must not // be replaced with '(*this)[UnpPtr++ & MaxWinMask]' UnpPtr=(UnpPtr+1) & MaxWinMask; @@ -116,13 +129,13 @@ public void CopyString(uint Length,uint Distance,size_t &UnpPtr,size_t MaxWinMas void CopyData(byte *Dest,size_t WinPos,size_t Size) { for (size_t I=0;I0) @@ -105,7 +105,7 @@ void Unpack5(bool Solid) { if (DBits>4) { - Distance+=((Inp.getbits32()>>(36-DBits))<<4); + Distance+=((Inp.getbits32()>>(int)(36-DBits))<<4); Inp.addbits(DBits-4); } uint LowDist=DecodeNumber(Inp,BlockTables.LDD); @@ -113,7 +113,7 @@ void Unpack5(bool Solid) } else { - Distance+=Inp.getbits32()>>(32-DBits); + Distance+=Inp.getbits32()>>(int)(32-DBits); Inp.addbits(DBits); } } From 2d0319f779a83f9691e2c645995a14b101c177c8 Mon Sep 17 00:00:00 2001 From: coderb Date: Wed, 20 Dec 2017 02:08:31 -0500 Subject: [PATCH 40/61] rar5: unpack wip --- .../FragmentedWindow.unpack50frag_cpp.cs | 4 +-- .../Rar/UnpackV2017/Unpack.unpack15_cpp.cs | 2 ++ .../Rar/UnpackV2017/Unpack.unpack20_cpp.cs | 2 ++ .../Rar/UnpackV2017/Unpack.unpack30_cpp.cs | 6 ++-- .../Rar/UnpackV2017/Unpack.unpack50_cpp.cs | 28 +++++++++---------- .../Rar/UnpackV2017/Unpack.unpack_cpp.cs | 6 ++++ .../Compressors/Rar/UnpackV2017/unpack_hpp.cs | 6 ++++ 7 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs index f8f3d6dc..32ebd57b 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs @@ -89,7 +89,7 @@ public void Init(size_t WinSize) } -byte this[size_t Item] { +public byte this[size_t Item] { get { if (Item 0 && I 0 && I>8)<<(I*8); + Data+=(Inp.fgetbits()>>8)<<(int)(I*8); Inp.addbits(8); } return Data; @@ -201,12 +201,12 @@ bool ReadFilter(BitInput Inp,UnpackFilter Filter) if (Filter.BlockLength>MAX_FILTER_BLOCK_SIZE) Filter.BlockLength=0; - Filter.Type=Inp.fgetbits()>>13; + Filter.Type=(byte)(Inp.fgetbits()>>13); Inp.faddbits(3); if (Filter.Type==FILTER_DELTA) { - Filter.Channels=(Inp.fgetbits()>>11)+1; + Filter.Channels=(byte)((Inp.fgetbits()>>11)+1); Inp.faddbits(5); } @@ -559,26 +559,26 @@ bool ReadBlockHeader(BitInput Inp,ref UnpackBlockHeader Header) if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-7) if (!UnpReadBuf()) return false; - Inp.faddbits((8-Inp.InBit)&7); + Inp.faddbits((uint)((8-Inp.InBit)&7)); - byte BlockFlags=Inp.fgetbits()>>8; + byte BlockFlags=(byte)(Inp.fgetbits()>>8); Inp.faddbits(8); - uint ByteCount=((BlockFlags>>3)&3)+1; // Block size byte count. + uint ByteCount=(uint)(((BlockFlags>>3)&3)+1); // Block size byte count. if (ByteCount==4) return false; - Header.HeaderSize=2+ByteCount; + Header.HeaderSize=(int)(2+ByteCount); Header.BlockBitSize=(BlockFlags&7)+1; - byte SavedCheckSum=Inp.fgetbits()>>8; + byte SavedCheckSum=(byte)(Inp.fgetbits()>>8); Inp.faddbits(8); int BlockSize=0; for (uint I=0;I>8)<<(I*8); + BlockSize+=(int)((Inp.fgetbits()>>8)<<(int)(I*8)); Inp.addbits(8); } @@ -625,7 +625,7 @@ bool ReadTables(BitInput Inp,ref UnpackBlockHeader Header, ref UnpackBlockTables } } else - BitLength[I]=Length; + BitLength[I]=(byte)Length; } MakeDecodeTables(BitLength,Tables.BD,BC); @@ -640,7 +640,7 @@ bool ReadTables(BitInput Inp,ref UnpackBlockHeader Header, ref UnpackBlockTables uint Number=DecodeNumber(Inp,Tables.BD); if (Number<16) { - Table[I]=Number; + Table[I]=(byte)Number; I++; } else diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs index f7c9217f..352da170 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs @@ -44,11 +44,13 @@ public Unpack(/* ComprDataIO *DataIO */) // It prevents crash if first DoUnpack call is later made with wrong // (true) 'Solid' value. UnpInitData(false); +#if !RarV2017_RAR5ONLY #if !RarV2017_SFX_MODULE // RAR 1.5 decompression initialization UnpInitData15(false); InitHuff(); #endif +#endif } // later: may need Dispose() if we support thread pool @@ -154,6 +156,7 @@ void DoUnpack(uint Method,bool Solid) // just for extra safety. switch(Method) { +#if !RarV2017_RAR5ONLY #if !RarV2017_SFX_MODULE case 15: // rar 1.5 compression if (!Fragmented) @@ -169,6 +172,7 @@ void DoUnpack(uint Method,bool Solid) if (!Fragmented) Unpack29(Solid); break; +#endif case 50: // RAR 5.0 compression algorithm. #if RarV2017_RAR_SMP if (MaxUserThreads>1) @@ -216,10 +220,12 @@ void UnpInitData(bool Solid) //memset(&BlockHeader,0,sizeof(BlockHeader)); BlockHeader = new UnpackBlockHeader(); BlockHeader.BlockSize=-1; // '-1' means not defined yet. +#if !RarV2017_RAR5ONLY #if !RarV2017_SFX_MODULE UnpInitData20(Solid); #endif UnpInitData30(Solid); +#endif UnpInitData50(Solid); } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs index ce35a154..893c6d2f 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs @@ -193,7 +193,9 @@ class UnpackFilter30 // in PrgStack array. Not defined for filters in Filters array. public uint ParentFilter; +#if !RarV2017_RAR5ONLY public VM_PreparedProgram Prg; +#endif }; @@ -369,7 +371,9 @@ internal partial class Unpack int PrevLowDist,LowDistRepCount; +#if !RarV2017_RAR5ONLY ModelPPM PPM; +#endif int PPMEscChar; readonly byte [] UnpOldTable = new byte[HUFF_TABLE_SIZE30]; @@ -383,7 +387,9 @@ internal partial class Unpack bool TablesRead2,TablesRead3,TablesRead5; // Virtual machine to execute filters code. +#if !RarV2017_RAR5ONLY RarVM VM; +#endif // Buffer to read VM filters code. We moved it here from AddVMCode // function to reduce time spent in BitInput constructor. From 0d408831769bbdc463e908f837a3029086a0283f Mon Sep 17 00:00:00 2001 From: coderb Date: Wed, 20 Dec 2017 11:19:30 -0500 Subject: [PATCH 41/61] unrar5: unpack wip --- .../FragmentedWindow.unpack50frag_cpp.cs | 4 +- .../Compressors/Rar/UnpackV2017/Unpack.cs | 4 ++ .../Rar/UnpackV2017/Unpack.unpack50_cpp.cs | 40 +++++++++++-------- .../Compressors/Rar/UnpackV2017/unpack_hpp.cs | 4 +- 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs index 32ebd57b..ca1b78df 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs @@ -126,10 +126,10 @@ public void CopyString(uint Length,uint Distance,ref size_t UnpPtr,size_t MaxWin } -public void CopyData(byte *Dest,size_t WinPos,size_t Size) +public void CopyData(byte[] Dest, size_t destOffset, size_t WinPos,size_t Size) { for (size_t I=0;I this.PPMEscChar; set => this.PPMEscChar = value; } + + public static byte[] EnsureCapacity(byte[] array, int length) { + return array.Length < length ? new byte[length] : array; + } } } #endif \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs index 57dfa466..73fb3722 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs @@ -325,31 +325,35 @@ void UnpWriteBuf() { uint BlockEnd=(BlockStart+BlockLength)&MaxWinMask; - FilterSrcMemory.Alloc(BlockLength); - byte *Mem=&FilterSrcMemory[0]; + //x FilterSrcMemory.Alloc(BlockLength); + FilterSrcMemory = EnsureCapacity(FilterSrcMemory, checked((int)BlockLength)); + byte[] Mem= FilterSrcMemory; if (BlockStart int + int EmptyCount=0; + // sharpcompress: size_t -> int + for (int I=0;I0) Filters[I-EmptyCount]=Filters[I]; @@ -419,9 +425,9 @@ void UnpWriteBuf() } -byte* ApplyFilter(byte *Data,uint DataSize,UnpackFilter Flt) +byte[] ApplyFilter(byte[] Data,uint DataSize,UnpackFilter Flt) { - byte *SrcData=Data; + byte[] SrcData=Data; switch(Flt.Type) { case FILTER_E8: @@ -484,8 +490,10 @@ byte* ApplyFilter(byte *Data,uint DataSize,UnpackFilter Flt) // values here, since RAR5 uses only 5 bits to store channel. uint Channels=Flt.Channels,SrcPos=0; - FilterDstMemory.Alloc(DataSize); - byte *DstData=&FilterDstMemory[0]; + //x FilterDstMemory.Alloc(DataSize); + FilterDstMemory = EnsureCapacity(FilterDstMemory, checked((int)DataSize)); + + byte[] DstData=FilterDstMemory; // Bytes from same channels are grouped to continual data blocks, // so we need to place them back to their interleaving positions. diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs index 893c6d2f..0c4cc62f 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs @@ -271,8 +271,8 @@ internal partial class Unpack byte *ReadBufMT; #endif - readonly List FilterSrcMemory = new List(); - readonly List FilterDstMemory = new List(); + byte[] FilterSrcMemory = new byte[0]; + byte[] FilterDstMemory = new byte[0]; // Filters code, one entry per filter. readonly List Filters = new List(); From fd9790cc36e7dfe5e1911c636717e50da467b484 Mon Sep 17 00:00:00 2001 From: coderb Date: Wed, 20 Dec 2017 14:15:20 -0500 Subject: [PATCH 42/61] unrar5: unpack wip --- .../Rar/UnpackV2017/Unpack.unpack50_cpp.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs index 73fb3722..b5eda3de 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs @@ -249,7 +249,8 @@ bool UnpReadBuf() // is not less than CRYPT_BLOCK_SIZE, so we can align it without risk // to make it zero. if (DataSize>0) - memmove(Inp.InBuf,Inp.InBuf+Inp.InAddr,DataSize); + //x memmove(Inp.InBuf,Inp.InBuf+Inp.InAddr,DataSize); + Buffer.BlockCopy(Inp.InBuf, Inp.InAddr, Inp.InBuf, 0, DataSize); Inp.InAddr=0; ReadTop=DataSize; } @@ -532,15 +533,15 @@ void UnpWriteArea(size_t StartPtr,size_t EndPtr) else if (EndPtr=DestUnpSize) return; @@ -548,7 +549,7 @@ void UnpWriteData(byte *Data,size_t Size) int64 LeftToWrite=DestUnpSize-WrittenFileSize; if ((int64)WriteSize>LeftToWrite) WriteSize=(size_t)LeftToWrite; - UnpIO->UnpWrite(Data,WriteSize); + UnpIO->UnpWrite(Data, offset, WriteSize); WrittenFileSize+=Size; } From 91c85f0aa62aab597dca306c9d08c78280f75ab0 Mon Sep 17 00:00:00 2001 From: coderb Date: Wed, 20 Dec 2017 14:22:47 -0500 Subject: [PATCH 43/61] unrar5: unpack wip --- .../Compressors/Rar/UnpackV2017/Unpack.cs | 10 ++++++++++ .../Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs | 6 +++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs index fdcd0df6..54c877e7 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs @@ -30,6 +30,15 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 } } + private int UnpIO_UnpRead(byte[] buf, int offset, int count) { + // NOTE: caller has logic to check for -1 for error we throw instead. + return this.readStream.Read(buf, offset, count); + } + + private void UnpIO_UnpWrite(byte[] buf, size_t offset, uint count) { + this.writeStream.Write(buf, checked((int)offset), checked((int)count)); + } + public void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream) { // as of 12/2017 .NET limits array indexing to using a signed integer @@ -95,6 +104,7 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 public static byte[] EnsureCapacity(byte[] array, int length) { return array.Length < length ? new byte[length] : array; } + } } #endif \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs index b5eda3de..b362dae0 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs @@ -258,7 +258,7 @@ bool UnpReadBuf() DataSize=ReadTop; int ReadCode=0; if (BitInput.MAX_SIZE!=DataSize) - ReadCode=UnpIO->UnpRead(Inp.InBuf+DataSize,BitInput.MAX_SIZE-DataSize); + ReadCode=UnpIO_UnpRead(Inp.InBuf,DataSize,BitInput.MAX_SIZE-DataSize); if (ReadCode>0) // Can be also -1. ReadTop+=ReadCode; ReadBorder=ReadTop-30; @@ -359,7 +359,7 @@ void UnpWriteBuf() Filters[I].Type=FILTER_NONE; if (OutMem!=null) - UnpIO->UnpWrite(OutMem,BlockLength); + UnpIO_UnpWrite(OutMem,0,BlockLength); UnpSomeRead=true; WrittenFileSize+=BlockLength; @@ -549,7 +549,7 @@ void UnpWriteData(byte[] Data, size_t offset, size_t Size) int64 LeftToWrite=DestUnpSize-WrittenFileSize; if ((int64)WriteSize>LeftToWrite) WriteSize=(size_t)LeftToWrite; - UnpIO->UnpWrite(Data, offset, WriteSize); + UnpIO_UnpWrite(Data, offset, WriteSize); WrittenFileSize+=Size; } From 66cb2ab662f67db93d04c520fe82318dfc9a8dac Mon Sep 17 00:00:00 2001 From: coderb Date: Wed, 20 Dec 2017 14:28:22 -0500 Subject: [PATCH 44/61] unrar5: unpack wip --- .../Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs | 10 +++++----- .../Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs | 6 +++--- .../Compressors/Rar/UnpackV2017/unpack_hpp.cs | 4 ++++ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs index b362dae0..4cf75009 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs @@ -637,7 +637,7 @@ bool ReadTables(BitInput Inp,ref UnpackBlockHeader Header, ref UnpackBlockTables BitLength[I]=(byte)Length; } - MakeDecodeTables(BitLength,Tables.BD,BC); + MakeDecodeTables(BitLength,0,Tables.BD,BC); byte[] Table = new byte[HUFF_TABLE_SIZE]; const uint TableSize=HUFF_TABLE_SIZE; @@ -702,10 +702,10 @@ bool ReadTables(BitInput Inp,ref UnpackBlockHeader Header, ref UnpackBlockTables TablesRead5=true; if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop) return false; - MakeDecodeTables(&Table[0],Tables.LD,NC); - MakeDecodeTables(&Table[NC],Tables.DD,DC); - MakeDecodeTables(&Table[NC+DC],Tables.LDD,LDC); - MakeDecodeTables(&Table[NC+DC+LDC],Tables.RD,RC); + MakeDecodeTables(Table, 0, Tables.LD,NC); + MakeDecodeTables(Table, (int)NC,Tables.DD,DC); + MakeDecodeTables(Table, (int)(NC+DC),Tables.LDD,LDC); + MakeDecodeTables(Table, (int)(NC+DC+LDC),Tables.RD,RC); return true; } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs index 352da170..619974c2 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs @@ -233,7 +233,7 @@ void UnpInitData(bool Solid) // LengthTable contains the length in bits for every element of alphabet. // Dec is the structure to decode Huffman code/ // Size is size of length table and DecodeNum field in Dec structure, -void MakeDecodeTables(byte[] LengthTable,DecodeTable Dec,uint Size) +void MakeDecodeTables(byte[] LengthTable, int offset, DecodeTable Dec,uint Size) { // Size of alphabet and DecodePos array. Dec.MaxNum=Size; @@ -242,7 +242,7 @@ void MakeDecodeTables(byte[] LengthTable,DecodeTable Dec,uint Size) uint[] LengthCount = new uint[16]; //memset(LengthCount,0,sizeof(LengthCount)); for (size_t I=0;I Date: Wed, 20 Dec 2017 14:58:49 -0500 Subject: [PATCH 45/61] unrar5: unpack wip --- src/SharpCompress/Archives/Rar/RarArchive.cs | 8 ++++++++ .../FragmentedWindow.unpack50frag_cpp.cs | 15 +++++++++++++++ .../Compressors/Rar/UnpackV2017/Unpack.cs | 1 - .../Rar/UnpackV2017/Unpack.unpack50_cpp.cs | 10 ++++++++-- .../Rar/UnpackV2017/Unpack.unpack_cpp.cs | 2 ++ src/SharpCompress/Readers/Rar/RarReader.cs | 4 ++++ 6 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/SharpCompress/Archives/Rar/RarArchive.cs b/src/SharpCompress/Archives/Rar/RarArchive.cs index ab341e0c..699073cb 100644 --- a/src/SharpCompress/Archives/Rar/RarArchive.cs +++ b/src/SharpCompress/Archives/Rar/RarArchive.cs @@ -24,7 +24,11 @@ namespace SharpCompress.Archives.Rar internal RarArchive(FileInfo fileInfo, ReaderOptions options) : base(ArchiveType.Rar, fileInfo, options) { +#if !RarV2017_USELEGACY + Unpack = new SharpCompress.Compressors.Rar.UnpackV2017.Unpack(); +#else Unpack = new SharpCompress.Compressors.Rar.UnpackV1.Unpack(); +#endif } protected override IEnumerable LoadVolumes(FileInfo file) @@ -41,7 +45,11 @@ namespace SharpCompress.Archives.Rar internal RarArchive(IEnumerable streams, ReaderOptions options) : base(ArchiveType.Rar, streams, options) { +#if !RarV2017_USELEGACY + Unpack = new SharpCompress.Compressors.Rar.UnpackV2017.Unpack(); +#else Unpack = new SharpCompress.Compressors.Rar.UnpackV1.Unpack(); +#endif } protected override IEnumerable LoadEntries(IEnumerable volumes) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs index ca1b78df..8d4dab7a 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs @@ -112,6 +112,21 @@ set { } } +// sharpcompress: added the following code +public void GetBuffer(size_t Item, out byte[] buf, out uint offset) { + if (Item0) - Filters.Alloc(Filters.Count-EmptyCount); + //Filters.Alloc(Filters.Count-EmptyCount); + throw new NotImplementedException(); if (!NotAllFiltersProcessed) // Only if all filters are processed. { @@ -428,6 +429,8 @@ void UnpWriteBuf() byte[] ApplyFilter(byte[] Data,uint DataSize,UnpackFilter Flt) { + throw new NotImplementedException(); +#if false byte[] SrcData=Data; switch(Flt.Type) { @@ -509,6 +512,7 @@ byte[] ApplyFilter(byte[] Data,uint DataSize,UnpackFilter Flt) } return null; +#endif } @@ -525,7 +529,9 @@ void UnpWriteArea(size_t StartPtr,size_t EndPtr) while (SizeToWrite>0) { size_t BlockSize=FragWindow.GetBlockSize(StartPtr,SizeToWrite); - UnpWriteData(&FragWindow[StartPtr],BlockSize); + //UnpWriteData(&FragWindow[StartPtr],BlockSize); + FragWindow.GetBuffer(StartPtr, out var __buffer, out var __offset); + UnpWriteData(__buffer, __offset, BlockSize); SizeToWrite-=BlockSize; StartPtr=(StartPtr+BlockSize) & MaxWinMask; } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs index 619974c2..502763ad 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs @@ -205,6 +205,8 @@ void UnpInitData(bool Solid) // memset(Window,0,MaxWinSize); //memset(&BlockTables,0,sizeof(BlockTables)); BlockTables = new UnpackBlockTables(); + // sharpcompress: no default ctor for struct + BlockTables.Init(); UnpPtr=WrPtr=0; WriteBorder=Math.Min(MaxWinSize,UNPACK_MAX_WRITE)&MaxWinMask; } diff --git a/src/SharpCompress/Readers/Rar/RarReader.cs b/src/SharpCompress/Readers/Rar/RarReader.cs index 20afa5e1..71fe260c 100644 --- a/src/SharpCompress/Readers/Rar/RarReader.cs +++ b/src/SharpCompress/Readers/Rar/RarReader.cs @@ -18,7 +18,11 @@ namespace SharpCompress.Readers.Rar internal RarReader(ReaderOptions options) : base(options, ArchiveType.Rar) { +#if !RarV2017_USELEGACY + this.pack = new SharpCompress.Compressors.Rar.UnpackV2017.Unpack(); +#else this.pack = new SharpCompress.Compressors.Rar.UnpackV1.Unpack(); +#endif } internal abstract void ValidateArchive(RarVolume archive); From 3e8f52689e77678d8a72d6fdffd97e1edaee9710 Mon Sep 17 00:00:00 2001 From: coderb Date: Wed, 20 Dec 2017 16:01:55 -0500 Subject: [PATCH 46/61] unrar5: unpack wip --- .../FragmentedWindow.unpack50frag_cpp.cs | 4 +-- .../Compressors/Rar/UnpackV2017/Unpack.cs | 9 +++-- .../Rar/UnpackV2017/Unpack.unpack15_cpp.cs | 34 +++++++++---------- .../Rar/UnpackV2017/Unpack.unpack20_cpp.cs | 18 +++++----- .../Rar/UnpackV2017/Unpack.unpack30_cpp.cs | 12 +++---- .../Rar/UnpackV2017/Unpack.unpack50_cpp.cs | 4 +-- .../Rar/UnpackV2017/Unpack.unpack_cpp.cs | 11 +++--- .../UnpackV2017/Unpack.unpackinline_cpp.cs | 4 +-- .../Compressors/Rar/UnpackV2017/unpack_hpp.cs | 6 ++-- 9 files changed, 45 insertions(+), 57 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs index 8d4dab7a..b0bc6da7 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs @@ -1,5 +1,4 @@ -#if RARWIP -#if !Rar2017_64bit +#if !Rar2017_64bit using nint = System.Int32; using nuint = System.UInt32; using size_t = System.UInt32; @@ -158,4 +157,3 @@ public size_t GetBlockSize(size_t StartPos,size_t RequiredSize) } } -#endif \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs index 6fbf86c0..e40ec2ec 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs @@ -1,5 +1,4 @@ -#if RARWIP -#if !Rar2017_64bit +#if !Rar2017_64bit using nint = System.Int32; using nuint = System.UInt32; using size_t = System.UInt32; @@ -43,7 +42,8 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 // as of 12/2017 .NET limits array indexing to using a signed integer // MaxWinSize causes unpack to use a fragmented window when the file // window size exceeds MaxWinSize - MaxWinSize = ((uint)int.MaxValue) + 1; + // uggh, that's not how this variable is used, it's the size of the currently allocated window buffer + //x MaxWinSize = ((uint)int.MaxValue) + 1; // may be long.MaxValue which could indicate unknown size (not present in header) DestUnpSize = fileHeader.UncompressedSize; @@ -63,7 +63,7 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 { UnstoreFile(); } else { - DoUnpack(this.fileHeader.CompressionMethod, this.fileHeader.IsSolid); + DoUnpack(this.fileHeader.CompressionAlgorithm, this.fileHeader.IsSolid); } } @@ -106,4 +106,3 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 } } -#endif \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs index 917116ef..9889e8e8 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs @@ -1,6 +1,4 @@ -#if RARWIP -#if !RarV2017_RAR5ONLY -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -11,6 +9,7 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 { internal partial class Unpack { +#if !RarV2017_RAR5ONLY const int STARTL1 =2; static uint[] DecL1={0x8000,0xa000,0xc000,0xd000,0xe000,0xea00, @@ -439,7 +438,7 @@ void GetFlagsBuf() ChSetC[NewFlagsPlace]=Flags; } - +#endif void UnpInitData15(bool Solid) { if (!Solid) @@ -457,33 +456,33 @@ void UnpInitData15(bool Solid) ReadTop=0; } - void InitHuff() { for (uint I=0;I<256;I++) { - ChSet[I]=ChSetB[I]=I<<8; - ChSetA[I]=I; - ChSetC[I]=((~I+1) & 0xff)<<8; + ChSet[I]=ChSetB[I]=(ushort)(I<<8); + ChSetA[I]=(ushort)I; + ChSetC[I]=(ushort)(((~I+1) & 0xff)<<8); } - memset(NToPl,0,sizeof(NToPl)); - memset(NToPlB,0,sizeof(NToPlB)); - memset(NToPlC,0,sizeof(NToPlC)); + Utility.Memset(NToPl,0,NToPl.Length); + Utility.Memset(NToPlB,0,NToPlB.Length); + Utility.Memset(NToPlC,0,NToPlC.Length); CorrHuff(ChSetB,NToPlB); } -void CorrHuff(ushort *CharSet,byte *NumToPlace) +void CorrHuff(ushort[] CharSet,byte[] NumToPlace) { int I,J; for (I=7;I>=0;I--) - for (J=0;J<32;J++,CharSet++) - *CharSet=(*CharSet & ~0xff) | I; - memset(NumToPlace,0,sizeof(NToPl)); + for (J=0;J<32;J++) + CharSet[J]=(ushort)((CharSet[J] & ~0xff) | I); + Utility.Memset(NumToPlace,0,NToPl.Length); for (I=6;I>=0;I--) - NumToPlace[I]=(7-I)*32; + NumToPlace[I]=(byte)((7-I)*32); } +#if !RarV2017_RAR5ONLY void CopyString15(uint Distance,uint Length) { @@ -504,8 +503,7 @@ uint DecodeNum(uint Num,uint StartPos,uint *DecTab,uint *PosTab) Inp.faddbits(StartPos); return(((Num-(I ? DecTab[I-1]:0))>>(16-StartPos))+PosTab[StartPos]); } +#endif } } -#endif -#endif \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs index 4744849b..40c5f055 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs @@ -1,6 +1,4 @@ -#if RARWIP -#if !RarV2017_RAR5ONLY -#if !Rar2017_64bit +#if !Rar2017_64bit using nint = System.Int32; using nuint = System.UInt32; using size_t = System.UInt32; @@ -17,13 +15,14 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; -using static SharpCompress.Compressors.Rar.UnpackV2017.Unpack.Unpack20Local; +//using static SharpCompress.Compressors.Rar.UnpackV2017.Unpack.Unpack20Local; namespace SharpCompress.Compressors.Rar.UnpackV2017 { internal partial class Unpack { +#if !RarV2017_RAR5ONLY void CopyString20(uint Length,uint Distance) { LastDist=OldDist[OldDistPtr++ & 3]=Distance; @@ -292,6 +291,7 @@ void ReadLastTables() ReadTables20(); } +#endif void UnpInitData20(bool Solid) { @@ -303,12 +303,15 @@ void UnpInitData20(bool Solid) UnpCurChannel=0; UnpChannels=1; - memset(AudV,0,sizeof(AudV)); + //memset(AudV,0,sizeof(AudV)); + AudV = new AudioVariables[4]; Utility.Memset(UnpOldTable20, 0, UnpOldTable20.Length); - memset(MD,0,sizeof(MD)); + //memset(MD,0,sizeof(MD)); + MD = new DecodeTable[4]; } } +#if !RarV2017_RAR5ONLY byte DecodeAudio(int Delta) { @@ -402,8 +405,7 @@ byte DecodeAudio(int Delta) } return (byte)Ch; } +#endif } } -#endif -#endif \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs index f440d0d5..4b63e61b 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs @@ -1,6 +1,4 @@ -#if RARWIP -#if !RarV2017_RAR5ONLY -#if !Rar2017_64bit +#if !Rar2017_64bit using nint = System.Int32; using nuint = System.UInt32; using size_t = System.UInt32; @@ -18,12 +16,14 @@ using System.Text; using System.Threading.Tasks; using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; using static SharpCompress.Compressors.Rar.UnpackV2017.UnpackGlobal; -using static SharpCompress.Compressors.Rar.UnpackV2017.Unpack.Unpack30Local; +//using static SharpCompress.Compressors.Rar.UnpackV2017.Unpack.Unpack30Local; namespace SharpCompress.Compressors.Rar.UnpackV2017 { internal partial class Unpack { + +#if !RarV2017_RAR5ONLY // We use it instead of direct PPM.DecodeChar call to be sure that // we reset PPM structures in case of corrupt data. It is important, // because these structures can be invalid after PPM.DecodeChar returned -1. @@ -761,6 +761,7 @@ bool ReadTables30() return true; } +#endif void UnpInitData30(bool Solid) { @@ -794,7 +795,6 @@ void InitFilters30(bool Solid) PrgStack.Clear(); } + } } -#endif -#endif \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs index d39dbb98..8e5b7b85 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs @@ -1,5 +1,4 @@ -#if RARWIP -#if !Rar2017_64bit +#if !Rar2017_64bit using nint = System.Int32; using nuint = System.UInt32; using size_t = System.UInt32; @@ -724,4 +723,3 @@ void InitFilters() } } -#endif \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs index 502763ad..4126bf34 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs @@ -1,5 +1,4 @@ -#if RARWIP -#if !Rar2017_64bit +#if !Rar2017_64bit using nint = System.Int32; using nuint = System.UInt32; using size_t = System.UInt32; @@ -44,13 +43,11 @@ public Unpack(/* ComprDataIO *DataIO */) // It prevents crash if first DoUnpack call is later made with wrong // (true) 'Solid' value. UnpInitData(false); -#if !RarV2017_RAR5ONLY #if !RarV2017_SFX_MODULE // RAR 1.5 decompression initialization UnpInitData15(false); InitHuff(); #endif -#endif } // later: may need Dispose() if we support thread pool @@ -191,6 +188,9 @@ void DoUnpack(uint Method,bool Solid) #endif Unpack5(Solid); break; +#if !Rar2017_NOSTRICT + default: throw new InvalidFormatException("unknown compression method " + Method); +#endif } } @@ -222,12 +222,10 @@ void UnpInitData(bool Solid) //memset(&BlockHeader,0,sizeof(BlockHeader)); BlockHeader = new UnpackBlockHeader(); BlockHeader.BlockSize=-1; // '-1' means not defined yet. -#if !RarV2017_RAR5ONLY #if !RarV2017_SFX_MODULE UnpInitData20(Solid); #endif UnpInitData30(Solid); -#endif UnpInitData50(Solid); } @@ -379,4 +377,3 @@ void MakeDecodeTables(byte[] LengthTable, int offset, DecodeTable Dec,uint Size) } } -#endif \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpackinline_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpackinline_cpp.cs index 0f839584..cd2fa8f0 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpackinline_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpackinline_cpp.cs @@ -1,5 +1,4 @@ -#if RARWIP -#if !Rar2017_64bit +#if !Rar2017_64bit using nint = System.Int32; using nuint = System.UInt32; using size_t = System.UInt32; @@ -175,4 +174,3 @@ uint SlotToLength(BitInput Inp,uint Slot) } } -#endif \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs index 3e238e24..afea642c 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs @@ -1,5 +1,4 @@ -#if RARWIP -#if !Rar2017_64bit +#if !Rar2017_64bit using nint = System.Int32; using nuint = System.UInt32; using size_t = System.UInt32; @@ -341,7 +340,7 @@ internal partial class Unpack /***************************** Unpack v 2.0 *********************************/ //void Unpack20(bool Solid); - readonly DecodeTable[] MD = new DecodeTable[4]; // Decode multimedia data, up to 4 channels. + DecodeTable[] MD = new DecodeTable[4]; // Decode multimedia data, up to 4 channels. readonly byte[] UnpOldTable20 = new byte[MC20*4]; bool UnpAudioBlock; @@ -442,4 +441,3 @@ internal partial class Unpack } } -#endif \ No newline at end of file From 218823e9b21b3f68cf188e3aa31f4ee56b60e2f1 Mon Sep 17 00:00:00 2001 From: coderb Date: Wed, 20 Dec 2017 18:13:04 -0500 Subject: [PATCH 47/61] rar5: unpack bugfix --- .../Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs index 4126bf34..5645edec 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs @@ -283,6 +283,7 @@ void MakeDecodeTables(byte[] LengthTable, int offset, DecodeTable Dec,uint Size) // so we cannot use the original DecodePos. uint[] CopyDecodePos = new uint[Dec.DecodePos.Length]; //memcpy(CopyDecodePos,Dec->DecodePos,sizeof(CopyDecodePos)); + Array.Copy(Dec.DecodePos, 0, CopyDecodePos, 0, CopyDecodePos.Length); // For every bit length in the bit length table and so for every item // of alphabet. From a1e7f55b953c7a3259b3ccf0e5541a34532785eb Mon Sep 17 00:00:00 2001 From: coderb Date: Wed, 20 Dec 2017 18:32:12 -0500 Subject: [PATCH 48/61] rar5: unpack working!!! --- .../Rar/UnpackV2017/Unpack.rawint_hpp.cs | 126 ++++++++++++++++++ .../Rar/UnpackV2017/Unpack.unpack50_cpp.cs | 33 +++-- 2 files changed, 142 insertions(+), 17 deletions(-) create mode 100644 src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.rawint_hpp.cs diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.rawint_hpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.rawint_hpp.cs new file mode 100644 index 00000000..0d30cd8e --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.rawint_hpp.cs @@ -0,0 +1,126 @@ +#if !Rar2017_64bit +using nint = System.Int32; +using nuint = System.UInt32; +using size_t = System.UInt32; +#else +using nint = System.Int64; +using nuint = System.UInt64; +using size_t = System.UInt64; +#endif +using int64 = System.Int64; +using uint32 = System.UInt32; + +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharpCompress.Compressors.Rar.UnpackV2017 +{ + partial class Unpack + { +//#define rotls(x,n,xsize) (((x)<<(n)) | ((x)>>(xsize-(n)))) +//#define rotrs(x,n,xsize) (((x)>>(n)) | ((x)<<(xsize-(n)))) +//#define rotl32(x,n) rotls(x,n,32) +//#define rotr32(x,n) rotrs(x,n,32) +// +//inline uint RawGet2(const void *Data) +//{ +// byte *D=(byte *)Data; +// return D[0]+(D[1]<<8); +//} + +uint32 RawGet4(byte[] D, int offset) +{ + return (uint)(D[offset]+(D[offset+1]<<8)+(D[offset+2]<<16)+(D[offset+3]<<24)); +} + +//inline uint64 RawGet8(const void *Data) +//{ +//#if defined(BIG_ENDIAN) || !defined(ALLOW_MISALIGNED) +// byte *D=(byte *)Data; +// return INT32TO64(RawGet4(D+4),RawGet4(D)); +//#else +// return *(uint64 *)Data; +//#endif +//} +// +// +//inline void RawPut2(uint Field,void *Data) +//{ +// byte *D=(byte *)Data; +// D[0]=(byte)(Field); +// D[1]=(byte)(Field>>8); +//} + +void RawPut4(uint32 Field,byte[] D, int offset) +{ + D[offset]=(byte)(Field); + D[offset+1]=(byte)(Field>>8); + D[offset+2]=(byte)(Field>>16); + D[offset+3]=(byte)(Field>>24); +} + +//inline void RawPut8(uint64 Field,void *Data) +//{ +//#if defined(BIG_ENDIAN) || !defined(ALLOW_MISALIGNED) +// byte *D=(byte *)Data; +// D[0]=(byte)(Field); +// D[1]=(byte)(Field>>8); +// D[2]=(byte)(Field>>16); +// D[3]=(byte)(Field>>24); +// D[4]=(byte)(Field>>32); +// D[5]=(byte)(Field>>40); +// D[6]=(byte)(Field>>48); +// D[7]=(byte)(Field>>56); +//#else +// *(uint64 *)Data=Field; +//#endif +//} + + +//#if defined(LITTLE_ENDIAN) && defined(ALLOW_MISALIGNED) +//#define USE_MEM_BYTESWAP +//#endif + +// Load 4 big endian bytes from memory and return uint32. +//inline uint32 RawGetBE4(const byte *m) +//{ +//#if defined(USE_MEM_BYTESWAP) && defined(_MSC_VER) +// return _byteswap_ulong(*(uint32 *)m); +//#elif defined(USE_MEM_BYTESWAP) && (__GNUC__ > 3) && (__GNUC_MINOR__ > 2) +// return __builtin_bswap32(*(uint32 *)m); +//#else +// return uint32(m[0]<<24) | uint32(m[1]<<16) | uint32(m[2]<<8) | m[3]; +//#endif +//} + + +// Save integer to memory as big endian. +//inline void RawPutBE4(uint32 i,byte *mem) +//{ +//#if defined(USE_MEM_BYTESWAP) && defined(_MSC_VER) +// *(uint32*)mem = _byteswap_ulong(i); +//#elif defined(USE_MEM_BYTESWAP) && (__GNUC__ > 3) && (__GNUC_MINOR__ > 2) +// *(uint32*)mem = __builtin_bswap32(i); +//#else +// mem[0]=byte(i>>24); +// mem[1]=byte(i>>16); +// mem[2]=byte(i>>8); +// mem[3]=byte(i); +//#endif +//} + + +//inline uint32 ByteSwap32(uint32 i) +//{ +//#ifdef _MSC_VER +// return _byteswap_ulong(i); +//#elif (__GNUC__ > 3) && (__GNUC_MINOR__ > 2) +// return __builtin_bswap32(i); +//#else +// return (rotl32(i,24)&0xFF00FF00)|(rotl32(i,8)&0x00FF00FF); +//#endif +//} + + } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs index 8e5b7b85..3a0d63c5 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs @@ -404,7 +404,7 @@ void UnpWriteBuf() } if (EmptyCount>0) //Filters.Alloc(Filters.Count-EmptyCount); - throw new NotImplementedException(); + Filters.RemoveRange(Filters.Count-EmptyCount, EmptyCount); if (!NotAllFiltersProcessed) // Only if all filters are processed. { @@ -426,11 +426,10 @@ void UnpWriteBuf() } -byte[] ApplyFilter(byte[] Data,uint DataSize,UnpackFilter Flt) +byte[] ApplyFilter(byte[] __d,uint DataSize,UnpackFilter Flt) { - throw new NotImplementedException(); -#if false - byte[] SrcData=Data; + int Data = 0; + byte[] SrcData=__d; switch(Flt.Type) { case FILTER_E8: @@ -444,23 +443,24 @@ byte[] ApplyFilter(byte[] Data,uint DataSize,UnpackFilter Flt) // to avoid overflow for DataSize<4. for (uint CurPos=0;CurPos+4=0 - RawPut4(Addr+FileSize,Data); + RawPut4(Addr+FileSize,__d,Data); } else if (((Addr-FileSize) & 0x80000000)!=0) // Addr>8); - D[2]=(byte)(Offset>>16); + __d[D]=(byte)Offset; + __d[D+1]=(byte)(Offset>>8); + __d[D+2]=(byte)(Offset>>16); } } } @@ -504,14 +504,13 @@ byte[] ApplyFilter(byte[] Data,uint DataSize,UnpackFilter Flt) { byte PrevByte=0; for (uint DestPos=CurChannel;DestPos Date: Wed, 20 Dec 2017 18:32:36 -0500 Subject: [PATCH 49/61] rar5: fix checkin --- .../Rar/UnpackV1/Decode/AudioVariables.cs | 26 +++ .../Rar/UnpackV1/Decode/BitDecode.cs | 10 + .../Rar/UnpackV1/Decode/CodeType.cs | 15 ++ .../Compressors/Rar/UnpackV1/Decode/Decode.cs | 34 +++ .../Rar/UnpackV1/Decode/DistDecode.cs | 10 + .../Rar/UnpackV1/Decode/FilterType.cs | 10 + .../Rar/UnpackV1/Decode/LitDecode.cs | 10 + .../Rar/UnpackV1/Decode/LowDistDecode.cs | 10 + .../Rar/UnpackV1/Decode/MultDecode.cs | 10 + .../Rar/UnpackV1/Decode/PackDef.cs | 48 ++++ .../Rar/UnpackV1/Decode/RepDecode.cs | 10 + .../Rar/UnpackV1/PPM/BlockTypes.cs | 8 + .../Compressors/Rar/UnpackV1/UnpackUtility.cs | 220 ++++++++++++++++++ 13 files changed, 421 insertions(+) create mode 100644 src/SharpCompress/Compressors/Rar/UnpackV1/Decode/AudioVariables.cs create mode 100644 src/SharpCompress/Compressors/Rar/UnpackV1/Decode/BitDecode.cs create mode 100644 src/SharpCompress/Compressors/Rar/UnpackV1/Decode/CodeType.cs create mode 100644 src/SharpCompress/Compressors/Rar/UnpackV1/Decode/Decode.cs create mode 100644 src/SharpCompress/Compressors/Rar/UnpackV1/Decode/DistDecode.cs create mode 100644 src/SharpCompress/Compressors/Rar/UnpackV1/Decode/FilterType.cs create mode 100644 src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LitDecode.cs create mode 100644 src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LowDistDecode.cs create mode 100644 src/SharpCompress/Compressors/Rar/UnpackV1/Decode/MultDecode.cs create mode 100644 src/SharpCompress/Compressors/Rar/UnpackV1/Decode/PackDef.cs create mode 100644 src/SharpCompress/Compressors/Rar/UnpackV1/Decode/RepDecode.cs create mode 100644 src/SharpCompress/Compressors/Rar/UnpackV1/PPM/BlockTypes.cs create mode 100644 src/SharpCompress/Compressors/Rar/UnpackV1/UnpackUtility.cs diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/AudioVariables.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/AudioVariables.cs new file mode 100644 index 00000000..c2510617 --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/AudioVariables.cs @@ -0,0 +1,26 @@ +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +{ + internal class AudioVariables + { + internal AudioVariables() + { + Dif = new int[11]; + } + + internal int[] Dif { get; } + internal int ByteCount { get; set; } + internal int D1 { get; set; } + + internal int D2 { get; set; } + internal int D3 { get; set; } + internal int D4 { get; set; } + + internal int K1 { get; set; } + internal int K2 { get; set; } + internal int K3 { get; set; } + internal int K4 { get; set; } + internal int K5 { get; set; } + internal int LastChar { get; set; } + internal int LastDelta { get; set; } + } +} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/BitDecode.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/BitDecode.cs new file mode 100644 index 00000000..a8d499b5 --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/BitDecode.cs @@ -0,0 +1,10 @@ +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +{ + internal class BitDecode : Decode + { + internal BitDecode() + : base(new int[PackDef.BC]) + { + } + } +} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/CodeType.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/CodeType.cs new file mode 100644 index 00000000..e9703ff1 --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/CodeType.cs @@ -0,0 +1,15 @@ +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +{ + internal enum CodeType + { + CODE_HUFFMAN, + CODE_LZ, + CODE_LZ2, + CODE_REPEATLZ, + CODE_CACHELZ, + CODE_STARTFILE, + CODE_ENDFILE, + CODE_VM, + CODE_VMDATA + } +} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/Decode.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/Decode.cs new file mode 100644 index 00000000..ceed74de --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/Decode.cs @@ -0,0 +1,34 @@ +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +{ + internal class Decode + { + internal Decode() + : this(new int[2]) + { + } + + protected Decode(int[] customDecodeNum) + { + DecodeLen = new int[16]; + DecodePos = new int[16]; + DecodeNum = customDecodeNum; + } + + /// returns the decode Length array + /// decodeLength + /// + internal int[] DecodeLen { get; } + + /// returns the decode num array + /// decodeNum + /// + internal int[] DecodeNum { get; } + + /// returns the decodePos array + /// decodePos + /// + internal int[] DecodePos { get; } + + internal int MaxNum { get; set; } + } +} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/DistDecode.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/DistDecode.cs new file mode 100644 index 00000000..1a0b666b --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/DistDecode.cs @@ -0,0 +1,10 @@ +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +{ + internal class DistDecode : Decode + { + internal DistDecode() + : base(new int[PackDef.DC]) + { + } + } +} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/FilterType.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/FilterType.cs new file mode 100644 index 00000000..6d915e44 --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/FilterType.cs @@ -0,0 +1,10 @@ +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +{ + internal enum FilterType : byte + { + // These values must not be changed, because we use them directly + // in RAR5 compression and decompression code. + FILTER_DELTA=0, FILTER_E8, FILTER_E8E9, FILTER_ARM, + FILTER_AUDIO, FILTER_RGB, FILTER_ITANIUM, FILTER_PPM, FILTER_NONE + } +} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LitDecode.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LitDecode.cs new file mode 100644 index 00000000..8316c261 --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LitDecode.cs @@ -0,0 +1,10 @@ +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +{ + internal class LitDecode : Decode + { + internal LitDecode() + : base(new int[PackDef.NC]) + { + } + } +} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LowDistDecode.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LowDistDecode.cs new file mode 100644 index 00000000..a2095bd4 --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LowDistDecode.cs @@ -0,0 +1,10 @@ +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +{ + internal class LowDistDecode : Decode + { + internal LowDistDecode() + : base(new int[PackDef.LDC]) + { + } + } +} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/MultDecode.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/MultDecode.cs new file mode 100644 index 00000000..e6df223c --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/MultDecode.cs @@ -0,0 +1,10 @@ +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +{ + internal class MultDecode : Decode + { + internal MultDecode() + : base(new int[PackDef.MC20]) + { + } + } +} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/PackDef.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/PackDef.cs new file mode 100644 index 00000000..87f1977e --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/PackDef.cs @@ -0,0 +1,48 @@ +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +{ + internal static class PackDef + { + // 20171217 NOTE: these contants are gone from unrar src code + // seems to be more dynamic + public const int MAXWINSIZE = 0x400000; + public const int MAXWINMASK = MAXWINSIZE - 1; + + public const uint MAX_LZ_MATCH = 0x1001; + public const uint MAX3_LZ_MATCH = 0x101; // Maximum match length for RAR v3. + public const int LOW_DIST_REP_COUNT = 16; + + public const int NC = 299; /* alphabet = {0, 1, 2, ..., NC - 1} */ + public const int DC = 60; + public const int LDC = 17; + public const int RC = 28; +// 20171217: NOTE: these constants seem to have been updated in the unrar src code +// at some unknown point. updating causes decompression failure, not sure why. +// public const int NC = 306; /* alphabet = {0, 1, 2, ..., NC - 1} */ +// public const int DC = 64; +// public const int LDC = 16; +// public const int RC = 44; + public const int HUFF_TABLE_SIZE = NC + DC + RC + LDC; + public const int BC = 20; + + public const uint NC30 = 299; /* alphabet = {0, 1, 2, ..., NC - 1} */ + public const uint DC30 = 60; + public const uint LDC30 = 17; + public const uint RC30 = 28; + public const uint BC30 = 20; + public const uint HUFF_TABLE_SIZE30 = NC30 + DC30 + RC30 + LDC30; + + public const int NC20 = 298; /* alphabet = {0, 1, 2, ..., NC - 1} */ + public const int DC20 = 48; + public const int RC20 = 28; + public const int BC20 = 19; + public const int MC20 = 257; + + // Largest alphabet size among all values listed above. + public const uint LARGEST_TABLE_SIZE = 306; + + //public enum { + // CODE_HUFFMAN, CODE_LZ, CODE_REPEATLZ, CODE_CACHELZ, CODE_STARTFILE, + // CODE_ENDFILE, CODE_FILTER, CODE_FILTERDATA + //} + } +} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/RepDecode.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/RepDecode.cs new file mode 100644 index 00000000..24d72d16 --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/RepDecode.cs @@ -0,0 +1,10 @@ +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +{ + internal class RepDecode : Decode + { + internal RepDecode() + : base(new int[PackDef.RC]) + { + } + } +} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/PPM/BlockTypes.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/PPM/BlockTypes.cs new file mode 100644 index 00000000..b3f79fdc --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/PPM/BlockTypes.cs @@ -0,0 +1,8 @@ +namespace SharpCompress.Compressors.Rar.UnpackV1.PPM +{ + internal enum BlockTypes + { + BLOCK_LZ = 0, + BLOCK_PPM = 1 + } +} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackUtility.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackUtility.cs new file mode 100644 index 00000000..46261496 --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackUtility.cs @@ -0,0 +1,220 @@ +using SharpCompress.Compressors.Rar.VM; + +namespace SharpCompress.Compressors.Rar.UnpackV1 +{ + internal static class UnpackUtility + { +//!!! TODO rename methods + internal static uint DecodeNumber(this BitInput input, Decode.Decode dec) { + return (uint)input.decodeNumber(dec); + } + + internal static int decodeNumber(this BitInput input, Decode.Decode dec) + { + int bits; + long bitField = input.GetBits() & 0xfffe; + + // if (bitField < dec.getDecodeLen()[8]) { + // if (bitField < dec.getDecodeLen()[4]) { + // if (bitField < dec.getDecodeLen()[2]) { + // if (bitField < dec.getDecodeLen()[1]) { + // bits = 1; + // } else { + // bits = 2; + // } + // } else { + // if (bitField < dec.getDecodeLen()[3]) { + // bits = 3; + // } else { + // bits = 4; + // } + // } + // } else { + // if (bitField < dec.getDecodeLen()[6]) { + // if (bitField < dec.getDecodeLen()[5]) + // bits = 5; + // else + // bits = 6; + // } else { + // if (bitField < dec.getDecodeLen()[7]) { + // bits = 7; + // } else { + // bits = 8; + // } + // } + // } + // } else { + // if (bitField < dec.getDecodeLen()[12]) { + // if (bitField < dec.getDecodeLen()[10]) + // if (bitField < dec.getDecodeLen()[9]) + // bits = 9; + // else + // bits = 10; + // else if (bitField < dec.getDecodeLen()[11]) + // bits = 11; + // else + // bits = 12; + // } else { + // if (bitField < dec.getDecodeLen()[14]) { + // if (bitField < dec.getDecodeLen()[13]) { + // bits = 13; + // } else { + // bits = 14; + // } + // } else { + // bits = 15; + // } + // } + // } + // addbits(bits); + // int N = dec.getDecodePos()[bits] + // + (((int) bitField - dec.getDecodeLen()[bits - 1]) >>> (16 - bits)); + // if (N >= dec.getMaxNum()) { + // N = 0; + // } + // return (dec.getDecodeNum()[N]); + int[] decodeLen = dec.DecodeLen; + if (bitField < decodeLen[8]) + { + if (bitField < decodeLen[4]) + { + if (bitField < decodeLen[2]) + { + if (bitField < decodeLen[1]) + { + bits = 1; + } + else + { + bits = 2; + } + } + else + { + if (bitField < decodeLen[3]) + { + bits = 3; + } + else + { + bits = 4; + } + } + } + else + { + if (bitField < decodeLen[6]) + { + if (bitField < decodeLen[5]) + { + bits = 5; + } + else + { + bits = 6; + } + } + else + { + if (bitField < decodeLen[7]) + { + bits = 7; + } + else + { + bits = 8; + } + } + } + } + else + { + if (bitField < decodeLen[12]) + { + if (bitField < decodeLen[10]) + { + if (bitField < decodeLen[9]) + { + bits = 9; + } + else + { + bits = 10; + } + } + else if (bitField < decodeLen[11]) + { + bits = 11; + } + else + { + bits = 12; + } + } + else + { + if (bitField < decodeLen[14]) + { + if (bitField < decodeLen[13]) + { + bits = 13; + } + else + { + bits = 14; + } + } + else + { + bits = 15; + } + } + } + input.AddBits(bits); + int N = dec.DecodePos[bits] + (Utility.URShift(((int)bitField - decodeLen[bits - 1]), (16 - bits))); + if (N >= dec.MaxNum) + { + N = 0; + } + return (dec.DecodeNum[N]); + } + + internal static void makeDecodeTables(byte[] lenTab, int offset, Decode.Decode dec, int size) + { + int[] lenCount = new int[16]; + int[] tmpPos = new int[16]; + int i; + long M, N; + + Utility.Fill(lenCount, 0); // memset(LenCount,0,sizeof(LenCount)); + + Utility.Fill(dec.DecodeNum, 0); // memset(Dec->DecodeNum,0,Size*sizeof(*Dec->DecodeNum)); + + for (i = 0; i < size; i++) + { + lenCount[lenTab[offset + i] & 0xF]++; + } + lenCount[0] = 0; + for (tmpPos[0] = 0, dec.DecodePos[0] = 0, dec.DecodeLen[0] = 0, N = 0, i = 1; i < 16; i++) + { + N = 2 * (N + lenCount[i]); + M = N << (15 - i); + if (M > 0xFFFF) + { + M = 0xFFFF; + } + dec.DecodeLen[i] = (int)M; + tmpPos[i] = dec.DecodePos[i] = dec.DecodePos[i - 1] + lenCount[i - 1]; + } + + for (i = 0; i < size; i++) + { + if (lenTab[offset + i] != 0) + { + dec.DecodeNum[tmpPos[lenTab[offset + i] & 0xF]++] = i; + } + } + dec.MaxNum = size; + } + } +} \ No newline at end of file From c42dc646ae99dcd0459ed968ba0d4eb06432d1de Mon Sep 17 00:00:00 2001 From: coderb Date: Wed, 20 Dec 2017 18:51:48 -0500 Subject: [PATCH 50/61] rar5: reporting older algos --- .../Rar/UnpackV2017/Unpack.unpack30_cpp.cs | 162 +++++++++--------- 1 file changed, 80 insertions(+), 82 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs index 4b63e61b..ac1be05f 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs @@ -10,20 +10,16 @@ using size_t = System.UInt64; using int64 = System.Int64; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; using static SharpCompress.Compressors.Rar.UnpackV2017.UnpackGlobal; -//using static SharpCompress.Compressors.Rar.UnpackV2017.Unpack.Unpack30Local; +using static SharpCompress.Compressors.Rar.UnpackV2017.Unpack.Unpack30Local; namespace SharpCompress.Compressors.Rar.UnpackV2017 { internal partial class Unpack { -#if !RarV2017_RAR5ONLY +#if true || !RarV2017_RAR5ONLY // We use it instead of direct PPM.DecodeChar call to be sure that // we reset PPM structures in case of corrupt data. It is important, // because these structures can be invalid after PPM.DecodeChar returned -1. @@ -41,8 +37,8 @@ int SafePPMDecodeChar() internal static class Unpack30Local { public static byte[] LDecode={0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224}; public static byte[] LBits= {0,0,0,0,0,0,0,0,1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5}; - public static int DDecode[DC]; - public static byte DBits[DC]; + public static int[] DDecode = new int[DC]; + public static byte[] DBits = new byte[DC]; public static int[] DBitLengthCounts= {4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,14,0,12}; public static byte[] SDDecode={0,4,8,16,32,64,128,192}; public static byte[] SDBits= {2,2,3, 4, 5, 6, 6, 6}; @@ -54,11 +50,11 @@ void Unpack29(bool Solid) if (DDecode[1]==0) { int Dist=0,BitLength=0,Slot=0; - for (int I=0;I=271) { - uint Length=LDecode[Number-=271]+3; + uint Length=(uint)(LDecode[Number-=271]+3); if ((Bits=LBits[Number])>0) { - Length+=Inp.getbits()>>(16-Bits); + Length+=Inp.getbits()>>(int)(16-Bits); Inp.addbits(Bits); } uint DistNumber=DecodeNumber(Inp,BlockTables.DD); - uint Distance=DDecode[DistNumber]+1; + uint Distance=(uint)(DDecode[DistNumber]+1); if ((Bits=DBits[DistNumber])>0) { if (DistNumber>9) { if (Bits>4) { - Distance+=((Inp.getbits()>>(20-Bits))<<4); + Distance+=((Inp.getbits()>>(int)(20-Bits))<<4); Inp.addbits(Bits-4); } if (LowDistRepCount>0) { LowDistRepCount--; - Distance+=PrevLowDist; + Distance+=(uint)PrevLowDist; } else { uint LowDist=DecodeNumber(Inp,BlockTables.LDD); if (LowDist==16) { - LowDistRepCount=LOW_DIST_REP_COUNT-1; - Distance+=PrevLowDist; + LowDistRepCount=(int)(LOW_DIST_REP_COUNT-1); + Distance+=(uint)PrevLowDist; } else { Distance+=LowDist; - PrevLowDist=LowDist; + PrevLowDist=(int)LowDist; } } } else { - Distance+=Inp.getbits()>>(16-Bits); + Distance+=Inp.getbits()>>(int)(16-Bits); Inp.addbits(Bits); } } @@ -255,19 +251,19 @@ void Unpack29(bool Solid) int Length=LDecode[LengthNumber]+2; if ((Bits=LBits[LengthNumber])>0) { - Length+=Inp.getbits()>>(16-Bits); + Length+=Inp.getbits()>>(int)(16-Bits); Inp.addbits(Bits); } - LastLength=Length; - CopyString(Length,Distance); + LastLength=(uint)Length; + CopyString((uint)Length,Distance); continue; } if (Number<272) { - uint Distance=SDDecode[Number-=263]+1; + uint Distance=(uint)(SDDecode[Number-=263]+1); if ((Bits=SDBits[Number])>0) { - Distance+=Inp.getbits()>>(16-Bits); + Distance+=Inp.getbits()>>(int)(16-Bits); Inp.addbits(Bits); } InsertOldDist(Distance); @@ -385,10 +381,11 @@ bool ReadVMCodePPM() } -bool AddVMCode(uint FirstByte,byte *Code,int CodeSize) +bool AddVMCode(uint FirstByte,byte[] Code,int CodeSize) { VMCodeInp.InitBitInput(); - memcpy(VMCodeInp.InBuf,Code,Min(BitInput.MAX_SIZE,CodeSize)); + //x memcpy(VMCodeInp.InBuf,Code,Min(BitInput.MAX_SIZE,CodeSize)); + Array.Copy(Code, 0, VMCodeInp.InBuf, 0, Math.Min(BitInput.MAX_SIZE,CodeSize)); VM.Init(); uint FiltPos; @@ -401,28 +398,28 @@ bool AddVMCode(uint FirstByte,byte *Code,int CodeSize) FiltPos--; } else - FiltPos=LastFilter; // Use the same filter as last time. + FiltPos=(uint)this.LastFilter; // Use the same filter as last time. if (FiltPos>Filters30.Count || FiltPos>OldFilterLengths.Count) return false; - LastFilter=FiltPos; + LastFilter=(int)FiltPos; bool NewFilter=(FiltPos==Filters30.Count); - UnpackFilter30 *StackFilter=new UnpackFilter30(); // New filter for PrgStack. + UnpackFilter30 StackFilter=new UnpackFilter30(); // New filter for PrgStack. - UnpackFilter30 *Filter; + UnpackFilter30 Filter; if (NewFilter) // New filter code, never used before since VM reset. { if (FiltPos>MAX3_UNPACK_FILTERS) { // Too many different filters, corrupt archive. - delete StackFilter; + //delete StackFilter; return false; } Filters30.Add(1); Filters30[Filters30.Count-1]=Filter=new UnpackFilter30(); - StackFilter->ParentFilter=(uint)(Filters30.Count-1); + StackFilter.ParentFilter=(uint)(Filters30.Count-1); // Reserve one item to store the data block length of our new filter // entry. We'll set it to real block length below, after reading it. @@ -433,11 +430,11 @@ bool AddVMCode(uint FirstByte,byte *Code,int CodeSize) else // Filter was used in the past. { Filter=Filters30[FiltPos]; - StackFilter->ParentFilter=FiltPos; + StackFilter.ParentFilter=FiltPos; } - uint EmptyCount=0; - for (uint I=0;IMAX3_UNPACK_FILTERS) { - delete StackFilter; + //delete StackFilter; return false; } PrgStack.Add(1); @@ -461,13 +458,13 @@ bool AddVMCode(uint FirstByte,byte *Code,int CodeSize) uint BlockStart=RarVM.ReadData(VMCodeInp); if ((FirstByte & 0x40)!=0) BlockStart+=258; - StackFilter->BlockStart=(uint)((BlockStart+UnpPtr)&MaxWinMask); + StackFilter.BlockStart=(uint)((BlockStart+UnpPtr)&MaxWinMask); if ((FirstByte & 0x20)!=0) { - StackFilter->BlockLength=RarVM.ReadData(VMCodeInp); + StackFilter.BlockLength=RarVM.ReadData(VMCodeInp); // Store the last data block length for current filter. - OldFilterLengths[FiltPos]=StackFilter->BlockLength; + OldFilterLengths[FiltPos]=StackFilter.BlockLength; } else { @@ -475,23 +472,23 @@ bool AddVMCode(uint FirstByte,byte *Code,int CodeSize) // for same filter. It is possible for corrupt data to access a new // and not filled yet item of OldFilterLengths array here. This is why // we set new OldFilterLengths items to zero above. - StackFilter->BlockLength=FiltPosNextWindow=WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<=BlockStart; + StackFilter.NextWindow=WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<=BlockStart; // DebugLog("\nNextWindow: UnpPtr=%08x WrPtr=%08x BlockStart=%08x",UnpPtr,WrPtr,BlockStart); - memset(StackFilter->Prg.InitR,0,sizeof(StackFilter->Prg.InitR)); - StackFilter->Prg.InitR[4]=StackFilter->BlockLength; + memset(StackFilter.Prg.InitR,0,sizeof(StackFilter.Prg.InitR)); + StackFilter.Prg.InitR[4]=StackFilter.BlockLength; if ((FirstByte & 0x10)!=0) // Set registers to optional parameters if any. { uint InitMask=VMCodeInp.fgetbits()>>9; VMCodeInp.faddbits(7); - for (uint I=0;I<7;I++) - if (InitMask & (1<Prg.InitR[I]=RarVM.ReadData(VMCodeInp); + for (int I=0;I<7;I++) + if ((InitMask & (1<Prg); } - StackFilter->Prg.Type=Filter->Prg.Type; + StackFilter.Prg.Type=Filter.Prg.Type; return true; } @@ -535,7 +532,7 @@ bool UnpReadBuf30() } else DataSize=ReadTop; - int ReadCode=UnpIO->UnpRead(Inp.InBuf+DataSize,BitInput.MAX_SIZE-DataSize); + int ReadCode=UnpIO_UnpRead(Inp.InBuf,DataSize,BitInput.MAX_SIZE-DataSize); if (ReadCode>0) ReadTop+=ReadCode; ReadBorder=ReadTop-30; @@ -555,16 +552,16 @@ void UnpWriteBuf30() // these data can be used for future string matches, so we must // preserve them in original form. - UnpackFilter30 *flt=PrgStack[I]; + UnpackFilter30 flt=PrgStack[I]; if (flt==null) continue; - if (flt->NextWindow) + if (flt.NextWindow) { - flt->NextWindow=false; + flt.NextWindow=false; continue; } - uint BlockStart=flt->BlockStart; - uint BlockLength=flt->BlockLength; + uint BlockStart=flt.BlockStart; + uint BlockLength=flt.BlockLength; if (((BlockStart-WrittenBorder)&MaxWinMask)FilteredData; - uint FilteredDataSize=Prg->FilteredDataSize; + byte[] FilteredData=Prg.FilteredData; + uint FilteredDataSize=Prg.FilteredDataSize; delete PrgStack[I]; PrgStack[I]=null; while (I+1BlockStart!=BlockStart || - NextFilter->BlockLength!=FilteredDataSize || NextFilter->NextWindow) + if (NextFilter==null || NextFilter.BlockStart!=BlockStart || + NextFilter.BlockLength!=FilteredDataSize || NextFilter.NextWindow) break; // Apply several filters to same data block. VM.SetMemory(0,FilteredData,FilteredDataSize); - VM_PreparedProgram *ParentPrg=&Filters30[NextFilter->ParentFilter]->Prg; + VM_PreparedProgram *ParentPrg=&Filters30[NextFilter.ParentFilter]->Prg; VM_PreparedProgram *NextPrg=&NextFilter->Prg; ExecuteCode(NextPrg); - FilteredData=NextPrg->FilteredData; - FilteredDataSize=NextPrg->FilteredDataSize; + FilteredData=NextPrg.FilteredData; + FilteredDataSize=NextPrg.FilteredDataSize; I++; delete PrgStack[I]; PrgStack[I]=null; } - UnpIO->UnpWrite(FilteredData,FilteredDataSize); + UnpIO_UnpWrite(FilteredData,0,FilteredDataSize); UnpSomeRead=true; WrittenFileSize+=FilteredDataSize; WrittenBorder=BlockEnd; @@ -630,9 +627,9 @@ void UnpWriteBuf30() // the window border to process this filter next time, not now. for (size_t J=I;JNextWindow) - flt->NextWindow=false; + UnpackFilter30 flt=PrgStack[J]; + if (flt!=null && flt.NextWindow) + flt.NextWindow=false; } WrPtr=WrittenBorder; return; @@ -654,12 +651,12 @@ void ExecuteCode(VM_PreparedProgram *Prg) bool ReadTables30() { - byte BitLength[BC]; - byte Table[HUFF_TABLE_SIZE30]; + byte[] BitLength = new byte[BC]; + byte[] Table = new byte[HUFF_TABLE_SIZE30]; if (Inp.InAddr>ReadTop-25) if (!UnpReadBuf30()) return(false); - Inp.faddbits((8-Inp.InBit)&7); + Inp.faddbits((uint)((8-Inp.InBit)&7)); uint BitField=Inp.fgetbits(); if ((BitField & 0x8000) != 0) { @@ -694,9 +691,9 @@ bool ReadTables30() } } else - BitLength[I]=Length; + BitLength[I]=(byte)Length; } - MakeDecodeTables(BitLength,BlockTables.BD,BC30); + MakeDecodeTables(BitLength,0,BlockTables.BD,BC30); const uint TableSize=HUFF_TABLE_SIZE30; for (uint I=0;IReadTop) return false; - MakeDecodeTables(&Table[0],&BlockTables.LD,NC30); - MakeDecodeTables(&Table[NC30],&BlockTables.DD,DC30); - MakeDecodeTables(&Table[NC30+DC30],&BlockTables.LDD,LDC30); - MakeDecodeTables(&Table[NC30+DC30+LDC30],&BlockTables.RD,RC30); - memcpy(UnpOldTable,Table,sizeof(UnpOldTable)); + MakeDecodeTables(Table,0,BlockTables.LD,NC30); + MakeDecodeTables(Table,(int)NC30,BlockTables.DD,DC30); + MakeDecodeTables(Table,(int)(NC30+DC30),BlockTables.LDD,LDC30); + MakeDecodeTables(Table,(int)(NC30+DC30+LDC30),BlockTables.RD,RC30); + //x memcpy(UnpOldTable,Table,sizeof(UnpOldTable)); + Array.Copy(Table,0,UnpOldTable,0,UnpOldTable.Length); return true; } From 3c29122dfeb51d6ccb7133db67c692c415f3d910 Mon Sep 17 00:00:00 2001 From: coderb Date: Wed, 20 Dec 2017 19:04:41 -0500 Subject: [PATCH 51/61] rar5: porting old algos --- .../Rar/UnpackV2017/Unpack.unpack15_cpp.cs | 64 ++++++------- .../Rar/UnpackV2017/Unpack.unpack20_cpp.cs | 89 +++++++++---------- .../Rar/UnpackV2017/Unpack.unpack30_cpp.cs | 14 +-- 3 files changed, 78 insertions(+), 89 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs index 9889e8e8..87830e02 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs @@ -1,15 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; +using static SharpCompress.Compressors.Rar.UnpackV2017.Unpack.Unpack15Local; namespace SharpCompress.Compressors.Rar.UnpackV2017 { internal partial class Unpack { -#if !RarV2017_RAR5ONLY +#if true || !RarV2017_RAR5ONLY const int STARTL1 =2; static uint[] DecL1={0x8000,0xa000,0xc000,0xd000,0xe000,0xea00, @@ -129,13 +124,14 @@ uint GetShortLen1(uint pos) { return ((pos)==1 ? (uint)(this.Buf60+3):ShortLen1[ //#define GetShortLen2(pos) ((pos)==3 ? Buf60+3:ShortLen2[pos]) uint GetShortLen2(uint pos) { return ((pos)==3 ? (uint)(this.Buf60+3):ShortLen2[pos]); } - static uint[] ShortLen1={1,3,4,4,5,6,7,8,8,4,4,5,6,6,4,0}; - static uint[] ShortXor1={0,0xa0,0xd0,0xe0,0xf0,0xf8,0xfc,0xfe, +internal static class Unpack15Local { + public static readonly uint[] ShortLen1={1,3,4,4,5,6,7,8,8,4,4,5,6,6,4,0}; + public static readonly uint[] ShortXor1={0,0xa0,0xd0,0xe0,0xf0,0xf8,0xfc,0xfe, 0xff,0xc0,0x80,0x90,0x98,0x9c,0xb0}; - static uint[] ShortLen2={2,3,3,3,4,4,5,6,6,4,4,5,6,6,4,0}; - static uint[] ShortXor2={0,0x40,0x60,0xa0,0xd0,0xe0,0xf0,0xf8, + public static readonly uint[] ShortLen2={2,3,3,3,4,4,5,6,6,4,4,5,6,6,4,0}; + public static readonly uint[] ShortXor2={0,0x40,0x60,0xa0,0xd0,0xe0,0xf0,0xf8, 0xfc,0xc0,0x80,0x90,0x98,0x9c,0xb0}; - +} void ShortLZ() { @@ -166,14 +162,14 @@ void ShortLZ() if (AvrLn1<37) { for (Length=0;;Length++) - if (((BitField^ShortXor1[Length]) & (~(0xff>>GetShortLen1(Length))))==0) + if (((BitField^ShortXor1[Length]) & (~(0xff>>(int)GetShortLen1(Length))))==0) break; Inp.faddbits(GetShortLen1(Length)); } else { for (Length=0;;Length++) - if (((BitField^ShortXor2[Length]) & (~(0xff>>GetShortLen2(Length))))==0) + if (((BitField^ShortXor2[Length]) & (~(0xff>>(int)GetShortLen2(Length))))==0) break; Inp.faddbits(GetShortLen2(Length)); } @@ -224,13 +220,13 @@ void ShortLZ() AvrLn1 += Length; AvrLn1 -= AvrLn1 >> 4; - DistancePlace=DecodeNum(Inp.fgetbits(),STARTHF2,DecHf2,PosHf2) & 0xff; + DistancePlace=(int)(DecodeNum(Inp.fgetbits(),STARTHF2,DecHf2,PosHf2) & 0xff); Distance=ChSetA[DistancePlace]; if (--DistancePlace != -1) { LastDistance=ChSetA[DistancePlace]; - ChSetA[DistancePlace+1]=LastDistance; - ChSetA[DistancePlace]=Distance; + ChSetA[DistancePlace+1]=(ushort)LastDistance; + ChSetA[DistancePlace]=(ushort)Distance; } Length+=2; OldDist[OldDistPtr++] = ++Distance; @@ -271,7 +267,7 @@ void LongLZ() } else { - for (Length=0;((BitField<> 8; - while (1) + while (true) { Distance = ChSetB[DistancePlace & 0xff]; NewDistancePlace = NToPlB[Distance++ & 0xff]++; - if (!(Distance & 0xff)) + if ((Distance & 0xff) != 0) CorrHuff(ChSetB,NToPlB); else break; } ChSetB[DistancePlace & 0xff]=ChSetB[NewDistancePlace]; - ChSetB[NewDistancePlace]=Distance; + ChSetB[NewDistancePlace]=(ushort)Distance; Distance=((Distance & 0xff00) | (Inp.fgetbits() >> 8)) >> 1; Inp.faddbits(7); @@ -343,18 +339,18 @@ void HuffDecode() uint BitField=Inp.fgetbits(); if (AvrPlc > 0x75ff) - BytePlace=DecodeNum(BitField,STARTHF4,DecHf4,PosHf4); + BytePlace=(int)DecodeNum(BitField,STARTHF4,DecHf4,PosHf4); else if (AvrPlc > 0x5dff) - BytePlace=DecodeNum(BitField,STARTHF3,DecHf3,PosHf3); + BytePlace=(int)DecodeNum(BitField,STARTHF3,DecHf3,PosHf3); else if (AvrPlc > 0x35ff) - BytePlace=DecodeNum(BitField,STARTHF2,DecHf2,PosHf2); + BytePlace=(int)DecodeNum(BitField,STARTHF2,DecHf2,PosHf2); else if (AvrPlc > 0x0dff) - BytePlace=DecodeNum(BitField,STARTHF1,DecHf1,PosHf1); + BytePlace=(int)DecodeNum(BitField,STARTHF1,DecHf1,PosHf1); else - BytePlace=DecodeNum(BitField,STARTHF0,DecHf0,PosHf0); + BytePlace=(int)DecodeNum(BitField,STARTHF0,DecHf0,PosHf0); BytePlace&=0xff; if (StMode != 0) { @@ -384,7 +380,7 @@ void HuffDecode() else if (NumHuf++ >= 16 && FlagsCnt==0) StMode=1; - AvrPlc += BytePlace; + AvrPlc += (uint)BytePlace; AvrPlc -= AvrPlc >> 8; Nhfb+=16; if (Nhfb > 0xff) @@ -396,7 +392,7 @@ void HuffDecode() Window[UnpPtr++]=(byte)(ChSet[BytePlace]>>8); --DestUnpSize; - while (1) + while (true) { CurByte=ChSet[BytePlace]; NewBytePlace=NToPl[CurByte++ & 0xff]++; @@ -407,7 +403,7 @@ void HuffDecode() } ChSet[BytePlace]=ChSet[NewBytePlace]; - ChSet[NewBytePlace]=CurByte; + ChSet[NewBytePlace]=(ushort)CurByte; } @@ -421,7 +417,7 @@ void GetFlagsBuf() // While normally we do not use the last item to code the flags byte here, // we need to check for value 256 when unpacking in case we unpack // a corrupt archive. - if (FlagsPlace>=sizeof(ChSetC)/sizeof(ChSetC[0])) + if (FlagsPlace>=ChSetC.Length) return; while (true) @@ -435,7 +431,7 @@ void GetFlagsBuf() } ChSetC[FlagsPlace]=ChSetC[NewFlagsPlace]; - ChSetC[NewFlagsPlace]=Flags; + ChSetC[NewFlagsPlace]=(ushort)Flags; } #endif @@ -482,7 +478,6 @@ void CorrHuff(ushort[] CharSet,byte[] NumToPlace) NumToPlace[I]=(byte)((7-I)*32); } -#if !RarV2017_RAR5ONLY void CopyString15(uint Distance,uint Length) { @@ -495,15 +490,14 @@ void CopyString15(uint Distance,uint Length) } -uint DecodeNum(uint Num,uint StartPos,uint *DecTab,uint *PosTab) +uint DecodeNum(uint Num,uint StartPos,uint[] DecTab,uint[] PosTab) { int I; for (Num&=0xfff0,I=0;DecTab[I]<=Num;I++) StartPos++; Inp.faddbits(StartPos); - return(((Num-(I ? DecTab[I-1]:0))>>(16-StartPos))+PosTab[StartPos]); + return(((Num-(I != 0 ? DecTab[I-1]:0))>>(int)(16-StartPos))+PosTab[StartPos]); } -#endif } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs index 40c5f055..a42b55d5 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs @@ -10,19 +10,15 @@ using size_t = System.UInt64; using int64 = System.Int64; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; -//using static SharpCompress.Compressors.Rar.UnpackV2017.Unpack.Unpack20Local; +using static SharpCompress.Compressors.Rar.UnpackV2017.Unpack.Unpack20Local; namespace SharpCompress.Compressors.Rar.UnpackV2017 { internal partial class Unpack { -#if !RarV2017_RAR5ONLY +#if true || !RarV2017_RAR5ONLY void CopyString20(uint Length,uint Distance) { LastDist=OldDist[OldDistPtr++ & 3]=Distance; @@ -33,12 +29,12 @@ void CopyString20(uint Length,uint Distance) internal static class Unpack20Local { - public static byte[] LDecode={0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224}; - public static byte[] LBits= {0,0,0,0,0,0,0,0,1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5}; - public static uint[] DDecode={0,1,2,3,4,6,8,12,16,24,32,48,64,96,128,192,256,384,512,768,1024,1536,2048,3072,4096,6144,8192,12288,16384,24576,32768U,49152U,65536,98304,131072,196608,262144,327680,393216,458752,524288,589824,655360,720896,786432,851968,917504,983040}; - public static byte[] DBits= {0,0,0,0,1,1,2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16}; - public static byte[] SDDecode={0,4,8,16,32,64,128,192}; - public static byte[] SDBits= {2,2,3, 4, 5, 6, 6, 6}; + public static readonly byte[] LDecode={0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224}; + public static readonly byte[] LBits= {0,0,0,0,0,0,0,0,1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5}; + public static readonly uint[] DDecode={0,1,2,3,4,6,8,12,16,24,32,48,64,96,128,192,256,384,512,768,1024,1536,2048,3072,4096,6144,8192,12288,16384,24576,32768U,49152U,65536,98304,131072,196608,262144,327680,393216,458752,524288,589824,655360,720896,786432,851968,917504,983040}; + public static readonly byte[] DBits= {0,0,0,0,1,1,2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16}; + public static readonly byte[] SDDecode={0,4,8,16,32,64,128,192}; + public static readonly byte[] SDBits= {2,2,3, 4, 5, 6, 6, 6}; } void Unpack20(bool Solid) { @@ -95,10 +91,10 @@ void Unpack20(bool Solid) } if (Number>269) { - uint Length=LDecode[Number-=270]+3; + uint Length=(uint)(LDecode[Number-=270]+3); if ((Bits=LBits[Number])>0) { - Length+=Inp.getbits()>>(16-Bits); + Length+=Inp.getbits()>>(int)(16-Bits); Inp.addbits(Bits); } @@ -106,7 +102,7 @@ void Unpack20(bool Solid) uint Distance=DDecode[DistNumber]+1; if ((Bits=DBits[DistNumber])>0) { - Distance+=Inp.getbits()>>(16-Bits); + Distance+=Inp.getbits()>>(int)(16-Bits); Inp.addbits(Bits); } @@ -135,10 +131,10 @@ void Unpack20(bool Solid) { uint Distance=OldDist[(OldDistPtr-(Number-256)) & 3]; uint LengthNumber=DecodeNumber(Inp,BlockTables.RD); - uint Length=LDecode[LengthNumber]+2; + uint Length=(uint)(LDecode[LengthNumber]+2); if ((Bits=LBits[LengthNumber])>0) { - Length+=Inp.getbits()>>(16-Bits); + Length+=Inp.getbits()>>(int)(16-Bits); Inp.addbits(Bits); } if (Distance>=0x101) @@ -156,10 +152,10 @@ void Unpack20(bool Solid) } if (Number<270) { - uint Distance=SDDecode[Number-=261]+1; + uint Distance=(uint)(SDDecode[Number-=261]+1); if ((Bits=SDBits[Number])>0) { - Distance+=Inp.getbits()>>(16-Bits); + Distance+=Inp.getbits()>>(int)(16-Bits); Inp.addbits(Bits); } CopyString20(2,Distance); @@ -177,20 +173,20 @@ void UnpWriteBuf20() UnpSomeRead=true; if (UnpPtrUnpWrite(&Window[WrPtr],-(int)WrPtr & MaxWinMask); - UnpIO->UnpWrite(Window,UnpPtr); + UnpIO_UnpWrite(Window, WrPtr,(uint)(-(int)this.WrPtr & this.MaxWinMask)); + UnpIO_UnpWrite(Window,0,UnpPtr); UnpAllBuf=true; } else - UnpIO->UnpWrite(&Window[WrPtr],UnpPtr-WrPtr); + UnpIO_UnpWrite(Window,WrPtr,UnpPtr-WrPtr); WrPtr=UnpPtr; } bool ReadTables20() { - byte BitLength[BC20]; - byte Table[MC20*4]; + byte[] BitLength = new byte[BC20]; + byte[] Table = new byte[MC20*4]; if (Inp.InAddr>ReadTop-25) if (!UnpReadBuf()) return false; @@ -218,7 +214,7 @@ bool ReadTables20() BitLength[I]=(byte)(Inp.getbits() >> 12); Inp.addbits(4); } - MakeDecodeTables(BitLength,BlockTables.BD,BC20); + MakeDecodeTables(BitLength,0,BlockTables.BD,BC20); for (uint I=0;IReadTop-5) @@ -227,7 +223,7 @@ bool ReadTables20() uint Number=DecodeNumber(Inp,BlockTables.BD); if (Number<16) { - Table[I]=(Number+UnpOldTable20[I]) & 0xf; + Table[I]=(byte)((Number+this.UnpOldTable20[I]) & 0xf); I++; } else @@ -266,14 +262,15 @@ bool ReadTables20() return true; if (UnpAudioBlock) for (uint I=0;I>3) & 0xFF; - uint Ch=PCh-Delta; + uint Ch=(uint)(PCh-Delta); int D=(sbyte)Delta; // Left shift of negative value is undefined behavior in C++, // so we cast it to unsigned to follow the standard. - D=(uint)D<<3; + D=(int)((uint)D<<3); - V.Dif[0]+=Math.Abs(D); - V.Dif[1]+=Math.Abs(D-V.D1); - V.Dif[2]+=Math.Abs(D+V.D1); - V.Dif[3]+=Math.Abs(D-V.D2); - V.Dif[4]+=Math.Abs(D+V.D2); - V.Dif[5]+=Math.Abs(D-V.D3); - V.Dif[6]+=Math.Abs(D+V.D3); - V.Dif[7]+=Math.Abs(D-V.D4); - V.Dif[8]+=Math.Abs(D+V.D4); - V.Dif[9]+=Math.Abs(D-UnpChannelDelta); - V.Dif[10]+=Math.Abs(D+UnpChannelDelta); + V.Dif[0]+=(uint)Math.Abs(D); + V.Dif[1]+=(uint)Math.Abs(D-V.D1); + V.Dif[2]+=(uint)Math.Abs(D+V.D1); + V.Dif[3]+=(uint)Math.Abs(D-V.D2); + V.Dif[4]+=(uint)Math.Abs(D+V.D2); + V.Dif[5]+=(uint)Math.Abs(D-V.D3); + V.Dif[6]+=(uint)Math.Abs(D+V.D3); + V.Dif[7]+=(uint)Math.Abs(D-V.D4); + V.Dif[8]+=(uint)Math.Abs(D+V.D4); + V.Dif[9]+=(uint)Math.Abs(D-UnpChannelDelta); + V.Dif[10]+=(uint)Math.Abs(D+UnpChannelDelta); UnpChannelDelta=V.LastDelta=(sbyte)(Ch-V.LastChar); - V.LastChar=Ch; + V.LastChar=(int)Ch; if ((V.ByteCount & 0x1F)==0) { @@ -405,7 +401,6 @@ byte DecodeAudio(int Delta) } return (byte)Ch; } -#endif } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs index ac1be05f..de7319ff 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs @@ -35,13 +35,13 @@ int SafePPMDecodeChar() } internal static class Unpack30Local { - public static byte[] LDecode={0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224}; - public static byte[] LBits= {0,0,0,0,0,0,0,0,1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5}; - public static int[] DDecode = new int[DC]; - public static byte[] DBits = new byte[DC]; - public static int[] DBitLengthCounts= {4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,14,0,12}; - public static byte[] SDDecode={0,4,8,16,32,64,128,192}; - public static byte[] SDBits= {2,2,3, 4, 5, 6, 6, 6}; + public static readonly byte[] LDecode={0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224}; + public static readonly byte[] LBits= {0,0,0,0,0,0,0,0,1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5}; + public static readonly int[] DDecode = new int[DC]; + public static readonly byte[] DBits = new byte[DC]; + public static readonly int[] DBitLengthCounts= {4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,14,0,12}; + public static readonly byte[] SDDecode={0,4,8,16,32,64,128,192}; + public static readonly byte[] SDBits= {2,2,3, 4, 5, 6, 6, 6}; } void Unpack29(bool Solid) { From 554153e6a0de1ee7f7ec385ae44c50589ddaac66 Mon Sep 17 00:00:00 2001 From: coderb Date: Wed, 20 Dec 2017 19:10:06 -0500 Subject: [PATCH 52/61] rar5: port old algos --- .../Rar/UnpackV2017/Unpack.unpack15_cpp.cs | 2 -- .../Rar/UnpackV2017/Unpack.unpack20_cpp.cs | 2 -- .../Rar/UnpackV2017/Unpack.unpack30_cpp.cs | 23 ++++++++++--------- .../Rar/UnpackV2017/Unpack.unpack_cpp.cs | 2 +- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs index 87830e02..d76ee10c 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs @@ -4,7 +4,6 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 { internal partial class Unpack { -#if true || !RarV2017_RAR5ONLY const int STARTL1 =2; static uint[] DecL1={0x8000,0xa000,0xc000,0xd000,0xe000,0xea00, @@ -434,7 +433,6 @@ void GetFlagsBuf() ChSetC[NewFlagsPlace]=(ushort)Flags; } -#endif void UnpInitData15(bool Solid) { if (!Solid) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs index a42b55d5..57ecdc02 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs @@ -18,7 +18,6 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 internal partial class Unpack { -#if true || !RarV2017_RAR5ONLY void CopyString20(uint Length,uint Distance) { LastDist=OldDist[OldDistPtr++ & 3]=Distance; @@ -288,7 +287,6 @@ void ReadLastTables() ReadTables20(); } -#endif void UnpInitData20(bool Solid) { diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs index de7319ff..8ec81763 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs @@ -12,14 +12,14 @@ using int64 = System.Int64; using System; using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; using static SharpCompress.Compressors.Rar.UnpackV2017.UnpackGlobal; -using static SharpCompress.Compressors.Rar.UnpackV2017.Unpack.Unpack30Local; +//using static SharpCompress.Compressors.Rar.UnpackV2017.Unpack.Unpack30Local; namespace SharpCompress.Compressors.Rar.UnpackV2017 { internal partial class Unpack { -#if true || !RarV2017_RAR5ONLY +#if !RarV2017_RAR5ONLY // We use it instead of direct PPM.DecodeChar call to be sure that // we reset PPM structures in case of corrupt data. It is important, // because these structures can be invalid after PPM.DecodeChar returned -1. @@ -251,7 +251,7 @@ void Unpack29(bool Solid) int Length=LDecode[LengthNumber]+2; if ((Bits=LBits[LengthNumber])>0) { - Length+=Inp.getbits()>>(int)(16-Bits); + Length+=(int)(Inp.getbits()>>(int)(16-Bits)); Inp.addbits(Bits); } LastLength=(uint)Length; @@ -345,7 +345,7 @@ bool ReadVMCode() bool ReadVMCodePPM() { - uint FirstByte=SafePPMDecodeChar(); + uint FirstByte=(uint)SafePPMDecodeChar(); if ((int)FirstByte==-1) return false; uint Length=(FirstByte & 7)+1; @@ -429,7 +429,7 @@ bool AddVMCode(uint FirstByte,byte[] Code,int CodeSize) } else // Filter was used in the past. { - Filter=Filters30[FiltPos]; + Filter=Filters30[(int)FiltPos]; StackFilter.ParentFilter=FiltPos; } @@ -452,8 +452,8 @@ bool AddVMCode(uint FirstByte,byte[] Code,int CodeSize) PrgStack.Add(1); EmptyCount=1; } - size_t StackPos=PrgStack.Count-EmptyCount; - PrgStack[StackPos]=StackFilter; + size_t StackPos=(uint)(this.PrgStack.Count-EmptyCount); + PrgStack[(int)StackPos]=StackFilter; uint BlockStart=RarVM.ReadData(VMCodeInp); if ((FirstByte & 0x40)!=0) @@ -464,7 +464,7 @@ bool AddVMCode(uint FirstByte,byte[] Code,int CodeSize) StackFilter.BlockLength=RarVM.ReadData(VMCodeInp); // Store the last data block length for current filter. - OldFilterLengths[FiltPos]=StackFilter.BlockLength; + OldFilterLengths[(int)FiltPos]=(int)StackFilter.BlockLength; } else { @@ -472,7 +472,7 @@ bool AddVMCode(uint FirstByte,byte[] Code,int CodeSize) // for same filter. It is possible for corrupt data to access a new // and not filled yet item of OldFilterLengths array here. This is why // we set new OldFilterLengths items to zero above. - StackFilter.BlockLength=FiltPos0) - memmove(Inp.InBuf,Inp.InBuf+Inp.InAddr,DataSize); + //x memmove(Inp.InBuf,Inp.InBuf+Inp.InAddr,DataSize); + Array.Copy(Inp.InBuf,Inp.InAddr,Inp.InBuf,0,DataSize); Inp.InAddr=0; ReadTop=DataSize; } @@ -544,7 +545,7 @@ void UnpWriteBuf30() { uint WrittenBorder=(uint)WrPtr; uint WriteSize=(uint)((UnpPtr-WrittenBorder)&MaxWinMask); - for (size_t I=0;I Date: Mon, 23 Apr 2018 09:39:50 +0100 Subject: [PATCH 53/61] Try to use both for Rarv5 support --- src/SharpCompress/Archives/Rar/RarArchive.cs | 14 +++---------- .../Archives/Rar/RarArchiveEntry.cs | 10 ++++++++-- .../Common/Rar/Headers/MarkHeader.cs | 12 ----------- src/SharpCompress/Common/Rar/RarEntry.cs | 5 +++++ .../Compressors/Rar/UnpackV1/UnpackInline.cs | 7 +------ .../Rar/UnpackV2017/Unpack.unpack30_cpp.cs | 3 ++- .../Rar/UnpackV2017/Unpack.unpack_cpp.cs | 4 ++-- .../Compressors/Rar/UnpackV2017/unpack_hpp.cs | 12 +++++------ src/SharpCompress/Readers/Rar/RarReader.cs | 20 ++++++++++--------- src/SharpCompress/SharpCompress.csproj | 2 +- src/SharpCompress/Utility.cs | 5 ----- 11 files changed, 39 insertions(+), 55 deletions(-) diff --git a/src/SharpCompress/Archives/Rar/RarArchive.cs b/src/SharpCompress/Archives/Rar/RarArchive.cs index 699073cb..fb052c37 100644 --- a/src/SharpCompress/Archives/Rar/RarArchive.cs +++ b/src/SharpCompress/Archives/Rar/RarArchive.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -12,7 +13,8 @@ namespace SharpCompress.Archives.Rar { public class RarArchive : AbstractArchive { - internal IRarUnpack Unpack { get; } + internal Lazy UnpackV2017 { get; } = new Lazy(() => new SharpCompress.Compressors.Rar.UnpackV2017.Unpack()); + internal Lazy UnpackV1 { get; } = new Lazy(() => new SharpCompress.Compressors.Rar.UnpackV1.Unpack()); #if !NO_FILE @@ -24,11 +26,6 @@ namespace SharpCompress.Archives.Rar internal RarArchive(FileInfo fileInfo, ReaderOptions options) : base(ArchiveType.Rar, fileInfo, options) { -#if !RarV2017_USELEGACY - Unpack = new SharpCompress.Compressors.Rar.UnpackV2017.Unpack(); -#else - Unpack = new SharpCompress.Compressors.Rar.UnpackV1.Unpack(); -#endif } protected override IEnumerable LoadVolumes(FileInfo file) @@ -45,11 +42,6 @@ namespace SharpCompress.Archives.Rar internal RarArchive(IEnumerable streams, ReaderOptions options) : base(ArchiveType.Rar, streams, options) { -#if !RarV2017_USELEGACY - Unpack = new SharpCompress.Compressors.Rar.UnpackV2017.Unpack(); -#else - Unpack = new SharpCompress.Compressors.Rar.UnpackV1.Unpack(); -#endif } protected override IEnumerable LoadEntries(IEnumerable volumes) diff --git a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs index 064dcf7a..60ece4ee 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs @@ -51,7 +51,7 @@ namespace SharpCompress.Archives.Rar get { CheckIncomplete(); - return parts.Aggregate(0L, (total, fp) => { return total + fp.FileHeader.CompressedSize; }); + return parts.Aggregate(0L, (total, fp) => total + fp.FileHeader.CompressedSize); } } @@ -61,7 +61,13 @@ namespace SharpCompress.Archives.Rar { throw new InvalidOperationException("Use ExtractAllEntries to extract SOLID archives."); } - return new RarStream(archive.Unpack, FileHeader, new MultiVolumeReadOnlyStream(Parts.Cast(), archive)); + + if (IsRarV3) + { + return new RarStream(archive.UnpackV1.Value, FileHeader, new MultiVolumeReadOnlyStream(Parts.Cast(), archive)); + } + + return new RarStream(archive.UnpackV2017.Value, FileHeader, new MultiVolumeReadOnlyStream(Parts.Cast(), archive)); } public bool IsComplete diff --git a/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs b/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs index 4e972913..c7b94149 100644 --- a/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs @@ -67,10 +67,6 @@ namespace SharpCompress.Common.Rar.Headers { return new MarkHeader(false); // Rar4 } - else - { - continue; - } } else if (b == 0x45) { @@ -80,19 +76,11 @@ namespace SharpCompress.Common.Rar.Headers if (b != 0x5e) continue; throw new InvalidFormatException("Rar format version pre-4 is unsupported."); } - else - { - continue; - } } else { b = GetByte(stream); start++; - continue; } - - // unreachable - throw new InvalidOperationException(); } } catch (Exception e) diff --git a/src/SharpCompress/Common/Rar/RarEntry.cs b/src/SharpCompress/Common/Rar/RarEntry.cs index cec1eb44..85be0bb3 100644 --- a/src/SharpCompress/Common/Rar/RarEntry.cs +++ b/src/SharpCompress/Common/Rar/RarEntry.cs @@ -7,6 +7,11 @@ namespace SharpCompress.Common.Rar { internal abstract FileHeader FileHeader { get; } + /// + /// As the V2017 port isn't complete, add this check to use the legacy Rar code. + /// + internal bool IsRarV3 => FileHeader.CompressionAlgorithm == 29 || FileHeader.CompressionAlgorithm == 36; + /// /// The File's 32 bit CRC Hash /// diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackInline.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackInline.cs index e5ca5b1e..35b65eb7 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackInline.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackInline.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - + namespace SharpCompress.Compressors.Rar.UnpackV1 { internal partial class Unpack diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs index 8ec81763..7e77c7d4 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs @@ -13,7 +13,7 @@ using System; using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; using static SharpCompress.Compressors.Rar.UnpackV2017.UnpackGlobal; //using static SharpCompress.Compressors.Rar.UnpackV2017.Unpack.Unpack30Local; - +/* namespace SharpCompress.Compressors.Rar.UnpackV2017 { internal partial class Unpack @@ -797,3 +797,4 @@ void InitFilters30(bool Solid) } } +*/ \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs index 0fa14295..44abffa3 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs @@ -167,7 +167,7 @@ void DoUnpack(uint Method,bool Solid) #if !RarV2017_RAR5ONLY case 29: // rar 3.x compression if (!Fragmented) - Unpack29(Solid); + throw new NotImplementedException(); break; #endif case 50: // RAR 5.0 compression algorithm. @@ -225,7 +225,7 @@ void UnpInitData(bool Solid) #if !RarV2017_SFX_MODULE UnpInitData20(Solid); #endif - UnpInitData30(Solid); + //UnpInitData30(Solid); UnpInitData50(Solid); } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs index afea642c..06520ab5 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs @@ -196,9 +196,9 @@ class UnpackFilter30 // in PrgStack array. Not defined for filters in Filters array. public uint ParentFilter; -#if !RarV2017_RAR5ONLY +/*#if !RarV2017_RAR5ONLY public VM_PreparedProgram Prg; -#endif +#endif*/ }; @@ -374,9 +374,9 @@ internal partial class Unpack int PrevLowDist,LowDistRepCount; -#if !RarV2017_RAR5ONLY +/*#if !RarV2017_RAR5ONLY ModelPPM PPM; -#endif +#endif*/ int PPMEscChar; readonly byte [] UnpOldTable = new byte[HUFF_TABLE_SIZE30]; @@ -390,9 +390,9 @@ internal partial class Unpack bool TablesRead2,TablesRead3,TablesRead5; // Virtual machine to execute filters code. -#if !RarV2017_RAR5ONLY +/*#if !RarV2017_RAR5ONLY RarVM VM; -#endif +#endif*/ // Buffer to read VM filters code. We moved it here from AddVMCode // function to reduce time spent in BitInput constructor. diff --git a/src/SharpCompress/Readers/Rar/RarReader.cs b/src/SharpCompress/Readers/Rar/RarReader.cs index 71fe260c..05fe0daa 100644 --- a/src/SharpCompress/Readers/Rar/RarReader.cs +++ b/src/SharpCompress/Readers/Rar/RarReader.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using SharpCompress.Common; @@ -13,16 +14,12 @@ namespace SharpCompress.Readers.Rar public abstract class RarReader : AbstractReader { private RarVolume volume; - private readonly IRarUnpack pack; + internal Lazy UnpackV2017 { get; } = new Lazy(() => new SharpCompress.Compressors.Rar.UnpackV2017.Unpack()); + internal Lazy UnpackV1 { get; } = new Lazy(() => new SharpCompress.Compressors.Rar.UnpackV1.Unpack()); internal RarReader(ReaderOptions options) : base(options, ArchiveType.Rar) { -#if !RarV2017_USELEGACY - this.pack = new SharpCompress.Compressors.Rar.UnpackV2017.Unpack(); -#else - this.pack = new SharpCompress.Compressors.Rar.UnpackV1.Unpack(); -#endif } internal abstract void ValidateArchive(RarVolume archive); @@ -68,9 +65,14 @@ namespace SharpCompress.Readers.Rar return Entry.Parts; } - protected override EntryStream GetEntryStream() { + protected override EntryStream GetEntryStream() + { var stream = new MultiVolumeReadOnlyStream(CreateFilePartEnumerableForCurrentEntry().Cast(), this); - return CreateEntryStream(new RarCrcStream(pack, Entry.FileHeader, stream)); + if (Entry.IsRarV3) + { + return CreateEntryStream(new RarCrcStream(UnpackV1.Value, Entry.FileHeader, stream)); + } + return CreateEntryStream(new RarCrcStream(UnpackV2017.Value, Entry.FileHeader, stream)); } } } diff --git a/src/SharpCompress/SharpCompress.csproj b/src/SharpCompress/SharpCompress.csproj index 5c1d0815..a20a92a9 100644 --- a/src/SharpCompress/SharpCompress.csproj +++ b/src/SharpCompress/SharpCompress.csproj @@ -6,7 +6,7 @@ 0.20.0 0.20.0 Adam Hathcock - net45;net35;netstandard1.0;netstandard1.3;netstandard2.0 + netstandard2.0 true true SharpCompress diff --git a/src/SharpCompress/Utility.cs b/src/SharpCompress/Utility.cs index 320a59c6..bca28a9d 100644 --- a/src/SharpCompress/Utility.cs +++ b/src/SharpCompress/Utility.cs @@ -427,10 +427,5 @@ namespace SharpCompress } return true; } - - public static void CopyTo(this byte[] array, byte[] destination, int index) - { - Array.Copy(array, 0, destination, index, array.Length); - } } } \ No newline at end of file From d38276e8cfec7adc82fd980710a5f4a100407016 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Mon, 23 Apr 2018 10:29:46 +0100 Subject: [PATCH 54/61] Fix solid and some other tests --- .gitignore | 2 ++ .../Archives/Rar/RarArchiveEntry.cs | 4 ++-- .../Common/Rar/Headers/ArchiveHeader.cs | 3 ++- .../Common/Rar/Headers/FileHeader.cs | 8 -------- src/SharpCompress/Common/Rar/Headers/Flags.cs | 2 +- .../Common/Rar/Headers/RarHeaderFactory.cs | 5 +++-- src/SharpCompress/Compressors/Rar/IRarUnpack.cs | 4 ++-- .../Compressors/Rar/RarCrcStream.cs | 15 +++++++++------ src/SharpCompress/Compressors/Rar/RarStream.cs | 8 +++++--- .../Compressors/Rar/UnpackV1/Unpack.cs | 17 ++++++++--------- .../Compressors/Rar/UnpackV2017/Unpack.cs | 10 +++++----- src/SharpCompress/Readers/AbstractReader.cs | 7 ++----- src/SharpCompress/Readers/Rar/RarReader.cs | 4 ++-- tests/SharpCompress.Test/Rar/RarArchiveTests.cs | 8 ++++---- tests/SharpCompress.Test/Rar/RarReaderTests.cs | 10 +++++----- 15 files changed, 52 insertions(+), 55 deletions(-) diff --git a/.gitignore b/.gitignore index 2e0b14fa..cc539531 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ tests/TestArchives/Scratch tools .vscode .idea/ + +.DS_Store diff --git a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs index 60ece4ee..d4197b3f 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs @@ -64,10 +64,10 @@ namespace SharpCompress.Archives.Rar if (IsRarV3) { - return new RarStream(archive.UnpackV1.Value, FileHeader, new MultiVolumeReadOnlyStream(Parts.Cast(), archive)); + return new RarStream(archive.UnpackV1.Value, archive.IsSolid, FileHeader, new MultiVolumeReadOnlyStream(Parts.Cast(), archive)); } - return new RarStream(archive.UnpackV2017.Value, FileHeader, new MultiVolumeReadOnlyStream(Parts.Cast(), archive)); + return new RarStream(archive.UnpackV2017.Value, archive.IsSolid, FileHeader, new MultiVolumeReadOnlyStream(Parts.Cast(), archive)); } public bool IsComplete diff --git a/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs b/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs index 83eda7f1..bc4955d0 100644 --- a/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs @@ -73,7 +73,8 @@ namespace SharpCompress.Common.Rar.Headers public bool IsVolume => HasFlag(IsRar5 ? ArchiveFlagsV5.Volume : ArchiveFlagsV4.Volume); - public bool IsFirstVolume => IsRar5 ? VolumeNumber == 1 : HasFlag(ArchiveFlagsV4.FirstVolume); + // RAR5: Volume number field is present. True for all volumes except first. + public bool IsFirstVolume => IsRar5 ? VolumeNumber == null : HasFlag(ArchiveFlagsV4.FirstVolume); public bool IsSolid => HasFlag(IsRar5 ? ArchiveFlagsV5.Solid : ArchiveFlagsV4.Solid); } diff --git a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs index 48d2f3e0..55a13322 100644 --- a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs @@ -64,11 +64,6 @@ namespace SharpCompress.Common.Rar.Headers // them. CompressionAlgorithm = (byte)((us & 0x3f) + 50); - // 7th bit (0x0040) defines the solid flag. If it is set, RAR continues to use the compression dictionary left after processing preceding files. - // It can be set only for file headers and is never set for service headers. - IsSolid = (us & 0x40) == 0x40; - if (IsSolid != HasHeaderFlag(HeaderFlagsV5.Solid_TESTME)) throw new InvalidFormatException("rar solid flag base header != file header"); - // Bits 8 - 10 (0x0380 mask) define the compression method. Currently only values 0 - 5 are used. 0 means no compression. CompressionMethod = (byte)((us >> 7) & 0x7); @@ -210,7 +205,6 @@ namespace SharpCompress.Common.Rar.Headers private void ReadFromReaderV4(MarkingBinaryReader reader) { Flags = HeaderFlags; - IsSolid = HasFlag(FileFlagsV4.Solid); WindowSize = IsDirectory ? 0U : ((size_t)0x10000) << ((Flags & FileFlagsV4.WindowMask) >> 5); uint lowUncompressedSize = reader.ReadUInt32(); @@ -415,8 +409,6 @@ namespace SharpCompress.Common.Rar.Headers //case 50: // RAR 5.0 compression algorithm. internal byte CompressionAlgorithm { get; private set; } - public bool IsSolid { get; private set; } - // unused for UnpackV1 implementation (limitation) internal size_t WindowSize { get; private set; } diff --git a/src/SharpCompress/Common/Rar/Headers/Flags.cs b/src/SharpCompress/Common/Rar/Headers/Flags.cs index 746b9018..9c5060e9 100644 --- a/src/SharpCompress/Common/Rar/Headers/Flags.cs +++ b/src/SharpCompress/Common/Rar/Headers/Flags.cs @@ -47,7 +47,7 @@ namespace SharpCompress.Common.Rar.Headers public const ushort Keep = 0x0004; // block must be kept during an update public const ushort SplitBefore = 0x0008; public const ushort SplitAfter = 0x0010; - public const ushort Solid_TESTME = 0x0020; // ??? Block depends on preceding file block. + public const ushort Child = 0x0020; // ??? Block depends on preceding file block. public const ushort PreserveChild = 0x0040; // ???? Preserve a child block if host block is modified } diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs index 05660583..cb670368 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs @@ -70,8 +70,9 @@ namespace SharpCompress.Common.Rar.Headers case HeaderCodeV.Rar4ArchiveHeader: { var ah = new ArchiveHeader(header, reader); - if (ah.IsEncrypted == true) { -//!!! rar5 we don't know yet + if (ah.IsEncrypted == true) + { + //!!! rar5 we don't know yet IsEncrypted = true; } return ah; diff --git a/src/SharpCompress/Compressors/Rar/IRarUnpack.cs b/src/SharpCompress/Compressors/Rar/IRarUnpack.cs index ecd70a47..887c590e 100644 --- a/src/SharpCompress/Compressors/Rar/IRarUnpack.cs +++ b/src/SharpCompress/Compressors/Rar/IRarUnpack.cs @@ -5,8 +5,8 @@ namespace SharpCompress.Compressors.Rar { interface IRarUnpack { - void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream); - void DoUnpack(); + void DoUnpack(bool isSolid, FileHeader fileHeader, Stream readStream, Stream writeStream); + void DoUnpack(bool isSolid); // eg u/i pause/resume button bool Suspended { get; set; } diff --git a/src/SharpCompress/Compressors/Rar/RarCrcStream.cs b/src/SharpCompress/Compressors/Rar/RarCrcStream.cs index 85ed3e34..2bf98579 100644 --- a/src/SharpCompress/Compressors/Rar/RarCrcStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarCrcStream.cs @@ -2,12 +2,15 @@ using System.IO; using SharpCompress.Common; using SharpCompress.Common.Rar.Headers; -namespace SharpCompress.Compressors.Rar { - internal class RarCrcStream : RarStream { +namespace SharpCompress.Compressors.Rar +{ + internal class RarCrcStream : RarStream + { private readonly MultiVolumeReadOnlyStream readStream; private uint currentCrc; - public RarCrcStream(IRarUnpack unpack, FileHeader fileHeader, MultiVolumeReadOnlyStream readStream) : base(unpack, fileHeader, readStream) + public RarCrcStream(IRarUnpack unpack, bool isSolid, FileHeader fileHeader, MultiVolumeReadOnlyStream readStream) + : base(unpack, isSolid, fileHeader, readStream) { this.readStream = readStream; ResetCrc(); @@ -23,19 +26,19 @@ namespace SharpCompress.Compressors.Rar { currentCrc = 0xffffffff; } - public override int Read(byte[] buffer, int offset, int count) { var result = base.Read(buffer, offset, count); - if (result != 0) + if (result != 0) { currentCrc = RarCRC.CheckCrc(currentCrc, buffer, offset, result); - } + } else if (GetCrc() != this.readStream.CurrentCrc && count != 0) { // NOTE: we use the last FileHeader in a multipart volume to check CRC throw new InvalidFormatException("file crc mismatch"); } + return result; } } diff --git a/src/SharpCompress/Compressors/Rar/RarStream.cs b/src/SharpCompress/Compressors/Rar/RarStream.cs index 08240859..6da56697 100644 --- a/src/SharpCompress/Compressors/Rar/RarStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarStream.cs @@ -9,6 +9,7 @@ namespace SharpCompress.Compressors.Rar private readonly IRarUnpack unpack; private readonly FileHeader fileHeader; private readonly Stream readStream; + private readonly bool isSolid; private bool fetch; @@ -22,13 +23,14 @@ namespace SharpCompress.Compressors.Rar private int outTotal; private bool isDisposed; - public RarStream(IRarUnpack unpack, FileHeader fileHeader, Stream readStream) + public RarStream(IRarUnpack unpack, bool isSolid, FileHeader fileHeader, Stream readStream) { + this.isSolid = isSolid; this.unpack = unpack; this.fileHeader = fileHeader; this.readStream = readStream; this.fetch = true; - unpack.DoUnpack(fileHeader, readStream, this); + unpack.DoUnpack(isSolid, fileHeader, readStream, this); this.fetch = false; } @@ -74,7 +76,7 @@ namespace SharpCompress.Compressors.Rar outOffset = offset; outCount = count; fetch = true; - unpack.DoUnpack(); + unpack.DoUnpack(isSolid); fetch = false; } return outTotal; diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs index b576361a..dd3b52dc 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs @@ -103,46 +103,45 @@ namespace SharpCompress.Compressors.Rar.UnpackV1 UnpInitData(false); } - public void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream) + public void DoUnpack(bool isSolid, FileHeader fileHeader, Stream readStream, Stream writeStream) { this.destUnpSize = fileHeader.UncompressedSize; this.fileHeader = fileHeader; this.readStream = readStream; this.writeStream = writeStream; - if (!fileHeader.IsSolid) + if (!isSolid) { Init(null); } this.suspended = false; - DoUnpack(); + DoUnpack(isSolid); } - public void DoUnpack() + public void DoUnpack(bool isSolid) { if (fileHeader.CompressionMethod == 0) { UnstoreFile(); return; } - var solid = fileHeader.IsSolid; switch (fileHeader.CompressionAlgorithm) { case 15: // rar 1.5 compression - unpack15(solid); + unpack15(isSolid); break; case 20: // rar 2.x compression case 26: // files larger than 2GB - unpack20(solid); + unpack20(isSolid); break; case 29: // rar 3.x compression case 36: // alternative hash - Unpack29(solid); + Unpack29(isSolid); break; case 50: // rar 5.x compression - Unpack5(solid); + Unpack5(isSolid); break; default: diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs index e40ec2ec..2d52da5b 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs @@ -37,7 +37,7 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 this.writeStream.Write(buf, checked((int)offset), checked((int)count)); } - public void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream) + public void DoUnpack(bool isSolid, FileHeader fileHeader, Stream readStream, Stream writeStream) { // as of 12/2017 .NET limits array indexing to using a signed integer // MaxWinSize causes unpack to use a fragmented window when the file @@ -51,19 +51,19 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 this.readStream = readStream; this.writeStream = writeStream; if (!fileHeader.IsStored) { - Init(fileHeader.WindowSize, fileHeader.IsSolid); + Init(fileHeader.WindowSize, isSolid); } Suspended = false; - DoUnpack(); + DoUnpack(isSolid); } - public void DoUnpack() + public void DoUnpack(bool isSolid) { if (this.fileHeader.IsStored) { UnstoreFile(); } else { - DoUnpack(this.fileHeader.CompressionAlgorithm, this.fileHeader.IsSolid); + DoUnpack(this.fileHeader.CompressionAlgorithm, isSolid); } } diff --git a/src/SharpCompress/Readers/AbstractReader.cs b/src/SharpCompress/Readers/AbstractReader.cs index a5cc157e..a488e628 100644 --- a/src/SharpCompress/Readers/AbstractReader.cs +++ b/src/SharpCompress/Readers/AbstractReader.cs @@ -46,11 +46,8 @@ namespace SharpCompress.Readers public void Dispose() { - if (entriesForCurrentReadStream != null) - { - entriesForCurrentReadStream.Dispose(); - } - Volume.Dispose(); + entriesForCurrentReadStream?.Dispose(); + Volume?.Dispose(); } #endregion diff --git a/src/SharpCompress/Readers/Rar/RarReader.cs b/src/SharpCompress/Readers/Rar/RarReader.cs index 05fe0daa..81f99d9b 100644 --- a/src/SharpCompress/Readers/Rar/RarReader.cs +++ b/src/SharpCompress/Readers/Rar/RarReader.cs @@ -70,9 +70,9 @@ namespace SharpCompress.Readers.Rar var stream = new MultiVolumeReadOnlyStream(CreateFilePartEnumerableForCurrentEntry().Cast(), this); if (Entry.IsRarV3) { - return CreateEntryStream(new RarCrcStream(UnpackV1.Value, Entry.FileHeader, stream)); + return CreateEntryStream(new RarCrcStream(UnpackV1.Value, volume.IsSolidArchive, Entry.FileHeader, stream)); } - return CreateEntryStream(new RarCrcStream(UnpackV2017.Value, Entry.FileHeader, stream)); + return CreateEntryStream(new RarCrcStream(UnpackV2017.Value, volume.IsSolidArchive, Entry.FileHeader, stream)); } } } diff --git a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs index d320f782..2fab450c 100644 --- a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs +++ b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs @@ -40,11 +40,11 @@ namespace SharpCompress.Test.Rar ReadRarPassword("Rar.Encrypted.rar", "test"); } - [Fact] + /*[Fact] public void Rar5_Encrypted_Archive() { ReadRarPassword("Rar5.Encrypted.rar", "test"); - } + }*/ private void ReadRarPassword(string testArchive, string password) { @@ -78,11 +78,11 @@ namespace SharpCompress.Test.Rar Assert.Throws(() => ArchiveFileReadPassword("Rar.EncryptedParts.part01.rar", "test")); } - [Fact] + /*[Fact] public void Rar5_Multi_Archive_Encrypted() { Assert.Throws(() => ArchiveFileReadPassword("Rar5.EncryptedParts.part01.rar", "test")); - } + }*/ protected void ArchiveFileReadPassword(string archiveName, string password) { diff --git a/tests/SharpCompress.Test/Rar/RarReaderTests.cs b/tests/SharpCompress.Test/Rar/RarReaderTests.cs index bc98dfab..79ee574b 100644 --- a/tests/SharpCompress.Test/Rar/RarReaderTests.cs +++ b/tests/SharpCompress.Test/Rar/RarReaderTests.cs @@ -60,7 +60,7 @@ namespace SharpCompress.Test.Rar "Rar.EncryptedParts.part06.rar"}); } - [Fact] + /*[Fact] public void Rar5_Multi_Reader_Encrypted() { DoRar_Multi_Reader_Encrypted(new string[] { "Rar5.EncryptedParts.part01.rar", @@ -69,7 +69,7 @@ namespace SharpCompress.Test.Rar "Rar5.EncryptedParts.part04.rar", "Rar5.EncryptedParts.part05.rar", "Rar5.EncryptedParts.part06.rar"}); - } + }*/ private void DoRar_Multi_Reader_Encrypted(string[] archives) { @@ -205,11 +205,11 @@ namespace SharpCompress.Test.Rar ReadRar("Rar.Encrypted.rar", "test"); } - [Fact] + /*[Fact] public void Rar5_Encrypted_Reader() { - ReadRar("Rar5.Encrypted.rar", "test"); - } + ReadRar("Rar5.encrypted_filesOnly.rar", "test"); + }*/ private void ReadRar(string testArchive, string password) { From bee7f43880f40b339291c0127aa2743e798dca8e Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Thu, 26 Apr 2018 09:46:01 +0100 Subject: [PATCH 55/61] Expose stream length. Clean up entry stream --- .gitignore | 1 + src/SharpCompress/Common/EntryStream.cs | 33 +++++++++++-------------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 2e0b14fa..ca133c68 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ tests/TestArchives/Scratch tools .vscode .idea/ +.DS_Store diff --git a/src/SharpCompress/Common/EntryStream.cs b/src/SharpCompress/Common/EntryStream.cs index 0120b782..4697ff8c 100644 --- a/src/SharpCompress/Common/EntryStream.cs +++ b/src/SharpCompress/Common/EntryStream.cs @@ -6,15 +6,15 @@ namespace SharpCompress.Common { public class EntryStream : Stream { - public IReader Reader { get; } - private readonly Stream stream; - private bool completed; - private bool isDisposed; + private readonly IReader _reader; + private readonly Stream _stream; + private bool _completed; + private bool _isDisposed; internal EntryStream(IReader reader, Stream stream) { - Reader = reader; - this.stream = stream; + _reader = reader; + _stream = stream; } /// @@ -22,26 +22,23 @@ namespace SharpCompress.Common /// public void SkipEntry() { - var buffer = new byte[4096]; - while (Read(buffer, 0, buffer.Length) > 0) - { - } - completed = true; + this.Skip(); + _completed = true; } protected override void Dispose(bool disposing) { - if (!(completed || Reader.Cancelled)) + if (!(_completed || _reader.Cancelled)) { SkipEntry(); } - if (isDisposed) + if (_isDisposed) { return; } - isDisposed = true; + _isDisposed = true; base.Dispose(disposing); - stream.Dispose(); + _stream.Dispose(); } public override bool CanRead => true; @@ -55,16 +52,16 @@ namespace SharpCompress.Common throw new NotSupportedException(); } - public override long Length => throw new NotSupportedException(); + public override long Length => _stream.Length; public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } public override int Read(byte[] buffer, int offset, int count) { - int read = stream.Read(buffer, offset, count); + int read = _stream.Read(buffer, offset, count); if (read <= 0) { - completed = true; + _completed = true; } return read; } From 9d63dcb8d63aae399bd816846886edf7446dce26 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Sat, 28 Apr 2018 18:14:47 +0100 Subject: [PATCH 56/61] Uncommit some tests --- tests/SharpCompress.Test/Rar/RarArchiveTests.cs | 12 +++--------- tests/SharpCompress.Test/Rar/RarReaderTests.cs | 15 ++------------- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs index 2fab450c..140ab367 100644 --- a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs +++ b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs @@ -40,11 +40,11 @@ namespace SharpCompress.Test.Rar ReadRarPassword("Rar.Encrypted.rar", "test"); } - /*[Fact] + [Fact] public void Rar5_Encrypted_Archive() { - ReadRarPassword("Rar5.Encrypted.rar", "test"); - }*/ + ReadRarPassword("Rar5.encrypted_filesAndHeader.rar", "test"); + } private void ReadRarPassword(string testArchive, string password) { @@ -78,12 +78,6 @@ namespace SharpCompress.Test.Rar Assert.Throws(() => ArchiveFileReadPassword("Rar.EncryptedParts.part01.rar", "test")); } - /*[Fact] - public void Rar5_Multi_Archive_Encrypted() - { - Assert.Throws(() => ArchiveFileReadPassword("Rar5.EncryptedParts.part01.rar", "test")); - }*/ - protected void ArchiveFileReadPassword(string archiveName, string password) { ResetScratch(); diff --git a/tests/SharpCompress.Test/Rar/RarReaderTests.cs b/tests/SharpCompress.Test/Rar/RarReaderTests.cs index 79ee574b..68af67a9 100644 --- a/tests/SharpCompress.Test/Rar/RarReaderTests.cs +++ b/tests/SharpCompress.Test/Rar/RarReaderTests.cs @@ -60,17 +60,6 @@ namespace SharpCompress.Test.Rar "Rar.EncryptedParts.part06.rar"}); } - /*[Fact] - public void Rar5_Multi_Reader_Encrypted() { - DoRar_Multi_Reader_Encrypted(new string[] { - "Rar5.EncryptedParts.part01.rar", - "Rar5.EncryptedParts.part02.rar", - "Rar5.EncryptedParts.part03.rar", - "Rar5.EncryptedParts.part04.rar", - "Rar5.EncryptedParts.part05.rar", - "Rar5.EncryptedParts.part06.rar"}); - }*/ - private void DoRar_Multi_Reader_Encrypted(string[] archives) { Assert.Throws(() => @@ -205,11 +194,11 @@ namespace SharpCompress.Test.Rar ReadRar("Rar.Encrypted.rar", "test"); } - /*[Fact] + [Fact] public void Rar5_Encrypted_Reader() { ReadRar("Rar5.encrypted_filesOnly.rar", "test"); - }*/ + } private void ReadRar(string testArchive, string password) { From 15534f466af2389844b8ae545f4d2116197bbf4f Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Sat, 28 Apr 2018 18:20:40 +0100 Subject: [PATCH 57/61] Add basic rar5 crypt header --- .../Common/Rar/Headers/CryptHeader.cs | 50 +++++++++++++++++++ src/SharpCompress/Common/Rar/Headers/Flags.cs | 9 +++- .../Common/Rar/Headers/RarHeaderFactory.cs | 6 +++ .../Rar/RarHeaderFactoryTest.cs | 2 +- 4 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 src/SharpCompress/Common/Rar/Headers/CryptHeader.cs diff --git a/src/SharpCompress/Common/Rar/Headers/CryptHeader.cs b/src/SharpCompress/Common/Rar/Headers/CryptHeader.cs new file mode 100644 index 00000000..6cb347c6 --- /dev/null +++ b/src/SharpCompress/Common/Rar/Headers/CryptHeader.cs @@ -0,0 +1,50 @@ +using SharpCompress.IO; + +namespace SharpCompress.Common.Rar.Headers +{ + internal class CryptHeader : RarHeader + { + + private const int CRYPT_VERSION = 0; // Supported encryption version. + private const int SIZE_SALT50 = 16; + private const int SIZE_PSWCHECK = 8; + private const int SIZE_PSWCHECK_CSUM = 4; + + + private bool UsePswCheck; + private uint Lg2Count; // Log2 of PBKDF2 repetition count. + private byte[] Salt; + private byte[] PswCheck; + + public CryptHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader, HeaderType.Crypt) + { + } + + protected override void ReadFinish(MarkingBinaryReader reader) + { + var cryptVersion = reader.ReadRarVIntUInt32(); + if (cryptVersion > CRYPT_VERSION) + { + //error? + return; + } + + UsePswCheck = HasHeaderFlag(EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK); + Lg2Count = reader.ReadRarVIntByte(1); + if (Lg2Count > 0) + { + //error? + return; + } + + Salt = reader.ReadBytes(SIZE_SALT50); + if (UsePswCheck) + { + PswCheck = reader.ReadBytes(SIZE_PSWCHECK); + + var csum = reader.ReadBytes(SIZE_PSWCHECK_CSUM); + } + } + } +} \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/Flags.cs b/src/SharpCompress/Common/Rar/Headers/Flags.cs index 9c5060e9..7fe7d9b2 100644 --- a/src/SharpCompress/Common/Rar/Headers/Flags.cs +++ b/src/SharpCompress/Common/Rar/Headers/Flags.cs @@ -12,7 +12,8 @@ namespace SharpCompress.Common.Rar.Headers Protect, Sign, NewSub, - EndArchive + EndArchive, + Crypt } internal static class HeaderCodeV @@ -40,6 +41,12 @@ namespace SharpCompress.Common.Rar.Headers public const ushort HasData = 0x8000; } + internal static class EncryptionFlagsV5 + { + // RAR 5.0 archive encryption header specific flags. + public const ushort CHFL_CRYPT_PSWCHECK = 0x0001; // Password check data is present. + } + internal static class HeaderFlagsV5 { public const ushort HasExtra = 0x0001; diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs index cb670368..0a637241 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs @@ -158,6 +158,12 @@ namespace SharpCompress.Common.Rar.Headers { return new EndArchiveHeader(header, reader); } + case HeaderCodeV.Rar5EncryptionHeader: + { + var ch = new CryptHeader(header, reader); + IsEncrypted = true; + return ch; + } default: { throw new InvalidFormatException("Unknown Rar Header: " + header.HeaderCode); diff --git a/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs b/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs index f334903b..c2732522 100644 --- a/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs +++ b/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs @@ -51,7 +51,7 @@ namespace SharpCompress.Test.Rar { foreach (var header in rarHeaderFactory.ReadHeaders(stream)) { - if (header.HeaderType == HeaderType.Archive) + if (header.HeaderType == HeaderType.Archive || header.HeaderType == HeaderType.Crypt) { Assert.Equal(isEncrypted, rarHeaderFactory.IsEncrypted); break; From d865120480a81b6f3454238536989d5060098e89 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Sun, 29 Apr 2018 10:13:46 +0100 Subject: [PATCH 58/61] ArchiveCryptHeader renamed --- .../{CryptHeader.cs => ArchiveCryptHeader.cs} | 23 ++++++++++++------- src/SharpCompress/Common/Rar/Headers/Flags.cs | 7 ++++-- .../Common/Rar/Headers/RarHeaderFactory.cs | 4 ++-- 3 files changed, 22 insertions(+), 12 deletions(-) rename src/SharpCompress/Common/Rar/Headers/{CryptHeader.cs => ArchiveCryptHeader.cs} (54%) diff --git a/src/SharpCompress/Common/Rar/Headers/CryptHeader.cs b/src/SharpCompress/Common/Rar/Headers/ArchiveCryptHeader.cs similarity index 54% rename from src/SharpCompress/Common/Rar/Headers/CryptHeader.cs rename to src/SharpCompress/Common/Rar/Headers/ArchiveCryptHeader.cs index 6cb347c6..0ffaed01 100644 --- a/src/SharpCompress/Common/Rar/Headers/CryptHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/ArchiveCryptHeader.cs @@ -2,21 +2,26 @@ namespace SharpCompress.Common.Rar.Headers { - internal class CryptHeader : RarHeader + internal class ArchiveCryptHeader : RarHeader { private const int CRYPT_VERSION = 0; // Supported encryption version. private const int SIZE_SALT50 = 16; + private const int SIZE_SALT30 = 8; + private const int SIZE_INITV = 16; private const int SIZE_PSWCHECK = 8; private const int SIZE_PSWCHECK_CSUM = 4; + private const int CRYPT5_KDF_LG2_COUNT = 15; // LOG2 of PDKDF2 iteration count. + private const int CRYPT5_KDF_LG2_COUNT_MAX = 24; // LOG2 of maximum accepted iteration count. private bool UsePswCheck; private uint Lg2Count; // Log2 of PBKDF2 repetition count. private byte[] Salt; private byte[] PswCheck; + private byte[] PswCheckCsm; - public CryptHeader(RarHeader header, RarCrcBinaryReader reader) + public ArchiveCryptHeader(RarHeader header, RarCrcBinaryReader reader) : base(header, reader, HeaderType.Crypt) { } @@ -28,11 +33,14 @@ namespace SharpCompress.Common.Rar.Headers { //error? return; - } + } + var encryptionFlags = reader.ReadRarVIntUInt32(); + UsePswCheck = FlagUtility.HasFlag(encryptionFlags, EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK); + Lg2Count = reader.ReadRarVIntByte(1); - UsePswCheck = HasHeaderFlag(EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK); - Lg2Count = reader.ReadRarVIntByte(1); - if (Lg2Count > 0) + + //UsePswCheck = HasHeaderFlag(EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK); + if (Lg2Count > CRYPT5_KDF_LG2_COUNT_MAX) { //error? return; @@ -42,8 +50,7 @@ namespace SharpCompress.Common.Rar.Headers if (UsePswCheck) { PswCheck = reader.ReadBytes(SIZE_PSWCHECK); - - var csum = reader.ReadBytes(SIZE_PSWCHECK_CSUM); + PswCheckCsm = reader.ReadBytes(SIZE_PSWCHECK_CSUM); } } } diff --git a/src/SharpCompress/Common/Rar/Headers/Flags.cs b/src/SharpCompress/Common/Rar/Headers/Flags.cs index 7fe7d9b2..488b3c5b 100644 --- a/src/SharpCompress/Common/Rar/Headers/Flags.cs +++ b/src/SharpCompress/Common/Rar/Headers/Flags.cs @@ -32,7 +32,7 @@ namespace SharpCompress.Common.Rar.Headers public const byte Rar5ArchiveHeader = 0x01; public const byte Rar5FileHeader = 0x02; public const byte Rar5ServiceHeader = 0x03; - public const byte Rar5EncryptionHeader = 0x04; + public const byte Rar5ArchiveEncryptionHeader = 0x04; public const byte Rar5EndArchiveHeader = 0x05; } @@ -44,7 +44,10 @@ namespace SharpCompress.Common.Rar.Headers internal static class EncryptionFlagsV5 { // RAR 5.0 archive encryption header specific flags. - public const ushort CHFL_CRYPT_PSWCHECK = 0x0001; // Password check data is present. + public const uint CHFL_CRYPT_PSWCHECK = 0x01; // Password check data is present. + + public const uint FHEXTRA_CRYPT_PSWCHECK = 0x01; // Password check data is present. + public const uint FHEXTRA_CRYPT_HASHMAC = 0x02; } internal static class HeaderFlagsV5 diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs index 0a637241..0f562314 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs @@ -158,9 +158,9 @@ namespace SharpCompress.Common.Rar.Headers { return new EndArchiveHeader(header, reader); } - case HeaderCodeV.Rar5EncryptionHeader: + case HeaderCodeV.Rar5ArchiveEncryptionHeader: { - var ch = new CryptHeader(header, reader); + var ch = new ArchiveCryptHeader(header, reader); IsEncrypted = true; return ch; } From b43d2c3d955cd2385012bee0aeb8273acd697783 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Sun, 29 Apr 2018 10:55:51 +0100 Subject: [PATCH 59/61] Disabled decryption tests --- tests/SharpCompress.Test/Rar/RarArchiveTests.cs | 12 ++++++------ tests/SharpCompress.Test/Rar/RarReaderTests.cs | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs index 140ab367..72bd222e 100644 --- a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs +++ b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs @@ -16,11 +16,11 @@ namespace SharpCompress.Test.Rar ReadRarPassword("Rar.encrypted_filesAndHeader.rar", "test"); } - [Fact] + /*[Fact] public void Rar5_EncryptedFileAndHeader_Archive() { ReadRarPassword("Rar5.encrypted_filesAndHeader.rar", "test"); - } + }*/ [Fact] public void Rar_EncryptedFileOnly_Archive() @@ -28,11 +28,11 @@ namespace SharpCompress.Test.Rar ReadRarPassword("Rar.encrypted_filesOnly.rar", "test"); } - [Fact] + /*[Fact] public void Rar5_EncryptedFileOnly_Archive() { ReadRarPassword("Rar5.encrypted_filesOnly.rar", "test"); - } + }*/ [Fact] public void Rar_Encrypted_Archive() @@ -40,11 +40,11 @@ namespace SharpCompress.Test.Rar ReadRarPassword("Rar.Encrypted.rar", "test"); } - [Fact] + /*[Fact] public void Rar5_Encrypted_Archive() { ReadRarPassword("Rar5.encrypted_filesAndHeader.rar", "test"); - } + }*/ private void ReadRarPassword(string testArchive, string password) { diff --git a/tests/SharpCompress.Test/Rar/RarReaderTests.cs b/tests/SharpCompress.Test/Rar/RarReaderTests.cs index 68af67a9..a4101c64 100644 --- a/tests/SharpCompress.Test/Rar/RarReaderTests.cs +++ b/tests/SharpCompress.Test/Rar/RarReaderTests.cs @@ -170,11 +170,11 @@ namespace SharpCompress.Test.Rar ReadRar("Rar.encrypted_filesAndHeader.rar", "test"); } - [Fact] + /*[Fact] public void Rar5_EncryptedFileAndHeader_Reader() { ReadRar("Rar5.encrypted_filesAndHeader.rar", "test"); - } + }*/ [Fact] public void Rar_EncryptedFileOnly_Reader() @@ -182,11 +182,11 @@ namespace SharpCompress.Test.Rar ReadRar("Rar.encrypted_filesOnly.rar", "test"); } - [Fact] + /*[Fact] public void Rar5_EncryptedFileOnly_Reader() { ReadRar("Rar5.encrypted_filesOnly.rar", "test"); - } + }*/ [Fact] public void Rar_Encrypted_Reader() @@ -194,11 +194,11 @@ namespace SharpCompress.Test.Rar ReadRar("Rar.Encrypted.rar", "test"); } - [Fact] + /*[Fact] public void Rar5_Encrypted_Reader() { ReadRar("Rar5.encrypted_filesOnly.rar", "test"); - } + }*/ private void ReadRar(string testArchive, string password) { From 031b3c55f64bbb3e3f5365ce638daedc214aba03 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Sun, 29 Apr 2018 11:12:28 +0100 Subject: [PATCH 60/61] FIx solid support. I did it wrong --- .../Archives/Rar/RarArchiveEntry.cs | 4 ++-- .../Common/Rar/Headers/FileHeader.cs | 15 +++++++++++---- src/SharpCompress/Compressors/Rar/IRarUnpack.cs | 4 ++-- .../Compressors/Rar/RarCrcStream.cs | 4 ++-- src/SharpCompress/Compressors/Rar/RarStream.cs | 8 +++----- .../Compressors/Rar/UnpackV1/Unpack.cs | 16 ++++++++-------- .../Compressors/Rar/UnpackV2017/Unpack.cs | 10 +++++----- src/SharpCompress/Readers/Rar/RarReader.cs | 4 ++-- 8 files changed, 35 insertions(+), 30 deletions(-) diff --git a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs index d4197b3f..60ece4ee 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs @@ -64,10 +64,10 @@ namespace SharpCompress.Archives.Rar if (IsRarV3) { - return new RarStream(archive.UnpackV1.Value, archive.IsSolid, FileHeader, new MultiVolumeReadOnlyStream(Parts.Cast(), archive)); + return new RarStream(archive.UnpackV1.Value, FileHeader, new MultiVolumeReadOnlyStream(Parts.Cast(), archive)); } - return new RarStream(archive.UnpackV2017.Value, archive.IsSolid, FileHeader, new MultiVolumeReadOnlyStream(Parts.Cast(), archive)); + return new RarStream(archive.UnpackV2017.Value, FileHeader, new MultiVolumeReadOnlyStream(Parts.Cast(), archive)); } public bool IsComplete diff --git a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs index 55a13322..39b238ab 100644 --- a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs @@ -55,20 +55,24 @@ namespace SharpCompress.Common.Rar.Headers FileCrc = reader.ReadUInt32(); } - var us = reader.ReadRarVIntUInt16(); + var compressionInfo = reader.ReadRarVIntUInt16(); // Lower 6 bits (0x003f mask) contain the version of compression algorithm, resulting in possible 0 - 63 values. Current version is 0. // "+ 50" to not mix with old RAR format algorithms. For example, // we may need to use the compression algorithm 15 in the future, // but it was already used in RAR 1.5 and Unpack needs to distinguish // them. - CompressionAlgorithm = (byte)((us & 0x3f) + 50); + CompressionAlgorithm = (byte)((compressionInfo & 0x3f) + 50); + + // 7th bit (0x0040) defines the solid flag. If it is set, RAR continues to use the compression dictionary left after processing preceding files. + // It can be set only for file headers and is never set for service headers. + IsSolid = (compressionInfo & 0x40) == 0x40; // Bits 8 - 10 (0x0380 mask) define the compression method. Currently only values 0 - 5 are used. 0 means no compression. - CompressionMethod = (byte)((us >> 7) & 0x7); + CompressionMethod = (byte)((compressionInfo >> 7) & 0x7); // Bits 11 - 14 (0x3c00) define the minimum size of dictionary size required to extract data. Value 0 means 128 KB, 1 - 256 KB, ..., 14 - 2048 MB, 15 - 4096 MB. - WindowSize = IsDirectory ? 0 : ((size_t)0x20000) << ((us>>10) & 0xf); + WindowSize = IsDirectory ? 0 : ((size_t)0x20000) << ((compressionInfo>>10) & 0xf); HostOs = reader.ReadRarVIntByte(); @@ -205,6 +209,7 @@ namespace SharpCompress.Common.Rar.Headers private void ReadFromReaderV4(MarkingBinaryReader reader) { Flags = HeaderFlags; + IsSolid = HasFlag(FileFlagsV4.Solid); WindowSize = IsDirectory ? 0U : ((size_t)0x10000) << ((Flags & FileFlagsV4.WindowMask) >> 5); uint lowUncompressedSize = reader.ReadUInt32(); @@ -408,6 +413,8 @@ namespace SharpCompress.Common.Rar.Headers //case 29: // rar 3.x compression //case 50: // RAR 5.0 compression algorithm. internal byte CompressionAlgorithm { get; private set; } + + public bool IsSolid { get; private set; } // unused for UnpackV1 implementation (limitation) internal size_t WindowSize { get; private set; } diff --git a/src/SharpCompress/Compressors/Rar/IRarUnpack.cs b/src/SharpCompress/Compressors/Rar/IRarUnpack.cs index 887c590e..ecd70a47 100644 --- a/src/SharpCompress/Compressors/Rar/IRarUnpack.cs +++ b/src/SharpCompress/Compressors/Rar/IRarUnpack.cs @@ -5,8 +5,8 @@ namespace SharpCompress.Compressors.Rar { interface IRarUnpack { - void DoUnpack(bool isSolid, FileHeader fileHeader, Stream readStream, Stream writeStream); - void DoUnpack(bool isSolid); + void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream); + void DoUnpack(); // eg u/i pause/resume button bool Suspended { get; set; } diff --git a/src/SharpCompress/Compressors/Rar/RarCrcStream.cs b/src/SharpCompress/Compressors/Rar/RarCrcStream.cs index 2bf98579..df7d85ff 100644 --- a/src/SharpCompress/Compressors/Rar/RarCrcStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarCrcStream.cs @@ -9,8 +9,8 @@ namespace SharpCompress.Compressors.Rar private readonly MultiVolumeReadOnlyStream readStream; private uint currentCrc; - public RarCrcStream(IRarUnpack unpack, bool isSolid, FileHeader fileHeader, MultiVolumeReadOnlyStream readStream) - : base(unpack, isSolid, fileHeader, readStream) + public RarCrcStream(IRarUnpack unpack, FileHeader fileHeader, MultiVolumeReadOnlyStream readStream) + : base(unpack, fileHeader, readStream) { this.readStream = readStream; ResetCrc(); diff --git a/src/SharpCompress/Compressors/Rar/RarStream.cs b/src/SharpCompress/Compressors/Rar/RarStream.cs index 6da56697..08240859 100644 --- a/src/SharpCompress/Compressors/Rar/RarStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarStream.cs @@ -9,7 +9,6 @@ namespace SharpCompress.Compressors.Rar private readonly IRarUnpack unpack; private readonly FileHeader fileHeader; private readonly Stream readStream; - private readonly bool isSolid; private bool fetch; @@ -23,14 +22,13 @@ namespace SharpCompress.Compressors.Rar private int outTotal; private bool isDisposed; - public RarStream(IRarUnpack unpack, bool isSolid, FileHeader fileHeader, Stream readStream) + public RarStream(IRarUnpack unpack, FileHeader fileHeader, Stream readStream) { - this.isSolid = isSolid; this.unpack = unpack; this.fileHeader = fileHeader; this.readStream = readStream; this.fetch = true; - unpack.DoUnpack(isSolid, fileHeader, readStream, this); + unpack.DoUnpack(fileHeader, readStream, this); this.fetch = false; } @@ -76,7 +74,7 @@ namespace SharpCompress.Compressors.Rar outOffset = offset; outCount = count; fetch = true; - unpack.DoUnpack(isSolid); + unpack.DoUnpack(); fetch = false; } return outTotal; diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs index dd3b52dc..90c09c6d 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs @@ -103,21 +103,21 @@ namespace SharpCompress.Compressors.Rar.UnpackV1 UnpInitData(false); } - public void DoUnpack(bool isSolid, FileHeader fileHeader, Stream readStream, Stream writeStream) + public void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream) { this.destUnpSize = fileHeader.UncompressedSize; this.fileHeader = fileHeader; this.readStream = readStream; this.writeStream = writeStream; - if (!isSolid) + if (!fileHeader.IsSolid) { Init(null); } this.suspended = false; - DoUnpack(isSolid); + DoUnpack(); } - public void DoUnpack(bool isSolid) + public void DoUnpack() { if (fileHeader.CompressionMethod == 0) { @@ -127,21 +127,21 @@ namespace SharpCompress.Compressors.Rar.UnpackV1 switch (fileHeader.CompressionAlgorithm) { case 15: // rar 1.5 compression - unpack15(isSolid); + unpack15(fileHeader.IsSolid); break; case 20: // rar 2.x compression case 26: // files larger than 2GB - unpack20(isSolid); + unpack20(fileHeader.IsSolid); break; case 29: // rar 3.x compression case 36: // alternative hash - Unpack29(isSolid); + Unpack29(fileHeader.IsSolid); break; case 50: // rar 5.x compression - Unpack5(isSolid); + Unpack5(fileHeader.IsSolid); break; default: diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs index 2d52da5b..e8bc8c36 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs @@ -37,7 +37,7 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 this.writeStream.Write(buf, checked((int)offset), checked((int)count)); } - public void DoUnpack(bool isSolid, FileHeader fileHeader, Stream readStream, Stream writeStream) + public void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream) { // as of 12/2017 .NET limits array indexing to using a signed integer // MaxWinSize causes unpack to use a fragmented window when the file @@ -51,19 +51,19 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 this.readStream = readStream; this.writeStream = writeStream; if (!fileHeader.IsStored) { - Init(fileHeader.WindowSize, isSolid); + Init(fileHeader.WindowSize, fileHeader.IsSolid); } Suspended = false; - DoUnpack(isSolid); + DoUnpack(); } - public void DoUnpack(bool isSolid) + public void DoUnpack() { if (this.fileHeader.IsStored) { UnstoreFile(); } else { - DoUnpack(this.fileHeader.CompressionAlgorithm, isSolid); + DoUnpack(this.fileHeader.CompressionAlgorithm, fileHeader.IsSolid); } } diff --git a/src/SharpCompress/Readers/Rar/RarReader.cs b/src/SharpCompress/Readers/Rar/RarReader.cs index 81f99d9b..05fe0daa 100644 --- a/src/SharpCompress/Readers/Rar/RarReader.cs +++ b/src/SharpCompress/Readers/Rar/RarReader.cs @@ -70,9 +70,9 @@ namespace SharpCompress.Readers.Rar var stream = new MultiVolumeReadOnlyStream(CreateFilePartEnumerableForCurrentEntry().Cast(), this); if (Entry.IsRarV3) { - return CreateEntryStream(new RarCrcStream(UnpackV1.Value, volume.IsSolidArchive, Entry.FileHeader, stream)); + return CreateEntryStream(new RarCrcStream(UnpackV1.Value, Entry.FileHeader, stream)); } - return CreateEntryStream(new RarCrcStream(UnpackV2017.Value, volume.IsSolidArchive, Entry.FileHeader, stream)); + return CreateEntryStream(new RarCrcStream(UnpackV2017.Value, Entry.FileHeader, stream)); } } } From d9c178cbee83e52886fadc9bddd7792ee94776e0 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Sun, 29 Apr 2018 11:33:49 +0100 Subject: [PATCH 61/61] Fix all platform support --- src/SharpCompress/Archives/Rar/RarArchive.cs | 1 - .../Archives/Rar/RarArchiveEntry.cs | 2 +- .../FragmentedWindow.unpack50frag_cpp.cs | 4 --- .../Rar/UnpackV2017/Unpack.unpack50_cpp.cs | 6 ++-- src/SharpCompress/Lazy.cs | 29 +++++++++++++++++++ src/SharpCompress/Readers/Rar/RarReader.cs | 3 +- src/SharpCompress/SharpCompress.csproj | 2 +- src/SharpCompress/Utility.cs | 12 ++++++++ 8 files changed, 47 insertions(+), 12 deletions(-) create mode 100644 src/SharpCompress/Lazy.cs diff --git a/src/SharpCompress/Archives/Rar/RarArchive.cs b/src/SharpCompress/Archives/Rar/RarArchive.cs index fb052c37..52ffaa1c 100644 --- a/src/SharpCompress/Archives/Rar/RarArchive.cs +++ b/src/SharpCompress/Archives/Rar/RarArchive.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.IO; using System.Linq; diff --git a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs index 60ece4ee..3f2360b2 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs @@ -24,7 +24,7 @@ namespace SharpCompress.Archives.Rar public IArchive Archive => archive; - internal override IEnumerable Parts => this.parts; + internal override IEnumerable Parts => parts.Cast(); internal override FileHeader FileHeader => parts.First().FileHeader; diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs index b0bc6da7..cd0b5eb0 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs @@ -10,10 +10,6 @@ using size_t = System.UInt64; using int64 = System.Int64; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace SharpCompress.Compressors.Rar.UnpackV2017 { diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs index 3a0d63c5..53f0e713 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs @@ -334,7 +334,7 @@ void UnpWriteBuf() FragWindow.CopyData(Mem,0,BlockStart,BlockLength); else //x memcpy(Mem,Window+BlockStart,BlockLength); - Array.Copy(Window, BlockStart, Mem, 0, BlockLength); + Utility.Copy(Window, BlockStart, Mem, 0, BlockLength); } else { @@ -347,9 +347,9 @@ void UnpWriteBuf() else { //x memcpy(Mem,Window+BlockStart,FirstPartLength); - Array.Copy(Window, BlockStart, Mem, 0, FirstPartLength); + Utility.Copy(Window, BlockStart, Mem, 0, FirstPartLength); //x memcpy(Mem+FirstPartLength,Window,BlockEnd); - Array.Copy(Window, 0, Mem, FirstPartLength,BlockEnd); + Utility.Copy(Window, 0, Mem, FirstPartLength,BlockEnd); } } diff --git a/src/SharpCompress/Lazy.cs b/src/SharpCompress/Lazy.cs new file mode 100644 index 00000000..893e3bb1 --- /dev/null +++ b/src/SharpCompress/Lazy.cs @@ -0,0 +1,29 @@ +using System; + +namespace SharpCompress +{ + public class Lazy + { + private readonly Func _lazyFunc; + private bool _evaluated; + private T _value; + + public Lazy(Func lazyFunc) + { + _lazyFunc = lazyFunc; + } + + public T Value + { + get + { + if (!_evaluated) + { + _value = _lazyFunc(); + _evaluated = true; + } + return _value; + } + } + } +} \ No newline at end of file diff --git a/src/SharpCompress/Readers/Rar/RarReader.cs b/src/SharpCompress/Readers/Rar/RarReader.cs index 05fe0daa..23a4005e 100644 --- a/src/SharpCompress/Readers/Rar/RarReader.cs +++ b/src/SharpCompress/Readers/Rar/RarReader.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using SharpCompress.Common; diff --git a/src/SharpCompress/SharpCompress.csproj b/src/SharpCompress/SharpCompress.csproj index a20a92a9..5c1d0815 100644 --- a/src/SharpCompress/SharpCompress.csproj +++ b/src/SharpCompress/SharpCompress.csproj @@ -6,7 +6,7 @@ 0.20.0 0.20.0 Adam Hathcock - netstandard2.0 + net45;net35;netstandard1.0;netstandard1.3;netstandard2.0 true true SharpCompress diff --git a/src/SharpCompress/Utility.cs b/src/SharpCompress/Utility.cs index bca28a9d..e97d093d 100644 --- a/src/SharpCompress/Utility.cs +++ b/src/SharpCompress/Utility.cs @@ -184,6 +184,18 @@ namespace SharpCompress action(item); } } + + public static void Copy(Array sourceArray, long sourceIndex, Array destinationArray, long destinationIndex, long length) + { + if (sourceIndex > Int32.MaxValue || sourceIndex < Int32.MinValue) + throw new ArgumentOutOfRangeException(); + if (destinationIndex > Int32.MaxValue || destinationIndex < Int32.MinValue) + throw new ArgumentOutOfRangeException(); + if (length > Int32.MaxValue || length < Int32.MinValue) + throw new ArgumentOutOfRangeException(); + + Array.Copy(sourceArray, (int)sourceIndex, destinationArray, (int)destinationIndex, (int)length); + } public static IEnumerable AsEnumerable(this T item) {

_dktVX!kB&v&CFqFUU}fZ)Mg$&!Sg5?y^tvu$wW}s;bU7=j{BbCzcieH)S^3 zy>&fl7kh<&g|A+6)s6>&xAm5;bG-FCRzV%gO(iKjGr* z+BQexv6KH!C-MGOVwWBXE$@0!R2FoFYN>HFBIk|DhFMRF{x8eA z{oifAcbfDQlRe31-E!X?&bRiOe!nlkY?{+>W6ssxuOEc``m?UZuk2aF&k3{M z9x1-j-0$*z>G}00YXh`?|0zsoK4u>|-TgW<`?*&iEhk-d+x&Wd5!*%Gm@5x1-)$59 zqtwIl{KvJ6&);0jOMQN<`?O3(w-o<7zmoI&I}>}otyOX++-BjJd2X7I@q=wvia~|C z|6XkEn(UyR)tN7P`_A2viMC~7M_1o|8}v5$Z}Q3Wx8@yM^u#ANE~l+qzwW+})xp*0 zQWY2%xOGZfA9{Ah=so-UjuX539YiG_#n*Ism3Rf${ag|&I@wgW=pM6bpUatMVCdDh>b^QHY_r0C_dcb$F|7yVc#mFk%nd)=^xDCc zb%Tq`za#Gok7{O_YgS1b-b~>?T^-W!`?&Xol#U;Ey1p!7noqYjdY?a_p!V~{Zk033 z9n-ovoSV6U4besO zL^Jp0{M3?63b$fzc9bJ%VmDs+)O$^M*Ox@`3*6z_SmLvKe^_4NPGug5P4jjlSL zyZ3qS0mJs7Mr9L+OGlp=eZPD2%dAHMi^^yJ-(zS0S6#l%F`WJVvb2zuB9pX@9?v-! z;?W|x;)+d3)Wa*1Z%^OHx^vJO34*82tU#(d{fbNqAD&QV61afL(cjrF{nr^qe+$zoaikX2>knux1*;d39Fbp870RDSkR zmf_p>%e+_4UKTifEGFr|0sGR4I$Wu;U*{*f&M5NyeJzRG{`@srt;_K%b3SfS?Z z+nj4&`DIzbbq@n&)_Bglx%5ll;lnTUHjAB~w8`k%Pd&Y3`ctFK3K@ghrkaFX3rS8t z<9hL_5{qF7*MXpg@1M9-{d(EEVDk6g{1pLSe%5dL7B?@9ye?dJ_V2geH;rY={|vHc zU)mD2yFST9C`)@c+P4^^MBk(w-c41?;unebSnoPnQp&*ub)g`X4-)x$CCprF~t<33HSHDlYlpwR_l2i07mEFgu z*q+LXdeU1uCnR&xz4pfi4lC|VTGlrCqQH@b0ZCdbeb!8|eG?FqUbW+)@uU8$3^QJy z*j5xe@9O~>&lV*|ze}H{Y>?l&LfWUE zn(rCr`wVNpwdzZ+_&?=#@w&Pa>lbrwIECD~GXLPt_pusTxsu6e3AxCwvyel3@Lu{ldlNnUoD&cbBQH~I6;HeHs^ zPM>5e)x@5{eQQcC+l~{b7&C>8=f8T;>ZNZYV3%I=AD-qL4MUgm+32Y=q+?|7<_-gvrB)_2jOv+ZmO zmqS_pgnGW!dD`zRk?^2%Z{pd2y={*f-<6iFXK`OJ@mh6=Weao&k(XvCEonpG7HwA{(l-) z=Z`39m3H=Tmy0~Cyo;T4-S)Q14Xvqy@;#o6Kg&5{k_EDs-xS`paT)vVt-tQAzM3O6 zRdI8_QzTpyg(7c^_!G0d|yI)N?IOWCb zf`XZ=O|sN8XC4d{tyg`1D`9rY+zUq_XAh#d#YQcRk_LNgP_z>?(n^Lw2v)pXi_udsAPTJ zkkagUCRfPSBk%3xYwQ{S_Z@vZad|tBeZuCbs3%N2SDwiCdix~6xNvII?71er{ol90 zbzji3P=D7}sSbtxj-6XPK0P>bwZvXk%ynUyo9SnJ^OS=Vx7CEi|0vgacE@mHr$ZH+ zMKRZUo&!9;5>?znt`x3{t?gI2etG^??_|#3sdqKfx}3f$K2o-?oMymeRHfK@E5=6l z@~0GU{XJ?q3V${|TAaM<2Y=)D$`d<`)?GMzV?lhMReL*k&OC?CjZ>wVWt@MtF8jv9 z(=}(_pJ$)Cr#z^$)wWT~fBwk#R_E)>UoFckWY@o*ss3s4ljDCVd&P+zly6SDe_`p|oi;gkag8xM-aR(|UCg#bONft~*@Ca7^8eu*_aYq=7_$F# zN>3JBZ{1!byM3=~^e0BiPCK@>@0|jEPukepc+2_SZ9^typ*P1@7<~x8_;1;@ko1?E zuHW(5-*@-|tCE#hNFz_h?+@?twaUxy2P}JSzL)h+L9^Jrsd>4-j%&{==%KfVJmaAW+Y28Z(7xB}}{v7>xu6f*Ym>0coVK^@+Itto|y>EHDl$uY?c1xgW9BHJkt(->=M{s9wulklBFKU z{6Kl%%-RM2Zpg58$xSu5l6!qBYoyP^d4B(kELV6|->H43)ywp*HuFQZiN1tUYRaz{ z3(MEry$Y~i>lrxbSI&NBo|u>30p6NN*KXTbA#wTdipp)Q`BHU8xhCiAn5|1Z7V3P+ z)SY?lpqKZ*2zTdfsb$j5(=?Ku{!jb4LVW$#CpFGO&ey)%Ia)sc^;RrH)Oc@U`3Jv8 z$I3QrFIuv0>C4hO?fXD`&ADSv6lyoG0?tY?&otEYl{)B`nXJ6Bt&( zq<5g!-TV8M62F=6XId_J`s}KOv-bia&$-k1`sNmA%~Rm{zp6Bwb-~7p^^5y&uRQlY z+|r~hHe~x&&aGb=vZZ*-xiX%-nP&UW<)o2B`6KQLH|MiRrr!*@ysmZ4oxYyL%{u2} z9?4py)lPqLbN@yQ?x&5rJ)AX8hi}`+BJ}X~gUc=Ewy`O}*@v9GPW_bTnzTXM z&qiV*--dUe&q}PA{7S4m zljHD8C;Q5K;nUOFO2yY5$x&(jpZe){>lqDJEjO_sb}7b2_1jUxE9OrPe}A!3adKhg z;Tq1f6F*I>l7GnaEAdT>Ea!u{8ZFCbIsZIa(k~Ic`rW3dMFGCy*?R+DsD`A=ImbTR z^vC(<{hc@1Eh=hH>|9c~!ro(l$K5%cHnnRGNm%C3yc7KV(52a1j%a+`qWm+fE%98- z#=gs1OIW*eu3dj~D#K|@xYd$7HipZW`Leu!W+Jq4k;t)5)`p9J-!XfC+;nNm)!I!z zp59cKn{d&;YsYl$i5phBH_L?N9SR6tw!G)#E|d3tik-(hsy|xpnq1)I7ji+fo~3ib zMBbX~X^;K4%@*8#?wQJ_91Dd=$sOw_PMV`3cWLspzwHv=)_v{zvWzPsmQlFi5_9wR z^;gcgZcr1~`1r%hDUWWv?V44=cR zqGBzzO!M876<0G{?p1YNeE-~gtKA7_n9LR&oUh`%_5Z~iPpx?8I!|5TQg&R4mBYFI zqtZsLyp3_~e6NpY+!A>8J0fn2l4jx0H3b2=XQr~A`5Jum>BhDN_t=hU-B!FSGFNrtlRo0nz1`QAJ=+w&Z}a?C zcP#Dd<@#+bCHi;qOcu{|cgtKIIeAZR>ES6`l70Eu#co6XnDxAuCQS}4bohNb?_JT~ z)zzQ0m=ANVw&o9dyh!k1=t;4ydz~kI{ncVGtG>9=+4x$d@o5RYjy-Ob#w8wHqB^qd z6VC2>a=*<{>gt)$seA5o>rNK?KV_lajKrQbzn(a7K2rXYq#7TVDj=N5{I;`Q>s6`I zBX;JTtK9R`o}Ah6;_W88Q~SgJzPu{vcbrT8N`X6%*N)iPhx)zB*%+(dWgMS+$MJZL z&MWWajQN>Kb+OySFMbu^I(nnsbAxl5@u$z3@2bwuo4hDhi%)WP-OAWGKQ?SLX`c1+ zpYT~0j+d*C>QJ~nZj)<(+&5XnEA~2{fd>7ikXeSCG0fSe7zt%+Oh z659BmZ#g?J$+$oI+Gb~4qfLK|)^52UwX8bXm4Rzs%m($M`IXleO*nXLHP5m|{Z{wu z4;qU!``es9D}K#Q=g6mjYfo@4IR0-z@A<@#5C6hiUM*cI8?C@*IOCwFVg7|Q9xuzQ z->d|i+6*kWR=K=cnR@)ot5>=!TYrA9R6M@%hjm6v>5T6u!%rQMd&aL-VzYM96qY@| zv)>(L(ApE&`SD>m|B>U>r3W}p%+uj=kz8hAb>_jreSM4}Uz*=2&$!IspO$htB*1Xl z)})`^p)(e&SYWQ;W$LFd+kTKgyX52jXSubhn>PoV2$yHSIoJJFq2S56Qu+3*?e(XreKpq>2DI?YfARKR@bt^sT_rr4 z85!n(fA=MyRoQcO(YxjAYTX~Vt+{URse1KR($T61V!bhaf#>dBD!*QEyDw^)kkGr> z{Zm3)O>ew5y0Gw?SormZi|>LrzntaPE-1QlP2RMX^${WYW)0`;WxMOl=dnDQ?sB5? zyMbWub^S*NrT0mFyJ5@P!KPWEc>U1pDH|Nmp7=T6Yt|(0zcCk7rBzP7n5=I*!O zwvLd+OJ1JW4#h>PTfet%GhETSC`sdTZbuxemAz3-%wZG$R&i0;0~5cjt#0l6bGIvb z*-TdJHAlbX8U!!eYgm0nHfxUg+9hXs(!L${IN)*W$!{~|byFR^n-`=?*zzxp)tRHj zn`UiucX8j}yR#KajxA#Ku{$So^xbd9S%J$l#r9f>`ZG5gZ?+9)_BztSUH3hVJ8GfS z$vwGyr0N$<|G;?9#!BW~-JgzY(f)t>f3bR0D4hc;^x+Z%0|4W-Lh1qS_xAlD`bo#@e$a~9zIhW7Wxw%emqDbZC8J=^sPfULG zoiFWv%A3@DF{T!`7gr@7oXpuG{N(SkVzU!B3{&M&s`oYcnB6+Rgg2<_LT_!#JI{X_ z`xL!wi;7+u^9W6uEAXO3VTJs6LrEvrv%<#@niTxLyrG+&dH?l?x94n-{w@9Nb*`u8 zofio_OqE~v_&qQEugZJJ$R@-%{?FaZ7Y+%Y4(~83y(p|)DaJ8x`IkDa?Nf8Jt|~Qg zEO5W!F3h#U*Gg4j9{)UsW4Bf-zY#f>dVOcari)A)5-X;}EJ-mu>UDLstjsj~H-T$- z95nwuwYyfd=URX0wv!Y6H*R4q3i$Bcpj^Tr<{9_@J5p;mSY}@rS+m>m-J|(Q(QhYh z)fVE|`!elUOU!~>e0`2fSF1!VJYL-TLy>E~?h2OG&w4yf)#QaH-Q2Z>=j{59NR}Lj zGY{s8tUAeXXP)=|R)s?g+gAzrs<#}l+rND|XQq>mP}h0iIwy|*38!72%)I%tSX1i3 z?_4v(sI`?6Tn)MWys}5x=iHE4xGAvmnfY$Zx8<(8@~RTPx~=(8w0Bn4$@fLMNvW4a z&)hIqT9hJw&-hT#?1}aj9m_X7)Lnj+uPK*FN%Xq>I^`m{6#braou#_W`&$+*E2%WG z^LRb^N9l{@-g8zaRL$0Bs&wv>{c-PEb&0t6_(+(9{OzOG2|wCvS38?Vj`ck>T%OTYsLpAezT!E$N5={xo6kQ7aZIuEM*$^WC9~Y*6!}s z`SWbr-cH$SZ>U@1Vs`mP=~3H=#?pu4Z(U7d%J#3g8W=v!dRrn}*UX9j|FZm}pYcgt zjoq}4KYUr*SMf(&i=I#XAb;ju@zhH{R2bJxx?BIQslr`y@{(W5LFbEw_Rl_IYEx2@ zvdc8jai(fdRo|s>|H-xAb~h>TarIh9|v z@pabk`YZ19Yg%R;u-fA_e;sSd<*5!|tbZt^Zv7}G=PFs!^+Lfa-iUu<)OXDrD<{S# zvI>@N*UnzHTrKL$Pgk#;=~Ew0y}9S^;w@pvd)6IZIcc8j>>vG49fGHAIuX0&l}w$0 z8#SD{_|#4jz6v zcVE@4{3D9oRsZxeSG~JeacEk{-3eFKbEX;?P5S3&A9}29Vn+JyiQ?*Jm1geT6|B$q zPrH)uvue#6&uv0qTr`?*P39@~YS#)bellfNt8xC9Z`h4_p@>HkzH|Lgi z?rT=Uua;UeJML$Ez0Fg;!0ter);HBFv#uLy&kf#ljrY$P?NjeF_n8Zbhn{QOaq`Fs zmpOGQElIKYs*Gz|4e!m~+nD<1I@6M!>+`l3m#wi~6yGg$kDIH6E%4f+-ip4mhlb`4 zdC$H~*q-SU`uAPnIjJvp*X|V0Q~JEE_w$6MJHC}Hl>5pkzipD8(XoiU{<>@Dg8wgl z^;TnzfKE=J*R`t}PO}U7+U>$Q@+7ZH#%y~le_}_G2Gi${vR>2X6s>R*NV1>y<(tjE z@;(6|F#a6r5YsY2LhYLP*3` z$rT^I?saJEKa+T30_Xpx9~X0L|4z&GII=fqU$APMOI(+yf!X;2!>Nn*x@d%2mt4v3 z7M!#p#C|n@c;EkdPYgfFzs=*$Gr6^#-+5>0ncrD{GrpbKUnC?J-}6k}J4y)j!daGS07zVDY0F8^@h)j{QRUw^n%9%NNX=HFv2ta-OH zrF7!FL-%5Hxr?X7eR@|l`_&tdBRvYup{l_q)88mc<=yZ5wNOH9!i=vg^W@m(NjM$o z`CT*N$~%Led&Uxt8-jMLi5dHrg(PYlZn{y)^qn(RYfD4TwwJ9wv0pEq{2s`6WZQqK zMUN*cUc3MA^_4jWiCrF*Z~J&ApoFIV=zw#Vya>4dEFN=h9oUv~QbY-)?t zeU`4+(iN!YChlqcbk?jhT2|L$-ab<~Ub9VbQG?sUy5%C@T{nLB+I4L84l~=7{6F39 z{3n=}q=^OE+|u1-`C6vLvIJ!)D!*-b&_ z^OonTPW=bnh~C2g^<8U9&;2V!oM-fW|L`AVOJ8~Xt?9F}dz>eK zFu#~!`Y|rk<2c*%xANB~sj%kC*A)1L2;5+3JHe?c%{Kg*xx>)&<0g5PXFbZ#?Oq7ZTPgYb@zZiUme-1tRm4_qds{hU*V4ExC-?;_ zI=?m4dw#H8!#GRhp8b&}duObhn$yoIlotLjkt;&teCwf<)7!t-9{du;dR0E5;-Toi zW$S#}r`-AcGko#!)jN&`?zBz4lNpybt>M zN?=OhEa^>GlsB4Y|GK?=;Vlo{ZL{3ZIQ?_FYRb*JdtZiJdf%=o4?63n=5CG1w=+9r z@@i#O@ysn>|0>m00KY1DU-~)3$ufO|jR>qlS2dW$IzMpY&XK>$x+fTydAMh+u{h&Af z26LTpp@rqwwf#ruT=g?s@tSGk>wm_|lMb(J+g=(NQd&Yspn1{>HV!r57w# zoiW}1qv24{=_y*l%92aICAVv@Jb0<|5Lfw~Ps<`ZmG5O=)QIJH=-=51APc^jP5Qm}d5ykNgimfzk)tg$vfv16`K z{JNlfTi3nk*<7*d*ZrgEbq{Y$a^!huC%5OXqQZ|Bcj-wtlT9}U=vN;Meq66SL1+7~ zumoS@gnK_Q&aX zZ$0PqbgzrP!=vmUe)Ze;2RHocTXpCnm%XLllKXDq&r&rT-`~lb+Ryz#w*3%KdWTvW=XitdGj)t zrPK%Wz0X$vZauf!j>Spho$iOUt9wl4=K5w{f7=s?!U51Jm>vvo2tcq zq;=DAq1&pWe;&P6%Vk@eXj1U8C^+hL=*PbH!-*khAM0r5nX`D+>;)pF2U5?7tKJki|LXUxFDpZ>la8h<$^Rb#!dP2I=MXDW0&)^EAB`0~cbHNwrnBt9 zF}2ofdensv|CY#?%t<`qJjJEm`@}5n-A5sAk){#)6QT9S#TScn!Zf z=1&55vrmn3ztPS##Uw1O(}6>$H=+I5Kh1wKt`^2V(*)kc%1zr+km)t=ybE*H$pf3C zy%>Ak9~YGDYkASO@nBrO(5^1K$zo&I5#~-T(Z0}UR8a9n#-}O^Q6YZPb#oQ+ae@*{%_kjB46iwq( z%{z8);SOkFY15v*Z(&)tOQgo0?ub9!`zBv7n^O7ju%PbNlaW_9ADfrizNbKUrek|Y z8RyN#dn^yl>oId~>0jjMAsaorR=9enuYdl=`qAFAT$@b~SFAX2Ey==fX6Rd4yCs{lXV!xed1u9}ZFb$Zf@G`~8EI zrLCXfMOXPx-}bro9*mRH`~25;pYY1bIR{RkpSZsJ)uofJv;4AcTX%mCIGVe`SYLNx z!CQ_KTv~okEbB`rF7a)BZuINKmtQ_LjmzI1w9P(J^ybNrzhR$moc(;3iR<|X6~!KL z4d1Y5(se%E-RsX5e$HzCciuMq9b-h2&dgv*(-YUH|L3dO&%N$QMAEUp7tOT%1q6Q- z`>(pZX-0zJr*kU}O(kXRt}l2uS+47tQ0;{KE9Pb{X4l{<`7&wRl&w!L-#9frNOMA> zp{GuHt0hyO_L2`Kjk)DF7EA9qZui1mX_M~CgM!@9mr>=gAT?eH@BbLytt%}GMvRUZ2+;`vc_usnK;%8k|>hlc1c4;HWb zd;Ph}lQkQn`wm=;SU-E)9}h;|OWWUv9p7>kiX-<*Caj z8Q<>xbVK~=r)5uTwb-NHc(5+1^lNx}pYufGob@Z%4cxVgrgksT`|es^qI1k7Rd28N z!@?Qf|ot!uZw+V<1iV5jNV-EUNaPhD&~uvzXw-QATt9FAUE za73phe~;Xy1E;IG#0-1|A3O^Cc6-~`M~@!l6!QouNj97ODR(HlW6bS#k@Y=;Rjr5; zf#obJj>fm-B!=zlNU|dsuzYc`@|+n#CuN}=szCTB~Wt#X+ zF?FBIla&|GcJm3imKMpq;>b@%M#=d*Pjmdp5R|dBZRmK^A@ST}Qqk=UH|Bel0+)Ao z&T$uB&++@`^Iei_)gOoGu^DFuFYm4sD#$ht?`Hlm^}<883DF_SzIPsoty`@%DJ)*j zCui+EOUWX2#YIxLUzZ(kxbWgjjqmi?EBPuuSMK}RI2jEGU0;cmcO=Bn4Z^|%~zhU^l~Q8&iSX>-5ozYc^|S}Re$EsE&+k|`R0Oo z*P^dqX*J3(_}ASSV8R%kzVle9%vPfZ%8fo1K090fC+NJ;5^^+?-xtz#;rad>M-DIv zN56JyfAK|k!<1FJ3x8YQ`;z>r>7kcJR?82rT?=Px1xr<4b>?)`$egR|mLjnkK3wXg`EStC88A@rF0d_Bd@bC?&^I;8ISQ>paue67Xr zEhlffE}CmlwR!qy$x@9^FP=)BT0Glb#xMHev-A8}H)mHO~$;1*yGWdiuwuH0MVjv=v%;1v#DHzB?z@)>HZO zB-5T7mlY2mGu-etx;=BsMESVWt=HrKYoEVpe>Uns375+D!`GMPa*K8!*(TLscl*d` z0SEK_sgI3k#d2@{bk^GQZ^;(@Ze{6dW~MbgNyZ38TGaMGgWOp z{H8iT3h=)id`NP${xRkTcH`zFA*yC4yk+XDVoqM-_;zB#j@}C=*kscxB-l30^hoM_ z-}pCd{${_*6+b3FjyhYZ`z8P3ibU7un5(}wrM|lC^17+=sr3ZY;Mp$a8!Hz)OZ;EP z)7iMp*x>2e*;C(6S#;}Vnc1zsPVt|6w-+kiyV$YlK>GbD7uU0;x|9YQ%y_()>(w33 zy1cc~d)%jb_GSM%@QP>Mwo4oLJ?s{m{w$!Pb<@Qw^9-1NmnVfV?0uzR#=q&SkYcFm zn=e+&ug}U?e7uoS(|T`ijm68?St0kM(!O@S?EQQ5Z>^M=pT$JK-OjaJoab-bwg1zz z69Rf``LcIMHEZN9I)7Tz;f7n~rnRqrv)ysta?x%_;d=hJjN<&QciP`Ac1-+v^`p}F zB@r#F4Uc}!O0yO`=sasxQCJx+(}bSJmn_UE z&;RmizE)9URm9in<{m*Z?>2onHuIeF&`_k-b z8WI-^8%oq3Jc~*QS-aPA?cD5p&d%S@3S`+coaW1#Z)mWilySeqp4+_T&)2Hd+^=&{ z6y*Hov@!m`5s8$xlz=l=p9c%BzNsNm%aC$%hS!#yaK@V(oquk4(Nes7vzqy`MYg=5=*4|ky)97!dqrIZT)#%q2D)6 z+vJJC3ah2EdcIfMmIZ$QQ+sVzp<{o??{>3q-*4oXd=6GMVg3Byf8iJN+h20#t&U0H z@Z7N?d)4e&hTC-KuTGPE_T$OwnO|GVpXD=YOsnR(uFd=?aE`mtj#a1A%+8#MI)0}= zE4I!Q3huc}RW`B!teH53i}p%9*nxS^dEqvkO&AvoEDDz9{P{f9%AYtD5isvO7H7#9_KHd#=IT zXmy^><445An%UP|&6yjo5Rudp>C;>9Wp_kxmg|Wvi)Ox)C=|-E+*i|Z$U|*kQCEoQ zt(5iew=G<=%3A)@kLfAb86_qKP4d#J_&?cls`ff%ypa_^3P%L>_dk52hT{#W)i+Wq~N z!#SCUs*@wSUT2+(JzEiYQTzCm?Rt~9?zl0LG3trl+68l?q@Py2-Y3??Q1Q1l-^Q8m zzl?(T+c|kVxOdFn@n*}d%Iz~kdUN&!R#cSP`>*!d`_U};z~xnH+`Y`nr8b+{SBbAX zXV$QJTWK8Y?|l<;R;+$r${$+KB@kQvELimxOH0?kOLYZrZIq3bJ7-Q-4R3Zi>Jqr} z#bucgMz-2@mu|#JRuw__H1IbkyJ~Dfo7$ z`fcgLt9BY2d4Ij%{4(;-)_5<^1vQcpYrOcL1x+ejZ(h0NvB6=oH%xWir)W5=jttzuKWd$qrNx2Y>1Ap zpUtCn%2TlSRBo7oLw)ak3l9s~6y0Kv)*U+APp!Isw083+3!N#38>XC2*!Rh_-9MGf zvDL-d-}i2B!H)Sp%%7N=Z?OLk z4%=^}DC(Z?TG`c7eD?F{PgghSZWQAPH(k;sy)b`Wd&I-}?vs2^e|NpN*;L7PSM!c@ z0hjmX?l$2$d;I-<9&4S|)x2Lfe=hCkzc8m_PN67o;oqfv%KUThinRV+B=Dlz&!KEl z^InIIN^ujH?pnaVympU^0ox1v?F*O9&g9Z(P4_4aR9N>zQa5LBaB|MV@|h|Y>(r{Z z1g}2zn*aWu;PkH&T&Hwc`x6)M+HvKl<>vohIbX%xCT*=a@p}R*=Z;_Xxh+0l?~B)% z%stKT;~{Kkq~W-6-szu*M}c*ojGadT+Ndcb?q`50_Ojz zStVo~eb4;)H^u%36RkSU9zBuak7|0%(HM4vBeC9PKC9rWEoYwY;TD+BIkiK)^=AF7 z%ES-P<=4qK#JKc{sztLnl=T;e$`v(CeRN50dHLNmDZ?hiF=6*p#GxnrzOUDyuWYT75izcv4HLTdFbL6v0#>;0*D>W-;PIqC_ z%XBHUVX=00-^CqU*qZqz@QuRFIsEpIvSJ@f-0NJs{($DmMef$?;u&`D5Q`V`c;f4@ zCUDl~vQUBIf8L84o{4O@BDuBWo9@w*u~}ZLkEzeqN&9qnMZ@9U5wq&=B(Dx<`V z)qSKykK0b#8TRj}bH`Z+rfmnrs_oy)CcS@EeeS(;gr{cVu9ecE%awlkxV^fRFl)l1 ziIE$o@pl_&?f)GYSW-#3dJA9{Wyz(b9>`TW8ex`!RzZ*Op9w=in!+|S{%^w8G{{-+k- zt*E^GOV!-@<(5AT_gFS8f8)yR`2EezlMzeyY1lr0bUO?!feQmZvsaRUG*?O?kbb zhIiD*u0>|QR&XQMv7s;-+XeU*RpzAM7Z;_LdpzBEjp6S_rt{k)*XBD3Fl zIdIS8<=n9C@I~9^qI+NYQlD>ISf!b8_n6rqF5&Z{D{FdMVwY{(IH8ECGGF=Toxe#s zvz}FKvMS{cTw0|O_hxo&)GIdULr48wy$*hBUz=xRYF5S*vG0CBkkLk_ASuPmo1c4K zdCPX^#8VHxAK98`JOY)PyR$Pa`9se-Ge+KG`)#r8TCQKH{?_1sD;C>?%jfeb@pPp3d2ACpBVVtR;F`YQh3SjW5@VHk-=c4Q1&;0FA*gdC!{?JFuS&;TqAY% zg@^UEzZ)NLuRgdhVdu5C;x&O86GUw~3?5vc62&GJJ)L#M(VWW>ET`vGJ`$)8%z6KG zdiCLZYs-^m!`Iv=b zi0o8J-N~0%w6L)-B)?p~+OR`?&6Txsk7Ktg|11$Wz_@s0Tj+iEO$UW7l zaon#kOD^}XA|{p7p4z*%Zheq?LhI3$L-m^&zUpf8q*b1@V%0ty<2|KlU*oz^n>_*E zBD-!iIPO2b;T31r3C+%bd%_NLnNR}^(|PriNP&}Wu+&mIVI@N|T*Z{yOiWEXhs-?hTiu#3eguT8Kg zp{`=Tp1#91m0N~s+to~Sc0J$;n{VP6^(4S|)ra5ysu%dGgfn`VdVbxRVs!Gr>B+5S z&o*nO=DwX(sy3r&tqR9St}qoR#-MHPybr~59BXq#-aL`A=BqUchaT5XVn`5e@Tj4z5KZ8!_?LK3Rfn?NbD_? z6wSTn>od16D((RW-B>x$Te!aXrG{7(M^C#gz_MD4-#u@)aXO}q5S2~s8-MC&~?#AOJ zz5azZ7Ow(d95lSv`y?Z3N@SlGSC6Xj-L8)cN6#@d?sN64m2q453YW)me%&;P_f&Wzo!LnGv}vjEB@Z!Xk9wv>x|pMPFcsqw;uT~-*jLphx)JI6@Ok! zHpPd_%H(?R$BJL=v6HPb_Yx?0pCc!r%p*3dRItU4~DQ4ky4%ym2=2lD9+}GfK(t1MB+4#%t52drUO6xRbbK@kNIx-!1 z$)--~$-17t`GMsh>yvsnLW{NdzwEyws1Q;;^Yf}TTv}6EbLX}HSa8yOr_OyY{vDSV zcXis_soNClaP{@&!*;imZI|x~4%uB1J9T=acOyefjP)Vrl$*N)X6%X7F$@sPDVf%q z|DZ={`HSoCXB8gHlwhpt5Z0@8o^vmIru__VlhDHz|87Pe&ULK@_b-wrN7v^iZ$EBrA{uJCB5jEC-a1+rdfAgzdh8|RAOLR-KFPn zEtTu!b(sS!aWj(QF^zB)) z!18?WJtKp=cV~*9Ff4j~DDA6(i|KEnr~f!LjX2a_EG?7NFVVPoZ_mf(XpZ!Er;h#0 zX_4C%`rzDxp3@To!c$E$MRz^VPg*X$L0V5c>9qMPo9{_=pF<)X4}bMya%vDT_IPF% zv3W|@x?geoy#2$z?5odLysGiwc**%+rxVYfo}6;rR`aFtj#o_LSG1a69FScn`O;vQ zXG3S3xy6?iGi$S_yuLg6vUsen#m>sNGnV(B;7-xracS1O20m}bv(M$qW9Qo>AK9E5 zcG2)_&-F>{!8KR^8c(>oj@pc^2;~hRN!yj;(2jmUQgjQJXe#qcvZyT8F4nh)}P&ERDR1L%wkHP_}uQi%}ZSqH%0yV zwM1L_%fzYP$@f}j_|HDVuqEfPK~{3y%9Rh^bMEkw<~E;wPVt}7^`MlpTjf8WEs?rA zbIZ=P?_Mu{HTm|0a=xpT46h@~?k8>f;ZS?bEGulw{(?r++DFxz?dQXqo@K$eJ!*#`V{|Q57>pkxu*<93h{uSw! zDr#m|`$%tJRM*NTMn7>W<)aUz9u(Z2P@QCRBecjlP9^1Rc2Uwy%< z4Z8~0cyCK}Zn(8Cx@~6ED^2s*NArI@49b#x620ciJLiP@DRL5zcSNr4nxfC^)qk{D zDDz^ti5P#;ry~nxt#G2Qe34d)VXUzB%*)B1AhrQAd^<9&{DMm%|xxRbSlY6d1 zLt1TLZn;Ho^%LP^mb0&Bt$2TM<%^c>%2NIP&t$Ify52qa^NM@)KH=Q*b3A8b1NCQ| zs`2!-+7o7f&Ua6`Q*Nc!^54@Mzg%ZdII_hz=GI|t^$GO`*)128&l~9!E{W>aP&+K< ztF=V=jej@2PU@aZ6I$JE7wC(ox@lez@b{0Km74X@(q!r+KfkxK51Bun z+&-cH`V7H2u~R;(^Pdfxm;R#KB!1l=rI-?~je9TaoUU)46n${V5-VPj`TxT@_}{%) zqshMOcJ~~C+D?gFQ;nx*SeTBnC$BLnIudcW`!jR8M7wr3lY#UnuW+?IiL3thf-mB~ zRi1srX61XuiLr@x6VEM&qV9j zJ)O{#%K`!Jt1 z6792eTW_`2&imAApQ7H$x?047=imFP(2vWicQghy`LItu=JraMd$!&CE9Uygn3&@i zWL{Wi6qBTAm%MPZ?dLOM7qt)Xn{C0!w$X9L54P>GKF)h9!rfRjnryGkwa)n`zcs=; zIj?Kg(;KW0SWnkoI#AHO{bsJ=_t%#Vqn`>YC2J(C^HFy>ySdcrZ0?+tilgh=SM;jt z-sno2t^473rP&ARh}fXqObv!N8V~e;z50FRs7}>Ag)3T?_CH=LdC)d6St2`Kxw&keJ3W14p?mbKN5uIB1(xf|s zS+8uaQ|m3+7n&ZDve)iDV*FVq(tp%gUhV8&Rb%#=(89!krRpyi#$JBw+?KANVEsCG z-D8uMgEBY03al<(`+Yq5V)Z0(^U8};ja3W`o~N#1cyprvm1?g3+ZDB2g8yWjsQn2p zs?DfbcQNb!2I1=sC$E{S9}M)YaG!9s;*-mD6~~Ts4{Q$PE%r%Umt+6+f9!Mo;r+8C1K0b_L zxhE@h_;u~ltqa~rF1pANWVN=%n!hagZPt-TljeIea?D$y(D!cAp@6@JdrE#c=yU}> zzsvex)L43({Dz>uiT5pke%l#x=(g(lt}|0|Bv1VKDd^lA93nOHB)2 z$LlS2xc%bL+iRBJMI3}?UU}p+*X+$UZ8Levz7@x8&wTg&?UJUWcK=GUVfD+OX*I?( zuc>dEeL*t!W+*G?QdY*zrZy7~JP2NyHM!y6)oVR#o)|d_91)zIvT4I9d(%eyS0arv zO~)UaaJ@_VpLOzD?VH8ci`NM0eZSLr$b|9KkH*(4P6?T|@3B07x9bYa)g(P>4+Rk? z_M{bWl{Y?`ar5gO>syy@ryE4uORiM=IA2fgaMEP<piI%}DxKbtS1706@*gBK%+L7~LXI=I2dOd3w*YrC}6|7$@ zmiYTKle6%-tio%*3R9Fxc6Ivp=ftny^OEB>+e`@!V#q+F3V z^@wTk$IilF$yb#O-0epzvcKB5i-y{U`)uFiV!xYdO0#kH_f5rNxA?DKb~rIF^X7_- z+b)fFj|!b^bv|YMapLL)-C2uW54M%XuRYIs%VO0DUXk$oRYiPHDx=%y&zckRq&mdnf^K9pY#5QUpZItCq`t^ z(=K<1An!@#Rk0OT3A;QDY{e#toKBICFgsq(RM@i5_6r%qwkXsT)HFtx#-Edmu555<6Rir z9oFXWzV?>=<;>{0Qdy?E(>`wBrk1r~hqchhB8h`}>2WzlmMRA&n{%vh9{C;jDq?cm zJgb_QeBf}?bi~_KkmBh z`%!-Nk7(`Vi4t8G&4Mm+`1R~IY)e`C|Ec$hIrGmhoFRYtAy@a-R}2$(-J2Zf^=_7c_G_zeITD2Hxji7-I+QcvyMwq=*U8&9oO`X^ zJ+;c$DGKHGmoB!A=LUTSFCCLivYqIG?f=lwRZ&5vgOf4DhU@W>IP1WuV~pV*vJ ze@>qJY$bTe_1mWVz2QF>Rxu0TXEe>f$t?4He#uUbb^o3_?_Z;_vf}f;h^_hoGk?7a z`Rbi|Kl!GKVn$%xjW@z_b66X$wsWya^S!l?l!!Af53v9A(aG5DuzI&g^=T!^dfBD5 zoVngYvlczts9o1IQ7Sd!!(NuoEt-?It;yefk9p!Q%XbRl?;rS?re9xn<-QfyXB)HG zciqIou6hU8eW{Y3t28-GrBnaK5BI}ICq3I{tGE3A#EXxbXZ|_3>9GwmyH+tc_?=E2z`E*6KLy)kp1FPm9=KE@Ea!3f=v7AT6(`4aY_lshSV%t4+Ebcwrv3AFXOP?qHasIyR zYI^7M8%Magtv_onJZj}$$@a)5Ynf`?$-w5?Q(m>dFNiwIF1YK^f62P=BXiM~JU`~O z3=@Ce-Pb1e^S^|dlSsq4y$hdu-0i=;S)%V|b@0_o(=0B&3Mfx9*5%@xk`!r{w$t>; zVW*?*Tn)1XZxhru5Z`}BD zLPn*!qh-C<>b2`^j_i!!k4+Mf>57Q!Ns)WHl~MH55xzjf)C zr4@P7Zc&k^9x}|-x?ufn_nNpfo1@FbuAY{vUbUGiKS?!T{fgKXmjkPt{U<;CaI0+9 zoBYKQkA3eo-q}!hw7yo~l;np?t3Lk{)mhP}8yRp+Ko zCCj;0%`ty}CoV|sP5<@W{kQyz*HUkS-kVu9?Tnild%IMepXbBk#Wp@KkLt;vv^W~| zC$sR*U6Ul^gk>+;J3m(`YsNpgwD8$aw|@&SJU?N))YV(7YmZ@I@0IWXHG`)c_!k&T z-SeCIZ?B)onc_lmk1c^lowBPx+D?~wZ}sbGcB$A=#oDhLAJjI#YVqFMl=0;(Pf+BG z(1?cM{ds5n-?RP5Q;a`jQuXRA->0r!%YX6hzROqK$EuaqcH%kX6zj(GBK`UiHBXLc z&R|_(^6RhqqGg8?9-F%#k1{@Lz3lCU&CH9nl_!7GcD=z7`bT5I%y#qnSA*9b_ED{I zzM>sgb$FH8;b^<)tAC#>&G!2`W2?B#tKM}%&fCjvu4SJNI+M1gVo!ym>*m)ckjs%-q!lPCqW*u$NahzVyUW zJE6t0?A85Kb-_%Tvy0ZO{;VkXB=yZT5f#Tv=3acSn9M_t^1gk&S9$HdgF!2^3hHXk zocMJ%{=*!jwsjwuUEy3QylLmTAW6rwhmW4R%GPzIh(W02NRNX2pWm!XrF<&`FD$P} zJSn=$BjfrXw(kP9+b&!ZdF-84v{-C%UN6`B$*UU7k>ihyqP%y6$?Uil>%W7sf2GC2h6J{> z=^{t^60FxhDLa(1;WD46(ZXdfZEu^|?z_Hi;wKjWTiG{G?wchcTcfe?e1i9f_vgY5 zIs4leJj|b^=ISVT=r3E)^(xbw(k5>|uJe|Q*46c9vtKgpPPXZji*>bD<=Phm7<~%v z&M@YeZ_&D%d}rC|tAav4m)C@ADKCmW@s0ay-~n6lNY?LDeW%&aUcX$m>B}OEqJ>L) zK1n$%bKDRxo9}(XBh=b?uh7+XR-avYR-d@KV&C;wE_;tiJG$+#S?Arb;>?onAF&JO zHwD%6Oih0~^Vkk+-aD66IQd*-?Vl#(MQg7M_4?5`?caikO*UKP&TYPLE}DDh^oyfP z(r!8n4OTsF^e%g1Z@jDXvtro^*@GLGZhMrk{z^ad$}b74iPxH#|8J-Zn)*e^b5icQ zq{aUj6*lTs`^{-_?biO`!xFqE^Kyvl^ZO?seVny;v3KA5DQ`9{o+34~cHYKr$-P!? ze=hILnsP&M#%7X%T=8wnVu_s_8fH#^91&&OzWYjQpPS+S zdw-tRavC0)sD+R?TJ4ULwb{}%H;?Ns(q&g=S?%Df>XC9tz|?Uq;CoBRqr z!sV97J=>*-FNukJoxoY z%&$n*Zdvhb#}@>s^sa7=-|KvAPsPEt6V~l1HNGyN^Low`ZvK;h6s{fM`gQM??N{b| z`WagkjGk}U75nF-=m~*$A7`nb{#m)BJ8Zi6+SNY}%@(i-r^pK_`jWzo{8owj(pv&Zv4CY-RJn7yK?X4s#j+&=(@?- zc3aMQ_Piy|{LFpF^HUBw@0oZgX`Zu@s7sgXqOI!%gA^)X7a#ntS+`~$qffVe!tF2i zn<4{WG#j<8uVtHD(gl zrc2K8MzrhiMSI@v*=;nP{k6o=9VdGPD*{&sdEN>#(3QTnZ%db2Qvc5bi|r(wCo?*n z4OF;!&Ed)JXZmym_uA|NLa>sVxp~uD-4Rv*4+wPFz`|5M!apJojh$^Gs#j z3^x2fwK%;=jz^;MS@@GRf`M~(i5`FDdb^90+4tG8j#a0NJj-F3#VAr9e?c< zIK4zn=J2XUo_KiZ+tvR*p zUodDE+<5twd#RSnE2r9IKiAM%zMecio6F>~*`{u^Q+N{e|qIMTY&E2MdVS&b~L!76tSM!|P$T4Bs+{T**rHef=XM zvf+CC7kRTiNsmr5^j`DRKC+b6wIf!JV{yf-S5Pn_{*|9 zO7W(XS1wbwKfJ0{qjSl{tN-lHxssM$+xY0@YHv2qAlbZl1)Yb{#~5mk?KxxJTVP~+ z@s{rQ>t|XXD12J*C6-m|P}$VIGBRsq3%=N`2(no5)=5@6FM6q)`lB^_UyGXj^{8m@ zSdh0X%yM^QyF%e#bq4>W)3K1cles!RG<_xztDky`IumGL#S$7Z_jF?lO}p=b~JbFOR$Z%;n) zY1_47<$C_QS?(CKGX1Ca5B{n-&UQOos;+R~XS$Hr(F5jTo zW#{x&`sa7=y6a@@#xP&Om1XINV zeoC4A*zZ5>(!1wE?zw_RbJx~stLd9o25gr;)~k9hl2_O9YJ+L2{_lTs&9Comz0j|< zoVoM1U-bFozBi+;mq_|o-Bj@syEDOI%9~{$RxI&YS)(YFxQf#~V&(1WcS4VEP!9KA z*;4KB|i+L@0J->k@Ap}Fa{!SfGGcE9`lxvluYN;C1rS8`VTn15oyfx7P(*^~iqcbOgTJSTi=C1WF4kn1TeNH=gJ*+E zsX?_iUg7gYln@$GnzGMshbo7Mr zw!RkW)HPS6Ca<4#q#$sy!V{jtQ+p2hF7H?vJcm=c^zWS_y_NS%iVrJt-kh_d^^V7r zJJ}`+j~x_xdiq0$Kr~0Hgw`*{my=6GW}fRfaVK5c$jkj&seQ4sd&4rbqK%@HE--zX zec*t__H1W1qw}sYTfWMkSdy|>`H$|_)@Qk9zcd?;EwDA^oOx`ufv!zgV>{FFD?Rs@ z6l-W&%DBzpxNV$#@SxHZ(fYR=pQ%Qv$nZD#6x>tO42xDez!C6_?Iyz;8P|0y>pwVX za(&OtI$WY^x$8(k`LWpg$Pq!gOhAOS$NaSJTC= z=v|EM+L-;T?%?AqrKdJePIuXt_}_swVA{2XGgfgOsek&1O>;?Wylhmj(-b}B=1Y># z-~Q!MNz4g*Dj8rteR=xjJ!|ZGy4j~~KbQaVlF|RB^N0Thinpd~bLdOyUv*DA@@FfT z%z5*>^OJj>JGgF_b6bd~bJwdXnIvtB-x4fs5Wta8bM4A|1G|-{c@!e@r!y}*U%24Y z{$nRtTt%{s+gkUUw)H4ppK*^Z|EKknnl}vrT~R6PHdTaWpMEs4?9cAF#f&M(i#|W^ ze00h7@guilQ!)M{6(8N*hVyJ3b$uouytO*z=-Ggv*A_*G zG!Hw}RRr<3-aU3r_OGYk{~Z$|IS*tqIUHqeyWM;I#Fe0J>Q*5(OW1BrJ}gpU%T}4O z;o-AZj_-B5v~)t3*Sq=$$V@#DB3a_$^W@pA-DRyh-p22`%Q++6Kkisw%dvOsk_7o6 z_jOCdmaMBz(@J8<4}B!C$@tH=z%>&eXx_T|_o}4Ve(8y`l&37_Xso}ic_FRt)^F&s6^Xb3w*jd{XCmb+oGlJ88i0ECA^(biH1F#5GZ%GnewWp{WSc0i*|}xfOl6<6rhtx5k=_Bu zjc3+&t`A90-x^+!yD?xx$-Aws@8<2`ou$=zOYDdj*KVr?#%d+KPqUO+{3B)L&Tsbr zYq#cf+!Jq)Qsso6C$5Wbu)8~@@x2j?e>hzt&-H_xSiaszU5_kjg@u*Yi+S#s@Pscc zzjgQEgj}tYZyZZfVqV<2@b}8=f{*`RsO^0IXNLPdp^t|jyt=t%-`Oue^SZ6qNG@4_ z#U;{LS#!Zb6`PDGIrGYf$Yu9eN-f-$_BTxS)Ti3l_a_GQ{QbCzDP5qQ`B1os#f8o;CXE;An=KE|_`U5}lGmhzI+Bi8 z=U2KeO}hG<*}S5W<6!*91MZuq@bs;^6_nsM!6|RzzL5B) z*Og_PIh7x*bX}5q&~yGG%d&|5{kHjQCRcBNmcKas&Dqn9wHEs66Ej1$Xf*3ivY(jc zdT{FO?FHxbA_{pgPwR_{c(HlmL6b$L4}VlYjS#r-M=&9_@3f|>;;cKIE59AjOTQVT za;dg^R)4H>$w|?>((hhA>^bta|GMM`lOjj`g4uI+97{NROGfWn8~^6hRtpMEKeIL} z)o$K1Q`fN0`Iix+%dAD(ydG*$Wn@`?Bs=zwK+@Utqx_xt{xA zr1$1R)wL!v&wUv-PQG;ZR$!tE>&u-_KNj2<->h{*i0SXmHbc?3>c{1G7oJQo2)*st z=V%wVQ2p8mo}Xt7oR&XY%g-z^_2@aD?>jGW%+>W=q&iK?+N*Kcbps^HiSS3iVTeT=`_r9=FzB)@{z*g&xfM zY1thgBL3*-Z%Dhvnmqk+^1E+euD)BvpmC?Dll#K2)Fz`_D`VPAcHQN$h{-4?lh7dGJzy1@}eCO&?QMTV?pwPE;qi2y>k9JkFOm$EMPzH zgpB>g7xT4$ZqU8U@1x7N%WOeIs7sySO2$18<`%oWE?m_<>5Q^$b4LWf-R|udmu))d zZ}ocGndOl#Y%}=NXLqYIoSr%JWY^`qxielI>D>^0(CX)e)QyRK=?cm}{L~b0OMI(x zu@t_SMQt?q)y7-^M;WcNwKMP0idb{~qqnly>iJ~*EU5E0y<{#3i zetvE-cTK@j@!Gm{Lk5@naHorDmqq5zVAT9l9?l+AyZ;dX!W*^R6An}#4Jr6~sV+Xi zMYnA3?S~9y%jVj?;n~h_tHbkmO>g&9=}k3LB))BVCAy(M*ROs5mfi&yiasnmb0jLi z>Qu4EM59|jUo~sU7fm(W;gl_M{1Mu~On4jw*`Anc>@Bx|3q$h|#nWqgaOmYjYs z*WEs0?`!+M@*W$ez8L`pVzrjM+c%qxCh=hrgOC4_)V+%{dCy&Jaaz4MWa`3Zr=U&Zo@_^U|7?%)-EllW?#}`}onJQV zn(lwzSzPBqiw zbJa@>&smSV{J!a@GW+{AKSzd={ER-Eo?CJM-mhjpv7zNo@heeRXTwa+bt#9Y%3Lgw znt#lAnX~Hl)OYsX&$c|=y4<1Z#$6Hh?;JUN`g7SH9Y1vC`0LGgM2eT3oftoT+n*_Q z9uF$YZ}Zrd-`E&_&}3p(=6Y@&u~$_IdcBN{EyX(ozX-hiaO>ug`xjpd7?-#hub%y) zETXI+sN{0nUycTYvLh!y)+~FgK;{>|3B|3pZakkkJ^K`zPq?feT7`M;8aqY@c$UB{F!=u5-pW&TM|- z;i-8bJn4sD%zuq8vAt*2^sc*dGM%>W6+QPN`tqxTWgD_zH0`)P$4~zO_dQvrE9?_C z7TY-Nl{pnQomFK^%?#tUTSZ^9*!65)=4`uS+AQ8LD{Wtvs+QiC?Cg;1@11&b9&1XJ zr>cTSD}%Hi|0>V=lTWv7YPxvZiJ2#R=6oiW4$qLEs}}A}x#@L0`Om{=I*0k4R_3^f zw_K>6KmUrSl>pm@l*Jw``_69R{(D)HKdR_Trp{t&!7C%4dAY4UnIROJ4RD;dO6R&w0xtI z5(U<^j}|`nR^$nh{(7s(<(In7 z;moTw{Hy-nTNQd{`rOEw{BH_GS5C|0cbu|oS;y%r?WT*;8-A{1&Uw*kAHftXuBx}_ z-}Cn;wwF#k>i#a^E1P!gNk;a9_lY|X91XSMoZjucXy!3PW2SFKsvNOB``*`OxY!-y8E&R@?DbTs>mCFeu9dRv&9r`Aksym&-PVl{m*rxTSY2T*#%L`e59SGSb zBxrs^+HB8(FWkPD-0sP3Z#kuVxwv}6p7thDF{^zy^H?4pP}o@Kr6hQF<)TTt3vXRq z*7HMCA&JpsujurvS6)kgW?m%Drar;-o9e6zr#&$yS3Q4wRvx+fvp7rNLY18$pUTenO{Y2yb`ys`D4}u599X}C#Y?`J#C@V;ggI}Z`8xTH0$lY%fRBc zTZeg5SEgo& zc-S754`6FP`C{MZb>6ET7hH?C`I@${=9rs|)y&7MmtIMo*RW6XzV-XWPk9dCQZ4__ znXK|PG@#}An!n4Z`)qztc)G4o_SEeUt*M=HWwt_DH9gPWo{9-=czpHDB;T3e9H!~H zUY@Z@ZTenA#!ovXQ^h*B-U{%0e}Hve^J-s_<#!C9&voo_Iag=Wwew1}#vJoZe#c{p z0jxUWeC}y?o|(qfr8Tzx6EpBXWc%%8ciX1MIt8ovkFQ@n33)Ze)osh6z|&gKxTS7g zdT{N2p%M4NQ&qwrmZflc-@bF^%%*p9LPD7D>3qsHPQBP8n6K7%Htp26s|f2OMK4jT(Rt6uYaS{rZv8vDc@zYFFI}icVNei&943z1i$_@k8S4; zm|tahV6r^Jn#k2s7hbJB;Pd@!((<1Q6YQ65TrBda&u7ME9shD)eCS~KdP}{xPSiu1|0lm%z0ay%2XSV-yejRo z!y97OpNv^I_e9T;?FnzD$(PA2mhe$cT9;k7G40vAwe5*tj!TMNZ>~AQdGDcxT)o_u z?j5Iu0&~p1$_M;PIh(D~`{c;RCV37m3z_APy8m)ZE}VKe@qgFM&7DWvl)Kiey&hT^949IvX2Z!ua`x7yu*Z(DA-TR5gNT?OgHmzv(!_OE1D?Ob@`&dnu)Z&aPNrW#qy z(~8pE_2pgulkkJ$7oWtm{koEw&KJq_?1KBspfh0wJ2tm(IFQZvP@?SlIrdrS@@6j9 zJ(Qu!3Xx`#mdm$G!Zb z{-Jeq#i9fOt`?yHhRoR$7C9+u1PLry_GNB1-(J-ECX%}IYfakbZI033l$==mXD|OAZz$9@xmi^FN901+oos&|P3*pxnqd6D zruN6QTIBUOkz#ceDPN-c+ThQ-gnQPj(L#{cykJ=&wJWJ=OD-4m+$)U(w8c z|G>HR!q22`X1N^~eRS5}Tu()&YZYHSI5@xd}exrbf#}Y zR9X9z)<8W=vx48O{ZDWAHt?J7<%zJoHL*+0#GmU}_}2GFtIjf7ZnF#cv07pJJg(Zj z8GiSev8Y!}`Q~;{5rBCufM2Ik=!vJpDh+-Dq$ zJ!^d}A&v9a?o+`D0s-$58S1W05zwE&UlbA?X5Yy9FLPPnek&e7Vi^& zPwVrX9htvFGM(D0`c*#td?oGVob0108+TUcQwsN59%U7)fNBA)jT0{T-FTU~r8-nd zNP#VEW3`3w1?Sa!RrZ~5lU458?3?uHxZe%iU+fvZeoME+_Gzl$R-E!`Zu&*f6;dJF zu4gT|TCD$N!s#Aer{CUI%zv{Vo)=l_8Y%cDS3S+~j%37dXXo9`-vo z;p#f!rllvUWX+r>I&3ftEmsL!BJ@^g?v(esA7z|0-&)n2Q{dhof7-R)I%B!?hTH$S z9ux#y&p7lvVn3(s<6Dl$QbL3ZQx|>z`$_c5gYaX^xc7v+tiNX@8S*pYiB8MjMK8T( zZ;X00_uq^o9{GEwIs{GXf2pjvV_#*Kaq1G!sf@)lSk^_wa+gc4IT96dch5~`F4ZkY zRe#ySHZry)?035_8Tm0_*#_r(0y~adWoW%H`nv68*vFgJCT|YhiPO1tkbQOKKQlkiwStp!%Y90vIBy1`(;^pM(=$P>5k6=}T#*=#WI2p@BYNZ9a zHm(~B6T+EK`(;Om$W34Wkh{#G*Y3lOOAn^s%?O$+ezH7q_Wf^n?W7kT?d+a8{o^l* zODhf6%4quRonRhV__Ql()1h)X`%m{}4_BQO{hB&OYU*3wm!;3{EPvLW8p}O-qST7& z9s1IfPbPHhO)6J8Hv5aftD-rxKiu$KC%1cr_f}K!nV;sdGnJ++|2w_Bb*q?#z{18} zm)yLx)(0)8A{V~h$v?w?YsaUai>yi)|2}4Ne<*p*X$rU1cV4xPl@a`&i}!R+n(}3d zpodeB_JX?%=`njUG!9<~3v0Da(VEhpKUs0Yt?IjLGQ-tBPY)DTsQg|qJ@uW!>6vT3 zxYVdvi_JNeUL`a==8G<0_+*2g@cUDAR_^GPKIngH+X9h$#@zNnr;b=|vGP1udAvEt zyz{~C)k>CcYvyljx%=!uLx7IQVM{lzKX0$+cRiWF7Wwn*sfM72J>~oLR{T+4r`3OU z$;yP%t_$h2B=Yx)ax`^$tUGXY!M61ijJ)e-xj+6Qj|}qo?mgy?zu@xUeRXD%fx$JmV;?1Q(#t&-NA^q?VsSdMOHbkI zhQh_y3}Zc75AJt-$g``(f3vQ0(lP;7{j}xX8=kZ$?mZg%;XSL&l$`XgRllY$Y5E+S zt5>>HJ$6H}Z`Hl4nhaY$U0N5(Fk#Mnv47{HjI9C{eX2BU6Wpz2>pE0LFJ>j56G;|& zy57L=nBz5@Uga1Y2L%JE+1K+UD_T<>82HM&xjy=|i_1P-b=Y9dPLCz;l1krr{+6%# zYJ4K-Q}_8En)i^}qE}WY`W@{gUtW5eB4*=uaVyJ{e;XG` zS+cYFFWAceJi4;(2k$PPp9fAjTz(jC=qVq5Vb?~bZMU~;zkj>Aq4jTo!ea&IRUR9Q z|7YZvAf}oz>3FHzCDOz@TfO8n65lLy4J<|e`j}fj;yxL_Oz!7#cgs`Dc)Aa6 zoswSpVrpR6pIbSbJdQ84pY_7>{Az|5?W>FLtc+~?GG(U39*KX^2`o9sFa3Ar_ulzmtefrT@|L9A@@9m;TPe~kQ_&z7$YE+8veZJ%Z=fA6;eOP+>N%U@& z?N3*HKRx|T#>8y9sdmqOzG~XKh+kOba;f@w{3$zWU$eN1hISX$8@KdoU)5Z2H(IFl za?SjT%e%K3%@9y{u9k4ac~!s(CVqcQWC%YJtS z`u0wVw)&d3=-b{o=ePoI+pQ_>m)9|pRxHtW{ih`!+)s<^$3^zt8g$Tbb;b6U6_d{9*lqgEdG5r_E6Zh7 zamdcgO3v4nYF=q7isu7Z_3M^>k0H1sp}-&^GJ zD3kZr2j!!Je{J@&RYWXMa9Et=c}~vsnPEAay$LEhhMr5d)xK@4u_nLnIvMW zEMGsnAa4<2x^S+rPs}V?TVst(jr6QI>wPYH-G^(x=Itn0 zYGiawWnSukS0Dd1a?izn*8N|AD))yXMp_nQXuGe@WaXDJ}LI^L@>gniuYV zx+_p+KXaIF@I;(OxBeE9fb<09$W6NXbN*B)PV&9dq8obDe*nNL4& z5r663YIBU0sq3S+y5PPCwUG)YJH$Syd%k~KG$nq6so3{3{t|*qJ9i0Q^5yRT_p4*V z5uO8&X1|=1dU4I9Z%VQH%MD(B7kPA{Va4=YlH1yjhCAN(YOcC{+2u}$1n1o!sirTV zTef#Ccdi!6HqcKCzqQD-_VCT*=W}ejZtgNqU;VjU$?0Iq)KjNdUJbJN-FBt+*=q3# zLi-yxsNc!qYkDSRr9VIJ^^IWW4~vbqWO1+3tM3z-CUE5U!;3{`H`CXsdG)M_Xtcd6 zn;MtDxt#Bfh23+BG{%IT5t2cFnplbqTQ=-Ia3l2m`PZ9kezheeRjpdIx93eU=Odea zD|c-^#%1DWx``v8?);3$(P^3A6?O!D*WEuer^B`4#rXqQw!goy@TdB6UjJSvJ&9@A z$4*an_DOlcaJ!Xr#>o`>C0BpCSbO>9$IXj4t-UO{QfJ0nf3Br7`}j{xP!Lg_dGh@| zVSV2fpXQ%3Y!77NTK3C*%IfHU-P7M}Yc^Q(Mt8ySD{dDa7mA2q)8D4N^3l>=OjGi1 z6~6qh;5|+B(8IEmoBccVw2t_n+Ud$u*xK}Ho9Zi3&ple9-b-TF@XUX^X!X-4JA#ew z{&Ow;@Urc4pY&8E#WSa3LU!yco%gBnxcU5E>uU#3g?yd6^ir=)daU-svqg7QvM&EH zd~;c6pW|)Y-_tf2cYlz0lI6D1!fdB^#Qwfz-*@Ff+>Gnu>&~u;Rj9$YS|2U^)BVNwj>=!9b3cB|6s(dy zJ7r#xyW{n?;}`msQ&R0pt9Gg`6YflXcYWyu_6a>U;gLI!wVuDtGV#H*2|;zAk6fJ- z#pZKoj%DFmzKoTfSF*Z2&T=gX-oIDcoBeNKW}fEPfJ(E@g%wVpZhupl+Q7|m(OE#B zB{u5C>4#fqy`C2_UFOQ$e|m?cR<*v|d?>bF#+XMm|JlKw4SzR3EK!)k%A=Ddyt8=C zd$H9AJ0`|O>z(y7HqQ>3rMYa!YT-NRDdbe*Bv^8wpe`PRyb9_g>d^1bzGsM0 z+9}$cAGFt1afW^F{N6rQ{M^a5QwjNZ<0MSGxc4r0^vrHO^GL0F$!EV7Gdsbe!`m-k z4q>y>u)e(R?{WQmY#(37eQweApPP{N<$~|UsnV6jyef_s5Aw z*$z*ppi|;c%@e2DPuMvj*KK{B_JxC$61(}No@=c%(GzHL{oVAe>huFHw!n)5Hs>z2 zvTiDgoclO#+kBqZgY)LL{GM-c+f!BO{0}2-r{y|XjHh;3K9Aa77vr#GhV%Chf7{mF%}m&y_r4 zXH}jlb$Qdm$!S*|*38)h1o4g$iii^ zqTQa4WoMK^mu}Ep_iu5jcFV^_UGuqO+5RVXsoOpNnzUAf`Q`_$GZT+rYJGU&k&M!r zGA_m^oi6J8&sVRybwF?btF51xye#GIFtYrxbEA~pe&G*~wG>ou-r3_b&Chz@mmcYA zk*wUQ{_Tr-Cr3|bPK*spYOa3GX4Skf-^4TL_Pv(R0d9U_6>YmWHJ?8H;LPC|z2C3+JCY4GpU=6~ zcuw9yKRxZ@GM>JQeHWQ6)>cw5` zoi}1LcmI|hx(|eHe7*(MbjU_AzSsM?XmOV85&I_`A$P3=B-I4vTunHI5)EGl#dYfL zJgX#g(du+o|5AMk>lIOn<$~P#?sGi%%xb&*Ae8rf&C8<6*F1kTCR@rHl(SG_K`*s_XqZt3PY^(p!P-We6?#++GtbOP7Z!+ARKo^R~lmK>6J7Jm7k z@7GhjhxVzLUA{SGvtNw!q)jFbirzOL_3vGEwCj@n8NX+m^;1;VpWWD?=pR}vld$Ij zvuMoANhwQ~sCj-CmG=7CbvS0mlFzCi!~bzewDk+we%bzn-%sC*ud_eT{PDq?M|~Bn zJ{31kx$`se(__VIqkWT}6vyvU$!|Y5JK$mJvuB4*%L@%&Uo9=XlU}xL0+ZSykuLt5 zVf9Imrt)*mzT0x;RjuyClI%X|6SsYSeeT-`fI^c zcBAMS{y`j9jylh_xtyR8z9gddlu5($lN)&3%S_YTztZikm&c`-KZ|8fI$E|Hsj4U? zCvNcdKXL8CsaCapSLX!Vl<}4Ivd;7@DBIChq4T3eFRwYkZ~G$OIe)#L3awo9@n-7h zl%`7Kd5bNi+yq2V2fFU~v~K5}Ka2g3EJ~R9R63aHc)~G>2`+|gegd2-iK%?;_Lp~L zt$zH{``Lnn@6)TFoe_Mw{PptnHCUz8!O^ zKQ-LlPNtS~@u$fljFSIegchA}zq%~7smsD3hj0INZgo>W_SJLu7oA^s&G5a5ILC&w z$3qNlK5;BMc{3#{Wv2X#W`{qCyb5W-ep9>myu9^R*!$+QxX8KuiaVZVp1T!4djeZ= z!kzYar$jSu3C%CQ^YMzi08@j@qw~VLe-6F6V{fmkee&ypZ5~Z)^>YJbl8kslFKxQ> z|98QyIi|l&-@b~Nkliyew0mk#*B_Rta!eK8pG<95b0iws*kENw+=4O1k>+QpCQ~&Jf<3sCo z{}qZq*i-fBm?BFuqdQ~!+m%Nuo(r>2bzM?9Zy}S?@vf*M#fJG(m*16KTcF8d*=ygp zqsIK=75f#RYmR$S6g-K#CQH!1zY3u z3A*0`P57mRZ4Ss+D&#*&PUKpq&~d-0oaHEg_ll1%j842(?we_$pKG4<Htp5TgPd@eY;(=E)lD>+Rzb!mC(|C>KdxtQ_Zr6pG z#(W3vaeWmCzEStwwL$T~kIS0`dL0iPW4V}kBsag$V*Ss9x%WTbt~H$afN$o-d+Xx= zZ2ii%%i&%4mX%kf?j4cqT=}rQc!%{hsb4uutX(3u2F5T?%{f)dr1ZNbb*Adn${oiG zs~6kwzOVbPxv+EfOzjCrN*3#g?6_q1(0}Wp-?ukTwG6doJH0+(dD|6>dY8DV;%r}K z9paCu^;b_^srF!D(1qJYYpz6P`AZ0wG%T`YRZz!<@=m&PW8GS zK1c4-myG4BHdX7{r#DK)Jy&7e3T3mTg`1;P-mY1^!y!czjQ^Em^o) z|LaUgk(6E65>~HG=hFSMn4RsW&Hv*IKl?vRdZ7Cww00@eOtbJ`?(;8BI&_u$p=nJ> z<>ghGa~FLoQ2I5o^w8>-1+fv5J+V8ssBl{>n1A!$LH(}8Bfs89xPPDDsuAJvD%j@D zmc5^Rr{$hI{lg%Mm%B&t_wJ5TrE>Y+>lu7(*}e%(kxE{5gS9)YmA2^UVz|(It}{wmj{y znU;9@?%svid|!?xFVZNa7uNqHwWUs0I+%V2Rx(BpI8H=YlB zo8Z7S??B8n2M@RYx$WiCLrVF+gclfB&r~QrbAZEZ)z9^F{T8Km*>LDsM7k$!xO?-Y zWuwD|$g2Lzw7yxZR~@~nxA#@rD$a&Xk3%t@X7@$6?zH2b;`Z{^w|dUylNY)r&9c8D zZoYu$){oE6>JC|boLV*eiSQQT*9JM>e1Y2`WZ8HAsopeMcUx1FRlJ(% z9vS~T0>AHluX{g5O5n$26M?;faSKm3ws|n`<6#sD$nQJVxMyQh#KLoz1frA{J4Gpc z>#)o@vg8)`y3@zIZ+JX5Ef1M>{Eck*bHjeVQ0aG;%%|;+^?beFSDYtY<8kf6k*{~} zL@wFDH+#+T=oy+$S<5($zh^|-2_<%_bteQ^7PU`p`jI_I-Mf@>?#yI4`E$QhP4V^z zCm1e&>~FudMe&cCNO|(%%_7~cH5M5)wzV8vonvo`7q2S{PM!QT;=S4BwZ~*qj;cE7 z|CwlZ>DJ+hLx!78I_|sbS$ZV2ZLAHM``^87vK_zKWQpgCc8l9zixs)J^Zm54e+M^T zKP0 zsd1;N%HJ6FXZgP$=)0f#vg?(3;vI+gC7Ktf3dH#Q``(jkJ(=sv3*R{xnSZM8)E9|; z8oM<7TLk}?EnZzG=C($i=iA73H0fdIvsH{AtR&a!%HHN!@^!kvo2qCRF0t1})a7|Cc;-zP!>dg6XMF_ARro$K%=CYJSbSUUftMxW_l`ho|oww9e#i=KA9qq#5~|`9b#ceP50#SLN1p z6#s2Jlt2GMW{8Ajs`h;`*8P|Mum4xMvY4AUbFOr{9eY6K-m8-O z7j>^*PKy0{{vq4HJrA@4mOYDMGGP9CV&~N#%!_U6a;`Xq_HX37W&QaygJ$HENB2HY z^L-F;mOpvI%AD_g6Hl+~6j?t{zW8d;=69(Jz6hC!UpT{`yrr1s%KjU#pVVKgPE@}0 zU+`Spn#XIV)buAeELrJ%jJHJPGP=&*iX}JALrt0bVQbRHbXGZ#ukP zFNTDztyv>%c6Xb}7WcAd2d^l8HOV@0<)Pb46D~+w-D$^N$L5_UtF}x_2`#PFAu%va*Nw`?ETU!T@xNY*vY`S^Pf$YL1)R-w4cI1_cX37 zDEn^gvE+C~*Tt|V$N$YimG|@N{vEj(USeHeV3Asry3l=oaop;vjJuhyC;V67n4G8k zT{Na_eIDyduYh%7r+3_}Tgf;lvO8?`-Xqehxl791YE_hX=rh=i(EHXWy1lX zf=|9Bk0S#&r1d6-h^x1+*Z8zcDXDcOlj8J%?WYcZt)9nKIYHYkP(|0i>Ow{66OWf_ z@ylv|F&{VCKJ&(H^S}Ao#jF`2kECZB>6OngEX%!JX>D=)R;!^vMDRgIvq^UAUVYkm zyZz(L2a-DbGA_<~eeKG1>8;P(e?EVB>m`G8a>n@-ujze?lT_ZFzO^ufe@{^*r_2%` ze!Vq2KNPF2;q;F2u8*Bz96ZV5ZS#KF^;t?X9v2>;y(a6iA^qv`^2UErf6rCE5ZOF7%H@#Vk!hxV`V-hIeK`s1oH8>^S|nhseT ziB=bGlD*K>lyNEe+g1NvPnRp)JsI9QA@B08Ni0o{E8q54m`Zh9w9~knlW%LnUe6jFZ`<}WFAsdtHU!L_#j;;Kh%(uhy zU`1{mx7v%Nj~?tkm0KAeUsUWe;}M_zfm)fXi}ZPZbgA*UG0rl43SWDl3Je4KXUlV!{I_2)e6re+@~OJZ2-TyA*PWZ}yGFUBAIla62fKk0$$ z&2atoZ8!H#t8%Xl`s(I+?^ud**p~w_KbAk&dD(et{u_3W`pq(z*K~b&+}q$EcY|$m zz0KnGc%QDWTE?T#6OLWu%rPiWxGA)e+4xDsHdW4@`B`Rvg{<9l)~h_ri8y8`?z+Fy zFl#Miw1Vzyb4QOwkLxe}3fyxi<)qX9;K_eoE}DFbuf6zoJ=-i%gJo0Kf4RF(Y z4J+;PSU-M|{rj2Ewu!6GJIt2#^`cMseW@4cSvHBsd1luv+LFHck+h2DBZcFH=m!gnP$Y^uzkzr!r`vx&O2X+ORqK8n4+5NSjv8`Nnq(hQe!k0s_$M{$l|9}UuLiE`ttDVF;CGaNz(d_7f#3R zT6VfwU{#S{{Xvz(4M+C6^Vg;?yuHM^KkejR#tXF-$JHvL%ND=>!Q1=gWd1|8ZpIn? ze$O~hy*TS#*S7oV;=VM~)`=II>krr)<&}Jxo~pD{p!|hxnT7v7=^LKgK3%w62 zx2A%1?&94CidbVhr8e^}JaxYD^@5{Y-US_WPhak5xAVF^Q;b6$4R{HIneQZmFemb`Bt$XezlC>zdw9C$>)yj{aVSK=&~ho z^`A2ZmTcO)#eJFY<)wu`dnB)@i+VTh=KA}2Qgc?vvI%Ez`wD*lzjES>jyBEzu61h; zaIM~EuM)el$ZSG##})48$oW2g1*?QQGll(8v%)Qsq4an}ESYz?QNrrWL; zyYIc=*)t)KeWJ>-bKH{`Ygu}nJ7@2BtnYrfpLmtMf82!Nr}MAP5EOpDFzrf|%ju@v zWeeQ-KK|RdTJ!V)Ng?O&x`M^YL2fF3KfbvaUf16-DRKUms7vxI(qtYrY_MmZ6xK19 zB~qjHbj%^$qZe%?jTsFsUFE)A^mCtgyxhp$UT^)8_0!qny{jv&{0`nf=Q5Q`xj?x0 zj-V_@Tg;w&lD_-aMaN8k)n31Tci7cu5j6pym%3dw+wXAeUC*wJzXFv%N|G-x_CC|5 z-P2aKVRLfDWU)=0EP85xGZuxkPWHX~+wRheU8!Gp$voudeSTLdesSRulmB~8H-BP& z=+rYo&nsu1`YBe&MVA{Ei=E!racseJ&eiJ7`#%P@E496_(!VeC_;;n9UjMrH{(s&~ z{vzN%@9BY~KVtG9aoOAzT(hb5wWx}~ED0lb?|Noszq7%|vH~uaBqid?O{JpY77dU$4TXCrnxO z>5cb2=0iKJl7D#LncZyMyJOPjFNW852Cb};kZJiRba+y%_@)1cP3C;Nur}I>MJ4)~ z{pxpL{3=eTyfiSMJNeSOJdtyW?`sYo)BJQMLt?cN+bbW_u9DJ-kkt-RGQP_=cvpwE zmDMY2s)Z`9{Fkf#HP7IYmVL|B+Yc1uIsA92ME%(-CZ?ystA zjWjZBPJ4?Qf8PATwXj$7l5^RvnK!s}XFOP{)pTI`<|$L6?_JQ%oWU?R>YB~EryjQa z?Xx<|?#??@uBx+qOMYrwZ;|e3)S94JUx=O z`Q$R+O`1y^Dqlof@B3>0`>^wOi=+kDlSRV*v%b%4+G>1o|Khw;u`TZdXGOl`I_z<& zDPu{pcCyd&w&oqjcAnW$Ch%&_3aO?aMN%fFmuhNj;D(d7MA@L8;_m% zFIm4sBD7c~iRp{elVA68nY7kEkBvUst?)Xs@VeL;Mb~1M`l6?cKl9y55qMr|{>9kX zK>6c)p=}es=wDpQ=VP(i?(d7hEgxb(o~oO+g3m)T;ey<$jHRkGMKmvL-~Mv`F$Q-} zm6y}m{!QMWA7^>eDl4ciXBj^;%S!&(d-AL7-I~0TtKuEio-Ozt#6v zvTk1*&&D=`O`ZKYcmEt|$8*otUA9T~ah{NqF>|TKyaSv5Fgsu5y8Ti#ee-oWqlFKZ z+#fbwVLIQmY*u=BX>O)r&oz&omwqv;A6V9}5T$?oWzjjGWxu?CFPMJ3+(}A6|Du|Q zhg_KD|D!$|zMFHK?Ot>6x)ui6C_&8s(nK?-*2`FSi`(P zXfpSSC8syD9iMmT!k3rF&MH*}uhoC};E=O$yv&2$O^%zjmw(&&HRU(!id^;QZ4PgY zbCiRl*N4g7wYj!Bv9y4B{)L?z!!7U3j;r^+u*`b<>ySw`>Oaz&*T0Qr@7R`g>t<^3 zBHbfxrU!(y0wROu{9{yNk6FbzZ~eYSY0I?O!t#s0*;@86&8jt`2LFK3i7 zI+YSg=bxwXBF(u`aXPfula@yiu`Sa#gzV2gh{?C-Y&1<;e?7Q!mHf-4ZbD~|O zE#r+@-iAw58s<%vOn!7E(s22`3oS`86IQvgO=)q>V5%v6#;Z$F^#e50{=QStk%8$O#qZJsD6VzWpoCQ3rFXOY0+$ALSV^yf_wIP9FH zH^<`Yx%EHJ`Z~{(V|-w~_G;o``TgWwe$AaqDXzhScjiPeuV#}hX)q0{pL8n9?NNzjn*6oPsn*92;_Mi`zVj$XWIS;=?~s2zkU0 zY5inB>+7cE$^Yu7RSWw6J%3K`Pw&E=EN4aZ?&+L9xTEj+Df@?;m;^h`_;mHTFZ9ak zJoUL=EvckycqQ-mtO||wm-l9@3^A(RY;$3IJHWA6I}6Q(lP z{rDF@`T4AWK_7Z7KCDl#;?1w{Ewpa6JRrPhg$|?ZmE3YYn{WG4qyGzw)(R-=FS1@O z6gPYNA(hS})6%c3YT3lX_I<|jF@dy z>#jv}w@VSHAF-X0R@$ggDzDVG z^ytwlqa&G{GGc6vyOZ|4FWdg3Sv^Ina!Y`I<*Fj@9Y^07?G6bRn`+~D?as0@i){aI zElAK>V%XuGySLxFQ4}Cgxkxj!|=iEo*X|FA(EedQj*|&Gyvel|4Oe+`b zI_h2h`DQk|=Il!z+{zn&cqp;_BivSJ%(3 z*z!r{lBS5qrK1fC{GuwCt8Koz$mMH&-Nx9s+DSWP`#dK+nJk~WLRDP-b*23p=a6G- zd#)@<)4W-*?cMCO>xZ)fHEXBrYpI=g^V+7@f0%gJTF%w~@%`X_5ZYO``esq7JxKvNxs7vrpDEYf!}D(?eB<40KYdTmTT5LI%_@F-#?U|3U*GdbOS`fh z9HH7D;-@awIM5kHrl`?ocoN;^JYUoye&JpY+#vmDY^b@VT=lwbM0K5Sner{B9z7jJCNZNAO$ zaDI@}^W8`1&G(qPES^j7&dc6c349CE?=EFuq2Va3@#@Iw**UQrg9W~Ey|FHm>&uGe z>JnE(_gXK7GAG~a3)VTZ8&~=Tna*cqd2oATcK)jS zOZcvJ{Wkn~Z0C~5{!fP{hu>KL=el2}Qf&JB;P922Vs0UuzgPY%J#{_fl4VI*V}HS= zSn(*Y1#fh&W_7;^<4@KB{0rp+IBWc-%f{Op6#v1~zpHD6NpztF4iN)}>dddM@C#oQ>a z-*A7k^TI~)4z=cp|EaH*|FKxJPR#%L_FKCeD=u5F|NQ(wY74v0OVzS0Ifb3R`Nt=@ z1z1lLO?935JD*khLhP4Z$-ci%?1mSrrNR=obqa0>Idykl%%7l*CN@*pGaHn{)MD4i z8L#H?Fm`>qwvPS$qZL<9t^dw(Qgh+4narB=YnAtl_&rH4yvud{_2!yu@$iJDyb#;2Lq8e{k8y8%T|VvYzn3R= z?UH$L_x-FHccP5h`SqOCx))4ZzIbh|x?;M9rGrI5zDX+M8+q3>%ur*l1>dp~ZE$KBA!dpiR%XX_?ixb~y*(=F?p9PNUIWxJnr8g9R~ zb>B3mZM)7F$86}EZ~IVm$7ZL!rI&Vk=kvXjys-J)#_}{3y{}UqtopI1n1NsB!G-m# z+Qt0q50||T>X%`6b=;`ARoCxg%7wy;>b z{Pb6o=d6rb|LnGBo1vliv^~0cKA%FvTlSvg5R}qXzO^MWBV^U`uRUAVthhY!rl{`k zQr@zL-;r(Jk;UFD6|B7Wa~jHcgCDgVR)4Thj^z=@y9`dP9|^hlKZO4YFG_4#TXy`l zkl5OS|NCtn4TB~e71=1dX_JHYwavjMzdL44IBT?jj_3pSOIwN;=Ncr|N0!vD7QMKA z;p+1r1a9bWN)vzb(KUTm{hGA?|183SNmga0oW-{b1Wz#@<6j!D+iY<1dCae$?k2BU zS8aMwXu&!s^k@8RLk5kNe;>|qfABzC(!^P2egt#HWr2Mms)=U-*;lyHYOZ^Ni?OrjCYur>4tECS_?|Ui+lPs%~lS zyx-Q<-Oc<-4Scgs)}N_4qVu<~vrNCQNu57wfvv{N)Z+Cqu8t0u-h^%Nj{cl0&-WyS zEw3i_O~?6PC43oHyJZwDlXr*uZ=LI+FiV}K_?6ujsg!Md9nP(ucE6}S+>EEn*PZi6 zj-g}0y+0~@9zWa2zkABNX=fD6<6jvS?ODBz>GbROlTw?H%41?O9-dclUa}!ovkmV)ZLOT)A^_+12zM zeMMuh{;O}}R)zmN+$DG8NUi2sTmRI8!+Ogm)v%tsuD|$knn>vK%g5h%o}BitrFN~V z?=@9P#`CH*e^djl*|UQ-9r!4>aaVw^`E!1aKGW6zGA~U1tlRkN!vu*CzdS=@;jg@B zcK*@s$tY7zk#dlk7}|JmZsyMWQ&+Dw?(4sPvFx7Ll~wcKNU^BBK7QFZ$W1$cj^b9a zPn(_dcWmFE;4Rznd(Dl!+mdnJF@_y6UsVsp{>|w8E#ia@*B&p*ci>a?s3vu@zTA~dG&7{%3WVR?ELz)i&O4r z%CCJgZ;xk3{P4T8#-QW4@x1xh*t%sB|9h8jefsv^kKFy&c1FGX>OAMeN|tCRk9X#m zo8?Y1+kSiSWWu|vt2F{Qv-6l_&Dr_OjrEy@V1wl1H$_%j%b1t@Hz%31EZtGQ@!h6< zJ68AmF7?n^nbxV~xbJDyE{WA0i3=7pDW+I+$XT1)>+G#Iic)EgwJHxk_OJ8w|K0D( zf9>Eg-j;d%ZXcKJr{wovFI%xKJ??475Vzn!glcU0tY1&m7s>U{f0l7`UGl5rM*^Am z9;j~P$hczndv*H7`TrYN{5<(VQS)J$j*ovH-&sowEuRZHH-cCgu7_;d$EldV>e^~rUg(8RqxwBvoZ{IZvafBq~>U`wt3 zy5VoD$2{}wp5?_|hZM}eKe)8t_4t-1pou)0N$C-#(w29Pb_{ z`Al(SE1mu2ae3#8?=rpr>L%S0QQsl06V`HgY3_}}cct8IE{=*EdOnMICJdzi<6~yzZ;f<3kfpiOyfG@k3{=^q0Tq%|5M^Z4{XEejUTCEx%GD zCk32|PTVQ|#%Fbg^voYoEKZg7c&%%1dXSO)gNilsFN-$!ejYj(5{< z;pnH%vAZ8!aIq23(OCC8ku8lsbK2}oi<{FX#B;3*JK17C{oT%+m!|%DeREpk+QZ*B z-994s(v>eyZ$o$eb-9%5u{Ha9vu`wQPQ9|-r?MupyXIV)+oq0s+plv$r zpEIx5ns%qHE4S7$eKdWQ?_X}sCla=dkNwD;@C^S~uWJl{Zm&4Bup>m`<~)^meiCm! z@4UXiOuuB$H(kF8`zF6?-4rsLQ`>8oaoo$}EB_iyR!z@k+1EQKK|5u6Ys{oYv03v! z<{p@)QAl={yEo_0F{XPfsNtn5`ktYBB=ktOrs&z>I_s;9Kv-zu_L;+_^? zwKz+X?1eoizfP9>QG6=ANvr#`Hf!(oH%*<5wf(Ett-f(^^}juHOkUl8HzUVpOX56( zqYS-)(-%(?l#+(XFKs8m@AS4o3cFQaG?!DmAw-qlRhXf}&Nf ze_ZbCP1?2Fau4@e#Zvj4V;28St}m{R;@R;&x2W_))yahDw_o&))7IPZxSh-8Mfij%*(TGEt}ROydX?_c_#?mLmX(^!jZO1oe=6LKTB4+~?dONp z+*ScwpO`X7x#w9+m@s&$SjW zxx1|3Ta$SH^2ij|RZC0+l(&hgZuz{eV*Wg-4?zu4_fHGN-ulO@_D#z)H7VwV`p(yZ zDT>0M3r=+0FjWwBDv15#_&a9mOSX`^oU)=nRF$k(>**CLU({*z>N%OzZl`zaY=T_d z+pUWxdM{hcG8zI%sX^C<0Vzkha3=FIHgu12HoRPC!>5xb}K+DHFLmve7_ z)pqjIesgz&v?bF%Cp|5Z@N?|5RJ>4q|Bj`)-?<&3p_;$9MeRK?<;jw@+{dcs&E#Bl zWvS(@Dj~&!JV{TZp1)row(a_L`Fz0}_C%`>mM==;j_&@fz4&rPo2Yz{Z$MV!!leQ$ z7nN<}y>s3^@PNKR-Nic>jn3^1=bd(G;>h+|IRz}r^TF&Q#$@!YVXTw>*l|B zdhy|%|Dom=mz2HwB(ZnO`4a{9T(Z)-O#3`4+S697E7)|bYJ%iiNygS3DV_vw72Vej zfBx)_Y0X&h{GP#_#*f^WHLDxmoY$J+?q9&~raIGW@&m?S>^;v^=52IYp(4`I;I*$j zCTmG9*X`{u+TKU8pJ}o&S(fgUc}aMgF~@~h$tiQ=zO)Ibzdt*jk*Bx&(*%nx=XqM& zFIzlI-lW+3V?k`9(AsFF4Ltw$Je0~ajJm4xXV=0#nKN@U{~S=03J#23mNnt?bnSb6 zbCNbBadZEgzI*a(^%b8E?~OQf`mEny-|4oU9F`LuFRN)kw|Sr(SY_3{xnfD4^Rf4( zYWipSep@|#`{e%-uQ!ouP#-$%m-Y ztUs}@LpR4ICiTqKvNMRd)~We*%S(~1Ug6s%zAfPD+xO+bv7#S+eLMEqaelwQf46SV zq-T;*_f{WdGrjTd9P^d9!|Iu?A`^UgDNAUUy$Jy+ILn%TXI{%~;dsZ;I;o48YhcNa^2e6mJ7_j>j^=9`PG z&avr4rAQSBC>$>4y(V3M+HOK=1~Uf!BZ_pdn5JuI<^uV|6kt)TR< zRpv`=e;>;^(*E3$ZE~rqc-mhDqsZ%XzpOeMv7_9f zEvJ9w-k&O+MN*S<_RhA|3|Zj+!Xe&Vxc?iowxLZfOHGf0SW@wx#{DcZ0rv#A_4{0( z;`YZ%A$l{{cWVVFs{*qoGxtRYXB!GVpa1(9&zZ(tR&B2Z8Hq*xUOQx(rO&#(sbBTj zF=~lrv*`b}m|5;7b9Em1eDb+`pevr`%g-L=|5Z~?NGY)N^8DLY`{ZVYeWjo&bJ~=9NdFhYH4ufd-e@PQoG=Ggtwq19G(Rt_7 zw2!;l_pZNa9sGi|D(Uuy#Zv@k>5DWj^WrVFu#QSde`2uDVB&8@A){pNOD_a3?vp!I zG-tv8?e)r&pZoE!RNAU|_ir$%IrF~f__-sh#>!K-<=bzqEHs;Y`uKlUrpv*`$Bvcg zPCfe~)NVssmF>fjuj(GRo1+%o`SImQr^Sv~b^U#r?ap>ZV<8t}J7HJmvA6;>NlH>C>}+ZT!#ra<8!7%LNA*OWTcY zO~XA>&&XJ;nbyw0pun5&QD`;etZT%zGY`_`^tG#l{M**7)!<$2q%Hq1cAI@e+V7o( zii}l?nqHn}XI34^)^)giTI-g1`Gp0a8GK8Rx;{7Hac41_$&vb<{at5P&Ci;x`m5)4 za4wl(DcT(>e*Wj<%j@@_^FJt&S6kKCY$A2pCSlRtaEIo&8`H_lemZ}7j& zBlgkn%KP50(XPJ>_1sbpI)1;C>QTEnx4d$1@Qtn2iD%O>3Ow+BUid z2uRkjK3Mohd+Td)HQpa`OFsMc{tH~Z;m{<3=jl3kpC_wKOkmy`u}!sOTe|NFzRzqc zr`*lD{5q%i*HzoozPD!7GQaYA*_%)tn(Z4hve>%5DJ&kWbJv7Ws3b)S=Xfd0%Sd0UklR!;o2 zE325Bj$eAr{ZOzUABv*_`Aul{;KnHA914 z(DHeKuiW{3(~4(@G-`x=SN3v*Gr0D*`7+yH&0Opr(j)5QD2Sp37oW`2ChG?MhX+p;x7DywGssL;b5Cm|onL z-w@LBU{it(@09i%(JvW;gZ}BV$W^L--|OU+7^+}gRrOXTOl-NBw(Vcl1p;55tNQuw z@1GU@bmfOgaf$EQs`Zh7jz-Drz3S&)tZ3tNxL$ktjvsGb&QJ80>FX&vbIU;eiOpuA9|daT;)UN`dirE-Qr)YJFR<$-j$UTy%%~0Z_7B4Xy2M1=3nytbEWLW z80*j5?ma%Ku;|Y5YuoNU*LhK()5~D09QR?G-I~V_Jx_2=3}BizuVU7OXPxS;omJ5; zM)7GAbrd3h?3*;DbPl`rTc*R>zi*ypkWq`8^h@|AYxkvk=Vw296OI|5bZb{Lv@Y53 zXGd5N)7x9OOjaC?Jbp@g)>iYWxB9e&r6lH@ohW;}_4&V>WeSO66N(Je9QiZ_4*JL3 z^4}cUZ+XJ;r$MN&*sA&6bM9{Vb?!?4>?bZpw#`KqGRf)Yxy!vbe-Aos__yF~?ECtK z^_F#ymRR?EC^gu#CRRjfj*-w}#h)x%39heIC2z(1&cAYQiO1EXibY>euEtrWV$v z2?Z^Eshi`%&N%b%#$waX+5PttY_IzzG|uUeT{2^mSk7LH-!Ur={eBx^nwrDFP(0Dm zg6ZIwmZ|z5E>Br}i(h(uxtO{WxA`u=vX&H`Ll@6}%gpk)S8{i`>dNnNUUB)yww^PW z5qf<=C1NAjD%(v*3M0PVQgF+V`s?Ims}t6{rBdZ%qV=g$LdhR)PsqE)^6}06)iyD4 z5xbtrs@2@_|Nb}Wisf-#wPwMZ*9UWLOm`fTFMWSk=lOxRCzkNluhS_gJz3a2y?Xfp zf0eDTj=#(gzsI&~$)7`ZXWp(kd&RMG4|_@9Wd09}?rgQZ_Se8ZZ<>T8gPi4>Yo2v? zcTbq?$S>4McotKRZ5e46gO_8MpA0S(^>wZ#>pK}pkQu|KOg z|NPeM$EDn!KSS~!YgPpwQ|aCj+#lpUF=Wk}n=|Z>?a^nr8_}KjUt_KFR~<1YZpTf% zwQLSL9L8>|k3?0h5;?RnZcBEbH^XbN}Bezu`bxM7MZos@_KB zAf~h57QUMA_IFPC`kKF!xTFu=YWW&$xlqjah)ziOgwuwO%imb(ocHL{{Fu4<^o?_Y z%ig92rK(@Ldt=g)CD%OWGX-7n>`{7SA~A_U*l2eD#l!BKXY5j3>7ZNOkRfwft6Elc z{i$}Z7{|<_)qRhnCwYCFE)vC@TEt+)Zg0>lmGn+;8|$}&0*50PJnwq)a&_6%Ji2J|Ky?;#X?Srpo&5 zA8Rh%GfJ!w{6(_buG zWEe4b!Pjld3aNp8T22?9ba`%Q>^BJUaW+>L3w&+Y-?vA~RmNY&l6%2|qg8)bSUO!O zdS$3;e`WDjPjRos|Mq`Z`_nT?qVtALPxqD|3)}TRJZOwdJ6qZ2Y4LFb*KrOfr`&n% zxywqvM{1Y4yo@V4_RI*E5c=6!@s*K{@o+Ch?`&fiTuP_@oi#e zdyc%?qGe#1|HQyKD|KV7Kp~Uyua>AMM*|)^-wEKpXjrjo+Ksv$d&?Fm@*KOvw%B&+ zr6~)N|D^u#7h8X4bHA6v?hSp@vaf|XytCME!Owm={BT>YW!%(Y^U%4o*Lqy!zgPCl zebL2T*UeTO-(ljoc`MJs-4CPKlysVw`OaExv7kH2e&+s<=0TR*nvd=BZGN)Y?e5Q{ z_s#(dMA|S+^HA zF0HgysB@Eis%rh}=^=?-!gni9yf;!?D0un8stvo&9ozIju~kfS+TpJUq?|MI;~pPi zoIWe>-S-L0+FVX9te89_JUwgu5q6CtUZJb`;@anyg`JCu%Dyu!0i(*fG zYN*)p^?5j!hLg}>buUcXXMRVp&s{P^Z?QYC?XTN_$U_`=>b{ZisGx%6#yIm;&b z1uYg2R6i!PDo(y}Q1VrPSgB|Qeoh+Lk@EY;$ z@5m^hWA$P6Gl{O8hHa)H_A*=Mn!CHbfByW)vj*#n9YObwEjb--laqZ|^h>?1z=zhV zF9owA@+avV&wa%c(S9+Rv14yfY21fp+o$b%ap1`LqZupjoLze|Xd3T^%%_)6&r|wQ z=hS@mz1VCIPBZEE>V6@4o7c#GSh#!Z>Q!+|LK|ES;$1Hn=N@|S-cm=ENxtr*%Ldno zSoWHp3wP$8Ot#z{Tu)cc0RoX9e0@KKq`iVd{E2-};8rjjlu?^Bu(-;+9Bj-)ZXpS2XQaQOI`HO*K13 zw)BPm6SU)!@L%!g{P_(Q-@N-Ois)Z5UB89m&0BrtQzyUZcBkR4>LQXnodM~EC1zR^BuNMd+g%- zWB0BFQv_%3aQP8l%U)Y%(pfN}Zn3@Wd)H%Yu?>?OD#< zHtn9*CC<;Ux10=cQ9q=2C${PRsiz$;nl3M!ba%_Tvx~j&GyQhjy5Ayg=j*VWM=KL_ zHCCqWlV04iM6j&E`^=e(^(njef9>k=$=$CwCwwzsY(!I_^U{S)qGqm#doQrFDy-0~ zdM~|v?c0eD=dnI`o+p#BcAjg~7nhjq1tLnkQzQ=DyT$Xq;pcK`se2_Zlh?NwG<(Nw zdST^qs?XJ}WF32ZsME!(Q$sd?kQRLAqte_rSNvmFrdLG65#EQ_vURzA?Ybu}n=|?D z^w)Da-bu@Co0=C|YyY9RtNQ$6y_|{uo^8xKH}#1nI&8iWz%KeJ-05`jr3JogFW=4H z2Fck*26tZTR3P@3SRlKQT7$&LBezZcG*QWrFhKfn77)AeWk zdASLC8Y#^eZtdEvl~?g*Z%3p4`&*(Lx7_V_lfSci{TvIG`ZdiL6&@6+=(nVNUwEl6YNBQfDdC`rZ8}+QA8wQ_ zo_1m0TQ3K}ywEs5o3GlZxg!gCDpxjq<}vl!;3InE@poIXbdQ&fL8XyS*S<4OIplRi ztnL6`uh*?GbD?&3^~X>7SRb=^d|G|#n7nHETrDNXy`GnzH;G+TY`J(RW%gawKYGhw z8tDJL>!)yfdH?z;7cLqV9e8kHx6|sg6HCD_%^Wo^po@pzb0?pY=trxxVS{5l9 zzDVi#g83y86&vc<@9cek_Rpi@BkvD=4fG5MwpHj@w|UC4p6~Tx)9Xy7^)xOm>zseq zaE3m!yn{1u$(~K0z~ynCQIVbDpHWI9bK$fdrWc(%G`@J&uC*yU zcv5QHF2>_@*>G2LjR@dIlYu>MZRkkiXW>PEAdX}>~>(ia5 z>&|KlJbJ?Pc}J9VhN~;nrsWTdwV(ZqtGIkF=-z`hQ9C?eCc3($)~Wxk-zM&ndM;k` z{npg|M%`--{#@c&BH#Rcw^P@dT;s24OtbtqcrDwP+kNH+==_-r8<<$O>}FE<%^21X zI)5gsARTi4OjbcU*7IkUsB&>vt?p^`Z9KLhYU;n@uc+tG1h>5D3bQ`Lcsp)pzTRt< zE0ZmI9o2RIN!~R3>*q7=Z0)@J;@YBuj@sAWf8Hn>y=i(4^WFO9K2y%wDh^3%rTkls zK21Mtk$k?jKjiv~awGMR7dabaXHWIMDfnVnm6QvsruHHipHPKjzH+sdy%8G^H9mbB z%(BL(sepeew|!IT7pBRl;;((N7xPJ)cx>AAA7>XUK4b6*e}TB*`f{xJ z|DSld2N%`8%?auCl-2BWj?R#Ztcp7I+Ay^^<*~kK!KW#6{f__MnqXC2a`#15(vqM{ zN2faVXa)YVFKjE>b9LFO)X&Es?FpT?Y4M~`-Jd5G`=61$<5?Xv>zxTtsek<2XA4U_ zJS_irpEHciz3}sUMfBe<1%g+Su5(v^IBH{Hl)3f$meR@@-pAh^_~*Q7dFpA?7m_UL z8-GR4UA&-!>*&1>cV6|1twuK^H?^PA2-JMoxFJyPYUPrC^H8C!k5`^vVp{T@vv9(` z9{Ia7RG*jB8?U_NB{?HX+qK$fc~w@@EH)0l^{f0Mh5Wn3PaeIR!{qp!byhOBE~5S9k%>Ih|C}g^)2DUxxH)VjnEg7$F719$2Ps$dEhj%30lv(vB^n(#gRN$Va+8?*Yn5G<+Q@Aa?E9dp! z$vf9&RLqzxz{#?FA=Pfm%Hx-Nn-4}zT^T-4;7&_sTbcjf z?3=b%O*U*Q2(vdR7n)%C!1$N1RK3v5%bl?`p@JSWej1saO?eWzbbm+GiOmd~c1`{+ z^R={MyHnA<$6|bke1o%Ha*ARW=ltaZ>ImY`C6QJU&|q3&otd|VSApn zc03!Na;iPQyI+6)RF%Ak-{+lvYE*cDvwEvqVL`4%M#t8ocXtbS962o(WBB>o{m=i7 zntgm4wQtGOg_|c%vrgH6EN*gqWyI5sq6UkXZdRWz!h46?_lVUt_pg_=V|u2mVp#;&59{%iO6d%iv1cdp7v{3x%QzV%OoPnyA{AAj3&&HpBUVs(u{Wcl-p>W815 z>k>X_ascctqMnHBqY zb;p8hTi0lP?}|^Zde+vQTSKT3>_tW(A z)NUUs-M4yfnb#%!hp2s_h{(?2fH zR``9n)HW^jg=g1}xTC2)p~-KjZ` zVlL|*{`E=PSxF{HRKTS}vqEy>%z3}IyzOPlJ1L*hDu4B(P21ha>t(f0ezHz!YdUNH zORZy-f`7s7cM5kVHF8TX`tE*W7usb^A0V%atdt5nc6ZTF0X=n>;MqEL<+M zOH~+3e|0%!RVKc&c*oBxbMu^c*MIzAd86p5Q(NUU@!VA(%(Cju?}<8W**tyzS!1z< zEc*k0Ry^*JQ{s&|o$;t7AxzMs(q1w4)s^3T0@L^Zx!<`Y-8<4ys%p>9gX&4fPuZ=e zr6t5%On(_($zA+%$K3Vl%!`(!3piRl;0rR=$T-gQuzKUP1xKo*)-iUuJ%6JZDEsV( zoc+n}=pnm+_10Z(BK#5aznOJ1D~oskW)k$Db}^!Tt81)+ z&|lk}HHY{7xWJv`+xMDh)@q$EVob;OwFVXQC%$*!G2Ox$G1wy! z!JmD@_~GI()qCH?)10cCe~&eynD#`^!L|kMR&`#IR`mB3vCyE&-cLK z%!%Aqo^8HG5z?PH_b)%HUea-Ptwh}G7xAle*WgB$6@U9OkC&{} zbdaj(elB2VuiMs~FWPK&ZieR!3%#)2M`M{U@@)zb^zdq3QN2R6hqtXK+0OW*h4**% znptIaHN4$V-qkH!+NH5rwEPC&4ChkY-D?g8d+Tgmow4eIz2X(GnJ*juxHB{D4C~Oj zFH`n4Ja@;L$~fbR{gb}v&%gOGoT2cXu<#q(Ur!Z!nlhQ1wt3BZF#WF@vk3S7mjT*x zlFSSdPHnQ||U)$c1-A8lB9=lRNs@hhY!xv8JrzJE@K$N$jN zK66R-m}viD_#dY^w?&}(*8BjUwEr-gDPWz{_1n;&m^8~EeY|Nu;hWBr|X|vvl9}&{#vwZqJZh!gMrIJ z44HX344yHizu%SMEUUg_fx>ss;}0i_6k1%m&mTBn%P1{-wR(2Ran*x6_j!F+&f`02 zFC-j0r9QN4smaUa3p3Z5uH{I4C z?fk@JvDn}5S1**!n7e5U<0or<$pWi+=bt;x;QX^Qe!{l(oXc!>@JAIEg#JBXtPnZd ztG?r#o3~PcWY69$^6GNG^=_m)2BvWQowqoi|Cg5X$;$W>_fH+Wspr=^Q`T@&p20hIo=g{^`5DF;^e7EGb7httG%>UK_DnTZo~OqO___2 z>YnS$O*n0RXW5gZAF}v7_%Cc)wS?u9v-K0v<$-roKHNQK;xSu5dTXoF?`?i>pSdS@ zolBgxl7HRxtD<|Qz8|stxU{!$y3e;$>-)am2<&-raIw~{nLLcySLS5xO}G`eX^HME z<5baW(HfuG8GJYPwb#AjX;zS*w!PN&oMW{_?1Nlp5zWABdw3ZHrzpS7R^t^iJ}D!q z&MWr%ps>2vDxFr(z(-xT7wdfglezOYqfl&fSA^DOv5mQ_WG2SFNZs3cy=23b=!R$k z-GJ+fNrpF!CrMcPJxtwPw&cQrfLZel%3^OszLg0$<$r4S_wQ`ew9Pe5PyT(O!P2N) zbU3wO&bqV8k6wHi;F@P6YIWpVV%?!RVHD%-Xt z7cg-?{1tfpuf<;@-m`C(ANuFjAEvg8S!eR~8C~6CLV6D?RoXiGo?bZMYWDTVD%ty) z(|Kz4I@ano^BdH#?dU#X-WF_Pt;2H7%SkVL$1mQn+@RxE-<)n?az9gD_w!+m&s5Jq zrCDER|6jC0*F5{~y2QitHTOPGa8v&H;C^Y*D#flxw`Mk9JG5f?Gxrw{H9BtW7WrXZ z{zjawRU&If_GZC}ma=!>oy=XF(y??x!tSz?xV%XM8%-Efs+JxtU}?!ayM4Fgov4@d z(i>hYpWu_drV^=f?~nQGreeRXx(7a=IH|2LcOvKFnaihqS{Xmncx72$*M9c37nJYD zmUalN+0=f?`;g>&D{;5dH-U|!okg-KC*C9}o0omhUi3Q4=jXEJzZdw%D!J~>GZUNe zeAZH>Vm|9qPOe95*IT{I=KrwM!_{Z*{F6UfS+`DQzo~oPWM0Xg2?<^};(oUMdas zGuFwgxfE_R{24NPuX9zC)N}`3_bAa7p|7Rtl^-7c#gj6rdCx3~z1t;Q=kL;g(6*>g zcKZGTNq@`U|A`?Fmhb*@M*hq65Pz@pJ7=8>opG-JjKuoH_Fa1x&pINi+ohVrG=*LG z@6>Xs4`%uqMcKuR?6)vgRG$C2#`9hOyNak2RwsU%N-EE@_`Qkc&HhE9|G0wg9dE0e z$nH1CC0*Qi>!Xj;mN*J!F1jwb<$(^H&}Fes8M77b?8)zbw*2B-%6#&?)466=xoPDS zo?X2v$1}xOIbB6W`;YjG!Y6wAyJeogJh)CI(q^l~$JoH~zNJl^Di?oDoy*N{w!rtv z-4pSv-k)Olz;^uA#ZP@x1^yPieZEZen(;=VmSi-lZ z-#j|2wtCm{1b#;u7aJL|xl?|xFyC`4iZA_^z8aItYQ8f`-{W&9F8XVv{x`R8XUfZ* zfC>(`lV4s2h8%wUn{(bfhJVZzZhJ1~KTqY37iRo&bNcDt^w0U-(yIz~IK@93q* ztaQ2HozPTORFk`E-LdrY`_Jb*D#+|x##hS66R7z5z}5L#1&5@%ZqABY?4Y&j>nyL; z3LlRboH7nla=k0BQ~D_2pWT~F{nNg$Og-8^bGlt$l@V*IduIB3$JC5BcUb1~g|Yte z`+TeP1n0}t4Gqr)=T3by@le&Qti4lN1Yhd!vtGS&#m_?<((|5Zn}&HY+XP0et*(y9 zUU1w!(l%k5VB(rdjaHj;rp^}k?Q!_o{nGW>_gPokcid0V3F?1cF0k#xgq*oqlm7k` zFg~iI^#71|*WSloZ)=ky{FyaVn;#wU5Lj?@pR~$7fs<+l4!fs|1@@HZJH4;-314j+ zm36FovFo$zTTjhgpVFTH^gyP>mFeq6Y_3=AN-o%PJm~H@-lH2zidHOnC}eb%qsc1sj27wG^8tiP<*2Cs5sHDQ8b)bVMJ$A3enb2Va|ekYgJ|BgNP<-F{L@6T_3U*g&=Ai;d@o6u+B zwx!{3#h2`UHo;M``p1o1C9{ieWY!-sSd)H{PqOdHnhZN>k!uaB_Fvu3#c8MeI%JXE zwZzXNpHKLiN(5DEzn_@)f2u)`a{S*1?fSbftUUJUU{v5r*Nqj`XS9DZ+~m~PY`w91 zipQ()N5#L@b*~9tw%zZ~Het#4P|HViXU_j5;Vs4#Zu#m}T6!S#9(57L5h-F5KN)r8(n2J8uks-`{y1 z>DJuB%?0=SO7B&Fdb6f-b&a2bo#YDF+kUNUCtN>fRpHPYY_sTPoonNa>U`1D^UbdE zCoFIhxcSU{FfuAqWoj&n%NzAS8#?J5SzfHXBS-5mU@)~xRr|l2Z&AETiGoHEN zlHIK%D|fy8kTfZd)i>l7H_Pp#hPT*%=wCm5neC$1+sfS=5HQDRwhh-t<0*DY;C?YVWFG#vsaIwY>R)p zz|?%l^8*FqAv3>a<~p|RjOp-uzCQlFj7eYEuh_C>XP12qSk1xyYUQcJksbS|a{k}m z;;>@H{Ir}M3m(4CURTF?&C2`covGaHu>qg+xR&+4W?uC1l(I#iyD*tiRhXu;38k4r|PjPzkth`XO?Dpa%xBaT`TshfZ{-fQ!CR@Oy zSAvh_(egD<8y4?4v8JRddBIW`_wS-9u9{Nj&!x>qjk^YS+F zP`GaFdrYoi$Nn3(a&z|N2rvjv-_&^J_^gX}?Oy8tKYM(xhQ|+&wfCp|T`KC)fA5Qt zN_{>1{2RKGQ(c)ZPyMl}J7{{9+vC#v9{bNu{{3I0;``!nIn2Mpv$lSaKloqS=JY<# zkh7+_H~}}Ia~KsIW1wepPv(A zX*}Ke{lCPd(0~;tCoU(Jxg45YwDDAziRs&WI>G;*7isZ3TOAFoRs4QN&o`uVLA2k4 zS(2;Uk}hnFDt}TGQ?O<7)%{x*H2>=KUiI&Jl*o~a)XNq-90K}f45~NncRVs_dYRa) zaQ5`5B7LoFI?qh zu~uQh=~UISP`Q`?W-!;C*>L<)$*tqLnH|=_Leeo$AI;19c6w$2*X}hzlbq!iB-+GW z<7Z?RsrI{*y5;oDbGPJFzR$DJJ^!dsKeN+J;jnYPyh8A0Rh@0?-3k`F{A_;1yWymd zo$=k3d_8SHQ~Td^UEO&y_Q2aM0h1U!e#vUTemwEpEa9nh_GX;0?=(BRdN!Bv6BTDI z-)4tbToe7?@hf@9N~-EAtefb4m}7SA!*kZFC)_Ayd!60B(I z%;4KNOVd+X#A-dy^^0m13`IQkc?DOpGr73t+&tdqwY_!8eoxWTu-sWK&x_KVw+ZxY z+VL*hVtv0eB-s7-^E;R9DW5QR%{j+8yuyEyJlj60G@37(|FvnIx+|^0Cs5|4V;#&4Kxf7*6ACLW*ut{OsPu{v4 z&#lCI|GV1GwmXt;zE9qM%5M`F=4(^7e382LZ(@CsdaQ+ut~HlvzxmbA&wDI{CD)ai zZG97a`{B;;O*eXdC|sg{IB0!f4;=}VezLg zW|3z?MIYO%=Wegrx$B;YG=Jvy%hLZ#vh?cnT*Q{BKl^<@HQjGx_~qx%&d%6%?XT^N zhp+6JuUww@<-ry$6RrL~&pJO&nZ?h&V(UA}j%^L=*L$ll?fD$b9p|$4cl6$bU)FDx zMIO1fROWK0I4iWXFKyhDymj6JZ% zxQ(Cj!=HKeCbc4e*WIu95Onj3l;r0_OTS&~b+KPjo~V7GAmZ-B6ib7a2j*cR3vMnzEp8S&K!_y5usal}CHo{(byXdBIwf$#_BYGpB@Z$BDcb zcYZCF(Pp{hXJ>kTCbPrRw}G5O&It{3Unc8#c~3o6@j>+JTlS6>DOc|w-W>GJ`s9bB zn;op`OZFc5;CD7Xi$zS5oBvlyH8E?CPMK)Rx9CgRLeE7eKWFaFUl3q% zVOdHb^9rtJxz}payBExf2z;ZTEupci^qRtJiwC+LhIxJ`tslSO$l2wo{Fh;@84zK?SV7=Zyf6?JG(46)>^r4>SF7uC%BZZEESoxLvNOfwD*r?yZ6~Yo4$4L!G<^c z{=IoV^UZ;6+Dq=HwQibK(RuxaP1m$nAC~Ct`RuRz=%Yv3^2t0qw(>ko&y)N4#Kcps zXL;9-f`XrZLh~PsT-|bghqixz_mZ5AfyWkZ__F1p$=35#IU>qobN_q`_1>d3ZIVEyu z!xfe}Nw#0vwVb&&eQ9Kkgzu8Kil5bbru^NraOz@H=U1@1_dzS(2FsMmN)$WsO&z0TN@t>u3hiH&Dn;r&*#``Y?Z|NeM?uyM4Au6^#c zzG}r%t5n(0drZ$d?&V!&J2hW=tL2>M-60>QarkbjD4Uw~zH#q~hx_cg^3PruWA6B3 ztiEH)M25o4$v?OBcFaF<)|dTyWV8DFR|g*^{MfZ3W@jbazIIYt^2Q#O zZ)O`fIye7QUFcFBz;HqRcBKh3-;?u?e@D!4v~IkhpimiimCuXq`l4T{ev>{NUZ#0O z?ED;-gMD|bI$vo-{(bx=R8Xb#bbp!SiGQ*0f5^9Ss%mOIyp|lganqA1o15Nw)4tE& zroXm?RU*h{`TS1}_b!W7KTH#UblqvvvOj;X>Oa3T;S0~H1y?8i+kSA#tCeSKPTyHo zb8maDHGjaS#*6>wci!tm4Uq zVB?R!&Md3gv0~re3E5SkM~RF zg%^!(K3I3tDIr%_Z$*q_t?GYO8L{~v!}sNt?rRas-Ymr7&-&T1Ez989^u~=B?8DVF z%4aSK{CIp%)&2Ox&2))_g&BQQ!%is*oXvXC*rCkXk}XnW)pX^rN08E|f=|uyzF&Jj zREsiBVAd(FpX4*?k>IY@^RF{C$0*?iBori=HnRC3Rm?KiBB$ z&kOgypR>ui8o$I!+jp6x+VeRns`1;dB~D-d<$-}!S%f>kNT^2U%UO%0UvBzYpWSPa z{;5(#;%UU>1?;N?`?(jEe0rZZ&3Wg}*y(!7`(|}~4dmgUGmo1)eQh~+{v~-&)8i64 zhrGU>GK*vRf67piP54;)r_3n%1{YD5lMR|I87a~4eA?Fnf8MZDOS{0i`W$cCPW>l) z*RRmMA32d#GWFu5@E(TWJe48gNih@VaM-S4>2dVxEr?M{3S9p-Grjk>ymNE&)gxb0 zKOC!`kf^YsUQ17s$(4EY<`>5*YvnF7-pfC7)kOEH+@*uHO}*V_1#;R-Ccgi_GgmZ~ z%8R``{!8_PmqNgmxoM{&4xjleb8PRCnOR3;z8uwjC${JOd{@^WlNfGJl&RgMV)imv z>nG>s#Ea||lf4si+m3}<9-HkQ-TiB_+%lHA`{HG$8+}b~>DKr0&hPW(`;c6OHOQ>Fi~Y<<_4q_EK{%&&T>jKO7bE5;9;XZ~^M{$9>@^ywLO z)=6)@!uM1a@Eb+RYrfrT)6sp8^-gAD+f?CwEHXc97#ysAltwAZcrPW6ggTV;rgy+BR05`nW3kh0t92gJLB!XYOQw z{-WrTeMF8&b9X|WFx!py9#duBuSiQ!I-j;|y})nf?!dd12df{oySz(27#<|cCm6*g zZxuP4aiP$I$+?}U{XcMUZu@^lc3tLd^MZ*%yZXWn-by@Ly07WZ^?lVP%d8Ta83Vdc z-gD@=sr7rAgW(~Lg?pwmCp|5%IAmI8qU3B7ZuZ(b?%qnrlJn2L{MMMG(sal9aZ&Hx zx06?{2}?{8|m9IP?(OkH|AthqMx zr@}L59r@EnZ`WKZ%aB%{cH)HE$;me(*&OODq+A&GX&JEm+LM%FF>mtW&?)b)ne1}C zR{8y}TV9U9PK%7*H@nt-u{EF2!#}@VI$)tqa>c|O%aRVA;7j=v9{$Hp*G_uE$sgOY z4o&;(^51^f-$1R-JEvW5rGJP!`&8ua)c5&G*t5)@Iu)w*2`YU?01g?ZTT*J^PQdRW`qp+GFdXa+0<3 z&Z=GawsE?s3FPdtQz(DBBi4S}1+8aq(z-f|o8oiI3oNaCO>O`D4BDU6JJWsF{v^ZcAiU`$yV9L)t_Rx^X2L9`+oVedcAViIlK0uhjXdzoi1R&nAjXlox?K2+;@>ZUR z*nZZyw5;`t!)$4`jDyFy*m+JIPIs_2`fpL(AGSU4os#wEH%EM?y0k@FNx$20Y7%3j zBx`K+)!iz76L(vxiCFI2^=QdYEk3bx&tI)NRB0v0_TYB@J8zel8edHGx3=z&@MpTV z@A#pdZNYzc8h&au+rjWQ%$138v3ROq{^=>F@24z$u+O~q zkJyLs0QOl1E3#^JA7_RvyZyn!&Hc$4mG%j#$961t+j#2ICFX=LWlu{ceEiP1fV+F{ z#z`-t7Cc&br>1Ql+ZV+JvwEjc>AY5F;+k0Rnx5!<^|Wuikb13U2MLe{=_ISJ6NN1@SiyDznhPWn-1FYK|L zIe3}ikrfhCDw260KX-b>vdW^r%qko_jBT7hf?mt zM!JhMp1+v(PUZWn|Nj}5+VVYOKWpxE#@%l*Pk11&`g`Z+f!kXioJ+d8d7g-P&6-_r zLLE4k91y5-T9|vNzBm8-eYCJtgZ`U7&+KNA{;%w|#zwujD))H(%WsS;uke35mF-#<%Ki zN>=8Ww9L-9{Gmwa{EH83gvxtLEyU!$Sw!}(nIG%EHCL2duylV4t5@d6Tz<2{HSLT^ z7jih?ChhzEai8l^)$}W(pSZb8a~vBO#IuiwKMwE?`X=2VH-~q#f&QwihL8Hs^lDt& zmaz29>RDIcIdk2yQ#Jf!boBj-^wkS4$hsce^z}=a-Mw95k!>p*uO>e}-%_^GJZ4_f z>WFPCV&hzPE?hs?JGEl^<-W$^eTQrI1@C^T{57p*P0UfXKa;L7F5>L=JvDdvDb-S= zv>CeZiZ1<K8D^`=E04!lY#tvwEa1&c>czqv>JVV*5ao5MJ{Ecx7X;soZIJ{*}0*$ z<>sCr{nP$Fcyu(u+3}fv_(2oX7o4rzE3P{F8Jqpt5@faRzj~);!_|mW$4faHwEy`? z+}2}p5npZBYiq!^JI&Wm^~Bqm6W3SqFek`txy-VWFD7%#wV0?m4VtGMrB5C!t#;y% zIF%5r9QjN1gl?(ofA3Y-<||EKyt|66NF|}SK4!kt`ufuaW9ix02lS!~vCX6Gd*V9q-C;n|*29 zF}=o_u@5pfZkeuq>ESmQgOIBwoblC-Z@wE%RR~+SB>BbU$NF;Kw|vxh2u-xy@k(KT z>ALV~QWjGt_ugOd_Ta|f)4M|M2Z%B)>a1Vc`D&$&;FlSFONBztn3m}G$Y^?h|GVX) z#?0N)SJw#K<38%yI3aJ7Nz2TdUtd}NF5$Ng*NV`1;4|UF>zUz>o9}fyhraXrAAj26 ztG|Fp&bvi#gKu5dN_+g~pL?#@k{n-NZj0+S=i9jdn+q;k*IaN`|9!iG@=C!3|Bydt zFPb~YH{H5l(D7V2oHa*H&~=)x)Xw=EUrc_ie4{+a? z*rBR=e5anQ;)D3-8#W((7*w*>;MA)J$#=BXpG^8+XMWgMzg6D5#JEVhMrwYP!HF8pb^BJ{TTwA(v&h^taZTLUwL}A56)e>aKX$b~ z*i$^?aVSSfPVU+LubKR=&pd3_d_mXu@cT{P3wB;@J21VkVX4dkF|~)MI!$9)-fudi zsb_c~e(L<&`vg>V)wp)7;bXd(81CkE+_isK=HXeV#oPAg=CCe(^<;g0)b7ar<_}Kz z6<_Oq@xjW9>51|0fVdXx_H{vrMJ$`URrp?7KlpbqI%!$|`!M^ki(g7kSU6<0IE&od z^Gdj|L4a?A*s2>-O3jOG)?Qwtbjd=_m33E8q)>5He^lxReNTmFjH?s%)DG=UzPW1y z|LTOv#(zKP#xn9&+59|lx8hvsKdt}w79IHXOyk^Mt^eCq3}x=Dk}lr(W0z&(#chh8 z{`Rz<@8>R(D`cMh|8Jf7#<_=%r5>CVUNmu&9pCJ?a(p?T|a!=r0=2~^# z*8R$f4YxwGC+*q5bn51|g}EDSr){$JTcXuO&5jw%>+>a2^hGBnOTB&Tvf=gC z-1iZ+*Y{0-_@&V<JK&bqx8^2kkE zpJrEn(dD_a;&8XjB&F(aipu656El40Cp=j6SCK`2bNBCycXxkHKj42{x16z@OYpq7 zPxrcDv8)A0a%VhQ{Qu|~=j)42ITzfQN^>zzVi2FMdAqY+`ztGl=GFOcpPZ@Xcr$-y z)2S#gKjUkQ?j*E)div67gY_o${&TjS4_z;n)lV~gqj&jgZ1HKPV#~Mtr1QO^w%bb_ zm~zD1B%kHi|Jj%H!_MVoKZ@GV{qy`Sr@6-^r?GnSEPWqe#JJBs`qjvtz}_4Ls45`f451_;p~8`@1t@a=v+JWLz{Qb2Ac&M+olwYd7Tk^tG;3DL$#}K z1ygySD6-f{7w`X&6UX4^`tfUXC!Upd`rf>{B@ zdUhH*^Y!??E^3(06MD+g^HmnZt#~Ex_Lo_*YLUG?zsn=K0&Epx5oLVw=3i58nM zr$YYG{S4MyCZ<0pT~$0N{Gfe~q<^cy9PKiFowsi6Z>>dJW}j=+wo5I(p`e$(Cbsd< ztGBJ8v!|ZE`0A`@`99%4*Dn0*eSOkIQ~%((A4zsCO)pQ`73K~wCOeG@*eUOBm( zFMWMw;`WxZ>nBbZo?NR|x#0b|g&S(_FV^4s$o_>#zn=kPPb%xvm*d~J2|l#n z`DjKUXJ>QesEIdxBtc4Cg_9@hDac};4MyO+h3cnB}J zCefnSk~7;;G^}eqW6IHv6}y)oT)9Jf`B_iNr3b>^y;Hd%=3aR4_Nf