From 4d74df209e708a503353c75077bf88644f05e4a9 Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Sun, 5 Oct 2025 14:49:54 -0400 Subject: [PATCH 01/22] BlockLua.dll compiles to build dir --- .gitignore | 1 + BlockLua.dll | Bin 374823 -> 0 bytes compile.bat | 9 ++++++--- 3 files changed, 7 insertions(+), 3 deletions(-) delete mode 100644 BlockLua.dll diff --git a/.gitignore b/.gitignore index 9ea0f13..5cc3a5d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .* !.gitignore +build/ diff --git a/BlockLua.dll b/BlockLua.dll deleted file mode 100644 index 962b4c36051ea8f2f51ad4d30783660b1c4d17f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 374823 zcmeFa4|JT@bteWYltnPK#l)1OBy#+jA~FLB011+kXvnlhk&wk>Qep&9azj$k02lxx z3})c@0|Jtv$bq~OGuB3%%BN~8pA~DhN2kYosx*o#Cr(1!I+2qmr;3wJyp4COW|fpG zbsBY}-DWr5Re!&G?|a|-zWD|N%HFi;DQyjA-gn=9_uhB!efQn>=R5whPxky!PfyQg z{P)f~Jv}esFaO;ppX+}-ipTH!(67I*=f&PX`0z`cj{U)h$LC7*Xt7qEtrZrcsY0bv zZA6pBsMf4RrAl<w@8G%TNzW}y*;f~aY6|#xgn}3Fd?)_? zavR)jyZ56U|DOK|e7uI|*WvGr?Qlbr^*Z6c^NVcoN`7to_bA&fYP%v!$h7Bu0DCn9 zUrFIa){uD*x~b=1WWY7b(k>tOyZ|^h|CeoW;I~+=PW5QtppOLK#WuLxcMmJW%f$Dp z;d?6sZtq73(ev3K$H!}cqkR5V2HgH(g7qAw(Xdg1Y)i`Lww|8bci(gq0eW61+;)T8 zo&k6BEdqCdpAq25?~V+(1A7GSGyL3Ra39Ek`-swaGuyq*+PyXdZg`J0ddOpFA2dv$nZinOJ|Nr0LKM9;X_ZMq#Ui-@CU7LGW3KxHQb#u?}epAXm zwYIvgr@8Zd56VAx;gPH5%i6_FUt+mEo>*&R1FA7kt=hCfc{~{Fj{B!3YF1+*V z+MAnOg#PZUzxZ(B+~{{+8U16{YaRbiYxIv>C%^mF)xWa!+~}KYt8ex+Up)WE09yN- zP0jZK$%g=T@%Ybg?zxnc^3Ppp{?sd15`yi#a%9)$@~g`Q08Zo2#r**E)qjdlAJC_# z`Sb&LsSYpZYfG(Ygl=&PjZmC@fvQBUjS@3%%@{e|u4zw_J6kWu49@}+fh`?>L5 zTZoPoTPL@i+p&vGZ97lVtzA$NzVV+5)@?mkf9@QF`LggZ zdVz4Q<1e>HFSJg6OJ(ua+Ui?9&6katD;i!Fhdv8g3;?He?}yR7$`WzBGWs<|EPdHJ z`BH22YrnAlCARXr-`Lh;nsMbHKl*Ti;H}Z^=Z@^!(i+`{Kj@$>=g?{NejYe){RxmS zZF_dPAR2UyaE*@GX5Ifr9W7kk^r6k7IgR%K>Wbz=nb_<9{+)Lqw{xR!Q~XyyMkUJx z{PR9w^MK6()yen7;Oq@$LdgCJMf}F=C?jn1%8MWRvCY}IkA5J=4ahU13HKQpDZC$4 z*zAV~mB{RekAF{CrN~4iKh(S{lheNeAIfRw<3oxD;=bBh+f`3q+Wh3DO;5gZCHv$Z z{A-QAZAN}AyAh&s3i5j^wEvkWUoj?8n;19 z-??U4>H6JoJ}FwA2N+SDCoke}OvM@p&VNRVK+!fvhfkcmNMmlDd>el@Q{$(0umiTk zL4D~6nt&_&(w+GGq)7DAAbREMuk?I$x%p!d?bTla+&26^@Qub*Liwc+uZ?cmv^Kg8 z|83u-z>i&M{;vCW^-~wlsQ{@KTyy@1*D2Kxm+cR%tWe17ETe!MmM*4JO#eC``p z|JLZAtzF!+>D6=LC2$4Z zW(k_9KTIF@CcnphL1I)9sbzH8)5>!j!c z9Nq);-mfZKB3V<4zlCC1YGnUIf=+_z^OZ>K__U*ONw>Gt|q0XMC zGH-en{qm8~x32#Ali(&EW$tW^?f^e&l@-nZ6llNtE5~0qwLP|Ld+Ydi=scM-Dn&m3 z5>3a7{NFBnMgGU~)hY6|7k~2Kft}5}9s{=*{}7|$mnaUCNz)7GeNqCrmqjY-o3GT^ zhyGW-nSSNZKL!+lb?$ZuC{$U3YrZNnK z63xOwuWKN81|a!V-);}IfZ#Hmh`+i3Z^;h7W?J}vD-=4c@XhnbSl-jRhJR_RSG2B~ z!`JgCq@3b{_guSh?YVEj%X56dkL~<%o&DIsACdjo#veQ6hxI51hY0jQde`9(y!EyC zvmFX0*+#?}_z5Qvj?X_1PSrTqF60fN6HaGzGI`BH_j}G2zkNCAen=#rLEXde3Z1G$ z2D*WFg^mfWOxgW&>2Er=c4j$^_*;%MrGJBQhJHlU2$Qc5+kn&Xwk@^RKxT9$gH{yIM?b^TJ-{}zQn4c zmi)-9y60Eps{e^rZJ_AlDywp6knth2>UV>xKc!W7yojo|v+5$N@*}hAG~gV;{}LJq zUY}vrt5~(cs{F{T`esn|d96C|YpDABZ=))`)p>qoRy_eY*Xn{+ZL#VzN@eT`LL z`)gF?sBoSinN@rKhq&rzwdxTRUHq?El_S7;eq>gCCa8LcR{a;O`XH-5$*TOwton9P z^@p|U{+Cd7H>x5txVCz+C&OqS1&CwwpTa^Rp%+=}-~KCc!jklAH?;#_jcfg5tyMM>D-*N)D872H5-(e&zBV%t3**g@$o4cYrP_*_p@45O57my4X zK7X5tKXy6~2!X5vr|9ZjfjZLgrB52-mp%U&!ZfbnFrzDE$BBPfPby( z7XT7-A+tQ8wTSW?B_w1;L$LWD_BA95!!SP$fMjbUz69}OhGV}0&wZHdNF0<(Z@HLm zhZDh2>w^?V*mxketB49xK{x+0Mf69m+I8|J8M*-Ie3l%4eG!w}Ag1G(3&!p%tI+-l z>a-8VA4DY>$cI94QaY|D&`(HOU8VhVr3@G7*$?Y@E}_z|07FQRbXjk|6&lKuia~$1lxq?m74l66#Bl%q!oIN}FGb8~-GrmCRdwKZ*eE zKt-kMr{aniCb--l#F?d4ctky8EZA#3%ypHh@N*|lYy#r@^uZPrR!)n5?G!a{q7rEL0mVOzr5DL90#4Rb6Omr!)Ozu`wb6z z=JPFuKD|pO(m{>=bDeUR-bG&Ki@hdk~^|1QanhjJ_>Z#qJnB%o+av= z6!o*MGYvIG`i#yPw_J=w*GD62lvHS^J z`?nvk&OSH#SZnmT)|qEptIxEWPm>ii`GhpaR+rf-IMZG}vMY~PcL96UT749Mu510K zGzk&5zDQU+R#vG#apS$(IK=V}GPK`)VEtx6VA@T79n7d{#($#*y^2 zBk3t4>G+f9o>)xLwg*iPfVRBSw#(BNdD^aPebH#Uw)LB=5AZC({cwuP+Y(g1&^q(w z*6Q=E=5s>jvyRGV9FRkU)T|f_r=zk7h0=dZZ)45!k=@5KkEp8#t{x>P{N@MN;s5ZF2%tC zki8#->`@K|JO_Et!7k4MRK+-esu%~*Ug89rtWJQ#P4My??+s z9@J0SgZc@3RKEvOeBK7KJ_-5W>$ic%kE-4t@Old&nB_qTW_b{Tur3xVtke5By@e2jb+J%kT`ZI>b`Szn$YHnt z1q$MBf4Oz$LTmMFt>#N2gcqFsIa$_199a)G6&Twp0G z7g&nOg_bg&63{VFd?y5SL@nhuZz&L!s0)qAc?5_`1VpdsECr$x0dZ(@mI6_UfW&i% zfW&i%fW&idUR>1rgQPoTy%MqI%JZ3MQtaf{Cf9 zU}7pNn3#$RCZ?i-iK(byVk#<_n2HJ}b|NLS6CnK@?1-1yZC+*{^)iD2n#^E; zqEr|ez06>MVq#)|CNmhI$qWW)GJ_W~nZXN@o(=|xQ6wR~X^bY9`Tf@Fw_44Y zMS2&U^uFe#_mYzyY*VEN+f?bnHdT7CO_d&OQ>6#nRO!JsReG>Zl^$$Uq(|F4mJ;JQ z#CQUH98=r8)7vHlx~vKUfi{#`2vo#~@ypvL1S+a00u?bb;BdAHw=QB7k0xSdaQmAg zM)7DOM)7E*tb;%iVk89megq-d*>w`LBu2oWil=P0Z(M9x`});Q>EG@okuO2J$xS&`Ert+?4&TzwkTmbfm=Je*4<>vIC6^ug zlaJz0VltJ)&Kb@-GMV}bqP|H{%j8C}AAg#Ysnw^>$)tsIHV9nW(8*MyotDhF(4wDA zC0pZ^MkiBW##b+wlN}~Am?r)j4TY0f*J?bO64tKsO=5sDqw?WsvT;scydVb>?RS{D zK(g9Cr+2i|kdSyXC4^wi{0yy31_B|31Hl)>x+xD%WvSAMzJ+qdB>m^;i&!KbrYV^2 z8Ak1>@U z<|LQ_8kO*vO6AjzO7y8Z+_+EU$&`o^s9@J0SgZe3ZP(K~i zWA%$CQ_^|alPRZu;Mh73%4zBc<+RR&atfa^>RPzdfn!c3JjY+eP!)6BVIG0mq;ZTe zSvh{*aSXjxj-j{8G4xhBhTaOt)Z5c3Cl*hpgmpy1rniWMO>g&jz0ICXIo6@o#yYgx zScd~-i!vZ&IzY}Fybzwo2p%(5p_PG&GblNk)q zWCjBir5b1KZ!*KMVkU-RMU;v$pOYC3&}0S!Bt4xCkm0|R-ld-i0{>vGX46S71%4M} zt6Ofl8zr)~^x2oWAS2tqW#cxMwq$wnJpj-ZOYX*BxrS4TJzw){L;u}4PCeX-Lcj9J zxq{XGMLfhhBA#y7Qroij76*4C0;q#M^%wCrBG)$SuU)(XD`j!p55#TnW81|JZNJaA z?J467#VC7gjFi1e%CyudlbycYSr$v^8?VQNyA1L%Pf^9rH{qoDTr<(td}=v-BnxPXE2syNGr4fX~ql z68LNP$l9gDjK6yLM%Cp%lc(%*FLuuMOzbQ2^i8f}f{fq$Q&QXADq;tOdGs63MTm5(Ye&xgh zCU|dve%nN0G;y(XOZ1yM1AhR2O%P85k=qxRKmRPw{n#FKijx}=$lw})UKxFxEK(G2 zKe6L;{Ei7F_G@mJT5t!l{rho%`fb$PKJ#sP$hJie@&4s+{4ZSf*0t&D)9tg;Zr{PdXsl2vHA)W^qsCk@I@T;i z#mZ8tR;?@)D~+g89oY()C^}IqF0r6KRVytv>d{QCx)2qnri%4?sWKbQl*+|=RBblu zrD?@9TUaPY)1_K*s!^@2B#|`hh1p_Mor%h&$y%XSVpX(W0||vnv~???Pc};BQe$N# zI$W(R6>E*C0O)#ywd#!;+N|S%cB6{og~DQ7I{2|hty$cPleUF&G}Ek1HA>Y=)BsGr z(I07XGddVOyn1UiQ>{f41CgBcE-e;HwK`#@t5Ne*5umKt+Wy)aYs3XM7bovPNLLZylnPL>)1FCD|D+C}_U!AW%fZC1z}ztxLn zD4M`qOHr;+uS0M6bP>u>=9gvr1~M8ei_%P^8W2>iG#4g|N+)@6j{rYgY%CNTINEMB z*Au@1P%E4T3|qlBJhgA=&4RRz(sHpPB;rbergA=U@8~^aNAJE1#j7T6+(0lQ<^#Wgwd1jRdNu6Q7`-ZtX42M7*%Tfm3Z^N0qI;y+Y?usX`ItLzK|>{;j=KWB!+6 z0wJ)rGVuwuhf1j|m1v&cYtZsRRb#FWXAvz{VU$s|QeNRNNWFR%2BO}9h5!!eX4;*@ zF|%z~#d!|;1`=5-7N%iMyU*6(tSYl6D}$Z@bV5A~)6;|1im2sep)UPARjtgFX4xrq zn65m6FDn)+^|`7laDA>YU98n}=m{{mL4t(W3~DAwu*e~>8W;;aUaEoVQr$5vvV?Af zD0r=UGzbq2$UMr!Y>Ht2Q>jfieqd=4xLC3?F>1I7W21E{rF zu8IVsv(;vK8osmyXW)EJy}D4GtDYssD1tUq)bI#GSI~Wq-msI{-A%aqW@SLD*Q?q* zD0r|5$6dfR8^tNWfRG`RSj_tb_zAlZ6r3$prmJV`Fs;b~tSL8Hg7xI43X}X?fPaC% zSm5U(>?>DXo{&#;c5b#XEvA?&Rc7!LRv@3t{H!;{1Rtyt&4bj9T)6@tl3S=x$!AGE z7v+=9E*6Mmu_T}MVSKL@p+~veEU`j|1Ye!rCs&^%*7_WuH1`q55(}0l+04=;i}5K# zD88QM@3VCxK3j(a+}aDS%cV+l89Yz1BsVXg=t_KtXz}R*7Yb9mWz+<4LAn=D3yWk0 zBM&|yorOY$a)Rf?XSGfNEY?W;*=lWm2F59V2dy5e%{DpU*GIIo>F}dGZmcLYN*FNE zxk;1lABZNa)p8NTwTzN*M563wrqqCYWXv3irUc5ni3p%hfAVx}r80|RP3SlMfd=BA zcyk-^qX6De^qV62k)8wX1l>`72I}sN5FT%)g7IsnGBR6lPUdr`Ptz0Z&b2+bKe~}m z+8;zW?s5;Pn{JGZrsfJYh#!t)Yp;eEr73M`2Kt-Now~m;_|T!jpWQR~k%_*+-BI7r z86pSEfHP-p;RQs2rtg4574e_W<%Wi4DnmoL)42%lt5JX=V+>e=6Pb?NjRX!ZA1scL zxzf}CaQACHc!b4j9Ttx$2v>iel_PUKN@agYvlASz7OtK}v3O+3ZI6o!r22=QT2+f%jfRTpSpkdnH&2>1EC8XqFF)M$)hu+z?58uw^MjJ z!r`=*??2N|@e@kGyp~fIZ591Ul*G^4Zs&Rpn4L(p;E^H1NS@YiLj%i|c23cJLsc5MW`NUlCOp)Jz4I47UlWj?NGo2e6bG z=yXH~2PHrvq!i0_m|bt^qJTV(AUSw2%p=c?siQo{7EI_WI+9>UH_bVbB7ibXt~l}Z zX9%hwPuth=iQmlACI>MY!o-qe2=95JD?vr?qJy%&A(*t&JsIEgPJAO?G=yJg&;xV( z=yjl+kZFdPP7#5h4W(NtG#bSPOm-31I30>{n5L))AQ~A-gBHOeI=NAuu8yF3X<@Nk zlw^rR1+45=4t>@mLpLCVY0k`iJhmk2wN-F3cat)w9>`K`?X4G!P$L*KD>!so$T=g` z?SyI}LdONn+%fPlC$QCJ27sf12&E`msA%NTkS~RbAY4YQ%v=PU(dSmCPyMivf^g*n z5eS(sVqgXVP-TqZLr5fXBqx<#1BZ%JbJct=!XRJ=X|JpRhu;HiV@1et zQ4Ho-GA*TC>r@kSm_Y7kFdZ5RaP* z6XK=LixP44HGePRy~GBVk#o@nl3u7jRIfH`Q^gS%74;3`f2aCRVes!egR#HwOrJ!{ zZmhN1J_~Ue(t`?}WrLF1iDn9=vJ5_js8JvM81;Z69-uW&)7}@6J4C33R1T0KG+|y! zXo;m~%8H&)zkyHs8W~dY>8Krv@kit`cER)g`1Lk#dTKFkrK{A%yej=X|cOKGG+#r5}h+qYSeccp6M%j05>0beft| zq6ZHS4(rTALZ9?wIO-9@V)O!IKl-lMN#4}j;#E^CB^)!XDUcTuo;zKEFOraBBM!{@ zt{xig?abWxlrP*%11T{kXpnDW;suwC)*FZ+3N`VU$KWT$*vG|D?3NgXc^lH$Ow0RgMUdc7PjtMvPYUxH)i|IVp}iQ@mxAtdm$5vRfm`xK%|@odu3NCp@ID5YVf39V5N zq&7d$W-4b-%~OMy+GTatb#zCMj8FrsEy&VgDG)}>Rtu07B$sBvIdSwiRNBZ))1o8q z(!ql|QFjrQTa`?zqUdUt+jC8+?`vz%G)C4v-(`d7#lzJ_4kw}u46+KaU0Kh~QuQeS zUyknzJ0#(|;?YYSJn<@*9ekDcwsBk*8(GlN#JW4}t;P@_dA8V_VjgTK-Jx|rDqjN; zlNYgDRyP&XMo*<>tdnmle1b3W$+}Q$nxw1kD&KU|HCfXYjidWusH&~wL0|^$y6(H1 z>+T4kl`2~@MCE~KK6DvsOY?y(0Y)8-+6>|gu^KvLW3TLai{*hV{Bd&kQ7FEn5`SI$QY)Z&LSuVPVq}yRYRE)JpFyY1{MrgJo z&Imn^iJo6?hP6o4n<^Wcm~gAtKA&PVyK=EJ%aWjJgh775lulP2xV~}FhCYhgP~;w7 zMNUytEG$)KAvqthS&?%v<3!Jg>WUc3%a9cgOwP^pdbXGPF{Kd7Y(~+BF7|g`4NON# z%z#>m7x7Xo;v1n+B4kVp2AD)Pg^|Wn&4AMD8z0-}WD*DgQ-Gl%5EyI5jl+Vgw^zy` zQsCmuLCN8qp}m5yk@1*mWf8TPb=(C^wQIj68-YTiR%F$ni_RD{TdSSrc)Y7xQSJOB z$MCp=*gu(W!S~KQl@fd*2#^Wad`3PH6ZBYYotk7PN(X4dP9m`RRM+!4XW7+SUNvIK zY4M1fbes=#Lk)sOjIIOj#SKWGmN3(Ls2KJ(Ej3GhVR+fp(ixJL&f-b#u2`?%g(oOV z?i4yQ;VJU-*_z7egLa31=^7i+ZA;hxK#8BHs)4F^MUP0|yOb<-rY5%=4NIxs&b=pu zMr(|xW`q)y zLtJrnm)C=plXF|IS2qjZ9bGXuS{siB2RCYl>`w|odZ&XOI=a0??KLajT1BY9(owoj z2V9?9sN&p%404X0gqShl?is*Lz>+wO5yo_!mamJ*%DBA+T#`07gB7ZJaS_Y<*v=wz zlo}VUxX_QFf;+C_$I&ddM^p%e2iO-v(PP@c36*vSVlPel5HK)^}{3F`-AmccXdGJLLEe5 z_UoWYpKLP#^N7(oa}PTMs6-VS5o?O~h{9y3i&!mG2^cn1tTs3-oI_xe7(j->+GnD{ zM=Lu1cSJ;kJR56Ip#^6dDnyem335=n8{@ z;WWbRmG-A=y)8f z0Y(DBS(`28Xz*3FHVtZhWIx z$Os^nbxLI(DO=#@Dt(@~@e_eNJCI*t4hl-mn?JQT3A#51-Aho@rvy2Lfk{(0s+=EK zBIuL$lOL?Cnv5TT&U4<2M5W<&(0L&38|K6>!_WCkc<3lxY;4bfeT0r11f z8kGnE8l~Ho{LX^nOrcqB=u;{;mWfYsTb#t7r}2*qDJaSL5?P=@$xdIg(_k8|1lts) zQq2{}=1A13xB_fp6o@^pU=Ip{Jqac(i)&zh`i(Yd-$1MThR(#2bHIo5Cye~wtv`XQ zHvFfiV0N`=ifuEDbz_svqisHhbPk`XG9`#PO!tSV8InvsK8e&PqqYpGn{b@W(-Jp% zJ8_$|ov=;TPShr8C+KWBH;ooLI;DzaW3|-L=;dzg#6T2H8^c?BeNFHle~)2LI?U<5 zQW2IaqpGlen#y2e17iX$#nm+z(-w1hdeE~luHIgtPR30RVg$$#(_){cGVJYVgxf-- zE~aO(FouSv;L`C(M`8`arw$&BA^ZfxQ|YG&ZqAk%)}V$|Jew4BQ9oHZJbL%mt#+J* zeFOc6SGotI9(+QhgsqfZT8*#*iF5@*Q`2US@{j@JS3S-nOX$TQg@=t@oB?4KJ?|E0 zL6~;eOi3#92#akTbIZ5b5;e(bIPh{~z)VSFq*SY7SBi?}FjGRBtuUS6c|&0*>f+~A zxr)Une7A~q=hyDTK>!>u!hsAXoRu{!Q>59G>=9_FH|4BSv|H0tj%98az^VmJ(k zvjv|{2FZuZg@r{+hffw8XEAM$7G#BG8jUTdL@`BZrUtM(Gc6F*%461{tQ3|73TDer zk(K*ftxkEXfw0?bAm{=C?gXE&r5L2-xKHOIUKfera!aPK|kxFw28R zSlpzz(1X~rfGtQ`%6;hKnumsf83K^t528U6_|-2v_%uE;`v6!ldS`N;=6V402=>?y zf^}eGhk?9D4)VJVTb(;RB?dA~Ie%`HWN+dCW*^|$q~_#g+JFZ( z7?1}novK}ku6NKIgJ0SQa7iJ^7>^%NAtKR?!O}imoU6i+hw+uFhd*pj9H^tm8id*9 zm-gW)AFj*Q@?~+gG`s}LG>jkm2H?FcbU1e{o3Jifw2s+erv5UIa zjf=ERrjmu>Od=uF-gTsEV(lbXzdS*TjZ%<400Ty^?;TjO-GWJVpp7BYQg-4VzYOZw z;ay2%fYi0)_jKuawlp{xDzNA{1ZWN~q3Ft)F-94zqsmlVKsZ%iSBJv{i(yxj!j!#) z12k;OvTG~&4#)Zs{=m6%*Ta4UcK8F5c`6vsIKjm;PKo!sBc{fo%ITrUpC{kTpWoJcB|;wF+E zjebcrsB)n9F}0kSfFF?DMw)K8kB+3vfnA~5pt_Ll;oIm9>YGF$E-EHJgg%^21!-1M zeQXY87@fSBm(hL8$ijBF>AX5uj{c1owvsniU3}F$J(}9P><3-~~9iEUrn#rxSP7 z=FEp@mL?|D%X;xIAt;I?1PWf{u25QQVxstIpa`mPbfSK3)NkkHV0c|tDl>;a85iI- z9yut(Ucz9)ooS+6&S%qfV^u3N58?>M+%NvYJ<Z7 zXr)^O+c3|rLclc^Fl_W{XEc+sM5clZBMzxXYMKP0({tUGQ}Q2{^`!B%csql3 zrEcy`@#KuscK*^#6_64n)8(-Kj>b*a$>=dtnEA&#>uzXK?=ke(jTAd(WVMBts%51rKl!I~L(R2e{C#R-WMVbtOxCkL@v2JYx zv$z?Z{zYPO4Hw}m z9*!tM@GpNMLltNQS%~*RebgP+u|?zfR9)5?G#t_ysk%CO$Jht*f}ZqsShrKVy4@YHHovCU)4;1}3 zz-Vpo$MT~ulCHPFi0Y@}Yt+Yd0W-9ttiV8p=fFjHhD?VOTTwyME{E~Jp~tyQa5BZ< zC1IwQ8ErW<(yYp$-ee5veIX8dfNje?=rexa80nZscO&VC^AZ-VH~G9hRv zkJyl~v&RInx-~9Mpe+G{v7#o})hTGUqLi^`!u5ZN1}hU*3BRz`!HvC3rIA@^0&gfd zg$pZisCTf4wQSompW4%~RDv=1X&^AjuurW35xt-X9-%}pWs@mKRJ_kM(@es=X1Hsm zR9WPWHVht8iOiDiO0cF)!ySK+&Gk#AU{^>G(cl4-iAy>=Un=ph(odSn27zGm~;ye=<lrwn&a-_J$v?OCO^cA#WSooc(j*+OSG8My0=to;Bd4qu*qr& z7K)K9(1jwq8mc<^AiAHs+f*AM!T6R3VmWDtZ+U%!TPozT9XLTIfJ=T{c9K&L50!Ai zLSDDMjF0uR_FV{pvHN9)ZMZvS(7&?gcXapNT-(F>X#hsi;X) z{?SCURG$M1T|GK{&#^m1L5|%q#MvAjrwh1t5O?~3Z=8}Ob)p`ij9vh`@o~4l zgT+p_3kSigKnBAU3B)i8U7=a>*9bPrV0RyX!ap!~FHArm#5wXu@)&>v8REK-z|v>B z5a@?erPwYDhvU7q49WvK0`w88`Tn&0i@R}^N7i=J1uX9BC#ZEwt zn@c5Swq7?mif(kK)=7aRFiCZkXo?;>xC3fn=5*9{e-NWy1j27vV#5{>89P&oD_(e*{5^`%9n+ znGoOMHG4*X1_P{(d^Qr^1HU&a8(YX1wS0(7xRsi?d)%sr6CQWjiZ7kOxP_aJK!VyO ztAjYEA-ucwt07V(;*E>cMGb>7uAsQZ5@3-*!ELhSV@DZz#|4SKy|Qt|1gtaI4s5bS_R$VBqsNY_c5)h$|M~*A1H~`t1g+38bCn z20Fb8OGi!&%}{gcoZ>RZaf~*+TBlko&0>p6S;i&Tg|#A{VqAlqO-&MaYbiot1Dq(D zb9($O8Y7pO$>G(rr8+tRun0A9ssk}9)ngKK5QeqW)nXl?1^}=Rf!AB&J_qhbi6AU& zZpR(MAQ3e=Iu|kTR({*AfsssxJ?WD$>XBOPW~uoVo~Ur6*W zon>*UTn`4SWgD043T#M&`L%fhZ)j>}$^CTRgZSNUOp|~X(K$L2h=P%j{DC7xWFYi4 zYH}a0^UkVDLRR8>MeMTxnKf#j=#R4o%sjE}%XA1^vg9ty_WNC=RfHqrgX8%EyfLn( z&gTwSDW70+MorHeXfh*gEe3q#BTRuRzu^n!w9NGj+Q8gM{_!>5aowYG2t31vA6auJE?JQW*6)*58jwCIuJ@&)t)jxBHkm8^^p_ zk}oPqnQrUh6HCg-jcDS#Kfc@Dfkrn>cREw$7(;%7LB`$}K*r?KSzHkU^Y{0Z=`?*3 zkLSx&4_DLP5a2*Hyl~TO7v-?;aos0lfUwIizkAJqptPZoz3o<*+}3$Z>?mS0*7i)A zh|m^zZ(f+targScC#X7wu%}R(-2P(gnQcIJ7|+;SL?ftb{u1}x!duGqvG!aJ;4yaP z^}I0tMcBUFNQH$I{(}`cLBWC&V7_O8(}U@|8CEExG%AnK-h3c7X>cK~G@M6?fR|&- zqPw!yUFd63WpG8Wl8o{y?)um52XQS+Rp0NbSu_R4cXK1CE-ToJgA)8FklnvbvE{Zpr(~2rM@!OQu8N8LUF# zyzfK!vA3T~dQf0_cZBGq>KWf6A(L`bWUp(IcM%HZBk6_>k6j5WYV4c|@7Sng%RJL} z<72TWHV1COhRnjyw7fQh?+?I+VNH$uLB=Xz;=|~K0LWxx5h3#6$TK6nXT#}A%m(L~ zg!Q@2z$HpKvYwe0;et8nI?}uh{n(iHIi>o&JO^?ZjcJR{Y0nI>*V66AQ?>ZQwKVnh zg+i?nvmmx0w;PjkX9_ZA^yQJ<74I98fjiuBS~(9RX9Z|`e|!Z4OF&izh=z2gaUE!< zy15Csb>0lKmF5h!U4}W7t!RYj4HJmAuEd1jy&)tycL(GIdL8a2?svtPuE6U;>FI(C zLt$UI+7W43ci-OD-MjUi3nmPSqb``^ab)E)+cV1t7V{a5*AW{ z(OKN!hPu3ajLSbFIR*+xjvcdsz8st-i5&oSA>XH}&?i3NVIiNlrOBbyV`po;5NE78 ziM3`2iIe>}KTScx#SyR#0^2Cjy~nTvMUV?{9n-Fd?dEp&sb5USD*s!FJ8H>gEIEp~(YG@(A-4XQ?{ zS~sXVq3YeB8iZhPoEq8-jA=FAYsD}vkP&cRvLQQmp zg15w!4Rh(8Nv9NV)q#hSmwU;8743wlRhJUGQM!Bep(mP?ILj|-LW>1uiWNpn7`A>ssnwC z;(RGJ(*J<=Y5putwDJ+pX)z_w78ELb90H$7<4H1p{)~%r7APGSUisn zY|(-T!C0#X)u@lN((NlWR`?|N`%^g#gSdVq_;=8KALL^0DOfJ|m<2E%04M7R5Nr-p zs2E_c0N@rF+%d$vIP6~xuwMYqVtU1!JN!QnFnqIO!1^Yxy^cXVz&>pPS%mrUIk>Zb zH)|7o`-Qyz(6wR!!+Z8B0N!}Gn1C>Vy?gc_5E`a{p;m}Va82yr^AROd1toYC$}k)l z{)iH}ST3w&5IKDFz5_}F?pjy~8sPB@{@b_rmYbD^rQ%!(Z_9{b6yWBYZrZ0b7=YxI zr6~az-Z#AG7Nen7<^D8G#0`l6?7eyK@P4HMn;>z`1h(J1Mg-udz5DkbFga9fi+EMw z8I!LPISeV?bc@n}#kg9jo&wlE{E_|p_GlA}b1U*D$A_>)D`0Hmz`g?qZr-C!s3h?w zO4kSghHt**mRt6kKA-`->(o6(Z2;`schgP#_nSTlK%tCDxFG?+&9@BiK^xZHqSZ*n zV@0|+4OBQMx^Ms9VYHz%Ocxdkv)o|oIt3?4Z$7Yp-%a~(0-J6hMQl>CftrbLm}v4M z(a`GDFNC#t-y*JE5^JZq(?rcBvKO4`R>$j_E+Gt7FX=H68|bjAfdJg5IZ(Uakf7Kv zSmchlio6Q}*@+6=5UvKD@;-&pVUri0BU^za0p7_{!q(2vZ}@nkQNJB8I>bI~=f0NU z)^tqgw>`QHognZS_6N|{$F5yT#`3P+H^XfO+_!^Ns=PKY?esz3IJl2JJ___UKJmw1 z4ebkB=bG){$FP2i!QC8ccpW59s?bFvDoNY}$+R^KSXb`t;9fSX4|o|ga>KfOx5pOk zyn|PK2^UX%S`g#$=UGUA<`PpVT%e@P0fiRCC{Q!AqOC`=9)N)rfJ1m6v8oNu_Qc`> zRvSgYs(mZT>a-hw{6&~a93WZppTJ(KC&psK4$L}qCKx0# zpD7F7%&P(~hvkC@_gbPiY(oxiFn^hB8mBtqOLx&eb}dWeH*cVEIS$13kvMKUjt4^I z#7ICN*!Nr4RW0#OPkDt2HP`|f-ItFSv3!wKv?136sl2a^^P^jnQ=-5Q9*Sfy&E;?N&VR%;38uoT00w)HI^2?C)CXL*qF2NTWkRB{H69jeqn6%TlG;H=*b$&#hW<{_$`eBgUuXlH^K7Qz{=Rb zLt{XL+VS4vzQK>{7Hk~p>5ua3{dDi#TpmlbsqH-MiCZ4)AMig`!XFQf^{<8tPRqj} z3jaNX|CaaSJN|oUZ<0hv5${D^kE}G&urpS=oI1i8L`IL9-)oZNS|~&eA`|Rlu3-24 z1S}7SY=Wgg0${Fx6>)QWrM)2-cn>S#*J@SBT4=m<=)aKuwpsr0Qef zMD+*|vP$ezA1GjiV{6cSJZ3XCezy})-s7N5eGNv55y+XR%tYOcF4kFxFo|9WgKAcT zi5Zs2DleaB<`v|K3E8@X?v%cA3@bil&~s*|P;AjhJdE22kWVBA`tu_r`i3^OAP!e@ zpO;zsWgzMsQbelL#*x-19f2f}6!WBS4S&EKT~L{yX=s;)V;B6f56w8@;~9$F=;TZ2 zIgEbu*Kmx0Ma1hjOrlvGh!EbMc3Anw>(hph-!bF>7rwYN9&|DgQ7XZ8SN#w(J4&$+ zI?_$M+Y%jz#GXu<>y9+x8!Bbn=vACsdlfq+Bml9q-rAdV`}&--2e53LE3t_Q8AYyH z;+=YU0Ta8JX@VK-8Y|YZ((6n@-fhRdfU;UBaBZ4qe2T*8yYNidYE^b+4vEs9L8hX1 z90%{}rv&;8tRD>vm{pWy$<&-S9=r~XFhdhUGp1x_ioM(j?)4{c)rN2ZqujC}p^gnU z@sLBiG$nvwLInCsxqC6$!RA*i`^szLUC!3`6=Tc0bzPcor!noOPT^*C3|$swiwcI( zb-cqN9l12>M;%5nT{m~KD9mSz{hniO9?B@-8d9X9A>zElvasq9x?*+U8^+5tD-Sc# z-CP!SSE(!Ich!j;tw&bliyN7-+l%@Y6B!F9WI|_6 zSx}AyF+pljytV^fD!J~1mO~|=(t(n&%=sXE2hsQp6+>G^CD9>*LyI)b@o~0R^m9W3 zwO8vHqXr~)I?#&C`*G#5P4&>Ha$A+SJqvs3`I3WLpteE}#p10*nsgeKFCcADur0a5 zWnsAUrTmGio#+jyQnv{C6?`!3go!rWh2kRjj_tHx@E-VmZ}5SEL5DntHuG_AA%Fsm zNl+9B(aU%!^awe#=5%#XJgvEiM*T4TGhHV11`$^L4kP?_!j?BWIJ$D$lTweNNMmS% z#v`#`Zyvu&73@R7ISe|eyhJN;p~A|Jc-dPHLYc{eq|y!?&1aSB%*+u+5Md{8g9+L; zOGXcI_{^))%j59;J&8*Vt%{>R^2QX8s?tVEQ`{IU&%{Rso#->{1oF zw_8-xIOxT-ZlYp*bINpSW(KKiyuTT5XTcxtfp*brZ%CAO21vvI;gvK(G(=c)1Lz!X zK&DB~l!}NMX(LS7AwgpysN|J6U(!n-s5*G4GZGi8snUur<0g!ag_R;Lt#XiJk`8i_ zVk-FKvkioPsW=HE7vj95Dz)PDaR#T!2^5;`pp0zMsV_9rT)uODa&BE)1Bw$~$lCuW zf&{fy^jQw-mx70SV^GAU<1j}N#qUzMq#hMv^yo%AV>yCT{_$(S^$G{wu8FQ>ScH46 zv0WUe{`I^XI*}%!a>04MI?e(KzI$|gvIPp*$3Mx#5N@F#s(|zfuhR?8cDnczo`JIJ zSMB%rSLP;kd*yVzA=l|YwnjJ#cu;i2{lebKa<#(u+p6-1DhBaB?mK+>A(^`W~H5(9EF<#}eE^gY=%>m1l?K>3?V{P+6t&Xwr-mk~z#kisIoO zalJCxTyfA*F7yZ1U=u!5`mir?fuykt_%nE4)>ufD=K zSUVW{U`9jsfOp2`-TvhWW&9a>_a(kh|6M!T8iVxv+;<`^-z)X{?_&{K>yW zB^98I1sUbcelg}G>m93`dWNuc3|__-KzHOTW(&F}1Je3@IYu(*U55{x1`63|s~mIT zAcYCl6ZymkWFU?h+u6molD-(J2Z=C*2foKUaJ*7+42dZ$yxKurVB$uP5mdw8btG`` z#myjiKAxV}H3Ve|6;1ks{`p;bV2AYka>8!VwkR$3?=kjlcN;ns^a8oJ;Y1I>Ky@G) z-GI+@Fh&Phu>?L?VA7rUrG{*~Ka1x$2M>T)szI2`o7qtNRHCnAE0HvsjJ(>d>T;wz zqz7plLQ<0ho%NP=lMR~MP3*Vz`v)%|WH12tCgVZsWw$a0gkIA-nKt2S=r+PVRPGc~ zlR8A@fD#eYkbV!vOfNU&#^&zGRS-aS`9pI@txzrJjEQ$@r-$H>*w`IFuIdg_LyFga zah$XQ^iN=&ZiGo%r<~q_u1HrNDd8<}IIe-6mAF$JRL#Kr8dRmrn3CIUJQ$?u1L0>9 z^u`l)1wU%49{V`Vmzdai%}891 zo#|O0JRnD9qUZbYKZc$mvsrJhRw$yg1^UMohziLX%-un%Rf(#Dk-5n<)QZ*3rvl8I~;-lPyg615fRvF496pXw4bvGJ_G7S2`mh+zZ(lp*oDvkEyEAZk| z-^}SER1{#xC{>tv(s3R}JSmp`OGZ%l@gh9A3~^kI$T6w=U3uFbFEi&MI~tMbG}gE9 z`XKSp=o&hdzt7g-92SRt^5OnC|*I(j_$4NKy z92^u)VQOT9Atq}4fJO{!I~``O+W;H~a&_wknjuGEHod2wgJ}i>PF&#%0+Ov1R}gwy z)+o-Q28~TkvMJ}4+^XG%8{5a-2<7r5PZP(nu1A+>GAmVphLCb~Y~;0{U3HC0)G6rh z+=-;#;awo+|LM-diG?2;$w}`bY0Oi7f5hsmf1pWvM|e?+(^&Lo5nfs;u&$-kB9(PuD9Z37B%J0-G!|HVN?{a&D2Sq zjqB-BuH~dwX;L6~+dJ~axUr&mFt?P8?9K0VFglTr*_F*9wIxoTJ8{h2tqq*wAdH`2 z0~cl?h=ol+2~o&>aA!;9GMHT8$vfqqYk!&yQRYPE2wyNIwuEtp92~{6xjA6d%0(9F zQ|@@bvs#^Z^aw(}N`O0J@Y3r_)Wp5DNKb?ul%}z99#`JESHc4pGinC!#?{dyK4*H@ z-Q(et3I}Uvitd&@t#w517MMoh6z@aB6AW?z`INvZK*0Z_@A=qbIDCzrC&Ea1l}$!A zgLQ#YqpK{$9Un>y8gBSi4hKw0@;{369-TnqR8}~{5hF>8NN3Elslf6zyn#kt}Xv+x>GAdC>fR&7h{0vh3CKETA@w<_kK;S*-u~StVf^eLu&=UVRE~TAzQI#M_ zHsof^*$|XUuVB#0t(=Eg$KeVqehM-;n85=&g5#bpVS-Lf{ZHpG-?NOo2rl5rIXAza zY+u#Ns7>dh}fpvajM)^1ptI3tH}-Xn20-3v>u+L)3}I4{z(qjLRT$Wzb1*) zx?xSp2%t^R2XoB8>QnpSGuN4bNjpI>gMrT2IF2P7XjZu1Ek5-Wpu(|`WYdsP+!2;! zyLLUSjXtdBEQW2yu9Ze&S3@NaBOl_0VeZ=XHqR?tL-m+FzH;8)tOt7ZixLtyxmba zm7@jue)S@p>zZnGtpHdzTXr6h2s8GHd7QBu&HEX;)Pb%mHu_!4e+GSqeLy2Ye$CPY z+-GO&^a2_08+N5q2S12OT^YZLD+_&>N5qCh+Nj{E zJdmT$drxwvr!p{rTf2z1PGfPKu`AQJXj9xsY%O{;4#T~WLFss5duhgNYq7tB4nYTX zQ8`}UMr95FUc@2E3hM@NkKAc`{y)}wU`(?_@RUI!hUa3RU!c!o;BK+x4&(=#M$BHn zM3ScAmmxnDpM2tjyU!S7z(lz6qohkLE|)LDz@Qbn6lN~e`Gw#Ds&r+L7>fjGT{rr@ z_Hu|xH{7%Flk(z?mDs<=0Uc0_OxcHSTAejRi_1DJg8+FSNFE=WYc~ zOH=zs3`3y*-qQje6q(O9mxP{bRFzrgE-mu=e!j7nM#R+>1K!aaC$w32WL_$KU14UgJ%P!*8%#xgbAb|cuHgBbyI!4Kw@ z+{B>0!Z;YW6e@%t{7fqFyKHDg7HqKcf>X4dd3j=pc3Q^^MW7>w9ixMB>bX9Db5Kck zHF2=8@B`yKcCR5V=7oa@C?-dCJ0!Gz9J7Z!WE0yz{fA}LMq~|sd<^T!t|+FJj>u)+ z$OIXaJV1aTm7LLYNYZ-%nsC*}Wy%XeGYX-TnAV36?!vWwk_FBv1oUwFya9&9sbYD& zK1O2m(q`OK`db1Sc``HyVZzegux0(`C>>E`SWL#f^Y$7DmAnn7;>lBPEvJKH=)!GPZr@;5fxh)yIkqzk-&nC8H8HK;Ka>VzYX6*+a_nz!2mMikK0 zP#~Gq`{_4@7KWppn?$|_;dNN^4kfU%!enXbfZ%`Nj$ zA~H4M#l*=a*|<6QunGWC2&%-+tL@0Z0{YJeeA7Cu9P1bn2v(A;1C7@=kj``eNW27E zfI=K!gJ2*iRt{t=S8m}t*~`434mU+u|Kesc3Df*i0(B5=frvc5L0oJCzxp4>PneSP z9_CN-?Rqk>)1%lqki>E8YI^90mP0;cPERekFd2)0pz)AXtY6j_In`~DiY5r*dxnPN z_k!cDAi*8OftQhCxm?8^-b^|grRcD;mCK+=?JI7{QA9sRYn-YsV99uTysGTVi08|+ z!9=*_pD8@;&&5S?@6k8sb16_60@5%L+;{<37EO;exQ__l9tb7d6^HwNx2Hx^W({6r zAsgS461*Xz6!hls_rePw1mVax-Lp zaemg1G6=}=ldjnhWa*wnBH*s(g4Q)ecu_qt$hIBS#JF#028VU)u@X=KW<1Womp}&9 z@E)l|ymZK{d+;C;h(~sF5v&5}cqL(%mFnB6SdwB~ZopCZmPJ~tYa$tHV!1hr08u!loRSbG@K7D>85 zBw$AZ68@IuD2z=YrOq+OhAD)kVkxmgTRzAQtb(gBS*2HqixG5rZJ4MP#}fwnq;kJc;|kdz#6Ds=pk2=)!7k7NRL7 zQRBJ`hn1N90A#2Vaxcta(_>PLFqv+a8(y76XP_VQH;yFCCqi;~wyu_i+yWLtuq1?U zNKiu#|55Z&IJ{hSlu8@b#%>yBij~>>S^l=Uh@I!fJD~^c7`5(!Xl~w1NKNdpz71h+ z-b~JNZ+T}6F35BjBq#b^QQ#I{#+zsbMz&L8PxghKuap3k>Ub#dyo_dVbvv0|ms$U# zck>ByqM)`pVYs%SP@K8vOAkIOU$Ck$8SkzZ0CoD#2t*9>relV1c4Cri(>N(@E7M5o zCy4<@MBu?y#iRo-<%6^6w=PNy23mG&#JnKxGy~z7SlE7VVip|{H<{zL`mmXU3T4O~ zaBoie@@H+_{ReXC0q#6-8&y+thIT2_R^wjx?Srv=&KflvU$^h2qV{%s&qEE28%{qc zGwa;Uj^_>3(Lq3gM+w(B15%_m<>^gKj#Rs3c9hlv;&*j=V+{v7ISxy{F{N@MFcn=@ z8gr|+w$I8)TYK)D&4y+b@4kW>`dI4T#L00ml%AJj@FS*h4;tR*?ow(_7-Tm!NhL0@ znz$n-yIEd!A?lu-5ofe4XVu|ekOcA0KBH;DmeDpvfNZ|XTu(1pk%-&vk8_Ej3<9tt zjx~Ie9QX1_c!GToFq_jk!INfLwuX@2tj&1qG`2sux-CvUV|aC^r8P;$?x{1D32qg| zrhrtS;~r|nm5gc5*r7tV1tJmfxQFXhJVlwrd55jN*^WI6Ep#z6=FW)DWszWXVX?6y z7fYBQs36HK+eDf1N<;x@u35>lg3Hx^#q$4*1+=&QcWwjiBwW4~c#k92YIY89kW>jb zjguKFAqVE>;c9}@`?0S3!?M}B_AnQD{F-s98Mp1sZC>!Zo4m^Sh^UU82D8X*Vf*7W z_Lt($T{mBsI8l8bTf!bw9S+R;Q6|xDZ`*8;P8q~m<84APTQnItPc2@@m1SG6fC&id+*>ubn*#YhHl5hlTK8}GVNgGPVjaXT4>pz+a+$@CK2=l24!6Pi!&nE zr?n2W@-8&zfMx$)mIrR*Qzh)3mgCt;ONDUTypBR zhWTc3XQfOi`Em#J6PIqoyL7$eO@9nLaM^aN3a)g<-mfa(S>~=hwQ)oZ#+IO$FsJ6- zz9ciAt4TVZH`EI${G!yz_%1(#i+RjewKhT)7*~9GHeA~&R&cXSEM_>i2HwmW0%~tt+~U* z3pfOo-CST@!wS3|-0@*}vZSySiXc-6-YKvlNrOE$GYd=7QQ)giLWN><7s9gZQa zjTZ~lnJ@8z!qm%%;-4EiWfyyK44}_+|FSb3=!4%M<;6=JCSN>l>ojRO-c_M{I2bO9 z$_Y6gGm==c7G=Xh>D$R#cq>`N#E(WTdY)6p)y!KB}e9YC|PH3if23p z)+PfyPj(mz{Ep!ei=Qa$BM$Qq-uU6y%q`?`zkUsyLvUUm?`NlAu^2sDL4J@4F4IF6rrjx%9y*+$MU6l?dWbA-wWPsm(R5cnfx`xQ#p6!DAF5HIuvyOxqVAtRp+2g>1G ziTSWaTt2*r+lLn)!jB4mRPduBcM%hno7Uf-*B7Pq)6Qr&mvx8lyQ(A?Ct62?RtT%% zH`V7!JXp5JxRoD|)!oN8|AMvP+Fmdj{^EVdzCN^ycQ~gJKpa682qyex2+ZU_4`f-b zLx#W=E4GT-t2Z`^yHRkgSg1|S9kt`xJRzm9-4sLXan}g5v~Uv}>2aen5Fl%zewG2^ zre$p;FAUX74`C^lYUgQ0IhEI09e(;pgbttaw}05lgwFADn+KcLTM-Z%mO~V%0 zirE6NJN7zBw{5am!}~&;^^i*~*$AJ`Gi#0Z^L=hq5LXsA69g#NV_v7VeR2WYg46do znK~$?qW$Xaan%g5e!oJ!ClG;`tD?SK$uhb-!8hFQ9xs&VcaP!?uog-!6qd(%9WmbM z2@d#-opjF^vUl5?TY!gUr#j9Gli;;kgBJs}xN%TEm{04%Y>D$g~?Nn62qXg@2 z`6@ji*z0srLqk-#69kp51+KLGj0_CS6%T=N*H&ox)>{f{2B*mP6ZNgtWj810rap=6Yfo< zIOqLbA+Sl1yc?8za^nz_81cfIm0-4S@wTndCEri3DA|cjx2cTt z?1+fy8RH(7eKt@um*)l!6A%Mg{1PeQdtL`!NuBzfJ@5WZ-J)tG?n)NablGRM3!CyN ztLFiU5IJVMzy4H%$M$=7WNJTD~i;s?0A&=2vd^^m;Ny%EGxIJ|O0f8vJdUMFXtH8c7p zQNtBtvLuoxes&6)M5L#Ip6YA@R_PCja>)Q`K}iBC@pXLUMTSDhJEde=Y-+&EP};V* z@vxq|_C%D_UmlePW!pH?tvl+S26u#Z`kZ;YZM*Kdl?qa=Q)YgHnxD>{uBZw1=G^QI zcdSC4xh4-S;N`5s^If1%@7?!5MYJq)7y{79iP!Gu4P)=(w(?dT456-8WzY`;(7rL-+YRz^!#{_Gm|jc_#_Z29!HEJHF!!LyKwpDG@j`$G#gduU}@@B zUHz>T)qzgq1+2xIJGQO}?Pv=&Sp%WoEH{ZW`f^;qR-E4!n^QG^OX^^7WGcsu0OTv(}VmD`&Q?B}~3uX%FSx>h;F<2DXjR`9x38Jhyb z<*-iID#xq1pl@`EJHt%~U8|g^RcA3i)?I~-NDM2xSosgc+_e>SU%gr`2Hn%O%J^8= z0!5ZpCE5L|r}vh`NwVJ!iFSJk3%3+Epolxcj!u^v8-+!aC*&=hooI;d2cTnoCn(3Q zJDYacJ&VOel{<@ta%1lHLT%$!K7sqM68fDf$V4kgEA-&Zz>BJOkbR=c;kjmIzBqk5 z-wwMdJ=c|&qr6=q(aBw_+*K774=Q!7q7>bZxr@`;?p3gPgcsyhrd>l_t58X*wR>=@ zl9O52Du?NieK&Se@Pv@riX%yTrfVyAU~qGcZ?sAcDhW3nx74*29>0fDdUeS*)&wud z;dHQ}`d-m9X{u{0fpRG;nVKA$IR=gz-uroHp(F;QEd*#%0}Wg*# zl10!lab2q%FX3gj)yi>sEwwb(wF-4BNnH0TGL&5wFJtoFW8v58b#3Kded%=2Oou9g zlhPB=?)LF1WS0=`<95K7az&*aj^$;0(Dug=l;E%M>jGeIrZDI zixIaF0B6;u$%$j%=&y;5F zF0a~MWACD|V_0bH)XbqJWTR@*%c8?~6qYssIza+@`f47u zt%n~#s4kXuB=`0z*3uSa4;oj8s1)}Uxk<1S+2R_Go2TTZ+cjJOvcA7)uOcOq+udL@ zkUPBd#(~9)T_!mA*m#XQOJ$~FHdLWY_a?-%eg`K&ySb+};4U6Y;Kz`pW0vAbd6v4c z?wFNTVLcbr4!>b79Xo=n)f&awYHdYTbfLP`Sw*p$hWfkBs1!9j@+owtxh* z-cc`WxE=d8Spj#pt}k;CRPERb93C-@|eX zUMC^Oz|+;kxD0@|x^?XO5V-SL5kqychY)Z*JBZ;HtBWT)YEKM?KySSOD+W}1T30qm z`Ax|pS60VR(iq=lTtv?t=I>6}w`ww?m6Jm@It6mYrNz`EQW0Yo^FYJ0tg@jfj}T4fERX6jN)l# zC|dg@L}Z}FL`)3a-RbI{zq6|+Cn?P22(dy?(-fpDHG9B8b!SLc zpwolngHInM6q*@5KnQh$V+%K!+TD?Sbd)+$EX4s5)>ts8GSO0yh_JFWGjo7!j-80b zAaVW>u$VA!r&wTg#lW5|dA;|)Z*0L0QK-wVPjHppVW)pc7)_}S9yOkeWi6l3hkNrc zwE4^-uSbiF?%Kt!+O#YNyw#EWJDEtH=D}_plWDy;nq=-|llh>{8ZT1K`sv_##tz-C zk}?vuKavSj$9)cWs<9IuxU;IQ6!26*!agCI|GK!O7o-vh8Gl_=^j1P%N-UqGJ4hW1 z&XLYna#|=@7D;sct!i?mmMT&hIg4y?ZJ~zwwp-v>QqP>R6Hix9 zfRSzjPd*268u8e6swKo6TBocbQ&&@E^I_Ac(BP7!W;|W57ovA-h=A8QLh(v*Jq=Ms z_L5-|Z8ND{f;#^mq+3iiip-L|LILLxnU`TB9L^+i0z84irOqAolU ze-`E{VJP-dxIOFy2OZ0Dt2Q8lit5xeuJ0ut1#RoZKekc;yu>A{0S2kGpjzUXKmegK zp!o~LaPFk)_1)2ml7q$5h@Y{V2+7g+ThZKa!(mS8%-np&MPl(p?nU>f<6 zrj#XS%XgWo)J5&^+9tj-(h%*Nw z`*a(s?ay=ygP~Eye2zF;=RW?J~ zh?NTJ@pO7mdR(I*KPp?QysKyjQku;++W*AfzJBiW?Kyt<3`VCRI*f{oyhU>ekB|R> zjp98#$rQW3y4OzxzEx%|c7*DHW9zt{)XVDlZv<2z6B48`=i;bowkGNW*4vsbg*z@=xkeaY|9k z?yxo<fPVFtH`4xx@I zQBy$u83ER8o+B`ya$g3 z91Idff)0MfggYUD2HBrLQLERxJve`9Z5!`Vpdh?xLG{3YR^*`v)-C8B95&hJ`BaW- z87BCg-JWsD07*=HWcqDj1HVLZ1AGMgC#VUa#1UEma*3zrxWgUrN4t1JuxI&@i)YCW zQAD_VXic;U++1m5C(;;ifNC^MyG^b4v*y8U1e;Pc2?8Kc4YYhH)Of=d9cvQ$cvv)o zv%Zff9jZG~`ZO173Xc~*ZJ)qpiP)J!RQ*O=k^SZ;TWsw1)<3Q&f7XO2-;SFd+h}8U zaf|ne_;#>n@uA69z4WbyQU7K0j8JRQXVOAL9-pG~vD!@+B!=uaQR0@Xh;dXTy!b85 zX1rl)2{8+)((r%C%JafRQUxKLAcJ}voDH3KKolH->Vylerc$|+c>+~(H!<<+4@ck4 z&dzQ~j!<-f!tnFeBbmbD0J`AG4z3q@yI3x7j;9DHhnab(+_cuP3j}}5oBDb7bO39a zVgaY~xn?Q{NR$v-V`-hj87>Ea_wm|!Dn#SpPK=0pCcv&KAGvrE&C(83jwcI%x=FCg zo~m_+@)-0H)P1jq)^t}FjA(pGXgW~G@?}xN@h7`@28phbHxeT}&z{`AMaFq$q#(BW zWc7z)xAA8SiPb)F)Xwjduu~fwUyInCmJ^AuH@Mp-Z{0TA|9EnG_;T{|i}RzG zP`fejlRflu^5dNxk|NCc$Ca*4`($vQFbOQf{nMM1gt>~WrG8hs7VhKW1@f%fCov#O z91ocYEIF0JNa!-OpX$gjw` zi>>Q?D_`H7pMAI?g*hH;v2hKG673{5Rs}W?R;DpiIYjEaP0*ya(csj=iL!^A91bX( z(+V_PtSg_Du3pswHp;cT?7}YS5)Y-ZhQybb?qZN`sSJ3>qa%TWP#tSQH>B#|3CF?l z1w0}qwOH(ED@|gxOBQ3daEC{>!#Os{Pp~vCAwiv8go|0xQfd>BcMT@nYW9qYQtX6~ zDx#CyP)+7SB=+FxTFEHtVW}>?Wm2H4`A+xv_rJr*E8mJY{b3IvI&>jIlmcs-OOSBX z685;XjJO zN}kNA?0La$NbI%22q#!*zIC7*M_XL&(Rsl6L-}eJ=IMuzqN&#b(}jx#O~>3zxC6IW zh&&P8cy-euSe*76&E71krv#Tvv}pn}A6768(1Lx$H=&usK{7Txg05*UOh}$#L#qq8 zdU<5gR%13wJ+qMwBfx39MGgD73O{U=O}E&2NMw=7=|-IE=d1}^KIagDHUhrDaF-DY z_oy0`exEu>8NXK@CX3y#4pM2(BLvkVS_Ru>S$Q3`sl$7TSM{4ONZfHk7S+ zVbQgbR!%`hx4L5RQq}6scD6<>6Vmt5TXm5{7ll4qTn+|kVnWa-(IkL!|6IIX^D`>Vvqgj+^E9=wC{XklfD zK;x!en1p-SqjUyDj^<>|;V4Ky{DFcH`lV(DsM#r}-AxbBu;cKA?o$y}ozNH4o$j3{ zlT7jC@&g4@rBY5Hkc*6VF`d-LKxsz z_uo|nxS4?ZT4O3BLjkblf{jXn4jkO*KJMf)MGY%Nx*{GTjH}{7s+ck!B2v{c9%|!2 zK?*n&B8O0p%atJ+ADo^XonPaE==_>?X7NLG2q*;8QHU0Kw~q}6vqieOsH*f(6dT`L zon^~gWudk4t_w#Zja;S>$KEEv_o(9QOa!PIgPSZ|7h;Z;fWJxDNtY(ABT9&-p5!kx z*cTosVFve~WzYUFmu}jV2iK6LjSnAy`clPp3&4Bw5wbIfH?{b-G(g1r3#y$x;Vl!9 z%K^Hm${eK8wrO^JySt)hIQnJ%Ac=nIGf1Le(|1Yq4t99J#FZK2sr^WQl_s{qMvk5# zP@BUB7mMj@BO})@4VeEV4d?SiLC+ml7xMBV3viL`F+hHpmM4dT&g;Sq47#-`^Z2xZ zv-yEfd=vl{uZ$27L_THTKz?Daautq5)bkSc>0PF_q1?=*U+TqgS*alhBo4eZRMeplI(>J{yc&?Gy- zx7O{@Uh`}C4b>db9t9QN*xlX1>CHCRt?YE{ktPYPD;tFqlKxR9l!0ss|8m@f@c2?| z&~etQLcm-#+|JJcRh+FJ5pL_K(z!lUJt}QXoaOi#+1LC`0(p3jkxedj&_{pBx9|)h z&%#cE6JlB)6Fm2=QB0T1!revCOI=h^PS9n0nMU1TeTuc)#?m1UKN0ufHaaiycf2nt@!Lkz z-H^R)X9@yuYy66j`GBh-Rc(gYCu|-pDQeJbhQ=+*u0KxqQ3$bC*n78#qe4I|Y!qoA z160Wbng$+dUzMH#d_>+FislgncOMbas~pSBmusnn{B%vWUKGzSp{zg8AvYm@8R~^? ziA(tS_$44379Bm{iIw%*?J(U-Xq;TD47N-P)QYQSzSuyDR^%W=Lh-q zBtB*ja1OgG?ZnS9<0Fn%B{M7nlM+wCNwe9er|{SLSMPma;gBS1=Qg2F_{LAuxrTjF z7vOsd*Ar+>bLNy+qr! z9?)_9=!WP3fx*gc9vNY`v3f)fv9kxnk+47Rfb#ye^ofuR>M10XQz;apPYA1&uMa{( zW)dZriWf54Cbcok%B@n*(jo|X#53?PdvHwR*pLIwbZ(L1w1M&{a><<#oY67tcu^E{ z{#b(VPNA?2B%{S6Zx>>3AJN+_=N}QO&A05d1!^Y@Z2QoF0KV#-cfd)72z9todWsV4I_aw#n6DRz7fG zrEwlLwcC9Agf7@Say&U-jWmeYMUD)ev z0dG*;U{}J|wp++$RYR7*x_QPa%n_oKU_&HK<;FRz6B;KU$Y*y4N7k-z{s{-$P&hY! zGFk6*YwC1cy<%u%(W4K!R;%I%ZjX#>hZ%dIGGi}kK9)&4z)>Xuc&W^^Q9)C3w1ROU zYWaf>H;w)?Hh$%=cGvWem`k=c=*q7jsu*TXZstWRzcM zrn+rj!jslB4hj9qIK3*MfNT`RFUMo99(b)XD0uur`3lnDRM11@4VVHSC@^Cr32G zEB#Hw`}Df8o)n9)%zTp+t}l2&JS6C~N2(3B!?C|>@Xczsr1v-nzr+jvfKZZbQ(xU_ zSDqbcG!CYNj|9PV(6I=Zls(>dw?L7R_p9dHo1!RMBe20A46Y+w*!6;R* z9Vl|>bfB_Qi;xvbvML}Vt#P3Uqg%td-O$Az#&N1Uj=J?bF{~>+7AOQ?;(mCij>b33 z1G2EGSOU~@E&l2iehV47y#I5ySQIZ=kKWl<#hB4P& zDPU1=&&qov8nR5qFUlGkgJp_bl6^XNAzccC zRZ>4EQ<8$_3nQ6PRPaaPp6TW>+gLW8i`$axaMKz8S`X-JpqOYa8i7Xl0Z(Ap>R+qW zNZ`}r$&5YAn*d|2-8I_9rJuiIU>`X1j?{!qJl{-DNNMgQoVzpL9It zQVlS_5z={gCw*9WA1Il244dmiHeJYt19*I=OS%NCytmP5hnLUQU(<@@ z`x5}4l6m^JaCt^CqfgJQ7woVH^HgnFSMNX<1azamTSXMQFjthQuBi1bR zw0Xg>@I@~J{1h-c(037?utk9eGC_{+?vw-%-?-cH5X7#J&!1Mza&%pBVcToyTKoNO zRfDx7Jl`FK;OHSfn7i>l*3C?`vO}b5Q-;8+Y|ht%;tbXsqnpLZ0`H&^5QJqr??Xyq7Tv*e?oywY7}1wta-BGcX6&EV+< z0AuIj7{*R;xKTu8yJ2qCRo)PGl>xl5Z8jZ^Sp5UEc{s*=JWZYK7@J3&6igXZiKg@m zBMsIgq)kanY;y(A@D~(vbpEKgD9~quA7ZI;)Y!LPKvd&VbfQOxLP#Yb$~z#vy7eLkylD(Y7SI+F*RM;L?`O-_yZLRj$kxt==>5dL0Wrv#-bHHL1@|v#U9P=U+%~G#XFxa<}>l)_N))MnkgTYmy#ORoX-gNZ@gQ_hr%xyT^D{Z zQ39$HX9oXPah=yJpOT};+@u=iIq?EQTQzp9+SwRi?q}2cd`49&qBB5M*C#MVBw)=1 z6#B}^s06L&fGjet%^G+$eKxGSEBK7~``b=Hu11yr1V(vwL@Dx&uzO0Lx7oGMM@H#L zp0DU6R$4BS-d=wm4<~+b|7o)BDjGfl;H_UJI;z`E?<8g&jvqcBQBs)ngc?nkORk;d zsy&EHrc2{O>5Zo%zUcFuNmCH?08gd)C~+mo?h!_~L84~3}N>d8UVc)fbM;5q@8#y{YFW2_#- zyrXIV)!FM8JHZ2=c1mRecxEkZlZjg$2J?+p3NiSS+jz!EGmj7Xol)^m4CcmUE#4C} z3~q5nnP|FmbRkvMQ*OkA4shJCVt4rrj{``bqM4`NI@?U_lST25>itH|EpDvWO1xKq z!$F@Eq!Wi5lXIXG{R!W~N*rsuryKWJnlv#TuyG^)sFi`7cwI!9-!`=ud)q8%5@u%n z2s&HN-Nnrj>N6SO+brat;J*46Xrgjmw(d-0q@7Zhnq9{F#!C)L7Nh?nUE5J zh*nP=nw_jSxW1HG2pf*iGrY;wRtaa_(+a0uzKoN&uIJ8V^nRXh#A}PXr9T$S+gw=+ zNS$^AcJ5Z;sv0lgth4t)^~aP1SS+&f=Bx z1>P8rHx2Jvj?A(J%C^E~V8GM;29W1m^j+M`8fKE}z0*B1kCcsYL+emYJSh!Pds5l769#L3)}Qzb>wyVM92H zKpOh|+V!O!H-a8W49p#%HU{4r98n$4i_NWkrU-iQMC^XE60J7{J;EJ-k}c>6(t~x5 ztSKIk^2z1FLY|Nag-@u%{a3yzRuly4guWE?Yh}=n$9Q03iW~!^8)_APYj`|1vxXvcjGlyp zS4{e>0ThP&Lai|zU=87S0owU{qb%402CH_xq*%H3csu8wf+b9^KHTAn1H8W6si;t z6GYeWFxg9ZGc14S46Z+kDzsc_sD{QDL>nJl5^Z#BQBY{WL4j)5Q28YFD0d=XWOl($ zGOA!~VH@qz;G_X_3uR(8yLX6)Tv+NoM3=~xEzzZ-Qgn0>Vg*jWly)GAko9;Pp<2bH z-w(59s8-*nM6N$-cx}8&5f57rwTBu)?Yr z>9E`qrgQHk)CE*wku`%f8&eeR#G}2%?VoQPPqb@RG;*;djY==hG)Rf8tl10LJdC{* zIM|tXkpLId7O~MdaSOJ4SMI0>=vz6Nbbz4X=TMTU@H80o8LXde`Tn7xFc<_Nm$)03 z^Ow3EV^LNLKoW&61svWJJY{Uwo|Ip9h}%E&VxY*8M4tY?Q0Vyb?KNYnK2M}FC>_de66Cz9xs&?k%TTYZw)P_hkficu`!ma2)9 zUPV;0TJDg#U?P5VzFCrz0N40xMNP0D#j!aTD1d)*v%SP+LiWR+EWJ$+U8-Nci0W4s zs*812Wx8t-bv0{~y~F6Bfq~EZa$1&npj=dpb(7b4nAI|;P)U1>Y}3gF|K_{zMiArT zyZdZ~1cPlhAKf9mazs}R*tg1JksU0+N>;vkJ3=6A_={sCqSA>Gzxw8O@oxLP&ffei z(R|!Haia4Mmn|6e$?>V=YVyeO*mkIE@EkC(my5yX9po9RrC8qJsyMF#5{T;e(zhMx z;SVX;saRdRX&F{N#(0WQJwC+?Cy_4RZNp@&!h(kb;AgE*>AuZ)?AmtNINuKa39ZS8 zs1_f|GmajJ#>{*JHgQkzYe+s$X>z7@YXokhwBuLj7CE!*2}kzW*NCoc~+RF)kl zQ{hO_v>ae94gctZSPjiqNK>Ow95!BmbjWz4IM63aw!>ScwT1p=Y$Z6fRkneyzn*EF z4VpQ*MJiV|T@5cvG=8+YN1-WLL?{VjkyMbNa>RUZbuT~B)QD4vo~$$3NN>ynqf+|`8nGI&VLz;phTYI3 zEqh^!ms`boW19t~CdcDjCG(E%KdCfU1%q{LX^>7=`?gkR#roL@#db$XCEg+67T|+o zxm1#t+F&@I=YK5ZVC6O%54RI5qKl@DR^ze*XpZ0!oF-s>N}EyhH5#npgD7=1g6@I( zxVp7{wzX1)BWw~dbcwiXHPUmpGq{?Xb`3fm1bQ+z&#*o)Q7p=Vs-G6*pyV>fa zqnBHKlF0p5pCER{wfC4WZ;P1x`JD+YiO}E-!y|C@sc5_OvK8tFKRh@GYEM6+@9os>*urvK0kQ zeag$-ba4zj$jiJFA`1fGc+AfjDvPV(vtIlb84VJnsVEp9Nr*j6R+EG$`KV!QTq}tl zZN7-Oq{Jr>m#5jC=pP&Cw5GeF9C&G3h!FmP?4GK^V3W|JfPBs@1<#5tRyneS*}H|i zXeWn^)$JjAQK>Ln!~7fEf<*LiQ662CG3XQ|l74Zd+EDc_H#>kQJiLu*Ft<-WHh@K2 z3cm}u@v~v$os+7~Z6{6)SuA$O>B(QkNQ(qjjyh{O=i`i3+2Wi4$W%kZ zKT)e?bZ8LIijQgJ31BY7ng~}iF8bV5Wj{<^M9kt=~1tD}?^e9@_jJ*gz_45G9PeFMa4i+ z_HidJ-2e_Vc%FiX8-zZSp8nu6KjL>PRaGhS@4G+JBbbQ$#;Yw~__bzX>y6S$(;5;M ztL>&(Xg+)iZD^fyS7MIQpnb+0c?qyqPSl{qfQ?H;@@v;O9-5cT1l3iKe4+puV^-aB zeb10T_s6cN#9EgLC4Xhu|A@#RK0AYX1;n`~-S#e9iVp#~iDlGXzA3kt*`_R37pujy$_EqV zs|RGA;nF1A+&^;>Au2&`pd;@jg4uF7A>!!5y^%n9x4Zj?Mx}^S$h;w98@mU>)sb{s zLN7jxuFzlxwh#Gco-zYC?jJp8{3*nNtX6o+8o)X@U5h0{pe^8&W$~DTzoy!LY~eyf zlWRw`^wIv8X*nLlLwksP!9omKu7TL8BS?5{vJLyvY<9Mb1+Pr3>1Oso+ciyJ(YrCn z38dIeGE@SG4;BX){=2yJLNF#`4O7a5okuDvwzzOsk)07bIGVVK*wQT~bX&Rw)x6L>gfx)-gAi#dY*0xwF2{5^TXID!U#dAJnt3?<$9KS(RjMccHo#)jva zyAUTETkuv1NvuN}?PJG=c=I|=J!!bcDjy^zv zcxa4?KDl-dlL(LGf`Af<8VxEDL6cwxQ)a@2ZKV$jyHCQ0iY@)V3`G<`pMac3Do3af zjnbg+@F7v~7EkfWzIdM93hr(aq+7-i`Gs7+@_tVoyEd_`=X2GA5;V(Ts&Q->TNPMl zLek{|MIW6_QE^sMFd@$^0#g?_BWm$Ytw93oK8dwu=etnsfRJ~zuAi<$a9;AavCK&Y z6?qQJfAjXfH~&I$H*+YVqXnOrjkK&%HMhw@yD!GOIkNVE=mM}sg^;m4Q1M&VBAu>4 z$oOkqmomMzN1F8vM$ZMidPy<=AXrJA9xK9nE|ciE7CI?$dxspAz_y=_LEbEQVCu?k zkE$WE&U{7TrolJ|Et+KR?ZihHHM;4mUW}?BH)}7LBnJPgl#nkq#zs^$}T`ty9pD*}vz6(*yY6_JSdHsiOlu zJe=H+KE21Gdo@4UZWiP-M+8QhQ9^?>KSXwk;GZ?uuJL4$4n$BPJz2iWtO-f;IJzY& zar@)}Z7Fr9Ju0|DWOx4n8H&Nq zXxyLkx%{%=5FKJ!VQ;VG1iwL|qC^JpYL_q@OA4Rl38j2?LCFvFRbb~@)gLj5&@O7t z^cTn-NgUZG!vm5We44-=Czp{&-?S7O{%X39;x);@BvR}pb(X-OJ8rwY*<>;lD1nC+ zd4yur!vK@6M=bOYN_F7FbJQuo+rv1$*P)jKUcAHGd$5l1r0mIZ90}q2aU?|Q$B_V8 zof`IGR6Y9J1X0nq=uj45?yi*tV_86pU#yuIjtHt=^q?hXBBAAB;e>Fe2CFCsni&%w z`yNkY64=kVl_+&qz*I=`D`|$Z+f5F~DV2IKKtyi28TL?DCC4rhlk(NQL8!LNG}d4?Q0nfcwlSvzF6 z6*!_BYFtD!cpQ5k)V0?jIhaJ>s5@ZoG=)#%hBO{Scs1nF`}Ja1-cVlQEuPnb1^4ml zR1(b!d`3^Jq9!n`1QSxz7FCl}vyB4*$zF*x>apcR@ic+_>_lCAG|#tHIG1Ic2;sUY zgh*W&0wgqYu`TwC;&F*65OI(V&;0Q$`0ELoq}Cx;==caLpj-YyB^qJT(NHUr_?$T4 zcs;T63L z)`C+Fc{3blhVm#=j~Tk~@|~Icz=)Lx$Xp-(5RJ3`b%FbbVz-%P+I2^p4Ci7#x#Oql z#K`WSf5N_w4AaQOc`ic=CH}9?x4-!2_-h{fqg* z!DD?g|4LMJQdq%JFK-hpeV!1u>ii`g-+YEKkMLAlK>Q~>;i7C!bqP`6Aui&eJdTaK z8HTZ$B8u=4@{3^DyE{o2x5)0MGd?Sc#$H{}lzScOj5$kmF*5hl8+83=OyN2_0NPdhE>;I-&8;q)hFtGu+ZViM+O@h7wg{^}T~eUc0C_y#s!x;~y!gyStigjRT^n z+7>e?>!v*u(+%Fhky=IX?&TYp5wwUoK1NcdRP@ZD^HsTqR}>DZEk98gr+`&iw;yFD z1Zz!K)$hm?4_QemHHRK5;=RqiXfXAvyiu_zLuE9w{^-tld_b{4)QV;g`rm`X+h*_S z^*Yq2THV9oxRBGh21pScQD*+(=@*DQL%RHtBE;l=`G07bmqJzmkA0-iL!<=Jkr&DUa(^l*=`dp37Sa^pzgr2fjse0@w> zGwq8zqf*z8DiQ6}d5o7gzpkX6H(x_dAU*=ykI^h_k)6u|kbv;Ijp4PV^dS=x43G1?|z8mC%0Z9Lz*x7dE&OkaZs>IZ zKRoV$bIMyzAxobG+xdoHboj8FTem*_@T<`0Y(SAtKKt6@S-W?X!1UZ**sJobMF+zYOuEVNz(i4vzRW~)2b<%4B z3tvPy9>J*pwcWbKjW}2mGXLF2Qb#$$FYo| z0b7_LJCG}q$jQ$gPCQ(!t}fuk8tWbWhbbP0qp3G@6jB(+?909?w!QyMm?4km#`|Sg zIs-5-IkcQ0n*lmLNV=89P3^~ZnqqIrtQx+du&R1r&=*e+785K07pxRj5M46P{xeg- zLjp5`JtStDd2tLGxY%uB62ujl=Wkier<$qi#HvBV>bSr~r^^@A?qn-X?I~BgU& zDW#_xvGg>>dMZDy)`z1=(r!bZCG(WQ?y_Qit!SEKO|5N;viva3H*{t&T*>1FlMY_J z4QauRhrY0f_%aF6mOGIaM|1))UAniZ4EG`}yzyqFMH64`;lTCVk@|>W`v5gOI{7s+ z%B@Fon$mgpL0%&Uc^ zdRu9&o3fi*eNg$*>q1KoZ^0=eA&sSyldVnPtAbj-m4o5{YHZjiMj{+*qPh*6FNHlF zUN!hHkw?HpHomqfny)F%RtJo=w7W&3u@VaAB#g3nP=CoX;l+lZ?xTP)ykcV~!iIne z;kYt86{U^uyQDq(BgKXqnS`mI*w@t#Z?hQA!NNjq&M;4v09))Ft?Gc$b*&d}~qwUccH`^XvR33U1`lj23Q-UDW zk)rG1gGGvTg3B-y+>s4LxQ$z@X-a@K%=X(hVnwAr;;xhs_oY% zKt9DzEcS@C8K5B^31~#i&mPqG_1dkw&zr_SJ{0qJjhCI3)qy8P7uyFIfA(9*776m~ zsP?g|%Pnh{7ESaMx6pS;epqY1ofbNN-`R)LLN~G=6}h??nbE{;@X=Xmpaw2D*`Tl* z+IW?l(v5&OaN(hFz8&Y;!qrd3$y}borg=N(ykrlbk#C*PxEn=?>-IskMy7-fTIE=! zf#tJczy#JFPQF~blrbQNz6Od`K{+U7xarE*+J+d=x>IX4%giqlxRuvO+dcQG z)BAp5ry-f?lQ>@8Cy7na*=cSg^fHO@SldOkvvAE>d2CCB2l$?V;kzJvF(!H<%( z#u_^FI;%-i)>=(uTyHhrzUFF@__}i*I-^lh`SaqNxd&SN=S5FMnk;x{;&eXu-b4$W z3zvBUEtI%onu>s+#uxp~gmy+W!7bc0XwH=Xf!566<#o*smKpt-;L-e%AtH{^RIlwd zs()4%wf`Uihe!M>uVo_oM2TI+l)(UZpIm@SB72@HfxkrFd=GYM@D$pvhez@dUG<6W z?}Q`sS&uHRsCpz{Vxd62UZ4WBmr!V{>h|JE)bH7ugqlXS?gskN-$x9PzzC|u;dw~D z*^!rO(=^_Fglqd8H6;`Y4dHa}dpP->5xpb_r6V^|ki`a{#7495LVjxemFnHmvZ;qR3wPnSeeSoCFgNzfcYBPG51)!Z5-9^NCoLug@AiRzl%H_L)X5 z-U0;~(|rVQW- z>2P75f^3#I9bhA>9D8_leOqX^fz_BT0X0pz*d9lO`MAo?LAqq7}tV3XPYj%j@ zbJ-q;B|NOtg*iB^LuUvL>rfa2!#Y&)FW;U5Jt%DQMA!N$Il z7O<`B7Kv$~bqDdZ;#TAKoZ=RlDWSMUVfqMu6i&$f4VSXdhFx&P=5~|Mw&$D+#K9G2 zT5`A~cj#VC_yF%iv3aKCEkk#~-j^l}0ijtW0vfwu1VriL5fCQ{NkC^36^c=>)rO0E zBVmxu1Nv=?DDqBmwaRM})0_wB9eMl2lI-2q@AB%2-bfx_YZN%sK;{lItrkR!AZcdV zv!DkchK*O3caas~C#XWZlcav$Z+GH-5hqBA{c7$E=Y5PsJ-fHNaRg8#krV$A75yxc zGa^at$)Ca#MwOSCg;-vDdq@F^c}(u6HAQr4ilj-{#KU*ngK;OmPtm)`4iyVeG~-+$ zQtk?esFEfj@W;HAF6B$1?3rD-8EhXsn#PLDu!T=G?{H_zT2xwRC>KGlaNadILZvFh z++^34%Jvab8#nHdB))xw=uI|*F+zD1R+i$bJ!l`59F+ z@;tV|c)T!Qs$q2`> zwK4WJNsWiFHC`zYajy%2@`U2$@nG3!sNQ3;;I#XZfT_NE%akb&hM3X7(H5}LGQ^jP|# zfOgs2EM594l5NHDw19^jyi~V2&6M}oxaAMU0y#}!L!WqRJAi@$hWrA%vwZ*4Z5}lG zn<@hY$LK`P&>A2(Eo8ZOL8ch!Fi^{ z2QzF_P!zkM8X4i9UsVv}F+!r(Q~5b}Y6f{{NU}W<)8`rt3X5pNXP3a~wA@bcDD30& zIm+nDMuKYAox_q*Os~_584_KXKC--|oPvC&PPPZ>O_jI_o+BR{ z7i1A2XN$STMguN((%IRKh5RFaaaQ=`4#t8FQTJf}0N@5e7AQ+5*5IW!>Ghm<^Mbvl zlZRYes6vjHp_x<0e+ODFQ~oRhT~Wwt-%`k(ZwA}*dNc{ z8{eKKVG4Dz43k7w%_-^c6>N!jP;bYDlyq6c zVBF)DvFrgpSDg>Rx3MC}}Xc6NhA zamwf(kEtH-k$VQ$7in~19mHEtszTKf9EMj9&g%u$s zu0oL@jjdkbJ)1q!qKC3IKov@z@&;?((C3S?W2^y5Vz}nvGu z59MkKbgmepI=NpJvaKjlU1fBV6GzqWE4iDPa_)B#QH>SBVH$5 zp|&T-r_y~@3(jYM#o5va+q^D)x+M3@0D0UG(=whHc8&*HD($M=ixNO8*z2cncMk@3cZnSQn5&ZojH}_MB7X{xY++rYAFk?Gx9BTU%8~nM_tQu)jO~F z71k=8D-4DA4XjYG5bxumz`CPG*R;xh~-BQqt_ugC{cSKo7<@ zPinl47kq&MGq`7C{`VG7(`RMKPpGA}C6f^qV(73S&-|0~u}~bS^YoH4i2i0L(GA#( zTb#QPr9eJ_>rH-tFZ-!45CpY%N}HSuY!H+S+(>&nr0iAE55#Nq#vU{*Qu(ahzq}*| z8;b3sBWaW&o}chtV8gz470A(<9#0SuJ04wsv8aN8?0C0WAUG19ychI&MOt%5##z!h zyJQA-kxn~Z*F_WQEAU^q`iC;JOvW-co}-Q%+OUD|kXERPCMK|Pf5yjtd319GZHJ2r z%5!j`H+Wm2`kBO1pNbv?u%n&I!d6I+Rf$Wu-;1M*Gj|CWXbw%+8k7H_!ls9Ky2z-y zaPzQ+3H?t+1}BF%F2D{?v2pFkOFUJv#rYvG`1V5BDlS7hxGYgLG9bj;F=B`C!+*d{ zkm*L?65vzBvoGllH>?8f_JFWw`P_(g-;&SgQ-oULxJ2<}yN%>x^7F_PI7h8|#0TLI*<*IrVOZYo%v= z+rGb&ZV}|W|1P|>Firh9miQCRi^FV->!Zl`sYX$e98jyj__X0`|y9*|HU%QeT=$W(=c3ga#=rV7{8Bbar?5SCOB)1Vx zw-4{=a9ONzs28OZcT*HGP3IFi%Z7gIYCFZfpbd6BhC7~p*z&~qc7d&Y~2!UD~+6xI>ZPEbdU+ zJ;fbDo13^p6!#HdK0G6GV7mAtQUb8HKI7|eq?Hvwab|>L_lqVgEi_`Alknsm`M92& z+lMsnq2hR+&5x;Iu_%)*ra;8wWPzTQveTL&P%zEs@kH@E_pn zjjFDS2f%SlK(~>IXqt0viP%6L2_6I7&G;JgMT8Kw7tu{%?j&%T-c4c$A1SB=wJNgB z3+AvPPUo|sP2pJIq8gd_Tx<~Ow1(Y`j(9ZGt>nvM=SvGMV*5}W^L1AenKbMB!Awp+R>&P5BJ+Psua z;lC#){V>CROp$YjwvQp*MksROu%*U_=WvF;E1-035R3>GJG{j>D#)I!M&?pgIc5wb zFHq$nVT$ew>CAe|5v6yN*g>OFlT^9mt|3n6v!PAlxUZrOayLMv%OIaB5bN?|Cf$cS zS@pOp?9b{tVW4)PVQFTR@i5a6q#a}eI>RBRL8lyG8Y*{q2{xSgEG3+Gf_}&!gs1D` z%^OceP1A-GQ4MA%Z9I9_3D0DW(1AKRH*$v+kX-LJw?!J=HG)Q|H=B1DRSEKSPN|B; z%H`E9I9XSk+!o$toPN&v+z-E?i3oFiz1d~^xQMweV3CXdwN_U|g}Fc)r2O#YWk|se zCm2aHc-1HzM^qhj2Vl(~VZVO}{*e1iUg;!6%}=-N}qTMbXPSYry02J`Re?S-5VNa)Rf`sgYwI!&bAe>xELHJ%r+vMY+5u$oNNn zFE#mBB#RybgJHD#^ImLOEc~>xyD$Z=rk#W%SwokwVxTm{2|kB>Z&DA#?LsagoEDzb z74jPLqi~y&`#2Y?L*!9Dh^uv&#`WaBmW}jbif5;h2xUgIza@brFKOm*c`xQ!H`7fM zFdplL1-j^*MH55bE#$n*E>VHEP9qf$`XqSJ&9-dZFg8eX+LdX8aWs*^aBU9?`rxM- zrt;--4TQ^ET0;#+sjl(69n$Wd&U#00XC89SgCH^i*s+Ma*kUH08(MHIe}}%I((05@ zZ=-v}M&BBLZL)Xcb|;i;bnzgupc7Ca3dC4`WpZ)j!o032&x>NA&a&mL9Dmfo zZ}^@pi3B4*n67c5E2i+a`Z$}e5HSD;Fj!+A1w&+*h?Y4~iK0DB<|_70sgj=P?H;Cd z6Ozs-W`&3}Z^mT$rODE~Qgh*2P|dmufLbui1XiX`-lf~c>V#b7d`VxVL*$eUQqe?F z?-0a{pDtJGBPm_*H3f3s!78(I^KqRvTt28LjMn21piY(b7tRrc#y)0}u=tn}Na6w~ zxtx0ZE(gJ6c<0r9T4za4H@WukdYb@DeTotA;Q|4kPsQf3%6nFG9^K)hO`xs;j=dzK z0M|=ovx*W?0}-x?90R3zt}D(k$+lE*$EA+b4c)UJEaD_-DALMt9fj0T(z8eMY9-Kc ze((uN(@MUm9^jPePWR4JyE>iX#PvX#XsMJF%n}L!vF{;;wpfQViu3WEtmyl53Po0b z4+?^(qGbuacgfQ}Xe^z1;JfHS#Jphlr)6eXwDb}KL9wfnRO*Iy-Ef7jOl-k~70}|m zc38Q}*3(?9(h)Qck0#1CP9NiJc%*@ad0vw4ioO9B@Bt}sku=A}7efyOcX6!?$hd(^r7B=w);3j znU4GC1g0E85>vjmlWhn<$M@jYmjFe?>}bK+DY~~@7V%|)&s?Kz+%uf&iio$p?fDPz z2^~xx^Och1KF~#iNH9y4d)hul5r1}@C9bxk#kdM^`=YLUNLkWMG+D?`GY+uDa3SMn z0FLBKj_x+880IINCyv?^*yO3z#{mTny?~BBrA5`YI!pNeyTAUGY9GRlZe)%LQj~(5 zW9H8?kN|Gu=n`j9Y-R}`|0+fQk`HLDHau*gLZU6k$7LWLXq`4K?tamxmcDf)&rUhHJ2`X=lXE zs=I(`hrlQ~0;oNbgaZH=bGr60-8J?rrNF8sxmRoNTcyuwae&e<1Um76)$4*!9ZJxalP1? zCMn9eLmCe>ZWAQL8n+4UvBPaTTTpPD2DY(xWLqXZ7H8IK+-KrthyZ)WmAkq zrHJtXiWwQ+%7Z&YB*lh;WX}TkW9(Bl92#CU8R$|;G8jMyn!wIjK;#qjqY~N-3!B~+ z@e+8NiGPNQQi#O1ch2OnB3zlsM7kpii=I5lFR2bp5qFp-DNN!@ygK_9u|HQxrjqJo zNWD%nJj4KV?23!@^VMSFfIm-j3Ov$Z{u$^V%B^&#D07FD`#_<$adUg~7`z6CqLs=D zZ$T{&hhZ#T8h3t7=tO@wY@i42;jk{;;5}^(;D};TPV>L)a`QZ)*c7QawK7JRExmY0 zh{jjg76^M(L)wQ4Qa@nR75-%Zj4d+jw`qt{zEDGz_?;TU)UVZ0+P~Q~0ITV`A8K>;uor@Qa?{c7Wp>$@;xdE;G-Ozo*bQDA0p)T{F)B= zax7Md1fji-LR4%W0vBa#rg=cHv5ULMUTx@u>OcT#9!1iS69xPJ%eD{2`~Z7nym1P?W@h+y+aL`HNnySl zqIf41P^H!>ZRQ40o<(|#2Ib+ZR~F)3AkPQ(xEz#L&rnVUbT7nGCJiTP^6Sv1&suo~ zsQw>Un2g;+=1WiEifAShk+fCGCUYiLCewF>^nOit8W@bl1vjcmP7UWZyWMW`+uc^x zJS~c}_Rp^!vFgEWrWA#4pZ8K&nsaWDVGe0!8DZuTVs+Ha8rtS{hsVo|a{;*9B_yX+ zGi`v%mCLY({09Wi-Z>)5P_mxiyR{9DUji^O>tl!w?IL#$`{HKG+xNxhRxYj^jMEdH z(i_HBqV=YrC$!?VW`m9>14KF^HPf!tX^$Ay>u^=Ok@a;BFvMNf3n=F(1rm6RxgI5i~#NO2r_zGy&IzT6GWez|C<{@K}S$ z8i1u@F|)W$r|WckjDe(R3N3W6ViGu~8AybSek?#278_4qy0kWVMKF6F7Hf{ucpZP* zcE(Lo<;3ekP4=h;<17QQ>&KiEEpb}X{E3hGXS@Ip7pO1IOAaEr&F6P36r`r2S^LPK zl4j@e`JHoMyCa@nk#_?06O5;$yF2)_T4fI8_SxM_^^T)gT940^dm1aL2PQa0)zl`~Xjgh8{>PmrNz5U`sHN@<$-vJ{;3X}Z|S3%c7eCBlI= zohZk917Nf$jVfjR4f5rh{5vNoZLJLQfvOn))kCA*r^7oR6ViFdr5VbR?aPXaMMIi#G_kGXm}2o);rFTt(>eMLVp_1ht3O=aJ%y3|}M z_@#Yg~l>D1Y`q|VTjOrs91v*lkpZKPp{)}~enN&2WQF7y+#xOB)9 zH@5)>oci~_NZCz1O)#3YZD61Qw+Z-P{GHrz#rj77zxW%yKw0|{yi`?p=j-@_lRFY5d$h8f;0bw3uhR+yK!ju!jp;eV7sX{e)c!TC;KL(Fb(c~h79iM zf(D}&@NiR6p>I&wAfMg1km2zGZ$|tVe(g%#g>MF%fMaX+s7D~T8KCoSYd#P7z4pkL z@O}5FO<%)PS2Z*+W0%GHGh?gwH{-fs#TX}Imz zu1n0@a77;3W^RT$x6ezrXY(iR5<0u*k-HhRS_FFyY73 zFZf7sw9OvzN6Yth6!!UXcx-(92VG(Dm>hvK4y-hI*3DQ=E*N`F1m>Jp$>#gd$_sJT z4#->r=A=6bPj5FR5maWh{AyjFCyXZE1s+aB31N)Dw&~Y!i%zP+CLyWNtsTas9YxjH`%jE42c%mA4lyf9XDIn}(n6lOtLO zNT}#mWKo1&nC!iddSF~Z`iSiw^-TQwrY`9w@%oV2iIFuk?xtKxx}qEo|UYjDCme+;5 z>uGVpcj3BQe2a}^@ABk^-li8n1~8E-I@HX2dexolNAYDwB z1sDw(30hrXgUrNY-vN*|yL~(bnQai4rqp5$NpS69;1_o8e!iN@*`2K>Jyt45L5Fh(rUr*fm2O!)W_4ti_GtPwdchMi8w88659I`QHE< z)d~UjF#bGOfOdy2=}Xl>=*qp-{cbVc!~!d8k}YnUC_2K$Z$^9A?w0r_2lSwC1k1+j zj6>$Hpe^-J7_)RMb{S@~azlzF3%5lWw{Amtf`J;6*^Sc{mHyOR2({U;Aw}GN4bXJQ zXBB;~OYP$CDOh3a3CJCuSYMkcI6vzQ-p*`e)=weL#uZ+w`qxE0pyNxstN$|E$b#&4 ztcNF6V`&ko`|B`)NpZD0fezzOF6_xe-FOnI=F(?`6x4Q@79rmPM&HkCq2Zu6w>;Sv zxTlo^^|@hsW(f;RiT?jQMNJks!Q&$9k_4!^Xswh6dI{SFslmc`XmA>b_T+?49s`$F z()+Hz=toysNkl(n*yE6Dz;87fCaaB)n;~#31Rc2C2Ktej`XUis7Eg#+lI-IMm2C=K zsswSE))|%=_g_)Huru*_?e5rocat@xI|)ivCrV{;vdxfemfl%KmfGF5T=z#B7m?Vy zVOuF`f3^Ez8pYu_fVNp<1B6LN*${o)HXEcu@Q7`7@nj^JbOXKFExRFN-M|~5fUkzd z7aEzKE4<3KyNy(wS|>-2<@7E~S`Iv2HcNA$@3_I)CyIGVhA4Em=@5l#^b8Y3jp1Q3 z{YWa%#E+4mAByYg;*p{Ue9)h?r#HwC(^3xM4q8N{x(Vm}KMJ(iEar3}S!~`-zvkup z{IAP`kKWSZpWj@oC~gsiMb8-E$+mj#r41wK2!7`Eb%EMpNPacIGqr|%$PX2#-)yA| zm=IZTvMbf7Al|`8CerN5j9ZvF(?BRylOmjZ{V#dO~=i<0i zC@A{NjE8%4Wf2HBUPeQbA2KP|HJsFnrdaRRbYXFi!Z<5&rwF9T(M>pF7qg4+Xc$iE z;itHUmN%rJoARM}f>m7Ny%BJDvbvMr2@1zluBPiNJmbpoPZqX-$MbfZ+%oWOagUeZ zs`_%cC@{4D1x|DMrZC`~V<|tb=E(L(aI|RgH1mT}mDANF-t5lmFA?WwfXLPrRV--+ zZl|evvyq?(Ng9Q_1xlpZ6zen!TqRJXvuO;#y2kzUKlD|c)(amVXnpz~OXq0#@nfQdmW&BEJlW4 zU9s<`(MS?P(6AnO5dg9DKIIsl7}^Nz0Y;1)`a6z1;B_?cxJ?%)sk$ysaw~UmM%X{g zUnU&If49N@FB|NC*kJ$D2KyHob_Q#W$~B!Kc3yU*VdgF-;b@h)YXg~neMg^2tIAci3;KfB zC=!P@$l@t73q>JqW}`~Bd%lffGn2PX#PHN8w8io!iwMN2SgqT8fUICuv& z&S{&<_Kn&!#t+pdihV7((jKQY{95StZlT>Hqv}&76R0!LZ(kpLA8#Iasstfj>XEa~ zk_mSi%<_7g_3^i{(ev0X>Vt)iM-F6Nc^gtyp$ppgWE&DmJvcD~oNY%k-XJ#vdi^kgHjYq#PVoI)xrfGuR2& z%;spl<9wCF5iW+u4v^!TApV{IXS!&#NRlL@OIa*Tg3%$1%SDI4CKeqcn^bg&;zH3O zFvvuQB2gq-)F~2iHC^ZPGDT%nU4?`%k&X_1nvArG&63h4OOls1X_C~mNzJm;CbLUW zo9<6jIv7D5Oe|qLA!`Lew;2 zk|Evz*Ay!0B&b-~rl(IApPWG&6db-Tb~D}VanIkS+LmPa)Rv`dLbb~#kGraVF%CcR z;c#d!N13KgU7zkHeHo+2=_7ypF_+r#8W1-I%DVnku)15B9Ddpv>6_N zTxKsh`^|_GzHZ@*6ulZm1IC++IGEoe3`KNHhhW*!)}SIcGa*o`nbqLdRHRJ$Gz~0r zbJ_r15hS_6#c~a2s^HbF+?a6xx=*6kcI$w3Z#uwR6r@36eS~q* zQCH@m#vLu!VvJbb-qrN^0JrU9zWC^0QoDFYh}D;=6yxL|mpFAxJRvo(Snhqn>E4Md z-;DfT{1RPwWM%^R%@ScFiwWP>lk=_mlkhF)p-)#_721Wv%zE7j5x@>0Sx( zOp;QV7k4O~hf)pj8B2kCF5dOW;<-enlSkoGIuPcnls>jY;AswU)r9^G+|Os_4AoS) z)aRIGd5>;hC0XvF$-LPob6DImdn9LYirphc)lM;?ItS)X^o0_#<6Qu(;9EAq z=kaz!aGYhJ$?o>HgJ55Ttmx%aT1dR#cUU~B1@OA~k83RqB19wf6&d`9sy?)g66gaO z=<(MyyctagD)s&54)0uDX~#Q{i^DLE0wD%lGRCR;F1n7Lm-Uxq=w>Y590{9G+e^Mte7#$afP*Tf(LME z`sw&bi5*H#OE^CRRL0KII|?Z*ouq6><8gqtH4cTkK)qZs2{@dlysXi-#t*Zp6q(y@ zmw>h=tK}cvnQ1C2tgT_cYE3PBu&wEn)ntbE0p4$>Yso3mo{o^f+67#Q7P|t)ddb^p zQcO7oA|gl6_6;knjo-@8W0kuGZfqXK*0j|2_H{<&634v4*@G~b$u>aby6T_#Pq^iV_xoPk8O{(}w>c4gNgwIB4BsdqP5zv{)Mgaqxb%G6zmy*%)1_x`Zh-joKIcAeL zO%sJ7va?wvBvE{}WADJ;W;1=>7i$N{$ZQLRyE~hHognizfx_yCa3fN0nY!b!MidRu z#V@G)L`I{=52P~5Ee-)s5{(e(GsQ-(^>c8LLT(=Beo@T3iRjPy5VLd+Ml`~jLiX4RaBUj`l5iQ#y?8tiOS{3Jm zK`42#-A-q4(?kwBgJ|Yp$Oh=3FVrOYyRXCWSnKnQI4AF|v3*EO-0-e=0T!>-tV->n zlA7_@Gp;p|UjmUobJaHl$9=m5WJDNf3BTK0aC(msRUxMfOlbT&TbHu`I%7QQq}o{xF#ZP0-PaJktv))b6nP^aqR_7# zTNJE>v?4zpp7velrIn#Z6ca|#5G4$Rg*X9UOL78ZHBCxCY+4(?-|=*wCA7^d1RjXx zbZz}0j-<8=tbCDy!ZVygKnpYle|>30^K(lH-;I<@wU~@O5Je+~2}CKAM4i+kmC%RR zstF_spQ5E-ja|S&l{UX1NS#!+E-+3m$wmTS1W(~`0{((wcsVhCI&`9~Y`AAIOnOXp>23h}UMN5g=y|i%_IEp+Xq}IUEYLwH z3Nj<$e33PEhsY`u4Ay&kKAsaGe|0EAil>)EBi~beJY=>`uhhbRd*Ep0jvhs%bVm;# ztKD(GTbWpYKoP5K^Ac!ujGd+!iLqeihyM3G>2COdpdE|5SO9az7!__+EsmSG)tQyC0t2|TJE zp`M#COcR}mUN7-^=#f~a0kJ7I#X?dJaG2MizlXpBpA>i~aYIvE_hOyr0n+HK4ba5r zZGgx!a}60vpa<_)rH`*opD@TaVaiC`l#$`$wS??Tj^KY(CU8k<%Inn#V*LU;;u6ka zIaV?+mE=SGvhGN*7zncBECvza)8Y=ZE$%wSh|s+JEh>0VSNpqsA^Do~@^tSU$*pjo zNe=V8Je$rCeO{E3V>vGyt7;&(4w0)!CYRsZ&dZN0Dg>?!1Q~qu;z{Q4^9E0tQhW-{ zJ-zZS%-x?eq$s|`9{V#Ycu>Y3Rbb@|S;+9~6IZM`ZU@gE8O{?YWVF z4s&2{HG96`$*_zlk~L0^L*64wcQv^@U(?{?Egz7>lEcZjMe6!VbL4_}{RM@j4G z61SL^*+B>XRGZkr!*hubokrxuDUF6o%%jds0+G`bhnwk>&)(x$Ksn+n{78=Y_~Mgg z_8b@v#!q=xcV;KN&Xz9GtKv^w1#3k6B2>GeXC$zPxnpJIp?4~Txp>KLhV+2v9}7It zHeI-~GXuNLx+u4s|SdEThBc7}dIwmcEWA#5CQ0*2$U@OQ4 z>^L2PXc;N=rD|h>2Q{=J`5uN>ip8$($-u7+G-g{;$D1sKsuo%sO17k}QO%kQ+r*!t z7v%k^!rp9WYn-LbPDPPSUaftS=u#aZiLTQ=jkZY3y0l64UDc1(Lu+55jFp-xvjz6t z{nL{}Q{P>*+IJi5dXfZ-0#oSPX@c@_S*+GrnQgx8)ejk%KuTpKG*?#Y>Jw3r@Yt^bQCXU)_@aFRuUl=nCN3^XxbiVgVS6h zwSA#>cgD}uCW?J4I%(FY6+>aHvPBS)%obK1GN&NEzha zuf66BL#>ROBSf0_YHsePRXjTW80RxxT|HS{41ILe%g`r@XtF+mpv}Jd?z<6O(5PlM zx9pOg^7sF}%oL-V1BSNhDd%dD)4fd(IvINy5tUE%%+y(qY zIFX25wuyLm&fI2U>7=@A=FLUG=RWU|20~S~*mKh7e&)+WPq3NK9_YGgxWy(H_Z@Vw zI54^jttTTgb4`;xbrCe4T_jIZXE~D;op~t$R~dXojS#e_nS=n}2Cftw?T1H6>;qQS zstvzrjLHofrxK*}&$VR8T z>8M1_f^WV-foZyeAKlz8(9})2M4;o*m+4~kpWnPfo4BMaoB>1W97T8#452(92h@M%rwqvCFKJ*nC$i_sHmYnAv3xIu>L2qx6SD8r%n z&AWH+-u`-&-;KcA^JtFzuY z-9mClzi9#9LG(XOkreah?XLmJ@7~}Clie-e(R{=BpO1d=^U)7)fBKu(fBM;Lv@^n( zX#~_R2#DgR)zt87#+mD@>8;n^4?iEl(T{v^*TGxv_|+>O=WCkx-~8s^Mz2Tz_lWzi zep_{co^do@ym=jd4Pxc>&qMg%M%kjwc+f$^Bm%{)ru3}>0Aga5Tg!VEok>coc{ zz>oj-&EJ0gjeoPc3Qc(veu*P|3Gm-tGft&)MPsBket^@Q_=X_yAKh<9T+9`}=H-Jm zzZ&h04Wp>O zEPV*9HE1^s|2vGIegJhjA}?jOA;n6n$L)0VH&U^IV#DvCo=^TWLzh^j?XkPNFPr1D{sP|; zqdukWWAqx}?=Dv5_q>Z#B%}-74~UZfMzTh_5sT5q9@4y_g|}#yv<`LafAs6oxA{N) z)30fx1?Zq0C3J&1==OpJxEJ3A{XOV{lz;x4-&C7Ow%HV$H?NQAKO;K7l7?|Zbo*p5 z!tv-lgY-k!VC?vq*!2Bqv2Ipq!0oL5hZrVS7<3}-1VC=T!z%W2 z4jq6P_z`OU<7a5BmY&{<9ledcskS`afs&eKJCX6>kT4 zA7+tlGaO>*jfxHIGW3i;!JtBBCycY2zn>HNXoN!#uy>=UhkW+Hn&bg#XrPDbi=!qq z&Cj9VXH+h3w2#Y%H4KA!J}0y4aaBCIMxJP>zGZYZ$F24fK@d)#JrTbT*=hu9@d-f; zG)}0i@*&^8>u!Khg2n2FJ~nN9zRA%6zp_$AMow6(j>mYRXGc}r@qsV6UU_A`@&1^7 znr?CmJ}ck6{^=k&ll>!q%~uNI04Hv;1!>*9`gV;XHW}1luyp>>YleR$;f2scztH9d z%pHQ#;B73o$N1SBPfXhM73Ld1s#jA*L!wTy2(hjdQAb;KaLmvH_q5<5$XJCQLcM?; zFY+y;3d49{l?hhgG)mxY{WIbf+rf9FRy!DGpIQ9Lw0RAWHjgTDs1 zKRDiAefwp~N(w(Bi*I^Ye>GBFwGOL#bm|8-XFlg|SO1=D!MaoJTmF0OJO2kb@#Cm? z)Y@a&fU@J6Cc6*+@W1(A^{3$PvzpU!xGxBR;(xWIlZ?{^8axhO&fjO-v%D;!%Xrwd zY58x=(gI=mi6lr~BVJuyT#ZMT##!t64%jS)6wu#sn1ZcA2ZKfpzVaI~uMQeD3l0x> zP$0iu)*~T13CqYW8WaGWZE_CBq$#4$KoZDXQSiHW+pk+kfOF&*gv<57)z9Dkd42DI z+lSrqwg=;%HXyq0?_ubN?`{{oC%yU>kypiLlJOU8vFCqL^A>)OfOl&D?OT$O-XwH> zaJ{z=DQwRO$ynHMF#d!_1FCL=^*kjsSJQMG^YfX|mM7^p<)=4s`a&{llGhBr8v?sW z)YJw0ZQkJQ`ky$eRlk1+-^uE@*r13AjM6*6NRbEn`X~HDJQ;Fyas0;XoNvhoK_Wo} z?~o2Lrz;HSCU}K&=xw&ZZ=v8m;|>RBRs0gZ0;klV0pU`>ulb-{VX(p-2&deC`ImpG z&i(lMRbx2iV%>O+ho6)3tUM9i9lIia`F6y>@4v61Btcah)WqK5;l+9L%TT_WJpoIw zVv@|lKq^_@E9;gN->X^+NyN1cM3(M9`~xio`e)>|SA-6R5SYb3Z}ETs>8UZKYL9BK zqPL@e!z6o@ZO!q=AZg%#$TQ;qJV4(9v%-h+58WxeV1wyC`#3o{2&Q{*zRz|Dh<4Vu z6Ad!Ii~IZ6UfP&71XR0Uwg375@U+mscx&MGF{3Cz zw>aE_aps!)Np-wye@Soz@_L6Pr$e<7Ix%p^b!W)ASZe+?U8cBJ9pF3v&!}@WC`WJp zi7&ZMaiSS{)OW7IzA@<|IBU%fW z4t|F4TWnlZu!~|6ad`V~^iy)=B9I2f07^W|wV_`?kN@%)wU_u+b*%TG#tq$!^!sL{ z{^&5+FmK9D|JB!U7k${rIbWjM#@z?uPXV@n$~xfY=HLBn{0BL^ejvYdUViEyQ2eUo z{TCmMPB%pH`o~X`8#vYJHj%y7b;GYG&e?c#n%z%l&xZvPh+n&2{Pb1#0He|5F<-CA z(-E{r4#m6a7x)cuwjXM%nwV-9CLjDB&I&l{WV{cbwzpx%?-Mi z-T~uu4R4VC;iktjtB{BSMv6vt3^9skL)mMjnVN2In z{2MROwX1W;XZYHgYIR507H$voyF0B}EZ=|uuiE?|f6236wSrk6(M82A?O^a<&mnT} zn$8?e{dYID{BQaf{eOVKUt6YuKjZQTpsW4=Y~w51dYd1x2B?V<9m3!K{gwf%vLQvt1LqFJOdrxw(gd9}h`itys?#VMj_q;6UEyWR6 zlU|46BU`j2bc(1XO*+!A!M|Tcwgy@&Fty$4%k%P$sYhIB&G^?fpXH+ zSf#isqt>DvU#c7Z_!3lOp!Y)aFrpF#(0hXamrqrG3$G>0T|)M^?KXce*|J-U-2J6m ziC9Im$L3D6ZR5J{LZ8L0l9QpmF}y1I9^O2);Q?2-O5RWw^Y!zvRm}rXMy$nr&VJ! zf20B*jUbL_F}}c7uKj0$T1G(NU4HL(5FCweF3xXGPbSxH|8T~E+d*_FNn>aM^mm;q zu!&`!Mo>lexETsqifm~iOFEQU0$a3T+8bNt8V@!$zis)mFI_{kwsadC`BJOp*STf( zd*A-0e=tWXb#VSTeaUcmD+;BR_6 z`d2cQ9pS52wcp4Om^{7Td9O^%lRpUcNN1(a3@&Wg<6zswwGjyMa_?8mkdkB`}uwO)~_=pu3l1z$oYH4_Y9$nJ<_L?}ypK>T$HEB2J zNDa;A_zZrPW3sV5sBtqSdEV}Zt0Dzew_Wk*znGs9f0z%ts=m=J zF3KMpk-{uC-|A#r(X~A|$3v@h$d5x;3D3oDgy6eBAyAE1HwP%;v+t_s)V;+ ze7*QBgn`;)ALEEWF={g>{}dwTiroS^7zni{k16TE0d86NiDhh1VKK=!Kb_Yl zHTdWC!tee9V%2aD*73iF)a`)WNp)M>5z=CN{Qt1`J#bN0S^v+#5SZkU(MDx86>hlx zi5Z4JBcLdZVWF`pYNg=_gHrqpW)w@%8Y!oVa$9SywOuTGb2lsBUANq_0=2N#b}89j zDs0K9hK9GLViVJCe&2KM^E~qmf7JH#zQ6ZCBbHMD;v;}Ck0O>P`nB?gz>VN z*p1p5MBz~nB3w|5U6~3Gzyi@d4xVQYaVuyxk@20(k=M-0kIYYAiW!k_C*id`m>9M` zIKE3;Dbc2yEyrb8DN;mMRQpVq3D+lN#V%;yG)qf^`=c{m=Kr=XQ`4{gU+*)?<_^}< z|8-p^I)G85f?dx4=MMAq`bwtpU+5`^HK>{n;=iwxGzll_hjc&6@t0FBdz_*J8lgYe z3>=1KOR%2-S8%{WA>D@a0OGhRq#3>shDGvRja17Fwl%OKIbV!SD3?lO1p^|WLl8Y<<<@ZYEhvwX+$(e=n^Zl#rzSSrn@3nn+FBk8f zNawQ?U4zB3PhbG!q&N4m_{I%MGwL;BF;E)u;=4$o%%b-(gpwo?O{<@e7;3Fd5X_PE zv84hHeEh4Ct8h0XXA$>b?VE8zNf{}k#tot77b1g(;wX1rwB$SozdF|#U+sxNz#r{P z4B!#5o0IL0mIC7wp19M#aU)kT%KxLSQBAdWuM}wG7k9J%Ns5qT8=75z+qhBYN5a%53<)_L^}lmxl_i166@{4rizpBx)#bC>t&+;x4D z>kF{3X#&{R)^j|dppV+x4E`anp*4cuh+v-^#3R8*ncjr0u^`Sp5av9)HG*DAyHMRQ z=a0G47kX_m9w?XB7LAAAYl|Y*n@33!*WaH4qSt{!kgWG#BxocGB^ahiegTz$?Ou;W zR$ITTQ{@}+t1e1%$;}bLt+GUx6cirLe3zV&qNGcQrDS`QOv#&Q39i#TcW-dej&*=bXy#@WAwwA6i9seBgW<^_VQPtCi2j)h5FBzJt!Jn?m0Z(H=o6NJVJGb5m)pqwrLjcVq zt5TP=sXb4!OcH zsPF|_*YTI!Jb6^BrT+{BxILlQPg1-3bsHNU12^BoGEZVBO- zzFCa6&?z=)5e&&hh^@^>`N;D5A`K7x5_*ca(e;0e@arBa<$J-cwi^3b(#b_nL z*-8RuKHi`b{&bSwe3+;hT^r-~d0{Mjy%K%4d2K!tQj(0IkouBPE4Qr|=B+o~7G3>H z&sb0*3Um+XoIet;aw|Hi6(2iJ^@MGPh3V^Qhn|v^(-zxD#WD@u5L_?R70*8Cw&8_l z_khuPGk#Vrbq_>2Bi#cr&a3?ML7U+P31E+{-D_(fzYavgelEuQIT9#DwEuca^rB>V zQAs2r6%tM5L`X%6CL<9Q*zS*SwY38a6UQaXb=XdIc<(#3CE5g8N#_-UC$xCp@_<+E z-U5)(O>D!HC?6jHDvzzpUDtKls&em~E;h+8Q2~ah_8#i`w#Dewd-j@|-9DYi2L0&5 z%S)g<;AuH@gm8ACEUT?^4LFTwM|}d74JQue;DJ{4;Q?TmOlLdMIR$ivux>W7u25y& zrT%%yNX<&-ys@BNvDb^CYW`FiMty(=mPN3OWtb2LHD5enko!K+(+7Dl+--y<3JPz+ z)-I@RL$je_J#g{|=`uz7nt=-P9MTgs=11?2K}s+~*Y-zxW0oEL>bB+>-?L{)(uDnu zsJy$`(Bjj#bm^ZYyW1K;wgG9WpF;1(&aIxj0GLO5_PfnRW~);+WqR-V(p~7@pkVoE zoc<^2OG&g@NyHtu>e9$`$U_UaAR!d1Z05ESHu7m~)W{M#KG!68LXzto(db;7pW4F9 zr85nE)E^ftg-tmh0OB}HDA2GY^oP@;&Uw3%Os%O*Ous*qxFL}-+^$p*$YpyDD-=uR zIE7WxdtpLj;{$D!Crj8f(Eo~pU1gC*hRCxqtbZAN;kSGLDBi*h>zkCfd!G_-k~5K< zFhj$`_*NSDTm|_?g}l}X;yJalztR_rmgS_dU#>#6R^*y7g?)G-7LJ9NBN)gm?`n;p z)&LGkpyM}L9IBL({9}AyhNGA$-?Kt7*bcy(WZxHZ1Qwj|+D^bwm1NGj39cKV@#i4Q zo0Z|&iziu?0WbcSGV^VaZaE1I|A4zWj@z@Jvyav(Fxk}>o*@+?j$lbLD1S4N^4xJ?S!D{1W~jsgd*6z zsEM`{q6x^_@J~KWmeklPug}HRF?%I#DoLAO!Oe0 zO$kY79f4=%NCo+0^^qG~ScUkbZ7sgb(48wbk!wjT86aM_!F+=yvJ5>&tqpY$Vlu!Y zOH4gkff$>?txo$ZUpC5o@ ze?ki)7Tt)!CF}v4h9wx4;hn~0uw;@8G9HT>K#6Ho$-@Rz6AVxWGW6wp^%5A3neOsv z9lS+vK`grU7;ixgJFDy^grwex)Si7vY29a-yN`?v)cXy@b$FMcfrV{?_D6#OPLxJ50DTv3UPH9!qMwQ2TEV#&H+E1a_pt>`hbU^f3=H&`s zNemUHXYE5n=o&p7UBk9p;>p`>@7&>=q)OXv6BZ7i=sLCC;H1OHo$g%{E!^L4WF1vB zK}5r?&fTk%l>?#?Y@k+;pHaQdd zh|$))hB)pKkLx;UqIgnK3E&R2cIUbQlvcg4&BosDaB`e3$C>x{riUz^8j4 z+y!$3L4ytGe?yuiN+vf06FxM;pfLh@yEJt2{Sj^1n473(@VrGPj%Xbz$MOXviBtiR z&eJmL9Ye!xHb)>A_MqNOgp=b%G=iuC_SEYyCeL0a3}r%BV2b*n4W9#mD!X8Y1rFz+ zq=SP8$@-Q&VlrU;^ zT{)OU!pgQVjt%VEk7n1MqGRIPk2E6)K| zo`bABZB#{DJFI^tJY7;B%!ZRahUR!}<4CW_9TrTW(YNm?2CMA;cH;|b6@mYiu{WLtJPi(4bHQ9#CojGOz7`x8NYDP1ogZEWxtnh1z@G^4dB{jyu7a3~N2K zkKWwPk!*+DUB*$!$0-PjcTTV2*|tMpeA_~E8f~U`J+=cLM2iw2-mZrrtLe6G*SV-H z+J0U4Q9?8h`pOYB^-NA-{UoHDXzQLJe_p~vS#GYrHzWu=nhpX_=Kpj5<;+dE=Y(;* zgfRe`H(KpLI6W5%oj?-E>tHBiF=gF1v7j3`F=b4?1;tH-BQD5e-(sBVTM$qo^+GD% zDRQXA>mg5JToXvSD0KB?g+f(HSdV3H53UwtouFh9mBJ80PAz*-!Mt1+Cc03Ts!>@a zuq0EeMos#brCDWO7YTkJJTSGJ9j6Gsj+6FTZQVlfL-2I}V-Oxy8!CkP|X5?9)#)4k3YMi%#I2PwEAd3+z#-1_q?6Ai9fU_Cb zg!!jGUZ$?-C5X0xN|Dinv&-K7hviYk38ZK3KLk%e848^uJ#oYLq>>)Ph8${ytQhye zKV3_x52C>_o@8y{{dJIatAdx}n};7SruoQI;$a2c2X^2aZAU0kWF9ex*G=k28(u6p z0=y-jd{B%g?e*ltJiyz^iiMwHSpOHw+eKpvDjPgibQN9f$P;>sYgt-^`9Oa}7H=3gQdx5FUH(m|2(u5b434It7QO(8^03hl ziS{9SW6(`XfTB*JX}4%1;%;*9auGGj1uGTJUWUi^E#&B1a>J%jGT8|y5p1-5OSx|D zM=ly7k*=bS6n7UD!%a^kz(#ZmqzM?_L_N)sS zyvG@+cVli?(6c@OCvw8x#+fv`;Xh9}6(Q8KohF*Na%&GOZ}k}DV??OX6Lh#ja;vFf z-v9tj=u0pFW#5VHhid6(_K|pVW*XJT3a9G>mYeedkkd{kLH`lb(nO=cD8xsTW+J8e z`tL|%6lsJMQ3GmAY(ol$Q=?5)u&I4r2#JY=^4Dqx_6vg~#z=+-euWgIKrcwxuLEx( zD4Q6H!+jwPVtRw!w0XegE{}m!VqNV_ z6Wf?S4!@`y50*(!F;VS%0bC;Vh6jtGfYh}r)#uv0^QBz@QrG-uuW6(n=Ru>|I z{rVW4kW;8+2sSscxv>&PscS#+U;P7Ek*gA-u$3r!Rqdgv+od`YyCA-g!U^;gbjv#@ zz@6nKB_%vc#DFM_2Z|FgB^E=JxIQGhvl83`0h$bhUL{a3B_hdh!aNPZ+3b_-VZJp? z_NVAm_NRu?OE8Xbe^RA zF0nXIO?Ab&N>eSQtrcrX6K$JGEp1zNd89XN}&CbuuVgmYk+3>7kLc%4~IU+#f1h6lww1Z6htXw$v(1xoT1Z1K7_d+Sgb zC3|TMa-dU(n6YZ{RlU!qEY-;w}DTp7n5|p(y=F4+1G>~uewD@8Eu>6-u4xe1Y21Lnx z(zkq}7kqM;ccN$NL{~T+T_Fm~U@fLK` zBv%Ku(q`U4A=eE33ccxQI+9V#{!!#^wj2?=BY*%{N~Y%V|}AMws{jbt^;{c z2$BZs+Az9f$Lnz*AjgZ-$bP#f4Le?|DUDI2Q5`P@ixGHz3k4Z6Vs=Jy6v{-exJcAU z?iH_6fMll%l+w|WgTh(bN;0ZrpAKJ1OdNE>f9QsX#)~8BjM-;!{AHO7nK5H*&nVcU z6_$~6B2VdY51t=F>z#7?{3yd~wj*RmgAJql0Ep6)$iU{T9<>`18Ng^=tP!%I@Yr^G z3U(P@oYQ1@tp)zhc2B_$Lc}h>c6I@F5cj-D$1cDwJfeNy`rrb%_piD6mO^=JWpKu# zOCG0_xzxk=l>5!96z1Y(Fu!syL<8<=SSp}|iQr7nvuH99>)Sk@rxnBmmlM7SEVw>; z9n~HtTrVSRO{NJ|NVf|vsTSu<8E5BL5Rb)#umn>X5P2+a;~irH^gvxuC^E^;SQu$v zShF7kQUE6hrc**H=u}=1dU(Z^Sj61`6b9WuyF7Js7&gS#rA*x%hr5NC!{ade1KkiO z!3gp!*p*foaTSQ}IT!u$u#BKlr(gsY_4G2rSr&y6gK#R0I0T2Shx+wlc;U2hWTup{ zHl2%nNtZ!g3Z~2jldv6eu!Se5n9@>%U{Uy_oQ*i4aQ1}ES>inUGtspWD(W{+!R-6t z0SIQ_)w`J#yBb8&mNc{!c)s&4N0hXbIv4cDyIO55=&iOrB=4=ZW_VfwwDNwM=Q@Dg z5~Gi|hnj0Ol5Y7XV%S(@K|jPmbflI2M`#*B?Y zVa~Jg8}7nbC$2GH?avoYdkP3wA1=`Iv6xpxL+K!Kp_8-o;=a6)-&=9au1T9DyHZ@UC^;-=n>_YH-U? zRuokYlcH$ZCQrdOe+f}TyEauiWUIx{+~MPqN9V$@7B=n07HxX0EcZpY#u{D#H@1;U6^F9YR1qwt|nG771Pb>V!4WAxQefmL8n+y=`GlX6~f_r z0J8a~QTH&adnY!B&^!&b$u8PjhidLxcQB0AZV!YX(ytP);eD%ZJLy`hjYbpjXGrc( z%02i2wMgmv3wpVp5|oN^+y{&(hhDm{=Y%XMii(EFe3&3G^3?cc)RkB`2f!pzhvfnp zED`wpAA;~aGRdFt7+^Sr{bXJDGx#(qJda3tXk>&`Ud<0dcn8d5&y3*^Mp2Km2whdr z6#%nvT;QG4rfDQvjO~dKKpmQO5U|7()AKpc%|H>!0aNUc7klz!dJYOuI0o?rj5Sy9 z!5AUR)Ap!G=lQ9==NTjzlIszUB>R}L!gY?@7OR7WB|nu{8E9+^!%LI#J@3Q!0*t0% zm8drm1rtk|Q(Rc~iG@E)9~Rd+a!Mc!qkV6jCP|yd%xP$1hSShqe!Q!wZwsO>Q|aO< zU`k2Z6e4DDO0+lJesnm3*hz>Sgz>_mQD-3vDx_OA4m}i=4%y{>-`F3}517=`4Gt(c zJMd1&GMv|t+EG8yALdFAauoX4ON%bpkPa7b0_!H6ap)vb=C$oU+JSz_kA=f`0z zFqXJO?adrts&Ex67#O8NyuoQ&O6&E;$meo&&Ex}NPrC|+*mZf}0EnI)R%|@b`Tp`g zG_VOXM;rZTZ}dlQ^y|e&d6Lle&4z|I0rF!ws&xl`i9ADH3DAx@--PrSkJ5oF-jI@@ zwkpfBKazHIzT@bJV~jl{u+wY5gsVn8{eB$r6ut{BPO#wMC|PhBEr$6HK@p3Km_Jg^ zjbW8RZ1{q(_0zg{Khy5|n0DYf(f46XT|Xi03u23M`yd=d1#NKahs#Q@S^AeCA~+Xx z=mYiNy_=_P!mAi&G5(C<-t~CYy*%Yx}CNu z?m->~cTw9Zpe_P+LV~=Qq}t;h+jx_X`>3PeV6%z4u0t}c{~kD6g_p=qB7HRzV)h>y zKU=_duh@0(ozp>^t|iZZ7@j}o`N;5mpYHpkA2&w-2>S!(G<%oq*Bv+)dvR!#g)x-3 zV3%0?Z}t@IC;wXVyzv@k7ckLbws;PC`aIuFIB?+y6Kw}B#s&m=Shn3;u!FL@y9C+F z7J@FO2!8c;;y(^T>@m3KqYb&D8176~nSsW{&=hkNXoya$t*ZvJ2Ve#|Q*4gG+-f&3 zSw@o%;+te)(G@gWF>E-8^cxchC+CgIbGdCmjlwNY3-ifqYxWlWPcel~8>ze)K`_9t z_|s!khkO5G9srXL8eW0G^lMr>B09o^utc_I{k~aEC zmVLUGfe2Mnc@sJW-=_#Mj0!0&a}%l?syjWL2TScRbON4T%?gbapz_g-vD=!U1# zGdGqhq)A9!gx97dY@viNQ^N5`$Qzt{E~YG8mLQGEFsYHWxA0-2U!=isTYQ1;EIR0i zxkt{C9S0(Hs51&Dn@LpCGg$cRRQNP1d^^@Q8V45{cex}Tems{-l1qrjMtDqn%iPEV zWPBiC0DI`w%fIJHQr~nDakzo<9**cjF(36Dj_}d*&BGCW^lXu9*pP@cAM)?vhyhOV zQOn^an`N*tt*gV2F6p?dydxRz0GZnQ^FFk79FCCoF+$sB&T-hb`TbW?fX(n?13lIq zj4tssSn&4#v-JDnlJ@tXB>*M-@^4=q?pS&8hqiWn{jKMY(fl~v;f(T!9qw?Kx5HSq zF}FnB8kyk*(Ym*=-qms>7M%eDXtuNVD*XOv?d77rFk6T9uQ!75FE_KhIb29k zs}24HNT`%MOLyHkX)Q_}$9%^QD_PD+_b_)NO7_o?Q)7n^F#+@Hsz8c_-J%jCsK~@P zFc*4uE5C^*<%H1f6THl|-fLU$v28+~bZg<$H8W8bvK3fMAuP@Ax@O6F1^uoDea+-( zvo*p@uX8l-mzrp6=5%ndHoV4C+Kok+Kk(a52^Ndrn&EzJ<+pac{Q_@-Dk@Rdmd9(E z$?cv7C%<%i8mjO@^NdJDl3;k?E-t?vYa6npk-pg4TL$1TbwGC9U9bbrd2UCCLD9jb z)Fj7*HoI*;oj;k=>5%S1`WAcwx-2AwEpk~?fcJf(R0v)} z87OqWLcfx;Tvh`SsgekU!jy2lAeGL?f>#MjczEKU$Hu%bP8jG^!W-QSZ_dhaZtRlvfb9Adg7ve>jiauMl@6=LMp+r^zg} zBCcCC4GCS*aJ9`m#Eak`H4%dxBpM;6a^N$J+-+|XirN?@0o6>FT zUD0mzWq8MSW&ap=`6ha&i7Bh|BfJS!G`tYw-mBXXk<} zW$YTKQ_B10X;qqdwrvyE#}AUc>>GOv?kC5REU_qoND!X|(GxIlfzDBH-CJixuNm%4 zB7eY1Lq){m+%|31lY3?ARqLH>k}Fg`*A-!wfdGr)o|1**ertcat%FYEcTBMDr{(wk z6Sz0ik+;*KBkyKE=tCXQCk6?_%+-$s;t93`L6bG>{*$+SBssTE6xq%%F7;1g`#WO08GR-XSus>zf?a11PgzC8x0%Q z!Um!UF`ti-7Du|M+y>g`Cj02WhK=-c1P?p9kZDu^T(ErHQsZn6+_tU8ZHBPT`ym14GQZ z5n=tBnvsH0?UhF2_CT6F8sceZ2iFU|{W5joggNjHcGW-z^4QPD5Ql0M52kiIHzufs1G3#+ zw@Y%)!#f&~h*~&`ste$DG?%+MtTDQQDvWQecpw=~7zZzj@x6?eBZg5!6GC8^EzoOR zW>hyQbdU7H5yEyBnpL1xD~LBY#R_gEc%a)fP4_KDHY{aB!oRd#$#sF3qcoW(A|1qt zs1OdNoC4&mMulYe(p8LBkzBW60)gfT;5xc)Aivy|hiO-&PFhheEZ%X^qJa}lSTJaKs%kA(nB-PWfnC->(blW!9 zC@+7Z(Uq5NL$h*Q{2OHj9X4&l9mc*lrQx(~^gQem<~1H%&Lv@@2(z`=(FcAu3$l%P z^xTUdxf$vUgA&8VUBHHCP;rac+-U{}YtK`fF%EN1qFV&Ff(1GksEwjroF zCW?I2^=DDuA(n($F$o?$bDmr5$3s(b)1JktWDWL**cTxHw1LLgg?(6P>=AJ6W#~3R zmO2jZxgMkUkyQ6XUr++HEL5vjZys70SF3;vlg+eRn7@e(8EK#s`T{{;{}iMosCzdk zy5P#~VHHpf4I}MBU57~N<>(aUUU2RCJmOraO3NI7xW;nQ^=|Awjg{Re(R{F@{+tje z3N}fut?EQwDSY8mh#6b?V{8((NlpwZw@Y*;jhfbpt%ux59{?;H&cEQ*e~!9PBhz5D zt*^m4&?Y!Kftbe6CpfT(8_8 zfOpJheq-3v%r~4w(K5*iER1}CcuJ_;Bw@ri&jb(9Z4bCw!4{3#VqT*`mye~9K11VP zZZ!H-=XUv86d3qEx=SmDT z(sJB!uWtm@T5JqJ-`P7Jf((1;JL>B>aGvSEcQlqktH`{YEaH~$2Qf=`-wCnlKN zet{c0BM3RHwB)~%>v)MKhOQ5yLbq<)D(>gg%hk|1KUFJ)raxJQ0gKKT>d7>ML#xIy2u1J{eP7Fwa9!Jzl%CGy z^+HTY=su<;3qgl+?++FQu+0(50LjHm9}avewGek|^sB2q{a#+f;WHuq3Saqv6O1`@ zL+>6KRS$E^ ze%o|g`3LZ)^8AP<)tHa?Q7s{aQF`vTRLP5FK)y9f!ubcmE1lvUX#8j*QiMrf&dqsj8`t;1G+?AX{(hMNchIiQ*W%CQU7FbG}x9u?Fi zuAiYC8Ws~N$bk{r0V-NqQm}P$4y7vdMp0vQ+V1cDvtg&P86Wr5L9Ts<)iV}-8Z zCd4JfI5jCZp%qQog8O&*x+F+RRL@n2+1hlUYVBN$6rprsyI2Us49M*;{J!ZB*C#1Y z2v{!SJ#d*smbk6neDV|C=>_U(fgpwCnunT9=0p~LA8zPW$$W@$Ff^C9v-u#?@6<)} z9up_BbH7Y;H?Er%a^DyrE!05ZwaCTrwvzqe26e(=A&KOht>R(5Ln*FB?Rj#ozb6|F zxe>gQ4QMW!fxRvWp3tv8dLdPWB9J>xgK0pZjyNx`Zo$DFp#5u=6@R+8Lw1anw)uuD z-NEwGt#r|FP_gF-^46_r7GJ`Vj>|4TMIoHn$E5|`JzV+*`BJ<*XNktc!^$5@LHqa= z7w?D%{+wLI=s8`&pYw5%oQ8Zpb_2VR36_=oxo(nAR2SEe3V*IwNB*3T{W(z6&Hh}s z@aH%a{5gya!O#w22Fb!yFo!#2t{O!pz%s=~fvbHJluJsMxa9ad82*=!qRpwAm+g%aZ85Wx+#l zMDhetIZ*wCe2_LBTQj~hO)X-C#BJ_Qkt9-z*5_S#!^V=@tgDr}kQ z75alY73`&;z6!Ue^wo@3@nN2fSjzpd&)s}JtfSwcFl;xZx^lY_%9q|T!{PE8Q%2@KsbH7W+VLQIYST<{`@sT%|y#za3|jHhZ0 zq`H}zuJ|qHvpg-*}NzO(n5SpkPH&}?FnB@Ekb|%Yh#p76{90iBm2f?E)%c`Ag!x2Ba zp_l-)?+V^7b~v-8i+qWPJrV5qlN68;^|Ge*`zJTT4MCYzqD)50IC@pZmy@DLFf#e* zb-EijzRf*cAD!I7PO9C>+*Ez?BmpS{()>9j4ccGclhq#qzD!qT6Xl*{9FoF6`M z7tU`OBpivf4cz580qI@~t2x>>fT211)GU~-tCcGvbMSG+UAQk2iRgmc0lt?NZS9ZV zEn$9~R6ws@`hx3mqTc`Xmw)Rz+<|?*-($b;_v0lSfz`VfL+BbVZ6@uKGHhz*TQcRn zu_NIfHZ9lv1aa#GxOHNpEehN9YB1+;LhuQ*J`}0Ygl*NR9cqF}g%en9i3$UpazH?_ zV_BlZX6UZv`y#g<#NZh2ean#uUE306?`|$y1r>&1;}!uEs@+|Z8IV?{eK@^`DZ$xHHc;XXm zCv!qO=CS@|~pASc} zKrZaaVw~JZsdRjZ1PM~AM|Bj*boZvod(mar$2W=c+kCzhes-fA+3UkTV@%fIi>86t zdiESfHF#WG?_UmG7B?G`@xqRBI3{=y15hsel=ox6CYzqMXA7nZv)YSwEoiikdSGl? zrYSxs1&my@@V-DdV*S<7V6-r{aK^y(2PI+)b-(|{la$?P`y16cMpL%zc3jCtQo&qg zPQ>3(C@A2v&Z4sXPt%kW%RgU=$u#?My9=hkz#ZWl&{29ZD*$Jds~W@1k@Y!29tW@t zT?+kxA*Q^(X;4ArWOgnfnING6&`3ZlfGlWa4MmzCDH+cJOmOcnNJ?Nz!}AhI*|>3? zh~*1uWRhqYzSmVxX_;Iv9WmtjJz{)+W9;k5Bd8%cA%-0VIkF9Rou?H25F!N=FrN&2 z&0E-!y+LGWV=5>K;#4AuO&Au#N2Pg;Yw?S2TS06k#Dqk4OKFJCT#5sLG7>EU0c0F6 zL}D@8R7p30i9jBhT)=4jlTaGQ~wOE}8}?p*Tj^a~o$Kr!(Ah3~`1RZ2h<_W*KA_qP@ZP z4>WfyQc%ql13_I}BafWeKv3neF%Z?<+9Vr@IOyH^zN1K}HA_0+*-e>yj&c?%f&S3} zRy)qWNzPAO?J7_s-6|}Fiasb7c8n`W+rmYhjYeD1NOP#ZojijAA}L_PXn0B`1b9 zvB=#_L-57%d>b*U8BInRwH1|yHnYL}8-~h3gQ=XgmQBAF+kPI<_xfn3A=%Kc`H8+| z7~4t8%eM0lKlTq&*A-2Q+)oY(G=ttqOwBZqH4#EfARL~U(~Z^VSnnJk9x=Gu zP`RB&G&<|Xq~O%nT1?6@7M#4n(LrdgY*zhwVAm0JpJyqBF$Ju!)KkThH^xUPd+`8k zxcedlA>Y5&$}=52f3|iH3eh&hx3FU0ps~R959k4|;{qgbJqBDqFof&e@WBR}P{Ku{DkEtzt(zT^udiACHLe2~@pj-K-Pnp&A=E)abP z(d3cgPqu+^z_4yV{*kE-po;#{9dS@->p?KjD=FQJuMNft#!>FR$p$*-QhARcE82uC z$o(<`@()-BCR2jn4wK+XLe3??SYV@W|J9rD&p?sb(8s}_6UzQ4^D*(Ym3{(kGObbS zr|2)Y?m>}6WMF)!%u~J0t0;b!Y!v?$@TBTEZ%%xMw=6!<>x{Q}7ssc;)LmlzsOG1p zfXCFeoQ>FzEKlv3YeNT0J^V5GQ2Y2K6;re*Vv^YN6`d;;ANd2kx z^%}kPJ?D9z4pX3KD%x*~OkK&w#_Ry%evCLUl6cAdnFOBXpv*jPUVIjKlb?@Mm95tmlARjtt@le>`s!hh|wj40qF{q4g_+`_FiK4tPEon%;A? z;oBg;zV)<%=68=CMX5*q8yZ(iFB5k>$0i&HfACKZ8D1cUd~?X}c^~Yde^4nF75+?) z_Bi9m5uXkn5fNZIe}0Pb%-3L4EZAEXA9wT<&+DG|tnXAuxyD-IPS-?F%!wa&F>bL0 zBVYHt#hJm4xCy=szxEsg_m3U&9r|JvP6Ww3SSfkln(+07`$`NIe?r(>Vp!HvVt9`X zJLm&5qkGN~EDnrMl;zSQKhxxAhWyNupE>d~SAOQn&wTkgUw$r~3h6#P)qn6KOp;Z;G{`#qD7_Fit%eso&g38rSp8o%pxp zf`4eeGk^bs96;y5e~a*M06`-8kGhR4gnh3C0?pHLeGsRRB%H#mwbXO`twR^C=K)tGO07Og0)SZ*w`mz5dqPUHB* zv|33@%Q0FkMzh73m}X2$GUk|#$%)2Uvy7>l$ZV_8mS&tT|Cg3z%;x0TNhn`ahH;HC z+g?#o=`@yvt~u%%(DFI(h6tE<@Rb1HJSPw?pHKg~dm*@le`4NwIp|4FHm1!a+$l4t8Yw}#Q!K_ACbcBgQl%j| z$(WpCOitCp!Heu<;sr4%**ME$r2I*0eiV{3gNn*Aoj%QT(V{g~_99SBEc(~zO*8&W z^jeLU9OJA+5}DI#Gvcv^-lA%U-Ra1yg!I@doYkv;AnnPfe~oJJ*GgPYI&E1ax^wJK zd-e)f#cfa{=#j%Qv#he{wwfPG^S>YysmVsu594umWkq43W2L>!1^!+xrI|>06p69d|-Bwr#p3bOV zKGRiF;;6n{5=sw*&|y_bMaN>Y{1BNCVn7_A5=gsel{(7Ue*RE71vNtyNKgpXl6%5` zmA0A?+CUi^UD4Cj6r*|O52>>vqc=F5uIdVfr4stI#9))n3>JV&KoGS3v?9_bG%D(r zYBaH+h)PlejNA`6Yf1{sv+YGI9P=EvyBrloWcf)AC16SlmY}1Mj1@L-C0>J6Ar8zQ4eL) zEcEhLBTP5C$CLA9`RwALo-<}m3uusPst-RTafa3p89qla^Qqcpxec8K^v&rgTwYeW z%w7glQ^T|rE-OPXQiu-4QC(eGEg6k@F1wN6jVtUG#bu7_sm4KxjO4EsJK&Buil<27 zYKE!C@ikJo(U>qEzT^0s2~ufAmCGqnMD^?p7GHQ$ob5JNsGg&8)j4Jpl*=ovYPm%4$2~K+*!d8TX2;U>5{={ith44Cp zaiY`y0K%9_NP}?AWT#!4g0u+FT`COPd_COhprQk?cT5&jb) zF4bwj7Qv0M4`FPY(_W77A;Qg8r~ONWO;errIq6ROg&9u!0KylUPW#`dIqi{IPP=)A z(=Ioivu29m)Er`DRdwac(qiJK>TpvxL3{{J4{l5~hGwZS$}EC#49XtN3yWJq)^@m- z(Ut-0H&Qx?5>d=QC{Y5+mG)|Sa1KeyDmjW)R1)RluXj(a{Wmk6_OI|9pY60C!}C=< zrB`b0qjH?~QF#6q&)>gPYrokBp5pu8@Ko}vb&%vy(Cms*3{LE&WsYJ*!6k&GOf}|J z+KVxikf1L%(i@|Ly&xJZL80KOmpPEmRw)CQL2%Q zA!LRSMGRN|cHU{%BkadN*6YL^juM^!lUn;6h|j9Yt1xJ+c2zh_%N;Ctu4>2Bv!xlu z#c1pr9Uvo)a9sNEeouXYBs*uxNNo4QA6W* zIGHjQDp$>uUtQ@09wpbRQW)Z*N*IX5+VL3g8;u1Ow^dZG0()v4F0i870Y$L$Kpm#U zSy@zBW?bp0uA!0p_~I#an(AbEmO-OxO7CzeX^BEa4Mk2f4pqiVm(y5TVyw1TEQeY+ zVZt3wQnc|^lZ>m$%Fu^%g~Lb#A|S0I0um}*Wo5AJ2~vgRlFINOnfbZ)xzg;461=^9 zORfELIfTp4H48=DbsE@{OKR;G%il?Oy6{c;4r$UTcz)<}+sy*k&*gIe{ia%b7~#{S`f1Q6`&Y|i0@9G}^(d11T=9{i5pl^{rK1)2u$cin=)RjS$p{U^Ie)8 zA+LKp9l!Y9yIV4np5Fe~E5Ds>&3w8)=bwhB%#Z)#{=Y=Mbo2P^${ocCXB{9FILTk% zAi8hN;>1}Gisy!X;(6-;Ki93FGK1i4V~hB?{y%=fPsiJD)6-bk6h^}Y-^$!ndOoq~ zqH;X5KHZY|1fCl&-M{BOJS{)|$8FKsl3ahOsiR?`Wo5Q>VD9qrmS?i1%%3goUi5Kx z*8P8|z2y1xbMpUwA1@t_pFQDCwH)?5Xg%N-M5eIC&u~Txm*L%H;Bzl||Lg$yJp#rL~ibSJ|se zCK;ExO3UEttv1fAM$hgv&O>(x(=y3eoiqUh(JAIBJXdlC-x>a$k(V?2amF5;v8QMH zk2C${8UNsne|5&6JbnL}HjY$n7UL~?SNwbR855inqq&LC5meZjm;W&i#L6Y>?zlnH zt%=fI9Ie-H&`Hub#8c?H`gnlT%or2Z5H|Cii2T=)LclTrZwH+INdrlNhf86Ug7Pj# zc*%5}F5e8omk0B&L3AtNze9P!{Lkr>{96!xW76@!#dxalg(Ck*bWcK3DmxeXD8HI- z*1h5P=-m-vb!YFJ@y3kS8HDDV(VR99$#x_C)!Ov8oxB_>gF@R2fq+Guuj=&j9Ynqa zZNB2u%a;W@Z`S4u6AU3*G7#AaN5hJ!mka&*8%=p!0FtT3NH!5n*iSh z_zW$)E(mW1{I7s#YvF5y@Gigy03V4y3A%I~@HTD!u!D?%>ks%dDaQl1g}{YUas2`R z7Vv9B;Iq{HO93~g9uM$@lENPWW_ex*_}PGuRKF&`C!Pe}40s;kBgt47;7-8fwDipn z@<$4nq%y#OGi0mr2$LHs`z_>G8K2Y46Y zYqW64c$iZNIq`TK(xjygZrW zz(>-d9Kat3{7Nl;jSdt8-gFZ9dcdDN349yi|8^4ie!zbN_%qt_!=6^_*A2MEIx>Is z*kD?85_mk|51j;_19;0x;KhLVodmuf@XMwi56l6bstpLAt)~ib`vaagZDbwV4|odT zdqV2JJ6M0f-vzur1bzp@jfm5qB}s|XPcDNANV5TH?hC=${*XAMi(!R>c?gunO-0 z{Dzy42d)c&U!%eY2tVLkL*S3A@VH1x`X2Cq4}m|c!qWhMaPi3a7Xkhf;3L_MTEIU6 zJYI``DWezl2mEuuw`<{H+tl)R175l0cz~`j3D*As72W~(Lm7dOK+0$zxxN>|v;YPpLM z9hDUbT&{&LIlXfGfp?uYUzm__rQG?zdw~`%Od!)t3hCQVurLyivxCS@H z(D8jW-R(jC?m+Znz+cgp8U9DL-46nO%oYedrp+Jr2qPG&o>VS{#j^r|4I%l~b3TG) zAnXVHS0V7nRQO`V&%{+l?hv?eR4D%%gr@-iAO!y2Y0;I7tI!=G`Rr=BL>K;n+W;?! zDRw7JFpSDwjBo^S;+x9PuoEO9t{C5W7djNkH@J2j? z_&qBAPDEdH?eM<&IKWOITmkqkcn0&|q~@o8`C=*Hdqd!_GW=BS*^7Ko^B`W~noFn3 z=R>||Z9dtb8!4X=X{j!IkZ-LvANe_}gr74~Udl&7x-Jl?(B=)hRpmhj;^zYXSqS`y z3SW%)*t|fXIs{&!!q*_a1n^nlrOM;*nLG}mvbP}IKWF&-M!@zUd<6JvJk|U&LVdU{ zfRg400@?7z)HLDK!-)tgLqgdLei{gT6QZ|&SM}Bi_yk;GK9SNYa;6#EW&v)YE6%m> zu;XeSmjZrXK_I}3k4pYB1~Y%x0ecR1nvvr(>*Gm5&++KQy^g0(h)vIZL3_s z|9W#E@UpgyuoqQ6tN^@faUk$W2>c-xz5(#h0DmR~|I>_r67p;Z{PtS{0lJhxt)o2F z50@f__%R(wKes3l_-9CY$JFu$0H28K-OEGDD^=?g2R_s~0)erkH06bl8AS+Ar8Dy@ zAk!h`&Q#0IK>Qtm&kcc#fho1+VubC0XKLZAIlDxBScC97;J*X?L-_C%_ikd&1K?B9 znRxpkdy*^4F(NO~IS%k|@l?wpnfWp(GfM&b5@}2{fKg;SeDe9H!z154BimjoyAN=> z#UhCJBDHOZM_=BHc}P4}c$iSCQ`I?PL*S(IDn)?nwB?5VQ*ApcH^+neYRfGbJR66& zH3$WOlMkZORUCwGLG%*9Lu|OrbIQL5VK(63;Hl;h`@33xC!)vQ7YNux;KE5G**Jl) z1n{3|=~r~*65S9TnS^u;y@9|~Exwh4Uc#3L`0arIV>o=iq4?GyT}ophaIZFfxLa+v zEr@^ofk1%nfl})ieuE0%gZM)a;_g8$Jz>9Qf{cjk1bo3p{3|%x{A3H;g)O8s67*us zLxakoG;@P#=)8Q^&jW#<560sS+ElV<&u<#!hs*O%r;@QIz~3Koe)D%GZT_ zmud3}_xzOk4j^BOHea=9!&8+<`G%YSI#s?s$T!@a#HsS_`WN%sp@KZBoLr+h$`no1h~(qvhx5> z3BhYt^@6+X)X`Jafym! z9kRL~3j_)VbnAEP?;N$?;@3#GsZ;U8$M%9%su)UV`AMA*<%tN zXOD@Uv2RSwj5o$a&uASJHRJU$`rOfThKapqbXd+X(Yd2Xq|qMKZnS;a*sz_$MCXh~`Sdpuk758L9<@In2#{NM=I_6k10T0^kyuKbe;z53I4qJw z!p2Bx!_Oim-$Rj74sPtrMW}-laTvRqh1*f$@?w9%-(Ff4K>v4}$b6;z#9R?FL~7I!m8XlCDKKj`v6LoCO-!BVR0V zmH}54aQ35o9r6_d?}Lcz1TG!Ye?ML?y^^4pVm9kp5U&Nze@5M`NS}kaCh(zYT_pCZ zMo3i%PK1pJwFoN^)*uukEJfIWa6iI2g!KrE5f&lLN61IWL&!pS3cPq75ONU;5nKqH5S~TYhwuTyafETdh?LS0<|EvO;6``~p%q~O;i}D%(gK7Agk1>l zAoL*U9tAB3DG2isDiJm!>_X^3IP0-U$$)SX!eoRDgaruI2yTSU2u~yY7U5NdcM*CK zB7YevU5b!_a1+8BgohAzBK#Sl2VvBfNa;$1OoZzZiV#*J(11Qn3de<$5z<*wq!cBM zlFpV!OVQFfxYxoU#YpE$W2N(8lg^iZBwZlI;f9Ngq>EvPE|D&kE|ZMX<m6#hG zFa21$3g-YO;BLl=7<)~|zU8Z>M9CzXr6kEBB}*w%D&`uk(o`uOeQTyPO`482o*~VY zvY|&dX_j;ibZNG9t#lpQe~vU)x*oFdQ|Sh29^|4xS|BY{j|TCL472HI#!P#SL*lo2 zIO`x4-egLoqe1g)vT>p(--^RJg?u6}|GJ`i=EBlaPC8M~Rd7P^ZH%}}p!>tLWP7RbmX1c6lUXi^b z8~eZWi{>%SYUF%JtuvnsPNK?zl-U(@;FqaG)QUpBWnw0dsNA}O&{>E+;*PVXD3nu! zpmgwS5Gk6xbn0+8XcgTl!RMMZP|*e|erF94K&9VWnlH1>Le$T~4Gh=w3BloGZ)Fs6 zJ?F0|twBYVI88-sZoM^M)RpRggQKbp_d9?sw=z?b<@~d7tcf_qZHveig{6X5*_BnR zZ@rcArUaRVQ!zLor>PXtk|N_$&p+QvRTW~Tz{F{66eOC7qv_Ne2y#?c*vnR`h~{Yy zE6a$2xKts%4I8$6U60^5qh;jmF{NE5CvDZx=bQJYk%4WB(Nl+T_HstzXz$_CB_ z4o)E0lwDa~Wv_Nnt@BsRD-;z=6)BfzW?1&al9&9j z`O1-!!>}o0h02!ZGncty;yj%KRN_|@juc-|u|g|uLZ7U}qCCXAs?>W(WKA6b1nxq- z&50x%ISx9_hJ$eOVLNVXGg~sYQ8UWQ=Fp8ej+$IY?X6_#$P}A{Ml6_sOd+SxreXCZ zxg(o6zfzdX{CR~k4Xk*f0->-&6^v3v8Z2apiAyQPd=Qad(`qW%x|QS?k#^oeQN$3o z=xqG0B)3(A%qmM4L!6a%SOHDfR8&eeWY!Uq5*e#?WoSt-`Ggpm3zzXd4$|^GiAtVX zCa&h7lX*Cj84@R78!}v+xVu3MfpODbYA^@{sp3014!T0Kyi8=MY{+ zpucuJyAi%dIDrtgEmDd>h(ovn;U@@Ygbainggk@=2*n5$2(<`zBixU$8Q}?p9SFM- z_97fa=s=*qBl7bEo-t2FN=5_=LJq=wgkpp$gmnlT5t>`u?X~c zh8ICzT&l})lsTM^?CMfyDLRpP^4aqlI$ZDNs2&_G?a|G`JzBT~pc*$ENfX21`)9kV zt8ohm4xAU^G`qBt;_~b@!Xd{j0)5Y^EOwPSa{2O+8JIm79EWXquTvZ$k$Y;)O@x#0 zR%$Q9SZgpLPlV5|$(flwFVA5wrg4U?7Hg^D_*yw6spJc%s~u#TQThe?Ik;h=l(NmQ zT!>3EvRB|1iVYF-${da=X-CBTvKo-GfKHxEha==O_@Ip32ubQvV)#@(W?jxwP|UAX z@k;w}Ra}-t5jTH+c%H^UDyp8fot6u;hG3Uh>X&G5xAJ8 zXa%+iH1Zqe*+_5f&SNhXz2lA-NqU&xDvHXhq*o$VmI#Lhb|PX`Q4Pb-l~ftPq4CP< za=vJQbnzSsUPN9Y9I&F>%76st{xJiJh)4I z;|>iZroQ06AY5m=VXiGN$;|T^KpP93|388_5-v(jK$-dTX5`PFl9QJQ8Zz+xUlGtE z$y3DLnZSDH?+ge24{-pCEzKZzRO01{KS?wvK9Kl$;@-p$61x-o5|1Y8Ok+&vn~Wxt z$!3~sDmS@IkD8t`{n_L*ePjCGbcK15xyam`G|Do`Vz$h*Vv8AX-`@YSv##?ShJ_*PVJa_Wa{Tr`=%blWa;?y z?DUfK_369QFV65}e4Y`OIVm$Y^QO$rnXhDio*6yOI&IFhdDEh%Tc`hY`i;|zrTjs;&&&|E&f0(~FM;LJkHQp`>FLNYrXXe z>)Y0VHErtZsjXA5NPjThknu!DZl-S9+tYRse-g#|?ynM`G`(#8EGa&DRm$3wZ7I*D zyp{50%7v-drLIripBkH1nbw)M*!p+t!&B#`UzBky!=KqV4U^WqXErJ^$y8;o1xIEj z6=K_9tia8}cWoAlo zN@>c<6nBa@<<}|OQ=U(0Mvl&uPg8nRVp8K$jj8FWw$wSP_S9QbZ%@5D^}f_!r9Ped zTATVorvD>-T*g%y*%`NG+>`Nq#;X~hW=zdokXfDCl=)od%bBldzMc6M zr0~jV>C=j*xu)GUT}sqr{YR2)iT|0n-t;r*#wVs8(?3lAH0jNknM=&KL8{i9z2=SP zr_FoJE#`O4Uz;Z+O-ZsPlIkXvfw9Q>>H_vhTWU|Ve(4i`%*pD7vQrb&rE(T z`Nt_!Qc}=9Yf~;tJv;4!wDs2CLhr7cddt+^Q~x^kob;R1Z%tp8u`=V4jCj;=AanAx z$4FkP1P*I@dAck8q4e$PJJVlF|8sg+#@QLhjENbU8P{jrm{F9mI^(X4pJ)6gz*#u21Lw-bT)MGsi`x+CA5_czFVOj^h z{MP(qa3m#ZTGFhfxk(F>Zb>Rhsz_Q1&fEjtdL-#r(5>g7TdyX)k#w0Q-jZaw$>Olw zX}JgXCNf;(+@yW+S1$8JJLJTyVASUed)dFed+z_1L-HQ+@sHk%81U0$%xH} z%NPfa#b+dBOv*53#%CtLnk8mhGSf0MGP5#sVA=9A^E2mXHc#I_{lN5t(>tb1&3fq| z%Fi|Bnet8ZVa*nsmYRxT(aKC!Ca0;^v<5s|Z`xqG-?Y)R$+Q_3uF15`wB5ABw9~Z9 zwA-`?)^5M)fa##A&D3t{Fm;-`Ox>{7y{0}>ziGg90`^aDjxtA^W6ZJUIP*AMw;FFw zfHh1sTg++Z409H2Vy-#QoDaLW$h_FR)Ld*{VJeG8I@?m`Y9fHi7OJFiM6moXa4>-asW%(SQn)~taamW zU0SW3_kQu0*rO4~Cn5w~j^_OtVNxQRSdM?V*3SD`UxdTgHrA5ZcxQ8hwHU5=|_alfJy-x2%>i>z6JSqkG|Am0efDWWhZ zfs6fu5&B4e56z*AQoluc(Rxl*Tg3fh28kY{y9qrKvw8$4X>dSaM=$%8hKvwt_4P zNp_qNXn>T$_)JUJutJj++QNX9x1%gCrCr9d!rrkKI#{!kQQFcr?4^UYbfb*5@BcaH zo_p@mksT*XKi+Nk7c2Mv&vTxy^PJ~=+^ee;Hl^4%gf1}q@r7fv4`s35uAmid>j(yV z!$CI|Y3YfDTEbROaGezhMuJ^|U{~8^hUuym4O$aWc_yZ6k63NpT^oZvv8bD`cj6l5 z$bFJL5RAsG_LeZTfu2@-caOC$+-6iriYSVrk#HzR#zc)uSt8VL54WsimPTVOSc^}S z6rJ50`;02rb+EI$=Q67^6zyz@wRKp$!YPuVy%*BjIwMv~SHNoRY3Tyd$_8Wl6unpfO7p2O}Y^c(yDiQ^Ua))R`l}NN=>m>SS&*=1Q3OhY>I+HEJXr=h6efnnVRL(D@RLv;?eZM|TgRptq|n7V7RY7O15?!B}rkSJYT2;r8y{9&3Fs z+9@3B48@GoWf43b+tktnU3-GF(r{bEj)3<_uqOzGI=cg* z_REYjB}~o=b+rY8ZN^y=4RytW>w-O2pdB@WtO26N*|NMl60=&?^&00$sAXe!C_sK^ zvEC90^kB&;`*OH@lNCa{s8__WtFZjCK644el zE|B!(wrER7yRk&Y+QPAbu~fxcW5zOxaD&v$(ApU?-s>U>tnWqk=MMs zp>~$3&}h*vW4SD|9o3F}7&I=Fc(5-*eZHkNYSc=U^zR6<#%&M5Wh>O8sH><>EoyJ? z2?irZy+nh3F{`O=Wz!l`44J1Z7Bw1V@jAq44^P>+>rQ?{_YE5>B?W=KUBdt2ZUia< zEYyjpZk&h;Er`A#$X2Ojk(Ra%Xh}OR$WspGYLzl}StJ+^Mxhw-iyCXxV$!9~YQ4;g z^mX?bO|psxeQ;E>EGN0^ zT4Eh=NCORBQKLmIjJCDJ$X#}6t6FMTZRAzk)Iw3!E)A%qcGV_cHK-Pfs&;9+Eae8( zH#D_(wpeW)EeH@}U1@Q{s@1OIj?&^)^$o7#kSyNV5NJoH3G}j@&$D%p)q#W<2^i}o zUCYKkh?pquH%vmPhJIM1bWJKcl|oqT^n)&?gftO-saq+5o$gsmL@A_dEL~C$2?g5_ z7nf=TTwO`gqfik45*AU7pp->SQrLsEzFBB<7rxB zY?K5%!BVad2fK_-vLMX^%jyakm&tMr6rJ7ZA9hZqk zMU8|kqomQ-*0HYDNU9*rt}_NDM#l22Mx&K6BujWVwlf|kFjPD&i|tu1ydDVgzKAIk zruVUy4QTDuu?a~sC*CTFxnEc~8CObJwEv~HbJDUXh>4uFkpieU5()MgBeIAk+-R)L z$ViMF)6>>tWF-_v^k9|Amrb;iRF+N8nXiduQ%et~^)x-QmrV9`gdA&Jx-qAW0H3(gHqhR5&F*sK9o(c7@CToG&G_s-sj?<}3-`y4)} z`)n`p6}6vivbPP)l5GQ%ZyT6pw+&FpnuHegX6d#8IO*|VmTnuE>b8ODY#W$j+rV^h z8$eBW;Q6AVBHewzVb;^I!H1GO;%`M-ZXz7Y&RziS<@A=W|@#R zOGDPwLe?xFvZgy^&2k|-Bd`fiBaGq+mMTw$XZjXwI98>z+l(`k)Ld1=si9%x96I+I zr&f7pcuv^`ic<>4Oi#@!I>XPl%<@dXo``1#HjQTJ2P!9Gis^8lgSuA^v>A>^*o6LhO%y^a3h-67@Kp$xbtb&gG|s z&gZ8MI6Z@%UR7tjW>>qa9;duozwysD*gt~PQyJ;DZN{JZnB3DmBlX4}-z)}?)Zw&- zP7PzekF=)yH!L~*6c@i|$>~1$stGky4bfLrQumlcA7|pjy96KCqcDR%Zv!sAgOfva@c~?1g6F>c`15w);s8$Y zg+Rj_#YLdXFxKMFdj&31I7!k)3K!SlWIJ6HadA6NZlw!KO8ao~Dg0GXQkr)cJ`TbR zdI^*Flel;aMNbm#eq0>E$zlA_aa#GD^$5ty<}51s{mNPMC(2n1c+TQ>{?0zlImYY- zbIiH3yffc3^Zc1Lh#bRQYIv*8#J99Pi;e2)wbfo~8K3E`TDxdk^&(%W=Hf->`ztP5 zRB6^=$TrPT4V_dpqSnQWJnyf*XwjTd%?D4v1YfDGY&6z^+)L!sLp2^e7BFp&d45$* zwfCI&oHKg?M*sO#ts1qeL*R1CEQq9&>8BY>z@T~&6q>Qf2-U2huZ^2hBM+BHny?Dq zMx(yQv*6EL!PLi?GF@01bgKu?$_hy`t%9j*zJJjVrf~*Ur`iTA`lyePCI~!5V78Fy zPOXnmCmJ%SS{ejLR+3ROYT&KvYM6z3yl}_q#9_`^W_?YMd7!nrcR^qEWfhyC-P~%k7t(5~QA8*6Y9W>jrRshr`m}0f zkom%`zegUt@we?k7BH+y&~Q9Z=9cj)+{v6tUZoa57Aoi0`Q+CHj$eOgJ@`K-IELyK zh7aFirum5Z?=w%O|6rMcKb)L{c?w1s{CRLb%QHkJ?8B(=(8B`W+fDP_b1&OaH}rwa zE~{Iy_S|zlCMpI%M|>|K`f_}|FQUW`A$9m;ynRs;Ne>Bl5_FcMj|@JD&&rATc)kmY z_d-j?!+SB!s{-`VMIRL=)TE_=_v6#eO0&XSIa*;>&UvEJ``k6_E4=e+=%CNjDk@Pt ztul-;)vWMUc2%yg+)&~5K4F?Q08H=qz-(Hj_iwMQn#)MMmEONMu9s!LO7F|p{_F#w zF)yj8s`O5;t*EXv2P-Nn!yx(xu=MWwNG%mZ(uRsta8nf6@C_AnD!qSd)>inu-@3LI z`q72t3P$fj;%T75>SQ?>kJZ%RVs>rC+3PE+uOD8%VFSPofXs^O*S+SZJZj@K7bW)qAViciOxfNS{?P=W=S6Id$|2=T|>m zhZflcq9Jm5#hewaoe01giK|`_qKkQzgFvdmp{lbMQAfnAb=IRw8)^PGYfMc0||z@r@o-ctB$ z^x2(Nco`MF``>Row$|0($Y%mcNxEBHVDB+d$a zV`nfmv?8^#Y4wVM?L*sAncAq)bMa=J(05xbJft&(2X`#~y^56$oALP0h5T8d&FrZj zYXrsHHnZR3!%vT1$Sy(0`5Uxl*b?k3!At|*Py6?VLq2r){osq=uNyW1F259&p=!^Tj5`a=2vv9+3?9s8Ko|l;(mkeEQRQ2!@uiJdlKbWVZl%HYj$A%W?*i|{_vl#W=Uo5 zCwv<};q%oui?inDsy)Uhe0zLA*WYXk_-6ACa~g;lez*CeQ}Cks3t8|B^N&1EbrLBg z(m5R@9fxnhd6m&m_#%*T-jZoJ+cSeJVS}VZm5r{yLXcjOa!6BS0vx-#@>0&}>g&vD zIAyG^BXtGeyUmA8LH>~Wv@Cd{G$ndd#3?E~6nC7LRe9*+jgXZ(5XU}H5g^KxrS&eF#tig6O0<%4PW>DpbZ~Ybc zxFSW@sjG2%HALc^F^YiE7jfB#msv5nn1Rdr9%;}^OnQCm`|**22EC~G68-lQ8m4m8 z*k&x==lg|uooCt}qmo@*=eg1IUR-qVnNc|xzcW!&WAi!iN%l2pPSS7_$se5 zs(yXO`TKl7hZ9c4b(MdcaVCez6?F`nk~8dDY-B_{B=w7nVw=rdILc3n)!GEO71yj<5D}pMd!xXEaXL7IgkScVJ zmpVt_K{zsEyo7Az5fTehqcX{ZP3XYE>XmdS{-M?8;s>~q%ZxrM%(Wt4P~ zUwrjz#aa7SaW?h^Q=ER;^Mc1dXBv@K_8(^jl_(h^2eV|jY#PpZGF&UbwQC))JuSd= z#sSA}F$H>yd9Uf<_fp!k89!uxT^4-Zd<0oqfvLaTDn^dQfUv<^Dgyo})n~ z+Gp(nx z^X7dN@%O>&NS;1ojp#-W6;9*4yQE7kZ{ z^KR1UZqGv=Tz%Q|1pW7f=WEmeUxS*+zFa@A?`889V)}~t4mH+02%i~XD0uH<)^N6D zD5}=)gMAq4wq|JX{5Uly22Tu`_p;Hf17*0P;OOnMlSE&Jg~KD1Bv`v|64Ku>Zzt7m z_uPqKV9a>N^FkApL{NfS%&C(&^uKA*d9YjLJdC@(ret{qI_YfZR8guh??8t>6I2lB;4I*nDda@=BH z(fvWnuKcaplmR62Ck&_5<;T-@_C?8mkK>xYM@BR}Y!2b-44g`YesgG@jd%?UuUMfM z*ZdMTpyjl4+3M_!q1K@{#8O31=_;)GkKT$nI2Zs5Au&=mr1K^(q105dD*Dk&n>dhbw+Jbg?Bkm z)E2_ee+WA6XS{82G~QS{kjuH);PGADlfB7j$5{^Eg@hpo48T+4}0}D8O zmI9N6g3lukjp!GD1>{YqEr`)eK;h7!AbWu%6yy;gw<*Y90eMtGUIOwp1$hg|Q3aWShFXS6oRoe6khFrd0J%d! z27y>u^~zc|0r@`)vJc2S+$fhc2Y{TdAkPvX1^GUZ{R%=4`5je|SrqVi3Q%%B2gpSV zLQnc#uORI}zN;YgWZzo~vJ*(|R5xdO4f!n!@?{|3QIHpb)YHS#hKQe|K-v`qk3Sfr z3UU^Zy$aF*+2vKfep2ZN<7df@0`1^FtFe^-#709lU|AZwk4TkCf#NGFh^ z3UUpQHFMmx?gFwKy<75m1jy|Q@=YM?F{DYFe+Kf5g3Lk>2w(vtX=;IdLqRqGxdxAT zNSX|g2l0LbiQER{eB7Rv$QOZpO+lUmatacNr1?3JKT(kR=o24W<)&E$Y7C6y(c5I@b^l9*V&q9!&-EqmUc`sZer=5v>_G!Ww)M zlrWV;j8!IRl7mL8K1464=`1Pbm!E?be&~TN4Ovb2lu8M?Pl2ZI7&J7( z(gTH@Pa{lh<^t?b;h(+CW2BSj4QDhz2#cBRMs145<;81WM<^kzsTN08EroIl(uhV@ zk=5UaGB%Ap7rh@m__#b6zcQ&PXbflKBLqo4gT>*|%$0`Wl4Z{i_X{f^v-HsHDo10_ zTO3Qv(Qr!}mO=ot!lZc^wWU04Z|JtW{bSqyB~+WpcF{V&MY|d)>bPALGwLB)dAVri za&tdBHr^DmVPb$TImCSI5O8igZmEsLaqD?Nq5K{qoZF86UZyRqTx}gyaC$DQ{T6Ed zos$6wng$h|HpRO#@s`X~##nwvX_kRy;0K|Qmi5GFRK3|kh6!8;bmJ%OXUz`cMFeS0GA9+(fgk6V3 z1}MXaYLH!TPhywQ`DdWxIv3N0!e~xvjG38bjVG+QvCGdb#(Iz;JB-~9g6hURv?;f% zd~6WS63{&7v@#)$Kz?i^$Xa&ZkpB4=X!!U@)N}~{P%o#Cm)E%Z_bChe@Gtbd~^yrU*PDRD8yl#5 zXa}`L6V_J8>Zgyv65dabqDD#XwdY6|JMD!8SsczMWK%J@N5SJ&#~TFw8<2Muq?(5F z>%{3pxG~IsFHCRkhSNz+p7TNFKxu*#60H=`^_6LY%FGV#mDiq zg=3fL7uA*j$18^;>orOrgDAEfhAFq$alh0X~_ z=UlRY_q@B@%$QPJaX~ zvIs~mzAGkcH2~SDAQ2#sIf(A(@4y9^yt)n%8&mN!o<2TiY>ZFhXA(_gb)T&d&D*_ZPLoBzpV>4RT0rB+r=A6#vS~0lf@UW^_2chs znwco#dpjy0(H8eQZ6TjX6x>9Qd)V=hD3Sl4n1A$@3vr}&r`G1}J_AJM+nrjQ%=4O` z#;1Oe5B-3-4=MW9IHVdB8x#>dHac)d`w{9v?M1Cb+Ir3DIuqcOjTfGdaz@jMPxTOf ze#r4NZiC{rmig^!2KWwSaX**O#(G89fyGS%;zx$`q-~YE`JD!xD+fhpL2#Ic=yA5ECU{uk~3y%O7V{I^2b z%7qB&=cSvL@yuUtwnH?tAm{Ufh5+L{Ah*8PuEmg*Kz^kl9Y6xh-87d2d0av0j?jt= z-83HolCN_kcK}JQbR+ixxzj-~Hl=8XjQSb13H1cr2?Wg@jqX}c0r?9B`4*5j6y#rk zY+UWGH3JFk2?bdK5}GVAgey; zM)m_~4Y-lVfP66MMxF-pa63oj%$&C=#p;b0;65btiTKd_Dfv!4#0++_SQV?JrMQ#D z^Utk_whyU{a78()3cp4?QI4Vt)Rz~+5)S8srZWREzjhd z!tgxz*aN6RXYvpp^PyAlx!+I%KV7J%eUR6{(g5Coz~Zos$E^Mowp_FQ5kW(Hm5fz; zO!!dvhw~x-ti%pQL@I-`ljR=~bqD5(h#CiXeWWHIJ}?CzzUBCkW-=vhYR{kNNgtMt zx^v;fal9*HA|HaWQ4gB^HVq#;J_zJ%TT6Y&`H&AE*IMIb`H=J>|GYf~|K#9j9%JO^ z^8R_hqh=B zkOv*a%B^C1=KKG^y>Ihxp!v*IqL!7bTSa4BBP0-4-U`!Z;~srM(2(@gfLvX4BYq%f zkGhfNK;9X1BWr*}#@$FOkRNOpi0IwCbtp2AAYU#afzF{vCRo4=?n8_zP9&JS)#WOP zMelB|;Z|bppfa%;QuKD);z(uaDAdIACvqrY~MWv+c^!{eh#Uq94FiMqf@Y*o~1dK?Sqc(sX`{M-j~G~$H4n1v7J+3 z4I*fqT@j9uH&!d2po^33jV9dEI#w@uathuURlVU30%k)Rk#Iy)zX&-sw5?tO`V5peuN($(X5 z{PWBd{PTk2pNUrif8qEio1usItzsO%o+6#JAC}&K&BQr@EO-JmM+6Ns=|v!OKO#IP z{KNT>e|~_#P^U?{e`qhRWM+OGbBTp#^V*ZmqawIV#q-9*tAG)x!1KmPKjLRw>FWG- zSbCFfY1#O(t94u_yg?3n2eq7Dz&th+)3`rXXif)mNc0o~KNHM;ME9H5yZKxQnr9pu z;b*Q7`S~7b4^gf|_&EZM_fuD)eX7TyzAs9oG-n&0f}fvp{JbAE$m8-5IxoI~?4mZ) zG<5DfJ}WJ?pHvHXOj@6x0n(xe~Ir2kBl z-grXNcWct0(WF1ANq<+9e#r?*ze$t+tS0@SCcO%cuX_H(q~EGZe@>HrNRvKWlO8xB z+wat*zo1EfMw5<*ol9&FpOAEVs$PlbRfjd{&uY@~D1@kG$9(LBq~D`Se?^o2f+n5b zS*o`C<`a^>Uz7f-CjGD`9b42T(i0~n{eDgQQBC?0O**!^OQfeyNcsbs^w%}%uV~W4 zn)KocNq<)$l`oo&^hfhfQbDDJ8u~%d@;w=p-vig7~{jn30{(>fb zp(cHXCjBW*`jaOl{jeteOilW1P5QH%^g|~k{fH*ruSuV$Nk1&5+jm&5#$3zZgPri( zF^@WZM|?ZxNy*vnJI@L$P*Qqmv*mI$_PWWLKbNDiza2AA=rhT;W2DaFdnGQNZQF02 z#C9>~|1H|pxKMc2L^G8f!ivSBl_#cI^NNTKlb)zi53&FJ8OQeYfQ9FikwxJZ67SVHZM*EZ|Q}k>2V-tH=XWSVaym6svVwQU`KpAX$Hh>BfC0_K{&`Ncm&$_4q7SmY3O5(H ze>xr9eL^FQ|8!w{6N*%3Lg!bQUBpXh*aupsp|gT^^bz0tTQe@=Ss<%ieVONkMyPE7hVoN`ILbe~&q|Gb;zimX*tn)G)y>3gMgJLc(kaE>*~Bpg0sl*vgy+dh0-SfPd&YRdaNPDGWXu}2vv z#>&yyqYUw6wofw3NS#G~cIj-}PCGdh*)ICUZ_%#ChaJ1nOeKe~;(+jcxtwY5YYmIo zF!AtK$stA=+DPNJ>ra_dl{YE-ebnA9EY}uxyK@@KWA0yt=R&niQjX6Q+MT%3lc@A7 z^!!QGd!hXWT!`zFo7>Br1q8facFDdm@x=liCR=lh&IzZ*4F^F_O# zd5m^{4-E3(coO@vfm{CLy%H00xcY*(W%d%_s2OC6L@r_8d}W$|{Krx$9#@L!KAB zkz!YRXpvju8 z$qHz)nlxFlDad+MlQmD1)uG9{M3a@6f~*6Ytc9AauqLZnla-r-tb>}YGc{TCmMehN zQ|uK-K$Ep=3bLNjWcf8&F-=y7ChN{A$a+qbwOEryql0Q!Sd+DX3bGDsvX*MHHfypX znyg2sAnO%P)^bhO7EM-6ll9~jWF6IH)oHTm2}jk3G&@j`)z3{q)|;BFMoreRCTp`M z>(wd9dRvp#q{&KavbJcl-kO3egN^U5zO#mY4p8yWs+=Y(p~><&qu#{hSCuBKS(8=N zWDRSw=1oBs(g8b;Z#N_1a2Bhr$4aF)(zgl}|E&I+wwqxKKhC|BEt>R4HR<p|Q2np-JDXNxxN--f@zo2Q=w-Y0_`fq|-OCPDac( zYtrx3r0>?G)330d4C$9>(r?wI@6x1Ko+Rl_n)I7A={q&)Ka-=we=6HFYSMRW(#JLF zFP|job(-{Dn)IS3{TnAq`f^SBPEC4Flm5g>lD<@vKCVemYtkP+NzxZ<(uR;-F~5*S?~6-WaUL~wEJEl;+uEE8#|1hez%5S0L_yM=dS?S1$}tb z;l%hh5IVSq__gy4ZtY(gP+I553Jp=yTNR&GIO7+u4VsTh&VC@G=W=f}0HH&AqaDaX z6w2uB1;TeBgrx&^Eo!rrNvc3_$*}`8Xk?f4j{%`QbE(f~32|iMT?C`W0mI-=YzkSA z+p=V>LqJ|tEPWmbUki&`{{%#hRRhn4t5LTQNLbNlIS?_%asR9XvI{*5lNq}YZUS;h zk)8y?cU*<^s|fMCYh4FK%$hjoyMfRlJ^zq~d>hCvznjl%KsKY6^oDOCadGoG6Ub5^ zvIQG~h_?%J>Fq%Hj+xN82Z-Nk3*xv1$TPe6_iA~qtAVKc+(LXD|5!QpmPF^55vUj7 zjVYEz>|B<93biC+4Q|Wh)rv-1iSUO(b0^#15E_0Jh}gm7`g{`z-zyL_|HwEOo>K1O^Ln!ADQbvRoi$&rEFc4N2Vu?IjS<^Y_}V?bh%PFEcH1`ydV zh!0*&W!$Rpc^x$TIR|0szX5qxiG(@0|3ilyo6Z6v<{Mnr3Ly6@oG$|Mg8iflr@0i! z0mm-lXaVWN|6FH2ro>&jmm>*d-0${b8ns+@S@fo^&KL#=m+d=1b6AmnBakKtl0D^K zAPI%@BS7A?=VM&wuLC)xc;j0@#GW~)`2q29IODrr1$+zAIHY*vm!MH2p&H-KrFER_ z-OGVgIebuLTnvQlk~(h!BJSgJ=>;It8&vChAp8y9qD?;ygzsYunY3u>=mV)d9vIVlFSN|MHLecO$Kzu-?K5qaK zd%2YBXq-kbQ2b-!`*UQ44S`Pk3w@BXy}k0`y7yW9nMttXF#4&kV8OYPml98f7W+*gqAg?$&Tk-yE9twvQ+q0nA z@6eDYw*jFU5$Vfy{ydPyO06#ep+nOA4G?i(nAiF~kQoZizXPF2k=dA@^(#7@`FdxE zk@ma&GY>R$aBr~hWe<$x9&>6@*)q`V#Q(A_I)KcEA~K%)fKUdIGy@7};WH2am-c^(MS$u@n><|C1JfIOhoI>is26)VmLLWi{L zB0uzA%AZ)^o^J;t?s0JBLqOC_I|3waxV>>b5IUqjpYb!mg**=Ae#IMq4dhHmLuwrw zK^|6G=S9$TfEDS&_4!vIIb6xMs9uEeOR;?c5IUqj7XXnNiOPZ+&4+>9W4irQ0J7Q9 zkZSD)5>}ACKzs`FAP{~p$#3g?0Ep_t?*KWBcewCbw-rq%ley+bk|1^dIcUV2$+_*n zBF+lU(sN6ZW+2b)a%afPf$Z`NSwz1T$dihni$IE|+xCwGY4o^}`+?kw+S2oX1>|+7 z*7LZkO%7vQfqt!`Hj(Tf9kSy2cxEVB5SOXUU{;*R`}_0BF>#enWt>V12gHS&hT_Dz z&JLtgg`&efpBduUNC&xm78B88F+MPyOo-o8<6p@(P?)p^EPCHL#x$Bqg}Oqq=K2Q1 z3Z&DW_+bQKQTiQ$(Hui99XG@a%UkT1(k$MwV~5Lrs1WS1+Iw0$gH~&BU8oDAo0UlB zlS9xrnde@z1~as&fM0&V!0s*(XlVuh0L)_R@}@#2mrrGigOP!)kqrYoP(jDo(=s5w zNW9X}rjA0DiO~8r#loRKn8E_Z2r!t8M zoSw{Qwr-6K^TLLeoTFV3!|&L|vTfN!GBOZ}TG2YDOnn0pkw9L;Rg@^zvf0%UWgv+L zF5lENrA5x zz(0f5x+I}3`62EI*u}8+bT&@tpl}aEqeX<@5MVn3;7o?chEoNgtT`ZV&WU;hRlq2AnX&d8tp$fAMp$AAf5=E&9cQdQNOMyldJ`(0$E}`X4E&+L~ zGc{4YEt@H1(@Badmuv{ZzZCs(#zI9GIky=kk#>A+O_657sn$pxC;i}Y(vKY{eZNM} z98@;w2azxIpkISuK%|}FY~C7* z&%8+0F-9`2>eF~mzdy|e@DgfH93G7`zfqS&e6vbSO0huZlCuy!tQp8a`U(W91T$mN z_#j$d#pHFY01|nLD$ye57B2FQ$YI&M0)L}an91v>I92OetyDS9M}d~XLS_lh&KCUxz%XItzpPs#OgoOc%& z4RfEk-bR&5iM9;@RmdRU>K`2}q-e(P2n6cB9B0?m51D_4taKtf%6f;aGBBo8NtRp> zmRu)Cz$_n6q{girIl(QCm7$H2`P6`g{65N}Mi|CQ(JmH-S-eJ3t2jv0OASqGumA68W~L&DOuTeryBajDV2!xTz1`mO3S*`H{!MnKyo!&C>=(^h18@v( zOt6>LQ7@7sljVLz8%mtjw~f=;XYuUV_cJ-=ND_5a91_CyabY>Hy zX{_7%FBYJ!n(J2^*2WYzZ7{{;n*$7@)!R@g5lFEX#o$3Y^z?ScLfD##M1#G7ZmTC4 z?ryWfp=gYB;xf{hm|59eGJ`P(jYw=-)6T*Z@rYHcfU(HJHcqmL^w)4jM-WlWYz~^D{?Pq_=o6g$an=0Q{zp&8&RHFgc+@ptUU; zPR0|o_Yxd$ZW0>A(glqCn1PHYnem+5nI=+}OCkF0=>?GllIdhIS*E~(J97m#U$a#f zLijxjemkB9()a-SBxXOUfdXq&44LS}gJcyY&g=*c4b<3b0sW9s9>&WsU>$QHOEZ7; zh^-jL3Zwmn?byW|vBvN-e>DFjYKwX&o)40aWOhpj_#KVkUhAJ3|?$DDgj^;D1cAUWztE_tVyhMuTZ;UnX z#vN?^-6Ep{<3I)jhr!HA6>y2&CUSB~zF-qRYZ%D|Q|*3iq;eI8XvH0+RBW$T&@tHv zK*@u39LW-2x-RuEe@=%HA~hGY&||WcVW%=Dl7^GMD{Em> z>Habj%3!uu^`|W*C!NGoVU)E=jM5z>+lk1D=~z|-dUFFMJotn)(aHvDs9YhQqvsQ$ z0w3Vwxt!fgsC9;f(^;t6&lWheG>oTcEh3wDV$vZ*L@r32_A0(jDW()4#%G`*V zXTnK>PG=RuNfD>x(vaMbRTK4eDxe{lo`PShN$3lcWiH>*yfI-ea1HW z{chX_U=y@fnz$i6X(ewD609pQB@s3QSK=ohc?Y1K<5moBm{QPIAyZl#TUa8(%2y0N zj87)H4FKEN3bn)>N7>21l?}v@Kj{$Z%vfX-ie|xjU$lxdt~deUqyTn`*{@wmH(MA?8thG1V@ENS>9<{4vUCkRY9dz{VI-+s8m z)tKt=$o=7(*bU$zn_@Cd<33yU!x4PkS5pYrgFMSOY=pn;M|5yW{mcmowh4w96=WsG z^RdrqYIg`YjqL=VGbr%}p!5N@%9IkCC-dbdP@-|bL#X0va_$T^e%lM5LCgveDWDY- zB{sb2^CTLWTuFxaiK5E^mV4(%&Y_%{7J36c0l@OB(%-0?P$+Sa(?%EDtz(u-THC|E^5Z`9ynD%Ghkc5+4Bdk# zORRJcI~^ZllWXQGF*8&A=K|FkfXrp2&%p4Nh2oW1)4D2K#jG>zDbs$|iQIuodro|0 z<3qiSE%*GKm+8hZ$A~8|>=+xvzM^}GmD5gbgTSo_-zvbs?9K{hS3eUja3;@dr85MX zgV=2skkkm>BKUyHzk8A%wOLM56 diff --git a/compile.bat b/compile.bat index d812f9a..921f37f 100644 --- a/compile.bat +++ b/compile.bat @@ -1,16 +1,19 @@ @echo off cd /d %~dp0 +rem ensure build directory exists +if not exist build mkdir build + set buildargs=-Wall -Werror -m32 -shared -Isrc -Iinc/tsfuncs -Iinc/lua -lpsapi -L. -llua5.1 -static-libgcc -static-libstdc++ echo on -g++ src/bllua4.cpp %buildargs% -o BlockLua.dll +g++ src/bllua4.cpp %buildargs% -o build\BlockLua.dll @rem g++ -DBLLUA_UNSAFE src/bllua4.cpp %buildargs% -o BlockLua-Unsafe.dll @echo off -rem objdump -d BlockLua.dll > BlockLua.dll.dump.txt -rem objdump -d BlockLua-Unsafe.dll > BlockLua-Unsafe.dll.dump.txt +rem objdump -d build\BlockLua.dll > build\BlockLua.dll.dump.txt +rem objdump -d build\BlockLua-Unsafe.dll > build\BlockLua-Unsafe.dll.dump.txt pause -- 2.49.1 From ce3f7c8b3fa2e61f6e9af479aac37887bbe8c11b Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Sun, 5 Oct 2025 15:37:49 -0400 Subject: [PATCH 02/22] msys_compile.bat --- compiling.md | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ msys_compile.bat | 13 ++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 compiling.md create mode 100644 msys_compile.bat diff --git a/compiling.md b/compiling.md new file mode 100644 index 0000000..3d3fa93 --- /dev/null +++ b/compiling.md @@ -0,0 +1,52 @@ +## Compiling on Windows with MSYS2 (32-bit) + +Follow these steps to build `BlockLua.dll` for 32-bit Windows using MSYS2's MinGW-w64 i686 toolchain. + +### 1) Install MSYS2 + +- Download and install MSYS2 from `https://www.msys2.org/`. +- After installing, open the "MSYS2 MSYS" terminal once and update the package database if prompted. + +### 2) Install required packages (i686 / 32-bit) + +Run this in the "MSYS2 MSYS" or "MSYS2 MinGW 32-bit" terminal: + +```bash +pacman -Sy --needed mingw-w64-i686-toolchain mingw-w64-i686-binutils mingw-w64-i686-lua51 +``` + +What these packages are for: + +- mingw-w64-i686-toolchain: 32-bit C/C++ compiler suite (g++, libstdc++, runtime libs) to build Windows binaries. +- mingw-w64-i686-binutils: Linker and binary utilities (ld, as, objdump) used by the compiler and for optional inspection. +- mingw-w64-i686-lua51: Lua 5.1 development files for 32-bit (import library). We use its import library to link; at runtime you can use the provided `lua5.1.dll`. + +### 3) Build (PowerShell, recommended) + +- Open PowerShell in the repo root. +- Run the script: + +```powershell +msys_compile.bat +``` + +What the script does: + +- Temporarily prepends `C:\\msys64\\mingw32\\bin` to PATH so the 32-bit toolchain is used. +- Compiles the project with `-m32` and links against `lua5.1`. +- Produces `build\BlockLua.dll` and `build\BlockLua-Unsafe.dll`. + +### 4) Optional: Verify 32-bit output + +If you installed binutils, you can check the architecture: + +```powershell +objdump -f build\BlockLua.dll | Select-String i386 +``` + +You should see `architecture: i386` in the output. + +### Notes + +- Ensure you installed the i686 (32-bit) variants of the packages; x86_64 packages won’t work for a 32-bit build. +- If the linker cannot find `-llua5.1`, confirm `mingw-w64-i686-lua51` is installed and you are using the `mingw32` toolchain (not `x86_64`). diff --git a/msys_compile.bat b/msys_compile.bat new file mode 100644 index 0000000..2d3243b --- /dev/null +++ b/msys_compile.bat @@ -0,0 +1,13 @@ +@echo off +cd /d %~dp0 + +set "PATH=C:\msys64\mingw32\bin;%PATH%" + +if not exist build mkdir build + +set buildargs=-Wall -Werror -m32 -shared -Isrc -Iinc/tsfuncs -Iinc/lua -lpsapi -L. -llua5.1 -static-libgcc -static-libstdc++ + +echo on +g++ src/bllua4.cpp %buildargs% -o build\BlockLua.dll +g++ -DBLLUA_UNSAFE src/bllua4.cpp %buildargs% -o build\BlockLua-Unsafe.dll +@echo off -- 2.49.1 From 30fa81f5130874cdffb582ab6bbe5e29110c4510 Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Sun, 5 Oct 2025 15:52:10 -0400 Subject: [PATCH 03/22] Update libts-lua.lua --- src/util/libts-lua.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/util/libts-lua.lua b/src/util/libts-lua.lua index 96ef5fc..ad740fd 100644 --- a/src/util/libts-lua.lua +++ b/src/util/libts-lua.lua @@ -7,7 +7,9 @@ ts = _bllua_ts -- Provide limited OS functions os = os or {} +---@diagnostic disable-next-line: duplicate-set-field function os.time() return math.floor(tonumber(_bllua_ts.call('getSimTime'))/1000) end +---@diagnostic disable-next-line: duplicate-set-field function os.clock() return tonumber(_bllua_ts.call('getSimTime'))/1000 end -- Virtual file class, emulating a file object as returned by io.open @@ -106,6 +108,7 @@ local function io_open_absolute(fn, mode) end io = io or {} +---@diagnostic disable-next-line: duplicate-set-field function io.open(fn, mode, errn) errn = errn or 1 @@ -126,13 +129,17 @@ function io.open(fn, mode, errn) return fi, err, fn end end +---@diagnostic disable-next-line: duplicate-set-field function io.lines(fn) local fi, err, fn2 = io.open(fn, nil, 2) if not fi then error('Error opening file \''..fn2..'\': '..err, 2) end return fi:lines() end +---@diagnostic disable-next-line: duplicate-set-field function io.type(f) +---@diagnostic disable-next-line: undefined-field if type(f)=='table' and f._is_file then +---@diagnostic disable-next-line: undefined-field return f._is_open and 'file' or 'closed file' else return _bllua_io_type(f) -- 2.49.1 From 01f216f31eb7a7e7ad1ad8d5ef7ae04fa0a05504 Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Sun, 5 Oct 2025 19:50:29 -0400 Subject: [PATCH 04/22] format --- .gitignore | 2 - lualib/ltn12.lua | 369 +++++---- lualib/mime.lua | 51 +- lualib/socket.lua | 156 ++-- lualib/socket/ftp.lua | 353 ++++----- lualib/socket/http.lua | 467 ++++++------ lualib/socket/smtp.lua | 263 ++++--- lualib/socket/tp.lua | 118 +-- lualib/socket/url.lua | 328 ++++---- src/lua-env-safe.lua | 160 ++-- src/lua-env.lua | 44 +- src/util/libbl-types.lua | 246 +++--- src/util/libbl.lua | 1564 ++++++++++++++++++++------------------ src/util/libts-lua.lua | 331 ++++---- src/util/std.lua | 555 +++++++------- src/util/vector.lua | 413 +++++----- 16 files changed, 2824 insertions(+), 2596 deletions(-) diff --git a/.gitignore b/.gitignore index 5cc3a5d..567609b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1 @@ -.* -!.gitignore build/ diff --git a/lualib/ltn12.lua b/lualib/ltn12.lua index 7822e53..f686ba1 100644 --- a/lualib/ltn12.lua +++ b/lualib/ltn12.lua @@ -27,43 +27,50 @@ _VERSION = "LTN12 1.0.1" ----------------------------------------------------------------------------- -- returns a high level filter that cycles a low-level filter function filter.cycle(low, ctx, extra) - base.assert(low) - return function(chunk) - local ret - ret, ctx = low(ctx, chunk, extra) - return ret - end + base.assert(low) + return function(chunk) + local ret + ret, ctx = low(ctx, chunk, extra) + return ret + end end -- chains a bunch of filters together -- (thanks to Wim Couwenberg) function filter.chain(...) - local n = #arg - local top, index = 1, 1 - local retry = "" - return function(chunk) - retry = chunk and retry - while true do - if index == top then - chunk = arg[index](chunk) - if chunk == "" or top == n then return chunk - elseif chunk then index = index + 1 - else - top = top+1 - index = top - end - else - chunk = arg[index](chunk or "") - if chunk == "" then - index = index - 1 - chunk = retry - elseif chunk then - if index == n then return chunk - else index = index + 1 end - else base.error("filter returned inappropriate nil") end - end + local n = #arg + local top, index = 1, 1 + local retry = "" + return function(chunk) + retry = chunk and retry + while true do + if index == top then + chunk = arg[index](chunk) + if chunk == "" or top == n then + return chunk + elseif chunk then + index = index + 1 + else + top = top + 1 + index = top end + else + chunk = arg[index](chunk or "") + if chunk == "" then + index = index - 1 + chunk = retry + elseif chunk then + if index == n then + return chunk + else + index = index + 1 + end + else + base.error("filter returned inappropriate nil") + end + end end + end end ----------------------------------------------------------------------------- @@ -71,130 +78,143 @@ end ----------------------------------------------------------------------------- -- create an empty source local function empty() - return nil + return nil end function source.empty() - return empty + return empty end -- returns a source that just outputs an error function source.error(err) - return function() - return nil, err - end + return function() + return nil, err + end end -- creates a file source function source.file(handle, io_err) - if handle then - return function() - local chunk = handle:read(BLOCKSIZE) - if not chunk then handle:close() end - return chunk - end - else return source.error(io_err or "unable to open file") end + if handle then + return function() + local chunk = handle:read(BLOCKSIZE) + if not chunk then handle:close() end + return chunk + end + else + return source.error(io_err or "unable to open file") + end end -- turns a fancy source into a simple source function source.simplify(src) - base.assert(src) - return function() - local chunk, err_or_new = src() - src = err_or_new or src - if not chunk then return nil, err_or_new - else return chunk end + base.assert(src) + return function() + local chunk, err_or_new = src() + src = err_or_new or src + if not chunk then + return nil, err_or_new + else + return chunk end + end end -- creates string source function source.string(s) - if s then - local i = 1 - return function() - local chunk = string.sub(s, i, i+BLOCKSIZE-1) - i = i + BLOCKSIZE - if chunk ~= "" then return chunk - else return nil end - end - else return source.empty() end + if s then + local i = 1 + return function() + local chunk = string.sub(s, i, i + BLOCKSIZE - 1) + i = i + BLOCKSIZE + if chunk ~= "" then + return chunk + else + return nil + end + end + else + return source.empty() + end end -- creates rewindable source function source.rewind(src) - base.assert(src) - local t = {} - return function(chunk) - if not chunk then - chunk = table.remove(t) - if not chunk then return src() - else return chunk end - else - table.insert(t, chunk) - end + base.assert(src) + local t = {} + return function(chunk) + if not chunk then + chunk = table.remove(t) + if not chunk then + return src() + else + return chunk + end + else + table.insert(t, chunk) end + end end function source.chain(src, f) - base.assert(src and f) - local last_in, last_out = "", "" - local state = "feeding" - local err - return function() - if not last_out then - base.error('source is empty!', 2) - end - while true do - if state == "feeding" then - last_in, err = src() - if err then return nil, err end - last_out = f(last_in) - if not last_out then - if last_in then - base.error('filter returned inappropriate nil') - else - return nil - end - elseif last_out ~= "" then - state = "eating" - if last_in then last_in = "" end - return last_out - end - else - last_out = f(last_in) - if last_out == "" then - if last_in == "" then - state = "feeding" - else - base.error('filter returned ""') - end - elseif not last_out then - if last_in then - base.error('filter returned inappropriate nil') - else - return nil - end - else - return last_out - end - end - end + base.assert(src and f) + local last_in, last_out = "", "" + local state = "feeding" + local err + return function() + if not last_out then + base.error('source is empty!', 2) end + while true do + if state == "feeding" then + last_in, err = src() + if err then return nil, err end + last_out = f(last_in) + if not last_out then + if last_in then + base.error('filter returned inappropriate nil') + else + return nil + end + elseif last_out ~= "" then + state = "eating" + if last_in then last_in = "" end + return last_out + end + else + last_out = f(last_in) + if last_out == "" then + if last_in == "" then + state = "feeding" + else + base.error('filter returned ""') + end + elseif not last_out then + if last_in then + base.error('filter returned inappropriate nil') + else + return nil + end + else + return last_out + end + end + end + end end -- creates a source that produces contents of several sources, one after the -- other, as if they were concatenated -- (thanks to Wim Couwenberg) function source.cat(...) - local src = table.remove(arg, 1) - return function() - while src do - local chunk, err = src() - if chunk then return chunk end - if err then return nil, err end - src = table.remove(arg, 1) - end + local src = table.remove(arg, 1) + return function() + while src do + local chunk, err = src() + if chunk then return chunk end + if err then return nil, err end + src = table.remove(arg, 1) end + end end ----------------------------------------------------------------------------- @@ -202,68 +222,74 @@ end ----------------------------------------------------------------------------- -- creates a sink that stores into a table function sink.table(t) - t = t or {} - local f = function(chunk, err) - if chunk then table.insert(t, chunk) end - return 1 - end - return f, t + t = t or {} + local f = function(chunk, err) + if chunk then table.insert(t, chunk) end + return 1 + end + return f, t end -- turns a fancy sink into a simple sink function sink.simplify(snk) - base.assert(snk) - return function(chunk, err) - local ret, err_or_new = snk(chunk, err) - if not ret then return nil, err_or_new end - snk = err_or_new or snk - return 1 - end + base.assert(snk) + return function(chunk, err) + local ret, err_or_new = snk(chunk, err) + if not ret then return nil, err_or_new end + snk = err_or_new or snk + return 1 + end end -- creates a file sink function sink.file(handle, io_err) - if handle then - return function(chunk, err) - if not chunk then - handle:close() - return 1 - else return handle:write(chunk) end - end - else return sink.error(io_err or "unable to open file") end + if handle then + return function(chunk, err) + if not chunk then + handle:close() + return 1 + else + return handle:write(chunk) + end + end + else + return sink.error(io_err or "unable to open file") + end end -- creates a sink that discards data local function null() - return 1 + return 1 end function sink.null() - return null + return null end -- creates a sink that just returns an error function sink.error(err) - return function() - return nil, err - end + return function() + return nil, err + end end -- chains a sink with a filter function sink.chain(f, snk) - base.assert(f and snk) - return function(chunk, err) - if chunk ~= "" then - local filtered = f(chunk) - local done = chunk and "" - while true do - local ret, snkerr = snk(filtered, err) - if not ret then return nil, snkerr end - if filtered == done then return 1 end - filtered = f(done) - end - else return 1 end + base.assert(f and snk) + return function(chunk, err) + if chunk ~= "" then + local filtered = f(chunk) + local done = chunk and "" + while true do + local ret, snkerr = snk(filtered, err) + if not ret then return nil, snkerr end + if filtered == done then return 1 end + filtered = f(done) + end + else + return 1 end + end end ----------------------------------------------------------------------------- @@ -271,22 +297,27 @@ end ----------------------------------------------------------------------------- -- pumps one chunk from the source to the sink function pump.step(src, snk) - local chunk, src_err = src() - local ret, snk_err = snk(chunk, src_err) - if chunk and ret then return 1 - else return nil, src_err or snk_err end + local chunk, src_err = src() + local ret, snk_err = snk(chunk, src_err) + if chunk and ret then + return 1 + else + return nil, src_err or snk_err + end end -- pumps all data from a source to a sink, using a step function function pump.all(src, snk, step) - base.assert(src and snk) - step = step or pump.step - while true do - local ret, err = step(src, snk) - if not ret then - if err then return nil, err - else return 1 end - end + base.assert(src and snk) + step = step or pump.step + while true do + local ret, err = step(src, snk) + if not ret then + if err then + return nil, err + else + return 1 + end end + end end - diff --git a/lualib/mime.lua b/lualib/mime.lua index 7fdd9ed..7e038af 100644 --- a/lualib/mime.lua +++ b/lualib/mime.lua @@ -22,53 +22,60 @@ wrapt = {} -- creates a function that chooses a filter by name from a given table local function choose(table) - return function(name, opt1, opt2) - if base.type(name) ~= "string" then - name, opt1, opt2 = "default", name, opt1 - end - local f = table[name or "nil"] - if not f then - base.error("unknown key (" .. base.tostring(name) .. ")", 3) - else return f(opt1, opt2) end + return function(name, opt1, opt2) + if base.type(name) ~= "string" then + name, opt1, opt2 = "default", name, opt1 end + local f = table[name or "nil"] + if not f then + base.error("unknown key (" .. base.tostring(name) .. ")", 3) + else + return f(opt1, opt2) + end + end end -- define the encoding filters encodet['base64'] = function() - return ltn12.filter.cycle(b64, "") + return ltn12.filter.cycle(b64, "") end encodet['quoted-printable'] = function(mode) - return ltn12.filter.cycle(qp, "", - (mode == "binary") and "=0D=0A" or "\r\n") + return ltn12.filter.cycle(qp, "", + (mode == "binary") and "=0D=0A" or "\r\n") end -- define the decoding filters decodet['base64'] = function() - return ltn12.filter.cycle(unb64, "") + return ltn12.filter.cycle(unb64, "") end decodet['quoted-printable'] = function() - return ltn12.filter.cycle(unqp, "") + return ltn12.filter.cycle(unqp, "") end local function format(chunk) - if chunk then - if chunk == "" then return "''" - else return string.len(chunk) end - else return "nil" end + if chunk then + if chunk == "" then + return "''" + else + return string.len(chunk) + end + else + return "nil" + end end -- define the line-wrap filters wrapt['text'] = function(length) - length = length or 76 - return ltn12.filter.cycle(wrp, length, length) + length = length or 76 + return ltn12.filter.cycle(wrp, length, length) end wrapt['base64'] = wrapt['text'] wrapt['default'] = wrapt['text'] wrapt['quoted-printable'] = function() - return ltn12.filter.cycle(qpwrp, 76, 76) + return ltn12.filter.cycle(qpwrp, 76, 76) end -- function that choose the encoding, decoding or wrap algorithm @@ -78,10 +85,10 @@ wrap = choose(wrapt) -- define the end-of-line normalization filter function normalize(marker) - return ltn12.filter.cycle(eol, 0, marker) + return ltn12.filter.cycle(eol, 0, marker) end -- high level stuffing filter function stuff() - return ltn12.filter.cycle(dot, 2) + return ltn12.filter.cycle(dot, 2) end diff --git a/lualib/socket.lua b/lualib/socket.lua index 211adcd..b705b0b 100644 --- a/lualib/socket.lua +++ b/lualib/socket.lua @@ -17,39 +17,42 @@ module("socket") -- Exported auxiliar functions ----------------------------------------------------------------------------- function connect(address, port, laddress, lport) - local sock, err = socket.tcp() - if not sock then return nil, err end - if laddress then - local res, err = sock:bind(laddress, lport, -1) - if not res then return nil, err end - end - local res, err = sock:connect(address, port) + local sock, err = socket.tcp() + if not sock then return nil, err end + if laddress then + local res, err = sock:bind(laddress, lport, -1) if not res then return nil, err end - return sock + end + local res, err = sock:connect(address, port) + if not res then return nil, err end + return sock end function bind(host, port, backlog) - local sock, err = socket.tcp() - if not sock then return nil, err end - sock:setoption("reuseaddr", true) - local res, err = sock:bind(host, port) - if not res then return nil, err end - res, err = sock:listen(backlog) - if not res then return nil, err end - return sock + local sock, err = socket.tcp() + if not sock then return nil, err end + sock:setoption("reuseaddr", true) + local res, err = sock:bind(host, port) + if not res then return nil, err end + res, err = sock:listen(backlog) + if not res then return nil, err end + return sock end try = newtry() function choose(table) - return function(name, opt1, opt2) - if base.type(name) ~= "string" then - name, opt1, opt2 = "default", name, opt1 - end - local f = table[name or "nil"] - if not f then base.error("unknown key (".. base.tostring(name) ..")", 3) - else return f(opt1, opt2) end + return function(name, opt1, opt2) + if base.type(name) ~= "string" then + name, opt1, opt2 = "default", name, opt1 end + local f = table[name or "nil"] + if not f then + base.error("unknown key (" .. base.tostring(name) .. ")", 3) + else + return f(opt1, opt2) + end + end end ----------------------------------------------------------------------------- @@ -62,29 +65,34 @@ sinkt = {} BLOCKSIZE = 2048 sinkt["close-when-done"] = function(sock) - return base.setmetatable({ - getfd = function() return sock:getfd() end, - dirty = function() return sock:dirty() end - }, { - __call = function(self, chunk, err) - if not chunk then - sock:close() - return 1 - else return sock:send(chunk) end - end - }) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if not chunk then + sock:close() + return 1 + else + return sock:send(chunk) + end + end + }) end sinkt["keep-open"] = function(sock) - return base.setmetatable({ - getfd = function() return sock:getfd() end, - dirty = function() return sock:dirty() end - }, { - __call = function(self, chunk, err) - if chunk then return sock:send(chunk) - else return 1 end - end - }) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if chunk then + return sock:send(chunk) + else + return 1 + end + end + }) end sinkt["default"] = sinkt["keep-open"] @@ -92,42 +100,44 @@ sinkt["default"] = sinkt["keep-open"] sink = choose(sinkt) sourcet["by-length"] = function(sock, length) - return base.setmetatable({ - getfd = function() return sock:getfd() end, - dirty = function() return sock:dirty() end - }, { - __call = function() - if length <= 0 then return nil end - local size = math.min(socket.BLOCKSIZE, length) - local chunk, err = sock:receive(size) - if err then return nil, err end - length = length - string.len(chunk) - return chunk - end - }) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + if length <= 0 then return nil end + local size = math.min(socket.BLOCKSIZE, length) + local chunk, err = sock:receive(size) + if err then return nil, err end + length = length - string.len(chunk) + return chunk + end + }) end sourcet["until-closed"] = function(sock) - local done - return base.setmetatable({ - getfd = function() return sock:getfd() end, - dirty = function() return sock:dirty() end - }, { - __call = function() - if done then return nil end - local chunk, err, partial = sock:receive(socket.BLOCKSIZE) - if not err then return chunk - elseif err == "closed" then - sock:close() - done = 1 - return partial - else return nil, err end - end - }) + local done + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + if done then return nil end + local chunk, err, partial = sock:receive(socket.BLOCKSIZE) + if not err then + return chunk + elseif err == "closed" then + sock:close() + done = 1 + return partial + else + return nil, err + end + end + }) end sourcet["default"] = sourcet["until-closed"] source = choose(sourcet) - diff --git a/lualib/socket/ftp.lua b/lualib/socket/ftp.lua index 598f65d..689b023 100644 --- a/lualib/socket/ftp.lua +++ b/lualib/socket/ftp.lua @@ -36,246 +36,253 @@ PASSWORD = "anonymous@anonymous.org" local metat = { __index = {} } function open(server, port, create) - local tp = socket.try(tp.connect(server, port or PORT, TIMEOUT, create)) - local f = base.setmetatable({ tp = tp }, metat) - -- make sure everything gets closed in an exception - f.try = socket.newtry(function() f:close() end) - return f + local tp = socket.try(tp.connect(server, port or PORT, TIMEOUT, create)) + local f = base.setmetatable({ tp = tp }, metat) + -- make sure everything gets closed in an exception + f.try = socket.newtry(function() f:close() end) + return f end function metat.__index:portconnect() - self.try(self.server:settimeout(TIMEOUT)) - self.data = self.try(self.server:accept()) - self.try(self.data:settimeout(TIMEOUT)) + self.try(self.server:settimeout(TIMEOUT)) + self.data = self.try(self.server:accept()) + self.try(self.data:settimeout(TIMEOUT)) end function metat.__index:pasvconnect() - self.data = self.try(socket.tcp()) - self.try(self.data:settimeout(TIMEOUT)) - self.try(self.data:connect(self.pasvt.ip, self.pasvt.port)) + self.data = self.try(socket.tcp()) + self.try(self.data:settimeout(TIMEOUT)) + self.try(self.data:connect(self.pasvt.ip, self.pasvt.port)) end function metat.__index:login(user, password) - self.try(self.tp:command("user", user or USER)) - local code, reply = self.try(self.tp:check{"2..", 331}) - if code == 331 then - self.try(self.tp:command("pass", password or PASSWORD)) - self.try(self.tp:check("2..")) - end - return 1 + self.try(self.tp:command("user", user or USER)) + local code, reply = self.try(self.tp:check { "2..", 331 }) + if code == 331 then + self.try(self.tp:command("pass", password or PASSWORD)) + self.try(self.tp:check("2..")) + end + return 1 end function metat.__index:pasv() - self.try(self.tp:command("pasv")) - local code, reply = self.try(self.tp:check("2..")) - local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)" - local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern)) - self.try(a and b and c and d and p1 and p2, reply) - self.pasvt = { - ip = string.format("%d.%d.%d.%d", a, b, c, d), - port = p1*256 + p2 - } - if self.server then - self.server:close() - self.server = nil - end - return self.pasvt.ip, self.pasvt.port + self.try(self.tp:command("pasv")) + local code, reply = self.try(self.tp:check("2..")) + local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)" + local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern)) + self.try(a and b and c and d and p1 and p2, reply) + self.pasvt = { + ip = string.format("%d.%d.%d.%d", a, b, c, d), + port = p1 * 256 + p2 + } + if self.server then + self.server:close() + self.server = nil + end + return self.pasvt.ip, self.pasvt.port end function metat.__index:port(ip, port) - self.pasvt = nil - if not ip then - ip, port = self.try(self.tp:getcontrol():getsockname()) - self.server = self.try(socket.bind(ip, 0)) - ip, port = self.try(self.server:getsockname()) - self.try(self.server:settimeout(TIMEOUT)) - end - local pl = math.mod(port, 256) - local ph = (port - pl)/256 - local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") - self.try(self.tp:command("port", arg)) - self.try(self.tp:check("2..")) - return 1 + self.pasvt = nil + if not ip then + ip, port = self.try(self.tp:getcontrol():getsockname()) + self.server = self.try(socket.bind(ip, 0)) + ip, port = self.try(self.server:getsockname()) + self.try(self.server:settimeout(TIMEOUT)) + end + local pl = math.mod(port, 256) + local ph = (port - pl) / 256 + local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") + self.try(self.tp:command("port", arg)) + self.try(self.tp:check("2..")) + return 1 end function metat.__index:send(sendt) - self.try(self.pasvt or self.server, "need port or pasv first") - -- if there is a pasvt table, we already sent a PASV command - -- we just get the data connection into self.data - if self.pasvt then self:pasvconnect() end - -- get the transfer argument and command - local argument = sendt.argument or - url.unescape(string.gsub(sendt.path or "", "^[/\\]", "")) - if argument == "" then argument = nil end - local command = sendt.command or "stor" - -- send the transfer command and check the reply - self.try(self.tp:command(command, argument)) - local code, reply = self.try(self.tp:check{"2..", "1.."}) - -- if there is not a a pasvt table, then there is a server - -- and we already sent a PORT command - if not self.pasvt then self:portconnect() end - -- get the sink, source and step for the transfer - local step = sendt.step or ltn12.pump.step - local readt = {self.tp.c} - local checkstep = function(src, snk) - -- check status in control connection while downloading - local readyt = socket.select(readt, nil, 0) - if readyt[tp] then code = self.try(self.tp:check("2..")) end - return step(src, snk) - end - local sink = socket.sink("close-when-done", self.data) - -- transfer all data and check error - self.try(ltn12.pump.all(sendt.source, sink, checkstep)) - if string.find(code, "1..") then self.try(self.tp:check("2..")) end - -- done with data connection - self.data:close() - -- find out how many bytes were sent - local sent = socket.skip(1, self.data:getstats()) - self.data = nil - return sent + self.try(self.pasvt or self.server, "need port or pasv first") + -- if there is a pasvt table, we already sent a PASV command + -- we just get the data connection into self.data + if self.pasvt then self:pasvconnect() end + -- get the transfer argument and command + local argument = sendt.argument or + url.unescape(string.gsub(sendt.path or "", "^[/\\]", "")) + if argument == "" then argument = nil end + local command = sendt.command or "stor" + -- send the transfer command and check the reply + self.try(self.tp:command(command, argument)) + local code, reply = self.try(self.tp:check { "2..", "1.." }) + -- if there is not a a pasvt table, then there is a server + -- and we already sent a PORT command + if not self.pasvt then self:portconnect() end + -- get the sink, source and step for the transfer + local step = sendt.step or ltn12.pump.step + local readt = { self.tp.c } + local checkstep = function(src, snk) + -- check status in control connection while downloading + local readyt = socket.select(readt, nil, 0) + if readyt[tp] then code = self.try(self.tp:check("2..")) end + return step(src, snk) + end + local sink = socket.sink("close-when-done", self.data) + -- transfer all data and check error + self.try(ltn12.pump.all(sendt.source, sink, checkstep)) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + -- done with data connection + self.data:close() + -- find out how many bytes were sent + local sent = socket.skip(1, self.data:getstats()) + self.data = nil + return sent end function metat.__index:receive(recvt) - self.try(self.pasvt or self.server, "need port or pasv first") - if self.pasvt then self:pasvconnect() end - local argument = recvt.argument or - url.unescape(string.gsub(recvt.path or "", "^[/\\]", "")) - if argument == "" then argument = nil end - local command = recvt.command or "retr" - self.try(self.tp:command(command, argument)) - local code = self.try(self.tp:check{"1..", "2.."}) - if not self.pasvt then self:portconnect() end - local source = socket.source("until-closed", self.data) - local step = recvt.step or ltn12.pump.step - self.try(ltn12.pump.all(source, recvt.sink, step)) - if string.find(code, "1..") then self.try(self.tp:check("2..")) end - self.data:close() - self.data = nil - return 1 + self.try(self.pasvt or self.server, "need port or pasv first") + if self.pasvt then self:pasvconnect() end + local argument = recvt.argument or + url.unescape(string.gsub(recvt.path or "", "^[/\\]", "")) + if argument == "" then argument = nil end + local command = recvt.command or "retr" + self.try(self.tp:command(command, argument)) + local code = self.try(self.tp:check { "1..", "2.." }) + if not self.pasvt then self:portconnect() end + local source = socket.source("until-closed", self.data) + local step = recvt.step or ltn12.pump.step + self.try(ltn12.pump.all(source, recvt.sink, step)) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + self.data:close() + self.data = nil + return 1 end function metat.__index:cwd(dir) - self.try(self.tp:command("cwd", dir)) - self.try(self.tp:check(250)) - return 1 + self.try(self.tp:command("cwd", dir)) + self.try(self.tp:check(250)) + return 1 end function metat.__index:type(type) - self.try(self.tp:command("type", type)) - self.try(self.tp:check(200)) - return 1 + self.try(self.tp:command("type", type)) + self.try(self.tp:check(200)) + return 1 end function metat.__index:greet() - local code = self.try(self.tp:check{"1..", "2.."}) - if string.find(code, "1..") then self.try(self.tp:check("2..")) end - return 1 + local code = self.try(self.tp:check { "1..", "2.." }) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + return 1 end function metat.__index:quit() - self.try(self.tp:command("quit")) - self.try(self.tp:check("2..")) - return 1 + self.try(self.tp:command("quit")) + self.try(self.tp:check("2..")) + return 1 end function metat.__index:close() - if self.data then self.data:close() end - if self.server then self.server:close() end - return self.tp:close() + if self.data then self.data:close() end + if self.server then self.server:close() end + return self.tp:close() end ----------------------------------------------------------------------------- -- High level FTP API ----------------------------------------------------------------------------- local function override(t) - if t.url then - local u = url.parse(t.url) - for i,v in base.pairs(t) do - u[i] = v - end - return u - else return t end + if t.url then + local u = url.parse(t.url) + for i, v in base.pairs(t) do + u[i] = v + end + return u + else + return t + end end local function tput(putt) - putt = override(putt) - socket.try(putt.host, "missing hostname") - local f = open(putt.host, putt.port, putt.create) - f:greet() - f:login(putt.user, putt.password) - if putt.type then f:type(putt.type) end - f:pasv() - local sent = f:send(putt) - f:quit() - f:close() - return sent + putt = override(putt) + socket.try(putt.host, "missing hostname") + local f = open(putt.host, putt.port, putt.create) + f:greet() + f:login(putt.user, putt.password) + if putt.type then f:type(putt.type) end + f:pasv() + local sent = f:send(putt) + f:quit() + f:close() + return sent end local default = { - path = "/", - scheme = "ftp" + path = "/", + scheme = "ftp" } local function parse(u) - local t = socket.try(url.parse(u, default)) - socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") - socket.try(t.host, "missing hostname") - local pat = "^type=(.)$" - if t.params then - t.type = socket.skip(2, string.find(t.params, pat)) - socket.try(t.type == "a" or t.type == "i", - "invalid type '" .. t.type .. "'") - end - return t + local t = socket.try(url.parse(u, default)) + socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") + socket.try(t.host, "missing hostname") + local pat = "^type=(.)$" + if t.params then + t.type = socket.skip(2, string.find(t.params, pat)) + socket.try(t.type == "a" or t.type == "i", + "invalid type '" .. t.type .. "'") + end + return t end local function sput(u, body) - local putt = parse(u) - putt.source = ltn12.source.string(body) - return tput(putt) + local putt = parse(u) + putt.source = ltn12.source.string(body) + return tput(putt) end put = socket.protect(function(putt, body) - if base.type(putt) == "string" then return sput(putt, body) - else return tput(putt) end + if base.type(putt) == "string" then + return sput(putt, body) + else + return tput(putt) + end end) local function tget(gett) - gett = override(gett) - socket.try(gett.host, "missing hostname") - local f = open(gett.host, gett.port, gett.create) - f:greet() - f:login(gett.user, gett.password) - if gett.type then f:type(gett.type) end - f:pasv() - f:receive(gett) - f:quit() - return f:close() + gett = override(gett) + socket.try(gett.host, "missing hostname") + local f = open(gett.host, gett.port, gett.create) + f:greet() + f:login(gett.user, gett.password) + if gett.type then f:type(gett.type) end + f:pasv() + f:receive(gett) + f:quit() + return f:close() end local function sget(u) - local gett = parse(u) - local t = {} - gett.sink = ltn12.sink.table(t) - tget(gett) - return table.concat(t) + local gett = parse(u) + local t = {} + gett.sink = ltn12.sink.table(t) + tget(gett) + return table.concat(t) end command = socket.protect(function(cmdt) - cmdt = override(cmdt) - socket.try(cmdt.host, "missing hostname") - socket.try(cmdt.command, "missing command") - local f = open(cmdt.host, cmdt.port, cmdt.create) - f:greet() - f:login(cmdt.user, cmdt.password) - f.try(f.tp:command(cmdt.command, cmdt.argument)) - if cmdt.check then f.try(f.tp:check(cmdt.check)) end - f:quit() - return f:close() + cmdt = override(cmdt) + socket.try(cmdt.host, "missing hostname") + socket.try(cmdt.command, "missing command") + local f = open(cmdt.host, cmdt.port, cmdt.create) + f:greet() + f:login(cmdt.user, cmdt.password) + f.try(f.tp:command(cmdt.command, cmdt.argument)) + if cmdt.check then f.try(f.tp:check(cmdt.check)) end + f:quit() + return f:close() end) get = socket.protect(function(gett) - if base.type(gett) == "string" then return sget(gett) - else return tget(gett) end + if base.type(gett) == "string" then + return sget(gett) + else + return tget(gett) + end end) - diff --git a/lualib/socket/http.lua b/lualib/socket/http.lua index c020d8e..66f90a4 100644 --- a/lualib/socket/http.lua +++ b/lualib/socket/http.lua @@ -31,73 +31,76 @@ USERAGENT = socket._VERSION -- Reads MIME headers from a connection, unfolding where needed ----------------------------------------------------------------------------- local function receiveheaders(sock, headers) - local line, name, value, err - headers = headers or {} - -- get first line + local line, name, value, err + headers = headers or {} + -- get first line + line, err = sock:receive() + if err then return nil, err end + -- headers go until a blank line is found + while line ~= "" do + -- get field-name and value + name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)")) + if not (name and value) then return nil, "malformed reponse headers" end + name = string.lower(name) + -- get next line (value might be folded) line, err = sock:receive() if err then return nil, err end - -- headers go until a blank line is found - while line ~= "" do - -- get field-name and value - name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)")) - if not (name and value) then return nil, "malformed reponse headers" end - name = string.lower(name) - -- get next line (value might be folded) - line, err = sock:receive() - if err then return nil, err end - -- unfold any folded values - while string.find(line, "^%s") do - value = value .. line - line = sock:receive() - if err then return nil, err end - end - -- save pair in table - if headers[name] then headers[name] = headers[name] .. ", " .. value - else headers[name] = value end + -- unfold any folded values + while string.find(line, "^%s") do + value = value .. line + line = sock:receive() + if err then return nil, err end end - return headers + -- save pair in table + if headers[name] then + headers[name] = headers[name] .. ", " .. value + else + headers[name] = value + end + end + return headers end ----------------------------------------------------------------------------- -- Extra sources and sinks ----------------------------------------------------------------------------- socket.sourcet["http-chunked"] = function(sock, headers) - return base.setmetatable({ - getfd = function() return sock:getfd() end, - dirty = function() return sock:dirty() end - }, { - __call = function() - -- get chunk size, skip extention - local line, err = sock:receive() - if err then return nil, err end - local size = base.tonumber(string.gsub(line, ";.*", ""), 16) - if not size then return nil, "invalid chunk size" end - -- was it the last chunk? - if size > 0 then - -- if not, get chunk and skip terminating CRLF - local chunk, err, part = sock:receive(size) - if chunk then sock:receive() end - return chunk, err - else - -- if it was, read trailers into headers table - headers, err = receiveheaders(sock, headers) - if not headers then return nil, err end - end - end - }) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + -- get chunk size, skip extention + local line, err = sock:receive() + if err then return nil, err end + local size = base.tonumber(string.gsub(line, ";.*", ""), 16) + if not size then return nil, "invalid chunk size" end + -- was it the last chunk? + if size > 0 then + -- if not, get chunk and skip terminating CRLF + local chunk, err, part = sock:receive(size) + if chunk then sock:receive() end + return chunk, err + else + -- if it was, read trailers into headers table + headers, err = receiveheaders(sock, headers) + if not headers then return nil, err end + end + end + }) end socket.sinkt["http-chunked"] = function(sock) - return base.setmetatable({ - getfd = function() return sock:getfd() end, - dirty = function() return sock:dirty() end - }, { - __call = function(self, chunk, err) - if not chunk then return sock:send("0\r\n\r\n") end - local size = string.format("%X\r\n", string.len(chunk)) - return sock:send(size .. chunk .. "\r\n") - end - }) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if not chunk then return sock:send("0\r\n\r\n") end + local size = string.format("%X\r\n", string.len(chunk)) + return sock:send(size .. chunk .. "\r\n") + end + }) end ----------------------------------------------------------------------------- @@ -106,245 +109,251 @@ end local metat = { __index = {} } function open(host, port, create) - -- create socket with user connect function, or with default - local c = socket.try((create or socket.tcp)()) - local h = base.setmetatable({ c = c }, metat) - -- create finalized try - h.try = socket.newtry(function() h:close() end) - -- set timeout before connecting - h.try(c:settimeout(TIMEOUT)) - h.try(c:connect(host, port or PORT)) - -- here everything worked - return h + -- create socket with user connect function, or with default + local c = socket.try((create or socket.tcp)()) + local h = base.setmetatable({ c = c }, metat) + -- create finalized try + h.try = socket.newtry(function() h:close() end) + -- set timeout before connecting + h.try(c:settimeout(TIMEOUT)) + h.try(c:connect(host, port or PORT)) + -- here everything worked + return h end function metat.__index:sendrequestline(method, uri) - local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri) - return self.try(self.c:send(reqline)) + local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri) + return self.try(self.c:send(reqline)) end function metat.__index:sendheaders(headers) - local h = "\r\n" - for i, v in base.pairs(headers) do - h = i .. ": " .. v .. "\r\n" .. h - end - self.try(self.c:send(h)) - return 1 + local h = "\r\n" + for i, v in base.pairs(headers) do + h = i .. ": " .. v .. "\r\n" .. h + end + self.try(self.c:send(h)) + return 1 end function metat.__index:sendbody(headers, source, step) - source = source or ltn12.source.empty() - step = step or ltn12.pump.step - -- if we don't know the size in advance, send chunked and hope for the best - local mode = "http-chunked" - if headers["content-length"] then mode = "keep-open" end - return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step)) + source = source or ltn12.source.empty() + step = step or ltn12.pump.step + -- if we don't know the size in advance, send chunked and hope for the best + local mode = "http-chunked" + if headers["content-length"] then mode = "keep-open" end + return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step)) end function metat.__index:receivestatusline() - local status = self.try(self.c:receive(5)) - -- identify HTTP/0.9 responses, which do not contain a status line - -- this is just a heuristic, but is what the RFC recommends - if status ~= "HTTP/" then return nil, status end - -- otherwise proceed reading a status line - status = self.try(self.c:receive("*l", status)) - local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) - return self.try(base.tonumber(code), status) + local status = self.try(self.c:receive(5)) + -- identify HTTP/0.9 responses, which do not contain a status line + -- this is just a heuristic, but is what the RFC recommends + if status ~= "HTTP/" then return nil, status end + -- otherwise proceed reading a status line + status = self.try(self.c:receive("*l", status)) + local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) + return self.try(base.tonumber(code), status) end function metat.__index:receiveheaders() - return self.try(receiveheaders(self.c)) + return self.try(receiveheaders(self.c)) end function metat.__index:receivebody(headers, sink, step) - sink = sink or ltn12.sink.null() - step = step or ltn12.pump.step - local length = base.tonumber(headers["content-length"]) - local t = headers["transfer-encoding"] -- shortcut - local mode = "default" -- connection close - if t and t ~= "identity" then mode = "http-chunked" - elseif base.tonumber(headers["content-length"]) then mode = "by-length" end - return self.try(ltn12.pump.all(socket.source(mode, self.c, length), - sink, step)) + sink = sink or ltn12.sink.null() + step = step or ltn12.pump.step + local length = base.tonumber(headers["content-length"]) + local t = headers["transfer-encoding"] -- shortcut + local mode = "default" -- connection close + if t and t ~= "identity" then + mode = "http-chunked" + elseif base.tonumber(headers["content-length"]) then + mode = "by-length" + end + return self.try(ltn12.pump.all(socket.source(mode, self.c, length), + sink, step)) end function metat.__index:receive09body(status, sink, step) - local source = ltn12.source.rewind(socket.source("until-closed", self.c)) - source(status) - return self.try(ltn12.pump.all(source, sink, step)) + local source = ltn12.source.rewind(socket.source("until-closed", self.c)) + source(status) + return self.try(ltn12.pump.all(source, sink, step)) end function metat.__index:close() - return self.c:close() + return self.c:close() end ----------------------------------------------------------------------------- -- High level HTTP API ----------------------------------------------------------------------------- local function adjusturi(reqt) - local u = reqt - -- if there is a proxy, we need the full url. otherwise, just a part. - if not reqt.proxy and not PROXY then - u = { - path = socket.try(reqt.path, "invalid path 'nil'"), - params = reqt.params, - query = reqt.query, - fragment = reqt.fragment - } - end - return url.build(u) + local u = reqt + -- if there is a proxy, we need the full url. otherwise, just a part. + if not reqt.proxy and not PROXY then + u = { + path = socket.try(reqt.path, "invalid path 'nil'"), + params = reqt.params, + query = reqt.query, + fragment = reqt.fragment + } + end + return url.build(u) end local function adjustproxy(reqt) - local proxy = reqt.proxy or PROXY - if proxy then - proxy = url.parse(proxy) - return proxy.host, proxy.port or 3128 - else - return reqt.host, reqt.port - end + local proxy = reqt.proxy or PROXY + if proxy then + proxy = url.parse(proxy) + return proxy.host, proxy.port or 3128 + else + return reqt.host, reqt.port + end end local function adjustheaders(reqt) - -- default headers - local lower = { - ["user-agent"] = USERAGENT, - ["host"] = reqt.host, - ["connection"] = "close, TE", - ["te"] = "trailers" - } - -- if we have authentication information, pass it along - if reqt.user and reqt.password then - lower["authorization"] = - "Basic " .. (mime.b64(reqt.user .. ":" .. reqt.password)) - end - -- override with user headers - for i,v in base.pairs(reqt.headers or lower) do - lower[string.lower(i)] = v - end - return lower + -- default headers + local lower = { + ["user-agent"] = USERAGENT, + ["host"] = reqt.host, + ["connection"] = "close, TE", + ["te"] = "trailers" + } + -- if we have authentication information, pass it along + if reqt.user and reqt.password then + lower["authorization"] = + "Basic " .. (mime.b64(reqt.user .. ":" .. reqt.password)) + end + -- override with user headers + for i, v in base.pairs(reqt.headers or lower) do + lower[string.lower(i)] = v + end + return lower end -- default url parts local default = { - host = "", - port = PORT, - path ="/", - scheme = "http" + host = "", + port = PORT, + path = "/", + scheme = "http" } local function adjustrequest(reqt) - -- parse url if provided - local nreqt = reqt.url and url.parse(reqt.url, default) or {} - -- explicit components override url - for i,v in base.pairs(reqt) do nreqt[i] = v end - if nreqt.port == "" then nreqt.port = 80 end - socket.try(nreqt.host and nreqt.host ~= "", - "invalid host '" .. base.tostring(nreqt.host) .. "'") - -- compute uri if user hasn't overriden - nreqt.uri = reqt.uri or adjusturi(nreqt) - -- ajust host and port if there is a proxy - nreqt.host, nreqt.port = adjustproxy(nreqt) - -- adjust headers in request - nreqt.headers = adjustheaders(nreqt) - return nreqt + -- parse url if provided + local nreqt = reqt.url and url.parse(reqt.url, default) or {} + -- explicit components override url + for i, v in base.pairs(reqt) do nreqt[i] = v end + if nreqt.port == "" then nreqt.port = 80 end + socket.try(nreqt.host and nreqt.host ~= "", + "invalid host '" .. base.tostring(nreqt.host) .. "'") + -- compute uri if user hasn't overriden + nreqt.uri = reqt.uri or adjusturi(nreqt) + -- ajust host and port if there is a proxy + nreqt.host, nreqt.port = adjustproxy(nreqt) + -- adjust headers in request + nreqt.headers = adjustheaders(nreqt) + return nreqt end local function shouldredirect(reqt, code, headers) - return headers.location and - string.gsub(headers.location, "%s", "") ~= "" and - (reqt.redirect ~= false) and - (code == 301 or code == 302) and - (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") - and (not reqt.nredirects or reqt.nredirects < 5) + return headers.location and + string.gsub(headers.location, "%s", "") ~= "" and + (reqt.redirect ~= false) and + (code == 301 or code == 302) and + (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") + and (not reqt.nredirects or reqt.nredirects < 5) end local function shouldreceivebody(reqt, code) - if reqt.method == "HEAD" then return nil end - if code == 204 or code == 304 then return nil end - if code >= 100 and code < 200 then return nil end - return 1 + if reqt.method == "HEAD" then return nil end + if code == 204 or code == 304 then return nil end + if code >= 100 and code < 200 then return nil end + return 1 end -- forward declarations local trequest, tredirect function tredirect(reqt, location) - local result, code, headers, status = trequest { - -- the RFC says the redirect URL has to be absolute, but some - -- servers do not respect that - url = url.absolute(reqt.url, location), - source = reqt.source, - sink = reqt.sink, - headers = reqt.headers, - proxy = reqt.proxy, - nredirects = (reqt.nredirects or 0) + 1, - create = reqt.create - } - -- pass location header back as a hint we redirected - headers = headers or {} - headers.location = headers.location or location - return result, code, headers, status + local result, code, headers, status = trequest { + -- the RFC says the redirect URL has to be absolute, but some + -- servers do not respect that + url = url.absolute(reqt.url, location), + source = reqt.source, + sink = reqt.sink, + headers = reqt.headers, + proxy = reqt.proxy, + nredirects = (reqt.nredirects or 0) + 1, + create = reqt.create + } + -- pass location header back as a hint we redirected + headers = headers or {} + headers.location = headers.location or location + return result, code, headers, status end function trequest(reqt) - -- we loop until we get what we want, or - -- until we are sure there is no way to get it - local nreqt = adjustrequest(reqt) - local h = open(nreqt.host, nreqt.port, nreqt.create) - -- send request line and headers - h:sendrequestline(nreqt.method, nreqt.uri) - h:sendheaders(nreqt.headers) - -- if there is a body, send it - if nreqt.source then - h:sendbody(nreqt.headers, nreqt.source, nreqt.step) - end - local code, status = h:receivestatusline() - -- if it is an HTTP/0.9 server, simply get the body and we are done - if not code then - h:receive09body(status, nreqt.sink, nreqt.step) - return 1, 200 - end - local headers - -- ignore any 100-continue messages - while code == 100 do - headers = h:receiveheaders() - code, status = h:receivestatusline() - end + -- we loop until we get what we want, or + -- until we are sure there is no way to get it + local nreqt = adjustrequest(reqt) + local h = open(nreqt.host, nreqt.port, nreqt.create) + -- send request line and headers + h:sendrequestline(nreqt.method, nreqt.uri) + h:sendheaders(nreqt.headers) + -- if there is a body, send it + if nreqt.source then + h:sendbody(nreqt.headers, nreqt.source, nreqt.step) + end + local code, status = h:receivestatusline() + -- if it is an HTTP/0.9 server, simply get the body and we are done + if not code then + h:receive09body(status, nreqt.sink, nreqt.step) + return 1, 200 + end + local headers + -- ignore any 100-continue messages + while code == 100 do headers = h:receiveheaders() - -- at this point we should have a honest reply from the server - -- we can't redirect if we already used the source, so we report the error - if shouldredirect(nreqt, code, headers) and not nreqt.source then - h:close() - return tredirect(reqt, headers.location) - end - -- here we are finally done - if shouldreceivebody(nreqt, code) then - h:receivebody(headers, nreqt.sink, nreqt.step) - end + code, status = h:receivestatusline() + end + headers = h:receiveheaders() + -- at this point we should have a honest reply from the server + -- we can't redirect if we already used the source, so we report the error + if shouldredirect(nreqt, code, headers) and not nreqt.source then h:close() - return 1, code, headers, status + return tredirect(reqt, headers.location) + end + -- here we are finally done + if shouldreceivebody(nreqt, code) then + h:receivebody(headers, nreqt.sink, nreqt.step) + end + h:close() + return 1, code, headers, status end local function srequest(u, b) - local t = {} - local reqt = { - url = u, - sink = ltn12.sink.table(t) + local t = {} + local reqt = { + url = u, + sink = ltn12.sink.table(t) + } + if b then + reqt.source = ltn12.source.string(b) + reqt.headers = { + ["content-length"] = string.len(b), + ["content-type"] = "application/x-www-form-urlencoded" } - if b then - reqt.source = ltn12.source.string(b) - reqt.headers = { - ["content-length"] = string.len(b), - ["content-type"] = "application/x-www-form-urlencoded" - } - reqt.method = "POST" - end - local code, headers, status = socket.skip(1, trequest(reqt)) - return table.concat(t), code, headers, status + reqt.method = "POST" + end + local code, headers, status = socket.skip(1, trequest(reqt)) + return table.concat(t), code, headers, status end request = socket.protect(function(reqt, body) - if base.type(reqt) == "string" then return srequest(reqt, body) - else return trequest(reqt) end + if base.type(reqt) == "string" then + return srequest(reqt, body) + else + return trequest(reqt) + end end) diff --git a/lualib/socket/smtp.lua b/lualib/socket/smtp.lua index 8f3cfcf..dea98a5 100644 --- a/lualib/socket/smtp.lua +++ b/lualib/socket/smtp.lua @@ -40,95 +40,95 @@ ZONE = "-0000" local metat = { __index = {} } function metat.__index:greet(domain) - self.try(self.tp:check("2..")) - self.try(self.tp:command("EHLO", domain or DOMAIN)) - return socket.skip(1, self.try(self.tp:check("2.."))) + self.try(self.tp:check("2..")) + self.try(self.tp:command("EHLO", domain or DOMAIN)) + return socket.skip(1, self.try(self.tp:check("2.."))) end function metat.__index:mail(from) - self.try(self.tp:command("MAIL", "FROM:" .. from)) - return self.try(self.tp:check("2..")) + self.try(self.tp:command("MAIL", "FROM:" .. from)) + return self.try(self.tp:check("2..")) end function metat.__index:rcpt(to) - self.try(self.tp:command("RCPT", "TO:" .. to)) - return self.try(self.tp:check("2..")) + self.try(self.tp:command("RCPT", "TO:" .. to)) + return self.try(self.tp:check("2..")) end function metat.__index:data(src, step) - self.try(self.tp:command("DATA")) - self.try(self.tp:check("3..")) - self.try(self.tp:source(src, step)) - self.try(self.tp:send("\r\n.\r\n")) - return self.try(self.tp:check("2..")) + self.try(self.tp:command("DATA")) + self.try(self.tp:check("3..")) + self.try(self.tp:source(src, step)) + self.try(self.tp:send("\r\n.\r\n")) + return self.try(self.tp:check("2..")) end function metat.__index:quit() - self.try(self.tp:command("QUIT")) - return self.try(self.tp:check("2..")) + self.try(self.tp:command("QUIT")) + return self.try(self.tp:check("2..")) end function metat.__index:close() - return self.tp:close() + return self.tp:close() end function metat.__index:login(user, password) - self.try(self.tp:command("AUTH", "LOGIN")) - self.try(self.tp:check("3..")) - self.try(self.tp:command(mime.b64(user))) - self.try(self.tp:check("3..")) - self.try(self.tp:command(mime.b64(password))) - return self.try(self.tp:check("2..")) + self.try(self.tp:command("AUTH", "LOGIN")) + self.try(self.tp:check("3..")) + self.try(self.tp:command(mime.b64(user))) + self.try(self.tp:check("3..")) + self.try(self.tp:command(mime.b64(password))) + return self.try(self.tp:check("2..")) end function metat.__index:plain(user, password) - local auth = "PLAIN " .. mime.b64("\0" .. user .. "\0" .. password) - self.try(self.tp:command("AUTH", auth)) - return self.try(self.tp:check("2..")) + local auth = "PLAIN " .. mime.b64("\0" .. user .. "\0" .. password) + self.try(self.tp:command("AUTH", auth)) + return self.try(self.tp:check("2..")) end function metat.__index:auth(user, password, ext) - if not user or not password then return 1 end - if string.find(ext, "AUTH[^\n]+LOGIN") then - return self:login(user, password) - elseif string.find(ext, "AUTH[^\n]+PLAIN") then - return self:plain(user, password) - else - self.try(nil, "authentication not supported") - end + if not user or not password then return 1 end + if string.find(ext, "AUTH[^\n]+LOGIN") then + return self:login(user, password) + elseif string.find(ext, "AUTH[^\n]+PLAIN") then + return self:plain(user, password) + else + self.try(nil, "authentication not supported") + end end -- send message or throw an exception function metat.__index:send(mailt) - self:mail(mailt.from) - if base.type(mailt.rcpt) == "table" then - for i,v in base.ipairs(mailt.rcpt) do - self:rcpt(v) - end - else - self:rcpt(mailt.rcpt) + self:mail(mailt.from) + if base.type(mailt.rcpt) == "table" then + for i, v in base.ipairs(mailt.rcpt) do + self:rcpt(v) end - self:data(ltn12.source.chain(mailt.source, mime.stuff()), mailt.step) + else + self:rcpt(mailt.rcpt) + end + self:data(ltn12.source.chain(mailt.source, mime.stuff()), mailt.step) end function open(server, port, create) - local tp = socket.try(tp.connect(server or SERVER, port or PORT, - TIMEOUT, create)) - local s = base.setmetatable({tp = tp}, metat) - -- make sure tp is closed if we get an exception - s.try = socket.newtry(function() - s:close() - end) - return s + local tp = socket.try(tp.connect(server or SERVER, port or PORT, + TIMEOUT, create)) + local s = base.setmetatable({ tp = tp }, metat) + -- make sure tp is closed if we get an exception + s.try = socket.newtry(function() + s:close() + end) + return s end -- convert headers to lowercase local function lower_headers(headers) - local lower = {} - for i,v in base.pairs(headers or lower) do - lower[string.lower(i)] = v - end - return lower + local lower = {} + for i, v in base.pairs(headers or lower) do + lower[string.lower(i)] = v + end + return lower end --------------------------------------------------------------------------- @@ -137,9 +137,9 @@ end -- returns a hopefully unique mime boundary local seqno = 0 local function newboundary() - seqno = seqno + 1 - return string.format('%s%05d==%05u', os.date('%d%m%Y%H%M%S'), - math.random(0, 99999), seqno) + seqno = seqno + 1 + return string.format('%s%05d==%05u', os.date('%d%m%Y%H%M%S'), + math.random(0, 99999), seqno) end -- send_message forward declaration @@ -147,105 +147,116 @@ local send_message -- yield the headers all at once, it's faster local function send_headers(headers) - local h = "\r\n" - for i,v in base.pairs(headers) do - h = i .. ': ' .. v .. "\r\n" .. h - end - coroutine.yield(h) + local h = "\r\n" + for i, v in base.pairs(headers) do + h = i .. ': ' .. v .. "\r\n" .. h + end + coroutine.yield(h) end -- yield multipart message body from a multipart message table local function send_multipart(mesgt) - -- make sure we have our boundary and send headers - local bd = newboundary() - local headers = lower_headers(mesgt.headers or {}) - headers['content-type'] = headers['content-type'] or 'multipart/mixed' - headers['content-type'] = headers['content-type'] .. - '; boundary="' .. bd .. '"' - send_headers(headers) - -- send preamble - if mesgt.body.preamble then - coroutine.yield(mesgt.body.preamble) - coroutine.yield("\r\n") - end - -- send each part separated by a boundary - for i, m in base.ipairs(mesgt.body) do - coroutine.yield("\r\n--" .. bd .. "\r\n") - send_message(m) - end - -- send last boundary - coroutine.yield("\r\n--" .. bd .. "--\r\n\r\n") - -- send epilogue - if mesgt.body.epilogue then - coroutine.yield(mesgt.body.epilogue) - coroutine.yield("\r\n") - end + -- make sure we have our boundary and send headers + local bd = newboundary() + local headers = lower_headers(mesgt.headers or {}) + headers['content-type'] = headers['content-type'] or 'multipart/mixed' + headers['content-type'] = headers['content-type'] .. + '; boundary="' .. bd .. '"' + send_headers(headers) + -- send preamble + if mesgt.body.preamble then + coroutine.yield(mesgt.body.preamble) + coroutine.yield("\r\n") + end + -- send each part separated by a boundary + for i, m in base.ipairs(mesgt.body) do + coroutine.yield("\r\n--" .. bd .. "\r\n") + send_message(m) + end + -- send last boundary + coroutine.yield("\r\n--" .. bd .. "--\r\n\r\n") + -- send epilogue + if mesgt.body.epilogue then + coroutine.yield(mesgt.body.epilogue) + coroutine.yield("\r\n") + end end -- yield message body from a source local function send_source(mesgt) - -- make sure we have a content-type - local headers = lower_headers(mesgt.headers or {}) - headers['content-type'] = headers['content-type'] or - 'text/plain; charset="iso-8859-1"' - send_headers(headers) - -- send body from source - while true do - local chunk, err = mesgt.body() - if err then coroutine.yield(nil, err) - elseif chunk then coroutine.yield(chunk) - else break end + -- make sure we have a content-type + local headers = lower_headers(mesgt.headers or {}) + headers['content-type'] = headers['content-type'] or + 'text/plain; charset="iso-8859-1"' + send_headers(headers) + -- send body from source + while true do + local chunk, err = mesgt.body() + if err then + coroutine.yield(nil, err) + elseif chunk then + coroutine.yield(chunk) + else + break end + end end -- yield message body from a string local function send_string(mesgt) - -- make sure we have a content-type - local headers = lower_headers(mesgt.headers or {}) - headers['content-type'] = headers['content-type'] or - 'text/plain; charset="iso-8859-1"' - send_headers(headers) - -- send body from string - coroutine.yield(mesgt.body) + -- make sure we have a content-type + local headers = lower_headers(mesgt.headers or {}) + headers['content-type'] = headers['content-type'] or + 'text/plain; charset="iso-8859-1"' + send_headers(headers) + -- send body from string + coroutine.yield(mesgt.body) end -- message source function send_message(mesgt) - if base.type(mesgt.body) == "table" then send_multipart(mesgt) - elseif base.type(mesgt.body) == "function" then send_source(mesgt) - else send_string(mesgt) end + if base.type(mesgt.body) == "table" then + send_multipart(mesgt) + elseif base.type(mesgt.body) == "function" then + send_source(mesgt) + else + send_string(mesgt) + end end -- set defaul headers local function adjust_headers(mesgt) - local lower = lower_headers(mesgt.headers) - lower["date"] = lower["date"] or - os.date("!%a, %d %b %Y %H:%M:%S ") .. (mesgt.zone or ZONE) - lower["x-mailer"] = lower["x-mailer"] or socket._VERSION - -- this can't be overriden - lower["mime-version"] = "1.0" - return lower + local lower = lower_headers(mesgt.headers) + lower["date"] = lower["date"] or + os.date("!%a, %d %b %Y %H:%M:%S ") .. (mesgt.zone or ZONE) + lower["x-mailer"] = lower["x-mailer"] or socket._VERSION + -- this can't be overriden + lower["mime-version"] = "1.0" + return lower end function message(mesgt) - mesgt.headers = adjust_headers(mesgt) - -- create and return message source - local co = coroutine.create(function() send_message(mesgt) end) - return function() - local ret, a, b = coroutine.resume(co) - if ret then return a, b - else return nil, a end + mesgt.headers = adjust_headers(mesgt) + -- create and return message source + local co = coroutine.create(function() send_message(mesgt) end) + return function() + local ret, a, b = coroutine.resume(co) + if ret then + return a, b + else + return nil, a end + end end --------------------------------------------------------------------------- -- High level SMTP API ----------------------------------------------------------------------------- send = socket.protect(function(mailt) - local s = open(mailt.server, mailt.port, mailt.create) - local ext = s:greet(mailt.domain) - s:auth(mailt.user, mailt.password, ext) - s:send(mailt) - s:quit() - return s:close() + local s = open(mailt.server, mailt.port, mailt.create) + local ext = s:greet(mailt.domain) + s:auth(mailt.user, mailt.password, ext) + s:send(mailt) + s:quit() + return s:close() end) diff --git a/lualib/socket/tp.lua b/lualib/socket/tp.lua index 0683869..47a0526 100644 --- a/lualib/socket/tp.lua +++ b/lualib/socket/tp.lua @@ -24,100 +24,104 @@ TIMEOUT = 60 ----------------------------------------------------------------------------- -- gets server reply (works for SMTP and FTP) local function get_reply(c) - local code, current, sep - local line, err = c:receive() - local reply = line - if err then return nil, err end - code, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) - if not code then return nil, "invalid server reply" end - if sep == "-" then -- reply is multiline - repeat - line, err = c:receive() - if err then return nil, err end - current, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) - reply = reply .. "\n" .. line - -- reply ends with same code - until code == current and sep == " " - end - return code, reply + local code, current, sep + local line, err = c:receive() + local reply = line + if err then return nil, err end + code, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) + if not code then return nil, "invalid server reply" end + if sep == "-" then -- reply is multiline + repeat + line, err = c:receive() + if err then return nil, err end + current, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) + reply = reply .. "\n" .. line + -- reply ends with same code + until code == current and sep == " " + end + return code, reply end -- metatable for sock object local metat = { __index = {} } function metat.__index:check(ok) - local code, reply = get_reply(self.c) - if not code then return nil, reply end - if base.type(ok) ~= "function" then - if base.type(ok) == "table" then - for i, v in base.ipairs(ok) do - if string.find(code, v) then - return base.tonumber(code), reply - end - end - return nil, reply - else - if string.find(code, ok) then return base.tonumber(code), reply - else return nil, reply end + local code, reply = get_reply(self.c) + if not code then return nil, reply end + if base.type(ok) ~= "function" then + if base.type(ok) == "table" then + for i, v in base.ipairs(ok) do + if string.find(code, v) then + return base.tonumber(code), reply end - else return ok(base.tonumber(code), reply) end + end + return nil, reply + else + if string.find(code, ok) then + return base.tonumber(code), reply + else + return nil, reply + end + end + else + return ok(base.tonumber(code), reply) + end end function metat.__index:command(cmd, arg) - if arg then - return self.c:send(cmd .. " " .. arg.. "\r\n") - else - return self.c:send(cmd .. "\r\n") - end + if arg then + return self.c:send(cmd .. " " .. arg .. "\r\n") + else + return self.c:send(cmd .. "\r\n") + end end function metat.__index:sink(snk, pat) - local chunk, err = c:receive(pat) - return snk(chunk, err) + local chunk, err = c:receive(pat) + return snk(chunk, err) end function metat.__index:send(data) - return self.c:send(data) + return self.c:send(data) end function metat.__index:receive(pat) - return self.c:receive(pat) + return self.c:receive(pat) end function metat.__index:getfd() - return self.c:getfd() + return self.c:getfd() end function metat.__index:dirty() - return self.c:dirty() + return self.c:dirty() end function metat.__index:getcontrol() - return self.c + return self.c end function metat.__index:source(source, step) - local sink = socket.sink("keep-open", self.c) - local ret, err = ltn12.pump.all(source, sink, step or ltn12.pump.step) - return ret, err + local sink = socket.sink("keep-open", self.c) + local ret, err = ltn12.pump.all(source, sink, step or ltn12.pump.step) + return ret, err end -- closes the underlying c function metat.__index:close() - self.c:close() - return 1 + self.c:close() + return 1 end -- connect with server and return c object function connect(host, port, timeout, create) - local c, e = (create or socket.tcp)() - if not c then return nil, e end - c:settimeout(timeout or TIMEOUT) - local r, e = c:connect(host, port) - if not r then - c:close() - return nil, e - end - return base.setmetatable({c = c}, metat) + local c, e = (create or socket.tcp)() + if not c then return nil, e end + c:settimeout(timeout or TIMEOUT) + local r, e = c:connect(host, port) + if not r then + c:close() + return nil, e + end + return base.setmetatable({ c = c }, metat) end - diff --git a/lualib/socket/url.lua b/lualib/socket/url.lua index f05d91c..daec8f7 100644 --- a/lualib/socket/url.lua +++ b/lualib/socket/url.lua @@ -26,9 +26,9 @@ _VERSION = "URL 1.0.1" -- escaped representation of string binary ----------------------------------------------------------------------------- function escape(s) - return string.gsub(s, "([^A-Za-z0-9_])", function(c) - return string.format("%%%02x", string.byte(c)) - end) + return string.gsub(s, "([^A-Za-z0-9_])", function(c) + return string.format("%%%02x", string.byte(c)) + end) end ----------------------------------------------------------------------------- @@ -40,25 +40,28 @@ end -- escaped representation of string binary ----------------------------------------------------------------------------- local function make_set(t) - local s = {} - for i,v in base.ipairs(t) do - s[t[i]] = 1 - end - return s + local s = {} + for i, v in base.ipairs(t) do + s[t[i]] = 1 + end + return s end -- these are allowed withing a path segment, along with alphanum -- other characters must be escaped local segment_set = make_set { - "-", "_", ".", "!", "~", "*", "'", "(", - ")", ":", "@", "&", "=", "+", "$", ",", + "-", "_", ".", "!", "~", "*", "'", "(", + ")", ":", "@", "&", "=", "+", "$", ",", } local function protect_segment(s) - return string.gsub(s, "([^A-Za-z0-9_])", function (c) - if segment_set[c] then return c - else return string.format("%%%02x", string.byte(c)) end - end) + return string.gsub(s, "([^A-Za-z0-9_])", function(c) + if segment_set[c] then + return c + else + return string.format("%%%02x", string.byte(c)) + end + end) end ----------------------------------------------------------------------------- @@ -69,9 +72,9 @@ end -- escaped representation of string binary ----------------------------------------------------------------------------- function unescape(s) - return string.gsub(s, "%%(%x%x)", function(hex) - return string.char(base.tonumber(hex, 16)) - end) + return string.gsub(s, "%%(%x%x)", function(hex) + return string.char(base.tonumber(hex, 16)) + end) end ----------------------------------------------------------------------------- @@ -83,24 +86,24 @@ end -- corresponding absolute path ----------------------------------------------------------------------------- local function absolute_path(base_path, relative_path) - if string.sub(relative_path, 1, 1) == "/" then return relative_path end - local path = string.gsub(base_path, "[^/]*$", "") - path = path .. relative_path - path = string.gsub(path, "([^/]*%./)", function (s) - if s ~= "./" then return s else return "" end + if string.sub(relative_path, 1, 1) == "/" then return relative_path end + local path = string.gsub(base_path, "[^/]*$", "") + path = path .. relative_path + path = string.gsub(path, "([^/]*%./)", function(s) + if s ~= "./" then return s else return "" end + end) + path = string.gsub(path, "/%.$", "/") + local reduced + while reduced ~= path do + reduced = path + path = string.gsub(reduced, "([^/]*/%.%./)", function(s) + if s ~= "../../" then return "" else return s end end) - path = string.gsub(path, "/%.$", "/") - local reduced - while reduced ~= path do - reduced = path - path = string.gsub(reduced, "([^/]*/%.%./)", function (s) - if s ~= "../../" then return "" else return s end - end) - end - path = string.gsub(reduced, "([^/]*/%.%.)$", function (s) - if s ~= "../.." then return "" else return s end - end) - return path + end + path = string.gsub(reduced, "([^/]*/%.%.)$", function(s) + if s ~= "../.." then return "" else return s end + end) + return path end ----------------------------------------------------------------------------- @@ -122,51 +125,59 @@ end -- the leading '/' in {/} is considered part of ----------------------------------------------------------------------------- function parse(url, default) - -- initialize default parameters - local parsed = {} - for i,v in base.pairs(default or parsed) do parsed[i] = v end - -- empty url is parsed to nil - if not url or url == "" then return nil, "invalid url" end - -- remove whitespace - -- url = string.gsub(url, "%s", "") - -- get fragment - url = string.gsub(url, "#(.*)$", function(f) - parsed.fragment = f - return "" + -- initialize default parameters + local parsed = {} + for i, v in base.pairs(default or parsed) do parsed[i] = v end + -- empty url is parsed to nil + if not url or url == "" then return nil, "invalid url" end + -- remove whitespace + -- url = string.gsub(url, "%s", "") + -- get fragment + url = string.gsub(url, "#(.*)$", function(f) + parsed.fragment = f + return "" + end) + -- get scheme + url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", + function(s) + parsed.scheme = s; return "" end) - -- get scheme - url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", - function(s) parsed.scheme = s; return "" end) - -- get authority - url = string.gsub(url, "^//([^/]*)", function(n) - parsed.authority = n - return "" + -- get authority + url = string.gsub(url, "^//([^/]*)", function(n) + parsed.authority = n + return "" + end) + -- get query stringing + url = string.gsub(url, "%?(.*)", function(q) + parsed.query = q + return "" + end) + -- get params + url = string.gsub(url, "%;(.*)", function(p) + parsed.params = p + return "" + end) + -- path is whatever was left + if url ~= "" then parsed.path = url end + local authority = parsed.authority + if not authority then return parsed end + authority = string.gsub(authority, "^([^@]*)@", + function(u) + parsed.userinfo = u; return "" end) - -- get query stringing - url = string.gsub(url, "%?(.*)", function(q) - parsed.query = q - return "" + authority = string.gsub(authority, ":([^:]*)$", + function(p) + parsed.port = p; return "" end) - -- get params - url = string.gsub(url, "%;(.*)", function(p) - parsed.params = p - return "" + if authority ~= "" then parsed.host = authority end + local userinfo = parsed.userinfo + if not userinfo then return parsed end + userinfo = string.gsub(userinfo, ":([^:]*)$", + function(p) + parsed.password = p; return "" end) - -- path is whatever was left - if url ~= "" then parsed.path = url end - local authority = parsed.authority - if not authority then return parsed end - authority = string.gsub(authority,"^([^@]*)@", - function(u) parsed.userinfo = u; return "" end) - authority = string.gsub(authority, ":([^:]*)$", - function(p) parsed.port = p; return "" end) - if authority ~= "" then parsed.host = authority end - local userinfo = parsed.userinfo - if not userinfo then return parsed end - userinfo = string.gsub(userinfo, ":([^:]*)$", - function(p) parsed.password = p; return "" end) - parsed.user = userinfo - return parsed + parsed.user = userinfo + return parsed end ----------------------------------------------------------------------------- @@ -178,28 +189,28 @@ end -- a stringing with the corresponding URL ----------------------------------------------------------------------------- function build(parsed) - local ppath = parse_path(parsed.path or "") - local url = build_path(ppath) - if parsed.params then url = url .. ";" .. parsed.params end - if parsed.query then url = url .. "?" .. parsed.query end - local authority = parsed.authority - if parsed.host then - authority = parsed.host - if parsed.port then authority = authority .. ":" .. parsed.port end - local userinfo = parsed.userinfo - if parsed.user then - userinfo = parsed.user - if parsed.password then - userinfo = userinfo .. ":" .. parsed.password - end - end - if userinfo then authority = userinfo .. "@" .. authority end - end - if authority then url = "//" .. authority .. url end - if parsed.scheme then url = parsed.scheme .. ":" .. url end - if parsed.fragment then url = url .. "#" .. parsed.fragment end - -- url = string.gsub(url, "%s", "") - return url + local ppath = parse_path(parsed.path or "") + local url = build_path(ppath) + if parsed.params then url = url .. ";" .. parsed.params end + if parsed.query then url = url .. "?" .. parsed.query end + local authority = parsed.authority + if parsed.host then + authority = parsed.host + if parsed.port then authority = authority .. ":" .. parsed.port end + local userinfo = parsed.userinfo + if parsed.user then + userinfo = parsed.user + if parsed.password then + userinfo = userinfo .. ":" .. parsed.password + end + end + if userinfo then authority = userinfo .. "@" .. authority end + end + if authority then url = "//" .. authority .. url end + if parsed.scheme then url = parsed.scheme .. ":" .. url end + if parsed.fragment then url = url .. "#" .. parsed.fragment end + -- url = string.gsub(url, "%s", "") + return url end ----------------------------------------------------------------------------- @@ -211,35 +222,38 @@ end -- corresponding absolute url ----------------------------------------------------------------------------- function absolute(base_url, relative_url) - if base.type(base_url) == "table" then - base_parsed = base_url - base_url = build(base_parsed) - else - base_parsed = parse(base_url) - end - local relative_parsed = parse(relative_url) - if not base_parsed then return relative_url - elseif not relative_parsed then return base_url - elseif relative_parsed.scheme then return relative_url - else - relative_parsed.scheme = base_parsed.scheme - if not relative_parsed.authority then - relative_parsed.authority = base_parsed.authority - if not relative_parsed.path then - relative_parsed.path = base_parsed.path - if not relative_parsed.params then - relative_parsed.params = base_parsed.params - if not relative_parsed.query then - relative_parsed.query = base_parsed.query - end - end - else - relative_parsed.path = absolute_path(base_parsed.path or "", - relative_parsed.path) - end + if base.type(base_url) == "table" then + base_parsed = base_url + base_url = build(base_parsed) + else + base_parsed = parse(base_url) + end + local relative_parsed = parse(relative_url) + if not base_parsed then + return relative_url + elseif not relative_parsed then + return base_url + elseif relative_parsed.scheme then + return relative_url + else + relative_parsed.scheme = base_parsed.scheme + if not relative_parsed.authority then + relative_parsed.authority = base_parsed.authority + if not relative_parsed.path then + relative_parsed.path = base_parsed.path + if not relative_parsed.params then + relative_parsed.params = base_parsed.params + if not relative_parsed.query then + relative_parsed.query = base_parsed.query + end end - return build(relative_parsed) + else + relative_parsed.path = absolute_path(base_parsed.path or "", + relative_parsed.path) + end end + return build(relative_parsed) + end end ----------------------------------------------------------------------------- @@ -250,16 +264,16 @@ end -- segment: a table with one entry per segment ----------------------------------------------------------------------------- function parse_path(path) - local parsed = {} - path = path or "" - --path = string.gsub(path, "%s", "") - string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) - for i = 1, #parsed do - parsed[i] = unescape(parsed[i]) - end - if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end - if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end - return parsed + local parsed = {} + path = path or "" + --path = string.gsub(path, "%s", "") + string.gsub(path, "([^/]+)", function(s) table.insert(parsed, s) end) + for i = 1, #parsed do + parsed[i] = unescape(parsed[i]) + end + if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end + if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end + return parsed end ----------------------------------------------------------------------------- @@ -271,27 +285,27 @@ end -- path: corresponding path stringing ----------------------------------------------------------------------------- function build_path(parsed, unsafe) - local path = "" - local n = #parsed - if unsafe then - for i = 1, n-1 do - path = path .. parsed[i] - path = path .. "/" - end - if n > 0 then - path = path .. parsed[n] - if parsed.is_directory then path = path .. "/" end - end - else - for i = 1, n-1 do - path = path .. protect_segment(parsed[i]) - path = path .. "/" - end - if n > 0 then - path = path .. protect_segment(parsed[n]) - if parsed.is_directory then path = path .. "/" end - end - end - if parsed.is_absolute then path = "/" .. path end - return path + local path = "" + local n = #parsed + if unsafe then + for i = 1, n - 1 do + path = path .. parsed[i] + path = path .. "/" + end + if n > 0 then + path = path .. parsed[n] + if parsed.is_directory then path = path .. "/" end + end + else + for i = 1, n - 1 do + path = path .. protect_segment(parsed[i]) + path = path .. "/" + end + if n > 0 then + path = path .. protect_segment(parsed[n]) + if parsed.is_directory then path = path .. "/" end + end + end + if parsed.is_absolute then path = "/" .. path end + return path end diff --git a/src/lua-env-safe.lua b/src/lua-env-safe.lua index c64a30e..aa46dad 100644 --- a/src/lua-env-safe.lua +++ b/src/lua-env-safe.lua @@ -1,10 +1,12 @@ -- Sanitize the Lua environment to: -- Prevent scripts from accessing files outside the game directory --- Prevent usage of libraries other than +-- Prevent usage of libraries other than -- Utility: Convert a list of strings into a map of string->true -local function tmap(t) local u = {}; for _, n in ipairs(t) do u[n] = true end; return u; end +local function tmap(t) + local u = {}; for _, n in ipairs(t) do u[n] = true end; return u; +end -- Save banned global variables for wrapping with safe functions local old_io = io @@ -15,98 +17,99 @@ local old_package = package -- Remove all global variables except a whitelist local ok_names = tmap { - '_G', '_bllua_ts', '_bllua_on_unload', '_bllua_on_error', - 'string', 'table', 'math', 'coroutine', 'bit', - 'pairs', 'ipairs', 'next', 'unpack', 'select', - 'error', 'assert', 'pcall', 'xpcall', - 'type', 'tostring', 'tonumber', - 'loadstring', - 'getmetatable', 'setmetatable', - 'rawget', 'rawset', 'rawequal', 'rawlen', - 'module', '_VERSION', + '_G', '_bllua_ts', '_bllua_on_unload', '_bllua_on_error', + 'string', 'table', 'math', 'coroutine', 'bit', + 'pairs', 'ipairs', 'next', 'unpack', 'select', + 'error', 'assert', 'pcall', 'xpcall', + 'type', 'tostring', 'tonumber', + 'loadstring', + 'getmetatable', 'setmetatable', + 'rawget', 'rawset', 'rawequal', 'rawlen', + 'module', '_VERSION', } local not_ok_names = {} for n, _ in pairs(_G) do - if not ok_names[n] then - table.insert(not_ok_names, n) - end + if not ok_names[n] then + table.insert(not_ok_names, n) + end end for _, n in ipairs(not_ok_names) do - _G[n] = nil + _G[n] = nil end -- Sanitize file paths to point only to allowed files within the game directory -- List of allowed directories for reading/writing local allowed_dirs = tmap { - 'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders' + 'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders' } -- List of allowed directories for reading only local allowed_dirs_readonly = tmap { - 'lualib' + 'lualib' } -- List of disallowed file extensions - basically executable file extensions -- Note that even without this protection, exploiting would still require somehow -- getting a file within the allowed directories to autorun, -- so this is just a precaution. local disallowed_exts = tmap { - -- windows - 'bat','bin','cab','cmd','com','cpl','ex_','exe','gadget','inf','ins','inx','isu', - 'job','jse','lnk','msc','msi','msp','mst','paf','pif','ps1','reg','rgs','scr', - 'sct','shb','shs','u3p','vb','vbe','vbs','vbscript','ws','wsf','wsh', - -- linux - 'csh','ksh','out','run','sh', - -- mac/other - 'action','apk','app','command','ipa','osx','prg','workflow', + -- windows + 'bat', 'bin', 'cab', 'cmd', 'com', 'cpl', 'ex_', 'exe', 'gadget', 'inf', 'ins', 'inx', 'isu', + 'job', 'jse', 'lnk', 'msc', 'msi', 'msp', 'mst', 'paf', 'pif', 'ps1', 'reg', 'rgs', 'scr', + 'sct', 'shb', 'shs', 'u3p', 'vb', 'vbe', 'vbs', 'vbscript', 'ws', 'wsf', 'wsh', + -- linux + 'csh', 'ksh', 'out', 'run', 'sh', + -- mac/other + 'action', 'apk', 'app', 'command', 'ipa', 'osx', 'prg', 'workflow', } -- Arguments: file name (relative to game directory), boolean true if only reading -- Return: clean file path if allowed (or nil if disallowed), -- error string (or nil if allowed) local function safe_path(fn, readonly) - fn = fn:gsub('\\', '/') - fn = fn:gsub('^ +', '') - fn = fn:gsub(' +$', '') - -- whitelist characters - local ic = fn:find('[^a-zA-Z0-9_%-/ %.]') - if ic then - return nil, 'Filename \''..fn..'\' contains invalid character \''.. - fn:sub(ic, ic)..'\' at position '..ic - end - -- disallow up-dirs, absolute paths, and relative paths - -- './' and '../' are possible in scripts, because they're processed into - -- absolute paths in util.lua before reaching here - if fn:find('^%.') or fn:find('%.%.') or fn:find(':') or fn:find('^/') then - return nil, 'Filename \''..fn..'\' contains invalid sequence' - end - -- allow only whitelisted dirs - local dir = fn:match('^([^/]+)/') - if (not dir) or ( - (not allowed_dirs[dir:lower()]) and - ((not readonly) or (not allowed_dirs_readonly[dir:lower()])) ) then - return nil, 'filename is in disallowed directory '..(dir or 'nil') - end - -- disallow blacklisted extensions or no extension - local ext = fn:match('%.([^/%.]+)$') - if (not ext) or (disallowed_exts[ext:lower()]) then - return nil, 'Filename \''..fn..'\' has disallowed extension \''.. - (ext or '')..'\'' - end - return fn, nil + fn = fn:gsub('\\', '/') + fn = fn:gsub('^ +', '') + fn = fn:gsub(' +$', '') + -- whitelist characters + local ic = fn:find('[^a-zA-Z0-9_%-/ %.]') + if ic then + return nil, 'Filename \'' .. fn .. '\' contains invalid character \'' .. + fn:sub(ic, ic) .. '\' at position ' .. ic + end + -- disallow up-dirs, absolute paths, and relative paths + -- './' and '../' are possible in scripts, because they're processed into + -- absolute paths in util.lua before reaching here + if fn:find('^%.') or fn:find('%.%.') or fn:find(':') or fn:find('^/') then + return nil, 'Filename \'' .. fn .. '\' contains invalid sequence' + end + -- allow only whitelisted dirs + local dir = fn:match('^([^/]+)/') + if (not dir) or ( + (not allowed_dirs[dir:lower()]) and + ((not readonly) or (not allowed_dirs_readonly[dir:lower()]))) then + return nil, 'filename is in disallowed directory ' .. (dir or 'nil') + end + -- disallow blacklisted extensions or no extension + local ext = fn:match('%.([^/%.]+)$') + if (not ext) or (disallowed_exts[ext:lower()]) then + return nil, 'Filename \'' .. fn .. '\' has disallowed extension \'' .. + (ext or '') .. '\'' + end + return fn, nil end -- Wrap io.open with path sanitization function _bllua_io_open(fn, md) - md = md or 'r' - local readonly = md=='r' or md=='rb' - local fns, err = safe_path(fn, readonly) - if fns then - return old_io.open(fns, md) - else - return nil, err - end + md = md or 'r' + local readonly = md == 'r' or md == 'rb' + local fns, err = safe_path(fn, readonly) + if fns then + return old_io.open(fns, md) + else + return nil, err + end end + -- Allow io.type (works on file handles returned by io.open) function _bllua_io_type(f) - return old_io.type(f) + return old_io.type(f) end -- Wrap require with a blacklist for unsafe built-in modules @@ -114,29 +117,30 @@ end -- Note that util.lua wraps this and provides 'require', -- only falling back here if the package is not found in user files local disallowed_packages = tmap { - 'ffi', 'debug', 'package', 'io', 'os', - '_bllua_ts', + 'ffi', 'debug', 'package', 'io', 'os', + '_bllua_ts', } function _bllua_requiresecure(name) - if name:find('[^a-zA-Z0-9_%-%.]') or name:find('%.%.') or - name:find('^%.') or name:find('%.$') then - error('require: package name contains invalid character', 3) - elseif disallowed_packages[name] then - error('require: attempt to require disallowed module \''..name..'\'', 3) - else - -- todo: reimplement require to not use package.* stuff? - return old_require(name) - end + if name:find('[^a-zA-Z0-9_%-%.]') or name:find('%.%.') or + name:find('^%.') or name:find('%.$') then + error('require: package name contains invalid character', 3) + elseif disallowed_packages[name] then + error('require: attempt to require disallowed module \'' .. name .. '\'', 3) + else + -- todo: reimplement require to not use package.* stuff? + return old_require(name) + end end + package = { - seeall = old_package.seeall, + seeall = old_package.seeall, } -- Provide limited debug debug = { - traceback = old_debug.traceback, - getinfo = old_debug.getinfo, - getfilename = old_debug.getfilename, -- defined in lua.env.lua + traceback = old_debug.traceback, + getinfo = old_debug.getinfo, + getfilename = old_debug.getfilename, -- defined in lua.env.lua } _bllua_ts.echo(' Executed bllua-env-safe.lua') diff --git a/src/lua-env.lua b/src/lua-env.lua index f485ee2..618fec4 100644 --- a/src/lua-env.lua +++ b/src/lua-env.lua @@ -9,32 +9,32 @@ _bllua_on_unload = {} -- Utility for getting the current filename function debug.getfilename(level) - if type(level) == 'number' then level = level+1 end - local info = debug.getinfo(level) - if not info then return nil end - local filename = info.source:match('^%-%-%[%[([^%]]+)%]%]') - return filename + if type(level) == 'number' then level = level + 1 end + local info = debug.getinfo(level) + if not info then return nil end + local filename = info.source:match('^%-%-%[%[([^%]]+)%]%]') + return filename end -- Called when pcall fails on a ts->lua call, used to print detailed error info function _bllua_on_error(err) - err = err:match(': (.+)$') or err - local tracelines = {err} - local level = 2 - while true do - local info = debug.getinfo(level) - if not info then break end - local filename = debug.getfilename(level) or info.short_src - local funcname = info.name - if funcname=='dofile' then break end - table.insert(tracelines, string.format('%s:%s in function \'%s\'', - filename, - info.currentline==-1 and '' or info.currentline..':', - funcname - )) - level = level+1 - end - return table.concat(tracelines, '\n') + err = err:match(': (.+)$') or err + local tracelines = { err } + local level = 2 + while true do + local info = debug.getinfo(level) + if not info then break end + local filename = debug.getfilename(level) or info.short_src + local funcname = info.name + if funcname == 'dofile' then break end + table.insert(tracelines, string.format('%s:%s in function \'%s\'', + filename, + info.currentline == -1 and '' or info.currentline .. ':', + funcname + )) + level = level + 1 + end + return table.concat(tracelines, '\n') end _bllua_ts.echo(' Executed bllua-env.lua') diff --git a/src/util/libbl-types.lua b/src/util/libbl-types.lua index 26deec1..b531bf7 100644 --- a/src/util/libbl-types.lua +++ b/src/util/libbl-types.lua @@ -4,129 +4,129 @@ -- Class hierarchy, adapted from https://notabug.org/Queuenard/blockland-DLL-tools/src/master/class_hierarchy bl.class('SimObject') - bl.class('ScriptObject', 'SimObject') - bl.class('SimSet', 'SimObject') - bl.class('SimGroup', 'SimSet') - bl.class('GuiControl', 'SimGroup') - bl.class('GuiTextCtrl' , 'GuiControl') - bl.class('GuiSwatchCtrl' , 'GuiControl') - bl.class('GuiButtonBaseCtrl' , 'GuiControl') - bl.class('GuiArrayCtrl' , 'GuiControl') - bl.class('GuiScrollCtrl' , 'GuiControl') - bl.class('GuiMouseEventCtrl' , 'GuiControl') - bl.class('GuiProgressCtrl' , 'GuiControl') - bl.class('GuiSliderCtrl' , 'GuiControl') - bl.class('GuiConsoleTextCtrl' , 'GuiControl') - bl.class('GuiTSCtrl' , 'GuiControl') - bl.class('GuiObjectView', 'GuiTSCtrl') - bl.class('GameTSCtrl' , 'GuiTSCtrl') - bl.class('EditTSCtrl' , 'GuiTSCtrl') - bl.class('GuiPlayerView', 'GuiTSCtrl') - bl.class('GuiShapeNameHud' , 'GuiControl') - bl.class('GuiHealthBarHud' , 'GuiControl') - bl.class('GuiGraphCtrl' , 'GuiControl') - bl.class('GuiInspector' , 'GuiControl') - bl.class('GuiChunkedBitmapCtrl', 'GuiControl') - bl.class('GuiInputCtrl' , 'GuiControl') - bl.class('GuiNoMouseCtrl' , 'GuiControl') - bl.class('GuiBitmapBorderCtrl' , 'GuiControl') - bl.class('GuiBackgroundCtrl' , 'GuiControl') - bl.class('GuiEditorRuler' , 'GuiControl') - bl.class('GuiClockHud' , 'GuiControl') - bl.class('GuiEditCtrl' , 'GuiControl') - bl.class('GuiFilterCtrl' , 'GuiControl') - bl.class('GuiFrameSetCtrl' , 'GuiControl') - bl.class('GuiMenuBar' , 'GuiControl') - bl.class('GuiMessageVectorCtrl', 'GuiControl') - bl.class('GuiBitmapCtrl' , 'GuiControl') - bl.class('GuiCrossHairHud', 'GuiBitmapCtrl') - bl.class('ScriptGroup', 'SimGroup') - bl.class('NetConnection', 'SimGroup') - bl.class('GameConnection', 'NetConnection') - bl.class('Path', 'SimGroup') - bl.class('TCPObject', 'SimObject') - bl.class('SOCKObject', 'TCPObject') - bl.class('HTTPObject', 'TCPObject') - bl.class('SimDataBlock', 'SimObject') - bl.class('AudioEnvironment' , 'SimDataBlock') - bl.class('AudioSampleEnvironment', 'SimDataBlock') - bl.class('AudioDescription' , 'SimDataBlock') - bl.class('GameBaseData' , 'SimDataBlock') - bl.class('ShapeBaseData' , 'GameBaseData') - bl.class('CameraData' , 'ShapeBaseData') - bl.class('ItemData' , 'ShapeBaseData') - bl.class('MissionMarkerData', 'ShapeBaseData') - bl.class('PathCameraData' , 'ShapeBaseData') - bl.class('PlayerData' , 'ShapeBaseData') - bl.class('StaticShapeData' , 'ShapeBaseData') - bl.class('VehicleData' , 'ShapeBaseData') - bl.class('FlyingVehicleData' , 'VehicleData') - bl.class('WheeledVehicleData', 'VehicleData') - bl.class('DebrisData' , 'GameBaseData') - bl.class('ProjectileData' , 'GameBaseData') - bl.class('ShapeBaseImageData' , 'GameBaseData') - bl.class('TriggerData' , 'GameBaseData') - bl.class('ExplosionData' , 'GameBaseData') - bl.class('fxLightData' , 'GameBaseData') - bl.class('LightningData' , 'GameBaseData') - bl.class('ParticleEmitterNodeData', 'GameBaseData') - bl.class('SplashData' , 'GameBaseData') - bl.class('fxDTSBrickData' , 'GameBaseData') - bl.class('ParticleEmitterData' , 'GameBaseData') - bl.class('WheeledVehicleTire' , 'SimDataBlock') - bl.class('WheeledVehicleSpring' , 'SimDataBlock') - bl.class('TSShapeConstructor' , 'SimDataBlock') - bl.class('AudioProfile' , 'SimDataBlock') - bl.class('ParticleData' , 'SimDataBlock') - bl.class('MaterialPropertyMap', 'SimObject') - bl.class('NetObject', 'SimObject') - bl.class('SceneObject', 'NetObject') - bl.class('GameBase', 'SceneObject') - bl.class('ShapeBase', 'GameBase') - bl.class('MissionMarker', 'ShapeBase') - bl.class('SpawnSphere' , 'MissionMarker') - bl.class('VehicleSpawnMarker', 'MissionMarker') - bl.class('Waypoint' , 'MissionMarker') - bl.class('StaticShape' , 'ShapeBase') - bl.class('ScopeAlwaysShape', 'StaticShape') - bl.class('Player' , 'ShapeBase') - bl.class('AIPlayer', 'Player') - bl.class('Camera' , 'ShapeBase') - bl.class('Item' , 'ShapeBase') - bl.class('PathCamera' , 'ShapeBase') - bl.class('Vehicle' , 'ShapeBase') - bl.class('FlyingVehicle' , 'Vehicle') - bl.class('WheeledVehicle', 'Vehicle') - bl.class('Explosion' , 'GameBase') - bl.class('Splash' , 'GameBase') - bl.class('Debris' , 'GameBase') - bl.class('Projectile' , 'GameBase') - bl.class('Trigger' , 'GameBase') - bl.class('fxLight' , 'GameBase') - bl.class('Lightning' , 'GameBase') - bl.class('ParticleEmitterNode', 'GameBase') - bl.class('ParticleEmitter' , 'GameBase') - bl.class('Precipitation' , 'GameBase') - bl.class('TSStatic' , 'SceneObject') - bl.class('VehicleBlocker', 'SceneObject') - bl.class('Marker' , 'SceneObject') - bl.class('AudioEmitter' , 'SceneObject') - bl.class('PhysicalZone' , 'SceneObject') - bl.class('fxDayCycle' , 'SceneObject') - bl.class('fxDTSBrick' , 'SceneObject') - bl.class('fxPlane' , 'SceneObject') - bl.class('fxSunLight' , 'SceneObject') - bl.class('Sky' , 'SceneObject') - bl.class('SceneRoot' , 'SceneObject') - bl.class('Sun', 'NetObject') - bl.class('GuiCursor', 'SimObject') - bl.class('ConsoleLogger' , 'SimObject') - bl.class('QuotaObject' , 'SimObject') - bl.class('FileObject' , 'SimObject') - bl.class('BanList' , 'SimObject') - bl.class('GuiControlProfile', 'SimObject') - bl.class('MessageVector' , 'SimObject') - bl.class('ActionMap' , 'SimObject') +bl.class('ScriptObject', 'SimObject') +bl.class('SimSet', 'SimObject') +bl.class('SimGroup', 'SimSet') +bl.class('GuiControl', 'SimGroup') +bl.class('GuiTextCtrl', 'GuiControl') +bl.class('GuiSwatchCtrl', 'GuiControl') +bl.class('GuiButtonBaseCtrl', 'GuiControl') +bl.class('GuiArrayCtrl', 'GuiControl') +bl.class('GuiScrollCtrl', 'GuiControl') +bl.class('GuiMouseEventCtrl', 'GuiControl') +bl.class('GuiProgressCtrl', 'GuiControl') +bl.class('GuiSliderCtrl', 'GuiControl') +bl.class('GuiConsoleTextCtrl', 'GuiControl') +bl.class('GuiTSCtrl', 'GuiControl') +bl.class('GuiObjectView', 'GuiTSCtrl') +bl.class('GameTSCtrl', 'GuiTSCtrl') +bl.class('EditTSCtrl', 'GuiTSCtrl') +bl.class('GuiPlayerView', 'GuiTSCtrl') +bl.class('GuiShapeNameHud', 'GuiControl') +bl.class('GuiHealthBarHud', 'GuiControl') +bl.class('GuiGraphCtrl', 'GuiControl') +bl.class('GuiInspector', 'GuiControl') +bl.class('GuiChunkedBitmapCtrl', 'GuiControl') +bl.class('GuiInputCtrl', 'GuiControl') +bl.class('GuiNoMouseCtrl', 'GuiControl') +bl.class('GuiBitmapBorderCtrl', 'GuiControl') +bl.class('GuiBackgroundCtrl', 'GuiControl') +bl.class('GuiEditorRuler', 'GuiControl') +bl.class('GuiClockHud', 'GuiControl') +bl.class('GuiEditCtrl', 'GuiControl') +bl.class('GuiFilterCtrl', 'GuiControl') +bl.class('GuiFrameSetCtrl', 'GuiControl') +bl.class('GuiMenuBar', 'GuiControl') +bl.class('GuiMessageVectorCtrl', 'GuiControl') +bl.class('GuiBitmapCtrl', 'GuiControl') +bl.class('GuiCrossHairHud', 'GuiBitmapCtrl') +bl.class('ScriptGroup', 'SimGroup') +bl.class('NetConnection', 'SimGroup') +bl.class('GameConnection', 'NetConnection') +bl.class('Path', 'SimGroup') +bl.class('TCPObject', 'SimObject') +bl.class('SOCKObject', 'TCPObject') +bl.class('HTTPObject', 'TCPObject') +bl.class('SimDataBlock', 'SimObject') +bl.class('AudioEnvironment', 'SimDataBlock') +bl.class('AudioSampleEnvironment', 'SimDataBlock') +bl.class('AudioDescription', 'SimDataBlock') +bl.class('GameBaseData', 'SimDataBlock') +bl.class('ShapeBaseData', 'GameBaseData') +bl.class('CameraData', 'ShapeBaseData') +bl.class('ItemData', 'ShapeBaseData') +bl.class('MissionMarkerData', 'ShapeBaseData') +bl.class('PathCameraData', 'ShapeBaseData') +bl.class('PlayerData', 'ShapeBaseData') +bl.class('StaticShapeData', 'ShapeBaseData') +bl.class('VehicleData', 'ShapeBaseData') +bl.class('FlyingVehicleData', 'VehicleData') +bl.class('WheeledVehicleData', 'VehicleData') +bl.class('DebrisData', 'GameBaseData') +bl.class('ProjectileData', 'GameBaseData') +bl.class('ShapeBaseImageData', 'GameBaseData') +bl.class('TriggerData', 'GameBaseData') +bl.class('ExplosionData', 'GameBaseData') +bl.class('fxLightData', 'GameBaseData') +bl.class('LightningData', 'GameBaseData') +bl.class('ParticleEmitterNodeData', 'GameBaseData') +bl.class('SplashData', 'GameBaseData') +bl.class('fxDTSBrickData', 'GameBaseData') +bl.class('ParticleEmitterData', 'GameBaseData') +bl.class('WheeledVehicleTire', 'SimDataBlock') +bl.class('WheeledVehicleSpring', 'SimDataBlock') +bl.class('TSShapeConstructor', 'SimDataBlock') +bl.class('AudioProfile', 'SimDataBlock') +bl.class('ParticleData', 'SimDataBlock') +bl.class('MaterialPropertyMap', 'SimObject') +bl.class('NetObject', 'SimObject') +bl.class('SceneObject', 'NetObject') +bl.class('GameBase', 'SceneObject') +bl.class('ShapeBase', 'GameBase') +bl.class('MissionMarker', 'ShapeBase') +bl.class('SpawnSphere', 'MissionMarker') +bl.class('VehicleSpawnMarker', 'MissionMarker') +bl.class('Waypoint', 'MissionMarker') +bl.class('StaticShape', 'ShapeBase') +bl.class('ScopeAlwaysShape', 'StaticShape') +bl.class('Player', 'ShapeBase') +bl.class('AIPlayer', 'Player') +bl.class('Camera', 'ShapeBase') +bl.class('Item', 'ShapeBase') +bl.class('PathCamera', 'ShapeBase') +bl.class('Vehicle', 'ShapeBase') +bl.class('FlyingVehicle', 'Vehicle') +bl.class('WheeledVehicle', 'Vehicle') +bl.class('Explosion', 'GameBase') +bl.class('Splash', 'GameBase') +bl.class('Debris', 'GameBase') +bl.class('Projectile', 'GameBase') +bl.class('Trigger', 'GameBase') +bl.class('fxLight', 'GameBase') +bl.class('Lightning', 'GameBase') +bl.class('ParticleEmitterNode', 'GameBase') +bl.class('ParticleEmitter', 'GameBase') +bl.class('Precipitation', 'GameBase') +bl.class('TSStatic', 'SceneObject') +bl.class('VehicleBlocker', 'SceneObject') +bl.class('Marker', 'SceneObject') +bl.class('AudioEmitter', 'SceneObject') +bl.class('PhysicalZone', 'SceneObject') +bl.class('fxDayCycle', 'SceneObject') +bl.class('fxDTSBrick', 'SceneObject') +bl.class('fxPlane', 'SceneObject') +bl.class('fxSunLight', 'SceneObject') +bl.class('Sky', 'SceneObject') +bl.class('SceneRoot', 'SceneObject') +bl.class('Sun', 'NetObject') +bl.class('GuiCursor', 'SimObject') +bl.class('ConsoleLogger', 'SimObject') +bl.class('QuotaObject', 'SimObject') +bl.class('FileObject', 'SimObject') +bl.class('BanList', 'SimObject') +bl.class('GuiControlProfile', 'SimObject') +bl.class('MessageVector', 'SimObject') +bl.class('ActionMap', 'SimObject') -- Auto-generated from game scripts bl.type('ActionMap::blockBind:1', 'object') diff --git a/src/util/libbl.lua b/src/util/libbl.lua index 62ea1a6..83e8f6a 100644 --- a/src/util/libbl.lua +++ b/src/util/libbl.lua @@ -10,182 +10,188 @@ bl = bl or {} -- Misc -- Apply a function to each element in a list, building a new list from the returns -local function map(t,f) - local u = {} - for i,v in ipairs(t) do - u[i] = f(v) - end - return u +local function map(t, f) + local u = {} + for i, v in ipairs(t) do + u[i] = f(v) + end + return u end local function isValidFuncName(name) - return type(name)=='string' and name:find('^[a-zA-Z0-9_]+$') + return type(name) == 'string' and name:find('^[a-zA-Z0-9_]+$') end local function isValidFuncNameNs(name) - return type(name)=='string' and ( - name:find('^[a-zA-Z0-9_]+$') or - name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+$') ) + return type(name) == 'string' and ( + name:find('^[a-zA-Z0-9_]+$') or + name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+$')) end local function isValidFuncNameNsArgn(name) - return type(name)=='string' and ( - name:find('^[a-zA-Z0-9_]+$') or - name:find('^[a-zA-Z0-9_]+%.[a-zA-Z0-9_]+$') or - name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+$') or - name:find('^[a-zA-Z0-9_]+:[0-9]+$') or - name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+:[0-9]+$') ) + return type(name) == 'string' and ( + name:find('^[a-zA-Z0-9_]+$') or + name:find('^[a-zA-Z0-9_]+%.[a-zA-Z0-9_]+$') or + name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+$') or + name:find('^[a-zA-Z0-9_]+:[0-9]+$') or + name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+:[0-9]+$')) end -- Whether a var can be converted into a TS vector local function isTsVector(val) - if type(val)~='table' then return false end - if #val~=3 and #val~=2 then return false end - if val.__is_vector then return true end - for _,v in ipairs(val) do - if type(v)~='number' then return false end - end - return true + if type(val) ~= 'table' then return false end + if #val ~= 3 and #val ~= 2 then return false end + if val.__is_vector then return true end + for _, v in ipairs(val) do + if type(v) ~= 'number' then return false end + end + return true end -- Use strings for object types instead of integer bitmasks like in TS local tsTypesByName = { - ['all'] = -1, - ['static'] = 1, - ['environment'] = 2, - ['terrain'] = 4, - ['water'] = 16, - ['trigger'] = 32, - ['marker'] = 64, - ['gamebase'] = 1024, - ['shapebase'] = 2048, - ['camera'] = 4096, - ['staticshape'] = 8192, - ['player'] = 16384, - ['item'] = 32768, - ['vehicle'] = 65536, - ['vehicleblocker'] = 131072, - ['projectile'] = 262144, - ['explosion'] = 524288, - ['corpse'] = 1048576, - ['debris'] = 4194304, - ['physicalzone'] = 8388608, - ['staticts'] = 16777216, - ['brick'] = 33554432, - ['brickalways'] = 67108864, - ['staticrendered'] = 134217728, - ['damagableitem'] = 268435456, + ['all'] = -1, + ['static'] = 1, + ['environment'] = 2, + ['terrain'] = 4, + ['water'] = 16, + ['trigger'] = 32, + ['marker'] = 64, + ['gamebase'] = 1024, + ['shapebase'] = 2048, + ['camera'] = 4096, + ['staticshape'] = 8192, + ['player'] = 16384, + ['item'] = 32768, + ['vehicle'] = 65536, + ['vehicleblocker'] = 131072, + ['projectile'] = 262144, + ['explosion'] = 524288, + ['corpse'] = 1048576, + ['debris'] = 4194304, + ['physicalzone'] = 8388608, + ['staticts'] = 16777216, + ['brick'] = 33554432, + ['brickalways'] = 67108864, + ['staticrendered'] = 134217728, + ['damagableitem'] = 268435456, } local tsTypesByNum = {} -for k,v in pairs(tsTypesByName) do - tsTypesByNum[v] = k +for k, v in pairs(tsTypesByName) do + tsTypesByNum[v] = k end -- Type conversion local toTsObject -- Convert a string from TS into a boolean -- Note: Nonempty nonnumeric strings evaluate to 1, unlike in TS -local function tsBool(v) return v~='' and v~='0' end +local function tsBool(v) return v ~= '' and v ~= '0' end -- Convert a Lua var into a TS string, or error if not possible local function valToTs(val) - if val==nil then -- nil -> '' - return '' - elseif type(val)=='boolean' then -- bool -> 0 or 1 - return val and '1' or '0' - elseif type(val)=='number' then -- number - return tostring(val) - elseif type(val)=='string' then -- string - return val - elseif type(val)=='table' then - if val._tsObjectId then -- object -> object id - return tostring(val._tsObjectId) - elseif isTsVector(val) then -- vector - > 3 numbers - return table.concat(val, ' ') - elseif #val==2 and isTsVector(val[1]) and isTsVector(val[2]) then - -- box - > 6 numbers - return table.concat(val[1], ' ')..' '..table.concat(val[2], ' ') - else - error('valToTs: could not convert table', 3) - end - else - error('valToTs: could not convert '..type(val), 3) - end + if val == nil then -- nil -> '' + return '' + elseif type(val) == 'boolean' then -- bool -> 0 or 1 + return val and '1' or '0' + elseif type(val) == 'number' then -- number + return tostring(val) + elseif type(val) == 'string' then -- string + return val + elseif type(val) == 'table' then + if val._tsObjectId then -- object -> object id + return tostring(val._tsObjectId) + elseif isTsVector(val) then -- vector - > 3 numbers + return table.concat(val, ' ') + elseif #val == 2 and isTsVector(val[1]) and isTsVector(val[2]) then + -- box - > 6 numbers + return table.concat(val[1], ' ') .. ' ' .. table.concat(val[2], ' ') + else + error('valToTs: could not convert table', 3) + end + else + error('valToTs: could not convert ' .. type(val), 3) + end end local function convertValFromTs(val, typ) - if typ=='boolean' then - return tsBool(val) - elseif typ=='object' then - return toTsObject(val) - else - error('valFromTs: invalid force type '..typ, 4) - end + if typ == 'boolean' then + return tsBool(val) + elseif typ == 'object' then + return toTsObject(val) + else + error('valFromTs: invalid force type ' .. typ, 4) + end end bl._forceType = bl._forceType or {} local function valFromTs(val, name, name2) -- todo: ensure name and name2 are already lowercase - if type(val)~='string' then - error('valFromTs: expected string, got '..type(val), 3) end - if name then - name = name:lower() - if bl._forceType[name] then - return convertValFromTs(val, bl._forceType[name]) - end - end - if name2 then - name2 = name2:lower() - if bl._forceType[name2] then - return convertValFromTs(val, bl._forceType[name2]) - end - end - -- '' -> nil - if val=='' then return nil end - -- number - local num = tonumber(val) - if num then return num end - -- vector - local xS,yS,zS = val:match('^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') - if xS then return vector{tonumber(xS),tonumber(yS),tonumber(zS)} end - local x1S,y1S,z1S,x2S,y2S,z2S = val:match( - '^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+) '.. - '(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') - -- box (2 vectors) - if x1S then return { - vector{tonumber(x1S),tonumber(y1S),tonumber(z1S)}, - vector{tonumber(x2S),tonumber(y2S),tonumber(z2S)} } end - -- string - return val + if type(val) ~= 'string' then + error('valFromTs: expected string, got ' .. type(val), 3) + end + if name then + name = name:lower() + if bl._forceType[name] then + return convertValFromTs(val, bl._forceType[name]) + end + end + if name2 then + name2 = name2:lower() + if bl._forceType[name2] then + return convertValFromTs(val, bl._forceType[name2]) + end + end + -- '' -> nil + if val == '' then return nil end + -- number + local num = tonumber(val) + if num then return num end + -- vector + local xS, yS, zS = val:match('^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') + if xS then return vector { tonumber(xS), tonumber(yS), tonumber(zS) } end + local x1S, y1S, z1S, x2S, y2S, z2S = val:match( + '^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+) ' .. + '(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') + -- box (2 vectors) + if x1S then + return { + vector { tonumber(x1S), tonumber(y1S), tonumber(z1S) }, + vector { tonumber(x2S), tonumber(y2S), tonumber(z2S) } } + end + -- string + return val end local function arglistFromTs(name, argsS) - local args = {} - for i,arg in ipairs(argsS) do - args[i] = valFromTs(arg, name..':'..i) - end - return args + local args = {} + for i, arg in ipairs(argsS) do + args[i] = valFromTs(arg, name .. ':' .. i) + end + return args end local function arglistToTs(args) - return map(args, valToTs) + return map(args, valToTs) end local function classFromForceTypeStr(name) - local class, rest = name:match('^([a-zA-Z0-9_]+)(::.+)$') - if not class then - class, rest = name:match('^([a-zA-Z0-9_]+)(%..+)$') end - return class,rest + local class, rest = name:match('^([a-zA-Z0-9_]+)(::.+)$') + if not class then + class, rest = name:match('^([a-zA-Z0-9_]+)(%..+)$') + end + return class, rest end local setForceType setForceType = function(ftname, typ) - if typ~='boolean' and typ~='object' and typ~=nil then - error('bl.type: can only set type to \'boolean\', \'object\', or nil', 2) end - if not isValidFuncNameNsArgn(ftname) then - error('bl.type: invalid function or variable name \''..ftname..'\'', 2) end - - ftname = ftname:lower() - - bl._forceType[ftname] = typ - - -- apply to child classes if present - local cname, rest = classFromForceTypeStr(ftname) - if cname then - local meta = bl._objectUserMetas[cname] - if meta then - for chcname,_ in pairs(meta._children) do - setForceType(chcname..rest, typ) - end - end - end + if typ ~= 'boolean' and typ ~= 'object' and typ ~= nil then + error('bl.type: can only set type to \'boolean\', \'object\', or nil', 2) + end + if not isValidFuncNameNsArgn(ftname) then + error('bl.type: invalid function or variable name \'' .. ftname .. '\'', 2) + end + + ftname = ftname:lower() + + bl._forceType[ftname] = typ + + -- apply to child classes if present + local cname, rest = classFromForceTypeStr(ftname) + if cname then + local meta = bl._objectUserMetas[cname] + if meta then + for chcname, _ in pairs(meta._children) do + setForceType(chcname .. rest, typ) + end + end + end end bl.type = setForceType @@ -193,761 +199,833 @@ bl.type = setForceType -- Value detection local function isTsObject(t) - return type(t)=='table' and t._tsObjectId~=nil + return type(t) == 'table' and t._tsObjectId ~= nil end local function tsIsObject(name) return tsBool(_bllua_ts.call('isObject', name)) end local function tsIsFunction(name) return tsBool(_bllua_ts.call('isFunction', name)) end local function tsIsFunctionNs(ns, name) return tsBool(_bllua_ts.call('isFunction', ns, name)) end local function tsIsFunctionNsname(nsname) - local ns, name = nsname:match('^([^:]+)::([^:]+)$') - if ns then return tsIsFunctionNs(ns, name) - else return tsIsFunction(nsname) end + local ns, name = nsname:match('^([^:]+)::([^:]+)$') + if ns then + return tsIsFunctionNs(ns, name) + else + return tsIsFunction(nsname) + end end function bl.isObject(obj) - if isTsObject(obj) then - obj = obj._tsObjectId - elseif type(obj)=='number' then - obj = tostring(obj) - elseif type(obj)~='string' then - error('bl.isObject: argument #1: expected torque object, number, or string', 2) - end - return tsIsObject(obj) + if isTsObject(obj) then + obj = obj._tsObjectId + elseif type(obj) == 'number' then + obj = tostring(obj) + elseif type(obj) ~= 'string' then + error('bl.isObject: argument #1: expected torque object, number, or string', 2) + end + return tsIsObject(obj) end + function bl.isFunction(a1, a2) - if type(a1)~='string' then - error('bl.isFunction: argument #1: expected string', 2) end - if a2 then - if type(a2)~='string' then - error('bl.isFunction: argument #2: expected string', 2) end - return tsIsFunctionNs(a1, a2) - else - return tsIsFunction(a1) - end + if type(a1) ~= 'string' then + error('bl.isFunction: argument #1: expected string', 2) + end + if a2 then + if type(a2) ~= 'string' then + error('bl.isFunction: argument #2: expected string', 2) + end + return tsIsFunctionNs(a1, a2) + else + return tsIsFunction(a1) + end end -- Torque object pseudo-class local tsClassMeta = { - __tostring = function(t) - return 'torqueClass:'..t._name.. - (t._inherit and (':'..t._inherit._name) or '') - end, + __tostring = function(t) + return 'torqueClass:' .. t._name .. + (t._inherit and (':' .. t._inherit._name) or '') + end, } bl._objectUserMetas = bl._objectUserMetas or {} function bl.class(cname, inhname) - if not ( type(cname)=='string' and isValidFuncName(cname) ) then - error('bl.class: argument #1: invalid class name', 2) end - if not ( inhname==nil or (type(inhname)=='string' and isValidFuncName(inhname)) ) then - error('bl.class: argument #2: inherit name must be a string or nil', 2) end - cname = cname:lower() - - local met = bl._objectUserMetas[cname] or { - _name = cname, - _inherit = nil, - _children = {}, - } - bl._objectUserMetas[cname] = met - setmetatable(met, tsClassMeta) - - if inhname then - inhname = inhname:lower() - - local inh = bl._objectUserMetas[inhname] - if not inh then error('bl.class: argument #2: \''..inhname..'\' is not the '.. - 'name of an existing class', 2) end - - inh._children[cname] = true - - local inhI = met._inherit - if inhI and inhI~=inh then - error('bl.class: argument #2: class already exists and '.. - 'inherits a different parent.', 2) end - met._inherit = inh - - -- apply inherited method and field types - for ftname, typ in pairs(bl._forceType) do - local cname2, rest = classFromForceTypeStr(ftname) - if cname2==inhname then - setForceType(cname..rest, typ) - end - end - end + if not (type(cname) == 'string' and isValidFuncName(cname)) then + error('bl.class: argument #1: invalid class name', 2) + end + if not (inhname == nil or (type(inhname) == 'string' and isValidFuncName(inhname))) then + error('bl.class: argument #2: inherit name must be a string or nil', 2) + end + cname = cname:lower() + + local met = bl._objectUserMetas[cname] or { + _name = cname, + _inherit = nil, + _children = {}, + } + bl._objectUserMetas[cname] = met + setmetatable(met, tsClassMeta) + + if inhname then + inhname = inhname:lower() + + local inh = bl._objectUserMetas[inhname] + if not inh then + error('bl.class: argument #2: \'' .. inhname .. '\' is not the ' .. + 'name of an existing class', 2) + end + + inh._children[cname] = true + + local inhI = met._inherit + if inhI and inhI ~= inh then + error('bl.class: argument #2: class already exists and ' .. + 'inherits a different parent.', 2) + end + met._inherit = inh + + -- apply inherited method and field types + for ftname, typ in pairs(bl._forceType) do + local cname2, rest = classFromForceTypeStr(ftname) + if cname2 == inhname then + setForceType(cname .. rest, typ) + end + end + end end + local function objectInheritedMetas(name) - local inh = bl._objectUserMetas[name:lower()] - return function() - local inhP = inh - if inhP==nil then return nil end - inh = inh._inherit - return inhP - end + local inh = bl._objectUserMetas[name:lower()] + return function() + local inhP = inh + if inhP == nil then return nil end + inh = inh._inherit + return inhP + end end local tsObjectMeta = { - -- __index: Called when accessing fields that don't exist in the object itself - -- Return torque member function or value - __index = function(t, name) - if rawget(t,'_deleted') then - error('ts object index: object no longer exists', 2) end - if type(name)~='string' and type(name)~='number' then - error('ts object index: index must be a string or number', 2) end - if getmetatable(t)[name] then - return getmetatable(t)[name] - elseif type(name)=='number' then - if not tsIsFunctionNs(rawget(t,'_tsNamespace'), 'getObject') then - error('ts object __index: index is number, but object does not have '.. - 'getObject method', 2) end - return toTsObject(_bllua_ts.callobj(t._tsObjectId, 'getObject', - tostring(name))) - else - for inh in objectInheritedMetas(rawget(t,'_tsClassName')) do - if inh[name] then return inh[name] end - end - if tsIsFunctionNs(rawget(t,'_tsNamespace'), name) then - return function(t, ...) - local args = {...} - local argsS = arglistToTs(args) - return valFromTs( - _bllua_ts.callobj(rawget(t,'_tsObjectId'), name, unpack(argsS)), - rawget(t,'_tsName') and rawget(t,'_tsName')..'::'..name, - rawget(t,'_tsNamespace')..'::'..name) - end - else - return valFromTs( - _bllua_ts.getfield(rawget(t,'_tsObjectId'), name), - rawget(t,'_tsName') and rawget(t,'_tsName')..'.'..name, - rawget(t,'_tsNamespace')..'.'..name) - end - end - end, - -- __newindex: Called when setting fields on the object - -- Set lua data - -- Use :set() to set Torque data - __newindex = function(t, name, val) - if rawget(t,'_deleted') then - error('ts object newindex: object no longer exists', 2) end - if type(name)~='string' then - error('ts object newindex: index must be a string', 2) end - rawset(t, name, val) - -- create strong reference since it's now storing lua data - bl._objectsStrong[rawget(t,'_tsObjectId')] = t - end, - -- object:set(fieldName, value) - -- Use to set torque data - set = function(t, name, val) - if t==nil or type(t)~='table' or not t._tsObjectId then - error('ts object method: be sure to use :func() not .func()', 2) end - if t._deleted then - error('ts object method: object no longer exists', 2) end - if type(name)~='string' then - error('ts object :set(): index must be a string', 2) end - _bllua_ts.setfield(t._tsObjectId, name, valToTs(val)) - end, - -- __tostring: Called when printing - -- Display a nice info string - __tostring = function(t) - return 'torque:'..t._tsNamespace..':'..t._tsObjectId.. - (t._tsName~='' and ('('..t._tsName..')') or '') - end, - -- #object - -- If the object has a getCount method, return its count - __len = function(t) - if t._deleted then - error('ts object __len: object no longer exists', 2) end - if not tsIsFunctionNs(t._tsNamespace, 'getCount') then - error('ts object __len: object has no getCount method', 2) end - return tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount')) - end, - -- object:members() - -- Return an iterator for Torque objects with the getCount and getObject methods - -- for index, object in group:members() do ... end - members = function(t) - if t==nil then - error('ts object method: be sure to use :func() not .func()', 2) end - if t._deleted then - error('ts object method: object no longer exists', 2) end - if not ( - tsIsFunctionNs(t._tsNamespace, 'getCount' ) and - tsIsFunctionNs(t._tsNamespace, 'getObject')) then - error('ts object :members() - '.. - 'Object does not have getCount and getObject methods', 2) end - local count = tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount')) - local idx = 0 - return function() - if idx < count then - local obj = toTsObject(_bllua_ts.callobj(t._tsObjectId, - 'getObject', tostring(idx))) - idx = idx+1 - return idx-1, obj - else - return nil - end - end - end, - -- Wrap some Torque functions for performance and error checking - getName = function(t) - if t==nil then - error('ts object method: be sure to use :func() not .func()', 2) end - if t._deleted then - error('ts object method: object no longer exists', 2) end - return t._tsName - end, - getId = function(t) - if t==nil then - error('ts object method: be sure to use :func() not .func()', 2) end - if t._deleted then - error('ts object method: object no longer exists', 2) end - return tonumber(t._tsObjectId) - end, - getType = function(t) - if t==nil then - error('ts object method: be sure to use :func() not .func()', 2) end - if t._deleted then - error('ts object method: object no longer exists', 2) end - return tsTypesByNum[_bllua_ts.callobj(t._tsObjectId, 'getType')] - end, - ---- Schedule method for objects - --schedule = function(t, time, cb, ...) - -- if type(t)~='table' or not t._tsObjectId then - -- error('ts object method: be sure to use :func() not .func()', 2) end - -- if t._deleted then - -- error('ts object method: object no longer exists', 2) end - -- if type(time)~='number' then - -- error('ts object schedule: argument #2: time must be a number', 2) end - -- if type(cb)~='function' then - -- error('ts object schedule: argument #3: callback must be a function', 2) end - -- local args = {...} - -- bl.schedule(time, function() - -- if tsBool(__bllua_ts.call('isObject', t._tsObjectId)) then - -- pcall(cb, unpack(args)) - -- end - -- end) - --end, - exists = function(t) - if t==nil then - error('ts object method: be sure to use :func() not .func()', 2) end - if t._deleted then - return false end - return tsIsObject(t._tsObjectId) - end, + -- __index: Called when accessing fields that don't exist in the object itself + -- Return torque member function or value + __index = function(t, name) + if rawget(t, '_deleted') then + error('ts object index: object no longer exists', 2) + end + if type(name) ~= 'string' and type(name) ~= 'number' then + error('ts object index: index must be a string or number', 2) + end + if getmetatable(t)[name] then + return getmetatable(t)[name] + elseif type(name) == 'number' then + if not tsIsFunctionNs(rawget(t, '_tsNamespace'), 'getObject') then + error('ts object __index: index is number, but object does not have ' .. + 'getObject method', 2) + end + return toTsObject(_bllua_ts.callobj(t._tsObjectId, 'getObject', + tostring(name))) + else + for inh in objectInheritedMetas(rawget(t, '_tsClassName')) do + if inh[name] then return inh[name] end + end + if tsIsFunctionNs(rawget(t, '_tsNamespace'), name) then + return function(t, ...) + local args = { ... } + local argsS = arglistToTs(args) + return valFromTs( + _bllua_ts.callobj(rawget(t, '_tsObjectId'), name, unpack(argsS)), + rawget(t, '_tsName') and rawget(t, '_tsName') .. '::' .. name, + rawget(t, '_tsNamespace') .. '::' .. name) + end + else + return valFromTs( + _bllua_ts.getfield(rawget(t, '_tsObjectId'), name), + rawget(t, '_tsName') and rawget(t, '_tsName') .. '.' .. name, + rawget(t, '_tsNamespace') .. '.' .. name) + end + end + end, + -- __newindex: Called when setting fields on the object + -- Set lua data + -- Use :set() to set Torque data + __newindex = function(t, name, val) + if rawget(t, '_deleted') then + error('ts object newindex: object no longer exists', 2) + end + if type(name) ~= 'string' then + error('ts object newindex: index must be a string', 2) + end + rawset(t, name, val) + -- create strong reference since it's now storing lua data + bl._objectsStrong[rawget(t, '_tsObjectId')] = t + end, + -- object:set(fieldName, value) + -- Use to set torque data + set = function(t, name, val) + if t == nil or type(t) ~= 'table' or not t._tsObjectId then + error('ts object method: be sure to use :func() not .func()', 2) + end + if t._deleted then + error('ts object method: object no longer exists', 2) + end + if type(name) ~= 'string' then + error('ts object :set(): index must be a string', 2) + end + _bllua_ts.setfield(t._tsObjectId, name, valToTs(val)) + end, + -- __tostring: Called when printing + -- Display a nice info string + __tostring = function(t) + return 'torque:' .. t._tsNamespace .. ':' .. t._tsObjectId .. + (t._tsName ~= '' and ('(' .. t._tsName .. ')') or '') + end, + -- #object + -- If the object has a getCount method, return its count + __len = function(t) + if t._deleted then + error('ts object __len: object no longer exists', 2) + end + if not tsIsFunctionNs(t._tsNamespace, 'getCount') then + error('ts object __len: object has no getCount method', 2) + end + return tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount')) + end, + -- object:members() + -- Return an iterator for Torque objects with the getCount and getObject methods + -- for index, object in group:members() do ... end + members = function(t) + if t == nil then + error('ts object method: be sure to use :func() not .func()', 2) + end + if t._deleted then + error('ts object method: object no longer exists', 2) + end + if not ( + tsIsFunctionNs(t._tsNamespace, 'getCount') and + tsIsFunctionNs(t._tsNamespace, 'getObject')) then + error('ts object :members() - ' .. + 'Object does not have getCount and getObject methods', 2) + end + local count = tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount')) + local idx = 0 + return function() + if idx < count then + local obj = toTsObject(_bllua_ts.callobj(t._tsObjectId, + 'getObject', tostring(idx))) + idx = idx + 1 + return idx - 1, obj + else + return nil + end + end + end, + -- Wrap some Torque functions for performance and error checking + getName = function(t) + if t == nil then + error('ts object method: be sure to use :func() not .func()', 2) + end + if t._deleted then + error('ts object method: object no longer exists', 2) + end + return t._tsName + end, + getId = function(t) + if t == nil then + error('ts object method: be sure to use :func() not .func()', 2) + end + if t._deleted then + error('ts object method: object no longer exists', 2) + end + return tonumber(t._tsObjectId) + end, + getType = function(t) + if t == nil then + error('ts object method: be sure to use :func() not .func()', 2) + end + if t._deleted then + error('ts object method: object no longer exists', 2) + end + return tsTypesByNum[_bllua_ts.callobj(t._tsObjectId, 'getType')] + end, + ---- Schedule method for objects + --schedule = function(t, time, cb, ...) + -- if type(t)~='table' or not t._tsObjectId then + -- error('ts object method: be sure to use :func() not .func()', 2) end + -- if t._deleted then + -- error('ts object method: object no longer exists', 2) end + -- if type(time)~='number' then + -- error('ts object schedule: argument #2: time must be a number', 2) end + -- if type(cb)~='function' then + -- error('ts object schedule: argument #3: callback must be a function', 2) end + -- local args = {...} + -- bl.schedule(time, function() + -- if tsBool(__bllua_ts.call('isObject', t._tsObjectId)) then + -- pcall(cb, unpack(args)) + -- end + -- end) + --end, + exists = function(t) + if t == nil then + error('ts object method: be sure to use :func() not .func()', 2) + end + if t._deleted then + return false + end + return tsIsObject(t._tsObjectId) + end, } -- Weak-values table for caching Torque object references -- Objects in this table can be garbage collected if there are no other refs to them if not bl._objectsWeak then - bl._objectsWeak = {} - setmetatable(bl._objectsWeak, { __mode='v' }) + bl._objectsWeak = {} + setmetatable(bl._objectsWeak, { __mode = 'v' }) end -- Strong table for preserving Torque object references containing lua data -- If an object in this table, it will remain here and in the Weak table until deleted if not bl._objectsStrong then - bl._objectsStrong = {} + bl._objectsStrong = {} end -- Hook object deletion to clean up its lua data -- idS is expected to be the object ID number, NOT the object name function _bllua_objectDeleted(idS) - local obj = bl._objectsWeak[idS] - if obj then - obj._deleted = true - bl._objectsStrong[idS] = nil - bl._objectsWeak[idS] = nil - bl._objectsWeak[obj._tsName:lower()] = nil - end + local obj = bl._objectsWeak[idS] + if obj then + obj._deleted = true + bl._objectsStrong[idS] = nil + bl._objectsWeak[idS] = nil + bl._objectsWeak[obj._tsName:lower()] = nil + end end -- Return a Torque object for the object ID string, or create one if none exists toTsObject = function(idiS) - if type(idiS)~='string' then - error('toTsObject: input must be a string', 2) end - idiS = idiS:lower() - if bl._objectsWeak[idiS] then return bl._objectsWeak[idiS] end - - if not tsBool(_bllua_ts.call('isObject', idiS)) then - --error('toTsObject: object \''..idiS..'\' does not exist', 2) end - return nil end - - local className = _bllua_ts.callobj(idiS, 'getClassName') - local obj = { - _tsObjectId = _bllua_ts.callobj(idiS, 'getId' ), - _tsName = _bllua_ts.callobj(idiS, 'getName' ), - _tsNamespace = className, - _tsClassName = className:lower(), - } - setmetatable(obj, tsObjectMeta) - - bl._objectsWeak[obj._tsObjectId ] = obj - bl._objectsWeak[obj._tsName:lower()] = obj - return obj + if type(idiS) ~= 'string' then + error('toTsObject: input must be a string', 2) + end + idiS = idiS:lower() + if bl._objectsWeak[idiS] then return bl._objectsWeak[idiS] end + + if not tsBool(_bllua_ts.call('isObject', idiS)) then + --error('toTsObject: object \''..idiS..'\' does not exist', 2) end + return nil + end + + local className = _bllua_ts.callobj(idiS, 'getClassName') + local obj = { + _tsObjectId = _bllua_ts.callobj(idiS, 'getId'), + _tsName = _bllua_ts.callobj(idiS, 'getName'), + _tsNamespace = className, + _tsClassName = className:lower(), + } + setmetatable(obj, tsObjectMeta) + + bl._objectsWeak[obj._tsObjectId] = obj + bl._objectsWeak[obj._tsName:lower()] = obj + return obj end -- Metatable for the global bl library -- Allows accessing Torque objects, variables, and functions by indexing it local tsMeta = { - -- __index: Called when accessing fields that don't exist in the table itself - -- Allow indexing by object id: bl[1234] - -- by object name: bl.mainMenuGui - -- by function name: bl.quit() - -- by variable name: bl.iAmAdmin - __index = function(t, name) - if getmetatable(t)[name] then - return getmetatable(t)[name] - elseif type(name)=='string' and bl._objectUserMetas[name:lower()] then - return bl._objectUserMetas[name:lower()] - else - if type(name)=='number' then - return toTsObject(tostring(name)) - elseif name:find('::') then - local ns, rest = name:match('^([^:]+)::(.+)$') - if not ns then error('ts index: invalid name \''..name..'\'', 2) end - if not rest:find('::') and tsIsFunctionNs(ns, rest) then - error('ts index: can\'t call a namespaced function from lua', 2) - else - return valFromTs(_bllua_ts.getvar(name), name) - end - elseif tsIsFunction(name) then - return function(...) - local args = {...} - local argsS = arglistToTs(args) - return valFromTs(_bllua_ts.call(name, unpack(argsS)), name) - end - elseif tsIsObject(name) then - return toTsObject(name) - else - return valFromTs(_bllua_ts.getvar(name), name) - end - end - end, + -- __index: Called when accessing fields that don't exist in the table itself + -- Allow indexing by object id: bl[1234] + -- by object name: bl.mainMenuGui + -- by function name: bl.quit() + -- by variable name: bl.iAmAdmin + __index = function(t, name) + if getmetatable(t)[name] then + return getmetatable(t)[name] + elseif type(name) == 'string' and bl._objectUserMetas[name:lower()] then + return bl._objectUserMetas[name:lower()] + else + if type(name) == 'number' then + return toTsObject(tostring(name)) + elseif name:find('::') then + local ns, rest = name:match('^([^:]+)::(.+)$') + if not ns then error('ts index: invalid name \'' .. name .. '\'', 2) end + if not rest:find('::') and tsIsFunctionNs(ns, rest) then + error('ts index: can\'t call a namespaced function from lua', 2) + else + return valFromTs(_bllua_ts.getvar(name), name) + end + elseif tsIsFunction(name) then + return function(...) + local args = { ... } + local argsS = arglistToTs(args) + return valFromTs(_bllua_ts.call(name, unpack(argsS)), name) + end + elseif tsIsObject(name) then + return toTsObject(name) + else + return valFromTs(_bllua_ts.getvar(name), name) + end + end + end, } -- bl.set(name, value) -- Used to set global variables function bl.set(name, val) - _bllua_ts.setvar(name, valToTs(val)) + _bllua_ts.setvar(name, valToTs(val)) end -- Utility functions function bl.call(func, ...) - local args = {...} - local argsS = arglistToTs(args) - return _bllua_ts.call(func, unpack(argsS)) + local args = { ... } + local argsS = arglistToTs(args) + return _bllua_ts.call(func, unpack(argsS)) end + function bl.eval(code) - return valFromTs(_bllua_ts.eval(code)) + return valFromTs(_bllua_ts.eval(code)) end + function bl.exec(file) - return valFromTs(_bllua_ts.call('exec', file)) + return valFromTs(_bllua_ts.call('exec', file)) end + function bl.boolean(val) - return val~=nil and - val~=false and - --val~='' and - --val~='0' and - val~=0 + return val ~= nil and + val ~= false and + --val~='' and + --val~='0' and + val ~= 0 end + function bl.object(id) - if type(id)=='table' and id._tsObjectId then - return id - elseif type(id)=='string' or type(id)=='number' then - return toTsObject(tostring(id)) - else - error('bl.toobject: id must be a ts object, number, or string', 2) - end + if type(id) == 'table' and id._tsObjectId then + return id + elseif type(id) == 'string' or type(id) == 'number' then + return toTsObject(tostring(id)) + else + error('bl.toobject: id must be a ts object, number, or string', 2) + end end + function bl.array(name, ...) - local rest = {...} - return name..table.concat(rest, '_') + local rest = { ... } + return name .. table.concat(rest, '_') end + function _bllua_call(fnameS, ...) - local args = arglistFromTs(fnameS:lower(), {...}) - if not _G[fnameS] then - error('luacall: no global lua function named \''..fnameS..'\'') end - -- todo: library fields and object methods - local res = _G[fnameS](args) - return valToTs(res) + local args = arglistFromTs(fnameS:lower(), { ... }) + if not _G[fnameS] then + error('luacall: no global lua function named \'' .. fnameS .. '\'') + end + -- todo: library fields and object methods + local res = _G[fnameS](args) + return valToTs(res) end -- bl.schedule: Use TS's schedule function to schedule lua calls -- bl.schedule(time, function, args...) -bl._scheduleTable = bl._scheduleTable or {} +bl._scheduleTable = bl._scheduleTable or {} bl._scheduleNextId = bl._scheduleNextId or 1 local function cancelTsSched(sched) - if not (sched and sched.handle) then - error('schedule:cancel() - invalid object', 2) - end - _bllua_ts.call('cancel', sched.handle) - bl._scheduleTable[id] = nil + if not (sched and sched.handle) then + error('schedule:cancel() - invalid object', 2) + end + _bllua_ts.call('cancel', sched.handle) + bl._scheduleTable[id] = nil end function bl.schedule(time, cb, ...) - local id = bl._scheduleNextId - bl._scheduleNextId = bl._scheduleNextId+1 - local args = {...} - local handle = tonumber(_bllua_ts.call('schedule', - time, 0, '_bllua_luacall', '_bllua_schedule_callback', id)) - local sch = { - callback = cb, - args = args, - handle = handle, - cancel = cancelTsSched, - } - bl._scheduleTable[id] = sch - return sch + local id = bl._scheduleNextId + bl._scheduleNextId = bl._scheduleNextId + 1 + local args = { ... } + local handle = tonumber(_bllua_ts.call('schedule', + time, 0, '_bllua_luacall', '_bllua_schedule_callback', id)) + local sch = { + callback = cb, + args = args, + handle = handle, + cancel = cancelTsSched, + } + bl._scheduleTable[id] = sch + return sch end + function _bllua_schedule_callback(idS) - local id = tonumber(idS) or - error('_bllua_schedule_callback: invalid id: '..tostring(idS)) - local sch = bl._scheduleTable[id] - if not sch then error('_bllua_schedule_callback: no schedule with id '..id) end - bl._scheduleTable[id] = nil - sch.callback(unpack(sch.args)) + local id = tonumber(idS) or + error('_bllua_schedule_callback: invalid id: ' .. tostring(idS)) + local sch = bl._scheduleTable[id] + if not sch then error('_bllua_schedule_callback: no schedule with id ' .. id) end + bl._scheduleTable[id] = nil + sch.callback(unpack(sch.args)) end -- serverCmd and clientCmd bl._cmds = bl._cmds or {} function _bllua_process_cmd(cmdS, ...) - local cmd = cmdS:lower() - local args = arglistFromTs(cmd, {...}) - local func = bl._cmds[cmd] - if not func then error('_bllua_process_cmd: no cmd named \''..cmd..'\'') end - func(unpack(args)) --pcall(func, unpack(args)) + local cmd = cmdS:lower() + local args = arglistFromTs(cmd, { ... }) + local func = bl._cmds[cmd] + if not func then error('_bllua_process_cmd: no cmd named \'' .. cmd .. '\'') end + func(unpack(args)) --pcall(func, unpack(args)) end + local function addCmd(cmd, func) - if not isValidFuncName(cmd) then - error('addCmd: invalid function name \''..tostring(cmd)..'\'') end - bl._cmds[cmd] = func - local arglist = '%a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p' - _bllua_ts.eval('function '..cmd..'('..arglist..'){'.. - '_bllua_luacall(_bllua_process_cmd,"'..cmd..'",'..arglist..');}') + if not isValidFuncName(cmd) then + error('addCmd: invalid function name \'' .. tostring(cmd) .. '\'') + end + bl._cmds[cmd] = func + local arglist = '%a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p' + _bllua_ts.eval('function ' .. cmd .. '(' .. arglist .. '){' .. + '_bllua_luacall(_bllua_process_cmd,"' .. cmd .. '",' .. arglist .. ');}') end function bl.addServerCmd(name, func) - name = name:lower() - addCmd('servercmd'..name, func) - bl._forceType['servercmd'..name..':1'] = 'object' + name = name:lower() + addCmd('servercmd' .. name, func) + bl._forceType['servercmd' .. name .. ':1'] = 'object' end + function bl.addClientCmd(name, func) - name = name:lower() - addCmd('clientcmd'..name, func) + name = name:lower() + addCmd('clientcmd' .. name, func) end -- commandToServer and commandToClient function bl.commandToServer(cmd, ...) - _bllua_ts.call('commandToServer', - _bllua_ts.call('addTaggedString', cmd), - unpack(arglistToTs({...}))) + _bllua_ts.call('commandToServer', + _bllua_ts.call('addTaggedString', cmd), + unpack(arglistToTs({ ... }))) end + function bl.commandToClient(cmd, ...) - _bllua_ts.call('commandToClient', - _bllua_ts.call('addTaggedString', cmd), - unpack(arglistToTs({...}))) + _bllua_ts.call('commandToClient', + _bllua_ts.call('addTaggedString', cmd), + unpack(arglistToTs({ ... }))) end + function bl.commandToAll(cmd, ...) - _bllua_ts.call('commandToAll', - _bllua_ts.call('addTaggedString', cmd), - unpack(arglistToTs({...}))) + _bllua_ts.call('commandToAll', + _bllua_ts.call('addTaggedString', cmd), + unpack(arglistToTs({ ... }))) end -- Hooks (using TS packages) local function isPackageActive(pkg) - local numpkgs = tonumber(_bllua_ts.call('getNumActivePackages')) - for i = 0, numpkgs-1 do - local apkg = _bllua_ts.call('getActivePackage', tostring(i)) - if apkg==pkg then return true end - end - return false + local numpkgs = tonumber(_bllua_ts.call('getNumActivePackages')) + for i = 0, numpkgs - 1 do + local apkg = _bllua_ts.call('getActivePackage', tostring(i)) + if apkg == pkg then return true end + end + return false end local function activatePackage(pkg) - if not isPackageActive(pkg) then - _bllua_ts.call('activatePackage', pkg) - end + if not isPackageActive(pkg) then + _bllua_ts.call('activatePackage', pkg) + end end local function deactivatePackage(pkg) - if isPackageActive(pkg) then - _bllua_ts.call('deactivatePackage', pkg) - end + if isPackageActive(pkg) then + _bllua_ts.call('deactivatePackage', pkg) + end end local hookNargs = 8 local hookArglistLocal = '%a,%b,%c,%d,%e,%f,%g,%h' -local hookArglistGlobal = '$_bllua_hook_arg1,$_bllua_hook_arg2,$_bllua_hook_arg3,$_bllua_hook_arg4,$_bllua_hook_arg5,$_bllua_hook_arg6,$_bllua_hook_arg7,$_bllua_hook_arg8' +local hookArglistGlobal = +'$_bllua_hook_arg1,$_bllua_hook_arg2,$_bllua_hook_arg3,$_bllua_hook_arg4,$_bllua_hook_arg5,$_bllua_hook_arg6,$_bllua_hook_arg7,$_bllua_hook_arg8' bl._hooks = bl._hooks or {} function _bllua_process_hook_before(pkgS, nameS, ...) - local args = arglistFromTs(nameS, {...}) - local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and - bl._hooks[pkgS][nameS].before - if not func then - error('_bllua_process_hook_before: no hook for '..pkgs..':'..nameS) end - _bllua_ts.setvar('_bllua_hook_abort', '0') - func(args) --pcall(func, args) - if args._return then - _bllua_ts.setvar('_bllua_hook_abort', '1') - _bllua_ts.setvar('_bllua_hook_return', valToTs(args._return)) - end - for i=1,hookNargs do - _bllua_ts.setvar('_bllua_hook_arg'..i, valToTs(args[i])) - end + local args = arglistFromTs(nameS, { ... }) + local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and + bl._hooks[pkgS][nameS].before + if not func then + error('_bllua_process_hook_before: no hook for ' .. pkgs .. ':' .. nameS) + end + _bllua_ts.setvar('_bllua_hook_abort', '0') + func(args) --pcall(func, args) + if args._return then + _bllua_ts.setvar('_bllua_hook_abort', '1') + _bllua_ts.setvar('_bllua_hook_return', valToTs(args._return)) + end + for i = 1, hookNargs do + _bllua_ts.setvar('_bllua_hook_arg' .. i, valToTs(args[i])) + end end + function _bllua_process_hook_after(pkgS, nameS, resultS, ...) - nameS = nameS:lower() - local args = arglistFromTs(nameS, {...}) - args._return = valFromTs(resultS, nameS) - local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and - bl._hooks[pkgS][nameS].after - if not func then - error('_bllua_process_hook_after: no hook for '..pkgs..':'..nameS) end - func(args) --pcall(func, args) - return valToTs(args._return) + nameS = nameS:lower() + local args = arglistFromTs(nameS, { ... }) + args._return = valFromTs(resultS, nameS) + local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and + bl._hooks[pkgS][nameS].after + if not func then + error('_bllua_process_hook_after: no hook for ' .. pkgs .. ':' .. nameS) + end + func(args) --pcall(func, args) + return valToTs(args._return) end + local function updateHook(pkg, name, hk) - local beforeCode = hk.before and - ('_bllua_luacall("_bllua_process_hook_before", "'..pkg..'","'..name.. - '",'..hookArglistLocal..');') or '' - local arglist = (hk.before and hookArglistGlobal or hookArglistLocal) - local parentCode = - tsIsFunctionNsname(name) and -- only call parent if it exists - (hk.before and - 'if($_bllua_hook_abort)return $_bllua_hook_return; else ' or '').. - ((hk.after and '%result=' or 'return ').. - 'parent::'..name:match('[^:]+$').. - '('..arglist..');') or '' - local afterCode = hk.after and - ('return _bllua_luacall("_bllua_process_hook_after","'..pkg..'","'..name..'",%result,'.. - arglist..');') or '' - local code = - 'package '..pkg..'{'.. - 'function '..name..'('..hookArglistLocal..'){'.. - beforeCode..parentCode..afterCode.. - '}'.. - '};' - _bllua_ts.eval(code) + local beforeCode = hk.before and + ('_bllua_luacall("_bllua_process_hook_before", "' .. pkg .. '","' .. name .. + '",' .. hookArglistLocal .. ');') or '' + local arglist = (hk.before and hookArglistGlobal or hookArglistLocal) + local parentCode = + tsIsFunctionNsname(name) and -- only call parent if it exists + (hk.before and + 'if($_bllua_hook_abort)return $_bllua_hook_return; else ' or '') .. + ((hk.after and '%result=' or 'return ') .. + 'parent::' .. name:match('[^:]+$') .. + '(' .. arglist .. ');') or '' + local afterCode = hk.after and + ('return _bllua_luacall("_bllua_process_hook_after","' .. pkg .. '","' .. name .. '",%result,' .. + arglist .. ');') or '' + local code = + 'package ' .. pkg .. '{' .. + 'function ' .. name .. '(' .. hookArglistLocal .. '){' .. + beforeCode .. parentCode .. afterCode .. + '}' .. + '};' + _bllua_ts.eval(code) end function bl.hook(pkg, name, time, func) - if not isValidFuncName(pkg) then - error('bl.hook: argument #1: invalid package name \''..tostring(pkg)..'\'', 2) end - if not isValidFuncNameNs(name) then - error('bl.hook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end - if time~='before' and time~='after' then - error('bl.hook: argument #3: time must be \'before\' or \'after\'', 2) end - if type(func)~='function' then - error('bl.hook: argument #4: expected a function', 2) end - - bl._hooks[pkg] = bl._hooks[pkg] or {} - bl._hooks[pkg][name] = bl._hooks[pkg][name] or {} - bl._hooks[pkg][name][time] = func - - updateHook(pkg, name, bl._hooks[pkg][name]) - activatePackage(pkg) + if not isValidFuncName(pkg) then + error('bl.hook: argument #1: invalid package name \'' .. tostring(pkg) .. '\'', 2) + end + if not isValidFuncNameNs(name) then + error('bl.hook: argument #2: invalid function name \'' .. tostring(name) .. '\'', 2) + end + if time ~= 'before' and time ~= 'after' then + error('bl.hook: argument #3: time must be \'before\' or \'after\'', 2) + end + if type(func) ~= 'function' then + error('bl.hook: argument #4: expected a function', 2) + end + + bl._hooks[pkg] = bl._hooks[pkg] or {} + bl._hooks[pkg][name] = bl._hooks[pkg][name] or {} + bl._hooks[pkg][name][time] = func + + updateHook(pkg, name, bl._hooks[pkg][name]) + activatePackage(pkg) end + local function tableEmpty(t) - return next(t)~=nil + return next(t) ~= nil end function bl.unhook(pkg, name, time) - if not isValidFuncName(pkg) then - error('bl.unhook: argument #1: invalid package name \''..tostring(pkg)..'\'', 2) end - if not isValidFuncNameNs(name) then - error('bl.unhook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end - if time~='before' and time~='after' then - error('bl.unhook: argument #3: time must be \'before\' or \'after\'', 2) end - - if not name then - if bl._hooks[pkg] then - for name,hk in pairs(bl._hooks[pkg]) do - updateHook(pkg, name, {}) - end - bl._hooks[pkg] = nil - else - --error('bl.unhook: no hooks registered under package name \''.. - -- pkg..'\'', 2) - end - deactivatePackage(pkg) - else - if bl._hooks[pkg][name] then - if not time then - bl._hooks[pkg][name] = nil - if table.empty(bl._hooks[pkg]) then - bl._hooks[pkg] = nil - deactivatePackage(pkg) - end - updateHook(pkg, name, {}) - else - if time~='before' and time~='after' then - error('bl.unhook: argument #3: time must be nil, \'before\', or \'after\'', 2) end - bl._hooks[pkg][name][time] = nil - if tableEmpty(bl._hooks[pkg][name]) and tableEmpty(bl._hooks[pkg]) then - bl._hooks[pkg] = nil - deactivatePackage(pkg) - updateHook(pkg, name, {}) - else - updateHook(pkg, name, bl._hooks[pkg][name]) - end - end - else - --error('bl.unhook: no hooks registered for function \''..name.. - -- '\' under package name \''..pkg..'\'', 2) - end - end + if not isValidFuncName(pkg) then + error('bl.unhook: argument #1: invalid package name \'' .. tostring(pkg) .. '\'', 2) + end + if not isValidFuncNameNs(name) then + error('bl.unhook: argument #2: invalid function name \'' .. tostring(name) .. '\'', 2) + end + if time ~= 'before' and time ~= 'after' then + error('bl.unhook: argument #3: time must be \'before\' or \'after\'', 2) + end + + if not name then + if bl._hooks[pkg] then + for name, hk in pairs(bl._hooks[pkg]) do + updateHook(pkg, name, {}) + end + bl._hooks[pkg] = nil + else + --error('bl.unhook: no hooks registered under package name \''.. + -- pkg..'\'', 2) + end + deactivatePackage(pkg) + else + if bl._hooks[pkg][name] then + if not time then + bl._hooks[pkg][name] = nil + if table.empty(bl._hooks[pkg]) then + bl._hooks[pkg] = nil + deactivatePackage(pkg) + end + updateHook(pkg, name, {}) + else + if time ~= 'before' and time ~= 'after' then + error('bl.unhook: argument #3: time must be nil, \'before\', or \'after\'', 2) + end + bl._hooks[pkg][name][time] = nil + if tableEmpty(bl._hooks[pkg][name]) and tableEmpty(bl._hooks[pkg]) then + bl._hooks[pkg] = nil + deactivatePackage(pkg) + updateHook(pkg, name, {}) + else + updateHook(pkg, name, bl._hooks[pkg][name]) + end + end + else + --error('bl.unhook: no hooks registered for function \''..name.. + -- '\' under package name \''..pkg..'\'', 2) + end + end end -- Container search/raycast local function vecToTs(v) - if not isTsVector(v) then - error('vecToTs: argument is not a vector', 3) end - return table.concat(v, ' ') + if not isTsVector(v) then + error('vecToTs: argument is not a vector', 3) + end + return table.concat(v, ' ') end local function maskToTs(mask) - if type(mask)=='string' then - local val = tsTypesByName[mask:lower()] - if not val then - error('maskToTs: invalid mask \''..mask..'\'', 3) end - return tostring(val) - elseif type(mask)=='table' then - local tval = 0 - local seen = {} - for i,v in ipairs(mask) do - if not seen[v] then - local val = tsTypesByName[v:lower()] - if not val then - error('maskToTs: invalid mask \''..v.. - '\' at index '..i..' in mask list', 3) end - tval = tval + val - seen[v] = true - end - end - return tostring(tval) - else - error('maskToTs: mask must be a string or table', 3) - end + if type(mask) == 'string' then + local val = tsTypesByName[mask:lower()] + if not val then + error('maskToTs: invalid mask \'' .. mask .. '\'', 3) + end + return tostring(val) + elseif type(mask) == 'table' then + local tval = 0 + local seen = {} + for i, v in ipairs(mask) do + if not seen[v] then + local val = tsTypesByName[v:lower()] + if not val then + error('maskToTs: invalid mask \'' .. v .. + '\' at index ' .. i .. ' in mask list', 3) + end + tval = tval + val + seen[v] = true + end + end + return tostring(tval) + else + error('maskToTs: mask must be a string or table', 3) + end end local function objToTs(obj) - if type(obj)=='number' or type(obj)=='string' then - return tostring(obj) - elseif type(obj)=='table' and obj._tsObjectId then - return tostring(obj._tsObjectId) - else - error('objToTs: invalid object \''..tostring(obj)..'\'', 3) - end + if type(obj) == 'number' or type(obj) == 'string' then + return tostring(obj) + elseif type(obj) == 'table' and obj._tsObjectId then + return tostring(obj._tsObjectId) + else + error('objToTs: invalid object \'' .. tostring(obj) .. '\'', 3) + end end function bl.raycast(start, stop, mask, ignores) - local startS = vecToTs(start) - local stopS = vecToTs(start) - local maskS = maskToTs(mask) - local ignoresS = {} - for _,v in ipairs(ignores) do - table.insert(ignoresS, objToTs(v)) - end - - local retS = _bllua_ts.call('containerRaycast', startS, stopS, maskS, unpack(ignoresS)) - - if retS=='0' then - return nil - else - local hitS, pxS,pyS,pzS, nxS,nyS,nzS = retS:match('^([0-9]+) '.. - '(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+) '.. - '(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') - local hit = toTsObject(hitS) - local pos = vector{tonumber(pxS),tonumber(pyS),tonumber(pzS)} - local norm = vector{tonumber(nxS),tonumber(nyS),tonumber(nzS)} - return hit, pos, norm - end + local startS = vecToTs(start) + local stopS = vecToTs(start) + local maskS = maskToTs(mask) + local ignoresS = {} + for _, v in ipairs(ignores) do + table.insert(ignoresS, objToTs(v)) + end + + local retS = _bllua_ts.call('containerRaycast', startS, stopS, maskS, unpack(ignoresS)) + + if retS == '0' then + return nil + else + local hitS, pxS, pyS, pzS, nxS, nyS, nzS = retS:match('^([0-9]+) ' .. + '(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+) ' .. + '(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') + local hit = toTsObject(hitS) + local pos = vector { tonumber(pxS), tonumber(pyS), tonumber(pzS) } + local norm = vector { tonumber(nxS), tonumber(nyS), tonumber(nzS) } + return hit, pos, norm + end end + local function tsContainerSearchIterator() - local retS = _bllua_ts.call('containerSearchNext') - if retS=='0' then - return nil - else - return toTsObject(retS) - end + local retS = _bllua_ts.call('containerSearchNext') + if retS == '0' then + return nil + else + return toTsObject(retS) + end end function bl.boxSearch(pos, size, mask) - local posS = vecToTs(pos) - local sizeS = vecToTs(size) - local maskS = maskToTs(mask) - - _bllua_ts.call('initContainerBoxSearch', posS, sizeS, maskS) - return tsContainerSearchIterator + local posS = vecToTs(pos) + local sizeS = vecToTs(size) + local maskS = maskToTs(mask) + + _bllua_ts.call('initContainerBoxSearch', posS, sizeS, maskS) + return tsContainerSearchIterator end + function bl.radiusSearch(pos, radius, mask) - local posS = vecToTs(pos) - if type(radius)~='number' then - error('bl.radiusSearch: argument #2: radius must be a number', 2) end - local radiusS = tostring(radius) - local maskS = maskToTs(mask) - - _bllua_ts.call('initContainerRadiusSearch', posS, radiusS, maskS) - return tsContainerSearchIterator + local posS = vecToTs(pos) + if type(radius) ~= 'number' then + error('bl.radiusSearch: argument #2: radius must be a number', 2) + end + local radiusS = tostring(radius) + local maskS = maskToTs(mask) + + _bllua_ts.call('initContainerRadiusSearch', posS, radiusS, maskS) + return tsContainerSearchIterator end -- Print/Talk/Echo local maxTsArgLen = 8192 local function valsToString(vals) - local strs = {} - for i,v in ipairs(vals) do - local tstr = table.tostring(v) - if #tstr>maxTsArgLen then - tstr = tostring(v) - end - strs[i] = tstr - end - return table.concat(strs, ' ') + local strs = {} + for i, v in ipairs(vals) do + local tstr = table.tostring(v) + if #tstr > maxTsArgLen then + tstr = tostring(v) + end + strs[i] = tstr + end + return table.concat(strs, ' ') end bl.echo = function(...) - local str = valsToString({...}) - _bllua_ts.call('echo', str) + local str = valsToString({ ... }) + _bllua_ts.call('echo', str) end print = bl.echo bl.talk = function(...) - local str = valsToString({...}) - _bllua_ts.call('echo', str) - _bllua_ts.call('talk', str) + local str = valsToString({ ... }) + _bllua_ts.call('echo', str) + _bllua_ts.call('talk', str) end -- bl.new and bl.datablock local function createTsObj(keyword, class, name, inherit, props) - local propsT = {} - for k,v in pairs(props) do - if not isValidFuncName(k) then - error('bl.new/bl.datablock: invalid property name \''..k..'\'') end - table.insert(propsT, k..'="'..valToTs(v)..'";') - end - - local objS = _bllua_ts.eval( - 'return '..keyword..' '..class..'('.. - (name or '')..(inherit and (':'..inherit) or '')..'){'.. - table.concat(propsT)..'};') - local obj = toTsObject(objS) - if not obj then - error('bl.new/bl.datablock: failed to create object', 3) end - - return obj + local propsT = {} + for k, v in pairs(props) do + if not isValidFuncName(k) then + error('bl.new/bl.datablock: invalid property name \'' .. k .. '\'') + end + table.insert(propsT, k .. '="' .. valToTs(v) .. '";') + end + + local objS = _bllua_ts.eval( + 'return ' .. keyword .. ' ' .. class .. '(' .. + (name or '') .. (inherit and (':' .. inherit) or '') .. '){' .. + table.concat(propsT) .. '};') + local obj = toTsObject(objS) + if not obj then + error('bl.new/bl.datablock: failed to create object', 3) + end + + return obj end local function parseTsDecl(decl) - local class, name, inherit - if decl:find(' ') then -- class ... - local cl, rest = decl:match('^([^ ]+) ([^ ]+)$') - class = cl - if rest:find(':') then -- class name:inherit - name, inherit = rest:match('^([^:]*):([^:]+)$') - if not name then class = nil end -- error - if name=='' then name = nil end -- class :inherit - else - name = rest - end - else -- class - class = decl - end - if not ( - isValidFuncName(class) and - (name==nil or isValidFuncName(name)) and - (inherit==nil or isValidFuncName(inherit)) ) then - error('bl.new/bl.datablock: invalid decl \''..decl..'\'\n'.. - 'must be of the format: \'className\', \'className name\', '.. - '\'className :inherit\', or \'className name:inherit\'', 3) end - return class, name, inherit + local class, name, inherit + if decl:find(' ') then -- class ... + local cl, rest = decl:match('^([^ ]+) ([^ ]+)$') + class = cl + if rest:find(':') then -- class name:inherit + name, inherit = rest:match('^([^:]*):([^:]+)$') + if not name then class = nil end -- error + if name == '' then name = nil end -- class :inherit + else + name = rest + end + else -- class + class = decl + end + if not ( + isValidFuncName(class) and + (name == nil or isValidFuncName(name)) and + (inherit == nil or isValidFuncName(inherit))) then + error('bl.new/bl.datablock: invalid decl \'' .. decl .. '\'\n' .. + 'must be of the format: \'className\', \'className name\', ' .. + '\'className :inherit\', or \'className name:inherit\'', 3) + end + return class, name, inherit end function bl.new(decl, props) - local class, name, inherit = parseTsDecl(decl) - return createTsObj('new', class, name, inherit, props) + local class, name, inherit = parseTsDecl(decl) + return createTsObj('new', class, name, inherit, props) end + function bl.datablock(decl, props) - local class, name, inherit = parseTsDecl(decl) - if not name then error('bl.datablock: must specify a name', 2) end - return createTsObj('datablock', class, name, inherit, props) + local class, name, inherit = parseTsDecl(decl) + if not name then error('bl.datablock: must specify a name', 2) end + return createTsObj('datablock', class, name, inherit, props) end setmetatable(bl, tsMeta) diff --git a/src/util/libts-lua.lua b/src/util/libts-lua.lua index ad740fd..a72d39d 100644 --- a/src/util/libts-lua.lua +++ b/src/util/libts-lua.lua @@ -1,4 +1,3 @@ - -- This Lua code provides some built-in utilities for writing Lua add-ons -- It is eval'd automatically once BLLua3 has loaded the TS API and environment -- It only has access to the sandboxed lua environment, just like user code. @@ -8,9 +7,10 @@ ts = _bllua_ts -- Provide limited OS functions os = os or {} ---@diagnostic disable-next-line: duplicate-set-field -function os.time() return math.floor(tonumber(_bllua_ts.call('getSimTime'))/1000) end +function os.time() return math.floor(tonumber(_bllua_ts.call('getSimTime')) / 1000) end + ---@diagnostic disable-next-line: duplicate-set-field -function os.clock() return tonumber(_bllua_ts.call('getSimTime'))/1000 end +function os.clock() return tonumber(_bllua_ts.call('getSimTime')) / 1000 end -- Virtual file class, emulating a file object as returned by io.open -- Used to wrap io.open to allow reading from zips (using TS) @@ -18,145 +18,150 @@ function os.clock() return tonumber(_bllua_ts.call('getSimTime'))/1000 end -- Can't read nulls, can't distinguish between CRLF and LF. -- Todo someday: actually read the zip in lua? local file_meta = { - read = function(file, mode) - file:_init() - if not file or type(file)~='table' or not file._is_file then error('File:read: Not a file', 2) end - if file._is_open ~= true then error('File:read: File is closed', 2) end - if mode=='*n' then - local ws, n = file.data:match('^([ \t\r\n]*)([0-9%.%-e]+)', file.pos) - if n then - file.pos = file.pos + #ws + #n - return n - else - return nil - end - elseif mode=='*a' then - local d = file.data:sub(file.pos, #file.data) - file.pos = #file.data + 1 - return d - elseif mode=='*l' then - local l, ws = file.data:match('^([^\r\n]*)(\r?\n)', file.pos) - if not l then - l = file.data:match('^([^\r\n]*)$', file.pos); ws = ''; - if l=='' then return nil end - end - if l then - file.pos = file.pos + #l + #ws - return l - else - return nil - end - elseif type(mode)=='number' then - local d = file.data:sub(file.pos, file.pos+mode) - file.pos = file.pos + #d - return d - else - error('File:read: Invalid mode \''..mode..'\'', 2) - end - end, - lines = function(file) - file:_init() - return function() - return file:read('*l') - end - end, - close = function(file) - if not file._is_open then error('File:close: File is not open', 2) end - file._is_open = false - end, - __index = function(f, k) return rawget(f, k) or getmetatable(f)[k] end, - _init = function(f) - if not f.data then - f.data = _bllua_ts.call('_bllua_ReadEntireFile', f.filename) - end - end, + read = function(file, mode) + file:_init() + if not file or type(file) ~= 'table' or not file._is_file then error('File:read: Not a file', 2) end + if file._is_open ~= true then error('File:read: File is closed', 2) end + if mode == '*n' then + local ws, n = file.data:match('^([ \t\r\n]*)([0-9%.%-e]+)', file.pos) + if n then + file.pos = file.pos + #ws + #n + return n + else + return nil + end + elseif mode == '*a' then + local d = file.data:sub(file.pos, #file.data) + file.pos = #file.data + 1 + return d + elseif mode == '*l' then + local l, ws = file.data:match('^([^\r\n]*)(\r?\n)', file.pos) + if not l then + l = file.data:match('^([^\r\n]*)$', file.pos); ws = ''; + if l == '' then return nil end + end + if l then + file.pos = file.pos + #l + #ws + return l + else + return nil + end + elseif type(mode) == 'number' then + local d = file.data:sub(file.pos, file.pos + mode) + file.pos = file.pos + #d + return d + else + error('File:read: Invalid mode \'' .. mode .. '\'', 2) + end + end, + lines = function(file) + file:_init() + return function() + return file:read('*l') + end + end, + close = function(file) + if not file._is_open then error('File:close: File is not open', 2) end + file._is_open = false + end, + __index = function(f, k) return rawget(f, k) or getmetatable(f)[k] end, + _init = function(f) + if not f.data then + f.data = _bllua_ts.call('_bllua_ReadEntireFile', f.filename) + end + end, } local function new_file_obj(fn) - local file = { - _is_file = true, - _is_open = true, - pos = 1, - __index = file_meta.__index, - filename = fn, - data = nil, - } - setmetatable(file, file_meta) - return file + local file = { + _is_file = true, + _is_open = true, + pos = 1, + __index = file_meta.__index, + filename = fn, + data = nil, + } + setmetatable(file, file_meta) + return file end -local function tflip(t) local u = {}; for _, n in ipairs(t) do u[n] = true end; return u; end -local allowed_zip_dirs = tflip{ - 'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders' +local function tflip(t) + local u = {}; for _, n in ipairs(t) do u[n] = true end; return u; +end +local allowed_zip_dirs = tflip { + 'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders' } local function io_open_absolute(fn, mode) - -- if file exists, use original mode - local res, err = _bllua_io_open(fn, mode) - if res then return res end - - -- otherwise, if TS sees file but Lua doesn't, it must be in a zip, so use TS reader - local dir = fn:match('^[^/]+') - if not allowed_zip_dirs[dir:lower()] then return nil, 'File is not in one of the allowed directories' end - local exist = _bllua_ts.call('isFile', fn) == '1' - if not exist then return nil, err end - - if mode~=nil and mode~='r' and mode~='rb' then - return nil, 'Files in zips can only be opened in read mode' end - - -- return a temp lua file object with the data - local fi = new_file_obj(fn) - return fi + -- if file exists, use original mode + local res, err = _bllua_io_open(fn, mode) + if res then return res end + + -- otherwise, if TS sees file but Lua doesn't, it must be in a zip, so use TS reader + local dir = fn:match('^[^/]+') + if not allowed_zip_dirs[dir:lower()] then return nil, 'File is not in one of the allowed directories' end + local exist = _bllua_ts.call('isFile', fn) == '1' + if not exist then return nil, err end + + if mode ~= nil and mode ~= 'r' and mode ~= 'rb' then + return nil, 'Files in zips can only be opened in read mode' + end + + -- return a temp lua file object with the data + local fi = new_file_obj(fn) + return fi end io = io or {} ---@diagnostic disable-next-line: duplicate-set-field function io.open(fn, mode, errn) - errn = errn or 1 - - -- try to open the file with relative path, otherwise use absolute path - local curfn = debug.getfilename(errn + 1) or _bllua_ts.getvar('Con::File') - if curfn == '' then curfn = nil end - if fn:find('^%.') then - local relfn = curfn and fn:find('^%./') and - curfn:gsub('[^/]+$', '')..fn:gsub('^%./', '') - if relfn then - local fi, err = io_open_absolute(relfn, mode) - return fi, err, relfn - else - return nil, 'Invalid path', fn - end - else - local fi, err = io_open_absolute(fn, mode) - return fi, err, fn - end + errn = errn or 1 + + -- try to open the file with relative path, otherwise use absolute path + local curfn = debug.getfilename(errn + 1) or _bllua_ts.getvar('Con::File') + if curfn == '' then curfn = nil end + if fn:find('^%.') then + local relfn = curfn and fn:find('^%./') and + curfn:gsub('[^/]+$', '') .. fn:gsub('^%./', '') + if relfn then + local fi, err = io_open_absolute(relfn, mode) + return fi, err, relfn + else + return nil, 'Invalid path', fn + end + else + local fi, err = io_open_absolute(fn, mode) + return fi, err, fn + end end + ---@diagnostic disable-next-line: duplicate-set-field function io.lines(fn) - local fi, err, fn2 = io.open(fn, nil, 2) - if not fi then error('Error opening file \''..fn2..'\': '..err, 2) end - return fi:lines() + local fi, err, fn2 = io.open(fn, nil, 2) + if not fi then error('Error opening file \'' .. fn2 .. '\': ' .. err, 2) end + return fi:lines() end + ---@diagnostic disable-next-line: duplicate-set-field function io.type(f) ----@diagnostic disable-next-line: undefined-field - if type(f)=='table' and f._is_file then ----@diagnostic disable-next-line: undefined-field - return f._is_open and 'file' or 'closed file' - else - return _bllua_io_type(f) - end + ---@diagnostic disable-next-line: undefined-field + if type(f) == 'table' and f._is_file then + ---@diagnostic disable-next-line: undefined-field + return f._is_open and 'file' or 'closed file' + else + return _bllua_io_type(f) + end end -- provide dofile function dofile(fn, errn) - errn = errn or 1 - - local fi, err, fn2 = io.open(fn, 'r', errn+1) - if not fi then error('Error executing file \''..fn2..'\': '..err, errn+1) end - - print('Executing '..fn2) - local text = fi:read('*a') - fi:close() - return assert(loadstring('--[['..fn2..']]'..text))() + errn = errn or 1 + + local fi, err, fn2 = io.open(fn, 'r', errn + 1) + if not fi then error('Error executing file \'' .. fn2 .. '\': ' .. err, errn + 1) end + + print('Executing ' .. fn2) + local text = fi:read('*a') + fi:close() + return assert(loadstring('--[[' .. fn2 .. ']]' .. text))() end -- provide require (just a wrapper for dofile) @@ -165,63 +170,67 @@ end -- blockland directory -- current add-on local function file_exists(fn, errn) - local fi, err, fn2 = io.open(fn, 'r', errn+1) - if fi then - fi:close() - return fn2 - else - return nil - end + local fi, err, fn2 = io.open(fn, 'r', errn + 1) + if fi then + fi:close() + return fn2 + else + return nil + end end local require_memo = {} function require(mod) - if require_memo[mod] then return unpack(require_memo[mod]) end - local fp = mod:gsub('%.', '/') - local fns = { - './'..fp..'.lua', -- local file - './'..fp..'/init.lua', -- local library - fp..'.lua', -- global file - fp..'/init.lua', -- global library - } - if fp:lower():find('^add-ons/') then - local addonpath = fp:lower():match('^add-ons/[^/]+')..'/' - table.insert(fns, addonpath..fp..'.lua') -- add-on file - table.insert(fns, addonpath..fp..'/init.lua') -- add-on library - end - for _,fn in ipairs(fns) do - local fne = file_exists(fn, 2) - if fne then - local res = {dofile(fne, 2)} - require_memo[mod] = res - return unpack(res) - end - end - return _bllua_requiresecure(mod) + if require_memo[mod] then return unpack(require_memo[mod]) end + local fp = mod:gsub('%.', '/') + local fns = { + './' .. fp .. '.lua', -- local file + './' .. fp .. '/init.lua', -- local library + fp .. '.lua', -- global file + fp .. '/init.lua', -- global library + } + if fp:lower():find('^add-ons/') then + local addonpath = fp:lower():match('^add-ons/[^/]+') .. '/' + table.insert(fns, addonpath .. fp .. '.lua') -- add-on file + table.insert(fns, addonpath .. fp .. '/init.lua') -- add-on library + end + for _, fn in ipairs(fns) do + local fne = file_exists(fn, 2) + if fne then + local res = { dofile(fne, 2) } + require_memo[mod] = res + return unpack(res) + end + end + return _bllua_requiresecure(mod) end -- Exposure to TS function _bllua_getvar(name) return _G[name] end + function _bllua_setvar(name, val) _G[name] = val end + function _bllua_eval(code) return loadstring(code)() end + function _bllua_exec(fn) return dofile(fn, 2) end local function isValidCode(code) - local f,e = loadstring(code) - return f~=nil + local f, e = loadstring(code) + return f ~= nil end function _bllua_smarteval(code) - if (not code:find('^print%(')) and isValidCode('print('..code..')') then - code = 'print('..code..')' end - local f,e = loadstring(code) - if f then - return f() - else - print(e) - end + if (not code:find('^print%(')) and isValidCode('print(' .. code .. ')') then + code = 'print(' .. code .. ')' + end + local f, e = loadstring(code) + if f then + return f() + else + print(e) + end end function ts.setvar(name, val) - _bllua_ts.call('_bllua_set_var', name, val) + _bllua_ts.call('_bllua_set_var', name, val) end _bllua_ts.call('echo', ' Executed libts-lua.lua') diff --git a/src/util/std.lua b/src/util/std.lua index f1041cf..6cd00fd 100644 --- a/src/util/std.lua +++ b/src/util/std.lua @@ -1,347 +1,374 @@ - -- Basic functionality that should be standard in Lua -- Table / List -- Whether a table contains no keys function table.empty(t) - return next(t)~=nil + return next(t) ~= nil end + -- Apply a function to each key in a table function table.map(f, ...) - local ts = {...} - local u = {} - for k,_ in pairs(ts[1]) do - local args = {} - for j=1,#ts do args[j] = ts[j][i] end - u[i] = f(unpack(args)) - end - return u + local ts = { ... } + local u = {} + for k, _ in pairs(ts[1]) do + local args = {} + for j = 1, #ts do args[j] = ts[j][i] end + u[i] = f(unpack(args)) + end + return u end + function table.map_list(f, ...) - local ts = {...} - local u = {} - for i=1,#ts[1] do - local args = {} - for j=1,#ts do args[j] = ts[j][i] end - u[i] = f(unpack(args)) - end - return u + local ts = { ... } + local u = {} + for i = 1, #ts[1] do + local args = {} + for j = 1, #ts do args[j] = ts[j][i] end + u[i] = f(unpack(args)) + end + return u end + -- Swap keys/values function table.swap(t) - local u = {} - for k,v in pairs(t) do u[v] = k end - return u + local u = {} + for k, v in pairs(t) do u[v] = k end + return u end + -- Reverse a list function table.reverse(l) - local m = {} - for i=1,#l do m[#l-i+1] = l[i] end - return m + local m = {} + for i = 1, #l do m[#l - i + 1] = l[i] end + return m end + -- Whether a table is a list/array (has only monotonic integer keys) function table.islist(t) - local n = 0 - for i,_ in pairs(t) do - if type(i)~='number' or i%1~=0 then return false end - n = n+1 - end - return n==#t + local n = 0 + for i, _ in pairs(t) do + if type(i) ~= 'number' or i % 1 ~= 0 then return false end + n = n + 1 + end + return n == #t end + -- Append contents of other tables to first table function table.append(t, ...) - local a = {...} - for _,u in ipairs(a) do - for _,v in ipairs(u) do table.insert(t,v) end - end - return t + local a = { ... } + for _, u in ipairs(a) do + for _, v in ipairs(u) do table.insert(t, v) end + end + return t end + -- Create a new table containing all keys from any number of tables -- latter tables in the arg list override prior ones -- overlaps, NOT appends, integer keys function table.join(...) - local ts = {...} - local w = {} - for _,t in ipairs(ts) do - for k,v in pairs(t) do w[k] = v end - end - return w + local ts = { ... } + local w = {} + for _, t in ipairs(ts) do + for k, v in pairs(t) do w[k] = v end + end + return w end + -- Whether a table contains a certain value in any key -function table.contains(t,s) - for _,v in pairs(t) do - if v==s then return true end - end - return false +function table.contains(t, s) + for _, v in pairs(t) do + if v == s then return true end + end + return false end -function table.contains_list(t,s) - for _,v in ipairs(t) do - if v==s then return true end - end - return false + +function table.contains_list(t, s) + for _, v in ipairs(t) do + if v == s then return true end + end + return false end + -- Copy a table to another table function table.copy(t) - local u = {} - for k,v in pairs(t) do u[k] = v end - return u + local u = {} + for k, v in pairs(t) do u[k] = v end + return u end + function table.copy_list(l) - local m = {} - for i,v in ipairs(l) do m[i] = v end - return m + local m = {} + for i, v in ipairs(l) do m[i] = v end + return m end + -- Sort a table in a new copy function table.sortcopy(t, f) - local u = table.copy_list(t) - table.sort(u, f) - return u + local u = table.copy_list(t) + table.sort(u, f) + return u end + -- Remove a value from a table function table.removevalue(t, r) - local rem = {} - for k,v in pairs(t) do - if v==r then table.insert(rem, k) end - end - for _,k in ipairs(rem) do t[k] = nil end + local rem = {} + for k, v in pairs(t) do + if v == r then table.insert(rem, k) end + end + for _, k in ipairs(rem) do t[k] = nil end end + function table.removevalue_list(t, r) - for i = #t, 1, -1 do - if t[i]==r then - table.remove(t, i) - end - end + for i = #t, 1, -1 do + if t[i] == r then + table.remove(t, i) + end + end end + -- Export tables into formatted executable strings local function tabs(tabLevel) - return (' '):rep(tabLevel) + return (' '):rep(tabLevel) end local valueToString local function tableToString(t, tabLevel, seen) - if type(t)~='table' or (getmetatable(t) and getmetatable(t).__tostring) then - return tostring(t) - elseif table.islist(t) then - if #t==0 then - return '{}' - else - local strs = {} - local containsTables = false - for _,v in ipairs(t) do - if type(v)=='table' then containsTables = true end - table.insert(strs, valueToString(v, tabLevel+1, seen)..',') - end - if containsTables or #t>3 then - return '{\n'..tabs(tabLevel+1) - ..table.concat(strs, '\n'..tabs(tabLevel+1)) - ..'\n'..tabs(tabLevel)..'}' - else - return '{ '..table.concat(strs, ' ')..' }' - end - end - else - local containsNonStringKeys = false - for k,v in pairs(t) do - if type(k)~='string' or k:find('[^a-zA-Z0-9_]') then - containsNonStringKeys = true - elseif type(k)=='table' then - error('table.tostring: table contains a table as key, cannot serialize') - end - end - local strs = {} - if containsNonStringKeys then - for k,v in pairs(t) do - table.insert(strs, '\n'..tabs(tabLevel+1) - ..'['..valueToString(k, tabLevel+1, seen)..'] = ' - ..valueToString(v, tabLevel+1, seen)..',') - end - else - for k,v in pairs(t) do - table.insert(strs, '\n'..tabs(tabLevel+1) - ..k..' = '..valueToString(v, tabLevel+1, seen)..',') - end - end - return '{'..table.concat(strs)..'\n'..tabs(tabLevel)..'}' - end + if type(t) ~= 'table' or (getmetatable(t) and getmetatable(t).__tostring) then + return tostring(t) + elseif table.islist(t) then + if #t == 0 then + return '{}' + else + local strs = {} + local containsTables = false + for _, v in ipairs(t) do + if type(v) == 'table' then containsTables = true end + table.insert(strs, valueToString(v, tabLevel + 1, seen) .. ',') + end + if containsTables or #t > 3 then + return '{\n' .. tabs(tabLevel + 1) + .. table.concat(strs, '\n' .. tabs(tabLevel + 1)) + .. '\n' .. tabs(tabLevel) .. '}' + else + return '{ ' .. table.concat(strs, ' ') .. ' }' + end + end + else + local containsNonStringKeys = false + for k, v in pairs(t) do + if type(k) ~= 'string' or k:find('[^a-zA-Z0-9_]') then + containsNonStringKeys = true + elseif type(k) == 'table' then + error('table.tostring: table contains a table as key, cannot serialize') + end + end + local strs = {} + if containsNonStringKeys then + for k, v in pairs(t) do + table.insert(strs, '\n' .. tabs(tabLevel + 1) + .. '[' .. valueToString(k, tabLevel + 1, seen) .. '] = ' + .. valueToString(v, tabLevel + 1, seen) .. ',') + end + else + for k, v in pairs(t) do + table.insert(strs, '\n' .. tabs(tabLevel + 1) + .. k .. ' = ' .. valueToString(v, tabLevel + 1, seen) .. ',') + end + end + return '{' .. table.concat(strs) .. '\n' .. tabs(tabLevel) .. '}' + end end valueToString = function(v, tabLevel, seen) - local t = type(v) - if t=='table' then - if seen[v] then - return 'nil --[[ already seen: '..tostring(v)..' ]]' - else - seen[v] = true - return tableToString(v, tabLevel, seen) - end - elseif t=='string' then - return '\''..string.escape(v)..'\'' - elseif t=='number' or t=='boolean' then - return tostring(v) - else - --error('table.tostring: table contains a '..t..' value, cannot serialize') - return 'nil --[[ cannot serialize '..t..': '..tostring(v)..' ]]' - end + local t = type(v) + if t == 'table' then + if seen[v] then + return 'nil --[[ already seen: ' .. tostring(v) .. ' ]]' + else + seen[v] = true + return tableToString(v, tabLevel, seen) + end + elseif t == 'string' then + return '\'' .. string.escape(v) .. '\'' + elseif t == 'number' or t == 'boolean' then + return tostring(v) + else + --error('table.tostring: table contains a '..t..' value, cannot serialize') + return 'nil --[[ cannot serialize ' .. t .. ': ' .. tostring(v) .. ' ]]' + end end function table.tostring(t) - return tableToString(t, 0, {}) + return tableToString(t, 0, {}) end - -- String -- Split string into table by separator -- or by chars if no separator given -- if regex is not true, sep is treated as a regex pattern function string.split(str, sep, noregex) - if type(str)~='string' then - error('string.split: argument #1: expected string, got '..type(str), 2) end - if sep==nil or sep=='' then - local t = {} - local ns = #str - for x = 1, ns do - table.insert(t, str:sub(x, x)) - end - return t - elseif type(sep)=='string' then - local t = {} - if #str>0 then - local first = 1 - while true do - local last, newfirst = str:find(sep, first, noregex) - if not last then break end - table.insert(t, str:sub(first, last-1)) - first = newfirst+1 - end - table.insert(t, str:sub(first, #str)) - end - return t - else - error( - 'string.split: argument #2: expected string or nil, got '..type(sep), 2) - end -end --- Split string to a list of char bytes -function string.bytes(s) - local b = {} - for i=1,#s do - local c = s:sub(i,i) - table.insert(b, c:byte()) - end - return b -end --- Trim leading and trailing whitespace -function string.trim(s, ws) - ws = ws or ' \t\r\n' - return s:gsub('^['..ws..']+', ''):gsub('['..ws..']+$', '')..'' -end --- String slicing and searching using [] operator -local str_meta = getmetatable('') -local str_meta_index_old= str_meta.__index -function str_meta.__index(s,k) - if type(k)=='string' then - return str_meta_index_old[k] - elseif type(k)=='number' then - if k<0 then k = #s+k+1 end - return string.sub(s,k,k) - elseif type(k)=='table' then - local a = k[1]<0 and (#s+k[1]+1) or k[1] - local b = k[2]<0 and (#s+k[2]+1) or k[2] - return string.sub(s,a,b) - end -end --- String iterator -function string.chars(s) - local i = 0 - return function() - i = i+1 - if i<=#s then return s:sub(i,i) - else return nil end - end -end --- Escape sequences -local defaultEscapes = { - ['\\'] = '\\\\', - ['\''] = '\\\'', - ['\"'] = '\\\"', - ['\t'] = '\\t', - ['\r'] = '\\r', - ['\n'] = '\\n', - ['\0'] = '\\0', -} -function string.escape(s, escapes) - escapes = escapes or defaultEscapes - local t = {} - for i=1,#s do - local c = s:sub(i,i) - table.insert(t, escapes[c] or c) - end - return table.concat(t) -end -local defaultEscapeChar = '\\' -local defaultUnescapes = { - ['\\'] = '\\', - ['\''] = '\'', - ['\"'] = '\"', - ['t'] = '\t', - ['r'] = '\r', - ['n'] = '\n', - ['0'] = '\0', -} -function string.unescape(s, escapeChar, unescapes) - escapeChar = escapeChar or defaultEscapeChar - unescapes = unescapes or defaultUnescapes - local t = {} - local inEscape = false - for i=1,#s do - local c = s:sub(i,i) - if inEscape then - table.insert(t, unescapes[c] - or error('string.unescape: invalid escape sequence: \'' - ..escapeChar..c..'\'')) - elseif c==escapeChar then - inEscape = true - else - table.insert(t, c) - end - end - return table.concat(t) + if type(str) ~= 'string' then + error('string.split: argument #1: expected string, got ' .. type(str), 2) + end + if sep == nil or sep == '' then + local t = {} + local ns = #str + for x = 1, ns do + table.insert(t, str:sub(x, x)) + end + return t + elseif type(sep) == 'string' then + local t = {} + if #str > 0 then + local first = 1 + while true do + local last, newfirst = str:find(sep, first, noregex) + if not last then break end + table.insert(t, str:sub(first, last - 1)) + first = newfirst + 1 + end + table.insert(t, str:sub(first, #str)) + end + return t + else + error( + 'string.split: argument #2: expected string or nil, got ' .. type(sep), 2) + end end +-- Split string to a list of char bytes +function string.bytes(s) + local b = {} + for i = 1, #s do + local c = s:sub(i, i) + table.insert(b, c:byte()) + end + return b +end + +-- Trim leading and trailing whitespace +function string.trim(s, ws) + ws = ws or ' \t\r\n' + return s:gsub('^[' .. ws .. ']+', ''):gsub('[' .. ws .. ']+$', '') .. '' +end + +-- String slicing and searching using [] operator +local str_meta = getmetatable('') +local str_meta_index_old = str_meta.__index +function str_meta.__index(s, k) + if type(k) == 'string' then + return str_meta_index_old[k] + elseif type(k) == 'number' then + if k < 0 then k = #s + k + 1 end + return string.sub(s, k, k) + elseif type(k) == 'table' then + local a = k[1] < 0 and (#s + k[1] + 1) or k[1] + local b = k[2] < 0 and (#s + k[2] + 1) or k[2] + return string.sub(s, a, b) + end +end + +-- String iterator +function string.chars(s) + local i = 0 + return function() + i = i + 1 + if i <= #s then + return s:sub(i, i) + else + return nil + end + end +end + +-- Escape sequences +local defaultEscapes = { + ['\\'] = '\\\\', + ['\''] = '\\\'', + ['\"'] = '\\\"', + ['\t'] = '\\t', + ['\r'] = '\\r', + ['\n'] = '\\n', + ['\0'] = '\\0', +} +function string.escape(s, escapes) + escapes = escapes or defaultEscapes + local t = {} + for i = 1, #s do + local c = s:sub(i, i) + table.insert(t, escapes[c] or c) + end + return table.concat(t) +end + +local defaultEscapeChar = '\\' +local defaultUnescapes = { + ['\\'] = '\\', + ['\''] = '\'', + ['\"'] = '\"', + ['t'] = '\t', + ['r'] = '\r', + ['n'] = '\n', + ['0'] = '\0', +} +function string.unescape(s, escapeChar, unescapes) + escapeChar = escapeChar or defaultEscapeChar + unescapes = unescapes or defaultUnescapes + local t = {} + local inEscape = false + for i = 1, #s do + local c = s:sub(i, i) + if inEscape then + table.insert(t, unescapes[c] + or error('string.unescape: invalid escape sequence: \'' + .. escapeChar .. c .. '\'')) + elseif c == escapeChar then + inEscape = true + else + table.insert(t, c) + end + end + return table.concat(t) +end -- IO io = io or {} -- Read entire file at once, return nil,err if access failed function io.readall(filename) - local fi,err = io.open(filename, 'rb') - if not fi then return nil,err end - local s = fi:read("*a") - fi:close() - return s -end --- Write data to file all at once, return true if success / false,err if failure -function io.writeall(filename, data) - local fi,err = io.open(filename, 'wb') - if not fi then return false,err end - fi:write(data) - fi:close() - return true,nil + local fi, err = io.open(filename, 'rb') + if not fi then return nil, err end + local s = fi:read("*a") + fi:close() + return s end +-- Write data to file all at once, return true if success / false,err if failure +function io.writeall(filename, data) + local fi, err = io.open(filename, 'wb') + if not fi then return false, err end + fi:write(data) + fi:close() + return true, nil +end -- Math -- Round function math.round(x) - return math.floor(x+0.5) + return math.floor(x + 0.5) end + -- Mod that accounts for floating point inaccuracy -function math.mod(a,b) - local m = a%b - if m==0 or math.abs(m)<1e-15 or math.abs(m-b)<1e-15 then return 0 - else return m end +function math.mod(a, b) + local m = a % b + if m == 0 or math.abs(m) < 1e-15 or math.abs(m - b) < 1e-15 then + return 0 + else + return m + end end + -- Clamp value between min and max function math.clamp(v, n, x) - return math.min(x, math.max(v, n)) + return math.min(x, math.max(v, n)) end diff --git a/src/util/vector.lua b/src/util/vector.lua index f166719..7c280d8 100644 --- a/src/util/vector.lua +++ b/src/util/vector.lua @@ -1,218 +1,237 @@ - -- Vector math class with operators local vector_meta local vector_new local function vector_check(v, n, name, argn) - if not v.__is_vector then - error('vector '..name..': argument #'..(argn or 1) - ..': expected vector, got '..type(v), n+1) end + if not v.__is_vector then + error('vector ' .. name .. ': argument #' .. (argn or 1) + .. ': expected vector, got ' .. type(v), n + 1) + end end local function vector_checksamelen(v1, v2, name) - vector_check(v1, 3, name, 1) - vector_check(v2, 3, name, 2) - if #v1~=#v2 then - error('vector '..name..': vector lengths do not match (lengths are ' - ..#v1..' and '..#v2..')', 3) end - return #v1 + vector_check(v1, 3, name, 1) + vector_check(v2, 3, name, 2) + if #v1 ~= #v2 then + error('vector ' .. name .. ': vector lengths do not match (lengths are ' + .. #v1 .. ' and ' .. #v2 .. ')', 3) + end + return #v1 end local function vector_checklen(v1, v2, name, len) - vector_check(v1, 3, name, 1) - vector_check(v2, 3, name, 2) - if #v1~=len or #v2~=len then - error('vector '..name..': vector lengths are not '..len..' (lengths are ' - ..#v1..' and '..#v2..')', 3) end + vector_check(v1, 3, name, 1) + vector_check(v2, 3, name, 2) + if #v1 ~= len or #v2 ~= len then + error('vector ' .. name .. ': vector lengths are not ' .. len .. ' (lengths are ' + .. #v1 .. ' and ' .. #v2 .. ')', 3) + end end local function vector_opnnn(name, op) - return function(v1, v2) - local len = vector_checksamelen(v1, v2, name) - local v3 = {} - for i = 1, len do - v3[i] = op(v1[i], v2[i]) - end - return vector_new(v3) - end + return function(v1, v2) + local len = vector_checksamelen(v1, v2, name) + local v3 = {} + for i = 1, len do + v3[i] = op(v1[i], v2[i]) + end + return vector_new(v3) + end end local function vector_opnxn(name, op) - return function(v1, v2) - local v1v = type(v1)=='table' and v1.__is_vector - local v2v = type(v2)=='table' and v2.__is_vector - if v1v and v2v then - local len = vector_checksamelen(v1, v2, name) - local v3 = {} - for i = 1, len do - v3[i] = op(v1[i], v2[i]) - end - return vector_new(v3) - else - if v2v then v1,v2 = v2,v1 end - local len = #v1 - local v3 = {} - for i = 1, len do - v3[i] = op(v1[i], v2) - end - return vector_new(v3) - end - end + return function(v1, v2) + local v1v = type(v1) == 'table' and v1.__is_vector + local v2v = type(v2) == 'table' and v2.__is_vector + if v1v and v2v then + local len = vector_checksamelen(v1, v2, name) + local v3 = {} + for i = 1, len do + v3[i] = op(v1[i], v2[i]) + end + return vector_new(v3) + else + if v2v then v1, v2 = v2, v1 end + local len = #v1 + local v3 = {} + for i = 1, len do + v3[i] = op(v1[i], v2) + end + return vector_new(v3) + end + end end local function vector_opn0n(name, op) - return function(v1) - --vector_check(v1, 1, name) - local len = #v1 - local v2 = {} - for i = 1, len do - v2[i] = op(v1[i]) - end - return vector_new(v2) - end + return function(v1) + --vector_check(v1, 1, name) + local len = #v1 + local v2 = {} + for i = 1, len do + v2[i] = op(v1[i]) + end + return vector_new(v2) + end end -local vector_indices = {x = 1, y = 2, z = 3, w = 4, r = 1, g = 2, b = 3, a = 4} +local vector_indices = { x = 1, y = 2, z = 3, w = 4, r = 1, g = 2, b = 3, a = 4 } local vector_meta = { - __is_vector = true, - __index = function(t, k) - if tonumber(k) then return rawget(t, k) - elseif vector_indices[k] then return rawget(t, vector_indices[k]) - else return getmetatable(t)[k] - end - end, - __newindex = function(t, k, v) - if tonumber(k) then rawset(t, k, v) - elseif vector_indices[k] then rawset(t, vector_indices[k], v) - else return - end - end, - __add = vector_opnnn('add', function(x1, x2) return x1+x2 end), - __sub = vector_opnnn('sub', function(x1, x2) return x1-x2 end), - __mul = vector_opnxn('mul', function(x1, x2) return x1*x2 end), - __div = vector_opnxn('div', function(x1, x2) return x1/x2 end), - __pow = vector_opnxn('pow', function(x1, x2) return x1^x2 end), - __unm = vector_opn0n('inv', function(x1) return -x1 end), - __concat = nil, - --__len = function(v1) return #v1 end, - __len = nil, - __eq = function(v1, v2) - local len = vector_checksamelen(v1, v2, 'equals') - for i = 1, len do - if v1[i]~=v2[i] then return false end - end - return true - end, - __lt = nil, - __le = nil, - __call = nil, - abs = vector_opn0n('abs', math.abs), - length = function(v1) - --vector_check(v1, 2, 'length') - local len = #v1 - local l = 0 - for i = 1, len do - l = l + v1[i]^2 - end - return math.sqrt(l) - end, - normalize = function(v1) - --vector_check(v1, 2, 'normal') - local length = v1:length() - local len = #v1 - local v3 = {} - for i = 1, len do - if length==0 then v3[i] = 0 - else v3[i] = v1[i]/length end - end - return vector_new(v3) - end, - __tostring = function(v1) - --vector_check(v1, 2, 'tostring') - local st = {} - local len = #v1 - for i = 1, len do - table.insert(st, tostring(v1[i])) - end - return 'vector{ '..table.concat(st, ', ')..' }' - end, - unpack = function(v1) return unpack(v1) end, - floor = vector_opn0n('floor', function(x1) return math.floor(x1) end), - ceil = vector_opn0n('ceil' , function(x1) return math.ceil (x1) end), - round = vector_opn0n('round', function(x1) return math.floor(x1+0.5) end), - dot = function(v1, v2) - local len = vector_checksamelen(v1, v2, 'dot') - local x = 0 - for i = 1, len do - x = x + v1[i]*v2[i] - end - return x - end, - cross = function(v1, v2) - vector_checklen(v1, v2, 'cross', 3) - return vector_new{ - v1[2]*v2[3] - v1[3]*v2[2], - v1[3]*v2[1] - v1[1]*v2[3], - v1[1]*v2[2] - v1[2]*v2[1], - } - end, - rotateByAngleId = function(v1, r) - --vector_check(v1, 2, 'rotate') - if type(r)~='number' or r%1~=0 then - error('vector rotateByAngleId: invalid rotation '..tostring(r), 2) end - r = r%4 - local v2 - if r==0 then v2 = vector_new{ v1[1], v1[2], v1[3] } - elseif r==1 then v2 = vector_new{ v1[2], -v1[1], v1[3] } - elseif r==2 then v2 = vector_new{ -v1[1], -v1[2], v1[3] } - elseif r==3 then v2 = vector_new{ -v1[2], v1[1], v1[3] } - else error('vector rotateByAngleId: invalid rotation '..r, 2) end - return v2 - end, - rotateZ = function(v, r) - --vector_check(v, 2, 'rotate2d') - if type(r)~='number' then - error('vector rotateZ: invalid rotation '..tostring(r), 2) end - local len = math.sqrt(v[1]^2 + v[2]^2) - local ang = math.atan2(v[2], v[1]) + r - local v2 = vector_new{ math.cos(ang)*len, math.sin(ang)*len } - return v2 - end, - tsString = function(v) - --vector_check(v, 2, 'tsString') - return table.concat(v, ' ') - end, - distance = function(v1, v2) - local len = vector_checksamelen(v1, v2, 'distance') - local sum = 0 - for i=1,len do - sum = sum + (v1[i] - v2[i])^2 - end - return math.sqrt(sum) - end, - copy = function(v) - --vector_check(v, 2, 'copy') - return vector_new(v) - end, + __is_vector = true, + __index = function(t, k) + if tonumber(k) then + return rawget(t, k) + elseif vector_indices[k] then + return rawget(t, vector_indices[k]) + else + return getmetatable(t)[k] + end + end, + __newindex = function(t, k, v) + if tonumber(k) then + rawset(t, k, v) + elseif vector_indices[k] then + rawset(t, vector_indices[k], v) + else + return + end + end, + __add = vector_opnnn('add', function(x1, x2) return x1 + x2 end), + __sub = vector_opnnn('sub', function(x1, x2) return x1 - x2 end), + __mul = vector_opnxn('mul', function(x1, x2) return x1 * x2 end), + __div = vector_opnxn('div', function(x1, x2) return x1 / x2 end), + __pow = vector_opnxn('pow', function(x1, x2) return x1 ^ x2 end), + __unm = vector_opn0n('inv', function(x1) return -x1 end), + __concat = nil, + --__len = function(v1) return #v1 end, + __len = nil, + __eq = function(v1, v2) + local len = vector_checksamelen(v1, v2, 'equals') + for i = 1, len do + if v1[i] ~= v2[i] then return false end + end + return true + end, + __lt = nil, + __le = nil, + __call = nil, + abs = vector_opn0n('abs', math.abs), + length = function(v1) + --vector_check(v1, 2, 'length') + local len = #v1 + local l = 0 + for i = 1, len do + l = l + v1[i] ^ 2 + end + return math.sqrt(l) + end, + normalize = function(v1) + --vector_check(v1, 2, 'normal') + local length = v1:length() + local len = #v1 + local v3 = {} + for i = 1, len do + if length == 0 then + v3[i] = 0 + else + v3[i] = v1[i] / length + end + end + return vector_new(v3) + end, + __tostring = function(v1) + --vector_check(v1, 2, 'tostring') + local st = {} + local len = #v1 + for i = 1, len do + table.insert(st, tostring(v1[i])) + end + return 'vector{ ' .. table.concat(st, ', ') .. ' }' + end, + unpack = function(v1) return unpack(v1) end, + floor = vector_opn0n('floor', function(x1) return math.floor(x1) end), + ceil = vector_opn0n('ceil', function(x1) return math.ceil(x1) end), + round = vector_opn0n('round', function(x1) return math.floor(x1 + 0.5) end), + dot = function(v1, v2) + local len = vector_checksamelen(v1, v2, 'dot') + local x = 0 + for i = 1, len do + x = x + v1[i] * v2[i] + end + return x + end, + cross = function(v1, v2) + vector_checklen(v1, v2, 'cross', 3) + return vector_new { + v1[2] * v2[3] - v1[3] * v2[2], + v1[3] * v2[1] - v1[1] * v2[3], + v1[1] * v2[2] - v1[2] * v2[1], + } + end, + rotateByAngleId = function(v1, r) + --vector_check(v1, 2, 'rotate') + if type(r) ~= 'number' or r % 1 ~= 0 then + error('vector rotateByAngleId: invalid rotation ' .. tostring(r), 2) + end + r = r % 4 + local v2 + if r == 0 then + v2 = vector_new { v1[1], v1[2], v1[3] } + elseif r == 1 then + v2 = vector_new { v1[2], -v1[1], v1[3] } + elseif r == 2 then + v2 = vector_new { -v1[1], -v1[2], v1[3] } + elseif r == 3 then + v2 = vector_new { -v1[2], v1[1], v1[3] } + else + error('vector rotateByAngleId: invalid rotation ' .. r, 2) + end + return v2 + end, + rotateZ = function(v, r) + --vector_check(v, 2, 'rotate2d') + if type(r) ~= 'number' then + error('vector rotateZ: invalid rotation ' .. tostring(r), 2) + end + local len = math.sqrt(v[1] ^ 2 + v[2] ^ 2) + local ang = math.atan2(v[2], v[1]) + r + local v2 = vector_new { math.cos(ang) * len, math.sin(ang) * len } + return v2 + end, + tsString = function(v) + --vector_check(v, 2, 'tsString') + return table.concat(v, ' ') + end, + distance = function(v1, v2) + local len = vector_checksamelen(v1, v2, 'distance') + local sum = 0 + for i = 1, len do + sum = sum + (v1[i] - v2[i]) ^ 2 + end + return math.sqrt(sum) + end, + copy = function(v) + --vector_check(v, 2, 'copy') + return vector_new(v) + end, } vector_new = function(vi) - if vi then - if type(vi)=='string' then - local vi2 = {} - for val in vi:gmatch('[0-9%.%-e]+') do - table.insert(vi2, tonumber(val)) - end - vi = vi2 - elseif type(vi)~='table' then - error('vector: argument #1: expected input table, got '..type(vi), 2) - end - local v = {} - if #vi>0 then - for i = 1, #vi do v[i] = vi[i] end - else - for n, i in pairs(vector_indices) do v[i] = vi[n] end - if #v==0 then - error('vector: argument #1: table contains no values', 2) - end - end - setmetatable(v, vector_meta) - return v - else - error('vector: argument #1: expected input table, got nil', 2) - end + if vi then + if type(vi) == 'string' then + local vi2 = {} + for val in vi:gmatch('[0-9%.%-e]+') do + table.insert(vi2, tonumber(val)) + end + vi = vi2 + elseif type(vi) ~= 'table' then + error('vector: argument #1: expected input table, got ' .. type(vi), 2) + end + local v = {} + if #vi > 0 then + for i = 1, #vi do v[i] = vi[i] end + else + for n, i in pairs(vector_indices) do v[i] = vi[n] end + if #v == 0 then + error('vector: argument #1: table contains no values', 2) + end + end + setmetatable(v, vector_meta) + return v + else + error('vector: argument #1: expected input table, got nil', 2) + end end vector = vector_new -- 2.49.1 From cbd0c294950ee897880534db4357b0c4c4767fc7 Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Sun, 5 Oct 2025 19:50:32 -0400 Subject: [PATCH 05/22] Create settings.json --- .vscode/settings.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a34bb45 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,12 @@ +{ + "Lua.diagnostics.globals": [ + "_bllua_ts", + "_bllua_requiresecure", + "_bllua_on_unload" + ], + "Lua.runtime.version": "Lua 5.1", + "Lua.diagnostics.disable": [ + "lowercase-global", + "undefined-global" + ] +} \ No newline at end of file -- 2.49.1 From 0815a6d2294791903b89f8443d6b688f7202600b Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Sun, 5 Oct 2025 19:51:10 -0400 Subject: [PATCH 06/22] Create .editorconfig --- .editorconfig | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d03e464 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true -- 2.49.1 From ee784869f169d99da1681bcbffa88b9b1adee28c Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Sun, 5 Oct 2025 19:52:41 -0400 Subject: [PATCH 07/22] Merge msys_compile.bat into compile.bat --- compile.bat | 12 ++---- compiling.md | 104 +++++++++++++++++++++++------------------------ msys_compile.bat | 13 ------ 3 files changed, 55 insertions(+), 74 deletions(-) delete mode 100644 msys_compile.bat diff --git a/compile.bat b/compile.bat index 921f37f..660075f 100644 --- a/compile.bat +++ b/compile.bat @@ -1,19 +1,13 @@ @echo off cd /d %~dp0 -rem ensure build directory exists +set "PATH=C:\msys64\mingw32\bin;%PATH%" + if not exist build mkdir build set buildargs=-Wall -Werror -m32 -shared -Isrc -Iinc/tsfuncs -Iinc/lua -lpsapi -L. -llua5.1 -static-libgcc -static-libstdc++ echo on - g++ src/bllua4.cpp %buildargs% -o build\BlockLua.dll -@rem g++ -DBLLUA_UNSAFE src/bllua4.cpp %buildargs% -o BlockLua-Unsafe.dll - +g++ -DBLLUA_UNSAFE src/bllua4.cpp %buildargs% -o build\BlockLua-Unsafe.dll @echo off - -rem objdump -d build\BlockLua.dll > build\BlockLua.dll.dump.txt -rem objdump -d build\BlockLua-Unsafe.dll > build\BlockLua-Unsafe.dll.dump.txt - -pause diff --git a/compiling.md b/compiling.md index 3d3fa93..0dd00b0 100644 --- a/compiling.md +++ b/compiling.md @@ -1,52 +1,52 @@ -## Compiling on Windows with MSYS2 (32-bit) - -Follow these steps to build `BlockLua.dll` for 32-bit Windows using MSYS2's MinGW-w64 i686 toolchain. - -### 1) Install MSYS2 - -- Download and install MSYS2 from `https://www.msys2.org/`. -- After installing, open the "MSYS2 MSYS" terminal once and update the package database if prompted. - -### 2) Install required packages (i686 / 32-bit) - -Run this in the "MSYS2 MSYS" or "MSYS2 MinGW 32-bit" terminal: - -```bash -pacman -Sy --needed mingw-w64-i686-toolchain mingw-w64-i686-binutils mingw-w64-i686-lua51 -``` - -What these packages are for: - -- mingw-w64-i686-toolchain: 32-bit C/C++ compiler suite (g++, libstdc++, runtime libs) to build Windows binaries. -- mingw-w64-i686-binutils: Linker and binary utilities (ld, as, objdump) used by the compiler and for optional inspection. -- mingw-w64-i686-lua51: Lua 5.1 development files for 32-bit (import library). We use its import library to link; at runtime you can use the provided `lua5.1.dll`. - -### 3) Build (PowerShell, recommended) - -- Open PowerShell in the repo root. -- Run the script: - -```powershell -msys_compile.bat -``` - -What the script does: - -- Temporarily prepends `C:\\msys64\\mingw32\\bin` to PATH so the 32-bit toolchain is used. -- Compiles the project with `-m32` and links against `lua5.1`. -- Produces `build\BlockLua.dll` and `build\BlockLua-Unsafe.dll`. - -### 4) Optional: Verify 32-bit output - -If you installed binutils, you can check the architecture: - -```powershell -objdump -f build\BlockLua.dll | Select-String i386 -``` - -You should see `architecture: i386` in the output. - -### Notes - -- Ensure you installed the i686 (32-bit) variants of the packages; x86_64 packages won’t work for a 32-bit build. -- If the linker cannot find `-llua5.1`, confirm `mingw-w64-i686-lua51` is installed and you are using the `mingw32` toolchain (not `x86_64`). +## Compiling on Windows with MSYS2 (32-bit) + +Follow these steps to build `BlockLua.dll` for 32-bit Windows using MSYS2's MinGW-w64 i686 toolchain. + +### 1) Install MSYS2 + +- Download and install MSYS2 from `https://www.msys2.org/`. +- After installing, open the "MSYS2 MSYS" terminal once and update the package database if prompted. + +### 2) Install required packages (i686 / 32-bit) + +Run this in the "MSYS2 MSYS" or "MSYS2 MinGW 32-bit" terminal: + +```bash +pacman -Sy --needed mingw-w64-i686-toolchain mingw-w64-i686-binutils mingw-w64-i686-lua51 +``` + +What these packages are for: + +- mingw-w64-i686-toolchain: 32-bit C/C++ compiler suite (g++, libstdc++, runtime libs) to build Windows binaries. +- mingw-w64-i686-binutils: Linker and binary utilities (ld, as, objdump) used by the compiler and for optional inspection. +- mingw-w64-i686-lua51: Lua 5.1 development files for 32-bit (import library). We use its import library to link; at runtime you can use the provided `lua5.1.dll`. + +### 3) Build (PowerShell, recommended) + +- Open PowerShell in the repo root. +- Run the script: + +```powershell +compile.bat +``` + +What the script does: + +- Temporarily prepends `C:\\msys64\\mingw32\\bin` to PATH so the 32-bit toolchain is used. +- Compiles the project with `-m32` and links against `lua5.1`. +- Produces `build\BlockLua.dll` and `build\BlockLua-Unsafe.dll`. + +### 4) Optional: Verify 32-bit output + +If you installed binutils, you can check the architecture: + +```powershell +objdump -f build\BlockLua.dll | Select-String i386 +``` + +You should see `architecture: i386` in the output. + +### Notes + +- Ensure you installed the i686 (32-bit) variants of the packages; x86_64 packages won’t work for a 32-bit build. +- If the linker cannot find `-llua5.1`, confirm `mingw-w64-i686-lua51` is installed and you are using the `mingw32` toolchain (not `x86_64`). diff --git a/msys_compile.bat b/msys_compile.bat deleted file mode 100644 index 2d3243b..0000000 --- a/msys_compile.bat +++ /dev/null @@ -1,13 +0,0 @@ -@echo off -cd /d %~dp0 - -set "PATH=C:\msys64\mingw32\bin;%PATH%" - -if not exist build mkdir build - -set buildargs=-Wall -Werror -m32 -shared -Isrc -Iinc/tsfuncs -Iinc/lua -lpsapi -L. -llua5.1 -static-libgcc -static-libstdc++ - -echo on -g++ src/bllua4.cpp %buildargs% -o build\BlockLua.dll -g++ -DBLLUA_UNSAFE src/bllua4.cpp %buildargs% -o build\BlockLua-Unsafe.dll -@echo off -- 2.49.1 From 66ed695010f99005568597b855c585ddd14b30bb Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Mon, 6 Oct 2025 10:01:54 -0400 Subject: [PATCH 08/22] Update libbl.lua --- src/util/libbl.lua | 1592 +++++++++++++++++++++++--------------------- 1 file changed, 835 insertions(+), 757 deletions(-) diff --git a/src/util/libbl.lua b/src/util/libbl.lua index efd5889..237d59c 100644 --- a/src/util/libbl.lua +++ b/src/util/libbl.lua @@ -10,182 +10,188 @@ bl = bl or {} -- Misc -- Apply a function to each element in a list, building a new list from the returns -local function map(t,f) - local u = {} - for i,v in ipairs(t) do - u[i] = f(v) - end - return u +local function map(t, f) + local u = {} + for i, v in ipairs(t) do + u[i] = f(v) + end + return u end local function isValidFuncName(name) - return type(name)=='string' and name:find('^[a-zA-Z0-9_]+$') + return type(name) == 'string' and name:find('^[a-zA-Z0-9_]+$') end local function isValidFuncNameNs(name) - return type(name)=='string' and ( - name:find('^[a-zA-Z0-9_]+$') or - name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+$') ) + return type(name) == 'string' and ( + name:find('^[a-zA-Z0-9_]+$') or + name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+$')) end local function isValidFuncNameNsArgn(name) - return type(name)=='string' and ( - name:find('^[a-zA-Z0-9_]+$') or - name:find('^[a-zA-Z0-9_]+%.[a-zA-Z0-9_]+$') or - name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+$') or - name:find('^[a-zA-Z0-9_]+:[0-9]+$') or - name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+:[0-9]+$') ) + return type(name) == 'string' and ( + name:find('^[a-zA-Z0-9_]+$') or + name:find('^[a-zA-Z0-9_]+%.[a-zA-Z0-9_]+$') or + name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+$') or + name:find('^[a-zA-Z0-9_]+:[0-9]+$') or + name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+:[0-9]+$')) end -- Whether a var can be converted into a TS vector local function isTsVector(val) - if type(val)~='table' then return false end - if #val~=3 and #val~=2 then return false end - if val.__is_vector then return true end - for _,v in ipairs(val) do - if type(v)~='number' then return false end - end - return true + if type(val) ~= 'table' then return false end + if #val ~= 3 and #val ~= 2 then return false end + if val.__is_vector then return true end + for _, v in ipairs(val) do + if type(v) ~= 'number' then return false end + end + return true end -- Use strings for object types instead of integer bitmasks like in TS local tsTypesByName = { - ['all'] = -1, - ['static'] = 1, - ['environment'] = 2, - ['terrain'] = 4, - ['water'] = 16, - ['trigger'] = 32, - ['marker'] = 64, - ['gamebase'] = 1024, - ['shapebase'] = 2048, - ['camera'] = 4096, - ['staticshape'] = 8192, - ['player'] = 16384, - ['item'] = 32768, - ['vehicle'] = 65536, - ['vehicleblocker'] = 131072, - ['projectile'] = 262144, - ['explosion'] = 524288, - ['corpse'] = 1048576, - ['debris'] = 4194304, - ['physicalzone'] = 8388608, - ['staticts'] = 16777216, - ['brick'] = 33554432, - ['brickalways'] = 67108864, - ['staticrendered'] = 134217728, - ['damagableitem'] = 268435456, + ['all'] = -1, + ['static'] = 1, + ['environment'] = 2, + ['terrain'] = 4, + ['water'] = 16, + ['trigger'] = 32, + ['marker'] = 64, + ['gamebase'] = 1024, + ['shapebase'] = 2048, + ['camera'] = 4096, + ['staticshape'] = 8192, + ['player'] = 16384, + ['item'] = 32768, + ['vehicle'] = 65536, + ['vehicleblocker'] = 131072, + ['projectile'] = 262144, + ['explosion'] = 524288, + ['corpse'] = 1048576, + ['debris'] = 4194304, + ['physicalzone'] = 8388608, + ['staticts'] = 16777216, + ['brick'] = 33554432, + ['brickalways'] = 67108864, + ['staticrendered'] = 134217728, + ['damagableitem'] = 268435456, } local tsTypesByNum = {} -for k,v in pairs(tsTypesByName) do - tsTypesByNum[v] = k +for k, v in pairs(tsTypesByName) do + tsTypesByNum[v] = k end -- Type conversion local toTsObject -- Convert a string from TS into a boolean -- Note: Nonempty nonnumeric strings evaluate to 1, unlike in TS -local function tsBool(v) return v~='' and v~='0' end +local function tsBool(v) return v ~= '' and v ~= '0' end -- Convert a Lua var into a TS string, or error if not possible local function valToTs(val) - if val==nil then -- nil -> '' - return '' - elseif type(val)=='boolean' then -- bool -> 0 or 1 - return val and '1' or '0' - elseif type(val)=='number' then -- number - return tostring(val) - elseif type(val)=='string' then -- string - return val - elseif type(val)=='table' then - if val._tsObjectId then -- object -> object id - return tostring(val._tsObjectId) - elseif isTsVector(val) then -- vector - > 3 numbers - return table.concat(val, ' ') - elseif #val==2 and isTsVector(val[1]) and isTsVector(val[2]) then - -- box - > 6 numbers - return table.concat(val[1], ' ')..' '..table.concat(val[2], ' ') - else - error('valToTs: could not convert table', 3) - end - else - error('valToTs: could not convert '..type(val), 3) - end + if val == nil then -- nil -> '' + return '' + elseif type(val) == 'boolean' then -- bool -> 0 or 1 + return val and '1' or '0' + elseif type(val) == 'number' then -- number + return tostring(val) + elseif type(val) == 'string' then -- string + return val + elseif type(val) == 'table' then + if val._tsObjectId then -- object -> object id + return tostring(val._tsObjectId) + elseif isTsVector(val) then -- vector - > 3 numbers + return table.concat(val, ' ') + elseif #val == 2 and isTsVector(val[1]) and isTsVector(val[2]) then + -- box - > 6 numbers + return table.concat(val[1], ' ') .. ' ' .. table.concat(val[2], ' ') + else + error('valToTs: could not convert table', 3) + end + else + error('valToTs: could not convert ' .. type(val), 3) + end end local fromTsForceTypes = { - ['boolean'] = tsBool, - ['object'] = function(val) toTsObject(val) end, -- toTsObject not defined yet - ['string'] = tostring, + ['boolean'] = tsBool, + ['object'] = function(val) toTsObject(val) end, -- toTsObject not defined yet + ['string'] = tostring, } local function convertValFromTs(val, typ) - return fromTsForceTypes[typ](val) or - error('valFromTs: invalid force type '..typ, 4) + return fromTsForceTypes[typ](val) or + error('valFromTs: invalid force type ' .. typ, 4) end bl._forceType = bl._forceType or {} local function valFromTs(val, name, name2) -- todo: ensure name and name2 are already lowercase - if type(val)~='string' then - error('valFromTs: expected string, got '..type(val), 3) end - if name then - name = name:lower() - if bl._forceType[name] then - return convertValFromTs(val, bl._forceType[name]) - end - end - if name2 then - name2 = name2:lower() - if bl._forceType[name2] then - return convertValFromTs(val, bl._forceType[name2]) - end - end - -- '' -> nil - if val=='' then return nil end - -- number - local num = tonumber(val) - if num then return num end - -- vector - local xS,yS,zS = val:match('^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') - if xS then return vector{tonumber(xS),tonumber(yS),tonumber(zS)} end - local x1S,y1S,z1S,x2S,y2S,z2S = val:match( - '^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+) '.. - '(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') - -- box (2 vectors) - if x1S then return { - vector{tonumber(x1S),tonumber(y1S),tonumber(z1S)}, - vector{tonumber(x2S),tonumber(y2S),tonumber(z2S)} } end - -- string - return val + if type(val) ~= 'string' then + error('valFromTs: expected string, got ' .. type(val), 3) + end + if name then + name = name:lower() + if bl._forceType[name] then + return convertValFromTs(val, bl._forceType[name]) + end + end + if name2 then + name2 = name2:lower() + if bl._forceType[name2] then + return convertValFromTs(val, bl._forceType[name2]) + end + end + -- '' -> nil + if val == '' then return nil end + -- number + local num = tonumber(val) + if num then return num end + -- vector + local xS, yS, zS = val:match('^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') + if xS then return vector { tonumber(xS), tonumber(yS), tonumber(zS) } end + local x1S, y1S, z1S, x2S, y2S, z2S = val:match( + '^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+) ' .. + '(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') + -- box (2 vectors) + if x1S then + return { + vector { tonumber(x1S), tonumber(y1S), tonumber(z1S) }, + vector { tonumber(x2S), tonumber(y2S), tonumber(z2S) } } + end + -- string + return val end local function arglistFromTs(name, argsS) - local args = {} - for i,arg in ipairs(argsS) do - args[i] = valFromTs(arg, name..':'..i) - end - return args + local args = {} + for i, arg in ipairs(argsS) do + args[i] = valFromTs(arg, name .. ':' .. i) + end + return args end local function arglistToTs(args) - return map(args, valToTs) + return map(args, valToTs) end local function classFromForceTypeStr(name) - local class, rest = name:match('^([a-zA-Z0-9_]+)(::.+)$') - if not class then - class, rest = name:match('^([a-zA-Z0-9_]+)(%..+)$') end - return class,rest + local class, rest = name:match('^([a-zA-Z0-9_]+)(::.+)$') + if not class then + class, rest = name:match('^([a-zA-Z0-9_]+)(%..+)$') + end + return class, rest end local setForceType setForceType = function(ftname, typ) - if typ~=nil and not fromTsForceTypes[typ] then - error('bl.type: invalid type \''..typ..'\'', 2) end - if not isValidFuncNameNsArgn(ftname) then - error('bl.type: invalid function or variable name \''..ftname..'\'', 2) end - - ftname = ftname:lower() - - bl._forceType[ftname] = typ - - -- apply to child classes if present - local cname, rest = classFromForceTypeStr(ftname) - if cname then - local meta = bl._objectUserMetas[cname] - if meta then - for chcname,_ in pairs(meta._children) do - setForceType(chcname..rest, typ) - end - end - end + if typ ~= nil and not fromTsForceTypes[typ] then + error('bl.type: invalid type \'' .. typ .. '\'', 2) + end + if not isValidFuncNameNsArgn(ftname) then + error('bl.type: invalid function or variable name \'' .. ftname .. '\'', 2) + end + + ftname = ftname:lower() + + bl._forceType[ftname] = typ + + -- apply to child classes if present + local cname, rest = classFromForceTypeStr(ftname) + if cname then + local meta = bl._objectUserMetas[cname] + if meta then + for chcname, _ in pairs(meta._children) do + setForceType(chcname .. rest, typ) + end + end + end end bl.type = setForceType @@ -193,787 +199,859 @@ bl.type = setForceType -- Value detection local function isTsObject(t) - return type(t)=='table' and t._tsObjectId~=nil + return type(t) == 'table' and t._tsObjectId ~= nil end local function tsIsObject(name) return tsBool(_bllua_ts.call('isObject', name)) end local function tsIsFunction(name) return tsBool(_bllua_ts.call('isFunction', name)) end local function tsIsFunctionNs(ns, name) return tsBool(_bllua_ts.call('isFunction', ns, name)) end local function tsIsFunctionNsname(nsname) - local ns, name = nsname:match('^([^:]+)::([^:]+)$') - if ns then return tsIsFunctionNs(ns, name) - else return tsIsFunction(nsname) end + local ns, name = nsname:match('^([^:]+)::([^:]+)$') + if ns then + return tsIsFunctionNs(ns, name) + else + return tsIsFunction(nsname) + end end function bl.isObject(obj) - if isTsObject(obj) then - obj = obj._tsObjectId - elseif type(obj)=='number' then - obj = tostring(obj) - elseif type(obj)~='string' then - error('bl.isObject: argument #1: expected torque object, number, or string', 2) - end - return tsIsObject(obj) + if isTsObject(obj) then + obj = obj._tsObjectId + elseif type(obj) == 'number' then + obj = tostring(obj) + elseif type(obj) ~= 'string' then + error('bl.isObject: argument #1: expected torque object, number, or string', 2) + end + return tsIsObject(obj) end + function bl.isFunction(a1, a2) - if type(a1)~='string' then - error('bl.isFunction: argument #1: expected string', 2) end - if a2 then - if type(a2)~='string' then - error('bl.isFunction: argument #2: expected string', 2) end - return tsIsFunctionNs(a1, a2) - else - return tsIsFunction(a1) - end + if type(a1) ~= 'string' then + error('bl.isFunction: argument #1: expected string', 2) + end + if a2 then + if type(a2) ~= 'string' then + error('bl.isFunction: argument #2: expected string', 2) + end + return tsIsFunctionNs(a1, a2) + else + return tsIsFunction(a1) + end end -- Torque object pseudo-class local tsClassMeta = { - __tostring = function(t) - return 'torqueClass:'..t._name.. - (t._inherit and (':'..t._inherit._name) or '') - end, + __tostring = function(t) + return 'torqueClass:' .. t._name .. + (t._inherit and (':' .. t._inherit._name) or '') + end, } bl._objectUserMetas = bl._objectUserMetas or {} function bl.class(cname, inhname) - if not ( type(cname)=='string' and isValidFuncName(cname) ) then - error('bl.class: argument #1: invalid class name', 2) end - if not ( inhname==nil or (type(inhname)=='string' and isValidFuncName(inhname)) ) then - error('bl.class: argument #2: inherit name must be a string or nil', 2) end - cname = cname:lower() - - local met = bl._objectUserMetas[cname] or { - _name = cname, - _inherit = nil, - _children = {}, - } - bl._objectUserMetas[cname] = met - setmetatable(met, tsClassMeta) - - if inhname then - inhname = inhname:lower() - - local inh = bl._objectUserMetas[inhname] - if not inh then error('bl.class: argument #2: \''..inhname..'\' is not the '.. - 'name of an existing class', 2) end - - inh._children[cname] = true - - local inhI = met._inherit - if inhI and inhI~=inh then - error('bl.class: argument #2: class already exists and '.. - 'inherits a different parent.', 2) end - met._inherit = inh - - -- apply inherited method and field types - for ftname, typ in pairs(bl._forceType) do - local cname2, rest = classFromForceTypeStr(ftname) - if cname2==inhname then - setForceType(cname..rest, typ) - end - end - end + if not (type(cname) == 'string' and isValidFuncName(cname)) then + error('bl.class: argument #1: invalid class name', 2) + end + if not (inhname == nil or (type(inhname) == 'string' and isValidFuncName(inhname))) then + error('bl.class: argument #2: inherit name must be a string or nil', 2) + end + cname = cname:lower() + + local met = bl._objectUserMetas[cname] or { + _name = cname, + _inherit = nil, + _children = {}, + } + bl._objectUserMetas[cname] = met + setmetatable(met, tsClassMeta) + + if inhname then + inhname = inhname:lower() + + local inh = bl._objectUserMetas[inhname] + if not inh then + error('bl.class: argument #2: \'' .. inhname .. '\' is not the ' .. + 'name of an existing class', 2) + end + + inh._children[cname] = true + + local inhI = met._inherit + if inhI and inhI ~= inh then + error('bl.class: argument #2: class already exists and ' .. + 'inherits a different parent.', 2) + end + met._inherit = inh + + -- apply inherited method and field types + for ftname, typ in pairs(bl._forceType) do + local cname2, rest = classFromForceTypeStr(ftname) + if cname2 == inhname then + setForceType(cname .. rest, typ) + end + end + end end + local function objectInheritedMetas(name) - local inh = bl._objectUserMetas[name:lower()] - return function() - local inhP = inh - if inhP==nil then return nil end - inh = inh._inherit - return inhP - end + local inh = bl._objectUserMetas[name:lower()] + return function() + local inhP = inh + if inhP == nil then return nil end + inh = inh._inherit + return inhP + end end local tsObjectMeta = { - -- __index: Called when accessing fields that don't exist in the object itself - -- Return torque member function or value - __index = function(t, name) - if rawget(t,'_deleted') then - error('ts object index: object no longer exists', 2) end - if type(name)~='string' and type(name)~='number' then - error('ts object index: index must be a string or number', 2) end - if getmetatable(t)[name] then - return getmetatable(t)[name] - elseif type(name)=='number' then - if not tsIsFunctionNs(rawget(t,'_tsNamespace'), 'getObject') then - error('ts object __index: index is number, but object does not have '.. - 'getObject method', 2) end - return toTsObject(_bllua_ts.callobj(t._tsObjectId, 'getObject', - tostring(name))) - else - for inh in objectInheritedMetas(rawget(t,'_tsClassName')) do - if inh[name] then return inh[name] end - end - if - tsIsFunctionNs(rawget(t,'_tsNamespace'), name) or - tsIsFunctionNs(rawget(t,'_tsName'), name) - then - return function(t, ...) - local args = {...} - local argsS = arglistToTs(args) - return valFromTs( - _bllua_ts.callobj(rawget(t,'_tsObjectId'), name, unpack(argsS)), - rawget(t,'_tsName') and rawget(t,'_tsName')..'::'..name, - rawget(t,'_tsNamespace')..'::'..name) - end - else - return valFromTs( - _bllua_ts.getfield(rawget(t,'_tsObjectId'), name), - rawget(t,'_tsName') and rawget(t,'_tsName')..'.'..name, - rawget(t,'_tsNamespace')..'.'..name) - end - end - end, - -- __newindex: Called when setting fields on the object - -- Set lua data - -- Use :set() to set Torque data - __newindex = function(t, name, val) - if rawget(t,'_deleted') then - error('ts object newindex: object no longer exists', 2) end - if type(name)~='string' then - error('ts object newindex: index must be a string', 2) end - rawset(t, name, val) - -- create strong reference since it's now storing lua data - bl._objectsStrong[rawget(t,'_tsObjectId')] = t - end, - -- object:set(fieldName, value) - -- Use to set torque data - set = function(t, name, val) - if t==nil or type(t)~='table' or not t._tsObjectId then - error('ts object method: be sure to use :func() not .func()', 2) end - if t._deleted then - error('ts object method: object no longer exists', 2) end - if type(name)~='string' then - error('ts object :set(): index must be a string', 2) end - _bllua_ts.setfield(t._tsObjectId, name, valToTs(val)) - end, - -- __tostring: Called when printing - -- Display a nice info string - __tostring = function(t) - return 'torque:'..t._tsNamespace..':'..t._tsObjectId.. - (t._tsName~='' and ('('..t._tsName..')') or '') - end, - -- #object - -- If the object has a getCount method, return its count - __len = function(t) - if t._deleted then - error('ts object __len: object no longer exists', 2) end - if not tsIsFunctionNs(t._tsNamespace, 'getCount') then - error('ts object __len: object has no getCount method', 2) end - return tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount')) - end, - -- object:members() - -- Return an iterator for Torque objects with the getCount and getObject methods - -- for index, object in group:members() do ... end - members = function(t) - if t==nil then - error('ts object method: be sure to use :func() not .func()', 2) end - if t._deleted then - error('ts object method: object no longer exists', 2) end - if not ( - tsIsFunctionNs(t._tsNamespace, 'getCount' ) and - tsIsFunctionNs(t._tsNamespace, 'getObject')) then - error('ts object :members() - '.. - 'Object does not have getCount and getObject methods', 2) end - local count = tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount')) - local idx = 0 - return function() - if idx < count then - local obj = toTsObject(_bllua_ts.callobj(t._tsObjectId, - 'getObject', tostring(idx))) - idx = idx+1 - return idx-1, obj - else - return nil - end - end - end, - -- Wrap some Torque functions for performance and error checking - getName = function(t) - if t==nil then - error('ts object method: be sure to use :func() not .func()', 2) end - if t._deleted then - error('ts object method: object no longer exists', 2) end - return t._tsName - end, - getId = function(t) - if t==nil then - error('ts object method: be sure to use :func() not .func()', 2) end - if t._deleted then - error('ts object method: object no longer exists', 2) end - return tonumber(t._tsObjectId) - end, - getType = function(t) - if t==nil then - error('ts object method: be sure to use :func() not .func()', 2) end - if t._deleted then - error('ts object method: object no longer exists', 2) end - return tsTypesByNum[_bllua_ts.callobj(t._tsObjectId, 'getType')] - end, - ---- Schedule method for objects - --schedule = function(t, time, cb, ...) - -- if type(t)~='table' or not t._tsObjectId then - -- error('ts object method: be sure to use :func() not .func()', 2) end - -- if t._deleted then - -- error('ts object method: object no longer exists', 2) end - -- if type(time)~='number' then - -- error('ts object schedule: argument #2: time must be a number', 2) end - -- if type(cb)~='function' then - -- error('ts object schedule: argument #3: callback must be a function', 2) end - -- local args = {...} - -- bl.schedule(time, function() - -- if tsBool(__bllua_ts.call('isObject', t._tsObjectId)) then - -- pcall(cb, unpack(args)) - -- end - -- end) - --end, - exists = function(t) - if t==nil then - error('ts object method: be sure to use :func() not .func()', 2) end - if t._deleted then - return false end - return tsIsObject(t._tsObjectId) - end, + -- __index: Called when accessing fields that don't exist in the object itself + -- Return torque member function or value + __index = function(t, name) + if rawget(t, '_deleted') then + error('ts object index: object no longer exists', 2) + end + if type(name) ~= 'string' and type(name) ~= 'number' then + error('ts object index: index must be a string or number', 2) + end + if getmetatable(t)[name] then + return getmetatable(t)[name] + elseif type(name) == 'number' then + if not tsIsFunctionNs(rawget(t, '_tsNamespace'), 'getObject') then + error('ts object __index: index is number, but object does not have ' .. + 'getObject method', 2) + end + return toTsObject(_bllua_ts.callobj(t._tsObjectId, 'getObject', + tostring(name))) + else + for inh in objectInheritedMetas(rawget(t, '_tsClassName')) do + if inh[name] then return inh[name] end + end + if + tsIsFunctionNs(rawget(t, '_tsNamespace'), name) or + tsIsFunctionNs(rawget(t, '_tsName'), name) + then + return function(t, ...) + local args = { ... } + local argsS = arglistToTs(args) + return valFromTs( + _bllua_ts.callobj(rawget(t, '_tsObjectId'), name, unpack(argsS)), + rawget(t, '_tsName') and rawget(t, '_tsName') .. '::' .. name, + rawget(t, '_tsNamespace') .. '::' .. name) + end + else + return valFromTs( + _bllua_ts.getfield(rawget(t, '_tsObjectId'), name), + rawget(t, '_tsName') and rawget(t, '_tsName') .. '.' .. name, + rawget(t, '_tsNamespace') .. '.' .. name) + end + end + end, + -- __newindex: Called when setting fields on the object + -- Set lua data + -- Use :set() to set Torque data + __newindex = function(t, name, val) + if rawget(t, '_deleted') then + error('ts object newindex: object no longer exists', 2) + end + if type(name) ~= 'string' then + error('ts object newindex: index must be a string', 2) + end + rawset(t, name, val) + -- create strong reference since it's now storing lua data + bl._objectsStrong[rawget(t, '_tsObjectId')] = t + end, + -- object:set(fieldName, value) + -- Use to set torque data + set = function(t, name, val) + if t == nil or type(t) ~= 'table' or not t._tsObjectId then + error('ts object method: be sure to use :func() not .func()', 2) + end + if t._deleted then + error('ts object method: object no longer exists', 2) + end + if type(name) ~= 'string' then + error('ts object :set(): index must be a string', 2) + end + _bllua_ts.setfield(t._tsObjectId, name, valToTs(val)) + end, + -- __tostring: Called when printing + -- Display a nice info string + __tostring = function(t) + return 'torque:' .. t._tsNamespace .. ':' .. t._tsObjectId .. + (t._tsName ~= '' and ('(' .. t._tsName .. ')') or '') + end, + -- #object + -- If the object has a getCount method, return its count + __len = function(t) + if t._deleted then + error('ts object __len: object no longer exists', 2) + end + if not tsIsFunctionNs(t._tsNamespace, 'getCount') then + error('ts object __len: object has no getCount method', 2) + end + return tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount')) + end, + -- object:members() + -- Return an iterator for Torque objects with the getCount and getObject methods + -- for index, object in group:members() do ... end + members = function(t) + if t == nil then + error('ts object method: be sure to use :func() not .func()', 2) + end + if t._deleted then + error('ts object method: object no longer exists', 2) + end + if not ( + tsIsFunctionNs(t._tsNamespace, 'getCount') and + tsIsFunctionNs(t._tsNamespace, 'getObject')) then + error('ts object :members() - ' .. + 'Object does not have getCount and getObject methods', 2) + end + local count = tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount')) + local idx = 0 + return function() + if idx < count then + local obj = toTsObject(_bllua_ts.callobj(t._tsObjectId, + 'getObject', tostring(idx))) + idx = idx + 1 + return idx - 1, obj + else + return nil + end + end + end, + -- Wrap some Torque functions for performance and error checking + getName = function(t) + if t == nil then + error('ts object method: be sure to use :func() not .func()', 2) + end + if t._deleted then + error('ts object method: object no longer exists', 2) + end + return t._tsName + end, + getId = function(t) + if t == nil then + error('ts object method: be sure to use :func() not .func()', 2) + end + if t._deleted then + error('ts object method: object no longer exists', 2) + end + return tonumber(t._tsObjectId) + end, + getType = function(t) + if t == nil then + error('ts object method: be sure to use :func() not .func()', 2) + end + if t._deleted then + error('ts object method: object no longer exists', 2) + end + return tsTypesByNum[_bllua_ts.callobj(t._tsObjectId, 'getType')] + end, + ---- Schedule method for objects + --schedule = function(t, time, cb, ...) + -- if type(t)~='table' or not t._tsObjectId then + -- error('ts object method: be sure to use :func() not .func()', 2) end + -- if t._deleted then + -- error('ts object method: object no longer exists', 2) end + -- if type(time)~='number' then + -- error('ts object schedule: argument #2: time must be a number', 2) end + -- if type(cb)~='function' then + -- error('ts object schedule: argument #3: callback must be a function', 2) end + -- local args = {...} + -- bl.schedule(time, function() + -- if tsBool(__bllua_ts.call('isObject', t._tsObjectId)) then + -- pcall(cb, unpack(args)) + -- end + -- end) + --end, + exists = function(t) + if t == nil then + error('ts object method: be sure to use :func() not .func()', 2) + end + if t._deleted then + return false + end + return tsIsObject(t._tsObjectId) + end, } -- Weak-values table for caching Torque object references -- Objects in this table can be garbage collected if there are no other refs to them if not bl._objectsWeak then - bl._objectsWeak = {} - setmetatable(bl._objectsWeak, { __mode='v' }) + bl._objectsWeak = {} + setmetatable(bl._objectsWeak, { __mode = 'v' }) end -- Strong table for preserving Torque object references containing lua data -- If an object in this table, it will remain here and in the Weak table until deleted if not bl._objectsStrong then - bl._objectsStrong = {} + bl._objectsStrong = {} end -- Hook object deletion to clean up its lua data -- idS is expected to be the object ID number, NOT the object name function _bllua_objectDeleted(idS) - local obj = bl._objectsWeak[idS] - if obj then - obj._deleted = true - bl._objectsStrong[idS] = nil - bl._objectsWeak[idS] = nil - bl._objectsWeak[obj._tsName:lower()] = nil - end + local obj = bl._objectsWeak[idS] + if obj then + obj._deleted = true + bl._objectsStrong[idS] = nil + bl._objectsWeak[idS] = nil + bl._objectsWeak[obj._tsName:lower()] = nil + end end -- Return a Torque object for the object ID string, or create one if none exists toTsObject = function(idiS) - if type(idiS)~='string' then - error('toTsObject: input must be a string', 2) end - idiS = idiS:lower() - if bl._objectsWeak[idiS] then return bl._objectsWeak[idiS] end - - if not tsBool(_bllua_ts.call('isObject', idiS)) then - --error('toTsObject: object \''..idiS..'\' does not exist', 2) end - return nil end - - local className = _bllua_ts.callobj(idiS, 'getClassName') - local obj = { - _tsObjectId = _bllua_ts.callobj(idiS, 'getId' ), - _tsName = _bllua_ts.callobj(idiS, 'getName' ), - _tsNamespace = className, - _tsClassName = className:lower(), - } - setmetatable(obj, tsObjectMeta) - - bl._objectsWeak[obj._tsObjectId ] = obj - bl._objectsWeak[obj._tsName:lower()] = obj - return obj + if type(idiS) ~= 'string' then + error('toTsObject: input must be a string', 2) + end + idiS = idiS:lower() + if bl._objectsWeak[idiS] then return bl._objectsWeak[idiS] end + + if not tsBool(_bllua_ts.call('isObject', idiS)) then + --error('toTsObject: object \''..idiS..'\' does not exist', 2) end + return nil + end + + local className = _bllua_ts.callobj(idiS, 'getClassName') + local obj = { + _tsObjectId = _bllua_ts.callobj(idiS, 'getId'), + _tsName = _bllua_ts.callobj(idiS, 'getName'), + _tsNamespace = className, + _tsClassName = className:lower(), + } + setmetatable(obj, tsObjectMeta) + + bl._objectsWeak[obj._tsObjectId] = obj + bl._objectsWeak[obj._tsName:lower()] = obj + return obj end -- Allow bl['namespaced::function']() local function safeNamespaceName(name) - return tostring(name:gsub(':', '_')) + return tostring(name:gsub(':', '_')) end local nscallArgStr = '%a,%b,%c,%d,%e,%f,%g,%h' bl._cachedNamespaceCalls = {} local function tsNamespacedCallTfname(name) - local tfname = bl._cachedNamespaceCalls[name] - if not tfname then - tfname = '_bllua_nscall_'..safeNamespaceName(name) - local tfcode = 'function '..tfname..'('..nscallArgStr..'){'.. - name..'('..nscallArgStr..');}' - _bllua_ts.eval(tfcode) - bl._cachedNamespaceCalls[name] = tfname - end - return tfname + local tfname = bl._cachedNamespaceCalls[name] + if not tfname then + tfname = '_bllua_nscall_' .. safeNamespaceName(name) + local tfcode = 'function ' .. tfname .. '(' .. nscallArgStr .. '){' .. + name .. '(' .. nscallArgStr .. ');}' + _bllua_ts.eval(tfcode) + bl._cachedNamespaceCalls[name] = tfname + end + return tfname end local function tsCallGen(name) - return function(...) - local args = {...} - local argsS = arglistToTs(args) - return valFromTs(_bllua_ts.call(name, unpack(argsS)), name) - end + return function(...) + local args = { ... } + local argsS = arglistToTs(args) + return valFromTs(_bllua_ts.call(name, unpack(argsS)), name) + end end -- Metatable for the global bl library -- Allows accessing Torque objects, variables, and functions by indexing it local tsMeta = { - -- __index: Called when accessing fields that don't exist in the table itself - -- Allow indexing by object id: bl[1234] - -- by object name: bl.mainMenuGui - -- by function name: bl.quit() - -- by variable name: bl.iAmAdmin - __index = function(t, name) - if getmetatable(t)[name] then - return getmetatable(t)[name] - elseif type(name)=='string' and bl._objectUserMetas[name:lower()] then - return bl._objectUserMetas[name:lower()] - else - if type(name)=='number' then - return toTsObject(tostring(name)) - elseif name:find('::') then - local ns, rest = name:match('^([^:]+)::(.+)$') - if not ns then error('ts index: invalid name \''..name..'\'', 2) end - if not rest:find('::') and tsIsFunctionNs(ns, rest) then - return tsCallGen(tsNamespacedCallTfname(name)) - else - return valFromTs(_bllua_ts.getvar(name), name) - end - elseif tsIsFunction(name) then - return tsCallGen(name) - elseif tsIsObject(name) then - return toTsObject(name) - else - return valFromTs(_bllua_ts.getvar(name), name) - end - end - end, + -- __index: Called when accessing fields that don't exist in the table itself + -- Allow indexing by object id: bl[1234] + -- by object name: bl.mainMenuGui + -- by function name: bl.quit() + -- by variable name: bl.iAmAdmin + __index = function(t, name) + if getmetatable(t)[name] then + return getmetatable(t)[name] + elseif type(name) == 'string' and bl._objectUserMetas[name:lower()] then + return bl._objectUserMetas[name:lower()] + else + if type(name) == 'number' then + return toTsObject(tostring(name)) + elseif name:find('::') then + local ns, rest = name:match('^([^:]+)::(.+)$') + if not ns then error('ts index: invalid name \'' .. name .. '\'', 2) end + if not rest:find('::') and tsIsFunctionNs(ns, rest) then + return tsCallGen(tsNamespacedCallTfname(name)) + else + return valFromTs(_bllua_ts.getvar(name), name) + end + elseif tsIsFunction(name) then + return tsCallGen(name) + elseif tsIsObject(name) then + return toTsObject(name) + else + return valFromTs(_bllua_ts.getvar(name), name) + end + end + end, } -- bl.set(name, value) -- Used to set global variables function bl.set(name, val) - _bllua_ts.setvar(name, valToTs(val)) + _bllua_ts.setvar(name, valToTs(val)) end -- Utility functions function bl.call(func, ...) - local args = {...} - local argsS = arglistToTs(args) - return _bllua_ts.call(func, unpack(argsS)) + local args = { ... } + local argsS = arglistToTs(args) + return _bllua_ts.call(func, unpack(argsS)) end + function bl.eval(code) - return valFromTs(_bllua_ts.eval(code)) + return valFromTs(_bllua_ts.eval(code)) end + function bl.exec(file) - return valFromTs(_bllua_ts.call('exec', file)) + return valFromTs(_bllua_ts.call('exec', file)) end + function bl.boolean(val) - return val~=nil and - val~=false and - --val~='' and - --val~='0' and - val~=0 + return val ~= nil and + val ~= false and + --val~='' and + --val~='0' and + val ~= 0 end + function bl.object(id) - if type(id)=='table' and id._tsObjectId then - return id - elseif type(id)=='string' or type(id)=='number' then - return toTsObject(tostring(id)) - else - error('bl.object: id must be a ts object, number, or string', 2) - end + if type(id) == 'table' and id._tsObjectId then + return id + elseif type(id) == 'string' or type(id) == 'number' then + return toTsObject(tostring(id)) + else + error('bl.object: id must be a ts object, number, or string', 2) + end end + function bl.array(name, ...) - local rest = {...} - return name..table.concat(rest, '_') + local rest = { ... } + return name .. table.concat(rest, '_') end + function _bllua_call(fnameS, ...) - local args = arglistFromTs('lua:'..fnameS:lower(), {...}) -- todo: allow this though bl.type - if not _G[fnameS] then - error('luacall: no global lua function named \''..fnameS..'\'') end - -- todo: library fields and object methods - local res = _G[fnameS](unpack(args)) - return valToTs(res) + local args = arglistFromTs('lua:' .. fnameS:lower(), { ... }) -- todo: allow this though bl.type + if not _G[fnameS] then + error('luacall: no global lua function named \'' .. fnameS .. '\'') + end + -- todo: library fields and object methods + local res = _G[fnameS](unpack(args)) + return valToTs(res) end -- bl.schedule: Use TS's schedule function to schedule lua calls -- bl.schedule(time, function, args...) -bl._scheduleTable = bl._scheduleTable or {} +bl._scheduleTable = bl._scheduleTable or {} bl._scheduleNextId = bl._scheduleNextId or 1 local function cancelTsSched(sched) - if not (sched and sched.handle) then - error('schedule:cancel() - invalid object', 2) - end - _bllua_ts.call('cancel', sched.handle) - bl._scheduleTable[id] = nil + if not (sched and sched.handle) then + error('schedule:cancel() - invalid object', 2) + end + _bllua_ts.call('cancel', sched.handle) + bl._scheduleTable[id] = nil end function bl.schedule(time, cb, ...) - local id = bl._scheduleNextId - bl._scheduleNextId = bl._scheduleNextId+1 - local args = {...} - local handle = tonumber(_bllua_ts.call('schedule', - time, 0, '_bllua_luacall', '_bllua_schedule_callback', id)) - local sch = { - callback = cb, - args = args, - handle = handle, - cancel = cancelTsSched, - } - bl._scheduleTable[id] = sch - return sch + local id = bl._scheduleNextId + bl._scheduleNextId = bl._scheduleNextId + 1 + local args = { ... } + local handle = tonumber(_bllua_ts.call('schedule', + time, 0, '_bllua_luacall', '_bllua_schedule_callback', id)) + local sch = { + callback = cb, + args = args, + handle = handle, + cancel = cancelTsSched, + } + bl._scheduleTable[id] = sch + return sch end + function _bllua_schedule_callback(idS) - local id = tonumber(idS) or - error('_bllua_schedule_callback: invalid id: '..tostring(idS)) - local sch = bl._scheduleTable[id] - if not sch then error('_bllua_schedule_callback: no schedule with id '..id) end - bl._scheduleTable[id] = nil - sch.callback(unpack(sch.args)) + local id = tonumber(idS) or + error('_bllua_schedule_callback: invalid id: ' .. tostring(idS)) + local sch = bl._scheduleTable[id] + if not sch then error('_bllua_schedule_callback: no schedule with id ' .. id) end + bl._scheduleTable[id] = nil + sch.callback(unpack(sch.args)) end -- serverCmd and clientCmd bl._cmds = bl._cmds or {} function _bllua_process_cmd(cmdS, ...) - local cmd = cmdS:lower() - local args = arglistFromTs(cmd, {...}) - local func = bl._cmds[cmd] - if not func then error('_bllua_process_cmd: no cmd named \''..cmd..'\'') end - func(unpack(args)) --pcall(func, unpack(args)) + local cmd = cmdS:lower() + local args = arglistFromTs(cmd, { ... }) + local func = bl._cmds[cmd] + if not func then error('_bllua_process_cmd: no cmd named \'' .. cmd .. '\'') end + func(unpack(args)) --pcall(func, unpack(args)) end + local function addCmd(cmd, func) - if not isValidFuncName(cmd) then - error('addCmd: invalid function name \''..tostring(cmd)..'\'') end - bl._cmds[cmd] = func - local arglist = '%a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p' - _bllua_ts.eval('function '..cmd..'('..arglist..'){'.. - '_bllua_luacall(_bllua_process_cmd,"'..cmd..'",'..arglist..');}') + if not isValidFuncName(cmd) then + error('addCmd: invalid function name \'' .. tostring(cmd) .. '\'') + end + bl._cmds[cmd] = func + local arglist = '%a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p' + _bllua_ts.eval('function ' .. cmd .. '(' .. arglist .. '){' .. + '_bllua_luacall(_bllua_process_cmd,"' .. cmd .. '",' .. arglist .. ');}') end function bl.addServerCmd(name, func) - name = name:lower() - addCmd('servercmd'..name, func) - bl._forceType['servercmd'..name..':1'] = 'object' + name = name:lower() + addCmd('servercmd' .. name, func) + bl._forceType['servercmd' .. name .. ':1'] = 'object' end + function bl.addClientCmd(name, func) - name = name:lower() - addCmd('clientcmd'..name, func) + name = name:lower() + addCmd('clientcmd' .. name, func) end -- commandToServer and commandToClient function bl.commandToServer(cmd, ...) - _bllua_ts.call('commandToServer', - _bllua_ts.call('addTaggedString', cmd), - unpack(arglistToTs({...}))) + _bllua_ts.call('commandToServer', + _bllua_ts.call('addTaggedString', cmd), + unpack(arglistToTs({ ... }))) end + function bl.commandToClient(cmd, ...) - _bllua_ts.call('commandToClient', - _bllua_ts.call('addTaggedString', cmd), - unpack(arglistToTs({...}))) + _bllua_ts.call('commandToClient', + _bllua_ts.call('addTaggedString', cmd), + unpack(arglistToTs({ ... }))) end + function bl.commandToAll(cmd, ...) - _bllua_ts.call('commandToAll', - _bllua_ts.call('addTaggedString', cmd), - unpack(arglistToTs({...}))) + _bllua_ts.call('commandToAll', + _bllua_ts.call('addTaggedString', cmd), + unpack(arglistToTs({ ... }))) end -- Hooks (using TS packages) local function isPackageActive(pkg) - local numpkgs = tonumber(_bllua_ts.call('getNumActivePackages')) - for i = 0, numpkgs-1 do - local apkg = _bllua_ts.call('getActivePackage', tostring(i)) - if apkg==pkg then return true end - end - return false + local numpkgs = tonumber(_bllua_ts.call('getNumActivePackages')) + for i = 0, numpkgs - 1 do + local apkg = _bllua_ts.call('getActivePackage', tostring(i)) + if apkg == pkg then return true end + end + return false end local function activatePackage(pkg) - if not isPackageActive(pkg) then - _bllua_ts.call('activatePackage', pkg) - end + if not isPackageActive(pkg) then + _bllua_ts.call('activatePackage', pkg) + end end local function deactivatePackage(pkg) - if isPackageActive(pkg) then - _bllua_ts.call('deactivatePackage', pkg) - end + if isPackageActive(pkg) then + _bllua_ts.call('deactivatePackage', pkg) + end end local hookNargs = 8 local hookArglistLocal = '%a,%b,%c,%d,%e,%f,%g,%h' -local hookArglistGlobal = '$_bllua_hook_arg1,$_bllua_hook_arg2,$_bllua_hook_arg3,$_bllua_hook_arg4,$_bllua_hook_arg5,$_bllua_hook_arg6,$_bllua_hook_arg7,$_bllua_hook_arg8' +local hookArglistGlobal = +'$_bllua_hook_arg1,$_bllua_hook_arg2,$_bllua_hook_arg3,$_bllua_hook_arg4,$_bllua_hook_arg5,$_bllua_hook_arg6,$_bllua_hook_arg7,$_bllua_hook_arg8' bl._hooks = bl._hooks or {} function _bllua_process_hook_before(pkgS, nameS, ...) - local args = arglistFromTs(nameS, {...}) - local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and - bl._hooks[pkgS][nameS].before - if not func then - error('_bllua_process_hook_before: no hook for '..pkgs..':'..nameS) end - _bllua_ts.setvar('_bllua_hook_abort', '0') - func(args) --pcall(func, args) - if args._return then - _bllua_ts.setvar('_bllua_hook_abort', '1') - _bllua_ts.setvar('_bllua_hook_return', valToTs(args._return)) - end - for i=1,hookNargs do - _bllua_ts.setvar('_bllua_hook_arg'..i, valToTs(args[i])) - end + local args = arglistFromTs(nameS, { ... }) + local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and + bl._hooks[pkgS][nameS].before + if not func then + error('_bllua_process_hook_before: no hook for ' .. pkgs .. ':' .. nameS) + end + _bllua_ts.setvar('_bllua_hook_abort', '0') + func(args) --pcall(func, args) + if args._return then + _bllua_ts.setvar('_bllua_hook_abort', '1') + _bllua_ts.setvar('_bllua_hook_return', valToTs(args._return)) + end + for i = 1, hookNargs do + _bllua_ts.setvar('_bllua_hook_arg' .. i, valToTs(args[i])) + end end + function _bllua_process_hook_after(pkgS, nameS, resultS, ...) - nameS = nameS:lower() - local args = arglistFromTs(nameS, {...}) - args._return = valFromTs(resultS, nameS) - local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and - bl._hooks[pkgS][nameS].after - if not func then - error('_bllua_process_hook_after: no hook for '..pkgs..':'..nameS) end - func(args) --pcall(func, args) - return valToTs(args._return) + nameS = nameS:lower() + local args = arglistFromTs(nameS, { ... }) + args._return = valFromTs(resultS, nameS) + local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and + bl._hooks[pkgS][nameS].after + if not func then + error('_bllua_process_hook_after: no hook for ' .. pkgs .. ':' .. nameS) + end + func(args) --pcall(func, args) + return valToTs(args._return) end + local function updateHook(pkg, name, hk) - local beforeCode = hk.before and - ('_bllua_luacall("_bllua_process_hook_before", "'..pkg..'","'..name.. - '",'..hookArglistLocal..');') or '' - local arglist = (hk.before and hookArglistGlobal or hookArglistLocal) - local parentCode = - tsIsFunctionNsname(name) and -- only call parent if it exists - (hk.before and - 'if($_bllua_hook_abort)return $_bllua_hook_return; else ' or '').. - ((hk.after and '%result=' or 'return ').. - 'parent::'..name:match('[^:]+$').. - '('..arglist..');') or '' - local afterCode = hk.after and - ('return _bllua_luacall("_bllua_process_hook_after","'..pkg..'","'..name..'",%result,'.. - arglist..');') or '' - local code = - 'package '..pkg..'{'.. - 'function '..name..'('..hookArglistLocal..'){'.. - beforeCode..parentCode..afterCode.. - '}'.. - '};' - _bllua_ts.eval(code) + local beforeCode = hk.before and + ('_bllua_luacall("_bllua_process_hook_before", "' .. pkg .. '","' .. name .. + '",' .. hookArglistLocal .. ');') or '' + local arglist = (hk.before and hookArglistGlobal or hookArglistLocal) + local parentCode = + tsIsFunctionNsname(name) and -- only call parent if it exists + (hk.before and + 'if($_bllua_hook_abort)return $_bllua_hook_return; else ' or '') .. + ((hk.after and '%result=' or 'return ') .. + 'parent::' .. name:match('[^:]+$') .. + '(' .. arglist .. ');') or '' + local afterCode = hk.after and + ('return _bllua_luacall("_bllua_process_hook_after","' .. pkg .. '","' .. name .. '",%result,' .. + arglist .. ');') or '' + local code = + 'package ' .. pkg .. '{' .. + 'function ' .. name .. '(' .. hookArglistLocal .. '){' .. + beforeCode .. parentCode .. afterCode .. + '}' .. + '};' + _bllua_ts.eval(code) end function bl.hook(pkg, name, time, func) - if not isValidFuncName(pkg) then - error('bl.hook: argument #1: invalid package name \''..tostring(pkg)..'\'', 2) end - if not isValidFuncNameNs(name) then - error('bl.hook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end - if time~='before' and time~='after' then - error('bl.hook: argument #3: time must be \'before\' or \'after\'', 2) end - if type(func)~='function' then - error('bl.hook: argument #4: expected a function', 2) end - - bl._hooks[pkg] = bl._hooks[pkg] or {} - bl._hooks[pkg][name] = bl._hooks[pkg][name] or {} - bl._hooks[pkg][name][time] = func - - updateHook(pkg, name, bl._hooks[pkg][name]) - activatePackage(pkg) + if not isValidFuncName(pkg) then + error('bl.hook: argument #1: invalid package name \'' .. tostring(pkg) .. '\'', 2) + end + if not isValidFuncNameNs(name) then + error('bl.hook: argument #2: invalid function name \'' .. tostring(name) .. '\'', 2) + end + if time ~= 'before' and time ~= 'after' then + error('bl.hook: argument #3: time must be \'before\' or \'after\'', 2) + end + if type(func) ~= 'function' then + error('bl.hook: argument #4: expected a function', 2) + end + + bl._hooks[pkg] = bl._hooks[pkg] or {} + bl._hooks[pkg][name] = bl._hooks[pkg][name] or {} + bl._hooks[pkg][name][time] = func + + updateHook(pkg, name, bl._hooks[pkg][name]) + activatePackage(pkg) end + local function tableEmpty(t) - return next(t)~=nil + return next(t) ~= nil end function bl.unhook(pkg, name, time) - if not isValidFuncName(pkg) then - error('bl.unhook: argument #1: invalid package name \''..tostring(pkg)..'\'', 2) end - if not isValidFuncNameNs(name) then - error('bl.unhook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end - if time~='before' and time~='after' then - error('bl.unhook: argument #3: time must be \'before\' or \'after\'', 2) end - - if not name then - if bl._hooks[pkg] then - for name,hk in pairs(bl._hooks[pkg]) do - updateHook(pkg, name, {}) - end - bl._hooks[pkg] = nil - else - --error('bl.unhook: no hooks registered under package name \''.. - -- pkg..'\'', 2) - end - deactivatePackage(pkg) - else - if bl._hooks[pkg][name] then - if not time then - bl._hooks[pkg][name] = nil - if table.empty(bl._hooks[pkg]) then - bl._hooks[pkg] = nil - deactivatePackage(pkg) - end - updateHook(pkg, name, {}) - else - if time~='before' and time~='after' then - error('bl.unhook: argument #3: time must be nil, \'before\', or \'after\'', 2) end - bl._hooks[pkg][name][time] = nil - if tableEmpty(bl._hooks[pkg][name]) and tableEmpty(bl._hooks[pkg]) then - bl._hooks[pkg] = nil - deactivatePackage(pkg) - updateHook(pkg, name, {}) - else - updateHook(pkg, name, bl._hooks[pkg][name]) - end - end - else - --error('bl.unhook: no hooks registered for function \''..name.. - -- '\' under package name \''..pkg..'\'', 2) - end - end + if not isValidFuncName(pkg) then + error('bl.unhook: argument #1: invalid package name \'' .. tostring(pkg) .. '\'', 2) + end + if not isValidFuncNameNs(name) then + error('bl.unhook: argument #2: invalid function name \'' .. tostring(name) .. '\'', 2) + end + if time ~= 'before' and time ~= 'after' then + error('bl.unhook: argument #3: time must be \'before\' or \'after\'', 2) + end + + if not name then + if bl._hooks[pkg] then + for name, hk in pairs(bl._hooks[pkg]) do + updateHook(pkg, name, {}) + end + bl._hooks[pkg] = nil + else + --error('bl.unhook: no hooks registered under package name \''.. + -- pkg..'\'', 2) + end + deactivatePackage(pkg) + else + if bl._hooks[pkg][name] then + if not time then + bl._hooks[pkg][name] = nil + if table.empty(bl._hooks[pkg]) then + bl._hooks[pkg] = nil + deactivatePackage(pkg) + end + updateHook(pkg, name, {}) + else + if time ~= 'before' and time ~= 'after' then + error('bl.unhook: argument #3: time must be nil, \'before\', or \'after\'', 2) + end + bl._hooks[pkg][name][time] = nil + if tableEmpty(bl._hooks[pkg][name]) and tableEmpty(bl._hooks[pkg]) then + bl._hooks[pkg] = nil + deactivatePackage(pkg) + updateHook(pkg, name, {}) + else + updateHook(pkg, name, bl._hooks[pkg][name]) + end + end + else + --error('bl.unhook: no hooks registered for function \''..name.. + -- '\' under package name \''..pkg..'\'', 2) + end + end end -- Container search/raycast local function vecToTs(v) - if not isTsVector(v) then - error('vecToTs: argument is not a vector', 3) end - return table.concat(v, ' ') + if not isTsVector(v) then + error('vecToTs: argument is not a vector', 3) + end + return table.concat(v, ' ') end local function maskToTs(mask) - if type(mask)=='string' then - local val = tsTypesByName[mask:lower()] - if not val then - error('maskToTs: invalid mask \''..mask..'\'', 3) end - return tostring(val) - elseif type(mask)=='table' then - local tval = 0 - local seen = {} - for i,v in ipairs(mask) do - if not seen[v] then - local val = tsTypesByName[v:lower()] - if not val then - error('maskToTs: invalid mask \''..v.. - '\' at index '..i..' in mask list', 3) end - tval = tval + val - seen[v] = true - end - end - return tostring(tval) - else - error('maskToTs: mask must be a string or table', 3) - end + if type(mask) == 'string' then + local val = tsTypesByName[mask:lower()] + if not val then + error('maskToTs: invalid mask \'' .. mask .. '\'', 3) + end + return tostring(val) + elseif type(mask) == 'table' then + local tval = 0 + local seen = {} + for i, v in ipairs(mask) do + if not seen[v] then + local val = tsTypesByName[v:lower()] + if not val then + error('maskToTs: invalid mask \'' .. v .. + '\' at index ' .. i .. ' in mask list', 3) + end + tval = tval + val + seen[v] = true + end + end + return tostring(tval) + else + error('maskToTs: mask must be a string or table', 3) + end end local function objToTs(obj) - if type(obj)=='number' or type(obj)=='string' then - return tostring(obj) - elseif type(obj)=='table' and obj._tsObjectId then - return tostring(obj._tsObjectId) - else - error('objToTs: invalid object \''..tostring(obj)..'\'', 3) - end + if type(obj) == 'number' or type(obj) == 'string' then + return tostring(obj) + elseif type(obj) == 'table' and obj._tsObjectId then + return tostring(obj._tsObjectId) + else + error('objToTs: invalid object \'' .. tostring(obj) .. '\'', 3) + end end function bl.raycast(start, stop, mask, ignores) - local startS = vecToTs(start) - local stopS = vecToTs(start) - local maskS = maskToTs(mask) - local ignoresS = {} - for _,v in ipairs(ignores) do - table.insert(ignoresS, objToTs(v)) - end - - local retS = _bllua_ts.call('containerRaycast', startS, stopS, maskS, unpack(ignoresS)) - - if retS=='0' then - return nil - else - local hitS, pxS,pyS,pzS, nxS,nyS,nzS = retS:match('^([0-9]+) '.. - '(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+) '.. - '(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') - local hit = toTsObject(hitS) - local pos = vector{tonumber(pxS),tonumber(pyS),tonumber(pzS)} - local norm = vector{tonumber(nxS),tonumber(nyS),tonumber(nzS)} - return hit, pos, norm - end + local startS = vecToTs(start) + local stopS = vecToTs(start) + local maskS = maskToTs(mask) + local ignoresS = {} + for _, v in ipairs(ignores) do + table.insert(ignoresS, objToTs(v)) + end + + local retS = _bllua_ts.call('containerRaycast', startS, stopS, maskS, unpack(ignoresS)) + + if retS == '0' then + return nil + else + local hitS, pxS, pyS, pzS, nxS, nyS, nzS = retS:match('^([0-9]+) ' .. + '(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+) ' .. + '(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') + local hit = toTsObject(hitS) + local pos = vector { tonumber(pxS), tonumber(pyS), tonumber(pzS) } + local norm = vector { tonumber(nxS), tonumber(nyS), tonumber(nzS) } + return hit, pos, norm + end end + local function tsContainerSearchIterator() - local retS = _bllua_ts.call('containerSearchNext') - if retS=='0' then - return nil - else - return toTsObject(retS) - end + local retS = _bllua_ts.call('containerSearchNext') + if retS == '0' then + return nil + else + return toTsObject(retS) + end end function bl.boxSearch(pos, size, mask) - local posS = vecToTs(pos) - local sizeS = vecToTs(size) - local maskS = maskToTs(mask) - - _bllua_ts.call('initContainerBoxSearch', posS, sizeS, maskS) - return tsContainerSearchIterator + local posS = vecToTs(pos) + local sizeS = vecToTs(size) + local maskS = maskToTs(mask) + + _bllua_ts.call('initContainerBoxSearch', posS, sizeS, maskS) + return tsContainerSearchIterator end + function bl.radiusSearch(pos, radius, mask) - local posS = vecToTs(pos) - if type(radius)~='number' then - error('bl.radiusSearch: argument #2: radius must be a number', 2) end - local radiusS = tostring(radius) - local maskS = maskToTs(mask) - - _bllua_ts.call('initContainerRadiusSearch', posS, radiusS, maskS) - return tsContainerSearchIterator + local posS = vecToTs(pos) + if type(radius) ~= 'number' then + error('bl.radiusSearch: argument #2: radius must be a number', 2) + end + local radiusS = tostring(radius) + local maskS = maskToTs(mask) + + _bllua_ts.call('initContainerRadiusSearch', posS, radiusS, maskS) + return tsContainerSearchIterator end -- Print/Talk/Echo local maxTsArgLen = 8192 local function valsToString(vals) - local strs = {} - for i,v in ipairs(vals) do - local tstr = table.tostring(v) - if #tstr>maxTsArgLen then - tstr = tostring(v) - end - strs[i] = tstr - end - return table.concat(strs, ' ') + local strs = {} + for i, v in ipairs(vals) do + local tstr = table.tostring(v) + if #tstr > maxTsArgLen then + tstr = tostring(v) + end + strs[i] = tstr + end + return table.concat(strs, ' ') end bl.echo = function(...) - local str = valsToString({...}) - _bllua_ts.call('echo', str) + local str = valsToString({ ... }) + _bllua_ts.call('echo', str) end print = bl.echo bl.talk = function(...) - local str = valsToString({...}) - _bllua_ts.call('echo', str) - _bllua_ts.call('talk', str) + local str = valsToString({ ... }) + _bllua_ts.call('echo', str) + _bllua_ts.call('talk', str) end -- bl.new and bl.datablock local function createTsObj(keyword, class, name, inherit, props) - local propsT = {} - if props then - for k,v in pairs(props) do - if not isValidFuncName(k) then - error('bl.new/bl.datablock: invalid property name \''..k..'\'') end - table.insert(propsT, k..'="'..valToTs(v)..'";') - end - end - - local objS = _bllua_ts.eval( - 'return '..keyword..' '..class..'('.. - (name or '')..(inherit and (':'..inherit) or '')..'){'.. - table.concat(propsT)..'};') - local obj = toTsObject(objS) - if not obj then - error('bl.new/bl.datablock: failed to create object', 3) end - - return obj + local propsT = {} + if props then + for k, v in pairs(props) do + if not isValidFuncName(k) then + error('bl.new/bl.datablock: invalid property name \'' .. k .. '\'') + end + table.insert(propsT, k .. '="' .. valToTs(v) .. '";') + end + end + + local objS = _bllua_ts.eval( + 'return ' .. keyword .. ' ' .. class .. '(' .. + (name or '') .. (inherit and (':' .. inherit) or '') .. '){' .. + table.concat(propsT) .. '};') + local obj = toTsObject(objS) + if not obj then + error('bl.new/bl.datablock: failed to create object', 3) + end + + return obj end local function parseTsDecl(decl) - local class, name, inherit - if decl:find(' ') then -- class ... - local cl, rest = decl:match('^([^ ]+) ([^ ]+)$') - class = cl - if rest:find(':') then -- class name:inherit - name, inherit = rest:match('^([^:]*):([^:]+)$') - if not name then class = nil end -- error - if name=='' then name = nil end -- class :inherit - else - name = rest - end - else -- class - class = decl - end - if not ( - isValidFuncName(class) and - (name==nil or isValidFuncName(name)) and - (inherit==nil or isValidFuncName(inherit)) ) then - error('bl.new/bl.datablock: invalid decl \''..decl..'\'\n'.. - 'must be of the format: \'className\', \'className name\', '.. - '\'className :inherit\', or \'className name:inherit\'', 3) end - return class, name, inherit + local class, name, inherit + if decl:find(' ') then -- class ... + local cl, rest = decl:match('^([^ ]+) ([^ ]+)$') + class = cl + if rest:find(':') then -- class name:inherit + name, inherit = rest:match('^([^:]*):([^:]+)$') + if not name then class = nil end -- error + if name == '' then name = nil end -- class :inherit + else + name = rest + end + else -- class + class = decl + end + if not ( + isValidFuncName(class) and + (name == nil or isValidFuncName(name)) and + (inherit == nil or isValidFuncName(inherit))) then + error('bl.new/bl.datablock: invalid decl \'' .. decl .. '\'\n' .. + 'must be of the format: \'className\', \'className name\', ' .. + '\'className :inherit\', or \'className name:inherit\'', 3) + end + return class, name, inherit end function bl.new(decl, props) - local class, name, inherit = parseTsDecl(decl) - return createTsObj('new', class, name, inherit, props) + local class, name, inherit = parseTsDecl(decl) + return createTsObj('new', class, name, inherit, props) end + function bl.datablock(decl, props) - local class, name, inherit = parseTsDecl(decl) - if not name then error('bl.datablock: must specify a name', 2) end - return createTsObj('datablock', class, name, inherit, props) + local class, name, inherit = parseTsDecl(decl) + if not name then error('bl.datablock: must specify a name', 2) end + return createTsObj('datablock', class, name, inherit, props) end setmetatable(bl, tsMeta) -- 2.49.1 From 87e199ea5c544ce60ae0e811eadc4a2f65a77134 Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Mon, 6 Oct 2025 10:08:17 -0400 Subject: [PATCH 09/22] formatting --- src/util/libbl.lua | 12 ++++++------ src/util/libts-lua.lua | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/util/libbl.lua b/src/util/libbl.lua index 237d59c..d875a76 100644 --- a/src/util/libbl.lua +++ b/src/util/libbl.lua @@ -83,16 +83,16 @@ local toTsObject local function tsBool(v) return v ~= '' and v ~= '0' end -- Convert a Lua var into a TS string, or error if not possible local function valToTs(val) - if val == nil then -- nil -> '' + if val == nil then -- nil -> '' return '' elseif type(val) == 'boolean' then -- bool -> 0 or 1 return val and '1' or '0' - elseif type(val) == 'number' then -- number + elseif type(val) == 'number' then -- number return tostring(val) - elseif type(val) == 'string' then -- string + elseif type(val) == 'string' then -- string return val elseif type(val) == 'table' then - if val._tsObjectId then -- object -> object id + if val._tsObjectId then -- object -> object id return tostring(val._tsObjectId) elseif isTsVector(val) then -- vector - > 3 numbers return table.concat(val, ' ') @@ -1023,9 +1023,9 @@ local function parseTsDecl(decl) if decl:find(' ') then -- class ... local cl, rest = decl:match('^([^ ]+) ([^ ]+)$') class = cl - if rest:find(':') then -- class name:inherit + if rest:find(':') then -- class name:inherit name, inherit = rest:match('^([^:]*):([^:]+)$') - if not name then class = nil end -- error + if not name then class = nil end -- error if name == '' then name = nil end -- class :inherit else name = rest diff --git a/src/util/libts-lua.lua b/src/util/libts-lua.lua index a72d39d..9fd7576 100644 --- a/src/util/libts-lua.lua +++ b/src/util/libts-lua.lua @@ -183,14 +183,14 @@ function require(mod) if require_memo[mod] then return unpack(require_memo[mod]) end local fp = mod:gsub('%.', '/') local fns = { - './' .. fp .. '.lua', -- local file + './' .. fp .. '.lua', -- local file './' .. fp .. '/init.lua', -- local library - fp .. '.lua', -- global file - fp .. '/init.lua', -- global library + fp .. '.lua', -- global file + fp .. '/init.lua', -- global library } if fp:lower():find('^add-ons/') then local addonpath = fp:lower():match('^add-ons/[^/]+') .. '/' - table.insert(fns, addonpath .. fp .. '.lua') -- add-on file + table.insert(fns, addonpath .. fp .. '.lua') -- add-on file table.insert(fns, addonpath .. fp .. '/init.lua') -- add-on library end for _, fn in ipairs(fns) do -- 2.49.1 From 93a47d54be89946a427c9d3a1cc5b3ec88dbe37d Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Mon, 6 Oct 2025 11:47:11 -0400 Subject: [PATCH 10/22] make --- .gitignore | 1 + .vscode/settings.json | 23 +++++++++--------- CMakeLists.txt | 56 +++++++++++++++++++++++++++++++++++++++++++ make.bat | 27 +++++++++++++++++++++ 4 files changed, 95 insertions(+), 12 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 make.bat diff --git a/.gitignore b/.gitignore index 567609b..a4fb4fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ build/ +.cache/ diff --git a/.vscode/settings.json b/.vscode/settings.json index a34bb45..5466a6b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,12 +1,11 @@ -{ - "Lua.diagnostics.globals": [ - "_bllua_ts", - "_bllua_requiresecure", - "_bllua_on_unload" - ], - "Lua.runtime.version": "Lua 5.1", - "Lua.diagnostics.disable": [ - "lowercase-global", - "undefined-global" - ] -} \ No newline at end of file +{ + "Lua.diagnostics.globals": [ + "_bllua_ts", + "_bllua_requiresecure", + "_bllua_on_unload" + ], + "Lua.runtime.version": "Lua 5.1", + "Lua.diagnostics.disable": ["lowercase-global", "undefined-global"], + "C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json", + "C_Cpp.default.compilerPath": "C:/msys64/mingw32/bin/g++.exe" +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5d38335 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,56 @@ +cmake_minimum_required(VERSION 3.10) + +project(BlockLua CXX) + +# Export compile_commands.json for VSCode IntelliSense +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Output directories to mirror compile.bat's build folder +set(OUTPUT_DIR ${CMAKE_SOURCE_DIR}/build) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${OUTPUT_DIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${OUTPUT_DIR}) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${OUTPUT_DIR}) + +# Global compile options to mirror compile.bat +add_compile_options( + -Wall + -Werror + -m32 + -static-libgcc + -static-libstdc++ +) + +# Include paths +include_directories( + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/inc/tsfuncs + ${CMAKE_SOURCE_DIR}/inc/lua +) + +# Link directories (for -L.) and libraries from compile.bat +link_directories( + ${CMAKE_SOURCE_DIR} +) + +# Safe DLL +add_library(BlockLua SHARED src/bllua4.cpp) +# Ensure output name matches compile.bat +set_target_properties(BlockLua PROPERTIES OUTPUT_NAME "BlockLua") +# Linker flags and libraries +if(MSVC) + # Not expected with mingw, but keep placeholder +else() + target_link_libraries(BlockLua PRIVATE psapi lua5.1) +endif() + +# Unsafe DLL (with BLLUA_UNSAFE definition) +add_library(BlockLuaUnsafe SHARED src/bllua4.cpp) +set_target_properties(BlockLuaUnsafe PROPERTIES OUTPUT_NAME "BlockLua-Unsafe") + +target_compile_definitions(BlockLuaUnsafe PRIVATE BLLUA_UNSAFE) + +if(MSVC) + # Not expected with mingw, but keep placeholder +else() + target_link_libraries(BlockLuaUnsafe PRIVATE psapi lua5.1) +endif() diff --git a/make.bat b/make.bat new file mode 100644 index 0000000..1884ee2 --- /dev/null +++ b/make.bat @@ -0,0 +1,27 @@ +@echo off +cd /d %~dp0 + +REM Ensure MinGW32 toolchain is first in PATH (matches compile.bat) +set "PATH=C:\msys64\mingw32\bin;%PATH%" + +REM Configure CMake (generate into build/) +cmake -S . -B build -G "MinGW Makefiles" +if errorlevel 1 goto :error + +REM Build (Release by default) +cmake --build build --config Release -j +if errorlevel 1 goto :error + +echo. +echo Build completed. +echo Outputs in .\build : +echo - BlockLua.dll + +echo - BlockLua-Unsafe.dll + +exit /b 0 + +:error +echo. +echo Build failed. See errors above. +exit /b 1 -- 2.49.1 From e47f6d4651d7c1f64b56b45060c318b7d1afbe21 Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Mon, 6 Oct 2025 12:03:51 -0400 Subject: [PATCH 11/22] Fix all warnings and errors in C++ --- .clang-format | 14 ++ inc/lua/lauxlib.h | 170 ++++++++-------- inc/lua/lua.h | 428 ++++++++++++++++++---------------------- inc/lua/lua.hpp | 5 +- inc/lua/luaconf.h | 137 ++++++------- inc/lua/luajit.h | 40 ++-- inc/lua/lualib.h | 26 +-- inc/tsfuncs/BlFuncs.cpp | 377 +++++++++++++++++++---------------- inc/tsfuncs/BlFuncs.hpp | 103 ++++++---- inc/tsfuncs/BlHooks.cpp | 272 ++++++++++++------------- inc/tsfuncs/BlHooks.hpp | 173 ++++++++-------- src/bllua4.cpp | 160 ++++++++------- src/luainterp.cpp | 114 ++++++----- src/luainterp.hpp | 16 ++ src/lualibts.cpp | 408 +++++++++++++++++++++++++------------- src/tsliblua.cpp | 47 +++-- src/util/libts-lua.lua | 6 +- 17 files changed, 1373 insertions(+), 1123 deletions(-) create mode 100644 .clang-format create mode 100644 src/luainterp.hpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..874b411 --- /dev/null +++ b/.clang-format @@ -0,0 +1,14 @@ +BasedOnStyle: LLVM +SortIncludes: CaseSensitive +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '' + Priority: 1 + - Regex: '' + Priority: 2 + - Regex: '"BlHooks\\.hpp"' + Priority: 3 + - Regex: '"BlFuncs\\.hpp"' + Priority: 4 + - Regex: ".*" + Priority: 5 diff --git a/inc/lua/lauxlib.h b/inc/lua/lauxlib.h index a44f027..d21293e 100644 --- a/inc/lua/lauxlib.h +++ b/inc/lua/lauxlib.h @@ -4,93 +4,88 @@ ** See Copyright Notice in lua.h */ - #ifndef lauxlib_h #define lauxlib_h - #include #include #include "lua.h" - /* extra error code for `luaL_load' */ -#define LUA_ERRFILE (LUA_ERRERR+1) +#define LUA_ERRFILE (LUA_ERRERR + 1) typedef struct luaL_Reg { const char *name; lua_CFunction func; } luaL_Reg; -LUALIB_API void (luaL_openlib) (lua_State *L, const char *libname, - const luaL_Reg *l, int nup); -LUALIB_API void (luaL_register) (lua_State *L, const char *libname, - const luaL_Reg *l); -LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e); -LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e); -LUALIB_API int (luaL_typerror) (lua_State *L, int narg, const char *tname); -LUALIB_API int (luaL_argerror) (lua_State *L, int numarg, const char *extramsg); -LUALIB_API const char *(luaL_checklstring) (lua_State *L, int numArg, - size_t *l); -LUALIB_API const char *(luaL_optlstring) (lua_State *L, int numArg, - const char *def, size_t *l); -LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int numArg); -LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int nArg, lua_Number def); +LUALIB_API void(luaL_openlib)(lua_State *L, const char *libname, + const luaL_Reg *l, int nup); +LUALIB_API void(luaL_register)(lua_State *L, const char *libname, + const luaL_Reg *l); +LUALIB_API int(luaL_getmetafield)(lua_State *L, int obj, const char *e); +LUALIB_API int(luaL_callmeta)(lua_State *L, int obj, const char *e); +LUALIB_API int(luaL_typerror)(lua_State *L, int narg, const char *tname); +LUALIB_API int(luaL_argerror)(lua_State *L, int numarg, const char *extramsg); +LUALIB_API const char *(luaL_checklstring)(lua_State * L, int numArg, + size_t *l); +LUALIB_API const char *(luaL_optlstring)(lua_State * L, int numArg, + const char *def, size_t *l); +LUALIB_API lua_Number(luaL_checknumber)(lua_State *L, int numArg); +LUALIB_API lua_Number(luaL_optnumber)(lua_State *L, int nArg, lua_Number def); -LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int numArg); -LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int nArg, - lua_Integer def); +LUALIB_API lua_Integer(luaL_checkinteger)(lua_State *L, int numArg); +LUALIB_API lua_Integer(luaL_optinteger)(lua_State *L, int nArg, + lua_Integer def); -LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg); -LUALIB_API void (luaL_checktype) (lua_State *L, int narg, int t); -LUALIB_API void (luaL_checkany) (lua_State *L, int narg); +LUALIB_API void(luaL_checkstack)(lua_State *L, int sz, const char *msg); +LUALIB_API void(luaL_checktype)(lua_State *L, int narg, int t); +LUALIB_API void(luaL_checkany)(lua_State *L, int narg); -LUALIB_API int (luaL_newmetatable) (lua_State *L, const char *tname); -LUALIB_API void *(luaL_checkudata) (lua_State *L, int ud, const char *tname); +LUALIB_API int(luaL_newmetatable)(lua_State *L, const char *tname); +LUALIB_API void *(luaL_checkudata)(lua_State * L, int ud, const char *tname); -LUALIB_API void (luaL_where) (lua_State *L, int lvl); -LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...); +LUALIB_API void(luaL_where)(lua_State *L, int lvl); +LUALIB_API int(luaL_error)(lua_State *L, const char *fmt, ...); -LUALIB_API int (luaL_checkoption) (lua_State *L, int narg, const char *def, - const char *const lst[]); +LUALIB_API int(luaL_checkoption)(lua_State *L, int narg, const char *def, + const char *const lst[]); /* pre-defined references */ -#define LUA_NOREF (-2) -#define LUA_REFNIL (-1) +#define LUA_NOREF (-2) +#define LUA_REFNIL (-1) -LUALIB_API int (luaL_ref) (lua_State *L, int t); -LUALIB_API void (luaL_unref) (lua_State *L, int t, int ref); +LUALIB_API int(luaL_ref)(lua_State *L, int t); +LUALIB_API void(luaL_unref)(lua_State *L, int t, int ref); -LUALIB_API int (luaL_loadfile) (lua_State *L, const char *filename); -LUALIB_API int (luaL_loadbuffer) (lua_State *L, const char *buff, size_t sz, - const char *name); -LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s); +LUALIB_API int(luaL_loadfile)(lua_State *L, const char *filename); +LUALIB_API int(luaL_loadbuffer)(lua_State *L, const char *buff, size_t sz, + const char *name); +LUALIB_API int(luaL_loadstring)(lua_State *L, const char *s); -LUALIB_API lua_State *(luaL_newstate) (void); +LUALIB_API lua_State *(luaL_newstate)(void); +LUALIB_API const char *(luaL_gsub)(lua_State * L, const char *s, const char *p, + const char *r); -LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, const char *p, - const char *r); - -LUALIB_API const char *(luaL_findtable) (lua_State *L, int idx, - const char *fname, int szhint); +LUALIB_API const char *(luaL_findtable)(lua_State * L, int idx, + const char *fname, int szhint); /* From Lua 5.2. */ LUALIB_API int luaL_fileresult(lua_State *L, int stat, const char *fname); LUALIB_API int luaL_execresult(lua_State *L, int stat); -LUALIB_API int (luaL_loadfilex) (lua_State *L, const char *filename, - const char *mode); -LUALIB_API int (luaL_loadbufferx) (lua_State *L, const char *buff, size_t sz, - const char *name, const char *mode); -LUALIB_API void luaL_traceback (lua_State *L, lua_State *L1, const char *msg, - int level); -LUALIB_API void (luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup); -LUALIB_API void (luaL_pushmodule) (lua_State *L, const char *modname, - int sizehint); -LUALIB_API void *(luaL_testudata) (lua_State *L, int ud, const char *tname); -LUALIB_API void (luaL_setmetatable) (lua_State *L, const char *tname); - +LUALIB_API int(luaL_loadfilex)(lua_State *L, const char *filename, + const char *mode); +LUALIB_API int(luaL_loadbufferx)(lua_State *L, const char *buff, size_t sz, + const char *name, const char *mode); +LUALIB_API void luaL_traceback(lua_State *L, lua_State *L1, const char *msg, + int level); +LUALIB_API void(luaL_setfuncs)(lua_State *L, const luaL_Reg *l, int nup); +LUALIB_API void(luaL_pushmodule)(lua_State *L, const char *modname, + int sizehint); +LUALIB_API void *(luaL_testudata)(lua_State * L, int ud, const char *tname); +LUALIB_API void(luaL_setmetatable)(lua_State *L, const char *tname); /* ** =============================================================== @@ -98,31 +93,31 @@ LUALIB_API void (luaL_setmetatable) (lua_State *L, const char *tname); ** =============================================================== */ -#define luaL_argcheck(L, cond,numarg,extramsg) \ - ((void)((cond) || luaL_argerror(L, (numarg), (extramsg)))) -#define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL)) -#define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL)) -#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n))) -#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d))) -#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n))) -#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d))) +#define luaL_argcheck(L, cond, numarg, extramsg) \ + ((void)((cond) || luaL_argerror(L, (numarg), (extramsg)))) +#define luaL_checkstring(L, n) (luaL_checklstring(L, (n), NULL)) +#define luaL_optstring(L, n, d) (luaL_optlstring(L, (n), (d), NULL)) +#define luaL_checkint(L, n) ((int)luaL_checkinteger(L, (n))) +#define luaL_optint(L, n, d) ((int)luaL_optinteger(L, (n), (d))) +#define luaL_checklong(L, n) ((long)luaL_checkinteger(L, (n))) +#define luaL_optlong(L, n, d) ((long)luaL_optinteger(L, (n), (d))) -#define luaL_typename(L,i) lua_typename(L, lua_type(L,(i))) +#define luaL_typename(L, i) lua_typename(L, lua_type(L, (i))) -#define luaL_dofile(L, fn) \ - (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0)) +#define luaL_dofile(L, fn) \ + (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0)) -#define luaL_dostring(L, s) \ - (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0)) +#define luaL_dostring(L, s) \ + (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0)) -#define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n))) +#define luaL_getmetatable(L, n) (lua_getfield(L, LUA_REGISTRYINDEX, (n))) -#define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n))) +#define luaL_opt(L, f, n, d) (lua_isnoneornil(L, (n)) ? (d) : f(L, (n))) /* From Lua 5.2. */ -#define luaL_newlibtable(L, l) \ - lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1) -#define luaL_newlib(L, l) (luaL_newlibtable(L, l), luaL_setfuncs(L, l, 0)) +#define luaL_newlibtable(L, l) \ + lua_createtable(L, 0, sizeof(l) / sizeof((l)[0]) - 1) +#define luaL_newlib(L, l) (luaL_newlibtable(L, l), luaL_setfuncs(L, l, 0)) /* ** {====================================================== @@ -130,31 +125,28 @@ LUALIB_API void (luaL_setmetatable) (lua_State *L, const char *tname); ** ======================================================= */ - - typedef struct luaL_Buffer { - char *p; /* current position in buffer */ - int lvl; /* number of strings in the stack (level) */ + char *p; /* current position in buffer */ + int lvl; /* number of strings in the stack (level) */ lua_State *L; char buffer[LUAL_BUFFERSIZE]; } luaL_Buffer; -#define luaL_addchar(B,c) \ - ((void)((B)->p < ((B)->buffer+LUAL_BUFFERSIZE) || luaL_prepbuffer(B)), \ +#define luaL_addchar(B, c) \ + ((void)((B)->p < ((B)->buffer + LUAL_BUFFERSIZE) || luaL_prepbuffer(B)), \ (*(B)->p++ = (char)(c))) /* compatibility only */ -#define luaL_putchar(B,c) luaL_addchar(B,c) +#define luaL_putchar(B, c) luaL_addchar(B, c) -#define luaL_addsize(B,n) ((B)->p += (n)) - -LUALIB_API void (luaL_buffinit) (lua_State *L, luaL_Buffer *B); -LUALIB_API char *(luaL_prepbuffer) (luaL_Buffer *B); -LUALIB_API void (luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l); -LUALIB_API void (luaL_addstring) (luaL_Buffer *B, const char *s); -LUALIB_API void (luaL_addvalue) (luaL_Buffer *B); -LUALIB_API void (luaL_pushresult) (luaL_Buffer *B); +#define luaL_addsize(B, n) ((B)->p += (n)) +LUALIB_API void(luaL_buffinit)(lua_State *L, luaL_Buffer *B); +LUALIB_API char *(luaL_prepbuffer)(luaL_Buffer * B); +LUALIB_API void(luaL_addlstring)(luaL_Buffer *B, const char *s, size_t l); +LUALIB_API void(luaL_addstring)(luaL_Buffer *B, const char *s); +LUALIB_API void(luaL_addvalue)(luaL_Buffer *B); +LUALIB_API void(luaL_pushresult)(luaL_Buffer *B); /* }====================================================== */ diff --git a/inc/lua/lua.h b/inc/lua/lua.h index 850bd79..eb61e5f 100644 --- a/inc/lua/lua.h +++ b/inc/lua/lua.h @@ -5,88 +5,75 @@ ** See Copyright Notice at the end of this file */ - #ifndef lua_h #define lua_h #include #include - #include "luaconf.h" - -#define LUA_VERSION "Lua 5.1" -#define LUA_RELEASE "Lua 5.1.4" -#define LUA_VERSION_NUM 501 -#define LUA_COPYRIGHT "Copyright (C) 1994-2008 Lua.org, PUC-Rio" -#define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo & W. Celes" - +#define LUA_VERSION "Lua 5.1" +#define LUA_RELEASE "Lua 5.1.4" +#define LUA_VERSION_NUM 501 +#define LUA_COPYRIGHT "Copyright (C) 1994-2008 Lua.org, PUC-Rio" +#define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo & W. Celes" /* mark for precompiled code (`Lua') */ -#define LUA_SIGNATURE "\033Lua" +#define LUA_SIGNATURE "\033Lua" /* option for multiple returns in `lua_pcall' and `lua_call' */ -#define LUA_MULTRET (-1) - +#define LUA_MULTRET (-1) /* ** pseudo-indices */ -#define LUA_REGISTRYINDEX (-10000) -#define LUA_ENVIRONINDEX (-10001) -#define LUA_GLOBALSINDEX (-10002) -#define lua_upvalueindex(i) (LUA_GLOBALSINDEX-(i)) - +#define LUA_REGISTRYINDEX (-10000) +#define LUA_ENVIRONINDEX (-10001) +#define LUA_GLOBALSINDEX (-10002) +#define lua_upvalueindex(i) (LUA_GLOBALSINDEX - (i)) /* thread status */ -#define LUA_OK 0 -#define LUA_YIELD 1 -#define LUA_ERRRUN 2 -#define LUA_ERRSYNTAX 3 -#define LUA_ERRMEM 4 -#define LUA_ERRERR 5 - +#define LUA_OK 0 +#define LUA_YIELD 1 +#define LUA_ERRRUN 2 +#define LUA_ERRSYNTAX 3 +#define LUA_ERRMEM 4 +#define LUA_ERRERR 5 typedef struct lua_State lua_State; -typedef int (*lua_CFunction) (lua_State *L); - +typedef int (*lua_CFunction)(lua_State *L); /* ** functions that read/write blocks when loading/dumping Lua chunks */ -typedef const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz); - -typedef int (*lua_Writer) (lua_State *L, const void* p, size_t sz, void* ud); +typedef const char *(*lua_Reader)(lua_State *L, void *ud, size_t *sz); +typedef int (*lua_Writer)(lua_State *L, const void *p, size_t sz, void *ud); /* ** prototype for memory-allocation functions */ -typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize); - +typedef void *(*lua_Alloc)(void *ud, void *ptr, size_t osize, size_t nsize); /* ** basic types */ -#define LUA_TNONE (-1) - -#define LUA_TNIL 0 -#define LUA_TBOOLEAN 1 -#define LUA_TLIGHTUSERDATA 2 -#define LUA_TNUMBER 3 -#define LUA_TSTRING 4 -#define LUA_TTABLE 5 -#define LUA_TFUNCTION 6 -#define LUA_TUSERDATA 7 -#define LUA_TTHREAD 8 - +#define LUA_TNONE (-1) +#define LUA_TNIL 0 +#define LUA_TBOOLEAN 1 +#define LUA_TLIGHTUSERDATA 2 +#define LUA_TNUMBER 3 +#define LUA_TSTRING 4 +#define LUA_TTABLE 5 +#define LUA_TFUNCTION 6 +#define LUA_TUSERDATA 7 +#define LUA_TTHREAD 8 /* minimum Lua stack available to a C function */ -#define LUA_MINSTACK 20 - +#define LUA_MINSTACK 20 /* ** generic extra include file @@ -95,157 +82,143 @@ typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize); #include LUA_USER_H #endif - /* type of numbers in Lua */ typedef LUA_NUMBER lua_Number; - /* type for integer functions */ typedef LUA_INTEGER lua_Integer; - - /* ** state manipulation */ -LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud); -LUA_API void (lua_close) (lua_State *L); -LUA_API lua_State *(lua_newthread) (lua_State *L); - -LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf); +LUA_API lua_State *(lua_newstate)(lua_Alloc f, void *ud); +LUA_API void(lua_close)(lua_State *L); +LUA_API lua_State *(lua_newthread)(lua_State * L); +LUA_API lua_CFunction(lua_atpanic)(lua_State *L, lua_CFunction panicf); /* ** basic stack manipulation */ -LUA_API int (lua_gettop) (lua_State *L); -LUA_API void (lua_settop) (lua_State *L, int idx); -LUA_API void (lua_pushvalue) (lua_State *L, int idx); -LUA_API void (lua_remove) (lua_State *L, int idx); -LUA_API void (lua_insert) (lua_State *L, int idx); -LUA_API void (lua_replace) (lua_State *L, int idx); -LUA_API int (lua_checkstack) (lua_State *L, int sz); - -LUA_API void (lua_xmove) (lua_State *from, lua_State *to, int n); +LUA_API int(lua_gettop)(lua_State *L); +LUA_API void(lua_settop)(lua_State *L, int idx); +LUA_API void(lua_pushvalue)(lua_State *L, int idx); +LUA_API void(lua_remove)(lua_State *L, int idx); +LUA_API void(lua_insert)(lua_State *L, int idx); +LUA_API void(lua_replace)(lua_State *L, int idx); +LUA_API int(lua_checkstack)(lua_State *L, int sz); +LUA_API void(lua_xmove)(lua_State *from, lua_State *to, int n); /* ** access functions (stack -> C) */ -LUA_API int (lua_isnumber) (lua_State *L, int idx); -LUA_API int (lua_isstring) (lua_State *L, int idx); -LUA_API int (lua_iscfunction) (lua_State *L, int idx); -LUA_API int (lua_isuserdata) (lua_State *L, int idx); -LUA_API int (lua_type) (lua_State *L, int idx); -LUA_API const char *(lua_typename) (lua_State *L, int tp); +LUA_API int(lua_isnumber)(lua_State *L, int idx); +LUA_API int(lua_isstring)(lua_State *L, int idx); +LUA_API int(lua_iscfunction)(lua_State *L, int idx); +LUA_API int(lua_isuserdata)(lua_State *L, int idx); +LUA_API int(lua_type)(lua_State *L, int idx); +LUA_API const char *(lua_typename)(lua_State * L, int tp); -LUA_API int (lua_equal) (lua_State *L, int idx1, int idx2); -LUA_API int (lua_rawequal) (lua_State *L, int idx1, int idx2); -LUA_API int (lua_lessthan) (lua_State *L, int idx1, int idx2); - -LUA_API lua_Number (lua_tonumber) (lua_State *L, int idx); -LUA_API lua_Integer (lua_tointeger) (lua_State *L, int idx); -LUA_API int (lua_toboolean) (lua_State *L, int idx); -LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len); -LUA_API size_t (lua_objlen) (lua_State *L, int idx); -LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx); -LUA_API void *(lua_touserdata) (lua_State *L, int idx); -LUA_API lua_State *(lua_tothread) (lua_State *L, int idx); -LUA_API const void *(lua_topointer) (lua_State *L, int idx); +LUA_API int(lua_equal)(lua_State *L, int idx1, int idx2); +LUA_API int(lua_rawequal)(lua_State *L, int idx1, int idx2); +LUA_API int(lua_lessthan)(lua_State *L, int idx1, int idx2); +LUA_API lua_Number(lua_tonumber)(lua_State *L, int idx); +LUA_API lua_Integer(lua_tointeger)(lua_State *L, int idx); +LUA_API int(lua_toboolean)(lua_State *L, int idx); +LUA_API const char *(lua_tolstring)(lua_State * L, int idx, size_t *len); +LUA_API size_t(lua_objlen)(lua_State *L, int idx); +LUA_API lua_CFunction(lua_tocfunction)(lua_State *L, int idx); +LUA_API void *(lua_touserdata)(lua_State * L, int idx); +LUA_API lua_State *(lua_tothread)(lua_State * L, int idx); +LUA_API const void *(lua_topointer)(lua_State * L, int idx); /* ** push functions (C -> stack) */ -LUA_API void (lua_pushnil) (lua_State *L); -LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n); -LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n); -LUA_API void (lua_pushlstring) (lua_State *L, const char *s, size_t l); -LUA_API void (lua_pushstring) (lua_State *L, const char *s); -LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt, - va_list argp); -LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...); -LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n); -LUA_API void (lua_pushboolean) (lua_State *L, int b); -LUA_API void (lua_pushlightuserdata) (lua_State *L, void *p); -LUA_API int (lua_pushthread) (lua_State *L); - +LUA_API void(lua_pushnil)(lua_State *L); +LUA_API void(lua_pushnumber)(lua_State *L, lua_Number n); +LUA_API void(lua_pushinteger)(lua_State *L, lua_Integer n); +LUA_API void(lua_pushlstring)(lua_State *L, const char *s, size_t l); +LUA_API void(lua_pushstring)(lua_State *L, const char *s); +LUA_API const char *(lua_pushvfstring)(lua_State * L, const char *fmt, + va_list argp); +LUA_API const char *(lua_pushfstring)(lua_State * L, const char *fmt, ...); +LUA_API void(lua_pushcclosure)(lua_State *L, lua_CFunction fn, int n); +LUA_API void(lua_pushboolean)(lua_State *L, int b); +LUA_API void(lua_pushlightuserdata)(lua_State *L, void *p); +LUA_API int(lua_pushthread)(lua_State *L); /* ** get functions (Lua -> stack) */ -LUA_API void (lua_gettable) (lua_State *L, int idx); -LUA_API void (lua_getfield) (lua_State *L, int idx, const char *k); -LUA_API void (lua_rawget) (lua_State *L, int idx); -LUA_API void (lua_rawgeti) (lua_State *L, int idx, int n); -LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec); -LUA_API void *(lua_newuserdata) (lua_State *L, size_t sz); -LUA_API int (lua_getmetatable) (lua_State *L, int objindex); -LUA_API void (lua_getfenv) (lua_State *L, int idx); - +LUA_API void(lua_gettable)(lua_State *L, int idx); +LUA_API void(lua_getfield)(lua_State *L, int idx, const char *k); +LUA_API void(lua_rawget)(lua_State *L, int idx); +LUA_API void(lua_rawgeti)(lua_State *L, int idx, int n); +LUA_API void(lua_createtable)(lua_State *L, int narr, int nrec); +LUA_API void *(lua_newuserdata)(lua_State * L, size_t sz); +LUA_API int(lua_getmetatable)(lua_State *L, int objindex); +LUA_API void(lua_getfenv)(lua_State *L, int idx); /* ** set functions (stack -> Lua) */ -LUA_API void (lua_settable) (lua_State *L, int idx); -LUA_API void (lua_setfield) (lua_State *L, int idx, const char *k); -LUA_API void (lua_rawset) (lua_State *L, int idx); -LUA_API void (lua_rawseti) (lua_State *L, int idx, int n); -LUA_API int (lua_setmetatable) (lua_State *L, int objindex); -LUA_API int (lua_setfenv) (lua_State *L, int idx); - +LUA_API void(lua_settable)(lua_State *L, int idx); +LUA_API void(lua_setfield)(lua_State *L, int idx, const char *k); +LUA_API void(lua_rawset)(lua_State *L, int idx); +LUA_API void(lua_rawseti)(lua_State *L, int idx, int n); +LUA_API int(lua_setmetatable)(lua_State *L, int objindex); +LUA_API int(lua_setfenv)(lua_State *L, int idx); /* ** `load' and `call' functions (load and run Lua code) */ -LUA_API void (lua_call) (lua_State *L, int nargs, int nresults); -LUA_API int (lua_pcall) (lua_State *L, int nargs, int nresults, int errfunc); -LUA_API int (lua_cpcall) (lua_State *L, lua_CFunction func, void *ud); -LUA_API int (lua_load) (lua_State *L, lua_Reader reader, void *dt, - const char *chunkname); - -LUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data); +LUA_API void(lua_call)(lua_State *L, int nargs, int nresults); +LUA_API int(lua_pcall)(lua_State *L, int nargs, int nresults, int errfunc); +LUA_API int(lua_cpcall)(lua_State *L, lua_CFunction func, void *ud); +LUA_API int(lua_load)(lua_State *L, lua_Reader reader, void *dt, + const char *chunkname); +LUA_API int(lua_dump)(lua_State *L, lua_Writer writer, void *data); /* ** coroutine functions */ -LUA_API int (lua_yield) (lua_State *L, int nresults); -LUA_API int (lua_resume) (lua_State *L, int narg); -LUA_API int (lua_status) (lua_State *L); +LUA_API int(lua_yield)(lua_State *L, int nresults); +LUA_API int(lua_resume)(lua_State *L, int narg); +LUA_API int(lua_status)(lua_State *L); /* ** garbage-collection function and options */ -#define LUA_GCSTOP 0 -#define LUA_GCRESTART 1 -#define LUA_GCCOLLECT 2 -#define LUA_GCCOUNT 3 -#define LUA_GCCOUNTB 4 -#define LUA_GCSTEP 5 -#define LUA_GCSETPAUSE 6 -#define LUA_GCSETSTEPMUL 7 -#define LUA_GCISRUNNING 9 - -LUA_API int (lua_gc) (lua_State *L, int what, int data); +#define LUA_GCSTOP 0 +#define LUA_GCRESTART 1 +#define LUA_GCCOLLECT 2 +#define LUA_GCCOUNT 3 +#define LUA_GCCOUNTB 4 +#define LUA_GCSTEP 5 +#define LUA_GCSETPAUSE 6 +#define LUA_GCSETSTEPMUL 7 +#define LUA_GCISRUNNING 9 +LUA_API int(lua_gc)(lua_State *L, int what, int data); /* ** miscellaneous functions */ -LUA_API int (lua_error) (lua_State *L); +LUA_API int(lua_error)(lua_State *L); -LUA_API int (lua_next) (lua_State *L, int idx); - -LUA_API void (lua_concat) (lua_State *L, int n); - -LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud); -LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud); +LUA_API int(lua_next)(lua_State *L, int idx); +LUA_API void(lua_concat)(lua_State *L, int n); +LUA_API lua_Alloc(lua_getallocf)(lua_State *L, void **ud); +LUA_API void lua_setallocf(lua_State *L, lua_Alloc f, void *ud); /* ** =============================================================== @@ -253,52 +226,48 @@ LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud); ** =============================================================== */ -#define lua_pop(L,n) lua_settop(L, -(n)-1) +#define lua_pop(L, n) lua_settop(L, -(n) - 1) -#define lua_newtable(L) lua_createtable(L, 0, 0) +#define lua_newtable(L) lua_createtable(L, 0, 0) -#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n))) +#define lua_register(L, n, f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n))) -#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0) +#define lua_pushcfunction(L, f) lua_pushcclosure(L, (f), 0) -#define lua_strlen(L,i) lua_objlen(L, (i)) +#define lua_strlen(L, i) lua_objlen(L, (i)) -#define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION) -#define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE) -#define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA) -#define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL) -#define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN) -#define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD) -#define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE) -#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0) +#define lua_isfunction(L, n) (lua_type(L, (n)) == LUA_TFUNCTION) +#define lua_istable(L, n) (lua_type(L, (n)) == LUA_TTABLE) +#define lua_islightuserdata(L, n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA) +#define lua_isnil(L, n) (lua_type(L, (n)) == LUA_TNIL) +#define lua_isboolean(L, n) (lua_type(L, (n)) == LUA_TBOOLEAN) +#define lua_isthread(L, n) (lua_type(L, (n)) == LUA_TTHREAD) +#define lua_isnone(L, n) (lua_type(L, (n)) == LUA_TNONE) +#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0) -#define lua_pushliteral(L, s) \ - lua_pushlstring(L, "" s, (sizeof(s)/sizeof(char))-1) - -#define lua_setglobal(L,s) lua_setfield(L, LUA_GLOBALSINDEX, (s)) -#define lua_getglobal(L,s) lua_getfield(L, LUA_GLOBALSINDEX, (s)) - -#define lua_tostring(L,i) lua_tolstring(L, (i), NULL) +#define lua_pushliteral(L, s) \ + lua_pushlstring(L, "" s, (sizeof(s) / sizeof(char)) - 1) +#define lua_setglobal(L, s) lua_setfield(L, LUA_GLOBALSINDEX, (s)) +#define lua_getglobal(L, s) lua_getfield(L, LUA_GLOBALSINDEX, (s)) +#define lua_tostring(L, i) lua_tolstring(L, (i), NULL) /* ** compatibility macros and functions */ -#define lua_open() luaL_newstate() +#define lua_open() luaL_newstate() -#define lua_getregistry(L) lua_pushvalue(L, LUA_REGISTRYINDEX) +#define lua_getregistry(L) lua_pushvalue(L, LUA_REGISTRYINDEX) -#define lua_getgccount(L) lua_gc(L, LUA_GCCOUNT, 0) - -#define lua_Chunkreader lua_Reader -#define lua_Chunkwriter lua_Writer +#define lua_getgccount(L) lua_gc(L, LUA_GCCOUNT, 0) +#define lua_Chunkreader lua_Reader +#define lua_Chunkwriter lua_Writer /* hack */ -LUA_API void lua_setlevel (lua_State *from, lua_State *to); - +LUA_API void lua_setlevel(lua_State *from, lua_State *to); /* ** {====================================================================== @@ -306,97 +275,90 @@ LUA_API void lua_setlevel (lua_State *from, lua_State *to); ** ======================================================================= */ - /* ** Event codes */ -#define LUA_HOOKCALL 0 -#define LUA_HOOKRET 1 -#define LUA_HOOKLINE 2 -#define LUA_HOOKCOUNT 3 +#define LUA_HOOKCALL 0 +#define LUA_HOOKRET 1 +#define LUA_HOOKLINE 2 +#define LUA_HOOKCOUNT 3 #define LUA_HOOKTAILRET 4 - /* ** Event masks */ -#define LUA_MASKCALL (1 << LUA_HOOKCALL) -#define LUA_MASKRET (1 << LUA_HOOKRET) -#define LUA_MASKLINE (1 << LUA_HOOKLINE) -#define LUA_MASKCOUNT (1 << LUA_HOOKCOUNT) - -typedef struct lua_Debug lua_Debug; /* activation record */ +#define LUA_MASKCALL (1 << LUA_HOOKCALL) +#define LUA_MASKRET (1 << LUA_HOOKRET) +#define LUA_MASKLINE (1 << LUA_HOOKLINE) +#define LUA_MASKCOUNT (1 << LUA_HOOKCOUNT) +typedef struct lua_Debug lua_Debug; /* activation record */ /* Functions to be called by the debuger in specific events */ -typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar); +typedef void (*lua_Hook)(lua_State *L, lua_Debug *ar); - -LUA_API int lua_getstack (lua_State *L, int level, lua_Debug *ar); -LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar); -LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n); -LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n); -LUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n); -LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n); -LUA_API int lua_sethook (lua_State *L, lua_Hook func, int mask, int count); -LUA_API lua_Hook lua_gethook (lua_State *L); -LUA_API int lua_gethookmask (lua_State *L); -LUA_API int lua_gethookcount (lua_State *L); +LUA_API int lua_getstack(lua_State *L, int level, lua_Debug *ar); +LUA_API int lua_getinfo(lua_State *L, const char *what, lua_Debug *ar); +LUA_API const char *lua_getlocal(lua_State *L, const lua_Debug *ar, int n); +LUA_API const char *lua_setlocal(lua_State *L, const lua_Debug *ar, int n); +LUA_API const char *lua_getupvalue(lua_State *L, int funcindex, int n); +LUA_API const char *lua_setupvalue(lua_State *L, int funcindex, int n); +LUA_API int lua_sethook(lua_State *L, lua_Hook func, int mask, int count); +LUA_API lua_Hook lua_gethook(lua_State *L); +LUA_API int lua_gethookmask(lua_State *L); +LUA_API int lua_gethookcount(lua_State *L); /* From Lua 5.2. */ -LUA_API void *lua_upvalueid (lua_State *L, int idx, int n); -LUA_API void lua_upvaluejoin (lua_State *L, int idx1, int n1, int idx2, int n2); -LUA_API int lua_loadx (lua_State *L, lua_Reader reader, void *dt, - const char *chunkname, const char *mode); -LUA_API const lua_Number *lua_version (lua_State *L); -LUA_API void lua_copy (lua_State *L, int fromidx, int toidx); -LUA_API lua_Number lua_tonumberx (lua_State *L, int idx, int *isnum); -LUA_API lua_Integer lua_tointegerx (lua_State *L, int idx, int *isnum); +LUA_API void *lua_upvalueid(lua_State *L, int idx, int n); +LUA_API void lua_upvaluejoin(lua_State *L, int idx1, int n1, int idx2, int n2); +LUA_API int lua_loadx(lua_State *L, lua_Reader reader, void *dt, + const char *chunkname, const char *mode); +LUA_API const lua_Number *lua_version(lua_State *L); +LUA_API void lua_copy(lua_State *L, int fromidx, int toidx); +LUA_API lua_Number lua_tonumberx(lua_State *L, int idx, int *isnum); +LUA_API lua_Integer lua_tointegerx(lua_State *L, int idx, int *isnum); /* From Lua 5.3. */ -LUA_API int lua_isyieldable (lua_State *L); - +LUA_API int lua_isyieldable(lua_State *L); struct lua_Debug { int event; - const char *name; /* (n) */ - const char *namewhat; /* (n) `global', `local', `field', `method' */ - const char *what; /* (S) `Lua', `C', `main', `tail' */ - const char *source; /* (S) */ - int currentline; /* (l) */ - int nups; /* (u) number of upvalues */ - int linedefined; /* (S) */ - int lastlinedefined; /* (S) */ + const char *name; /* (n) */ + const char *namewhat; /* (n) `global', `local', `field', `method' */ + const char *what; /* (S) `Lua', `C', `main', `tail' */ + const char *source; /* (S) */ + int currentline; /* (l) */ + int nups; /* (u) number of upvalues */ + int linedefined; /* (S) */ + int lastlinedefined; /* (S) */ char short_src[LUA_IDSIZE]; /* (S) */ /* private part */ - int i_ci; /* active function */ + int i_ci; /* active function */ }; /* }====================================================================== */ - /****************************************************************************** -* Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved. -* -* Permission is hereby granted, free of charge, to any person obtaining -* a copy of this software and associated documentation files (the -* "Software"), to deal in the Software without restriction, including -* without limitation the rights to use, copy, modify, merge, publish, -* distribute, sublicense, and/or sell copies of the Software, and to -* permit persons to whom the Software is furnished to do so, subject to -* the following conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -******************************************************************************/ - + * Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ******************************************************************************/ #endif diff --git a/inc/lua/lua.hpp b/inc/lua/lua.hpp index 07e9002..d16a012 100644 --- a/inc/lua/lua.hpp +++ b/inc/lua/lua.hpp @@ -1,9 +1,8 @@ // C++ wrapper for LuaJIT header files. extern "C" { -#include "lua.h" #include "lauxlib.h" -#include "lualib.h" +#include "lua.h" #include "luajit.h" +#include "lualib.h" } - diff --git a/inc/lua/luaconf.h b/inc/lua/luaconf.h index c2d29d9..64a789a 100644 --- a/inc/lua/luaconf.h +++ b/inc/lua/luaconf.h @@ -18,135 +18,140 @@ ** In Windows, any exclamation mark ('!') in the path is replaced by the ** path of the directory of the executable file of the current process. */ -#define LUA_LDIR "!\\lua\\" -#define LUA_CDIR "!\\" -#define LUA_PATH_DEFAULT \ - ".\\?.lua;" LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" -#define LUA_CPATH_DEFAULT \ - ".\\?.dll;" LUA_CDIR"?.dll;" LUA_CDIR"loadall.dll" +#define LUA_LDIR "!\\lua\\" +#define LUA_CDIR "!\\" +#define LUA_PATH_DEFAULT ".\\?.lua;" LUA_LDIR "?.lua;" LUA_LDIR "?\\init.lua;" +#define LUA_CPATH_DEFAULT ".\\?.dll;" LUA_CDIR "?.dll;" LUA_CDIR "loadall.dll" #else /* ** Note to distribution maintainers: do NOT patch the following lines! ** Please read ../doc/install.html#distro and pass PREFIX=/usr instead. */ #ifndef LUA_MULTILIB -#define LUA_MULTILIB "lib" +#define LUA_MULTILIB "lib" #endif #ifndef LUA_LMULTILIB -#define LUA_LMULTILIB "lib" +#define LUA_LMULTILIB "lib" #endif -#define LUA_LROOT "/usr/local" -#define LUA_LUADIR "/lua/5.1/" -#define LUA_LJDIR "/luajit-2.1.0-beta3/" +#define LUA_LROOT "/usr/local" +#define LUA_LUADIR "/lua/5.1/" +#define LUA_LJDIR "/luajit-2.1.0-beta3/" #ifdef LUA_ROOT -#define LUA_JROOT LUA_ROOT -#define LUA_RLDIR LUA_ROOT "/share" LUA_LUADIR -#define LUA_RCDIR LUA_ROOT "/" LUA_MULTILIB LUA_LUADIR -#define LUA_RLPATH ";" LUA_RLDIR "?.lua;" LUA_RLDIR "?/init.lua" -#define LUA_RCPATH ";" LUA_RCDIR "?.so" +#define LUA_JROOT LUA_ROOT +#define LUA_RLDIR LUA_ROOT "/share" LUA_LUADIR +#define LUA_RCDIR LUA_ROOT "/" LUA_MULTILIB LUA_LUADIR +#define LUA_RLPATH ";" LUA_RLDIR "?.lua;" LUA_RLDIR "?/init.lua" +#define LUA_RCPATH ";" LUA_RCDIR "?.so" #else -#define LUA_JROOT LUA_LROOT +#define LUA_JROOT LUA_LROOT #define LUA_RLPATH #define LUA_RCPATH #endif -#define LUA_JPATH ";" LUA_JROOT "/share" LUA_LJDIR "?.lua" -#define LUA_LLDIR LUA_LROOT "/share" LUA_LUADIR -#define LUA_LCDIR LUA_LROOT "/" LUA_LMULTILIB LUA_LUADIR -#define LUA_LLPATH ";" LUA_LLDIR "?.lua;" LUA_LLDIR "?/init.lua" -#define LUA_LCPATH1 ";" LUA_LCDIR "?.so" -#define LUA_LCPATH2 ";" LUA_LCDIR "loadall.so" +#define LUA_JPATH ";" LUA_JROOT "/share" LUA_LJDIR "?.lua" +#define LUA_LLDIR LUA_LROOT "/share" LUA_LUADIR +#define LUA_LCDIR LUA_LROOT "/" LUA_LMULTILIB LUA_LUADIR +#define LUA_LLPATH ";" LUA_LLDIR "?.lua;" LUA_LLDIR "?/init.lua" +#define LUA_LCPATH1 ";" LUA_LCDIR "?.so" +#define LUA_LCPATH2 ";" LUA_LCDIR "loadall.so" -#define LUA_PATH_DEFAULT "./?.lua" LUA_JPATH LUA_LLPATH LUA_RLPATH -#define LUA_CPATH_DEFAULT "./?.so" LUA_LCPATH1 LUA_RCPATH LUA_LCPATH2 +#define LUA_PATH_DEFAULT "./?.lua" LUA_JPATH LUA_LLPATH LUA_RLPATH +#define LUA_CPATH_DEFAULT "./?.so" LUA_LCPATH1 LUA_RCPATH LUA_LCPATH2 #endif /* Environment variable names for path overrides and initialization code. */ -#define LUA_PATH "LUA_PATH" -#define LUA_CPATH "LUA_CPATH" -#define LUA_INIT "LUA_INIT" +#define LUA_PATH "LUA_PATH" +#define LUA_CPATH "LUA_CPATH" +#define LUA_INIT "LUA_INIT" /* Special file system characters. */ #if defined(_WIN32) -#define LUA_DIRSEP "\\" +#define LUA_DIRSEP "\\" #else -#define LUA_DIRSEP "/" +#define LUA_DIRSEP "/" #endif -#define LUA_PATHSEP ";" -#define LUA_PATH_MARK "?" -#define LUA_EXECDIR "!" -#define LUA_IGMARK "-" -#define LUA_PATH_CONFIG \ - LUA_DIRSEP "\n" LUA_PATHSEP "\n" LUA_PATH_MARK "\n" \ - LUA_EXECDIR "\n" LUA_IGMARK "\n" +#define LUA_PATHSEP ";" +#define LUA_PATH_MARK "?" +#define LUA_EXECDIR "!" +#define LUA_IGMARK "-" +#define LUA_PATH_CONFIG \ + LUA_DIRSEP "\n" LUA_PATHSEP "\n" LUA_PATH_MARK "\n" LUA_EXECDIR \ + "\n" LUA_IGMARK "\n" /* Quoting in error messages. */ -#define LUA_QL(x) "'" x "'" -#define LUA_QS LUA_QL("%s") +#define LUA_QL(x) "'" x "'" +#define LUA_QS LUA_QL("%s") /* Various tunables. */ -#define LUAI_MAXSTACK 65500 /* Max. # of stack slots for a thread (<64K). */ -#define LUAI_MAXCSTACK 8000 /* Max. # of stack slots for a C func (<10K). */ -#define LUAI_GCPAUSE 200 /* Pause GC until memory is at 200%. */ -#define LUAI_GCMUL 200 /* Run GC at 200% of allocation speed. */ -#define LUA_MAXCAPTURES 32 /* Max. pattern captures. */ +#define LUAI_MAXSTACK 65500 /* Max. # of stack slots for a thread (<64K). */ +#define LUAI_MAXCSTACK 8000 /* Max. # of stack slots for a C func (<10K). */ +#define LUAI_GCPAUSE 200 /* Pause GC until memory is at 200%. */ +#define LUAI_GCMUL 200 /* Run GC at 200% of allocation speed. */ +#define LUA_MAXCAPTURES 32 /* Max. pattern captures. */ /* Configuration for the frontend (the luajit executable). */ #if defined(luajit_c) -#define LUA_PROGNAME "luajit" /* Fallback frontend name. */ -#define LUA_PROMPT "> " /* Interactive prompt. */ -#define LUA_PROMPT2 ">> " /* Continuation prompt. */ -#define LUA_MAXINPUT 512 /* Max. input line length. */ +#define LUA_PROGNAME "luajit" /* Fallback frontend name. */ +#define LUA_PROMPT "> " /* Interactive prompt. */ +#define LUA_PROMPT2 ">> " /* Continuation prompt. */ +#define LUA_MAXINPUT 512 /* Max. input line length. */ #endif /* Note: changing the following defines breaks the Lua 5.1 ABI. */ -#define LUA_INTEGER ptrdiff_t -#define LUA_IDSIZE 60 /* Size of lua_Debug.short_src. */ +#define LUA_INTEGER ptrdiff_t +#define LUA_IDSIZE 60 /* Size of lua_Debug.short_src. */ /* ** Size of lauxlib and io.* on-stack buffers. Weird workaround to avoid using ** unreasonable amounts of stack space, but still retain ABI compatibility. ** Blame Lua for depending on BUFSIZ in the ABI, blame **** for wrecking it. */ -#define LUAL_BUFFERSIZE (BUFSIZ > 16384 ? 8192 : BUFSIZ) +#define LUAL_BUFFERSIZE (BUFSIZ > 16384 ? 8192 : BUFSIZ) /* The following defines are here only for compatibility with luaconf.h ** from the standard Lua distribution. They must not be changed for LuaJIT. */ #define LUA_NUMBER_DOUBLE -#define LUA_NUMBER double -#define LUAI_UACNUMBER double -#define LUA_NUMBER_SCAN "%lf" -#define LUA_NUMBER_FMT "%.14g" -#define lua_number2str(s, n) sprintf((s), LUA_NUMBER_FMT, (n)) -#define LUAI_MAXNUMBER2STR 32 -#define LUA_INTFRMLEN "l" -#define LUA_INTFRM_T long +#define LUA_NUMBER double +#define LUAI_UACNUMBER double +#define LUA_NUMBER_SCAN "%lf" +#define LUA_NUMBER_FMT "%.14g" +#define lua_number2str(s, n) sprintf((s), LUA_NUMBER_FMT, (n)) +#define LUAI_MAXNUMBER2STR 32 +#define LUA_INTFRMLEN "l" +#define LUA_INTFRM_T long /* Linkage of public API functions. */ #if defined(LUA_BUILD_AS_DLL) #if defined(LUA_CORE) || defined(LUA_LIB) -#define LUA_API __declspec(dllexport) +#define LUA_API __declspec(dllexport) #else -#define LUA_API __declspec(dllimport) +#define LUA_API __declspec(dllimport) #endif #else -#define LUA_API extern +#define LUA_API extern #endif -#define LUALIB_API LUA_API +#define LUALIB_API LUA_API /* Support for internal assertions. */ #if defined(LUA_USE_ASSERT) || defined(LUA_USE_APICHECK) #include #endif #ifdef LUA_USE_ASSERT -#define lua_assert(x) assert(x) +#define lua_assert(x) assert(x) #endif #ifdef LUA_USE_APICHECK -#define luai_apicheck(L, o) { (void)L; assert(o); } +#define luai_apicheck(L, o) \ + { \ + (void)L; \ + assert(o); \ + } #else -#define luai_apicheck(L, o) { (void)L; } +#define luai_apicheck(L, o) \ + { \ + (void)L; \ + } #endif #endif diff --git a/inc/lua/luajit.h b/inc/lua/luajit.h index 708a5a1..542c25c 100644 --- a/inc/lua/luajit.h +++ b/inc/lua/luajit.h @@ -30,34 +30,34 @@ #include "lua.h" -#define LUAJIT_VERSION "LuaJIT 2.1.0-beta3" -#define LUAJIT_VERSION_NUM 20100 /* Version 2.1.0 = 02.01.00. */ -#define LUAJIT_VERSION_SYM luaJIT_version_2_1_0_beta3 -#define LUAJIT_COPYRIGHT "Copyright (C) 2005-2017 Mike Pall" -#define LUAJIT_URL "http://luajit.org/" +#define LUAJIT_VERSION "LuaJIT 2.1.0-beta3" +#define LUAJIT_VERSION_NUM 20100 /* Version 2.1.0 = 02.01.00. */ +#define LUAJIT_VERSION_SYM luaJIT_version_2_1_0_beta3 +#define LUAJIT_COPYRIGHT "Copyright (C) 2005-2017 Mike Pall" +#define LUAJIT_URL "http://luajit.org/" /* Modes for luaJIT_setmode. */ -#define LUAJIT_MODE_MASK 0x00ff +#define LUAJIT_MODE_MASK 0x00ff enum { - LUAJIT_MODE_ENGINE, /* Set mode for whole JIT engine. */ - LUAJIT_MODE_DEBUG, /* Set debug mode (idx = level). */ + LUAJIT_MODE_ENGINE, /* Set mode for whole JIT engine. */ + LUAJIT_MODE_DEBUG, /* Set debug mode (idx = level). */ - LUAJIT_MODE_FUNC, /* Change mode for a function. */ - LUAJIT_MODE_ALLFUNC, /* Recurse into subroutine protos. */ - LUAJIT_MODE_ALLSUBFUNC, /* Change only the subroutines. */ + LUAJIT_MODE_FUNC, /* Change mode for a function. */ + LUAJIT_MODE_ALLFUNC, /* Recurse into subroutine protos. */ + LUAJIT_MODE_ALLSUBFUNC, /* Change only the subroutines. */ - LUAJIT_MODE_TRACE, /* Flush a compiled trace. */ + LUAJIT_MODE_TRACE, /* Flush a compiled trace. */ - LUAJIT_MODE_WRAPCFUNC = 0x10, /* Set wrapper mode for C function calls. */ + LUAJIT_MODE_WRAPCFUNC = 0x10, /* Set wrapper mode for C function calls. */ LUAJIT_MODE_MAX }; /* Flags or'ed in to the mode. */ -#define LUAJIT_MODE_OFF 0x0000 /* Turn feature off. */ -#define LUAJIT_MODE_ON 0x0100 /* Turn feature on. */ -#define LUAJIT_MODE_FLUSH 0x0200 /* Flush JIT-compiled code. */ +#define LUAJIT_MODE_OFF 0x0000 /* Turn feature off. */ +#define LUAJIT_MODE_ON 0x0100 /* Turn feature on. */ +#define LUAJIT_MODE_FLUSH 0x0200 /* Flush JIT-compiled code. */ /* LuaJIT public C API. */ @@ -65,13 +65,13 @@ enum { LUA_API int luaJIT_setmode(lua_State *L, int idx, int mode); /* Low-overhead profiling API. */ -typedef void (*luaJIT_profile_callback)(void *data, lua_State *L, - int samples, int vmstate); +typedef void (*luaJIT_profile_callback)(void *data, lua_State *L, int samples, + int vmstate); LUA_API void luaJIT_profile_start(lua_State *L, const char *mode, - luaJIT_profile_callback cb, void *data); + luaJIT_profile_callback cb, void *data); LUA_API void luaJIT_profile_stop(lua_State *L); LUA_API const char *luaJIT_profile_dumpstack(lua_State *L, const char *fmt, - int depth, size_t *len); + int depth, size_t *len); /* Enforce (dynamic) linker error for version mismatches. Call from main. */ LUA_API void LUAJIT_VERSION_SYM(void); diff --git a/inc/lua/lualib.h b/inc/lua/lualib.h index bfc130a..e7e8332 100644 --- a/inc/lua/lualib.h +++ b/inc/lua/lualib.h @@ -8,19 +8,19 @@ #include "lua.h" -#define LUA_FILEHANDLE "FILE*" +#define LUA_FILEHANDLE "FILE*" -#define LUA_COLIBNAME "coroutine" -#define LUA_MATHLIBNAME "math" -#define LUA_STRLIBNAME "string" -#define LUA_TABLIBNAME "table" -#define LUA_IOLIBNAME "io" -#define LUA_OSLIBNAME "os" -#define LUA_LOADLIBNAME "package" -#define LUA_DBLIBNAME "debug" -#define LUA_BITLIBNAME "bit" -#define LUA_JITLIBNAME "jit" -#define LUA_FFILIBNAME "ffi" +#define LUA_COLIBNAME "coroutine" +#define LUA_MATHLIBNAME "math" +#define LUA_STRLIBNAME "string" +#define LUA_TABLIBNAME "table" +#define LUA_IOLIBNAME "io" +#define LUA_OSLIBNAME "os" +#define LUA_LOADLIBNAME "package" +#define LUA_DBLIBNAME "debug" +#define LUA_BITLIBNAME "bit" +#define LUA_JITLIBNAME "jit" +#define LUA_FFILIBNAME "ffi" LUALIB_API int luaopen_base(lua_State *L); LUALIB_API int luaopen_math(lua_State *L); @@ -37,7 +37,7 @@ LUALIB_API int luaopen_ffi(lua_State *L); LUALIB_API void luaL_openlibs(lua_State *L); #ifndef lua_assert -#define lua_assert(x) ((void)0) +#define lua_assert(x) ((void)0) #endif #endif diff --git a/inc/tsfuncs/BlFuncs.cpp b/inc/tsfuncs/BlFuncs.cpp index 68b789d..524e76a 100644 --- a/inc/tsfuncs/BlFuncs.cpp +++ b/inc/tsfuncs/BlFuncs.cpp @@ -2,15 +2,18 @@ ////////////////////////////////////////////////// // BlFuncs Version 1.0 - // Includes - -#include "BlHooks.hpp" +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif #include "BlFuncs.hpp" +#include +#include "BlHooks.hpp" +#include +#include #include - // Scanned structures ADDR tsf_mCacheSequence; @@ -18,221 +21,263 @@ ADDR tsf_mCacheAllocator; ADDR tsf_gIdDictionary; ADDR tsf_gEvalState_globalVars; -BlFunctionDefIntern(tsf_BlStringTable__insert ); -BlFunctionDefIntern(tsf_BlNamespace__find ); +BlFunctionDefIntern(tsf_BlStringTable__insert); +BlFunctionDefIntern(tsf_BlNamespace__find); BlFunctionDefIntern(tsf_BlNamespace__createLocalEntry); -BlFunctionDefIntern(tsf_BlDataChunker__freeBlocks ); -BlFunctionDefIntern(tsf_BlCon__evaluate ); -BlFunctionDefIntern(tsf_BlCon__executef ); -BlFunctionDefIntern(tsf_BlCon__executefSimObj ); -BlFunctionDefIntern(tsf_BlCon__getVariable ); -BlFunctionDefIntern(tsf_BlDictionary__addVariable ); -BlFunctionDefIntern(tsf_BlSim__findObject_name ); -BlFunctionDefIntern(tsf_BlStringStack__getArgBuffer ); -BlFunctionDefIntern(tsf_BlSimObject__getDataField ); -BlFunctionDefIntern(tsf_BlSimObject__setDataField ); -BlFunctionDefIntern(tsf_BlCon__getReturnBuffer ); - +BlFunctionDefIntern(tsf_BlDataChunker__freeBlocks); +BlFunctionDefIntern(tsf_BlCon__evaluate); +BlFunctionDefIntern(tsf_BlCon__executef); +BlFunctionDefIntern(tsf_BlCon__executefSimObj); +BlFunctionDefIntern(tsf_BlCon__getVariable); +BlFunctionDefIntern(tsf_BlDictionary__addVariable); +BlFunctionDefIntern(tsf_BlSim__findObject_name); +BlFunctionDefIntern(tsf_BlStringStack__getArgBuffer); +BlFunctionDefIntern(tsf_BlSimObject__getDataField); +BlFunctionDefIntern(tsf_BlSimObject__setDataField); +BlFunctionDefIntern(tsf_BlCon__getReturnBuffer); // C->TS Args -char* tsf_GetIntArg(signed int value) { - char* ret = tsf_BlStringStack__getArgBuffer(16); - snprintf(ret, 16, "%d", value); - return ret; +char *tsf_GetIntArg(signed int value) { + char *ret = tsf_BlStringStack__getArgBuffer(16); + snprintf(ret, 16, "%d", value); + return ret; } -char* tsf_GetFloatArg(float value) { - char* ret = tsf_BlStringStack__getArgBuffer(32); - snprintf(ret, 32, "%g", value); - return ret; +char *tsf_GetFloatArg(float value) { + char *ret = tsf_BlStringStack__getArgBuffer(32); + snprintf(ret, 32, "%g", value); + return ret; } -char* tsf_GetStringArg(char* value) { - int len = strlen(value)+1; - char* ret = tsf_BlStringStack__getArgBuffer(len); - memcpy(ret, value, len); - return ret; +char *tsf_GetStringArg(char *value) { + int len = strlen(value) + 1; + char *ret = tsf_BlStringStack__getArgBuffer(len); + memcpy(ret, value, len); + return ret; } -char* tsf_GetThisArg(ADDR obj) { - return tsf_GetIntArg(*(signed int *)(obj + 32)); +char *tsf_GetThisArg(ADDR obj) { + return tsf_GetIntArg(*(signed int *)(obj + 32)); } - // Eval -const char* tsf_Eval(const char *code) { - const char *argv[] = {nullptr, code}; - return tsf_BlCon__evaluate(0, 2, argv); +const char *tsf_Eval(const char *code) { + const char *argv[] = {nullptr, code}; + return tsf_BlCon__evaluate(0, 2, argv); } -const char* tsf_Evalf(const char *fmt, ...) { - va_list args; - char code[4096]; - va_start(args, fmt); - vsnprintf(code, 4096, fmt, args); - va_end(args); - - return tsf_Eval((const char*)code); -} +const char *tsf_Evalf(const char *fmt, ...) { + va_list args; + char code[4096]; + va_start(args, fmt); + vsnprintf(code, 4096, fmt, args); + va_end(args); + return tsf_Eval((const char *)code); +} // Objects ADDR tsf_FindObject(unsigned int id) { - ADDR obj = *(ADDR*)(*(ADDR*)(tsf_gIdDictionary) + 4*(id & 0xFFF)); - if(!obj) return 0; - - while(obj && *(unsigned int *)(obj + 32) != id) { - obj = *(ADDR*)(obj + 16); - if(!obj) return 0; - } - - return obj; + ADDR obj = *(ADDR *)(*(ADDR *)(tsf_gIdDictionary) + 4 * (id & 0xFFF)); + if (!obj) + return 0; + + while (obj && *(unsigned int *)(obj + 32) != id) { + obj = *(ADDR *)(obj + 16); + if (!obj) + return 0; + } + + return obj; } -ADDR tsf_FindObject(const char* name) { - return (ADDR)tsf_BlSim__findObject_name(name); +ADDR tsf_FindObject(const char *name) { + return (ADDR)tsf_BlSim__findObject_name(name); } -ADDR tsf_LookupNamespace(const char* ns, const char* package) { - const char* ste_package; - if(package) { - ste_package = tsf_BlStringTable__insert(package, 0); - } else { - ste_package = nullptr; - } - - if(ns) { - const char* ste_namespace = tsf_BlStringTable__insert(ns, 0); - return tsf_BlNamespace__find(ste_namespace, ste_package); - } else { - return tsf_BlNamespace__find(nullptr, ste_package); - } -} -ADDR tsf_LookupNamespace(const char* ns) { - return tsf_LookupNamespace(ns, nullptr); -} +ADDR tsf_LookupNamespace(const char *ns, const char *package) { + const char *ste_package; + if (package) { + ste_package = tsf_BlStringTable__insert(package, 0); + } else { + ste_package = nullptr; + } + if (ns) { + const char *ste_namespace = tsf_BlStringTable__insert(ns, 0); + return tsf_BlNamespace__find(ste_namespace, ste_package); + } else { + return tsf_BlNamespace__find(nullptr, ste_package); + } +} +ADDR tsf_LookupNamespace(const char *ns) { + return tsf_LookupNamespace(ns, nullptr); +} // Object Fields -const char* tsf_GetDataField(ADDR simObject, const char* slotName, const char* array) { - const char *ste_slotName; - if(slotName) { - ste_slotName = tsf_BlStringTable__insert(slotName, 0); - } else { - ste_slotName = nullptr; - } - - return tsf_BlSimObject__getDataField(simObject, ste_slotName, array); +const char *tsf_GetDataField(ADDR simObject, const char *slotName, + const char *array) { + const char *ste_slotName; + if (slotName) { + ste_slotName = tsf_BlStringTable__insert(slotName, 0); + } else { + ste_slotName = nullptr; + } + + return tsf_BlSimObject__getDataField(simObject, ste_slotName, array); } -void tsf_SetDataField(ADDR simObject, const char* slotName, const char* array, const char* value) { - const char* ste_slotName; - if(slotName) { - ste_slotName = tsf_BlStringTable__insert(slotName, 0); - } else { - ste_slotName = nullptr; - } - - tsf_BlSimObject__setDataField(simObject, ste_slotName, array, value); -} +void tsf_SetDataField(ADDR simObject, const char *slotName, const char *array, + const char *value) { + const char *ste_slotName; + if (slotName) { + ste_slotName = tsf_BlStringTable__insert(slotName, 0); + } else { + ste_slotName = nullptr; + } + tsf_BlSimObject__setDataField(simObject, ste_slotName, array, value); +} // TS Global Variables -const char *tsf_GetVar(const char* name) { - return tsf_BlCon__getVariable(name); +const char *tsf_GetVar(const char *name) { + return tsf_BlCon__getVariable(name); } -void tsf_AddVarInternal(const char* name, signed int varType, void* data) { - tsf_BlDictionary__addVariable((ADDR *)tsf_gEvalState_globalVars, name, varType, data); +void tsf_AddVarInternal(const char *name, signed int varType, void *data) { + tsf_BlDictionary__addVariable((ADDR *)tsf_gEvalState_globalVars, name, + varType, data); } -void tsf_AddVar(const char* name, const char** data) { - tsf_AddVarInternal(name, 10, data); +void tsf_AddVar(const char *name, const char **data) { + tsf_AddVarInternal(name, 10, data); } -void tsf_AddVar(const char* name, signed int* data) { - tsf_AddVarInternal(name, 4, data); +void tsf_AddVar(const char *name, signed int *data) { + tsf_AddVarInternal(name, 4, data); } -void tsf_AddVar(const char* name, float* data) { - tsf_AddVarInternal(name, 8, data); +void tsf_AddVar(const char *name, float *data) { + tsf_AddVarInternal(name, 8, data); } -void tsf_AddVar(const char* name, bool* data) { - tsf_AddVarInternal(name, 6, data); +void tsf_AddVar(const char *name, bool *data) { + tsf_AddVarInternal(name, 6, data); } - // TS->C Functions -ADDR tsf_AddConsoleFuncInternal(const char* pname, const char* cname, const char* fname, signed int cbtype, const char* usage, signed int mina, signed int maxa) { - const char *ste_fname = tsf_BlStringTable__insert(fname, 0); - ADDR ns = tsf_LookupNamespace(cname, pname); - ADDR ent = tsf_BlNamespace__createLocalEntry(ns, ste_fname); - - *(signed int *)tsf_mCacheSequence += 1; - tsf_BlDataChunker__freeBlocks(*(ADDR *)tsf_mCacheAllocator); - - *(const char**)(ent + 24) = usage ; - *(signed int* )(ent + 16) = mina ; - *(signed int* )(ent + 20) = maxa ; - *(signed int* )(ent + 12) = cbtype; - - return ent; +ADDR tsf_AddConsoleFuncInternal(const char *pname, const char *cname, + const char *fname, signed int cbtype, + const char *usage, signed int mina, + signed int maxa) { + const char *ste_fname = tsf_BlStringTable__insert(fname, 0); + ADDR ns = tsf_LookupNamespace(cname, pname); + ADDR ent = tsf_BlNamespace__createLocalEntry(ns, ste_fname); + + *(signed int *)tsf_mCacheSequence += 1; + tsf_BlDataChunker__freeBlocks(*(ADDR *)tsf_mCacheAllocator); + + *(const char **)(ent + 24) = usage; + *(signed int *)(ent + 16) = mina; + *(signed int *)(ent + 20) = maxa; + *(signed int *)(ent + 12) = cbtype; + + return ent; } -void tsf_AddConsoleFunc(const char* pname, const char* cname, const char* fname, tsf_StringCallback sc, const char* usage, signed int mina, signed int maxa) { - ADDR ent = tsf_AddConsoleFuncInternal(pname, cname, fname, 1, usage, mina, maxa); - *(tsf_StringCallback *)(ent + 40) = sc; +void tsf_AddConsoleFunc(const char *pname, const char *cname, const char *fname, + tsf_StringCallback sc, const char *usage, + signed int mina, signed int maxa) { + ADDR ent = + tsf_AddConsoleFuncInternal(pname, cname, fname, 1, usage, mina, maxa); + *(tsf_StringCallback *)(ent + 40) = sc; } -void tsf_AddConsoleFunc(const char* pname, const char* cname, const char* fname, tsf_IntCallback ic, const char* usage, signed int mina, signed int maxa) { - ADDR ent = tsf_AddConsoleFuncInternal(pname, cname, fname, 2, usage, mina, maxa); - *(tsf_IntCallback *)(ent + 40) = ic; +void tsf_AddConsoleFunc(const char *pname, const char *cname, const char *fname, + tsf_IntCallback ic, const char *usage, signed int mina, + signed int maxa) { + ADDR ent = + tsf_AddConsoleFuncInternal(pname, cname, fname, 2, usage, mina, maxa); + *(tsf_IntCallback *)(ent + 40) = ic; } -void tsf_AddConsoleFunc(const char* pname, const char* cname, const char* fname, tsf_FloatCallback fc, const char* usage, signed int mina, signed int maxa) { - ADDR ent = tsf_AddConsoleFuncInternal(pname, cname, fname, 3, usage, mina, maxa); - *(tsf_FloatCallback *)(ent + 40) = fc; +void tsf_AddConsoleFunc(const char *pname, const char *cname, const char *fname, + tsf_FloatCallback fc, const char *usage, + signed int mina, signed int maxa) { + ADDR ent = + tsf_AddConsoleFuncInternal(pname, cname, fname, 3, usage, mina, maxa); + *(tsf_FloatCallback *)(ent + 40) = fc; } -void tsf_AddConsoleFunc(const char* pname, const char* cname, const char* fname, tsf_VoidCallback vc, const char* usage, signed int mina, signed int maxa) { - ADDR ent = tsf_AddConsoleFuncInternal(pname, cname, fname, 4, usage, mina, maxa); - *(tsf_VoidCallback *)(ent + 40) = vc; +void tsf_AddConsoleFunc(const char *pname, const char *cname, const char *fname, + tsf_VoidCallback vc, const char *usage, signed int mina, + signed int maxa) { + ADDR ent = + tsf_AddConsoleFuncInternal(pname, cname, fname, 4, usage, mina, maxa); + *(tsf_VoidCallback *)(ent + 40) = vc; } -void tsf_AddConsoleFunc(const char* pname, const char* cname, const char* fname, tsf_BoolCallback bc, const char* usage, signed int mina, signed int maxa) { - ADDR ent = tsf_AddConsoleFuncInternal(pname, cname, fname, 5, usage, mina, maxa); - *(tsf_BoolCallback *)(ent + 40) = bc; +void tsf_AddConsoleFunc(const char *pname, const char *cname, const char *fname, + tsf_BoolCallback bc, const char *usage, signed int mina, + signed int maxa) { + ADDR ent = + tsf_AddConsoleFuncInternal(pname, cname, fname, 5, usage, mina, maxa); + *(tsf_BoolCallback *)(ent + 40) = bc; } - // Initialization bool tsf_InitInternal() { - BlScanFunctionText(tsf_BlStringTable__insert , "83 EC 0C 80 3D ? ? ? ? ?" ); - BlScanFunctionText(tsf_BlNamespace__find , "55 8B EC 6A FF 68 ? ? ? ? 64 A1 ? ? ? ? 50 83 EC 0C 53 56 57 A1 ? ? ? ? 33 C5 50 8D 45 F4 64 A3 ? ? ? ? 8B DA 8B D1" ); - BlScanFunctionText(tsf_BlNamespace__createLocalEntry, "55 8B EC 6A FF 68 ? ? ? ? 64 A1 ? ? ? ? 50 83 EC 08 53 56 57 A1 ? ? ? ? 33 C5 50 8D 45 F4 64 A3 ? ? ? ? 89 4D F0" ); - BlScanFunctionText(tsf_BlDataChunker__freeBlocks , "55 8B EC 6A FF 68 ? ? ? ? 64 A1 ? ? ? ? 50 51 53 56 57 A1 ? ? ? ? 33 C5 50 8D 45 F4 64 A3 ? ? ? ? 8B D9 8B 33" ); - BlScanFunctionText(tsf_BlCon__evaluate , "55 8B EC 6A FF 68 ? ? ? ? 64 A1 ? ? ? ? 50 56 57 A1 ? ? ? ? 33 C5 50 8D 45 F4 64 A3 ? ? ? ? 8B 75 10" ); - BlScanFunctionText(tsf_BlCon__executef , "81 EC ? ? ? ? A1 ? ? ? ? 33 C4 89 84 24 ? ? ? ? 53 55 56 8B B4 24 ? ? ? ? 33 C9" ); - BlScanFunctionText(tsf_BlCon__executefSimObj , "81 EC ? ? ? ? A1 ? ? ? ? 33 C4 89 84 24 ? ? ? ? 53 56 8B B4 24 ? ? ? ? 33 C9" ); - BlScanFunctionText(tsf_BlCon__getVariable , "53 56 8B F1 57 85 F6 0F 84 ? ? ? ?" ); - BlScanFunctionText(tsf_BlDictionary__addVariable , "8B 44 24 04 56 57 8B F9" ); - BlScanFunctionText(tsf_BlSim__findObject_name , "57 8B F9 8A 17" ); - BlScanFunctionText(tsf_BlStringStack__getArgBuffer , "55 8B EC 83 E4 F8 8B 0D ? ? ? ? A1 ? ? ? ? 56 57 8B 7D 08 8D 14 01 03 D7 3B 15 ? ? ? ? 72 2C 8B 0D" ); - BlScanFunctionText(tsf_BlSimObject__getDataField , "51 53 8B D9 55 56 8B 74 24 14" ); - BlScanFunctionText(tsf_BlSimObject__setDataField , "81 EC ? ? ? ? A1 ? ? ? ? 33 C4 89 84 24 ? ? ? ? 8B 84 24 ? ? ? ? 53 8B D9 89 44 24 04" ); - BlScanFunctionText(tsf_BlCon__getReturnBuffer , "81 F9 ? ? ? ? 76 2B" ); - - ADDR BlScanText (tsf_mCacheSequenceLoc , "FF 05 ? ? ? ? B9 ? ? ? ? 8B F8 E8 ? ? ? ? 8B 44 24 1C 89 47 18 8B 44 24 14" ); - ADDR BlScanText (tsf_mCacheAllocatorLoc , "89 35 ? ? ? ? C7 06 ? ? ? ? A1 ? ? ? ? 68 ? ? ? ? C7 40 ? ? ? ? ? E8 ? ? ? ? 83 C4 04 8B 4D F4 64 89 0D ? ? ? ? 59 5E 8B E5 5D C3"); - ADDR BlScanText (tsf_gIdDictionaryLoc , "89 15 ? ? ? ? E8 ? ? ? ? 8B F0 89 75 F0" ); - ADDR BlScanText (tsf_gEvalState_globalVarsLoc , "B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? 6A 0A 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? E8 ? ? ? ?" ); - - tsf_mCacheSequence = *(ADDR*)(tsf_mCacheSequenceLoc + 2); - tsf_mCacheAllocator = *(ADDR*)(tsf_mCacheAllocatorLoc + 2); - tsf_gIdDictionary = *(ADDR*)(tsf_gIdDictionaryLoc + 2); - tsf_gEvalState_globalVars = *(ADDR*)(tsf_gEvalState_globalVarsLoc + 1); - - return true; + BlScanFunctionText(tsf_BlStringTable__insert, "83 EC 0C 80 3D ? ? ? ? ?"); + BlScanFunctionText( + tsf_BlNamespace__find, + "55 8B EC 6A FF 68 ? ? ? ? 64 A1 ? ? ? ? 50 83 EC 0C 53 56 57 A1 ? ? ? ? " + "33 C5 50 8D 45 F4 64 A3 ? ? ? ? 8B DA 8B D1"); + BlScanFunctionText( + tsf_BlNamespace__createLocalEntry, + "55 8B EC 6A FF 68 ? ? ? ? 64 A1 ? ? ? ? 50 83 EC 08 53 56 57 A1 ? ? ? ? " + "33 C5 50 8D 45 F4 64 A3 ? ? ? ? 89 4D F0"); + BlScanFunctionText(tsf_BlDataChunker__freeBlocks, + "55 8B EC 6A FF 68 ? ? ? ? 64 A1 ? ? ? ? 50 51 53 56 57 " + "A1 ? ? ? ? 33 C5 50 8D 45 F4 64 A3 ? ? ? ? 8B D9 8B 33"); + BlScanFunctionText(tsf_BlCon__evaluate, + "55 8B EC 6A FF 68 ? ? ? ? 64 A1 ? ? ? ? 50 56 57 A1 ? ? " + "? ? 33 C5 50 8D 45 F4 64 A3 ? ? ? ? 8B 75 10"); + BlScanFunctionText(tsf_BlCon__executef, + "81 EC ? ? ? ? A1 ? ? ? ? 33 C4 89 84 24 ? ? ? ? 53 55 56 " + "8B B4 24 ? ? ? ? 33 C9"); + BlScanFunctionText(tsf_BlCon__executefSimObj, + "81 EC ? ? ? ? A1 ? ? ? ? 33 C4 89 84 24 ? ? ? ? 53 56 8B " + "B4 24 ? ? ? ? 33 C9"); + BlScanFunctionText(tsf_BlCon__getVariable, + "53 56 8B F1 57 85 F6 0F 84 ? ? ? ?"); + BlScanFunctionText(tsf_BlDictionary__addVariable, "8B 44 24 04 56 57 8B F9"); + BlScanFunctionText(tsf_BlSim__findObject_name, "57 8B F9 8A 17"); + BlScanFunctionText(tsf_BlStringStack__getArgBuffer, + "55 8B EC 83 E4 F8 8B 0D ? ? ? ? A1 ? ? ? ? 56 57 8B 7D " + "08 8D 14 01 03 D7 3B 15 ? ? ? ? 72 2C 8B 0D"); + BlScanFunctionText(tsf_BlSimObject__getDataField, + "51 53 8B D9 55 56 8B 74 24 14"); + BlScanFunctionText(tsf_BlSimObject__setDataField, + "81 EC ? ? ? ? A1 ? ? ? ? 33 C4 89 84 24 ? ? ? ? 8B 84 24 " + "? ? ? ? 53 8B D9 89 44 24 04"); + BlScanFunctionText(tsf_BlCon__getReturnBuffer, "81 F9 ? ? ? ? 76 2B"); + + ADDR BlScanText(tsf_mCacheSequenceLoc, + "FF 05 ? ? ? ? B9 ? ? ? ? 8B F8 E8 ? ? ? ? 8B 44 24 1C 89 47 " + "18 8B 44 24 14"); + ADDR BlScanText( + tsf_mCacheAllocatorLoc, + "89 35 ? ? ? ? C7 06 ? ? ? ? A1 ? ? ? ? 68 ? ? ? ? C7 40 ? ? ? ? ? E8 ? " + "? ? ? 83 C4 04 8B 4D F4 64 89 0D ? ? ? ? 59 5E 8B E5 5D C3"); + ADDR BlScanText(tsf_gIdDictionaryLoc, + "89 15 ? ? ? ? E8 ? ? ? ? 8B F0 89 75 F0"); + ADDR BlScanText(tsf_gEvalState_globalVarsLoc, + "B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? 6A 0A 68 ? ? ? ? B9 ? ? ? " + "? E8 ? ? ? ? E8 ? ? ? ?"); + + tsf_mCacheSequence = *(ADDR *)(tsf_mCacheSequenceLoc + 2); + tsf_mCacheAllocator = *(ADDR *)(tsf_mCacheAllocatorLoc + 2); + tsf_gIdDictionary = *(ADDR *)(tsf_gIdDictionaryLoc + 2); + tsf_gEvalState_globalVars = *(ADDR *)(tsf_gEvalState_globalVarsLoc + 1); + + return true; } -bool tsf_DeinitInternal() { - return true; -} +bool tsf_DeinitInternal() { return true; } diff --git a/inc/tsfuncs/BlFuncs.hpp b/inc/tsfuncs/BlFuncs.hpp index b14626d..c9c0d32 100644 --- a/inc/tsfuncs/BlFuncs.hpp +++ b/inc/tsfuncs/BlFuncs.hpp @@ -5,16 +5,16 @@ #ifndef _H_BLFUNCS #define _H_BLFUNCS -// Require BlHooks to be included before this header +// Ensure BlHooks is available (include it if not already included) #ifndef _H_BLHOOKS - #error "BlFuncs.hpp: You must include BlHooks.hpp first" -#else +#include "BlHooks.hpp" +#endif -typedef const char * (*tsf_StringCallback)(ADDR, signed int, const char *[]); -typedef signed int (*tsf_IntCallback )(ADDR, signed int, const char *[]); -typedef float (*tsf_FloatCallback )(ADDR, signed int, const char *[]); -typedef void (*tsf_VoidCallback )(ADDR, signed int, const char *[]); -typedef bool (*tsf_BoolCallback )(ADDR, signed int, const char *[]); +typedef const char *(*tsf_StringCallback)(ADDR, signed int, const char *[]); +typedef signed int (*tsf_IntCallback)(ADDR, signed int, const char *[]); +typedef float (*tsf_FloatCallback)(ADDR, signed int, const char *[]); +typedef void (*tsf_VoidCallback)(ADDR, signed int, const char *[]); +typedef bool (*tsf_BoolCallback)(ADDR, signed int, const char *[]); /* These functions are used for tsf_BlCon__executefSimObj. They refer to a special buffer for the argument stack. @@ -42,12 +42,21 @@ void tsf_AddVar(const char *, signed int *); void tsf_AddVar(const char *, float *); void tsf_AddVar(const char *, bool *); -ADDR tsf_AddConsoleFuncInternal(const char *, const char *, const char *, signed int, const char *, signed int, signed int); -void tsf_AddConsoleFunc(const char *, const char *, const char *, tsf_StringCallback, const char *, signed int, signed int); -void tsf_AddConsoleFunc(const char *, const char *, const char *, tsf_IntCallback, const char *, signed int, signed int); -void tsf_AddConsoleFunc(const char *, const char *, const char *, tsf_FloatCallback, const char *, signed int, signed int); -void tsf_AddConsoleFunc(const char *, const char *, const char *, tsf_VoidCallback, const char *, signed int, signed int); -void tsf_AddConsoleFunc(const char *, const char *, const char *, tsf_BoolCallback, const char *, signed int, signed int); +ADDR tsf_AddConsoleFuncInternal(const char *, const char *, const char *, + signed int, const char *, signed int, + signed int); +void tsf_AddConsoleFunc(const char *, const char *, const char *, + tsf_StringCallback, const char *, signed int, + signed int); +void tsf_AddConsoleFunc(const char *, const char *, const char *, + tsf_IntCallback, const char *, signed int, signed int); +void tsf_AddConsoleFunc(const char *, const char *, const char *, + tsf_FloatCallback, const char *, signed int, + signed int); +void tsf_AddConsoleFunc(const char *, const char *, const char *, + tsf_VoidCallback, const char *, signed int, signed int); +void tsf_AddConsoleFunc(const char *, const char *, const char *, + tsf_BoolCallback, const char *, signed int, signed int); bool tsf_InitInternal(); @@ -56,21 +65,32 @@ extern ADDR tsf_mCacheAllocator; extern ADDR tsf_gIdDictionary; extern ADDR tsf_gEvalState_globalVars; -BlFunctionDefExtern(const char *, __stdcall, tsf_BlStringTable__insert, const char *, bool); -BlFunctionDefExtern(ADDR, __fastcall, tsf_BlNamespace__find, const char *, const char *); -BlFunctionDefExtern(ADDR, __thiscall, tsf_BlNamespace__createLocalEntry, ADDR, const char *); +BlFunctionDefExtern(const char *, __stdcall, tsf_BlStringTable__insert, + const char *, bool); +BlFunctionDefExtern(ADDR, __fastcall, tsf_BlNamespace__find, const char *, + const char *); +BlFunctionDefExtern(ADDR, __thiscall, tsf_BlNamespace__createLocalEntry, ADDR, + const char *); BlFunctionDefExtern(void, __thiscall, tsf_BlDataChunker__freeBlocks, ADDR); -BlFunctionDefExtern(const char *, , tsf_BlCon__evaluate, ADDR, signed int, const char **); +BlFunctionDefExtern(const char *, , tsf_BlCon__evaluate, ADDR, signed int, + const char **); BlFunctionDefExtern(const char *, , tsf_BlCon__executef, signed int, ...); -BlFunctionDefExtern(const char *, , tsf_BlCon__executefSimObj, ADDR *, signed int, ...); -BlFunctionDefExtern(const char *, __thiscall, tsf_BlCon__getVariable, const char *); -BlFunctionDefExtern(void, __thiscall, tsf_BlDictionary__addVariable, ADDR *, const char *, signed int, void *); -BlFunctionDefExtern(ADDR *, __thiscall, tsf_BlSim__findObject_name, const char *); -BlFunctionDefExtern(char *, __stdcall, tsf_BlStringStack__getArgBuffer, unsigned int); -BlFunctionDefExtern(const char *, __thiscall, tsf_BlSimObject__getDataField, ADDR, const char *, const char *); -BlFunctionDefExtern(void, __thiscall, tsf_BlSimObject__setDataField, ADDR, const char *, const char *, const char *); -BlFunctionDefExtern(char *, __fastcall, tsf_BlCon__getReturnBuffer, unsigned int); - +BlFunctionDefExtern(const char *, , tsf_BlCon__executefSimObj, ADDR *, + signed int, ...); +BlFunctionDefExtern(const char *, __thiscall, tsf_BlCon__getVariable, + const char *); +BlFunctionDefExtern(void, __thiscall, tsf_BlDictionary__addVariable, ADDR *, + const char *, signed int, void *); +BlFunctionDefExtern(ADDR *, __thiscall, tsf_BlSim__findObject_name, + const char *); +BlFunctionDefExtern(char *, __stdcall, tsf_BlStringStack__getArgBuffer, + unsigned int); +BlFunctionDefExtern(const char *, __thiscall, tsf_BlSimObject__getDataField, + ADDR, const char *, const char *); +BlFunctionDefExtern(void, __thiscall, tsf_BlSimObject__setDataField, ADDR, + const char *, const char *, const char *); +BlFunctionDefExtern(char *, __fastcall, tsf_BlCon__getReturnBuffer, + unsigned int); // Function short names @@ -80,7 +100,7 @@ BlFunctionDefExtern(char *, __fastcall, tsf_BlCon__getReturnBuffer, unsigned int #define BlIntArg tsf_GetIntArg #define BlFloatArg tsf_GetFloatArg #define BlThisArg tsf_GetThisArg -#define BlStringArg(x) tsf_GetStringArg((char*)x) +#define BlStringArg(x) tsf_GetStringArg((char *)x) #define BlReturnBuffer tsf_BlCon__getReturnBuffer #define BlObject tsf_FindObject @@ -94,16 +114,23 @@ BlFunctionDefExtern(char *, __fastcall, tsf_BlCon__getReturnBuffer, unsigned int #define BlAddFunction tsf_AddConsoleFunc -#define __22ND_ARGUMENT(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, ...) a22 -#define __NUM_LIST(...) __22ND_ARGUMENT(dummy, ##__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) -#define BlCall(...) \ - tsf_BlCon__executef(__NUM_LIST(__VA_ARGS__), __VA_ARGS__) -#define BlCallObj(obj, ...) \ - tsf_BlCon__executefSimObj((ADDR*)obj, __NUM_LIST(__VA_ARGS__), __VA_ARGS__) - -#define BlFuncsInit() if(!tsf_InitInternal()) { return false; } -#define BlFuncsDeinit() if(!tsf_DeinitInternal()) { return false; } +#define __22ND_ARGUMENT(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, \ + a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, ...) \ + a22 +#define __NUM_LIST(...) \ + __22ND_ARGUMENT(dummy, ##__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, \ + 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#define BlCall(...) tsf_BlCon__executef(__NUM_LIST(__VA_ARGS__), __VA_ARGS__) +#define BlCallObj(obj, ...) \ + tsf_BlCon__executefSimObj((ADDR *)obj, __NUM_LIST(__VA_ARGS__), __VA_ARGS__) +#define BlFuncsInit() \ + if (!tsf_InitInternal()) { \ + return false; \ + } +#define BlFuncsDeinit() \ + if (!tsf_DeinitInternal()) { \ + return false; \ + } #endif -#endif diff --git a/inc/tsfuncs/BlHooks.cpp b/inc/tsfuncs/BlHooks.cpp index e46a852..b29d922 100644 --- a/inc/tsfuncs/BlHooks.cpp +++ b/inc/tsfuncs/BlHooks.cpp @@ -2,206 +2,208 @@ ////////////////////////////////////////////////// // RedoBlHooks Version 3.0 - // Includes +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#include +#endif #include #include -//#include +// #include #include "BlHooks.hpp" - // Scanned structures BlFunctionDefIntern(tsh_BlPrintf); - // Sig Scanning ADDR ImageBase; ADDR ImageSize; -void tsh_i_InitScanner(){ - HMODULE module = GetModuleHandle(NULL); - if(module) { - MODULEINFO info; - GetModuleInformation(GetCurrentProcess(), module, &info, sizeof(MODULEINFO)); - ImageBase = (ADDR)info.lpBaseOfDll; - ImageSize = info.SizeOfImage; - } +void tsh_i_InitScanner() { + HMODULE module = GetModuleHandle(NULL); + if (module) { + MODULEINFO info; + GetModuleInformation(GetCurrentProcess(), module, &info, + sizeof(MODULEINFO)); + ImageBase = (ADDR)info.lpBaseOfDll; + ImageSize = info.SizeOfImage; + } } -bool tsh_i_CompareData(BYTE *data, BYTE *pattern, char* mask){ - for (; *mask; ++data, ++pattern, ++mask){ - if (*mask=='x' && *data!=*pattern) - return false; - } - return (*mask)==0; +bool tsh_i_CompareData(BYTE *data, BYTE *pattern, char *mask) { + for (; *mask; ++data, ++pattern, ++mask) { + if (*mask == 'x' && *data != *pattern) + return false; + } + return (*mask) == 0; } -ADDR tsh_i_FindPattern(ADDR imageBase, ADDR imageSize, BYTE *pattern, char *mask){ - for (ADDR i=imageBase; i < imageBase+imageSize; i++){ - if(tsh_i_CompareData((PBYTE)i, pattern, mask)){ - return i; - } - } - return 0; +ADDR tsh_i_FindPattern(ADDR imageBase, ADDR imageSize, BYTE *pattern, + char *mask) { + for (ADDR i = imageBase; i < imageBase + imageSize; i++) { + if (tsh_i_CompareData((PBYTE)i, pattern, mask)) { + return i; + } + } + return 0; } // Convert a text-style pattern into code-style -void tsh_i_PatternTextToCode(char* text, char** opatt, char** omask) { - unsigned int len = strlen(text); - char* patt = (char*)malloc(len); - char* mask = (char*)malloc(len); - - int outidx = 0; - int val = 0; - bool uk = false; - for(unsigned int i=0; i='0' && c<='9'){ - val = (val<<4) + (c-'0'); - }else if(c>='A' && c<='F'){ - val = (val<<4) + (c-'A'+10); - }else if(c>='a' && c<='f'){ - val = (val<<4) + (c-'a'+10); - }else if(c==' '){ - patt[outidx] = uk ? 0 : val; - mask[outidx] = uk ? '?' : 'x'; - val = 0; - uk = false; - outidx++; - } - } - - patt[outidx] = uk ? 0 : val; - mask[outidx] = uk ? '?' : 'x'; - outidx++; - patt[outidx] = 0; - mask[outidx] = 0; - - *opatt = patt; - *omask = mask; -} +void tsh_i_PatternTextToCode(char *text, char **opatt, char **omask) { + unsigned int len = strlen(text); + char *patt = (char *)malloc(len); + char *mask = (char *)malloc(len); + int outidx = 0; + int val = 0; + bool uk = false; + for (unsigned int i = 0; i < len; i++) { + char c = text[i]; + if (c == '?') { + uk = true; + } else if (c >= '0' && c <= '9') { + val = (val << 4) + (c - '0'); + } else if (c >= 'A' && c <= 'F') { + val = (val << 4) + (c - 'A' + 10); + } else if (c >= 'a' && c <= 'f') { + val = (val << 4) + (c - 'a' + 10); + } else if (c == ' ') { + patt[outidx] = uk ? 0 : val; + mask[outidx] = uk ? '?' : 'x'; + val = 0; + uk = false; + outidx++; + } + } + + patt[outidx] = uk ? 0 : val; + mask[outidx] = uk ? '?' : 'x'; + outidx++; + patt[outidx] = 0; + mask[outidx] = 0; + + *opatt = patt; + *omask = mask; +} // Public functions for sig scanning // Scan using code-style pattern -ADDR tsh_ScanCode(char* pattern, char* mask) { - return tsh_i_FindPattern(ImageBase, ImageSize-strlen(mask), (BYTE*)pattern, mask); +ADDR tsh_ScanCode(char *pattern, char *mask) { + return tsh_i_FindPattern(ImageBase, ImageSize - strlen(mask), (BYTE *)pattern, + mask); } // Scan using a text-style pattern -ADDR tsh_ScanText(char* text) { - char* patt; - char* mask; - tsh_i_PatternTextToCode(text, &patt, &mask); - - ADDR res = tsh_ScanCode(patt, mask); - - free(patt); - free(mask); - - return res; -} +ADDR tsh_ScanText(char *text) { + char *patt; + char *mask; + tsh_i_PatternTextToCode(text, &patt, &mask); + ADDR res = tsh_ScanCode(patt, mask); + + free(patt); + free(mask); + + return res; +} // Call Patching and Hooking // Remove protection from address -//std::map> tsh_DeprotectedAddresses; +// std::map> tsh_DeprotectedAddresses; void tsh_DeprotectAddress(ADDR length, ADDR location) { - DWORD oldProtection; - VirtualProtect((void*)location, length, PAGE_EXECUTE_READWRITE, &oldProtection); - //tsh_DeprotectedAddresses[location] = {length, oldProtection}; + DWORD oldProtection; + VirtualProtect((void *)location, length, PAGE_EXECUTE_READWRITE, + &oldProtection); + // tsh_DeprotectedAddresses[location] = {length, oldProtection}; } // Patch a string of bytes by deprotecting and then overwriting -void tsh_PatchBytes(ADDR length, ADDR location, BYTE* repl) { - tsh_DeprotectAddress(length, location); - memcpy((void*)location, (void*)repl, (size_t)length); +void tsh_PatchBytes(ADDR length, ADDR location, BYTE *repl) { + tsh_DeprotectAddress(length, location); + memcpy((void *)location, (void *)repl, (size_t)length); } void tsh_PatchByte(ADDR location, BYTE value) { - tsh_PatchBytes(location, 1, &value); + tsh_PatchBytes(location, 1, &value); } void tsh_ReplaceInt(ADDR addr, int rval) { - tsh_PatchBytes(4, addr, (BYTE*)(&rval)); + tsh_PatchBytes(4, addr, (BYTE *)(&rval)); } -int tsh_i_CallOffset(ADDR instr, ADDR func) { - return func - (instr+4); -} +int tsh_i_CallOffset(ADDR instr, ADDR func) { return func - (instr + 4); } void tsh_i_ReplaceCall(ADDR instr, ADDR target) { - tsh_ReplaceInt(instr, tsh_i_CallOffset(instr, target)); + tsh_ReplaceInt(instr, tsh_i_CallOffset(instr, target)); } void tsh_i_PatchCopy(ADDR dest, ADDR src, unsigned int len) { - for(unsigned int i=0; i bool: success -#define BlHooksInit() if(!tsh_InitInternal()) { BlPrintf("BlHooksInit failed"); return false; } +#define BlHooksInit() \ + if (!tsh_InitInternal()) { \ + BlPrintf("BlHooksInit failed"); \ + return false; \ + } // BlHooksDeinit() -> bool: success -#define BlHooksDeinit() if(!tsh_DeinitInternal()) { BlPrintf("BlHooksDeinit failed"); return false; } - +#define BlHooksDeinit() \ + if (!tsh_DeinitInternal()) { \ + BlPrintf("BlHooksDeinit failed"); \ + return false; \ + } // Scanned structures -BlFunctionDefExtern(void, , tsh_BlPrintf, const char*, ...); - +BlFunctionDefExtern(void, , tsh_BlPrintf, const char *, ...); #endif diff --git a/src/bllua4.cpp b/src/bllua4.cpp index c8955fe..d5acac0 100644 --- a/src/bllua4.cpp +++ b/src/bllua4.cpp @@ -2,16 +2,19 @@ // Includes +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif #include #include -#include "lua.hpp" -#include "BlHooks.cpp" #include "BlFuncs.cpp" +#include "BlHooks.cpp" +#include "lua.hpp" #include "luainterp.cpp" #include "lualibts.cpp" -lua_State* gL; +lua_State *gL; #include "tsliblua.cpp" // Global variables @@ -19,87 +22,92 @@ lua_State* gL; // Setup // Hack to encode the contents of text files as static strings -#define INCLUDE_BIN(varname, filename) \ - asm("_" #varname ": .incbin \"" filename "\""); \ - asm(".byte 0"); \ - extern char varname[]; +#define INCLUDE_BIN(varname, filename) \ + asm("_" #varname ": .incbin \"" filename "\""); \ + asm(".byte 0"); \ + extern char varname[]; INCLUDE_BIN(bll_fileLuaEnvSafe, "lua-env-safe.lua"); -INCLUDE_BIN(bll_fileLuaEnv , "lua-env.lua"); -INCLUDE_BIN(bll_fileTsEnv , "ts-env.cs" ); -INCLUDE_BIN(bll_fileLuaStd , "util/std.lua"); -INCLUDE_BIN(bll_fileLuaVector , "util/vector.lua"); -INCLUDE_BIN(bll_fileLuaLibts , "util/libts-lua.lua"); -INCLUDE_BIN(bll_fileTsLibts , "util/libts-ts.cs"); -INCLUDE_BIN(bll_fileLuaLibbl , "util/libbl.lua" ); -INCLUDE_BIN(bll_fileLuaLibblTypes , "util/libbl-types.lua"); +INCLUDE_BIN(bll_fileLuaEnv, "lua-env.lua"); +INCLUDE_BIN(bll_fileTsEnv, "ts-env.cs"); +INCLUDE_BIN(bll_fileLuaStd, "util/std.lua"); +INCLUDE_BIN(bll_fileLuaVector, "util/vector.lua"); +INCLUDE_BIN(bll_fileLuaLibts, "util/libts-lua.lua"); +INCLUDE_BIN(bll_fileTsLibts, "util/libts-ts.cs"); +INCLUDE_BIN(bll_fileLuaLibbl, "util/libbl.lua"); +INCLUDE_BIN(bll_fileLuaLibblTypes, "util/libbl-types.lua"); INCLUDE_BIN(bll_fileTsLibblSupport, "util/libbl-support.cs"); -INCLUDE_BIN(bll_fileLoadaddons , "util/loadaddons.cs"); +INCLUDE_BIN(bll_fileLoadaddons, "util/loadaddons.cs"); -#define BLL_LOAD_LUA(lstate, vname) \ - if(!bll_LuaEval(lstate, vname)) { \ - BlPrintf(" Error executing " #vname); \ - return false; \ - } +#define BLL_LOAD_LUA(lstate, vname) \ + if (!bll_LuaEval(lstate, vname)) { \ + BlPrintf(" Error executing " #vname); \ + return false; \ + } bool init() { - BlHooksInit(); - BlPrintf("BlockLua: Loading"); - - BlFuncsInit(); - - // Initialize Lua environment - gL = lua_open(); - luaL_openlibs(gL); - - // Expose TS API to Lua - llibbl_init(gL); - - // Set up Lua environment - BLL_LOAD_LUA(gL, bll_fileLuaEnv); - #ifndef BLLUA_UNSAFE - BLL_LOAD_LUA(gL, bll_fileLuaEnvSafe); - #endif - - // Expose Lua API to TS - BlAddFunction(NULL, NULL, "_bllua_luacall", bll_ts_luacall, "LuaCall(name, ...) - Call Lua function and return result", 2, 20); - BlEval(bll_fileTsEnv); - - // Load utilities - BLL_LOAD_LUA(gL, bll_fileLuaStd); - BLL_LOAD_LUA(gL, bll_fileLuaVector); - BLL_LOAD_LUA(gL, bll_fileLuaLibts); - BlEval(bll_fileTsLibts); - BLL_LOAD_LUA(gL, bll_fileLuaLibbl); - BLL_LOAD_LUA(gL, bll_fileLuaLibblTypes); - BlEval(bll_fileTsLibblSupport); - BlEval(bll_fileLoadaddons); - - BlEval("$_bllua_active = 1;"); - - BlPrintf(" BlockLua: Done Loading"); - return true; + BlHooksInit(); + BlPrintf("BlockLua: Loading"); + + BlFuncsInit(); + + // Initialize Lua environment + gL = lua_open(); + luaL_openlibs(gL); + + // Expose TS API to Lua + llibbl_init(gL); + + // Set up Lua environment + BLL_LOAD_LUA(gL, bll_fileLuaEnv); +#ifndef BLLUA_UNSAFE + BLL_LOAD_LUA(gL, bll_fileLuaEnvSafe); +#endif + + // Expose Lua API to TS + BlAddFunction(NULL, NULL, "_bllua_luacall", bll_ts_luacall, + "LuaCall(name, ...) - Call Lua function and return result", 2, + 20); + BlEval(bll_fileTsEnv); + + // Load utilities + BLL_LOAD_LUA(gL, bll_fileLuaStd); + BLL_LOAD_LUA(gL, bll_fileLuaVector); + BLL_LOAD_LUA(gL, bll_fileLuaLibts); + BlEval(bll_fileTsLibts); + BLL_LOAD_LUA(gL, bll_fileLuaLibbl); + BLL_LOAD_LUA(gL, bll_fileLuaLibblTypes); + BlEval(bll_fileTsLibblSupport); + BlEval(bll_fileLoadaddons); + + BlEval("$_bllua_active = 1;"); + + BlPrintf(" BlockLua: Done Loading"); + return true; } bool deinit() { - BlPrintf("BlockLua: Unloading"); - - BlEval("deactivatePackage(_bllua_main);"); - BlEval("$_bllua_active = 0;"); - bll_LuaEval(gL, "for _,f in pairs(_bllua_on_unload) do f() end"); - - lua_close(gL); - - BlHooksDeinit(); - BlFuncsDeinit(); - - BlPrintf(" BlockLua: Done Unloading"); - return true; + BlPrintf("BlockLua: Unloading"); + + BlEval("deactivatePackage(_bllua_main);"); + BlEval("$_bllua_active = 0;"); + bll_LuaEval(gL, "for _,f in pairs(_bllua_on_unload) do f() end"); + + lua_close(gL); + + BlHooksDeinit(); + BlFuncsDeinit(); + + BlPrintf(" BlockLua: Done Unloading"); + return true; } -bool __stdcall DllMain(HINSTANCE hinstance, DWORD reason, void* reserved) { - switch(reason) { - case DLL_PROCESS_ATTACH: return init(); - case DLL_PROCESS_DETACH: return deinit(); - default : return true; - } +bool __stdcall DllMain(HINSTANCE hinstance, DWORD reason, void *reserved) { + switch (reason) { + case DLL_PROCESS_ATTACH: + return init(); + case DLL_PROCESS_DETACH: + return deinit(); + default: + return true; + } } diff --git a/src/luainterp.cpp b/src/luainterp.cpp index 86f22a6..a202f6a 100644 --- a/src/luainterp.cpp +++ b/src/luainterp.cpp @@ -1,68 +1,78 @@ // Handle errors with a Lua function, defined in lua-env.lua +#include "luainterp.hpp" +#include "BlHooks.hpp" +#include "lauxlib.h" +#include "lua.h" +#include int bll_error_handler(lua_State *L) { - lua_getfield(L, LUA_GLOBALSINDEX, "_bllua_on_error"); - if (!lua_isfunction(L, -1)) { - BlPrintf(" Lua error handler: _bllua_on_error not defined."); - lua_pop(L, 1); - return 1; - } - - // move function to before arg - int hpos = lua_gettop(L) - 1; - lua_insert(L, hpos); - - lua_pcall(L, 1, 1, 0); - return 1; + lua_getfield(L, LUA_GLOBALSINDEX, "_bllua_on_error"); + if (!lua_isfunction(L, -1)) { + BlPrintf(" Lua error handler: _bllua_on_error not defined."); + lua_pop(L, 1); + return 1; + } + + // move function to before arg + int hpos = lua_gettop(L) - 1; + lua_insert(L, hpos); + + lua_pcall(L, 1, 1, 0); + return 1; } -int bll_pcall(lua_State* L, int nargs, int nret) { - // calculate stack position for message handler - int hpos = lua_gettop(L) - nargs; - // push custom error message handler - lua_pushcfunction(L, bll_error_handler); - // move it before function and arguments - lua_insert(L, hpos); - // call lua_pcall function with custom handler - int ret = lua_pcall(L, nargs, nret, hpos); - // remove custom error message handler from stack - lua_remove(L, hpos); - return ret; +int bll_pcall(lua_State *L, int nargs, int nret) { + // calculate stack position for message handler + int hpos = lua_gettop(L) - nargs; + // push custom error message handler + lua_pushcfunction(L, bll_error_handler); + // move it before function and arguments + lua_insert(L, hpos); + // call lua_pcall function with custom handler + int ret = lua_pcall(L, nargs, nret, hpos); + // remove custom error message handler from stack + lua_remove(L, hpos); + return ret; } // Display the last Lua error in the BL console -void bll_printError(lua_State* L, const char* operation, const char* item) { - //error_handler(L); - BlPrintf("\x03Lua error: %s", lua_tostring(L, -1)); - BlPrintf("\x03 (%s: %s)", operation, item); - lua_pop(L, 1); +void bll_printError(lua_State *L, const char *operation, const char *item) { + // error_handler(L); + BlPrintf("\x03Lua error: %s", lua_tostring(L, -1)); + BlPrintf("\x03 (%s: %s)", operation, item); + lua_pop(L, 1); } // Eval a string of Lua code -bool bll_LuaEval(lua_State* L, const char* str) { - if(luaL_loadbuffer(L, str, strlen(str), "input") || bll_pcall(L, 0, 1)) { - bll_printError(L, "eval", str); - return false; - } - return true; +bool bll_LuaEval(lua_State *L, const char *str) { + if (luaL_loadbuffer(L, str, strlen(str), "input") || bll_pcall(L, 0, 1)) { + bll_printError(L, "eval", str); + return false; + } + return true; } // Convert a Lua stack entry into a string for providing to TS // Use static buffer to avoid excessive malloc -#define BLL_ARG_COUNT 20 -#define BLL_ARG_MAX 8192 char bll_arg_buffer[BLL_ARG_COUNT][BLL_ARG_MAX]; -bool bll_toarg(lua_State* L, char* buf, int i, bool err) { - if(lua_isstring(L, i)) { - const char* str = lua_tostring(L, i); - if(strlen(str) >= BLL_ARG_MAX) { - if(err) luaL_error(L, "argument to TS is too long - max length is 8192"); - return true; - } else { - strcpy(buf, str); - return false; - } - } else { - if(err) luaL_error(L, "argument to TS must be a string"); - return true; - } +bool bll_toarg(lua_State *L, char *buf, int i, bool err) { + if (lua_isstring(L, i)) { + const char *str = lua_tostring(L, i); + if (strlen(str) >= BLL_ARG_MAX) { + if (err) + luaL_error(L, "argument to TS is too long - max length is 8192"); + return true; + } else { +#ifdef _MSC_VER + strncpy_s(buf, BLL_ARG_MAX, str, _TRUNCATE); +#else + strncpy(buf, str, BLL_ARG_MAX - 1); + buf[BLL_ARG_MAX - 1] = '\0'; +#endif + return false; + } + } else { + if (err) + luaL_error(L, "argument to TS must be a string"); + return true; + } } diff --git a/src/luainterp.hpp b/src/luainterp.hpp new file mode 100644 index 0000000..e672ca5 --- /dev/null +++ b/src/luainterp.hpp @@ -0,0 +1,16 @@ +// Shared declarations for Lua <-> TS argument handling +#ifndef _H_LUAINTERP_SHARED +#define _H_LUAINTERP_SHARED + +#include "lua.h" +#define BLL_ARG_COUNT 20 +#define BLL_ARG_MAX 8192 + +extern char bll_arg_buffer[BLL_ARG_COUNT][BLL_ARG_MAX]; +bool bll_toarg(lua_State *L, char *buf, int i, bool err); +int bll_pcall(lua_State *L, int nargs, int nret); +void bll_printError(lua_State *L, const char *operation, const char *item); + +extern lua_State *gL; + +#endif diff --git a/src/lualibts.cpp b/src/lualibts.cpp index ee90a47..7283c70 100644 --- a/src/lualibts.cpp +++ b/src/lualibts.cpp @@ -1,159 +1,303 @@ -//run ../compile +// run ../compile // TS -> Lua API // Call a TS function from Lua, push the result to Lua stack -int bll_TsCall(lua_State* L, const char* oname, const char* fname, int argc, int ofs) { - ADDR obj = (ADDR)NULL; - if(oname) { - obj = BlObject(oname); - if(!obj) { return luaL_error(L, "Lua->TS call: Object not found"); } - } - - if(argc > BLL_ARG_COUNT) { return luaL_error(L, "Lua->TS call: Too many arguments (Max is 19)"); } - - char* argv[BLL_ARG_COUNT]; - for(int i=0; iTS object call: Too many arguments (Max is 19)"); - } - } else { - switch(argc) { - case 0: res = BlCall(fname); break; - case 1: res = BlCall(fname, argv[0]); break; - case 2: res = BlCall(fname, argv[0], argv[1]); break; - case 3: res = BlCall(fname, argv[0], argv[1], argv[2]); break; - case 4: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3]); break; - case 5: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4]); break; - case 6: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); break; - case 7: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]); break; - case 8: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7]); break; - case 9: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8]); break; - case 10: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9]); break; - case 11: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10]); break; - case 12: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11]); break; - case 13: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12]); break; - case 14: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13]); break; - case 15: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14]); break; - case 16: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15]); break; - case 17: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16]); break; - case 18: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17]); break; - case 19: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], argv[18]); break; - default: res = ""; luaL_error(L, "Lua->TS call: Too many arguments (Max is 19)"); - } - } - - lua_pushstring(L, res); - - return 1; +#include "BlFuncs.hpp" +#include "BlHooks.hpp" +#include "lauxlib.h" +#include "lua.h" +#include "luainterp.hpp" +int bll_TsCall(lua_State *L, const char *oname, const char *fname, int argc, + int ofs) { + ADDR obj = (ADDR)NULL; + if (oname) { + obj = BlObject(oname); + if (!obj) { + return luaL_error(L, "Lua->TS call: Object not found"); + } + } + + if (argc > BLL_ARG_COUNT) { + return luaL_error(L, "Lua->TS call: Too many arguments (Max is 19)"); + } + + char *argv[BLL_ARG_COUNT]; + for (int i = 0; i < argc; i++) { + char *argbuf = bll_arg_buffer[i]; + argv[i] = argbuf; + bll_toarg(L, argbuf, i + ofs + 1, true); + } + + // /:^| / + const char *res; + if (obj) { + switch (argc) { + case 0: + res = BlCallObj(obj, fname); + break; // no idea why this happens sometimes, it shouldnt be possible + case 1: + res = BlCallObj(obj, fname); + break; + case 2: + res = BlCallObj(obj, fname, argv[0]); + break; + case 3: + res = BlCallObj(obj, fname, argv[0], argv[1]); + break; + case 4: + res = BlCallObj(obj, fname, argv[0], argv[1], argv[2]); + break; + case 5: + res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3]); + break; + case 6: + res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4]); + break; + case 7: + res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], + argv[5]); + break; + case 8: + res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], + argv[5], argv[6]); + break; + case 9: + res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], + argv[5], argv[6], argv[7]); + break; + case 10: + res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], + argv[5], argv[6], argv[7], argv[8]); + break; + case 11: + res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], + argv[5], argv[6], argv[7], argv[8], argv[9]); + break; + case 12: + res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], + argv[5], argv[6], argv[7], argv[8], argv[9], argv[10]); + break; + case 13: + res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], + argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], + argv[11]); + break; + case 14: + res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], + argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], + argv[11], argv[12]); + break; + case 15: + res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], + argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], + argv[11], argv[12], argv[13]); + break; + case 16: + res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], + argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], + argv[11], argv[12], argv[13], argv[14]); + break; + case 17: + res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], + argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], + argv[11], argv[12], argv[13], argv[14], argv[15]); + break; + case 18: + res = + BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], + argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], + argv[11], argv[12], argv[13], argv[14], argv[15], argv[16]); + break; + case 19: + res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], + argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], + argv[11], argv[12], argv[13], argv[14], argv[15], + argv[16], argv[17]); + break; + case 20: + res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], + argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], + argv[11], argv[12], argv[13], argv[14], argv[15], + argv[16], argv[17], argv[18]); + break; + default: + res = ""; + luaL_error(L, "Lua->TS object call: Too many arguments (Max is 19)"); + } + } else { + switch (argc) { + case 0: + res = BlCall(fname); + break; + case 1: + res = BlCall(fname, argv[0]); + break; + case 2: + res = BlCall(fname, argv[0], argv[1]); + break; + case 3: + res = BlCall(fname, argv[0], argv[1], argv[2]); + break; + case 4: + res = BlCall(fname, argv[0], argv[1], argv[2], argv[3]); + break; + case 5: + res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4]); + break; + case 6: + res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); + break; + case 7: + res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], + argv[6]); + break; + case 8: + res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], + argv[6], argv[7]); + break; + case 9: + res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], + argv[6], argv[7], argv[8]); + break; + case 10: + res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], + argv[6], argv[7], argv[8], argv[9]); + break; + case 11: + res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], + argv[6], argv[7], argv[8], argv[9], argv[10]); + break; + case 12: + res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], + argv[6], argv[7], argv[8], argv[9], argv[10], argv[11]); + break; + case 13: + res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], + argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], + argv[12]); + break; + case 14: + res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], + argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], + argv[12], argv[13]); + break; + case 15: + res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], + argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], + argv[12], argv[13], argv[14]); + break; + case 16: + res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], + argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], + argv[12], argv[13], argv[14], argv[15]); + break; + case 17: + res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], + argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], + argv[12], argv[13], argv[14], argv[15], argv[16]); + break; + case 18: + res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], + argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], + argv[12], argv[13], argv[14], argv[15], argv[16], argv[17]); + break; + case 19: + res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], + argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], + argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], + argv[18]); + break; + default: + res = ""; + luaL_error(L, "Lua->TS call: Too many arguments (Max is 19)"); + } + } + + lua_pushstring(L, res); + + return 1; } // Lua lib function: ts.call -int bll_lua_tscall(lua_State* L) { - int argc = lua_gettop(L)-1; // number of arguments after function name - if(argc < 0) return luaL_error(L, "_bllua_ts.call: Must provide a function name"); - - const char* fname = luaL_checkstring(L, 1); - - return bll_TsCall(L, NULL, fname, argc, 1); +int bll_lua_tscall(lua_State *L) { + int argc = lua_gettop(L) - 1; // number of arguments after function name + if (argc < 0) + return luaL_error(L, "_bllua_ts.call: Must provide a function name"); + + const char *fname = luaL_checkstring(L, 1); + + return bll_TsCall(L, NULL, fname, argc, 1); } // Lua lib function: ts.callobj -int bll_lua_tscallobj(lua_State* L) { - int argc = lua_gettop(L)-2; // number of arguments after function name and object? - if(argc < 0) return luaL_error(L, "_bllua_ts.callobj: Must provide an object and function name"); - - const char* oname = luaL_checkstring(L, 1); - const char* fname = luaL_checkstring(L, 2); - - return bll_TsCall(L, oname, fname, argc, 2); +int bll_lua_tscallobj(lua_State *L) { + int argc = + lua_gettop(L) - 2; // number of arguments after function name and object? + if (argc < 0) + return luaL_error( + L, "_bllua_ts.callobj: Must provide an object and function name"); + + const char *oname = luaL_checkstring(L, 1); + const char *fname = luaL_checkstring(L, 2); + + return bll_TsCall(L, oname, fname, argc, 2); } // Lua lib function: ts.getvar -int bll_lua_tsgetvar(lua_State* L) { - const char* vname = luaL_checkstring(L, 1); - - const char* var = BlGetVar(vname); - lua_pushstring(L, var); - return 1; +int bll_lua_tsgetvar(lua_State *L) { + const char *vname = luaL_checkstring(L, 1); + + const char *var = BlGetVar(vname); + lua_pushstring(L, var); + return 1; } // Lua lib function: ts.getfield -int bll_lua_tsgetfield(lua_State* L) { - const char* oname = luaL_checkstring(L, 1); - const char* vname = luaL_checkstring(L, 2); - ADDR obj = BlObject(oname); - if(!obj) { return luaL_error(L, "_bllua_ts.getfield: Object not found"); } - - const char* val = BlGetField(obj, vname, NULL); - lua_pushstring(L, val); - return 1; +int bll_lua_tsgetfield(lua_State *L) { + const char *oname = luaL_checkstring(L, 1); + const char *vname = luaL_checkstring(L, 2); + ADDR obj = BlObject(oname); + if (!obj) { + return luaL_error(L, "_bllua_ts.getfield: Object not found"); + } + + const char *val = BlGetField(obj, vname, NULL); + lua_pushstring(L, val); + return 1; } // Lua lib function: ts.setfield -int bll_lua_tssetfield(lua_State* L) { - const char* oname = luaL_checkstring(L, 1); - const char* vname = luaL_checkstring(L, 2); - const char* val = luaL_checkstring(L, 3); - ADDR obj = BlObject(oname); - if(!obj) { return luaL_error(L, "_bllua_ts.setfield: Object not found"); } - - BlSetField(obj, vname, NULL, val); - return 0; +int bll_lua_tssetfield(lua_State *L) { + const char *oname = luaL_checkstring(L, 1); + const char *vname = luaL_checkstring(L, 2); + const char *val = luaL_checkstring(L, 3); + ADDR obj = BlObject(oname); + if (!obj) { + return luaL_error(L, "_bllua_ts.setfield: Object not found"); + } + + BlSetField(obj, vname, NULL, val); + return 0; } // Lua lib function: ts.eval -int bll_lua_tseval(lua_State* L) { - const char* str = luaL_checkstring(L, 1); - const char* res = BlEval(str); - lua_pushstring(L, res); - return 1; +int bll_lua_tseval(lua_State *L) { + const char *str = luaL_checkstring(L, 1); + const char *res = BlEval(str); + lua_pushstring(L, res); + return 1; } // Lua lib function: ts.echo // Print to BL console - used in Lua print implementation -int bll_lua_tsecho(lua_State* L) { - const char* str = luaL_checkstring(L, 1); - BlPrintf("%s", str); - return 0; +int bll_lua_tsecho(lua_State *L) { + const char *str = luaL_checkstring(L, 1); + BlPrintf("%s", str); + return 0; } const luaL_Reg bll_lua_reg[] = { - {"call" , bll_lua_tscall }, - {"callobj" , bll_lua_tscallobj }, - {"getvar" , bll_lua_tsgetvar }, - {"getfield", bll_lua_tsgetfield}, - {"setfield", bll_lua_tssetfield}, - {"eval" , bll_lua_tseval }, - {"echo" , bll_lua_tsecho }, - {NULL, NULL}, + {"call", bll_lua_tscall}, {"callobj", bll_lua_tscallobj}, + {"getvar", bll_lua_tsgetvar}, {"getfield", bll_lua_tsgetfield}, + {"setfield", bll_lua_tssetfield}, {"eval", bll_lua_tseval}, + {"echo", bll_lua_tsecho}, {NULL, NULL}, }; -void llibbl_init(lua_State* L) { - luaL_register(L, "_bllua_ts", bll_lua_reg); -} +void llibbl_init(lua_State *L) { luaL_register(L, "_bllua_ts", bll_lua_reg); } diff --git a/src/tsliblua.cpp b/src/tsliblua.cpp index 03fc03d..1448d1f 100644 --- a/src/tsliblua.cpp +++ b/src/tsliblua.cpp @@ -1,25 +1,32 @@ -// Call a Lua function from TS, return true if success - result will be on Lua stack -bool bll_LuaCall(const char* fname, int argc, const char* argv[]) { - lua_getglobal(gL, fname); - for(int i=0; i Date: Mon, 6 Oct 2025 13:16:24 -0400 Subject: [PATCH 12/22] Merge branch 'master' --- inc/lua/luaconf.h | 1 + src/luainterp.cpp | 1 + src/luainterp.hpp | 1 + src/lualibts.cpp | 1 + src/tsliblua.cpp | 1 + src/util/libbl.lua | 42 ++-- src/util/libts-lua.lua | 331 ++++++++++++------------ src/util/matrix.lua | 1 - src/util/std.lua | 556 +++++++++++++++++++++-------------------- 9 files changed, 489 insertions(+), 446 deletions(-) diff --git a/inc/lua/luaconf.h b/inc/lua/luaconf.h index 64a789a..7ce5617 100644 --- a/inc/lua/luaconf.h +++ b/inc/lua/luaconf.h @@ -9,6 +9,7 @@ #ifndef WINVER #define WINVER 0x0501 #endif + #include #include diff --git a/src/luainterp.cpp b/src/luainterp.cpp index a202f6a..8b3aa34 100644 --- a/src/luainterp.cpp +++ b/src/luainterp.cpp @@ -5,6 +5,7 @@ #include "lauxlib.h" #include "lua.h" #include + int bll_error_handler(lua_State *L) { lua_getfield(L, LUA_GLOBALSINDEX, "_bllua_on_error"); if (!lua_isfunction(L, -1)) { diff --git a/src/luainterp.hpp b/src/luainterp.hpp index e672ca5..361c204 100644 --- a/src/luainterp.hpp +++ b/src/luainterp.hpp @@ -3,6 +3,7 @@ #define _H_LUAINTERP_SHARED #include "lua.h" + #define BLL_ARG_COUNT 20 #define BLL_ARG_MAX 8192 diff --git a/src/lualibts.cpp b/src/lualibts.cpp index 7283c70..7f2ce21 100644 --- a/src/lualibts.cpp +++ b/src/lualibts.cpp @@ -8,6 +8,7 @@ #include "lauxlib.h" #include "lua.h" #include "luainterp.hpp" + int bll_TsCall(lua_State *L, const char *oname, const char *fname, int argc, int ofs) { ADDR obj = (ADDR)NULL; diff --git a/src/tsliblua.cpp b/src/tsliblua.cpp index 1448d1f..f2c85f9 100644 --- a/src/tsliblua.cpp +++ b/src/tsliblua.cpp @@ -4,6 +4,7 @@ #include "BlFuncs.hpp" #include "BlHooks.hpp" #include "luainterp.hpp" + bool bll_LuaCall(const char *fname, int argc, const char *argv[]) { lua_getglobal(gL, fname); for (int i = 0; i < argc; i++) { diff --git a/src/util/libbl.lua b/src/util/libbl.lua index 52195df..e3f0f87 100644 --- a/src/util/libbl.lua +++ b/src/util/libbl.lua @@ -202,11 +202,11 @@ setForceType = function(ftname, typ) error('bl.type: invalid type \''..typ..'\'', 2) end if not isValidFuncNameNsArgn(ftname) then error('bl.type: invalid function or variable name \''..ftname..'\'', 2) end - + ftname = ftname:lower() - + bl._forceType[ftname] = typ - + -- apply to child classes if present local cname, rest = classFromForceTypeStr(ftname) if cname then @@ -296,7 +296,7 @@ function bl.class(cname, inhname) if not ( inhname==nil or (type(inhname)=='string' and isValidFuncName(inhname)) ) then error('bl.class: argument #2: inherit name must be a string or nil', 2) end cname = cname:lower() - + local met = bl._objectUserMetas[cname] or { _name = cname, _inherit = nil, @@ -304,22 +304,22 @@ function bl.class(cname, inhname) } bl._objectUserMetas[cname] = met setmetatable(met, tsClassMeta) - + if inhname then inhname = inhname:lower() - + local inh = bl._objectUserMetas[inhname] if not inh then error('bl.class: argument #2: \''..inhname..'\' is not the '.. 'name of an existing class', 2) end - + inh._children[cname] = true - + local inhI = met._inherit if inhI and inhI~=inh then error('bl.class: argument #2: class already exists and '.. 'inherits a different parent.', 2) end met._inherit = inh - + -- apply inherited method and field types for ftname, typ in pairs(bl._forceType) do local cname2, rest = classFromForceTypeStr(ftname) @@ -521,11 +521,11 @@ toTsObject = function(idiS) error('toTsObject: input must be a string', 2) end idiS = idiS:lower() if bl._objectsWeak[idiS] then return bl._objectsWeak[idiS] end - + if not tsIsObject(idiS) then --error('toTsObject: object \''..idiS..'\' does not exist', 2) end return nil end - + local className = _bllua_ts.callobj(idiS, 'getClassName') local obj = { _tsObjectId = _bllua_ts.callobj(idiS, 'getId' ), @@ -534,7 +534,7 @@ toTsObject = function(idiS) _tsClassName = className:lower(), } setmetatable(obj, tsObjectMeta) - + bl._objectsWeak[obj._tsObjectId ] = obj bl._objectsWeak[obj._tsName:lower()] = obj return obj @@ -856,11 +856,11 @@ function bl.hook(pkg, name, time, func) error('bl.hook: argument #3: time must be \'before\' or \'after\'', 2) end if type(func)~='function' then error('bl.hook: argument #4: expected a function', 2) end - + bl._hooks[pkg] = bl._hooks[pkg] or {} bl._hooks[pkg][name] = bl._hooks[pkg][name] or {} bl._hooks[pkg][name][time] = func - + updateHook(pkg, name, bl._hooks[pkg][name]) activatePackage(pkg) end @@ -874,7 +874,7 @@ function bl.unhook(pkg, name, time) error('bl.unhook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end if time~='before' and time~='after' then error('bl.unhook: argument #3: time must be \'before\' or \'after\'', 2) end - + if not name then if bl._hooks[pkg] then for name,hk in pairs(bl._hooks[pkg]) do @@ -961,9 +961,9 @@ function bl.raycast(start, stop, mask, ignores) for _,v in ipairs(ignores) do table.insert(ignoresS, objToTs(v)) end - + local retS = _bllua_ts.call('containerRaycast', startS, stopS, maskS, unpack(ignoresS)) - + if retS=='0' then return nil else @@ -988,7 +988,7 @@ function bl.boxSearch(pos, size, mask) local posS = vecToTs(pos) local sizeS = vecToTs(size) local maskS = maskToTs(mask) - + _bllua_ts.call('initContainerBoxSearch', posS, sizeS, maskS) return tsContainerSearchIterator end @@ -998,7 +998,7 @@ function bl.radiusSearch(pos, radius, mask) error('bl.radiusSearch: argument #2: radius must be a number', 2) end local radiusS = tostring(radius) local maskS = maskToTs(mask) - + _bllua_ts.call('initContainerRadiusSearch', posS, radiusS, maskS) return tsContainerSearchIterator end @@ -1037,7 +1037,7 @@ local function createTsObj(keyword, class, name, inherit, props) table.insert(propsT, k..'="'..valToTs(v)..'";') end end - + local objS = _bllua_ts.eval( 'return '..keyword..' '..class..'('.. (name or '')..(inherit and (':'..inherit) or '')..'){'.. @@ -1045,7 +1045,7 @@ local function createTsObj(keyword, class, name, inherit, props) local obj = toTsObject(objS) if not obj then error('bl.new/bl.datablock: failed to create object', 3) end - + return obj end local function parseTsDecl(decl) diff --git a/src/util/libts-lua.lua b/src/util/libts-lua.lua index 56f4a9d..ebf3d29 100644 --- a/src/util/libts-lua.lua +++ b/src/util/libts-lua.lua @@ -1,4 +1,3 @@ - -- This Lua code provides some built-in utilities for writing Lua add-ons -- It is eval'd automatically once BLLua3 has loaded the TS API and environment -- It only has access to the sandboxed lua environment, just like user code. @@ -7,8 +6,11 @@ ts = _bllua_ts -- Provide limited OS functions os = os or {} -function os.time() return math.floor(tonumber(_bllua_ts.call('getSimTime'))/1000) end -function os.clock() return tonumber(_bllua_ts.call('getSimTime'))/1000 end +---@diagnostic disable-next-line: duplicate-set-field +function os.time() return math.floor(tonumber(_bllua_ts.call('getSimTime')) / 1000) end + +---@diagnostic disable-next-line: duplicate-set-field +function os.clock() return tonumber(_bllua_ts.call('getSimTime')) / 1000 end -- Virtual file class, emulating a file object as returned by io.open -- Used to wrap io.open to allow reading from zips (using TS) @@ -16,140 +18,150 @@ function os.clock() return tonumber(_bllua_ts.call('getSimTime'))/1000 end -- Can't read nulls, can't distinguish between CRLF and LF. -- Todo someday: actually read the zip in lua? local file_meta = { - read = function(file, mode) - file:_init() - if not file or type(file)~='table' or not file._is_file then error('File:read: Not a file', 2) end - if file._is_open ~= true then error('File:read: File is closed', 2) end - if mode=='*n' then - local ws, n = file.data:match('^([ \t\r\n]*)([0-9%.%-e]+)', file.pos) - if n then - file.pos = file.pos + #ws + #n - return n - else - return nil - end - elseif mode=='*a' then - local d = file.data:sub(file.pos, #file.data) - file.pos = #file.data + 1 - return d - elseif mode=='*l' then - local l, ws = file.data:match('^([^\r\n]*)(\r?\n)', file.pos) - if not l then - l = file.data:match('^([^\r\n]*)$', file.pos); ws = ''; - if l=='' then return nil end - end - if l then - file.pos = file.pos + #l + #ws - return l - else - return nil - end - elseif type(mode)=='number' then - local d = file.data:sub(file.pos, file.pos+mode) - file.pos = file.pos + #d - return d - else - error('File:read: Invalid mode \''..mode..'\'', 2) - end - end, - lines = function(file) - file:_init() - return function() - return file:read('*l') - end - end, - close = function(file) - if not file._is_open then error('File:close: File is not open', 2) end - file._is_open = false - end, - __index = function(f, k) return rawget(f, k) or getmetatable(f)[k] end, - _init = function(f) - if not f.data then - f.data = _bllua_ts.call('_bllua_ReadEntireFile', f.filename) - end - end, + read = function(file, mode) + file:_init() + if not file or type(file) ~= 'table' or not file._is_file then error('File:read: Not a file', 2) end + if file._is_open ~= true then error('File:read: File is closed', 2) end + if mode == '*n' then + local ws, n = file.data:match('^([ \t\r\n]*)([0-9%.%-e]+)', file.pos) + if n then + file.pos = file.pos + #ws + #n + return n + else + return nil + end + elseif mode == '*a' then + local d = file.data:sub(file.pos, #file.data) + file.pos = #file.data + 1 + return d + elseif mode == '*l' then + local l, ws = file.data:match('^([^\r\n]*)(\r?\n)', file.pos) + if not l then + l = file.data:match('^([^\r\n]*)$', file.pos); ws = ''; + if l == '' then return nil end + end + if l then + file.pos = file.pos + #l + #ws + return l + else + return nil + end + elseif type(mode) == 'number' then + local d = file.data:sub(file.pos, file.pos + mode) + file.pos = file.pos + #d + return d + else + error('File:read: Invalid mode \'' .. mode .. '\'', 2) + end + end, + lines = function(file) + file:_init() + return function() + return file:read('*l') + end + end, + close = function(file) + if not file._is_open then error('File:close: File is not open', 2) end + file._is_open = false + end, + __index = function(f, k) return rawget(f, k) or getmetatable(f)[k] end, + _init = function(f) + if not f.data then + f.data = _bllua_ts.call('_bllua_ReadEntireFile', f.filename) + end + end, } local function new_file_obj(fn) - local file = { - _is_file = true, - _is_open = true, - pos = 1, - __index = file_meta.__index, - filename = fn, - data = nil, - } - setmetatable(file, file_meta) - return file + local file = { + _is_file = true, + _is_open = true, + pos = 1, + __index = file_meta.__index, + filename = fn, + data = nil, + } + setmetatable(file, file_meta) + return file end -local function tflip(t) local u = {}; for _, n in ipairs(t) do u[n] = true end; return u; end -local allowed_zip_dirs = tflip{ - 'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders' +local function tflip(t) + local u = {}; for _, n in ipairs(t) do u[n] = true end; return u; +end +local allowed_zip_dirs = tflip { + 'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders' } local function io_open_absolute(fn, mode) - -- if file exists, use original mode - local res, err = _bllua_io_open(fn, mode) - if res then return res end - - -- otherwise, if TS sees file but Lua doesn't, it must be in a zip, so use TS reader - local dir = fn:match('^[^/]+') - if not allowed_zip_dirs[dir:lower()] then return nil, 'File is not in one of the allowed directories' end - local exist = _bllua_ts.call('isFile', fn) == '1' - if not exist then return nil, err end - - if mode~=nil and mode~='r' and mode~='rb' then - return nil, 'Files in zips can only be opened in read mode' end - - -- return a temp lua file object with the data - local fi = new_file_obj(fn) - return fi + -- if file exists, use original mode + local res, err = _bllua_io_open(fn, mode) + if res then return res end + + -- otherwise, if TS sees file but Lua doesn't, it must be in a zip, so use TS reader + local dir = fn:match('^[^/]+') + if not allowed_zip_dirs[dir:lower()] then return nil, 'File is not in one of the allowed directories' end + local exist = _bllua_ts.call('isFile', fn) == '1' + if not exist then return nil, err end + + if mode ~= nil and mode ~= 'r' and mode ~= 'rb' then + return nil, 'Files in zips can only be opened in read mode' + end + + -- return a temp lua file object with the data + local fi = new_file_obj(fn) + return fi end io = io or {} +---@diagnostic disable-next-line: duplicate-set-field function io.open(fn, mode, errn) - errn = errn or 1 - - -- try to open the file with relative path, otherwise use absolute path - local curfn = debug.getfilename(errn + 1) or _bllua_ts.getvar('Con::File') - if curfn == '' then curfn = nil end - if fn:find('^%.') then - local relfn = curfn and fn:find('^%./') and - curfn:gsub('[^/]+$', '')..fn:gsub('^%./', '') - if relfn then - local fi, err = io_open_absolute(relfn, mode) - return fi, err, relfn - else - return nil, 'Invalid path', fn - end - else - local fi, err = io_open_absolute(fn, mode) - return fi, err, fn - end + errn = errn or 1 + + -- try to open the file with relative path, otherwise use absolute path + local curfn = debug.getfilename(errn + 1) or _bllua_ts.getvar('Con::File') + if curfn == '' then curfn = nil end + if fn:find('^%.') then + local relfn = curfn and fn:find('^%./') and + curfn:gsub('[^/]+$', '') .. fn:gsub('^%./', '') + if relfn then + local fi, err = io_open_absolute(relfn, mode) + return fi, err, relfn + else + return nil, 'Invalid path', fn + end + else + local fi, err = io_open_absolute(fn, mode) + return fi, err, fn + end end + +---@diagnostic disable-next-line: duplicate-set-field function io.lines(fn) - local fi, err, fn2 = io.open(fn, nil, 2) - if not fi then error('Error opening file \''..fn2..'\': '..err, 2) end - return fi:lines() + local fi, err, fn2 = io.open(fn, nil, 2) + if not fi then error('Error opening file \'' .. fn2 .. '\': ' .. err, 2) end + return fi:lines() end + +---@diagnostic disable-next-line: duplicate-set-field function io.type(f) - if type(f)=='table' and f._is_file then - return f._is_open and 'file' or 'closed file' - else - return _bllua_io_type(f) - end +---@diagnostic disable-next-line: undefined-field + if type(f) == 'table' and f._is_file then +---@diagnostic disable-next-line: undefined-field + return f._is_open and 'file' or 'closed file' + else + return _bllua_io_type(f) + end end -- provide dofile function dofile(fn, errn) - errn = errn or 1 - - local fi, err, fn2 = io.open(fn, 'r', errn+1) - if not fi then error('Error executing file \''..fn2..'\': '..err, errn+1) end - - print('Executing '..fn2) - local text = fi:read('*a') - fi:close() - return assert(loadstring('--[['..fn2..']]'..text))() + errn = errn or 1 + + local fi, err, fn2 = io.open(fn, 'r', errn + 1) + if not fi then error('Error executing file \'' .. fn2 .. '\': ' .. err, errn + 1) end + + print('Executing ' .. fn2) + local text = fi:read('*a') + fi:close() + return assert(loadstring('--[[' .. fn2 .. ']]' .. text))() end -- provide require (just a wrapper for dofile) @@ -158,57 +170,58 @@ end -- blockland directory -- current add-on local function file_exists(fn, errn) - local fi, err, fn2 = io.open(fn, 'r', errn+1) - if fi then - fi:close() - return fn2 - else - return nil - end + local fi, err, fn2 = io.open(fn, 'r', errn + 1) + if fi then + fi:close() + return fn2 + else + return nil + end end local require_memo = {} function require(mod) - if require_memo[mod] then return unpack(require_memo[mod]) end - local fp = mod:gsub('%.', '/') - local fns = { - './'..fp..'.lua', -- local file - './'..fp..'/init.lua', -- local library - fp..'.lua', -- global file - fp..'/init.lua', -- global library - } - if fp:lower():find('^add-ons/') then - local addonpath = fp:lower():match('^add-ons/[^/]+')..'/' - table.insert(fns, addonpath..fp..'.lua') -- add-on file - table.insert(fns, addonpath..fp..'/init.lua') -- add-on library - end - for _,fn in ipairs(fns) do - local fne = file_exists(fn, 2) - if fne then - local res = {dofile(fne, 2)} - require_memo[mod] = res - return unpack(res) - end - end - return _bllua_requiresecure(mod) + if require_memo[mod] then return unpack(require_memo[mod]) end + local fp = mod:gsub('%.', '/') + local fns = { + './' .. fp .. '.lua', -- local file + './' .. fp .. '/init.lua', -- local library + fp .. '.lua', -- global file + fp .. '/init.lua', -- global library + } + if fp:lower():find('^add-ons/') then + local addonpath = fp:lower():match('^add-ons/[^/]+') .. '/' + table.insert(fns, addonpath .. fp .. '.lua') -- add-on file + table.insert(fns, addonpath .. fp .. '/init.lua') -- add-on library + end + for _, fn in ipairs(fns) do + local fne = file_exists(fn, 2) + if fne then + local res = { dofile(fne, 2) } + require_memo[mod] = res + return unpack(res) + end + end + return _bllua_requiresecure(mod) end local function isValidCode(code) - local f,e = loadstring(code) - return f~=nil + local f, e = loadstring(code) + return f ~= nil end function _bllua_smarteval(code) - if (not code:find('^print%(')) and isValidCode('print('..code..')') then - code = 'print('..code..')' end - local f,e = loadstring(code) - if f then - return f() - else - print(e) - end + if (not code:find('^print%(')) and isValidCode('print(' .. code .. ')') then + code = 'print(' .. code .. ')' + end + local f, e = loadstring(code) + if f then + return f() + else + print(e) + end end function ts.setvar(name, val) - _bllua_ts.call('_bllua_set_var', name, val) + _bllua_ts.call('_bllua_set_var', name, val) end _bllua_ts.call('echo', ' Executed libts-lua.lua') diff --git a/src/util/matrix.lua b/src/util/matrix.lua index 41a3542..8c16791 100644 --- a/src/util/matrix.lua +++ b/src/util/matrix.lua @@ -1,4 +1,3 @@ - -- todo -- Matrix class with math operators diff --git a/src/util/std.lua b/src/util/std.lua index ca79eea..ce57486 100644 --- a/src/util/std.lua +++ b/src/util/std.lua @@ -1,350 +1,376 @@ - -- Basic functionality that should be standard in Lua -- Table / List -- Whether a table contains no keys function table.empty(t) - return next(t)~=nil + return next(t) ~= nil end + -- Apply a function to each key in a table function table.map(f, ...) - local ts = {...} - local u = {} - for k,_ in pairs(ts[1]) do - local args = {} - for j=1,#ts do args[j] = ts[j][i] end - u[i] = f(unpack(args)) - end - return u + local ts = { ... } + local u = {} + for k, _ in pairs(ts[1]) do + local args = {} + for j = 1, #ts do args[j] = ts[j][i] end + u[i] = f(unpack(args)) + end + return u end + function table.map_list(f, ...) - local ts = {...} - local u = {} - for i=1,#ts[1] do - local args = {} - for j=1,#ts do args[j] = ts[j][i] end - u[i] = f(unpack(args)) - end - return u + local ts = { ... } + local u = {} + for i = 1, #ts[1] do + local args = {} + for j = 1, #ts do args[j] = ts[j][i] end + u[i] = f(unpack(args)) + end + return u end + -- Swap keys/values function table.swap(t) - local u = {} - for k,v in pairs(t) do u[v] = k end - return u + local u = {} + for k, v in pairs(t) do u[v] = k end + return u end + -- Reverse a list function table.reverse(l) - local m = {} - for i=1,#l do m[#l-i+1] = l[i] end - return m + local m = {} + for i = 1, #l do m[#l - i + 1] = l[i] end + return m end + -- Whether a table is a list/array (has only monotonic integer keys) function table.islist(t) - local n = 0 - for i,_ in pairs(t) do - if type(i)~='number' or i%1~=0 then return false end - n = n+1 - end - return n==#t + local n = 0 + for i, _ in pairs(t) do + if type(i) ~= 'number' or i % 1 ~= 0 then return false end + n = n + 1 + end + return n == #t end + -- Append contents of other tables to first table function table.append(t, ...) - local a = {...} - for _,u in ipairs(a) do - for _,v in ipairs(u) do table.insert(t,v) end - end - return t + local a = { ... } + for _, u in ipairs(a) do + for _, v in ipairs(u) do table.insert(t, v) end + end + return t end + -- Create a new table containing all keys from any number of tables -- latter tables in the arg list override prior ones -- overlaps, NOT appends, integer keys function table.join(...) - local ts = {...} - local w = {} - for _,t in ipairs(ts) do - for k,v in pairs(t) do w[k] = v end - end - return w + local ts = { ... } + local w = {} + for _, t in ipairs(ts) do + for k, v in pairs(t) do w[k] = v end + end + return w end + -- Whether a table contains a certain value in any key -function table.contains(t,s) - for _,v in pairs(t) do - if v==s then return true end - end - return false +function table.contains(t, s) + for _, v in pairs(t) do + if v == s then return true end + end + return false end -function table.contains_list(t,s) - for _,v in ipairs(t) do - if v==s then return true end - end - return false + +function table.contains_list(t, s) + for _, v in ipairs(t) do + if v == s then return true end + end + return false end + -- Copy a table to another table function table.copy(t) - local u = {} - for k,v in pairs(t) do u[k] = v end - return u + local u = {} + for k, v in pairs(t) do u[k] = v end + return u end + function table.copy_list(l) - local m = {} - for i,v in ipairs(l) do m[i] = v end - return m + local m = {} + for i, v in ipairs(l) do m[i] = v end + return m end + -- Sort a table in a new copy function table.sortcopy(t, f) - local u = table.copy_list(t) - table.sort(u, f) - return u + local u = table.copy_list(t) + table.sort(u, f) + return u end + -- Remove a value from a table function table.removevalue(t, r) - local rem = {} - for k,v in pairs(t) do - if v==r then table.insert(rem, k) end - end - for _,k in ipairs(rem) do t[k] = nil end + local rem = {} + for k, v in pairs(t) do + if v == r then table.insert(rem, k) end + end + for _, k in ipairs(rem) do t[k] = nil end end + function table.removevalue_list(t, r) - for i = #t, 1, -1 do - if t[i]==r then - table.remove(t, i) - end - end + for i = #t, 1, -1 do + if t[i] == r then + table.remove(t, i) + end + end end + -- Export tables into formatted executable strings local function tabs(tabLevel) - return (' '):rep(tabLevel) + return (' '):rep(tabLevel) end local valueToString local function tableToString(t, tabLevel, seen) - if type(t)~='table' or (getmetatable(t) and getmetatable(t).__tostring) then - return tostring(t) - elseif table.islist(t) then - if #t==0 then - return '{}' - else - local strs = {} - local containsTables = false - for _,v in ipairs(t) do - if type(v)=='table' then containsTables = true end - table.insert(strs, valueToString(v, tabLevel+1, seen)..',') - end - if containsTables or #t>3 then - return '{\n'..tabs(tabLevel+1) - ..table.concat(strs, '\n'..tabs(tabLevel+1)) - ..'\n'..tabs(tabLevel)..'}' - else - return '{ '..table.concat(strs, ' ')..' }' - end - end - else - local containsNonStringKeys = false - for k,v in pairs(t) do - if type(k)~='string' or k:find('[^a-zA-Z0-9_]') then - containsNonStringKeys = true - elseif type(k)=='table' then - error('table.tostring: table contains a table as key, cannot serialize') - end - end - local strs = {} - if containsNonStringKeys then - for k,v in pairs(t) do - table.insert(strs, '\n'..tabs(tabLevel+1) - ..'['..valueToString(k, tabLevel+1, seen)..'] = ' - ..valueToString(v, tabLevel+1, seen)..',') - end - else - for k,v in pairs(t) do - table.insert(strs, '\n'..tabs(tabLevel+1) - ..k..' = '..valueToString(v, tabLevel+1, seen)..',') - end - end - return '{'..table.concat(strs)..'\n'..tabs(tabLevel)..'}' - end + if type(t) ~= 'table' or (getmetatable(t) and getmetatable(t).__tostring) then + return tostring(t) + elseif table.islist(t) then + if #t == 0 then + return '{}' + else + local strs = {} + local containsTables = false + for _, v in ipairs(t) do + if type(v) == 'table' then containsTables = true end + table.insert(strs, valueToString(v, tabLevel + 1, seen) .. ',') + end + if containsTables or #t > 3 then + return '{\n' .. tabs(tabLevel + 1) + .. table.concat(strs, '\n' .. tabs(tabLevel + 1)) + .. '\n' .. tabs(tabLevel) .. '}' + else + return '{ ' .. table.concat(strs, ' ') .. ' }' + end + end + else + local containsNonStringKeys = false + for k, v in pairs(t) do + if type(k) ~= 'string' or k:find('[^a-zA-Z0-9_]') then + containsNonStringKeys = true + elseif type(k) == 'table' then + error('table.tostring: table contains a table as key, cannot serialize') + end + end + local strs = {} + if containsNonStringKeys then + for k, v in pairs(t) do + table.insert(strs, '\n' .. tabs(tabLevel + 1) + .. '[' .. valueToString(k, tabLevel + 1, seen) .. '] = ' + .. valueToString(v, tabLevel + 1, seen) .. ',') + end + else + for k, v in pairs(t) do + table.insert(strs, '\n' .. tabs(tabLevel + 1) + .. k .. ' = ' .. valueToString(v, tabLevel + 1, seen) .. ',') + end + end + return '{' .. table.concat(strs) .. '\n' .. tabs(tabLevel) .. '}' + end end valueToString = function(v, tabLevel, seen) - local t = type(v) - if t=='table' then - if seen[v] then - return 'nil --[[ already seen: '..tostring(v)..' ]]' - else - seen[v] = true - return tableToString(v, tabLevel, seen) - end - elseif t=='string' then - return '\''..string.escape(v)..'\'' - elseif t=='number' or t=='boolean' then - return tostring(v) - else - --error('table.tostring: table contains a '..t..' value, cannot serialize') - return 'nil --[[ cannot serialize '..tostring(v)..' ]]' - end + local t = type(v) + if t == 'table' then + if seen[v] then + return 'nil --[[ already seen: ' .. tostring(v) .. ' ]]' + else + seen[v] = true + return tableToString(v, tabLevel, seen) + end + elseif t == 'string' then + return '\'' .. string.escape(v) .. '\'' + elseif t == 'number' or t == 'boolean' then + return tostring(v) + else + --error('table.tostring: table contains a '..t..' value, cannot serialize') + return 'nil --[[ cannot serialize ' .. tostring(v) .. ' ]]' + end end function table.tostring(t) - return tableToString(t, 0, {}) + return tableToString(t, 0, {}) end - -- String -- Split string into table by separator -- or by chars if no separator given -- if regex is not true, sep is treated as a regex pattern function string.split(str, sep, noregex) - if type(str)~='string' then - error('string.split: argument #1: expected string, got '..type(str), 2) end - if sep==nil or sep=='' then - local t = {} - local ns = #str - for x = 1, ns do - table.insert(t, str:sub(x, x)) - end - return t - elseif type(sep)=='string' then - local t = {} - if #str>0 then - local first = 1 - while true do - local last, newfirst = str:find(sep, first, noregex) - if not last then break end - table.insert(t, str:sub(first, last-1)) - first = newfirst+1 - end - table.insert(t, str:sub(first, #str)) - end - return t - else - error( - 'string.split: argument #2: expected string or nil, got '..type(sep), 2) - end -end --- Split string to a list of char bytes -function string.bytes(s) - local b = {} - for i=1,#s do - local c = s:sub(i,i) - table.insert(b, c:byte()) - end - return b -end --- Trim leading and trailing whitespace -function string.trim(s, ws) - ws = ws or ' \t\r\n' - return s:gsub('^['..ws..']+', ''):gsub('['..ws..']+$', '')..'' -end --- String slicing and searching using [] operator -local str_meta = getmetatable('') -local str_meta_index_old= str_meta.__index -function str_meta.__index(s,k) - if type(k)=='string' then - return str_meta_index_old[k] - elseif type(k)=='number' then - if k<0 then k = #s+k+1 end - return string.sub(s,k,k) - elseif type(k)=='table' then - local a = k[1]<0 and (#s+k[1]+1) or k[1] - local b = k[2]<0 and (#s+k[2]+1) or k[2] - return string.sub(s,a,b) - end -end --- String iterator -function string.chars(s) - local i = 0 - return function() - i = i+1 - if i<=#s then return s:sub(i,i) - else return nil end - end -end --- Escape sequences -local defaultEscapes = { - ['\\'] = '\\\\', - ['\''] = '\\\'', - ['\"'] = '\\\"', - ['\t'] = '\\t', - ['\r'] = '\\r', - ['\n'] = '\\n', - ['\0'] = '\\0', -} -function string.escape(s, escapes) - escapes = escapes or defaultEscapes - local t = {} - for i=1,#s do - local c = s:sub(i,i) - table.insert(t, escapes[c] or c) - end - return table.concat(t) -end -local defaultEscapeChar = '\\' -local defaultUnescapes = { - ['\\'] = '\\', - ['\''] = '\'', - ['\"'] = '\"', - ['t'] = '\t', - ['r'] = '\r', - ['n'] = '\n', - ['0'] = '\0', -} -function string.unescape(s, escapeChar, unescapes) - escapeChar = escapeChar or defaultEscapeChar - unescapes = unescapes or defaultUnescapes - local t = {} - local inEscape = false - for i=1,#s do - local c = s:sub(i,i) - if inEscape then - table.insert(t, unescapes[c] - or error('string.unescape: invalid escape sequence: \'' - ..escapeChar..c..'\'')) - elseif c==escapeChar then - inEscape = true - else - table.insert(t, c) - end - end - return table.concat(t) + if type(str) ~= 'string' then + error('string.split: argument #1: expected string, got ' .. type(str), 2) + end + if sep == nil or sep == '' then + local t = {} + local ns = #str + for x = 1, ns do + table.insert(t, str:sub(x, x)) + end + return t + elseif type(sep) == 'string' then + local t = {} + if #str > 0 then + local first = 1 + while true do + local last, newfirst = str:find(sep, first, noregex) + if not last then break end + table.insert(t, str:sub(first, last - 1)) + first = newfirst + 1 + end + table.insert(t, str:sub(first, #str)) + end + return t + else + error( + 'string.split: argument #2: expected string or nil, got ' .. type(sep), 2) + end end +-- Split string to a list of char bytes +function string.bytes(s) + local b = {} + for i = 1, #s do + local c = s:sub(i, i) + table.insert(b, c:byte()) + end + return b +end + +-- Trim leading and trailing whitespace +function string.trim(s, ws) + ws = ws or ' \t\r\n' + return s:gsub('^[' .. ws .. ']+', ''):gsub('[' .. ws .. ']+$', '') .. '' +end + +-- String slicing and searching using [] operator +local str_meta = getmetatable('') +local str_meta_index_old = str_meta.__index +function str_meta.__index(s, k) + if type(k) == 'string' then + return str_meta_index_old[k] + elseif type(k) == 'number' then + if k < 0 then k = #s + k + 1 end + return string.sub(s, k, k) + elseif type(k) == 'table' then + local a = k[1] < 0 and (#s + k[1] + 1) or k[1] + local b = k[2] < 0 and (#s + k[2] + 1) or k[2] + return string.sub(s, a, b) + end +end + +-- String iterator +function string.chars(s) + local i = 0 + return function() + i = i + 1 + if i <= #s then + return s:sub(i, i) + else + return nil + end + end +end + +-- Escape sequences +local defaultEscapes = { + ['\\'] = '\\\\', + ['\''] = '\\\'', + ['\"'] = '\\\"', + ['\t'] = '\\t', + ['\r'] = '\\r', + ['\n'] = '\\n', + ['\0'] = '\\0', +} +function string.escape(s, escapes) + escapes = escapes or defaultEscapes + local t = {} + for i = 1, #s do + local c = s:sub(i, i) + table.insert(t, escapes[c] or c) + end + return table.concat(t) +end + +local defaultEscapeChar = '\\' +local defaultUnescapes = { + ['\\'] = '\\', + ['\''] = '\'', + ['\"'] = '\"', + ['t'] = '\t', + ['r'] = '\r', + ['n'] = '\n', + ['0'] = '\0', +} +function string.unescape(s, escapeChar, unescapes) + escapeChar = escapeChar or defaultEscapeChar + unescapes = unescapes or defaultUnescapes + local t = {} + local inEscape = false + for i = 1, #s do + local c = s:sub(i, i) + if inEscape then + table.insert(t, unescapes[c] + or error('string.unescape: invalid escape sequence: \'' + .. escapeChar .. c .. '\'')) + elseif c == escapeChar then + inEscape = true + else + table.insert(t, c) + end + end + return table.concat(t) +end -- IO io = io or {} -- Read entire file at once, return nil,err if access failed function io.readall(filename) - local fi,err = io.open(filename, 'rb') - if not fi then return nil,err end - local s = fi:read("*a") - fi:close() - return s -end --- Write data to file all at once, return true if success / false,err if failure -function io.writeall(filename, data) - local fi,err = io.open(filename, 'wb') - if not fi then return false,err end - fi:write(data) - fi:close() - return true,nil + local fi, err = io.open(filename, 'rb') + if not fi then return nil, err end + local s = fi:read("*a") + fi:close() + return s end +-- Write data to file all at once, return true if success / false,err if failure +function io.writeall(filename, data) + local fi, err = io.open(filename, 'wb') + if not fi then return false, err end + fi:write(data) + fi:close() + return true, nil +end -- Math -- Round function math.round(x) - return math.floor(x+0.5) + return math.floor(x + 0.5) end + -- Mod that accounts for floating point inaccuracy -function math.mod(a,b) - local m = a%b - if m==0 or math.abs(m)<1e-15 or math.abs(m-b)<1e-15 then return 0 - else return m end +function math.mod(a, b) + local m = a % b + if m == 0 or math.abs(m) < 1e-15 or math.abs(m - b) < 1e-15 then + return 0 + else + return m + end end + -- Clamp value between min and max function math.clamp(v, n, x) - return math.min(x, math.max(v, n)) + return math.min(x, math.max(v, n)) end - print(' Executed std.lua') -- 2.49.1 From ed5c254480a003c8a8844e96fd69c4a440e09b5a Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Mon, 6 Oct 2025 17:00:12 -0400 Subject: [PATCH 13/22] .clang-format updates --- .clang-format | 28 +++++ inc/lua/lauxlib.h | 122 ++++++++---------- inc/lua/lua.h | 200 +++++++++++++++-------------- inc/lua/luaconf.h | 18 ++- inc/lua/luajit.h | 13 +- inc/lua/lualib.h | 24 ++-- inc/tsfuncs/BlFuncs.cpp | 269 ++++++++++++++++++++++------------------ inc/tsfuncs/BlFuncs.hpp | 125 ++++++++----------- inc/tsfuncs/BlHooks.cpp | 74 +++++------ inc/tsfuncs/BlHooks.hpp | 140 ++++++++++----------- src/bllua4.cpp | 23 ++-- src/luainterp.cpp | 14 ++- src/luainterp.hpp | 8 +- src/lualibts.cpp | 203 +++++++++++++++--------------- src/tsliblua.cpp | 6 +- 15 files changed, 617 insertions(+), 650 deletions(-) diff --git a/.clang-format b/.clang-format index 874b411..566a555 100644 --- a/.clang-format +++ b/.clang-format @@ -1,4 +1,6 @@ BasedOnStyle: LLVM + +# Include sorting and grouping SortIncludes: CaseSensitive IncludeBlocks: Preserve IncludeCategories: @@ -12,3 +14,29 @@ IncludeCategories: Priority: 4 - Regex: ".*" Priority: 5 + +# Indentation settings +IndentWidth: 2 +UseTab: Never +IndentAccessModifiers: false +AccessModifierOffset: 0 +NamespaceIndentation: All + +# Line breaking and wrapping +ColumnLimit: 120 +BreakBeforeTernaryOperators: false +BreakBeforeBinaryOperators: None +AlwaysBreakAfterReturnType: None +AlwaysBreakAfterDefinitionReturnType: None + +# Alignment +AlignOperands: DontAlign +AlignAfterOpenBracket: AlwaysBreak +PointerAlignment: Left + +# Parameter formatting +BinPackParameters: false +AllowAllParametersOfDeclarationOnNextLine: false + +# Code organization +SeparateDefinitionBlocks: Always diff --git a/inc/lua/lauxlib.h b/inc/lua/lauxlib.h index d21293e..41a6470 100644 --- a/inc/lua/lauxlib.h +++ b/inc/lua/lauxlib.h @@ -16,76 +16,63 @@ #define LUA_ERRFILE (LUA_ERRERR + 1) typedef struct luaL_Reg { - const char *name; + const char* name; lua_CFunction func; } luaL_Reg; -LUALIB_API void(luaL_openlib)(lua_State *L, const char *libname, - const luaL_Reg *l, int nup); -LUALIB_API void(luaL_register)(lua_State *L, const char *libname, - const luaL_Reg *l); -LUALIB_API int(luaL_getmetafield)(lua_State *L, int obj, const char *e); -LUALIB_API int(luaL_callmeta)(lua_State *L, int obj, const char *e); -LUALIB_API int(luaL_typerror)(lua_State *L, int narg, const char *tname); -LUALIB_API int(luaL_argerror)(lua_State *L, int numarg, const char *extramsg); -LUALIB_API const char *(luaL_checklstring)(lua_State * L, int numArg, - size_t *l); -LUALIB_API const char *(luaL_optlstring)(lua_State * L, int numArg, - const char *def, size_t *l); -LUALIB_API lua_Number(luaL_checknumber)(lua_State *L, int numArg); -LUALIB_API lua_Number(luaL_optnumber)(lua_State *L, int nArg, lua_Number def); +LUALIB_API void(luaL_openlib)(lua_State* L, const char* libname, const luaL_Reg* l, int nup); +LUALIB_API void(luaL_register)(lua_State* L, const char* libname, const luaL_Reg* l); +LUALIB_API int(luaL_getmetafield)(lua_State* L, int obj, const char* e); +LUALIB_API int(luaL_callmeta)(lua_State* L, int obj, const char* e); +LUALIB_API int(luaL_typerror)(lua_State* L, int narg, const char* tname); +LUALIB_API int(luaL_argerror)(lua_State* L, int numarg, const char* extramsg); +LUALIB_API const char*(luaL_checklstring)(lua_State * L, int numArg, size_t* l); +LUALIB_API const char*(luaL_optlstring)(lua_State * L, int numArg, const char* def, size_t* l); +LUALIB_API lua_Number(luaL_checknumber)(lua_State* L, int numArg); +LUALIB_API lua_Number(luaL_optnumber)(lua_State* L, int nArg, lua_Number def); -LUALIB_API lua_Integer(luaL_checkinteger)(lua_State *L, int numArg); -LUALIB_API lua_Integer(luaL_optinteger)(lua_State *L, int nArg, - lua_Integer def); +LUALIB_API lua_Integer(luaL_checkinteger)(lua_State* L, int numArg); +LUALIB_API lua_Integer(luaL_optinteger)(lua_State* L, int nArg, lua_Integer def); -LUALIB_API void(luaL_checkstack)(lua_State *L, int sz, const char *msg); -LUALIB_API void(luaL_checktype)(lua_State *L, int narg, int t); -LUALIB_API void(luaL_checkany)(lua_State *L, int narg); +LUALIB_API void(luaL_checkstack)(lua_State* L, int sz, const char* msg); +LUALIB_API void(luaL_checktype)(lua_State* L, int narg, int t); +LUALIB_API void(luaL_checkany)(lua_State* L, int narg); -LUALIB_API int(luaL_newmetatable)(lua_State *L, const char *tname); -LUALIB_API void *(luaL_checkudata)(lua_State * L, int ud, const char *tname); +LUALIB_API int(luaL_newmetatable)(lua_State* L, const char* tname); +LUALIB_API void*(luaL_checkudata)(lua_State * L, int ud, const char* tname); -LUALIB_API void(luaL_where)(lua_State *L, int lvl); -LUALIB_API int(luaL_error)(lua_State *L, const char *fmt, ...); +LUALIB_API void(luaL_where)(lua_State* L, int lvl); +LUALIB_API int(luaL_error)(lua_State* L, const char* fmt, ...); -LUALIB_API int(luaL_checkoption)(lua_State *L, int narg, const char *def, - const char *const lst[]); +LUALIB_API int(luaL_checkoption)(lua_State* L, int narg, const char* def, const char* const lst[]); /* pre-defined references */ #define LUA_NOREF (-2) #define LUA_REFNIL (-1) -LUALIB_API int(luaL_ref)(lua_State *L, int t); -LUALIB_API void(luaL_unref)(lua_State *L, int t, int ref); +LUALIB_API int(luaL_ref)(lua_State* L, int t); +LUALIB_API void(luaL_unref)(lua_State* L, int t, int ref); -LUALIB_API int(luaL_loadfile)(lua_State *L, const char *filename); -LUALIB_API int(luaL_loadbuffer)(lua_State *L, const char *buff, size_t sz, - const char *name); -LUALIB_API int(luaL_loadstring)(lua_State *L, const char *s); +LUALIB_API int(luaL_loadfile)(lua_State* L, const char* filename); +LUALIB_API int(luaL_loadbuffer)(lua_State* L, const char* buff, size_t sz, const char* name); +LUALIB_API int(luaL_loadstring)(lua_State* L, const char* s); -LUALIB_API lua_State *(luaL_newstate)(void); +LUALIB_API lua_State*(luaL_newstate)(void); -LUALIB_API const char *(luaL_gsub)(lua_State * L, const char *s, const char *p, - const char *r); +LUALIB_API const char*(luaL_gsub)(lua_State * L, const char* s, const char* p, const char* r); -LUALIB_API const char *(luaL_findtable)(lua_State * L, int idx, - const char *fname, int szhint); +LUALIB_API const char*(luaL_findtable)(lua_State * L, int idx, const char* fname, int szhint); /* From Lua 5.2. */ -LUALIB_API int luaL_fileresult(lua_State *L, int stat, const char *fname); -LUALIB_API int luaL_execresult(lua_State *L, int stat); -LUALIB_API int(luaL_loadfilex)(lua_State *L, const char *filename, - const char *mode); -LUALIB_API int(luaL_loadbufferx)(lua_State *L, const char *buff, size_t sz, - const char *name, const char *mode); -LUALIB_API void luaL_traceback(lua_State *L, lua_State *L1, const char *msg, - int level); -LUALIB_API void(luaL_setfuncs)(lua_State *L, const luaL_Reg *l, int nup); -LUALIB_API void(luaL_pushmodule)(lua_State *L, const char *modname, - int sizehint); -LUALIB_API void *(luaL_testudata)(lua_State * L, int ud, const char *tname); -LUALIB_API void(luaL_setmetatable)(lua_State *L, const char *tname); +LUALIB_API int luaL_fileresult(lua_State* L, int stat, const char* fname); +LUALIB_API int luaL_execresult(lua_State* L, int stat); +LUALIB_API int(luaL_loadfilex)(lua_State* L, const char* filename, const char* mode); +LUALIB_API int(luaL_loadbufferx)(lua_State* L, const char* buff, size_t sz, const char* name, const char* mode); +LUALIB_API void luaL_traceback(lua_State* L, lua_State* L1, const char* msg, int level); +LUALIB_API void(luaL_setfuncs)(lua_State* L, const luaL_Reg* l, int nup); +LUALIB_API void(luaL_pushmodule)(lua_State* L, const char* modname, int sizehint); +LUALIB_API void*(luaL_testudata)(lua_State * L, int ud, const char* tname); +LUALIB_API void(luaL_setmetatable)(lua_State* L, const char* tname); /* ** =============================================================== @@ -93,8 +80,7 @@ LUALIB_API void(luaL_setmetatable)(lua_State *L, const char *tname); ** =============================================================== */ -#define luaL_argcheck(L, cond, numarg, extramsg) \ - ((void)((cond) || luaL_argerror(L, (numarg), (extramsg)))) +#define luaL_argcheck(L, cond, numarg, extramsg) ((void)((cond) || luaL_argerror(L, (numarg), (extramsg)))) #define luaL_checkstring(L, n) (luaL_checklstring(L, (n), NULL)) #define luaL_optstring(L, n, d) (luaL_optlstring(L, (n), (d), NULL)) #define luaL_checkint(L, n) ((int)luaL_checkinteger(L, (n))) @@ -104,19 +90,16 @@ LUALIB_API void(luaL_setmetatable)(lua_State *L, const char *tname); #define luaL_typename(L, i) lua_typename(L, lua_type(L, (i))) -#define luaL_dofile(L, fn) \ - (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0)) +#define luaL_dofile(L, fn) (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0)) -#define luaL_dostring(L, s) \ - (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0)) +#define luaL_dostring(L, s) (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0)) #define luaL_getmetatable(L, n) (lua_getfield(L, LUA_REGISTRYINDEX, (n))) #define luaL_opt(L, f, n, d) (lua_isnoneornil(L, (n)) ? (d) : f(L, (n))) /* From Lua 5.2. */ -#define luaL_newlibtable(L, l) \ - lua_createtable(L, 0, sizeof(l) / sizeof((l)[0]) - 1) +#define luaL_newlibtable(L, l) lua_createtable(L, 0, sizeof(l) / sizeof((l)[0]) - 1) #define luaL_newlib(L, l) (luaL_newlibtable(L, l), luaL_setfuncs(L, l, 0)) /* @@ -126,27 +109,26 @@ LUALIB_API void(luaL_setmetatable)(lua_State *L, const char *tname); */ typedef struct luaL_Buffer { - char *p; /* current position in buffer */ + char* p; /* current position in buffer */ int lvl; /* number of strings in the stack (level) */ - lua_State *L; + lua_State* L; char buffer[LUAL_BUFFERSIZE]; } luaL_Buffer; -#define luaL_addchar(B, c) \ - ((void)((B)->p < ((B)->buffer + LUAL_BUFFERSIZE) || luaL_prepbuffer(B)), \ - (*(B)->p++ = (char)(c))) +#define luaL_addchar(B, c) \ + ((void)((B)->p < ((B)->buffer + LUAL_BUFFERSIZE) || luaL_prepbuffer(B)), (*(B)->p++ = (char)(c))) /* compatibility only */ #define luaL_putchar(B, c) luaL_addchar(B, c) #define luaL_addsize(B, n) ((B)->p += (n)) -LUALIB_API void(luaL_buffinit)(lua_State *L, luaL_Buffer *B); -LUALIB_API char *(luaL_prepbuffer)(luaL_Buffer * B); -LUALIB_API void(luaL_addlstring)(luaL_Buffer *B, const char *s, size_t l); -LUALIB_API void(luaL_addstring)(luaL_Buffer *B, const char *s); -LUALIB_API void(luaL_addvalue)(luaL_Buffer *B); -LUALIB_API void(luaL_pushresult)(luaL_Buffer *B); +LUALIB_API void(luaL_buffinit)(lua_State* L, luaL_Buffer* B); +LUALIB_API char*(luaL_prepbuffer)(luaL_Buffer * B); +LUALIB_API void(luaL_addlstring)(luaL_Buffer* B, const char* s, size_t l); +LUALIB_API void(luaL_addstring)(luaL_Buffer* B, const char* s); +LUALIB_API void(luaL_addvalue)(luaL_Buffer* B); +LUALIB_API void(luaL_pushresult)(luaL_Buffer* B); /* }====================================================== */ diff --git a/inc/lua/lua.h b/inc/lua/lua.h index eb61e5f..05dd6a3 100644 --- a/inc/lua/lua.h +++ b/inc/lua/lua.h @@ -43,19 +43,19 @@ typedef struct lua_State lua_State; -typedef int (*lua_CFunction)(lua_State *L); +typedef int (*lua_CFunction)(lua_State* L); /* ** functions that read/write blocks when loading/dumping Lua chunks */ -typedef const char *(*lua_Reader)(lua_State *L, void *ud, size_t *sz); +typedef const char* (*lua_Reader)(lua_State* L, void* ud, size_t* sz); -typedef int (*lua_Writer)(lua_State *L, const void *p, size_t sz, void *ud); +typedef int (*lua_Writer)(lua_State* L, const void* p, size_t sz, void* ud); /* ** prototype for memory-allocation functions */ -typedef void *(*lua_Alloc)(void *ud, void *ptr, size_t osize, size_t nsize); +typedef void* (*lua_Alloc)(void* ud, void* ptr, size_t osize, size_t nsize); /* ** basic types @@ -91,105 +91,103 @@ typedef LUA_INTEGER lua_Integer; /* ** state manipulation */ -LUA_API lua_State *(lua_newstate)(lua_Alloc f, void *ud); -LUA_API void(lua_close)(lua_State *L); -LUA_API lua_State *(lua_newthread)(lua_State * L); +LUA_API lua_State*(lua_newstate)(lua_Alloc f, void* ud); +LUA_API void(lua_close)(lua_State* L); +LUA_API lua_State*(lua_newthread)(lua_State * L); -LUA_API lua_CFunction(lua_atpanic)(lua_State *L, lua_CFunction panicf); +LUA_API lua_CFunction(lua_atpanic)(lua_State* L, lua_CFunction panicf); /* ** basic stack manipulation */ -LUA_API int(lua_gettop)(lua_State *L); -LUA_API void(lua_settop)(lua_State *L, int idx); -LUA_API void(lua_pushvalue)(lua_State *L, int idx); -LUA_API void(lua_remove)(lua_State *L, int idx); -LUA_API void(lua_insert)(lua_State *L, int idx); -LUA_API void(lua_replace)(lua_State *L, int idx); -LUA_API int(lua_checkstack)(lua_State *L, int sz); +LUA_API int(lua_gettop)(lua_State* L); +LUA_API void(lua_settop)(lua_State* L, int idx); +LUA_API void(lua_pushvalue)(lua_State* L, int idx); +LUA_API void(lua_remove)(lua_State* L, int idx); +LUA_API void(lua_insert)(lua_State* L, int idx); +LUA_API void(lua_replace)(lua_State* L, int idx); +LUA_API int(lua_checkstack)(lua_State* L, int sz); -LUA_API void(lua_xmove)(lua_State *from, lua_State *to, int n); +LUA_API void(lua_xmove)(lua_State* from, lua_State* to, int n); /* ** access functions (stack -> C) */ -LUA_API int(lua_isnumber)(lua_State *L, int idx); -LUA_API int(lua_isstring)(lua_State *L, int idx); -LUA_API int(lua_iscfunction)(lua_State *L, int idx); -LUA_API int(lua_isuserdata)(lua_State *L, int idx); -LUA_API int(lua_type)(lua_State *L, int idx); -LUA_API const char *(lua_typename)(lua_State * L, int tp); +LUA_API int(lua_isnumber)(lua_State* L, int idx); +LUA_API int(lua_isstring)(lua_State* L, int idx); +LUA_API int(lua_iscfunction)(lua_State* L, int idx); +LUA_API int(lua_isuserdata)(lua_State* L, int idx); +LUA_API int(lua_type)(lua_State* L, int idx); +LUA_API const char*(lua_typename)(lua_State * L, int tp); -LUA_API int(lua_equal)(lua_State *L, int idx1, int idx2); -LUA_API int(lua_rawequal)(lua_State *L, int idx1, int idx2); -LUA_API int(lua_lessthan)(lua_State *L, int idx1, int idx2); +LUA_API int(lua_equal)(lua_State* L, int idx1, int idx2); +LUA_API int(lua_rawequal)(lua_State* L, int idx1, int idx2); +LUA_API int(lua_lessthan)(lua_State* L, int idx1, int idx2); -LUA_API lua_Number(lua_tonumber)(lua_State *L, int idx); -LUA_API lua_Integer(lua_tointeger)(lua_State *L, int idx); -LUA_API int(lua_toboolean)(lua_State *L, int idx); -LUA_API const char *(lua_tolstring)(lua_State * L, int idx, size_t *len); -LUA_API size_t(lua_objlen)(lua_State *L, int idx); -LUA_API lua_CFunction(lua_tocfunction)(lua_State *L, int idx); -LUA_API void *(lua_touserdata)(lua_State * L, int idx); -LUA_API lua_State *(lua_tothread)(lua_State * L, int idx); -LUA_API const void *(lua_topointer)(lua_State * L, int idx); +LUA_API lua_Number(lua_tonumber)(lua_State* L, int idx); +LUA_API lua_Integer(lua_tointeger)(lua_State* L, int idx); +LUA_API int(lua_toboolean)(lua_State* L, int idx); +LUA_API const char*(lua_tolstring)(lua_State * L, int idx, size_t* len); +LUA_API size_t(lua_objlen)(lua_State* L, int idx); +LUA_API lua_CFunction(lua_tocfunction)(lua_State* L, int idx); +LUA_API void*(lua_touserdata)(lua_State * L, int idx); +LUA_API lua_State*(lua_tothread)(lua_State * L, int idx); +LUA_API const void*(lua_topointer)(lua_State * L, int idx); /* ** push functions (C -> stack) */ -LUA_API void(lua_pushnil)(lua_State *L); -LUA_API void(lua_pushnumber)(lua_State *L, lua_Number n); -LUA_API void(lua_pushinteger)(lua_State *L, lua_Integer n); -LUA_API void(lua_pushlstring)(lua_State *L, const char *s, size_t l); -LUA_API void(lua_pushstring)(lua_State *L, const char *s); -LUA_API const char *(lua_pushvfstring)(lua_State * L, const char *fmt, - va_list argp); -LUA_API const char *(lua_pushfstring)(lua_State * L, const char *fmt, ...); -LUA_API void(lua_pushcclosure)(lua_State *L, lua_CFunction fn, int n); -LUA_API void(lua_pushboolean)(lua_State *L, int b); -LUA_API void(lua_pushlightuserdata)(lua_State *L, void *p); -LUA_API int(lua_pushthread)(lua_State *L); +LUA_API void(lua_pushnil)(lua_State* L); +LUA_API void(lua_pushnumber)(lua_State* L, lua_Number n); +LUA_API void(lua_pushinteger)(lua_State* L, lua_Integer n); +LUA_API void(lua_pushlstring)(lua_State* L, const char* s, size_t l); +LUA_API void(lua_pushstring)(lua_State* L, const char* s); +LUA_API const char*(lua_pushvfstring)(lua_State * L, const char* fmt, va_list argp); +LUA_API const char*(lua_pushfstring)(lua_State * L, const char* fmt, ...); +LUA_API void(lua_pushcclosure)(lua_State* L, lua_CFunction fn, int n); +LUA_API void(lua_pushboolean)(lua_State* L, int b); +LUA_API void(lua_pushlightuserdata)(lua_State* L, void* p); +LUA_API int(lua_pushthread)(lua_State* L); /* ** get functions (Lua -> stack) */ -LUA_API void(lua_gettable)(lua_State *L, int idx); -LUA_API void(lua_getfield)(lua_State *L, int idx, const char *k); -LUA_API void(lua_rawget)(lua_State *L, int idx); -LUA_API void(lua_rawgeti)(lua_State *L, int idx, int n); -LUA_API void(lua_createtable)(lua_State *L, int narr, int nrec); -LUA_API void *(lua_newuserdata)(lua_State * L, size_t sz); -LUA_API int(lua_getmetatable)(lua_State *L, int objindex); -LUA_API void(lua_getfenv)(lua_State *L, int idx); +LUA_API void(lua_gettable)(lua_State* L, int idx); +LUA_API void(lua_getfield)(lua_State* L, int idx, const char* k); +LUA_API void(lua_rawget)(lua_State* L, int idx); +LUA_API void(lua_rawgeti)(lua_State* L, int idx, int n); +LUA_API void(lua_createtable)(lua_State* L, int narr, int nrec); +LUA_API void*(lua_newuserdata)(lua_State * L, size_t sz); +LUA_API int(lua_getmetatable)(lua_State* L, int objindex); +LUA_API void(lua_getfenv)(lua_State* L, int idx); /* ** set functions (stack -> Lua) */ -LUA_API void(lua_settable)(lua_State *L, int idx); -LUA_API void(lua_setfield)(lua_State *L, int idx, const char *k); -LUA_API void(lua_rawset)(lua_State *L, int idx); -LUA_API void(lua_rawseti)(lua_State *L, int idx, int n); -LUA_API int(lua_setmetatable)(lua_State *L, int objindex); -LUA_API int(lua_setfenv)(lua_State *L, int idx); +LUA_API void(lua_settable)(lua_State* L, int idx); +LUA_API void(lua_setfield)(lua_State* L, int idx, const char* k); +LUA_API void(lua_rawset)(lua_State* L, int idx); +LUA_API void(lua_rawseti)(lua_State* L, int idx, int n); +LUA_API int(lua_setmetatable)(lua_State* L, int objindex); +LUA_API int(lua_setfenv)(lua_State* L, int idx); /* ** `load' and `call' functions (load and run Lua code) */ -LUA_API void(lua_call)(lua_State *L, int nargs, int nresults); -LUA_API int(lua_pcall)(lua_State *L, int nargs, int nresults, int errfunc); -LUA_API int(lua_cpcall)(lua_State *L, lua_CFunction func, void *ud); -LUA_API int(lua_load)(lua_State *L, lua_Reader reader, void *dt, - const char *chunkname); +LUA_API void(lua_call)(lua_State* L, int nargs, int nresults); +LUA_API int(lua_pcall)(lua_State* L, int nargs, int nresults, int errfunc); +LUA_API int(lua_cpcall)(lua_State* L, lua_CFunction func, void* ud); +LUA_API int(lua_load)(lua_State* L, lua_Reader reader, void* dt, const char* chunkname); -LUA_API int(lua_dump)(lua_State *L, lua_Writer writer, void *data); +LUA_API int(lua_dump)(lua_State* L, lua_Writer writer, void* data); /* ** coroutine functions */ -LUA_API int(lua_yield)(lua_State *L, int nresults); -LUA_API int(lua_resume)(lua_State *L, int narg); -LUA_API int(lua_status)(lua_State *L); +LUA_API int(lua_yield)(lua_State* L, int nresults); +LUA_API int(lua_resume)(lua_State* L, int narg); +LUA_API int(lua_status)(lua_State* L); /* ** garbage-collection function and options @@ -205,20 +203,20 @@ LUA_API int(lua_status)(lua_State *L); #define LUA_GCSETSTEPMUL 7 #define LUA_GCISRUNNING 9 -LUA_API int(lua_gc)(lua_State *L, int what, int data); +LUA_API int(lua_gc)(lua_State* L, int what, int data); /* ** miscellaneous functions */ -LUA_API int(lua_error)(lua_State *L); +LUA_API int(lua_error)(lua_State* L); -LUA_API int(lua_next)(lua_State *L, int idx); +LUA_API int(lua_next)(lua_State* L, int idx); -LUA_API void(lua_concat)(lua_State *L, int n); +LUA_API void(lua_concat)(lua_State* L, int n); -LUA_API lua_Alloc(lua_getallocf)(lua_State *L, void **ud); -LUA_API void lua_setallocf(lua_State *L, lua_Alloc f, void *ud); +LUA_API lua_Alloc(lua_getallocf)(lua_State* L, void** ud); +LUA_API void lua_setallocf(lua_State* L, lua_Alloc f, void* ud); /* ** =============================================================== @@ -245,8 +243,7 @@ LUA_API void lua_setallocf(lua_State *L, lua_Alloc f, void *ud); #define lua_isnone(L, n) (lua_type(L, (n)) == LUA_TNONE) #define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0) -#define lua_pushliteral(L, s) \ - lua_pushlstring(L, "" s, (sizeof(s) / sizeof(char)) - 1) +#define lua_pushliteral(L, s) lua_pushlstring(L, "" s, (sizeof(s) / sizeof(char)) - 1) #define lua_setglobal(L, s) lua_setfield(L, LUA_GLOBALSINDEX, (s)) #define lua_getglobal(L, s) lua_getfield(L, LUA_GLOBALSINDEX, (s)) @@ -267,7 +264,7 @@ LUA_API void lua_setallocf(lua_State *L, lua_Alloc f, void *ud); #define lua_Chunkwriter lua_Writer /* hack */ -LUA_API void lua_setlevel(lua_State *from, lua_State *to); +LUA_API void lua_setlevel(lua_State* from, lua_State* to); /* ** {====================================================================== @@ -295,38 +292,37 @@ LUA_API void lua_setlevel(lua_State *from, lua_State *to); typedef struct lua_Debug lua_Debug; /* activation record */ /* Functions to be called by the debuger in specific events */ -typedef void (*lua_Hook)(lua_State *L, lua_Debug *ar); +typedef void (*lua_Hook)(lua_State* L, lua_Debug* ar); -LUA_API int lua_getstack(lua_State *L, int level, lua_Debug *ar); -LUA_API int lua_getinfo(lua_State *L, const char *what, lua_Debug *ar); -LUA_API const char *lua_getlocal(lua_State *L, const lua_Debug *ar, int n); -LUA_API const char *lua_setlocal(lua_State *L, const lua_Debug *ar, int n); -LUA_API const char *lua_getupvalue(lua_State *L, int funcindex, int n); -LUA_API const char *lua_setupvalue(lua_State *L, int funcindex, int n); -LUA_API int lua_sethook(lua_State *L, lua_Hook func, int mask, int count); -LUA_API lua_Hook lua_gethook(lua_State *L); -LUA_API int lua_gethookmask(lua_State *L); -LUA_API int lua_gethookcount(lua_State *L); +LUA_API int lua_getstack(lua_State* L, int level, lua_Debug* ar); +LUA_API int lua_getinfo(lua_State* L, const char* what, lua_Debug* ar); +LUA_API const char* lua_getlocal(lua_State* L, const lua_Debug* ar, int n); +LUA_API const char* lua_setlocal(lua_State* L, const lua_Debug* ar, int n); +LUA_API const char* lua_getupvalue(lua_State* L, int funcindex, int n); +LUA_API const char* lua_setupvalue(lua_State* L, int funcindex, int n); +LUA_API int lua_sethook(lua_State* L, lua_Hook func, int mask, int count); +LUA_API lua_Hook lua_gethook(lua_State* L); +LUA_API int lua_gethookmask(lua_State* L); +LUA_API int lua_gethookcount(lua_State* L); /* From Lua 5.2. */ -LUA_API void *lua_upvalueid(lua_State *L, int idx, int n); -LUA_API void lua_upvaluejoin(lua_State *L, int idx1, int n1, int idx2, int n2); -LUA_API int lua_loadx(lua_State *L, lua_Reader reader, void *dt, - const char *chunkname, const char *mode); -LUA_API const lua_Number *lua_version(lua_State *L); -LUA_API void lua_copy(lua_State *L, int fromidx, int toidx); -LUA_API lua_Number lua_tonumberx(lua_State *L, int idx, int *isnum); -LUA_API lua_Integer lua_tointegerx(lua_State *L, int idx, int *isnum); +LUA_API void* lua_upvalueid(lua_State* L, int idx, int n); +LUA_API void lua_upvaluejoin(lua_State* L, int idx1, int n1, int idx2, int n2); +LUA_API int lua_loadx(lua_State* L, lua_Reader reader, void* dt, const char* chunkname, const char* mode); +LUA_API const lua_Number* lua_version(lua_State* L); +LUA_API void lua_copy(lua_State* L, int fromidx, int toidx); +LUA_API lua_Number lua_tonumberx(lua_State* L, int idx, int* isnum); +LUA_API lua_Integer lua_tointegerx(lua_State* L, int idx, int* isnum); /* From Lua 5.3. */ -LUA_API int lua_isyieldable(lua_State *L); +LUA_API int lua_isyieldable(lua_State* L); struct lua_Debug { int event; - const char *name; /* (n) */ - const char *namewhat; /* (n) `global', `local', `field', `method' */ - const char *what; /* (S) `Lua', `C', `main', `tail' */ - const char *source; /* (S) */ + const char* name; /* (n) */ + const char* namewhat; /* (n) `global', `local', `field', `method' */ + const char* what; /* (S) `Lua', `C', `main', `tail' */ + const char* source; /* (S) */ int currentline; /* (l) */ int nups; /* (u) number of upvalues */ int linedefined; /* (S) */ diff --git a/inc/lua/luaconf.h b/inc/lua/luaconf.h index 7ce5617..474d20b 100644 --- a/inc/lua/luaconf.h +++ b/inc/lua/luaconf.h @@ -76,9 +76,7 @@ #define LUA_PATH_MARK "?" #define LUA_EXECDIR "!" #define LUA_IGMARK "-" -#define LUA_PATH_CONFIG \ - LUA_DIRSEP "\n" LUA_PATHSEP "\n" LUA_PATH_MARK "\n" LUA_EXECDIR \ - "\n" LUA_IGMARK "\n" +#define LUA_PATH_CONFIG LUA_DIRSEP "\n" LUA_PATHSEP "\n" LUA_PATH_MARK "\n" LUA_EXECDIR "\n" LUA_IGMARK "\n" /* Quoting in error messages. */ #define LUA_QL(x) "'" x "'" @@ -143,15 +141,15 @@ #define lua_assert(x) assert(x) #endif #ifdef LUA_USE_APICHECK -#define luai_apicheck(L, o) \ - { \ - (void)L; \ - assert(o); \ +#define luai_apicheck(L, o) \ + { \ + (void)L; \ + assert(o); \ } #else -#define luai_apicheck(L, o) \ - { \ - (void)L; \ +#define luai_apicheck(L, o) \ + { \ + (void)L; \ } #endif diff --git a/inc/lua/luajit.h b/inc/lua/luajit.h index 542c25c..2ff826d 100644 --- a/inc/lua/luajit.h +++ b/inc/lua/luajit.h @@ -62,16 +62,13 @@ enum { /* LuaJIT public C API. */ /* Control the JIT engine. */ -LUA_API int luaJIT_setmode(lua_State *L, int idx, int mode); +LUA_API int luaJIT_setmode(lua_State* L, int idx, int mode); /* Low-overhead profiling API. */ -typedef void (*luaJIT_profile_callback)(void *data, lua_State *L, int samples, - int vmstate); -LUA_API void luaJIT_profile_start(lua_State *L, const char *mode, - luaJIT_profile_callback cb, void *data); -LUA_API void luaJIT_profile_stop(lua_State *L); -LUA_API const char *luaJIT_profile_dumpstack(lua_State *L, const char *fmt, - int depth, size_t *len); +typedef void (*luaJIT_profile_callback)(void* data, lua_State* L, int samples, int vmstate); +LUA_API void luaJIT_profile_start(lua_State* L, const char* mode, luaJIT_profile_callback cb, void* data); +LUA_API void luaJIT_profile_stop(lua_State* L); +LUA_API const char* luaJIT_profile_dumpstack(lua_State* L, const char* fmt, int depth, size_t* len); /* Enforce (dynamic) linker error for version mismatches. Call from main. */ LUA_API void LUAJIT_VERSION_SYM(void); diff --git a/inc/lua/lualib.h b/inc/lua/lualib.h index e7e8332..d50098b 100644 --- a/inc/lua/lualib.h +++ b/inc/lua/lualib.h @@ -22,19 +22,19 @@ #define LUA_JITLIBNAME "jit" #define LUA_FFILIBNAME "ffi" -LUALIB_API int luaopen_base(lua_State *L); -LUALIB_API int luaopen_math(lua_State *L); -LUALIB_API int luaopen_string(lua_State *L); -LUALIB_API int luaopen_table(lua_State *L); -LUALIB_API int luaopen_io(lua_State *L); -LUALIB_API int luaopen_os(lua_State *L); -LUALIB_API int luaopen_package(lua_State *L); -LUALIB_API int luaopen_debug(lua_State *L); -LUALIB_API int luaopen_bit(lua_State *L); -LUALIB_API int luaopen_jit(lua_State *L); -LUALIB_API int luaopen_ffi(lua_State *L); +LUALIB_API int luaopen_base(lua_State* L); +LUALIB_API int luaopen_math(lua_State* L); +LUALIB_API int luaopen_string(lua_State* L); +LUALIB_API int luaopen_table(lua_State* L); +LUALIB_API int luaopen_io(lua_State* L); +LUALIB_API int luaopen_os(lua_State* L); +LUALIB_API int luaopen_package(lua_State* L); +LUALIB_API int luaopen_debug(lua_State* L); +LUALIB_API int luaopen_bit(lua_State* L); +LUALIB_API int luaopen_jit(lua_State* L); +LUALIB_API int luaopen_ffi(lua_State* L); -LUALIB_API void luaL_openlibs(lua_State *L); +LUALIB_API void luaL_openlibs(lua_State* L); #ifndef lua_assert #define lua_assert(x) ((void)0) diff --git a/inc/tsfuncs/BlFuncs.cpp b/inc/tsfuncs/BlFuncs.cpp index 524e76a..7d36726 100644 --- a/inc/tsfuncs/BlFuncs.cpp +++ b/inc/tsfuncs/BlFuncs.cpp @@ -38,52 +38,53 @@ BlFunctionDefIntern(tsf_BlCon__getReturnBuffer); // C->TS Args -char *tsf_GetIntArg(signed int value) { - char *ret = tsf_BlStringStack__getArgBuffer(16); +char* tsf_GetIntArg(signed int value) { + char* ret = tsf_BlStringStack__getArgBuffer(16); snprintf(ret, 16, "%d", value); return ret; } -char *tsf_GetFloatArg(float value) { - char *ret = tsf_BlStringStack__getArgBuffer(32); + +char* tsf_GetFloatArg(float value) { + char* ret = tsf_BlStringStack__getArgBuffer(32); snprintf(ret, 32, "%g", value); return ret; } -char *tsf_GetStringArg(char *value) { + +char* tsf_GetStringArg(char* value) { int len = strlen(value) + 1; - char *ret = tsf_BlStringStack__getArgBuffer(len); + char* ret = tsf_BlStringStack__getArgBuffer(len); memcpy(ret, value, len); return ret; } -char *tsf_GetThisArg(ADDR obj) { - return tsf_GetIntArg(*(signed int *)(obj + 32)); -} + +char* tsf_GetThisArg(ADDR obj) { return tsf_GetIntArg(*(signed int*)(obj + 32)); } // Eval -const char *tsf_Eval(const char *code) { - const char *argv[] = {nullptr, code}; +const char* tsf_Eval(const char* code) { + const char* argv[] = {nullptr, code}; return tsf_BlCon__evaluate(0, 2, argv); } -const char *tsf_Evalf(const char *fmt, ...) { +const char* tsf_Evalf(const char* fmt, ...) { va_list args; char code[4096]; va_start(args, fmt); vsnprintf(code, 4096, fmt, args); va_end(args); - return tsf_Eval((const char *)code); + return tsf_Eval((const char*)code); } // Objects ADDR tsf_FindObject(unsigned int id) { - ADDR obj = *(ADDR *)(*(ADDR *)(tsf_gIdDictionary) + 4 * (id & 0xFFF)); + ADDR obj = *(ADDR*)(*(ADDR*)(tsf_gIdDictionary) + 4 * (id & 0xFFF)); if (!obj) return 0; - while (obj && *(unsigned int *)(obj + 32) != id) { - obj = *(ADDR *)(obj + 16); + while (obj && *(unsigned int*)(obj + 32) != id) { + obj = *(ADDR*)(obj + 16); if (!obj) return 0; } @@ -91,12 +92,10 @@ ADDR tsf_FindObject(unsigned int id) { return obj; } -ADDR tsf_FindObject(const char *name) { - return (ADDR)tsf_BlSim__findObject_name(name); -} +ADDR tsf_FindObject(const char* name) { return (ADDR)tsf_BlSim__findObject_name(name); } -ADDR tsf_LookupNamespace(const char *ns, const char *package) { - const char *ste_package; +ADDR tsf_LookupNamespace(const char* ns, const char* package) { + const char* ste_package; if (package) { ste_package = tsf_BlStringTable__insert(package, 0); } else { @@ -104,21 +103,19 @@ ADDR tsf_LookupNamespace(const char *ns, const char *package) { } if (ns) { - const char *ste_namespace = tsf_BlStringTable__insert(ns, 0); + const char* ste_namespace = tsf_BlStringTable__insert(ns, 0); return tsf_BlNamespace__find(ste_namespace, ste_package); } else { return tsf_BlNamespace__find(nullptr, ste_package); } } -ADDR tsf_LookupNamespace(const char *ns) { - return tsf_LookupNamespace(ns, nullptr); -} + +ADDR tsf_LookupNamespace(const char* ns) { return tsf_LookupNamespace(ns, nullptr); } // Object Fields -const char *tsf_GetDataField(ADDR simObject, const char *slotName, - const char *array) { - const char *ste_slotName; +const char* tsf_GetDataField(ADDR simObject, const char* slotName, const char* array) { + const char* ste_slotName; if (slotName) { ste_slotName = tsf_BlStringTable__insert(slotName, 0); } else { @@ -128,9 +125,8 @@ const char *tsf_GetDataField(ADDR simObject, const char *slotName, return tsf_BlSimObject__getDataField(simObject, ste_slotName, array); } -void tsf_SetDataField(ADDR simObject, const char *slotName, const char *array, - const char *value) { - const char *ste_slotName; +void tsf_SetDataField(ADDR simObject, const char* slotName, const char* array, const char* value) { + const char* ste_slotName; if (slotName) { ste_slotName = tsf_BlStringTable__insert(slotName, 0); } else { @@ -142,83 +138,103 @@ void tsf_SetDataField(ADDR simObject, const char *slotName, const char *array, // TS Global Variables -const char *tsf_GetVar(const char *name) { - return tsf_BlCon__getVariable(name); +const char* tsf_GetVar(const char* name) { return tsf_BlCon__getVariable(name); } + +void tsf_AddVarInternal(const char* name, signed int varType, void* data) { + tsf_BlDictionary__addVariable((ADDR*)tsf_gEvalState_globalVars, name, varType, data); } -void tsf_AddVarInternal(const char *name, signed int varType, void *data) { - tsf_BlDictionary__addVariable((ADDR *)tsf_gEvalState_globalVars, name, - varType, data); -} +void tsf_AddVar(const char* name, const char** data) { tsf_AddVarInternal(name, 10, data); } -void tsf_AddVar(const char *name, const char **data) { - tsf_AddVarInternal(name, 10, data); -} -void tsf_AddVar(const char *name, signed int *data) { - tsf_AddVarInternal(name, 4, data); -} -void tsf_AddVar(const char *name, float *data) { - tsf_AddVarInternal(name, 8, data); -} -void tsf_AddVar(const char *name, bool *data) { - tsf_AddVarInternal(name, 6, data); -} +void tsf_AddVar(const char* name, signed int* data) { tsf_AddVarInternal(name, 4, data); } + +void tsf_AddVar(const char* name, float* data) { tsf_AddVarInternal(name, 8, data); } + +void tsf_AddVar(const char* name, bool* data) { tsf_AddVarInternal(name, 6, data); } // TS->C Functions -ADDR tsf_AddConsoleFuncInternal(const char *pname, const char *cname, - const char *fname, signed int cbtype, - const char *usage, signed int mina, - signed int maxa) { - const char *ste_fname = tsf_BlStringTable__insert(fname, 0); +ADDR tsf_AddConsoleFuncInternal( + const char* pname, + const char* cname, + const char* fname, + signed int cbtype, + const char* usage, + signed int mina, + signed int maxa) { + const char* ste_fname = tsf_BlStringTable__insert(fname, 0); ADDR ns = tsf_LookupNamespace(cname, pname); ADDR ent = tsf_BlNamespace__createLocalEntry(ns, ste_fname); - *(signed int *)tsf_mCacheSequence += 1; - tsf_BlDataChunker__freeBlocks(*(ADDR *)tsf_mCacheAllocator); + *(signed int*)tsf_mCacheSequence += 1; + tsf_BlDataChunker__freeBlocks(*(ADDR*)tsf_mCacheAllocator); - *(const char **)(ent + 24) = usage; - *(signed int *)(ent + 16) = mina; - *(signed int *)(ent + 20) = maxa; - *(signed int *)(ent + 12) = cbtype; + *(const char**)(ent + 24) = usage; + *(signed int*)(ent + 16) = mina; + *(signed int*)(ent + 20) = maxa; + *(signed int*)(ent + 12) = cbtype; return ent; } -void tsf_AddConsoleFunc(const char *pname, const char *cname, const char *fname, - tsf_StringCallback sc, const char *usage, - signed int mina, signed int maxa) { - ADDR ent = - tsf_AddConsoleFuncInternal(pname, cname, fname, 1, usage, mina, maxa); - *(tsf_StringCallback *)(ent + 40) = sc; +void tsf_AddConsoleFunc( + const char* pname, + const char* cname, + const char* fname, + tsf_StringCallback sc, + const char* usage, + signed int mina, + signed int maxa) { + ADDR ent = tsf_AddConsoleFuncInternal(pname, cname, fname, 1, usage, mina, maxa); + *(tsf_StringCallback*)(ent + 40) = sc; } -void tsf_AddConsoleFunc(const char *pname, const char *cname, const char *fname, - tsf_IntCallback ic, const char *usage, signed int mina, - signed int maxa) { - ADDR ent = - tsf_AddConsoleFuncInternal(pname, cname, fname, 2, usage, mina, maxa); - *(tsf_IntCallback *)(ent + 40) = ic; + +void tsf_AddConsoleFunc( + const char* pname, + const char* cname, + const char* fname, + tsf_IntCallback ic, + const char* usage, + signed int mina, + signed int maxa) { + ADDR ent = tsf_AddConsoleFuncInternal(pname, cname, fname, 2, usage, mina, maxa); + *(tsf_IntCallback*)(ent + 40) = ic; } -void tsf_AddConsoleFunc(const char *pname, const char *cname, const char *fname, - tsf_FloatCallback fc, const char *usage, - signed int mina, signed int maxa) { - ADDR ent = - tsf_AddConsoleFuncInternal(pname, cname, fname, 3, usage, mina, maxa); - *(tsf_FloatCallback *)(ent + 40) = fc; + +void tsf_AddConsoleFunc( + const char* pname, + const char* cname, + const char* fname, + tsf_FloatCallback fc, + const char* usage, + signed int mina, + signed int maxa) { + ADDR ent = tsf_AddConsoleFuncInternal(pname, cname, fname, 3, usage, mina, maxa); + *(tsf_FloatCallback*)(ent + 40) = fc; } -void tsf_AddConsoleFunc(const char *pname, const char *cname, const char *fname, - tsf_VoidCallback vc, const char *usage, signed int mina, - signed int maxa) { - ADDR ent = - tsf_AddConsoleFuncInternal(pname, cname, fname, 4, usage, mina, maxa); - *(tsf_VoidCallback *)(ent + 40) = vc; + +void tsf_AddConsoleFunc( + const char* pname, + const char* cname, + const char* fname, + tsf_VoidCallback vc, + const char* usage, + signed int mina, + signed int maxa) { + ADDR ent = tsf_AddConsoleFuncInternal(pname, cname, fname, 4, usage, mina, maxa); + *(tsf_VoidCallback*)(ent + 40) = vc; } -void tsf_AddConsoleFunc(const char *pname, const char *cname, const char *fname, - tsf_BoolCallback bc, const char *usage, signed int mina, - signed int maxa) { - ADDR ent = - tsf_AddConsoleFuncInternal(pname, cname, fname, 5, usage, mina, maxa); - *(tsf_BoolCallback *)(ent + 40) = bc; + +void tsf_AddConsoleFunc( + const char* pname, + const char* cname, + const char* fname, + tsf_BoolCallback bc, + const char* usage, + signed int mina, + signed int maxa) { + ADDR ent = tsf_AddConsoleFuncInternal(pname, cname, fname, 5, usage, mina, maxa); + *(tsf_BoolCallback*)(ent + 40) = bc; } // Initialization @@ -233,49 +249,54 @@ bool tsf_InitInternal() { tsf_BlNamespace__createLocalEntry, "55 8B EC 6A FF 68 ? ? ? ? 64 A1 ? ? ? ? 50 83 EC 08 53 56 57 A1 ? ? ? ? " "33 C5 50 8D 45 F4 64 A3 ? ? ? ? 89 4D F0"); - BlScanFunctionText(tsf_BlDataChunker__freeBlocks, - "55 8B EC 6A FF 68 ? ? ? ? 64 A1 ? ? ? ? 50 51 53 56 57 " - "A1 ? ? ? ? 33 C5 50 8D 45 F4 64 A3 ? ? ? ? 8B D9 8B 33"); - BlScanFunctionText(tsf_BlCon__evaluate, - "55 8B EC 6A FF 68 ? ? ? ? 64 A1 ? ? ? ? 50 56 57 A1 ? ? " - "? ? 33 C5 50 8D 45 F4 64 A3 ? ? ? ? 8B 75 10"); - BlScanFunctionText(tsf_BlCon__executef, - "81 EC ? ? ? ? A1 ? ? ? ? 33 C4 89 84 24 ? ? ? ? 53 55 56 " - "8B B4 24 ? ? ? ? 33 C9"); - BlScanFunctionText(tsf_BlCon__executefSimObj, - "81 EC ? ? ? ? A1 ? ? ? ? 33 C4 89 84 24 ? ? ? ? 53 56 8B " - "B4 24 ? ? ? ? 33 C9"); - BlScanFunctionText(tsf_BlCon__getVariable, - "53 56 8B F1 57 85 F6 0F 84 ? ? ? ?"); + BlScanFunctionText( + tsf_BlDataChunker__freeBlocks, + "55 8B EC 6A FF 68 ? ? ? ? 64 A1 ? ? ? ? 50 51 53 56 57 " + "A1 ? ? ? ? 33 C5 50 8D 45 F4 64 A3 ? ? ? ? 8B D9 8B 33"); + BlScanFunctionText( + tsf_BlCon__evaluate, + "55 8B EC 6A FF 68 ? ? ? ? 64 A1 ? ? ? ? 50 56 57 A1 ? ? " + "? ? 33 C5 50 8D 45 F4 64 A3 ? ? ? ? 8B 75 10"); + BlScanFunctionText( + tsf_BlCon__executef, + "81 EC ? ? ? ? A1 ? ? ? ? 33 C4 89 84 24 ? ? ? ? 53 55 56 " + "8B B4 24 ? ? ? ? 33 C9"); + BlScanFunctionText( + tsf_BlCon__executefSimObj, + "81 EC ? ? ? ? A1 ? ? ? ? 33 C4 89 84 24 ? ? ? ? 53 56 8B " + "B4 24 ? ? ? ? 33 C9"); + BlScanFunctionText(tsf_BlCon__getVariable, "53 56 8B F1 57 85 F6 0F 84 ? ? ? ?"); BlScanFunctionText(tsf_BlDictionary__addVariable, "8B 44 24 04 56 57 8B F9"); BlScanFunctionText(tsf_BlSim__findObject_name, "57 8B F9 8A 17"); - BlScanFunctionText(tsf_BlStringStack__getArgBuffer, - "55 8B EC 83 E4 F8 8B 0D ? ? ? ? A1 ? ? ? ? 56 57 8B 7D " - "08 8D 14 01 03 D7 3B 15 ? ? ? ? 72 2C 8B 0D"); - BlScanFunctionText(tsf_BlSimObject__getDataField, - "51 53 8B D9 55 56 8B 74 24 14"); - BlScanFunctionText(tsf_BlSimObject__setDataField, - "81 EC ? ? ? ? A1 ? ? ? ? 33 C4 89 84 24 ? ? ? ? 8B 84 24 " - "? ? ? ? 53 8B D9 89 44 24 04"); + BlScanFunctionText( + tsf_BlStringStack__getArgBuffer, + "55 8B EC 83 E4 F8 8B 0D ? ? ? ? A1 ? ? ? ? 56 57 8B 7D " + "08 8D 14 01 03 D7 3B 15 ? ? ? ? 72 2C 8B 0D"); + BlScanFunctionText(tsf_BlSimObject__getDataField, "51 53 8B D9 55 56 8B 74 24 14"); + BlScanFunctionText( + tsf_BlSimObject__setDataField, + "81 EC ? ? ? ? A1 ? ? ? ? 33 C4 89 84 24 ? ? ? ? 8B 84 24 " + "? ? ? ? 53 8B D9 89 44 24 04"); BlScanFunctionText(tsf_BlCon__getReturnBuffer, "81 F9 ? ? ? ? 76 2B"); - ADDR BlScanText(tsf_mCacheSequenceLoc, - "FF 05 ? ? ? ? B9 ? ? ? ? 8B F8 E8 ? ? ? ? 8B 44 24 1C 89 47 " - "18 8B 44 24 14"); + ADDR BlScanText( + tsf_mCacheSequenceLoc, + "FF 05 ? ? ? ? B9 ? ? ? ? 8B F8 E8 ? ? ? ? 8B 44 24 1C 89 47 " + "18 8B 44 24 14"); ADDR BlScanText( tsf_mCacheAllocatorLoc, "89 35 ? ? ? ? C7 06 ? ? ? ? A1 ? ? ? ? 68 ? ? ? ? C7 40 ? ? ? ? ? E8 ? " "? ? ? 83 C4 04 8B 4D F4 64 89 0D ? ? ? ? 59 5E 8B E5 5D C3"); - ADDR BlScanText(tsf_gIdDictionaryLoc, - "89 15 ? ? ? ? E8 ? ? ? ? 8B F0 89 75 F0"); - ADDR BlScanText(tsf_gEvalState_globalVarsLoc, - "B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? 6A 0A 68 ? ? ? ? B9 ? ? ? " - "? E8 ? ? ? ? E8 ? ? ? ?"); + ADDR BlScanText(tsf_gIdDictionaryLoc, "89 15 ? ? ? ? E8 ? ? ? ? 8B F0 89 75 F0"); + ADDR BlScanText( + tsf_gEvalState_globalVarsLoc, + "B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? 6A 0A 68 ? ? ? ? B9 ? ? ? " + "? E8 ? ? ? ? E8 ? ? ? ?"); - tsf_mCacheSequence = *(ADDR *)(tsf_mCacheSequenceLoc + 2); - tsf_mCacheAllocator = *(ADDR *)(tsf_mCacheAllocatorLoc + 2); - tsf_gIdDictionary = *(ADDR *)(tsf_gIdDictionaryLoc + 2); - tsf_gEvalState_globalVars = *(ADDR *)(tsf_gEvalState_globalVarsLoc + 1); + tsf_mCacheSequence = *(ADDR*)(tsf_mCacheSequenceLoc + 2); + tsf_mCacheAllocator = *(ADDR*)(tsf_mCacheAllocatorLoc + 2); + tsf_gIdDictionary = *(ADDR*)(tsf_gIdDictionaryLoc + 2); + tsf_gEvalState_globalVars = *(ADDR*)(tsf_gEvalState_globalVarsLoc + 1); return true; } diff --git a/inc/tsfuncs/BlFuncs.hpp b/inc/tsfuncs/BlFuncs.hpp index c9c0d32..a50661e 100644 --- a/inc/tsfuncs/BlFuncs.hpp +++ b/inc/tsfuncs/BlFuncs.hpp @@ -10,53 +10,44 @@ #include "BlHooks.hpp" #endif -typedef const char *(*tsf_StringCallback)(ADDR, signed int, const char *[]); -typedef signed int (*tsf_IntCallback)(ADDR, signed int, const char *[]); -typedef float (*tsf_FloatCallback)(ADDR, signed int, const char *[]); -typedef void (*tsf_VoidCallback)(ADDR, signed int, const char *[]); -typedef bool (*tsf_BoolCallback)(ADDR, signed int, const char *[]); +typedef const char* (*tsf_StringCallback)(ADDR, signed int, const char*[]); +typedef signed int (*tsf_IntCallback)(ADDR, signed int, const char*[]); +typedef float (*tsf_FloatCallback)(ADDR, signed int, const char*[]); +typedef void (*tsf_VoidCallback)(ADDR, signed int, const char*[]); +typedef bool (*tsf_BoolCallback)(ADDR, signed int, const char*[]); /* These functions are used for tsf_BlCon__executefSimObj. They refer to a special buffer for the argument stack. For tsf_BlCon__executef, you need to use your own buffers. */ -char *tsf_GetIntArg(int); -char *tsf_GetFloatArg(float); -char *tsf_ScriptThis(ADDR); +char* tsf_GetIntArg(int); +char* tsf_GetFloatArg(float); +char* tsf_ScriptThis(ADDR); -const char *tsf_Eval(const char *); -const char *tsf_Evalf(const char *, ...); +const char* tsf_Eval(const char*); +const char* tsf_Evalf(const char*, ...); ADDR tsf_FindObject(unsigned int); -ADDR tsf_FindObject(const char *); +ADDR tsf_FindObject(const char*); -ADDR tsf_LookupNamespace(const char *, const char *); +ADDR tsf_LookupNamespace(const char*, const char*); -const char *tsf_GetDataField(ADDR, const char *, const char *); -void tsf_SetDataField(ADDR, const char *, const char *, const char *); +const char* tsf_GetDataField(ADDR, const char*, const char*); +void tsf_SetDataField(ADDR, const char*, const char*, const char*); -const char *tsf_GetVar(const char *); +const char* tsf_GetVar(const char*); -void tsf_AddVarInternal(const char *, signed int, void *); -void tsf_AddVar(const char *, const char **); -void tsf_AddVar(const char *, signed int *); -void tsf_AddVar(const char *, float *); -void tsf_AddVar(const char *, bool *); +void tsf_AddVarInternal(const char*, signed int, void*); +void tsf_AddVar(const char*, const char**); +void tsf_AddVar(const char*, signed int*); +void tsf_AddVar(const char*, float*); +void tsf_AddVar(const char*, bool*); -ADDR tsf_AddConsoleFuncInternal(const char *, const char *, const char *, - signed int, const char *, signed int, - signed int); -void tsf_AddConsoleFunc(const char *, const char *, const char *, - tsf_StringCallback, const char *, signed int, - signed int); -void tsf_AddConsoleFunc(const char *, const char *, const char *, - tsf_IntCallback, const char *, signed int, signed int); -void tsf_AddConsoleFunc(const char *, const char *, const char *, - tsf_FloatCallback, const char *, signed int, - signed int); -void tsf_AddConsoleFunc(const char *, const char *, const char *, - tsf_VoidCallback, const char *, signed int, signed int); -void tsf_AddConsoleFunc(const char *, const char *, const char *, - tsf_BoolCallback, const char *, signed int, signed int); +ADDR tsf_AddConsoleFuncInternal(const char*, const char*, const char*, signed int, const char*, signed int, signed int); +void tsf_AddConsoleFunc(const char*, const char*, const char*, tsf_StringCallback, const char*, signed int, signed int); +void tsf_AddConsoleFunc(const char*, const char*, const char*, tsf_IntCallback, const char*, signed int, signed int); +void tsf_AddConsoleFunc(const char*, const char*, const char*, tsf_FloatCallback, const char*, signed int, signed int); +void tsf_AddConsoleFunc(const char*, const char*, const char*, tsf_VoidCallback, const char*, signed int, signed int); +void tsf_AddConsoleFunc(const char*, const char*, const char*, tsf_BoolCallback, const char*, signed int, signed int); bool tsf_InitInternal(); @@ -65,32 +56,20 @@ extern ADDR tsf_mCacheAllocator; extern ADDR tsf_gIdDictionary; extern ADDR tsf_gEvalState_globalVars; -BlFunctionDefExtern(const char *, __stdcall, tsf_BlStringTable__insert, - const char *, bool); -BlFunctionDefExtern(ADDR, __fastcall, tsf_BlNamespace__find, const char *, - const char *); -BlFunctionDefExtern(ADDR, __thiscall, tsf_BlNamespace__createLocalEntry, ADDR, - const char *); +BlFunctionDefExtern(const char*, __stdcall, tsf_BlStringTable__insert, const char*, bool); +BlFunctionDefExtern(ADDR, __fastcall, tsf_BlNamespace__find, const char*, const char*); +BlFunctionDefExtern(ADDR, __thiscall, tsf_BlNamespace__createLocalEntry, ADDR, const char*); BlFunctionDefExtern(void, __thiscall, tsf_BlDataChunker__freeBlocks, ADDR); -BlFunctionDefExtern(const char *, , tsf_BlCon__evaluate, ADDR, signed int, - const char **); -BlFunctionDefExtern(const char *, , tsf_BlCon__executef, signed int, ...); -BlFunctionDefExtern(const char *, , tsf_BlCon__executefSimObj, ADDR *, - signed int, ...); -BlFunctionDefExtern(const char *, __thiscall, tsf_BlCon__getVariable, - const char *); -BlFunctionDefExtern(void, __thiscall, tsf_BlDictionary__addVariable, ADDR *, - const char *, signed int, void *); -BlFunctionDefExtern(ADDR *, __thiscall, tsf_BlSim__findObject_name, - const char *); -BlFunctionDefExtern(char *, __stdcall, tsf_BlStringStack__getArgBuffer, - unsigned int); -BlFunctionDefExtern(const char *, __thiscall, tsf_BlSimObject__getDataField, - ADDR, const char *, const char *); -BlFunctionDefExtern(void, __thiscall, tsf_BlSimObject__setDataField, ADDR, - const char *, const char *, const char *); -BlFunctionDefExtern(char *, __fastcall, tsf_BlCon__getReturnBuffer, - unsigned int); +BlFunctionDefExtern(const char*, , tsf_BlCon__evaluate, ADDR, signed int, const char**); +BlFunctionDefExtern(const char*, , tsf_BlCon__executef, signed int, ...); +BlFunctionDefExtern(const char*, , tsf_BlCon__executefSimObj, ADDR*, signed int, ...); +BlFunctionDefExtern(const char*, __thiscall, tsf_BlCon__getVariable, const char*); +BlFunctionDefExtern(void, __thiscall, tsf_BlDictionary__addVariable, ADDR*, const char*, signed int, void*); +BlFunctionDefExtern(ADDR*, __thiscall, tsf_BlSim__findObject_name, const char*); +BlFunctionDefExtern(char*, __stdcall, tsf_BlStringStack__getArgBuffer, unsigned int); +BlFunctionDefExtern(const char*, __thiscall, tsf_BlSimObject__getDataField, ADDR, const char*, const char*); +BlFunctionDefExtern(void, __thiscall, tsf_BlSimObject__setDataField, ADDR, const char*, const char*, const char*); +BlFunctionDefExtern(char*, __fastcall, tsf_BlCon__getReturnBuffer, unsigned int); // Function short names @@ -100,7 +79,7 @@ BlFunctionDefExtern(char *, __fastcall, tsf_BlCon__getReturnBuffer, #define BlIntArg tsf_GetIntArg #define BlFloatArg tsf_GetFloatArg #define BlThisArg tsf_GetThisArg -#define BlStringArg(x) tsf_GetStringArg((char *)x) +#define BlStringArg(x) tsf_GetStringArg((char*)x) #define BlReturnBuffer tsf_BlCon__getReturnBuffer #define BlObject tsf_FindObject @@ -114,23 +93,21 @@ BlFunctionDefExtern(char *, __fastcall, tsf_BlCon__getReturnBuffer, #define BlAddFunction tsf_AddConsoleFunc -#define __22ND_ARGUMENT(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, \ - a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, ...) \ +#define __22ND_ARGUMENT( \ + a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, ...) \ a22 -#define __NUM_LIST(...) \ - __22ND_ARGUMENT(dummy, ##__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, \ - 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#define __NUM_LIST(...) \ + __22ND_ARGUMENT(dummy, ##__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) #define BlCall(...) tsf_BlCon__executef(__NUM_LIST(__VA_ARGS__), __VA_ARGS__) -#define BlCallObj(obj, ...) \ - tsf_BlCon__executefSimObj((ADDR *)obj, __NUM_LIST(__VA_ARGS__), __VA_ARGS__) +#define BlCallObj(obj, ...) tsf_BlCon__executefSimObj((ADDR*)obj, __NUM_LIST(__VA_ARGS__), __VA_ARGS__) -#define BlFuncsInit() \ - if (!tsf_InitInternal()) { \ - return false; \ +#define BlFuncsInit() \ + if (!tsf_InitInternal()) { \ + return false; \ } -#define BlFuncsDeinit() \ - if (!tsf_DeinitInternal()) { \ - return false; \ +#define BlFuncsDeinit() \ + if (!tsf_DeinitInternal()) { \ + return false; \ } #endif diff --git a/inc/tsfuncs/BlHooks.cpp b/inc/tsfuncs/BlHooks.cpp index b29d922..342de70 100644 --- a/inc/tsfuncs/BlHooks.cpp +++ b/inc/tsfuncs/BlHooks.cpp @@ -27,14 +27,13 @@ void tsh_i_InitScanner() { HMODULE module = GetModuleHandle(NULL); if (module) { MODULEINFO info; - GetModuleInformation(GetCurrentProcess(), module, &info, - sizeof(MODULEINFO)); + GetModuleInformation(GetCurrentProcess(), module, &info, sizeof(MODULEINFO)); ImageBase = (ADDR)info.lpBaseOfDll; ImageSize = info.SizeOfImage; } } -bool tsh_i_CompareData(BYTE *data, BYTE *pattern, char *mask) { +bool tsh_i_CompareData(BYTE* data, BYTE* pattern, char* mask) { for (; *mask; ++data, ++pattern, ++mask) { if (*mask == 'x' && *data != *pattern) return false; @@ -42,8 +41,7 @@ bool tsh_i_CompareData(BYTE *data, BYTE *pattern, char *mask) { return (*mask) == 0; } -ADDR tsh_i_FindPattern(ADDR imageBase, ADDR imageSize, BYTE *pattern, - char *mask) { +ADDR tsh_i_FindPattern(ADDR imageBase, ADDR imageSize, BYTE* pattern, char* mask) { for (ADDR i = imageBase; i < imageBase + imageSize; i++) { if (tsh_i_CompareData((PBYTE)i, pattern, mask)) { return i; @@ -53,10 +51,10 @@ ADDR tsh_i_FindPattern(ADDR imageBase, ADDR imageSize, BYTE *pattern, } // Convert a text-style pattern into code-style -void tsh_i_PatternTextToCode(char *text, char **opatt, char **omask) { +void tsh_i_PatternTextToCode(char* text, char** opatt, char** omask) { unsigned int len = strlen(text); - char *patt = (char *)malloc(len); - char *mask = (char *)malloc(len); + char* patt = (char*)malloc(len); + char* mask = (char*)malloc(len); int outidx = 0; int val = 0; @@ -93,15 +91,14 @@ void tsh_i_PatternTextToCode(char *text, char **opatt, char **omask) { // Public functions for sig scanning // Scan using code-style pattern -ADDR tsh_ScanCode(char *pattern, char *mask) { - return tsh_i_FindPattern(ImageBase, ImageSize - strlen(mask), (BYTE *)pattern, - mask); +ADDR tsh_ScanCode(char* pattern, char* mask) { + return tsh_i_FindPattern(ImageBase, ImageSize - strlen(mask), (BYTE*)pattern, mask); } // Scan using a text-style pattern -ADDR tsh_ScanText(char *text) { - char *patt; - char *mask; +ADDR tsh_ScanText(char* text) { + char* patt; + char* mask; tsh_i_PatternTextToCode(text, &patt, &mask); ADDR res = tsh_ScanCode(patt, mask); @@ -119,54 +116,46 @@ ADDR tsh_ScanText(char *text) { void tsh_DeprotectAddress(ADDR length, ADDR location) { DWORD oldProtection; - VirtualProtect((void *)location, length, PAGE_EXECUTE_READWRITE, - &oldProtection); + VirtualProtect((void*)location, length, PAGE_EXECUTE_READWRITE, &oldProtection); // tsh_DeprotectedAddresses[location] = {length, oldProtection}; } // Patch a string of bytes by deprotecting and then overwriting -void tsh_PatchBytes(ADDR length, ADDR location, BYTE *repl) { +void tsh_PatchBytes(ADDR length, ADDR location, BYTE* repl) { tsh_DeprotectAddress(length, location); - memcpy((void *)location, (void *)repl, (size_t)length); + memcpy((void*)location, (void*)repl, (size_t)length); } -void tsh_PatchByte(ADDR location, BYTE value) { - tsh_PatchBytes(location, 1, &value); -} +void tsh_PatchByte(ADDR location, BYTE value) { tsh_PatchBytes(location, 1, &value); } -void tsh_ReplaceInt(ADDR addr, int rval) { - tsh_PatchBytes(4, addr, (BYTE *)(&rval)); -} +void tsh_ReplaceInt(ADDR addr, int rval) { tsh_PatchBytes(4, addr, (BYTE*)(&rval)); } int tsh_i_CallOffset(ADDR instr, ADDR func) { return func - (instr + 4); } -void tsh_i_ReplaceCall(ADDR instr, ADDR target) { - tsh_ReplaceInt(instr, tsh_i_CallOffset(instr, target)); -} +void tsh_i_ReplaceCall(ADDR instr, ADDR target) { tsh_ReplaceInt(instr, tsh_i_CallOffset(instr, target)); } void tsh_i_PatchCopy(ADDR dest, ADDR src, unsigned int len) { for (unsigned int i = 0; i < len; i++) { - tsh_PatchByte(dest + i, *((BYTE *)(src + i))); + tsh_PatchByte(dest + i, *((BYTE*)(src + i))); } } -void tsh_HookFunction(ADDR victim, ADDR detour, BYTE *origbytes) { - memcpy(origbytes, (BYTE *)victim, 6); // save old data +void tsh_HookFunction(ADDR victim, ADDR detour, BYTE* origbytes) { + memcpy(origbytes, (BYTE*)victim, 6); // save old data - *(BYTE *)victim = 0xE9; // jmp rel32 - *(ADDR *)(victim + 1) = (detour - (victim + 5)); // jump offset - *(BYTE *)(victim + 5) = 0xC3; // retn + *(BYTE*)victim = 0xE9; // jmp rel32 + *(ADDR*)(victim + 1) = (detour - (victim + 5)); // jump offset + *(BYTE*)(victim + 5) = 0xC3; // retn } -void tsh_UnhookFunction(ADDR victim, BYTE *origbytes) { +void tsh_UnhookFunction(ADDR victim, BYTE* origbytes) { tsh_i_PatchCopy(victim, (ADDR)origbytes, 6); // restore old data } -int tsh_PatchAllMatchesCode(ADDR len, char *patt, char *mask, char *replace, - bool debugprint) { +int tsh_PatchAllMatchesCode(ADDR len, char* patt, char* mask, char* replace, bool debugprint) { int numpatched = 0; for (ADDR i = ImageBase; i < ImageBase + ImageSize - len; i++) { - if (tsh_i_CompareData((BYTE *)i, (BYTE *)patt, mask)) { + if (tsh_i_CompareData((BYTE*)i, (BYTE*)patt, mask)) { if (debugprint) BlPrintf("RedoBlHooks: Patching call at %08x", i); @@ -180,10 +169,9 @@ int tsh_PatchAllMatchesCode(ADDR len, char *patt, char *mask, char *replace, return numpatched; } -int tsh_PatchAllMatchesHex(ADDR len, char *text, char *replace, - bool debugprint) { - char *patt; - char *mask; +int tsh_PatchAllMatchesHex(ADDR len, char* text, char* replace, bool debugprint) { + char* patt; + char* mask; tsh_i_PatternTextToCode(text, &patt, &mask); int res = tsh_PatchAllMatchesCode(len, patt, mask, replace, debugprint); @@ -199,9 +187,7 @@ int tsh_PatchAllMatchesHex(ADDR len, char *text, char *replace, bool tsh_InitInternal() { tsh_i_InitScanner(); - BlScanFunctionText( - tsh_BlPrintf, - "8D 44 24 08 33 D2 50 FF 74 24 08 33 C9 E8 ? ? ? ? 83 C4 08 C3"); + BlScanFunctionText(tsh_BlPrintf, "8D 44 24 08 33 D2 50 FF 74 24 08 33 C9 E8 ? ? ? ? 83 C4 08 C3"); return true; } diff --git a/inc/tsfuncs/BlHooks.hpp b/inc/tsfuncs/BlHooks.hpp index 6178eee..6f1d0e8 100644 --- a/inc/tsfuncs/BlHooks.hpp +++ b/inc/tsfuncs/BlHooks.hpp @@ -14,13 +14,13 @@ typedef unsigned int ADDR; bool tsh_InitInternal(); bool tsh_DeinitInternal(); -ADDR tsh_ScanCode(char *, char *); -ADDR tsh_ScanText(char *); -void tsh_HookFunction(ADDR, ADDR, BYTE *); -void tsh_UnhookFunction(ADDR, BYTE *); -int tsh_PatchAllMatches(unsigned int, char *, char *, char *, bool); +ADDR tsh_ScanCode(char*, char*); +ADDR tsh_ScanText(char*); +void tsh_HookFunction(ADDR, ADDR, BYTE*); +void tsh_UnhookFunction(ADDR, BYTE*); +int tsh_PatchAllMatches(unsigned int, char*, char*, char*, bool); void tsh_PatchByte(ADDR, BYTE); -void tsh_PatchBytes(unsigned int, ADDR, BYTE *); +void tsh_PatchBytes(unsigned int, ADDR, BYTE*); void tsh_PatchInt(ADDR, int); // Debug print settings @@ -34,13 +34,13 @@ void tsh_PatchInt(ADDR, int); // Function short names // Use in code when the def is not shared in a header -#define BlFunctionDef(returnType, convention, name, ...) \ - typedef returnType(convention *tsh_##name##FnT)(__VA_ARGS__); \ +#define BlFunctionDef(returnType, convention, name, ...) \ + typedef returnType(convention* tsh_##name##FnT)(__VA_ARGS__); \ tsh_##name##FnT name; // Use in header for shared function defs when a BlFunctionDefIntern exists in // code -#define BlFunctionDefExtern(returnType, convention, name, ...) \ - typedef returnType(convention *tsh_##name##FnT)(__VA_ARGS__); \ +#define BlFunctionDefExtern(returnType, convention, name, ...) \ + typedef returnType(convention* tsh_##name##FnT)(__VA_ARGS__); \ extern tsh_##name##FnT name; // Use in code for shared function defs when a BlFunctionDefExtern exists in // header @@ -48,94 +48,84 @@ void tsh_PatchInt(ADDR, int); // Scan for and assign the pattern to the variable, or err and return if not // found -#define BlScanFunctionCode(target, patt, mask) \ - target = (tsh_##target##FnT)tsh_ScanCode((char *)patt, (char *)mask); \ - if (!target) { \ - BlPrintf("RedoBlHooks | Cannot find function " #target "!"); \ - return false; \ - } else { \ - if (tsh_DEBUGPRINT) \ - BlPrintf("RedoBlHooks | Found function " #target " at %08x", \ - (int)target); \ +#define BlScanFunctionCode(target, patt, mask) \ + target = (tsh_##target##FnT)tsh_ScanCode((char*)patt, (char*)mask); \ + if (!target) { \ + BlPrintf("RedoBlHooks | Cannot find function " #target "!"); \ + return false; \ + } else { \ + if (tsh_DEBUGPRINT) \ + BlPrintf("RedoBlHooks | Found function " #target " at %08x", (int)target); \ } -#define BlScanFunctionText(target, text) \ - target = (tsh_##target##FnT)tsh_ScanText((char *)text); \ - if (!target) { \ - BlPrintf("RedoBlHooks | Cannot find function " #target "!"); \ - return false; \ - } else { \ - if (tsh_DEBUGPRINT) \ - BlPrintf("RedoBlHooks | Found function " #target " at %08x", \ - (int)target); \ +#define BlScanFunctionText(target, text) \ + target = (tsh_##target##FnT)tsh_ScanText((char*)text); \ + if (!target) { \ + BlPrintf("RedoBlHooks | Cannot find function " #target "!"); \ + return false; \ + } else { \ + if (tsh_DEBUGPRINT) \ + BlPrintf("RedoBlHooks | Found function " #target " at %08x", (int)target); \ } -#define BlScanCode(target, patt, mask) \ - target = tsh_ScanCode((char *)patt, (char *)mask); \ - if (!target) { \ - BlPrintf("RedoBlHooks | Cannot find pattern " #target "!"); \ - return false; \ - } else { \ - if (tsh_DEBUGPRINT) \ - BlPrintf("RedoBlHooks | Found " #target " at %08x", (int)target); \ +#define BlScanCode(target, patt, mask) \ + target = tsh_ScanCode((char*)patt, (char*)mask); \ + if (!target) { \ + BlPrintf("RedoBlHooks | Cannot find pattern " #target "!"); \ + return false; \ + } else { \ + if (tsh_DEBUGPRINT) \ + BlPrintf("RedoBlHooks | Found " #target " at %08x", (int)target); \ } -#define BlScanText(target, text) \ - target = tsh_ScanText((char *)text); \ - if (!target) { \ - BlPrintf("RedoBlHooks | Cannot find " #target "!"); \ - return false; \ - } else { \ - if (tsh_DEBUGPRINT) \ - BlPrintf("RedoBlHooks | Found " #target " at %08x", (int)target); \ +#define BlScanText(target, text) \ + target = tsh_ScanText((char*)text); \ + if (!target) { \ + BlPrintf("RedoBlHooks | Cannot find " #target "!"); \ + return false; \ + } else { \ + if (tsh_DEBUGPRINT) \ + BlPrintf("RedoBlHooks | Found " #target " at %08x", (int)target); \ } // Use in code to define the data and functions for hooking a function -#define BlFunctionHookDef(func) \ - BYTE tsh_BlFunctionHook##func##Data[6]; \ - void func##HookOn() { \ - tsh_HookFunction((ADDR)func, (ADDR)func##Hook, \ - tsh_BlFunctionHook##func##Data); \ - } \ - void func##HookOff() { \ - tsh_UnhookFunction((ADDR)func, tsh_BlFunctionHook##func##Data); \ - } +#define BlFunctionHookDef(func) \ + BYTE tsh_BlFunctionHook##func##Data[6]; \ + void func##HookOn() { tsh_HookFunction((ADDR)func, (ADDR)func##Hook, tsh_BlFunctionHook##func##Data); } \ + void func##HookOff() { tsh_UnhookFunction((ADDR)func, tsh_BlFunctionHook##func##Data); } // Use in code to initialize the hook once -#define BlFunctionHookInit(func) \ - tsh_DeprotectAddress(6, (ADDR)func); \ +#define BlFunctionHookInit(func) \ + tsh_DeprotectAddress(6, (ADDR)func); \ func##HookOn(); // Replace all matches of the pattern with the given byte string -#define BlPatchAllMatchesCode(len, patt, mask, repl) \ - tsh_PatchAllMatchesCode((ADDR)len, (char *)patt, (char *)mask, (char *)repl, \ - tsh_DEBUGPRINT); -#define BlPatchAllMatchesText(len, text, repl) \ - tsh_PatchAllMatchesCode((ADDR)len, (char *)text, (char *)repl, \ - tsh_DEBUGPRINT); +#define BlPatchAllMatchesCode(len, patt, mask, repl) \ + tsh_PatchAllMatchesCode((ADDR)len, (char*)patt, (char*)mask, (char*)repl, tsh_DEBUGPRINT); +#define BlPatchAllMatchesText(len, text, repl) \ + tsh_PatchAllMatchesCode((ADDR)len, (char*)text, (char*)repl, tsh_DEBUGPRINT); // Deprotect and replace one byte #define BlPatchByte(addr, byte) tsh_PatchByte((ADDR)addr, (BYTE)byte); // Deprotect and replace a byte string -#define BlPatchBytes(len, addr, repl) \ - tsh_PatchBytes((ADDR)len, (ADDR)addr, (BYTE *)repl); +#define BlPatchBytes(len, addr, repl) tsh_PatchBytes((ADDR)len, (ADDR)addr, (BYTE*)repl); // BlPrintf(char* format, ...) -#define BlPrintf(...) \ - if (tsh_BlPrintf) { \ - tsh_BlPrintf(__VA_ARGS__); \ +#define BlPrintf(...) \ + if (tsh_BlPrintf) { \ + tsh_BlPrintf(__VA_ARGS__); \ } // BlHooksInit() -> bool: success -#define BlHooksInit() \ - if (!tsh_InitInternal()) { \ - BlPrintf("BlHooksInit failed"); \ - return false; \ +#define BlHooksInit() \ + if (!tsh_InitInternal()) { \ + BlPrintf("BlHooksInit failed"); \ + return false; \ } // BlHooksDeinit() -> bool: success -#define BlHooksDeinit() \ - if (!tsh_DeinitInternal()) { \ - BlPrintf("BlHooksDeinit failed"); \ - return false; \ +#define BlHooksDeinit() \ + if (!tsh_DeinitInternal()) { \ + BlPrintf("BlHooksDeinit failed"); \ + return false; \ } // Scanned structures -BlFunctionDefExtern(void, , tsh_BlPrintf, const char *, ...); +BlFunctionDefExtern(void, , tsh_BlPrintf, const char*, ...); #endif diff --git a/src/bllua4.cpp b/src/bllua4.cpp index 01bcf36..7286212 100644 --- a/src/bllua4.cpp +++ b/src/bllua4.cpp @@ -14,7 +14,7 @@ #include "luainterp.cpp" #include "lualibts.cpp" -lua_State *gL; +lua_State* gL; #include "tsliblua.cpp" // Global variables @@ -22,9 +22,9 @@ lua_State *gL; // Setup // Hack to encode the contents of text files as static strings -#define INCLUDE_BIN(varname, filename) \ - asm("_" #varname ": .incbin \"" filename "\""); \ - asm(".byte 0"); \ +#define INCLUDE_BIN(varname, filename) \ + asm("_" #varname ": .incbin \"" filename "\""); \ + asm(".byte 0"); \ extern char varname[]; INCLUDE_BIN(bll_fileLuaEnvSafe, "lua-env-safe.lua"); INCLUDE_BIN(bll_fileLuaEnv, "lua-env.lua"); @@ -39,10 +39,10 @@ INCLUDE_BIN(bll_fileLuaLibblTypes, "util/libbl-types.lua"); INCLUDE_BIN(bll_fileTsLibblSupport, "util/libbl-support.cs"); INCLUDE_BIN(bll_fileLoadaddons, "util/loadaddons.cs"); -#define BLL_LOAD_LUA(lstate, vname) \ - if (!bll_LuaEval(lstate, vname)) { \ - BlPrintf(" Error executing " #vname); \ - return false; \ +#define BLL_LOAD_LUA(lstate, vname) \ + if (!bll_LuaEval(lstate, vname)) { \ + BlPrintf(" Error executing " #vname); \ + return false; \ } bool init() { @@ -65,9 +65,8 @@ bool init() { #endif // Expose Lua API to TS - BlAddFunction(NULL, NULL, "_bllua_luacall", bll_ts_luacall, - "LuaCall(name, ...) - Call Lua function and return result", 2, - 20); + BlAddFunction( + NULL, NULL, "_bllua_luacall", bll_ts_luacall, "LuaCall(name, ...) - Call Lua function and return result", 2, 20); BlEval(bll_fileTsEnv); // Load utilities @@ -103,7 +102,7 @@ bool deinit() { return true; } -bool __stdcall DllMain(HINSTANCE hinstance, DWORD reason, void *reserved) { +bool __stdcall DllMain(HINSTANCE hinstance, DWORD reason, void* reserved) { switch (reason) { case DLL_PROCESS_ATTACH: return init(); diff --git a/src/luainterp.cpp b/src/luainterp.cpp index 8b3aa34..ca542e1 100644 --- a/src/luainterp.cpp +++ b/src/luainterp.cpp @@ -6,7 +6,7 @@ #include "lua.h" #include -int bll_error_handler(lua_State *L) { +int bll_error_handler(lua_State* L) { lua_getfield(L, LUA_GLOBALSINDEX, "_bllua_on_error"); if (!lua_isfunction(L, -1)) { BlPrintf(" Lua error handler: _bllua_on_error not defined."); @@ -21,7 +21,8 @@ int bll_error_handler(lua_State *L) { lua_pcall(L, 1, 1, 0); return 1; } -int bll_pcall(lua_State *L, int nargs, int nret) { + +int bll_pcall(lua_State* L, int nargs, int nret) { // calculate stack position for message handler int hpos = lua_gettop(L) - nargs; // push custom error message handler @@ -36,7 +37,7 @@ int bll_pcall(lua_State *L, int nargs, int nret) { } // Display the last Lua error in the BL console -void bll_printError(lua_State *L, const char *operation, const char *item) { +void bll_printError(lua_State* L, const char* operation, const char* item) { // error_handler(L); BlPrintf("\x03Lua error: %s", lua_tostring(L, -1)); BlPrintf("\x03 (%s: %s)", operation, item); @@ -44,7 +45,7 @@ void bll_printError(lua_State *L, const char *operation, const char *item) { } // Eval a string of Lua code -bool bll_LuaEval(lua_State *L, const char *str) { +bool bll_LuaEval(lua_State* L, const char* str) { if (luaL_loadbuffer(L, str, strlen(str), "input") || bll_pcall(L, 0, 1)) { bll_printError(L, "eval", str); return false; @@ -55,9 +56,10 @@ bool bll_LuaEval(lua_State *L, const char *str) { // Convert a Lua stack entry into a string for providing to TS // Use static buffer to avoid excessive malloc char bll_arg_buffer[BLL_ARG_COUNT][BLL_ARG_MAX]; -bool bll_toarg(lua_State *L, char *buf, int i, bool err) { + +bool bll_toarg(lua_State* L, char* buf, int i, bool err) { if (lua_isstring(L, i)) { - const char *str = lua_tostring(L, i); + const char* str = lua_tostring(L, i); if (strlen(str) >= BLL_ARG_MAX) { if (err) luaL_error(L, "argument to TS is too long - max length is 8192"); diff --git a/src/luainterp.hpp b/src/luainterp.hpp index 361c204..d503a0a 100644 --- a/src/luainterp.hpp +++ b/src/luainterp.hpp @@ -8,10 +8,10 @@ #define BLL_ARG_MAX 8192 extern char bll_arg_buffer[BLL_ARG_COUNT][BLL_ARG_MAX]; -bool bll_toarg(lua_State *L, char *buf, int i, bool err); -int bll_pcall(lua_State *L, int nargs, int nret); -void bll_printError(lua_State *L, const char *operation, const char *item); +bool bll_toarg(lua_State* L, char* buf, int i, bool err); +int bll_pcall(lua_State* L, int nargs, int nret); +void bll_printError(lua_State* L, const char* operation, const char* item); -extern lua_State *gL; +extern lua_State* gL; #endif diff --git a/src/lualibts.cpp b/src/lualibts.cpp index 7f2ce21..be2e8e7 100644 --- a/src/lualibts.cpp +++ b/src/lualibts.cpp @@ -9,8 +9,7 @@ #include "lua.h" #include "luainterp.hpp" -int bll_TsCall(lua_State *L, const char *oname, const char *fname, int argc, - int ofs) { +int bll_TsCall(lua_State* L, const char* oname, const char* fname, int argc, int ofs) { ADDR obj = (ADDR)NULL; if (oname) { obj = BlObject(oname); @@ -23,15 +22,15 @@ int bll_TsCall(lua_State *L, const char *oname, const char *fname, int argc, return luaL_error(L, "Lua->TS call: Too many arguments (Max is 19)"); } - char *argv[BLL_ARG_COUNT]; + char* argv[BLL_ARG_COUNT]; for (int i = 0; i < argc; i++) { - char *argbuf = bll_arg_buffer[i]; + char* argbuf = bll_arg_buffer[i]; argv[i] = argbuf; bll_toarg(L, argbuf, i + ofs + 1, true); } // /:^| / - const char *res; + const char* res; if (obj) { switch (argc) { case 0: @@ -56,71 +55,65 @@ int bll_TsCall(lua_State *L, const char *oname, const char *fname, int argc, res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4]); break; case 7: - res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], - argv[5]); + res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); break; case 8: - res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], - argv[5], argv[6]); + res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]); break; case 9: - res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], - argv[5], argv[6], argv[7]); + res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7]); break; case 10: - res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], - argv[5], argv[6], argv[7], argv[8]); + res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8]); break; case 11: - res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], - argv[5], argv[6], argv[7], argv[8], argv[9]); + res = BlCallObj( + obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9]); break; case 12: - res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], - argv[5], argv[6], argv[7], argv[8], argv[9], argv[10]); + res = BlCallObj( + obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], + argv[10]); break; case 13: - res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], - argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], - argv[11]); + res = BlCallObj( + obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], + argv[10], argv[11]); break; case 14: - res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], - argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], - argv[11], argv[12]); + res = BlCallObj( + obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], + argv[10], argv[11], argv[12]); break; case 15: - res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], - argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], - argv[11], argv[12], argv[13]); + res = BlCallObj( + obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], + argv[10], argv[11], argv[12], argv[13]); break; case 16: - res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], - argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], - argv[11], argv[12], argv[13], argv[14]); + res = BlCallObj( + obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], + argv[10], argv[11], argv[12], argv[13], argv[14]); break; case 17: - res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], - argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], - argv[11], argv[12], argv[13], argv[14], argv[15]); + res = BlCallObj( + obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], + argv[10], argv[11], argv[12], argv[13], argv[14], argv[15]); break; case 18: - res = - BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], - argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], - argv[11], argv[12], argv[13], argv[14], argv[15], argv[16]); + res = BlCallObj( + obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], + argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16]); break; case 19: - res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], - argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], - argv[11], argv[12], argv[13], argv[14], argv[15], - argv[16], argv[17]); + res = BlCallObj( + obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], + argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17]); break; case 20: - res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], - argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], - argv[11], argv[12], argv[13], argv[14], argv[15], - argv[16], argv[17], argv[18]); + res = BlCallObj( + obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], + argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], argv[18]); break; default: res = ""; @@ -150,64 +143,60 @@ int bll_TsCall(lua_State *L, const char *oname, const char *fname, int argc, res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); break; case 7: - res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], - argv[6]); + res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]); break; case 8: - res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], - argv[6], argv[7]); + res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7]); break; case 9: - res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], - argv[6], argv[7], argv[8]); + res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8]); break; case 10: - res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], - argv[6], argv[7], argv[8], argv[9]); + res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9]); break; case 11: - res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], - argv[6], argv[7], argv[8], argv[9], argv[10]); + res = BlCall( + fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10]); break; case 12: - res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], - argv[6], argv[7], argv[8], argv[9], argv[10], argv[11]); + res = BlCall( + fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], + argv[11]); break; case 13: - res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], - argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], - argv[12]); + res = BlCall( + fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], + argv[11], argv[12]); break; case 14: - res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], - argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], - argv[12], argv[13]); + res = BlCall( + fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], + argv[11], argv[12], argv[13]); break; case 15: - res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], - argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], - argv[12], argv[13], argv[14]); + res = BlCall( + fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], + argv[11], argv[12], argv[13], argv[14]); break; case 16: - res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], - argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], - argv[12], argv[13], argv[14], argv[15]); + res = BlCall( + fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], + argv[11], argv[12], argv[13], argv[14], argv[15]); break; case 17: - res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], - argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], - argv[12], argv[13], argv[14], argv[15], argv[16]); + res = BlCall( + fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], + argv[11], argv[12], argv[13], argv[14], argv[15], argv[16]); break; case 18: - res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], - argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], - argv[12], argv[13], argv[14], argv[15], argv[16], argv[17]); + res = BlCall( + fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], + argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17]); break; case 19: - res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], - argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], - argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], - argv[18]); + res = BlCall( + fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], + argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], argv[18]); break; default: res = ""; @@ -219,57 +208,58 @@ int bll_TsCall(lua_State *L, const char *oname, const char *fname, int argc, return 1; } + // Lua lib function: ts.call -int bll_lua_tscall(lua_State *L) { +int bll_lua_tscall(lua_State* L) { int argc = lua_gettop(L) - 1; // number of arguments after function name if (argc < 0) return luaL_error(L, "_bllua_ts.call: Must provide a function name"); - const char *fname = luaL_checkstring(L, 1); + const char* fname = luaL_checkstring(L, 1); return bll_TsCall(L, NULL, fname, argc, 1); } -// Lua lib function: ts.callobj -int bll_lua_tscallobj(lua_State *L) { - int argc = - lua_gettop(L) - 2; // number of arguments after function name and object? - if (argc < 0) - return luaL_error( - L, "_bllua_ts.callobj: Must provide an object and function name"); - const char *oname = luaL_checkstring(L, 1); - const char *fname = luaL_checkstring(L, 2); +// Lua lib function: ts.callobj +int bll_lua_tscallobj(lua_State* L) { + int argc = lua_gettop(L) - 2; // number of arguments after function name and object? + if (argc < 0) + return luaL_error(L, "_bllua_ts.callobj: Must provide an object and function name"); + + const char* oname = luaL_checkstring(L, 1); + const char* fname = luaL_checkstring(L, 2); return bll_TsCall(L, oname, fname, argc, 2); } // Lua lib function: ts.getvar -int bll_lua_tsgetvar(lua_State *L) { - const char *vname = luaL_checkstring(L, 1); +int bll_lua_tsgetvar(lua_State* L) { + const char* vname = luaL_checkstring(L, 1); - const char *var = BlGetVar(vname); + const char* var = BlGetVar(vname); lua_pushstring(L, var); return 1; } // Lua lib function: ts.getfield -int bll_lua_tsgetfield(lua_State *L) { - const char *oname = luaL_checkstring(L, 1); - const char *vname = luaL_checkstring(L, 2); +int bll_lua_tsgetfield(lua_State* L) { + const char* oname = luaL_checkstring(L, 1); + const char* vname = luaL_checkstring(L, 2); ADDR obj = BlObject(oname); if (!obj) { return luaL_error(L, "_bllua_ts.getfield: Object not found"); } - const char *val = BlGetField(obj, vname, NULL); + const char* val = BlGetField(obj, vname, NULL); lua_pushstring(L, val); return 1; } + // Lua lib function: ts.setfield -int bll_lua_tssetfield(lua_State *L) { - const char *oname = luaL_checkstring(L, 1); - const char *vname = luaL_checkstring(L, 2); - const char *val = luaL_checkstring(L, 3); +int bll_lua_tssetfield(lua_State* L) { + const char* oname = luaL_checkstring(L, 1); + const char* vname = luaL_checkstring(L, 2); + const char* val = luaL_checkstring(L, 3); ADDR obj = BlObject(oname); if (!obj) { return luaL_error(L, "_bllua_ts.setfield: Object not found"); @@ -280,17 +270,17 @@ int bll_lua_tssetfield(lua_State *L) { } // Lua lib function: ts.eval -int bll_lua_tseval(lua_State *L) { - const char *str = luaL_checkstring(L, 1); - const char *res = BlEval(str); +int bll_lua_tseval(lua_State* L) { + const char* str = luaL_checkstring(L, 1); + const char* res = BlEval(str); lua_pushstring(L, res); return 1; } // Lua lib function: ts.echo // Print to BL console - used in Lua print implementation -int bll_lua_tsecho(lua_State *L) { - const char *str = luaL_checkstring(L, 1); +int bll_lua_tsecho(lua_State* L) { + const char* str = luaL_checkstring(L, 1); BlPrintf("%s", str); return 0; } @@ -301,4 +291,5 @@ const luaL_Reg bll_lua_reg[] = { {"setfield", bll_lua_tssetfield}, {"eval", bll_lua_tseval}, {"echo", bll_lua_tsecho}, {NULL, NULL}, }; -void llibbl_init(lua_State *L) { luaL_register(L, "_bllua_ts", bll_lua_reg); } + +void llibbl_init(lua_State* L) { luaL_register(L, "_bllua_ts", bll_lua_reg); } diff --git a/src/tsliblua.cpp b/src/tsliblua.cpp index f2c85f9..5e2786f 100644 --- a/src/tsliblua.cpp +++ b/src/tsliblua.cpp @@ -5,7 +5,7 @@ #include "BlHooks.hpp" #include "luainterp.hpp" -bool bll_LuaCall(const char *fname, int argc, const char *argv[]) { +bool bll_LuaCall(const char* fname, int argc, const char* argv[]) { lua_getglobal(gL, fname); for (int i = 0; i < argc; i++) { lua_pushstring(gL, argv[i]); @@ -18,7 +18,7 @@ bool bll_LuaCall(const char *fname, int argc, const char *argv[]) { } // TS lib function: luacall -const char *bll_ts_luacall(ADDR obj, int argc, const char *argv[]) { +const char* bll_ts_luacall(ADDR obj, int argc, const char* argv[]) { if (argc < 2) return ""; @@ -26,7 +26,7 @@ const char *bll_ts_luacall(ADDR obj, int argc, const char *argv[]) { return ""; } - char *retbuf = BlReturnBuffer(BLL_ARG_MAX); + char* retbuf = BlReturnBuffer(BLL_ARG_MAX); bll_toarg(gL, retbuf, -1, false); // provide returned value to ts lua_pop(gL, 1); // pop returned value return retbuf; -- 2.49.1 From 5885dcbed350bb639e3e91cb952caac7510b038d Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Mon, 8 Dec 2025 03:09:47 -0500 Subject: [PATCH 14/22] Update libbl.lua --- src/util/libbl.lua | 1797 +++++++++++++++++++++++--------------------- 1 file changed, 946 insertions(+), 851 deletions(-) diff --git a/src/util/libbl.lua b/src/util/libbl.lua index 8abe920..e4d46c5 100644 --- a/src/util/libbl.lua +++ b/src/util/libbl.lua @@ -9,1110 +9,1205 @@ bl = bl or {} -- Config local tsMaxArgs = 16 local tsArgsLocal = '%a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p' -local tsArgsGlobal = - '$_bllua_hook_arg1,$_bllua_hook_arg2,$_bllua_hook_arg3,$_bllua_hook_arg4,'.. - '$_bllua_hook_arg5,$_bllua_hook_arg6,$_bllua_hook_arg7,$_bllua_hook_arg8,'.. - '$_bllua_hook_arg9,$_bllua_hook_arg10,$_bllua_hook_arg11,$_bllua_hook_arg12,'.. - '$_bllua_hook_arg13,$_bllua_hook_arg14,$_bllua_hook_arg15,$_bllua_hook_arg16' +local tsArgsGlobal = + '$_bllua_hook_arg1,$_bllua_hook_arg2,$_bllua_hook_arg3,$_bllua_hook_arg4,' .. + '$_bllua_hook_arg5,$_bllua_hook_arg6,$_bllua_hook_arg7,$_bllua_hook_arg8,' .. + '$_bllua_hook_arg9,$_bllua_hook_arg10,$_bllua_hook_arg11,$_bllua_hook_arg12,' .. + '$_bllua_hook_arg13,$_bllua_hook_arg14,$_bllua_hook_arg15,$_bllua_hook_arg16' -- Misc local function ipairsNilable(t) - local maxk = 0 - for k,_ in pairs(t) do - if k>maxk then maxk = k end - end - local i = 0 - return function() - i = i+1 - if i>maxk then return nil - else return i, t[i] end - end + local maxk = 0 + for k, _ in pairs(t) do + if k > maxk then maxk = k end + end + local i = 0 + return function() + i = i + 1 + if i > maxk then + return nil + else + return i, t[i] + end + end end -- Validation local function isValidFuncName(name) - return type(name)=='string' and name:find('^[a-zA-Z_][a-zA-Z0-9_]*$') + return type(name) == 'string' and name:find('^[a-zA-Z_][a-zA-Z0-9_]*$') end local function isValidFuncNameNs(name) - return type(name)=='string' and ( - name:find('^[a-zA-Z0-9_]+$') or - name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+$') ) + return type(name) == 'string' and ( + name:find('^[a-zA-Z0-9_]+$') or + name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+$')) end local function isValidFuncNameNsArgn(name) - return type(name)=='string' and ( - name:find('^[a-zA-Z0-9_]+$') or - name:find('^[a-zA-Z0-9_]+%.[a-zA-Z0-9_]+$') or - name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+$') or - name:find('^[a-zA-Z0-9_]+:[0-9]+$') or - name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+:[0-9]+$') ) + return type(name) == 'string' and ( + name:find('^[a-zA-Z0-9_]+$') or + name:find('^[a-zA-Z0-9_]+%.[a-zA-Z0-9_]+$') or + name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+$') or + name:find('^[a-zA-Z0-9_]+:[0-9]+$') or + name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+:[0-9]+$')) end -- Whether a var can be converted into a TS vector local function isTsVector(val) - if type(val)~='table' then return false end - if #val~=3 and #val~=2 then return false end - if val.__is_vector then return true end - for _,v in ipairs(val) do - if type(v)~='number' then return false end - end - return true + if type(val) ~= 'table' then return false end + if #val ~= 3 and #val ~= 2 then return false end + if val.__is_vector then return true end + for _, v in ipairs(val) do + if type(v) ~= 'number' then return false end + end + return true end -- Use strings for object types instead of integer bitmasks like in TS local tsTypesByName = { - ['all'] = -1, - ['static'] = 1, - ['environment'] = 2, - ['terrain'] = 4, - ['water'] = 16, - ['trigger'] = 32, - ['marker'] = 64, - ['gamebase'] = 1024, - ['shapebase'] = 2048, - ['camera'] = 4096, - ['staticshape'] = 8192, - ['player'] = 16384, - ['item'] = 32768, - ['vehicle'] = 65536, - ['vehicleblocker'] = 131072, - ['projectile'] = 262144, - ['explosion'] = 524288, - ['corpse'] = 1048576, - ['debris'] = 4194304, - ['physicalzone'] = 8388608, - ['staticts'] = 16777216, - ['brick'] = 33554432, - ['brickalways'] = 67108864, - ['staticrendered'] = 134217728, - ['damagableitem'] = 268435456, + ['all'] = -1, + ['static'] = 1, + ['environment'] = 2, + ['terrain'] = 4, + ['water'] = 16, + ['trigger'] = 32, + ['marker'] = 64, + ['gamebase'] = 1024, + ['shapebase'] = 2048, + ['camera'] = 4096, + ['staticshape'] = 8192, + ['player'] = 16384, + ['item'] = 32768, + ['vehicle'] = 65536, + ['vehicleblocker'] = 131072, + ['projectile'] = 262144, + ['explosion'] = 524288, + ['corpse'] = 1048576, + ['debris'] = 4194304, + ['physicalzone'] = 8388608, + ['staticts'] = 16777216, + ['brick'] = 33554432, + ['brickalways'] = 67108864, + ['staticrendered'] = 134217728, + ['damagableitem'] = 268435456, } local tsTypesByNum = {} -for k,v in pairs(tsTypesByName) do - tsTypesByNum[v] = k +for k, v in pairs(tsTypesByName) do + tsTypesByNum[v] = k end -- Type conversion from Lua to TS local toTsObject -- Convert a string from TS into a boolean -- Note: Nonempty nonnumeric strings evaluate to 1, unlike in TS -local function tsBool(v) return v~='' and v~='0' end +local function tsBool(v) return v ~= '' and v ~= '0' end -- Convert a Lua var into a TS string, or error if not possible local function valToTs(val) - if val==nil then -- nil -> '' - return '' - elseif type(val)=='boolean' then -- bool -> 0 or 1 - return val and '1' or '0' - elseif type(val)=='number' then -- number - return tostring(val) - elseif type(val)=='string' then -- string - return val - elseif type(val)=='table' then - if val._tsObjectId then -- object -> object id - return tostring(val._tsObjectId) - elseif isTsVector(val) then -- vector - > 3 numbers - return table.concat(val, ' ') - elseif #val==2 and isTsVector(val[1]) and isTsVector(val[2]) then - -- box - > 6 numbers - return table.concat(val[1], ' ')..' '..table.concat(val[2], ' ') - else - error('valToTs: cannot pass Lua tables to TorqueScript.', 3) - end - else - error('valToTs: could not convert value to TorqueScript: '..tostring(val), 3) - end + if val == nil then -- nil -> '' + return '' + elseif type(val) == 'boolean' then -- bool -> 0 or 1 + return val and '1' or '0' + elseif type(val) == 'number' then -- number + return tostring(val) + elseif type(val) == 'string' then -- string + return val + elseif type(val) == 'table' then + if val._tsObjectId then -- object -> object id + return tostring(val._tsObjectId) + elseif isTsVector(val) then -- vector - > 3 numbers + return table.concat(val, ' ') + elseif #val == 2 and isTsVector(val[1]) and isTsVector(val[2]) then + -- box - > 6 numbers + return table.concat(val[1], ' ') .. ' ' .. table.concat(val[2], ' ') + else + error('valToTs: cannot pass Lua tables to TorqueScript.', 3) + end + else + error('valToTs: could not convert value to TorqueScript: ' .. tostring(val), 3) + end end -- Type conversion from TS to Lua local fromTsForceTypes = { - ['boolean'] = function(val) return tsBool(val) end, - ['object'] = function(val) return toTsObject(val) end, -- wrap because toTsObject not defined yet - ['string'] = function(val) return val end, + ['boolean'] = function(val) return tsBool(val) end, + ['object'] = function(val) return toTsObject(val) end, -- wrap because toTsObject not defined yet + ['string'] = function(val) return val end, } local function forceValFromTs(val, typ) - local func = fromTsForceTypes[typ] - if not func then error('valFromTs: invalid force type \''..typ..'\'', 4) end - return func(val) + local func = fromTsForceTypes[typ] + if not func then error('valFromTs: invalid force type \'' .. typ .. '\'', 4) end + return func(val) end local function vectorFromTs(val) - local xS,yS,zS = val:match('^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') - if xS then return vector{tonumber(xS),tonumber(yS),tonumber(zS)} - else return nil end + local xS, yS, zS = val:match('^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') + if xS then + return vector { tonumber(xS), tonumber(yS), tonumber(zS) } + else + return nil + end end local function boxFromTs(val) - local x1S,y1S,z1S,x2S,y2S,z2S = val:match( - '^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+) '.. - '(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') - if x1S then return { - vector{tonumber(x1S),tonumber(y1S),tonumber(z1S)}, - vector{tonumber(x2S),tonumber(y2S),tonumber(z2S)} } - else return nil end + local x1S, y1S, z1S, x2S, y2S, z2S = val:match( + '^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+) ' .. + '(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') + if x1S then + return { + vector { tonumber(x1S), tonumber(y1S), tonumber(z1S) }, + vector { tonumber(x2S), tonumber(y2S), tonumber(z2S) } } + else + return nil + end end local function multinumericFromTs(val) - local tsNumPat = '%-?[0-9]+%.?[0-9]*e?[0-9]*' - if val:find('^'..tsNumPat) then - local nums = {} - for _,part in ipairs(val:split(' ')) do - if part:find('^'..tsNumPat..'$') then - table.insert(nums, tonumber(part)) - else - return nil - end - end - if #nums==2 or #nums==3 then - return vector(nums) - elseif #nums==6 then -- box - return { - vector{nums[1], nums[2], nums[3]}, - vector{nums[4], nums[5], nums[6]} } - elseif #nums==7 then -- axis - return nil --return matrix(nums) - else - return nil - end - end + local tsNumPat = '%-?[0-9]+%.?[0-9]*e?[0-9]*' + if val:find('^' .. tsNumPat) then + local nums = {} + for _, part in ipairs(val:split(' ')) do + if part:find('^' .. tsNumPat .. '$') then + table.insert(nums, tonumber(part)) + else + return nil + end + end + if #nums == 2 or #nums == 3 then + return vector(nums) + elseif #nums == 6 then -- box + return { + vector { nums[1], nums[2], nums[3] }, + vector { nums[4], nums[5], nums[6] } } + elseif #nums == 7 then -- axis + return nil --return matrix(nums) + else + return nil + end + end end bl._forceType = bl._forceType or {} -- todo: ensure name and name2 are already lowercase local function valFromTs(val, name, name2) - if type(val)~='string' then - error('valFromTs: expected string, got '..type(val), 3) end - if name then - name = name:lower() - if bl._forceType[name] then - return forceValFromTs(val, bl._forceType[name]) - end - end - if name2 then - name2 = name2:lower() - if bl._forceType[name2] then - return forceValFromTs(val, bl._forceType[name2]) - end - end - -- '' -> nil - if val=='' then return nil end - -- number - local num = tonumber(val) - if num then return num end - -- vector, box, or axis->matrix - local vec = multinumericFromTs(val) - if vec then return vec end - -- net string - if val:sub(1,1)=='\x01' then - return _bllua_ts.call('getTaggedString', val) - end - -- string - return val + if type(val) ~= 'string' then + error('valFromTs: expected string, got ' .. type(val), 3) + end + if name then + name = name:lower() + if bl._forceType[name] then + return forceValFromTs(val, bl._forceType[name]) + end + end + if name2 then + name2 = name2:lower() + if bl._forceType[name2] then + return forceValFromTs(val, bl._forceType[name2]) + end + end + -- '' -> nil + if val == '' then return nil end + -- number + local num = tonumber(val) + if num then return num end + -- vector, box, or axis->matrix + local vec = multinumericFromTs(val) + if vec then return vec end + -- net string + if val:sub(1, 1) == '\x01' then + return _bllua_ts.call('getTaggedString', val) + end + -- string + return val end local function arglistFromTs(name, argsS) - local args = {} - for i,arg in ipairsNilable(argsS) do - args[i] = valFromTs(arg, name..':'..i) - end - return args + local args = {} + for i, arg in ipairsNilable(argsS) do + args[i] = valFromTs(arg, name .. ':' .. i) + end + return args end local function arglistToTs(args) - local argsS = {} - for i,v in ipairsNilable(args) do - table.insert(argsS, valToTs(v)) - end - return argsS + local argsS = {} + for i, v in ipairsNilable(args) do + table.insert(argsS, valToTs(v)) + end + return argsS end local function classFromForceTypeStr(name) - local class, rest = name:match('^([a-zA-Z0-9_]+)(::.+)$') - if not class then - class, rest = name:match('^([a-zA-Z0-9_]+)(%..+)$') end - return class,rest + local class, rest = name:match('^([a-zA-Z0-9_]+)(::.+)$') + if not class then + class, rest = name:match('^([a-zA-Z0-9_]+)(%..+)$') + end + return class, rest end local setForceType setForceType = function(ftname, typ) - if typ~=nil and not fromTsForceTypes[typ] then - error('bl.type: invalid type \''..typ..'\'', 2) end - if not isValidFuncNameNsArgn(ftname) then - error('bl.type: invalid function or variable name \''..ftname..'\'', 2) end + if typ ~= nil and not fromTsForceTypes[typ] then + error('bl.type: invalid type \'' .. typ .. '\'', 2) + end + if not isValidFuncNameNsArgn(ftname) then + error('bl.type: invalid function or variable name \'' .. ftname .. '\'', 2) + end - ftname = ftname:lower() + ftname = ftname:lower() - bl._forceType[ftname] = typ + bl._forceType[ftname] = typ - -- apply to child classes if present - local cname, rest = classFromForceTypeStr(ftname) - if cname then - local meta = bl._objectUserMetas[cname] - if meta then - for chcname,_ in pairs(meta._children) do - setForceType(chcname..rest, typ) - end - end - end + -- apply to child classes if present + local cname, rest = classFromForceTypeStr(ftname) + if cname then + local meta = bl._objectUserMetas[cname] + if meta then + for chcname, _ in pairs(meta._children) do + setForceType(chcname .. rest, typ) + end + end + end end bl.type = setForceType -- Type conversion TS->Lua backwards, convert back to string local function numFromTsTostring(val) - -- todo: as-good-as-possible scientific notation for numbers - return tostring(val) + -- todo: as-good-as-possible scientific notation for numbers + return tostring(val) end local function valFromTsTostring(val) - if type(val)=='string' then - return val - elseif type(val)=='number' then - return numFromTsTostring(val) - elseif type(val)=='table' then - if val.__is_vector then - local strs = {} - for i=1,#val do strs[i] = numFromTsTostring(val[i]) end - return table.concat(strs, ' ') - elseif val.__is_matrix then - -- todo: matrix back to axis-angle string - error('bl.string: matrix not yet supported', 3) - end - end - error('bl.string: cannot convert \''..type(val)..'\'', 3) + if type(val) == 'string' then + return val + elseif type(val) == 'number' then + return numFromTsTostring(val) + elseif type(val) == 'table' then + if val.__is_vector then + local strs = {} + for i = 1, #val do strs[i] = numFromTsTostring(val[i]) end + return table.concat(strs, ' ') + elseif val.__is_matrix then + -- todo: matrix back to axis-angle string + error('bl.string: matrix not yet supported', 3) + end + end + error('bl.string: cannot convert \'' .. type(val) .. '\'', 3) end -- Getting info from TS about functions and objects local function isTsObject(t) - return type(t)=='table' and t._tsObjectId~=nil + return type(t) == 'table' and t._tsObjectId ~= nil end local function tsIsObject(name) return tsBool(_bllua_ts.call('isObject', name)) end local function tsIsFunction(name) return tsBool(_bllua_ts.call('isFunction', name)) end local function tsIsFunctionNs(ns, name) return tsBool(_bllua_ts.call('isFunction', ns, name)) end local function tsIsFunctionNsname(nsname) - local ns, name = nsname:match('^([^:]+)::([^:]+)$') - if ns then return tsIsFunctionNs(ns, name) - else return tsIsFunction(nsname) end + local ns, name = nsname:match('^([^:]+)::([^:]+)$') + if ns then + return tsIsFunctionNs(ns, name) + else + return tsIsFunction(nsname) + end end -- sanity check to make sure objects that don't isObject are always marked as ._deleted -- can be removed later local function assertTsObjectExists(obj) - local is = tsIsObject(obj._tsObjectId) - if not is then - print('Warning: TS object :exists or isobject from lua but no longer exists') end - return is + local is = tsIsObject(obj._tsObjectId) + if not is then + print('Warning: TS object :exists or isobject from lua but no longer exists') + end + return is end function bl.isObject(obj) - if type(obj)=='number' then -- object id - return tsIsObject(tostring(obj)) - elseif type(obj)=='string' then -- object name - return tsIsObject(obj) - elseif isTsObject(obj) then -- lua object - if obj._deleted then - return false - else - return assertTsObjectExists(obj) - end - else - error('bl.isObject: argument #1: expected torque object, number, or string', 2) - end + if type(obj) == 'number' then -- object id + return tsIsObject(tostring(obj)) + elseif type(obj) == 'string' then -- object name + return tsIsObject(obj) + elseif isTsObject(obj) then -- lua object + if obj._deleted then + return false + else + return assertTsObjectExists(obj) + end + else + error('bl.isObject: argument #1: expected torque object, number, or string', 2) + end end + function bl.isFunction(name) - return tsIsFunctionNsname(name) + return tsIsFunctionNsname(name) end -- Torque object pseudo-class local tsClassMeta = { - __tostring = function(t) - return 'torqueClass:'..t._name.. - (t._inherit and (':'..t._inherit._name) or '') - end, + __tostring = function(t) + return 'torqueClass:' .. t._name .. + (t._inherit and (':' .. t._inherit._name) or '') + end, } bl._objectUserMetas = bl._objectUserMetas or {} function bl.class(cname, inhname) - if not ( type(cname)=='string' and isValidFuncName(cname) ) then - error('bl.class: argument #1: invalid class name', 2) end - if not ( inhname==nil or (type(inhname)=='string' and isValidFuncName(inhname)) ) then - error('bl.class: argument #2: inherit name must be a string or nil', 2) end - cname = cname:lower() + if not (type(cname) == 'string' and isValidFuncName(cname)) then + error('bl.class: argument #1: invalid class name', 2) + end + if not (inhname == nil or (type(inhname) == 'string' and isValidFuncName(inhname))) then + error('bl.class: argument #2: inherit name must be a string or nil', 2) + end + cname = cname:lower() - local met = bl._objectUserMetas[cname] or { - _name = cname, - _inherit = nil, - _children = {}, - } - bl._objectUserMetas[cname] = met - setmetatable(met, tsClassMeta) + local met = bl._objectUserMetas[cname] or { + _name = cname, + _inherit = nil, + _children = {}, + } + bl._objectUserMetas[cname] = met + setmetatable(met, tsClassMeta) - if inhname then - inhname = inhname:lower() + if inhname then + inhname = inhname:lower() - local inh = bl._objectUserMetas[inhname] - if not inh then error('bl.class: argument #2: \''..inhname..'\' is not the '.. - 'name of an existing class', 2) end + local inh = bl._objectUserMetas[inhname] + if not inh then + error('bl.class: argument #2: \'' .. inhname .. '\' is not the ' .. + 'name of an existing class', 2) + end - inh._children[cname] = true + inh._children[cname] = true - local inhI = met._inherit - if inhI and inhI~=inh then - error('bl.class: argument #2: class already exists and '.. - 'inherits a different parent.', 2) end - met._inherit = inh + local inhI = met._inherit + if inhI and inhI ~= inh then + error('bl.class: argument #2: class already exists and ' .. + 'inherits a different parent.', 2) + end + met._inherit = inh - -- apply inherited method and field types - for ftname, typ in pairs(bl._forceType) do - local cname2, rest = classFromForceTypeStr(ftname) - if cname2==inhname then - setForceType(cname..rest, typ) - end - end - end + -- apply inherited method and field types + for ftname, typ in pairs(bl._forceType) do + local cname2, rest = classFromForceTypeStr(ftname) + if cname2 == inhname then + setForceType(cname .. rest, typ) + end + end + end end + local function objectInheritedMetas(name) - local inh = bl._objectUserMetas[name:lower()] - return function() - local inhP = inh - if inhP==nil then return nil end - inh = inh._inherit - return inhP - end + local inh = bl._objectUserMetas[name:lower()] + return function() + local inhP = inh + if inhP == nil then return nil end + inh = inh._inherit + return inhP + end end local tsObjectMeta = { - -- __index: Called when accessing fields that don't exist in the object itself - -- Return torque member function or value - __index = function(t, name) - if rawget(t,'_deleted') then - error('ts object index: object no longer exists', 2) end - if type(name)~='string' and type(name)~='number' then - error('ts object index: index must be a string or number', 2) end - if getmetatable(t)[name] then - return getmetatable(t)[name] - elseif type(name)=='number' then - if not tsIsFunctionNs(rawget(t,'_tsNamespace'), 'getObject') then - error('ts object __index: index is number, but object does not have '.. - 'getObject method', 2) end - return toTsObject(_bllua_ts.callobj(t._tsObjectId, 'getObject', - tostring(name))) - else - for inh in objectInheritedMetas(rawget(t,'_tsClassName')) do - if inh[name] then return inh[name] end - end - if - tsIsFunctionNs(rawget(t,'_tsNamespace'), name) or - tsIsFunctionNs(rawget(t,'_tsName'), name) - then - return function(t2, ...) - if t2==nil or type(t2)~='table' or not t2._tsObjectId then - error('ts object method: be sure to use :func() not .func()', 2) end - local argsS = arglistToTs({...}) - local res = - _bllua_ts.callobj(t2._tsObjectId, name, unpack(argsS)) - return valFromTs(res, - t2._tsName and t2._tsName..'::'..name, - t2._tsNamespace..'::'..name) - end - else - local res = _bllua_ts.getfield(rawget(t,'_tsObjectId'), name) - return valFromTs(res, - rawget(t,'_tsName') and rawget(t,'_tsName')..'.'..name, - rawget(t,'_tsNamespace')..'.'..name) - end - end - end, - -- __newindex: Called when setting fields on the object - -- Set lua data - -- Use :set() to set Torque data - __newindex = function(t, name, val) - if rawget(t,'_deleted') then - error('ts object newindex: object no longer exists', 2) end - if type(name)~='string' then - error('ts object newindex: index must be a string', 2) end - rawset(t, name, val) - -- create strong reference since it's now storing lua data - bl._objectsStrong[rawget(t,'_tsObjectId')] = t - end, - -- object:set(fieldName, value) - -- Use to set torque data - set = function(t, name, val) - if t==nil or type(t)~='table' or not t._tsObjectId then - error('ts object method: be sure to use :func() not .func()', 2) end - if t._deleted then - error('ts object method: object no longer exists', 2) end - if type(name)~='string' then - error('ts object :set(): index must be a string', 2) end - _bllua_ts.setfield(t._tsObjectId, name, valToTs(val)) - end, - -- __tostring: Called when printing - -- Display a nice info string - __tostring = function(t) - return 'torque:'..t._tsNamespace..':'..t._tsObjectId.. - (t._tsName~='' and ('('..t._tsName..')') or '').. - (t._deleted and '(deleted)' or '') - end, - -- #object - -- If the object has a getCount method, return its count - __len = function(t) - if t._deleted then - error('ts object __len: object no longer exists', 2) end - if not tsIsFunctionNs(t._tsNamespace, 'getCount') then - error('ts object __len: object has no getCount method', 2) end - return tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount')) - end, - -- object:members() - -- Return an iterator for Torque objects with the getCount and getObject methods - -- for index, object in group:members() do ... end - members = function(t) - if t==nil then - error('ts object method: be sure to use :func() not .func()', 2) end - if t._deleted then - error('ts object method: object no longer exists', 2) end - if not ( - tsIsFunctionNs(t._tsNamespace, 'getCount' ) and - tsIsFunctionNs(t._tsNamespace, 'getObject')) then - error('ts object :members() - '.. - 'Object does not have getCount and getObject methods', 2) end - local count = tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount')) - local idx = 0 - return function() - if idx < count then - local obj = toTsObject(_bllua_ts.callobj(t._tsObjectId, - 'getObject', tostring(idx))) - idx = idx+1 - --return idx-1, obj - return obj - else - return nil - end - end - end, - -- Wrap some Torque functions for performance and error checking - getName = function(t) - if t==nil then - error('ts object method: be sure to use :func() not .func()', 2) end - if t._deleted then - error('ts object method: object no longer exists', 2) end - return t._tsName - end, - getId = function(t) - if t==nil then - error('ts object method: be sure to use :func() not .func()', 2) end - if t._deleted then - error('ts object method: object no longer exists', 2) end - return tonumber(t._tsObjectId) - end, - getType = function(t) - if t==nil then - error('ts object method: be sure to use :func() not .func()', 2) end - if t._deleted then - error('ts object method: object no longer exists', 2) end - return tsTypesByNum[_bllua_ts.callobj(t._tsObjectId, 'getType')] - end, - ---- Schedule method for objects - --schedule = function(t, time, cb, ...) - -- if type(t)~='table' or not t._tsObjectId then - -- error('ts object method: be sure to use :func() not .func()', 2) end - -- if t._deleted then - -- error('ts object method: object no longer exists', 2) end - -- if type(time)~='number' then - -- error('ts object schedule: argument #2: time must be a number', 2) end - -- if type(cb)~='function' then - -- error('ts object schedule: argument #3: callback must be a function', 2) end - -- local args = {...} - -- bl.schedule(time, function() - -- if tsBool(__bllua_ts.call('isObject', t._tsObjectId)) then - -- pcall(cb, unpack(args)) - -- end - -- end) - --end, - exists = function(t) - if t==nil then - error('ts object method: be sure to use :func() not .func()', 2) end - if t._deleted then - return false - else - return assertTsObjectExists(t) - end - end, + -- __index: Called when accessing fields that don't exist in the object itself + -- Return torque member function or value + __index = function(t, name) + if rawget(t, '_deleted') then + error('ts object index: object no longer exists', 2) + end + if type(name) ~= 'string' and type(name) ~= 'number' then + error('ts object index: index must be a string or number', 2) + end + if getmetatable(t)[name] then + return getmetatable(t)[name] + elseif type(name) == 'number' then + if not tsIsFunctionNs(rawget(t, '_tsNamespace'), 'getObject') then + error('ts object __index: index is number, but object does not have ' .. + 'getObject method', 2) + end + return toTsObject(_bllua_ts.callobj(t._tsObjectId, 'getObject', + tostring(name))) + else + for inh in objectInheritedMetas(rawget(t, '_tsClassName')) do + if inh[name] then return inh[name] end + end + if + tsIsFunctionNs(rawget(t, '_tsNamespace'), name) or + tsIsFunctionNs(rawget(t, '_tsName'), name) + then + return function(t2, ...) + if t2 == nil or type(t2) ~= 'table' or not t2._tsObjectId then + error('ts object method: be sure to use :func() not .func()', 2) + end + local argsS = arglistToTs({ ... }) + local res = + _bllua_ts.callobj(t2._tsObjectId, name, unpack(argsS)) + return valFromTs(res, + t2._tsName and t2._tsName .. '::' .. name, + t2._tsNamespace .. '::' .. name) + end + else + local res = _bllua_ts.getfield(rawget(t, '_tsObjectId'), name) + return valFromTs(res, + rawget(t, '_tsName') and rawget(t, '_tsName') .. '.' .. name, + rawget(t, '_tsNamespace') .. '.' .. name) + end + end + end, + -- __newindex: Called when setting fields on the object + -- Set lua data + -- Use :set() to set Torque data + __newindex = function(t, name, val) + if rawget(t, '_deleted') then + error('ts object newindex: object no longer exists', 2) + end + if type(name) ~= 'string' then + error('ts object newindex: index must be a string', 2) + end + rawset(t, name, val) + -- create strong reference since it's now storing lua data + bl._objectsStrong[rawget(t, '_tsObjectId')] = t + end, + -- object:set(fieldName, value) + -- Use to set torque data + set = function(t, name, val) + if t == nil or type(t) ~= 'table' or not t._tsObjectId then + error('ts object method: be sure to use :func() not .func()', 2) + end + if t._deleted then + error('ts object method: object no longer exists', 2) + end + if type(name) ~= 'string' then + error('ts object :set(): index must be a string', 2) + end + _bllua_ts.setfield(t._tsObjectId, name, valToTs(val)) + end, + -- __tostring: Called when printing + -- Display a nice info string + __tostring = function(t) + return 'torque:' .. t._tsNamespace .. ':' .. t._tsObjectId .. + (t._tsName ~= '' and ('(' .. t._tsName .. ')') or '') .. + (t._deleted and '(deleted)' or '') + end, + -- #object + -- If the object has a getCount method, return its count + __len = function(t) + if t._deleted then + error('ts object __len: object no longer exists', 2) + end + if not tsIsFunctionNs(t._tsNamespace, 'getCount') then + error('ts object __len: object has no getCount method', 2) + end + return tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount')) + end, + -- object:members() + -- Return an iterator for Torque objects with the getCount and getObject methods + -- for index, object in group:members() do ... end + members = function(t) + if t == nil then + error('ts object method: be sure to use :func() not .func()', 2) + end + if t._deleted then + error('ts object method: object no longer exists', 2) + end + if not ( + tsIsFunctionNs(t._tsNamespace, 'getCount') and + tsIsFunctionNs(t._tsNamespace, 'getObject')) then + error('ts object :members() - ' .. + 'Object does not have getCount and getObject methods', 2) + end + local count = tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount')) + local idx = 0 + return function() + if idx < count then + local obj = toTsObject(_bllua_ts.callobj(t._tsObjectId, + 'getObject', tostring(idx))) + idx = idx + 1 + --return idx-1, obj + return obj + else + return nil + end + end + end, + -- Wrap some Torque functions for performance and error checking + getName = function(t) + if t == nil then + error('ts object method: be sure to use :func() not .func()', 2) + end + if t._deleted then + error('ts object method: object no longer exists', 2) + end + return t._tsName + end, + getId = function(t) + if t == nil then + error('ts object method: be sure to use :func() not .func()', 2) + end + if t._deleted then + error('ts object method: object no longer exists', 2) + end + return tonumber(t._tsObjectId) + end, + getType = function(t) + if t == nil then + error('ts object method: be sure to use :func() not .func()', 2) + end + if t._deleted then + error('ts object method: object no longer exists', 2) + end + return tsTypesByNum[_bllua_ts.callobj(t._tsObjectId, 'getType')] + end, + ---- Schedule method for objects + --schedule = function(t, time, cb, ...) + -- if type(t)~='table' or not t._tsObjectId then + -- error('ts object method: be sure to use :func() not .func()', 2) end + -- if t._deleted then + -- error('ts object method: object no longer exists', 2) end + -- if type(time)~='number' then + -- error('ts object schedule: argument #2: time must be a number', 2) end + -- if type(cb)~='function' then + -- error('ts object schedule: argument #3: callback must be a function', 2) end + -- local args = {...} + -- bl.schedule(time, function() + -- if tsBool(__bllua_ts.call('isObject', t._tsObjectId)) then + -- pcall(cb, unpack(args)) + -- end + -- end) + --end, + exists = function(t) + if t == nil then + error('ts object method: be sure to use :func() not .func()', 2) + end + if t._deleted then + return false + else + return assertTsObjectExists(t) + end + end, } -- Weak-values table for caching Torque object references -- Objects in this table can be garbage collected if there are no other refs to them if not bl._objectsWeak then - bl._objectsWeak = {} - setmetatable(bl._objectsWeak, { __mode='v' }) + bl._objectsWeak = {} + setmetatable(bl._objectsWeak, { __mode = 'v' }) end -- Strong table for preserving Torque object references containing lua data -- If an object in this table, it will remain here and in the Weak table until deleted if not bl._objectsStrong then - bl._objectsStrong = {} + bl._objectsStrong = {} end -- Hook object deletion to clean up its lua data -- idS is expected to be the object ID number, NOT the object name function _bllua_objectDeleted(idS) - local obj = bl._objectsWeak[idS] - if obj then - obj._deleted = true - bl._objectsStrong[idS] = nil - bl._objectsWeak[idS] = nil - bl._objectsWeak[obj._tsName:lower()] = nil - end + local obj = bl._objectsWeak[idS] + if obj then + obj._deleted = true + bl._objectsStrong[idS] = nil + bl._objectsWeak[idS] = nil + bl._objectsWeak[obj._tsName:lower()] = nil + end end -- Return a Torque object for the object ID string, or create one if none exists toTsObject = function(idiS) - if type(idiS)~='string' then - error('toTsObject: input must be a string', 2) end - idiS = idiS:lower() - if bl._objectsWeak[idiS] then return bl._objectsWeak[idiS] end + if type(idiS) ~= 'string' then + error('toTsObject: input must be a string', 2) + end + idiS = idiS:lower() + if bl._objectsWeak[idiS] then return bl._objectsWeak[idiS] end - if not tsIsObject(idiS) then - --error('toTsObject: object \''..idiS..'\' does not exist', 2) end - return nil end + if not tsIsObject(idiS) then + --error('toTsObject: object \''..idiS..'\' does not exist', 2) end + return nil + end - local className = _bllua_ts.callobj(idiS, 'getClassName') - local obj = { - _tsObjectId = _bllua_ts.callobj(idiS, 'getId' ), - _tsName = _bllua_ts.callobj(idiS, 'getName' ), - _tsNamespace = className, - _tsClassName = className:lower(), - } - setmetatable(obj, tsObjectMeta) + local className = _bllua_ts.callobj(idiS, 'getClassName') + local obj = { + _tsObjectId = _bllua_ts.callobj(idiS, 'getId'), + _tsName = _bllua_ts.callobj(idiS, 'getName'), + _tsNamespace = className, + _tsClassName = className:lower(), + } + setmetatable(obj, tsObjectMeta) - bl._objectsWeak[obj._tsObjectId ] = obj - bl._objectsWeak[obj._tsName:lower()] = obj - return obj + bl._objectsWeak[obj._tsObjectId] = obj + bl._objectsWeak[obj._tsName:lower()] = obj + return obj end -- Allow bl['namespaced::function']() local function safeNamespaceName(name) - return tostring(name:gsub(':', '_')) + return tostring(name:gsub(':', '_')) end bl._cachedNamespaceCalls = {} local function tsNamespacedCallTfname(name) - local tfname = bl._cachedNamespaceCalls[name] - if not tfname then - tfname = '_bllua_nscall_'..safeNamespaceName(name) - local tfcode = 'function '..tfname..'('..tsArgsLocal..'){'.. - name..'('..tsArgsLocal..');}' - _bllua_ts.eval(tfcode) - bl._cachedNamespaceCalls[name] = tfname - end - return tfname + local tfname = bl._cachedNamespaceCalls[name] + if not tfname then + tfname = '_bllua_nscall_' .. safeNamespaceName(name) + local tfcode = 'function ' .. tfname .. '(' .. tsArgsLocal .. '){' .. + name .. '(' .. tsArgsLocal .. ');}' + _bllua_ts.eval(tfcode) + bl._cachedNamespaceCalls[name] = tfname + end + return tfname end local function tsCallGen(name) - return function(...) - local argsS = arglistToTs({...}) - local res = _bllua_ts.call(name, unpack(argsS)) - return valFromTs(res, name) - end + return function(...) + local argsS = arglistToTs({ ... }) + local res = _bllua_ts.call(name, unpack(argsS)) + return valFromTs(res, name) + end end -- Metatable for the global bl library -- Allows accessing Torque objects, variables, and functions by indexing it local tsMeta = { - -- __index: Called when accessing fields that don't exist in the table itself - -- Allow indexing by object id: bl[1234] - -- by object name: bl.mainMenuGui - -- by function name: bl.quit() - -- by variable name: bl.iAmAdmin - __index = function(t, name) - if getmetatable(t)[name] then - return getmetatable(t)[name] - elseif type(name)=='string' and bl._objectUserMetas[name:lower()] then - return bl._objectUserMetas[name:lower()] - else - if type(name)=='number' then - return toTsObject(tostring(name)) - elseif name:find('::') then - local ns, rest = name:match('^([^:]+)::(.+)$') - if not ns then error('ts index: invalid name \''..name..'\'', 2) end - if not rest:find('::') and tsIsFunctionNs(ns, rest) then - return tsCallGen(tsNamespacedCallTfname(name)) - else - local res = _bllua_ts.getvar(name) - return valFromTs(res, name) - end - elseif tsIsFunction(name) then - return tsCallGen(name) - elseif tsIsObject(name) then - return toTsObject(name) - else - local res = _bllua_ts.getvar(name) - return valFromTs(res, name) - end - end - end, + -- __index: Called when accessing fields that don't exist in the table itself + -- Allow indexing by object id: bl[1234] + -- by object name: bl.mainMenuGui + -- by function name: bl.quit() + -- by variable name: bl.iAmAdmin + __index = function(t, name) + if getmetatable(t)[name] then + return getmetatable(t)[name] + elseif type(name) == 'string' and bl._objectUserMetas[name:lower()] then + return bl._objectUserMetas[name:lower()] + else + if type(name) == 'number' then + return toTsObject(tostring(name)) + elseif name:find('::') then + local ns, rest = name:match('^([^:]+)::(.+)$') + if not ns then error('ts index: invalid name \'' .. name .. '\'', 2) end + if not rest:find('::') and tsIsFunctionNs(ns, rest) then + return tsCallGen(tsNamespacedCallTfname(name)) + else + local res = _bllua_ts.getvar(name) + return valFromTs(res, name) + end + elseif tsIsFunction(name) then + return tsCallGen(name) + elseif tsIsObject(name) then + return toTsObject(name) + else + local res = _bllua_ts.getvar(name) + return valFromTs(res, name) + end + end + end, } -- bl.set(name, value) -- Used to set global variables function bl.set(name, val) - _bllua_ts.setvar(name, valToTs(val)) + _bllua_ts.setvar(name, valToTs(val)) end -- Utility functions function bl.call(func, ...) - local args = {...} - local argsS = arglistToTs(args) - return _bllua_ts.call(func, unpack(argsS)) + local args = { ... } + local argsS = arglistToTs(args) + return _bllua_ts.call(func, unpack(argsS)) end + function bl.eval(code) - local res = _bllua_ts.eval(code) - return valFromTs(res) + local res = _bllua_ts.eval(code) + return valFromTs(res) end + function bl.exec(file) - local res = _bllua_ts.call('exec', file) - return valFromTs(res) + local res = _bllua_ts.call('exec', file) + return valFromTs(res) end + function bl.array(name, ...) - local rest = {...} - return name..table.concat(rest, '_') + local rest = { ... } + return name .. table.concat(rest, '_') end + function bl.boolean(val) - return val~=nil and - val~=false and - --val~='' and - --val~='0' and - val~=0 + return val ~= nil and + val ~= false and + --val~='' and + --val~='0' and + val ~= 0 end + function bl.object(id) - if type(id)=='table' and id._tsObjectId then - return id - elseif type(id)=='string' or type(id)=='number' then - return toTsObject(tostring(id)) - else - error('bl.object: id must be a ts object, number, or string', 2) - end + if type(id) == 'table' and id._tsObjectId then + return id + elseif type(id) == 'string' or type(id) == 'number' then + return toTsObject(tostring(id)) + else + error('bl.object: id must be a ts object, number, or string', 2) + end end + function bl.string(val) - return valFromTsTostring(val) + return valFromTsTostring(val) end -- Lua calling from TS local luaLookup luaLookup = function(tbl, name, set, val) - if name:find('%.') then - local first, rest = name:match('^([^%.:]+)%.(.+)$') - if not isValidFuncName(first) then - error('luaLookup: invalid name \''..tostring(first)..'\'', 3) end - if tbl[first]==nil then - if set then tbl[first] = {} - else return nil end - end - return luaLookup(tbl[first], rest, set, val) - elseif name:find(':') then - local first, rest = name:match('^([^%.:]+):(.*)$') - if rest:find('[%.:]') then - error('luacall: cannot have : or . after :', 3) end - if not isValidFuncName(first) then - error('luacall: invalid name \''..tostring(first)..'\'', 3) end - if not isValidFuncName(rest) then - error('luacall: invalid method name \''..tostring(first)..'\'', 3) end - if not tbl[first] then - error('luacall: no object named \''..rest..'\'', 3) end - if tbl[first][rest]==nil then - error('luacall: no method named \''..rest..'\'', 3) end - return function(...) - tbl[first][rest](tbl[first], ...) - end - else - if set then - tbl[name] = val - else - return tbl[name] - end - end + if name:find('%.') then + local first, rest = name:match('^([^%.:]+)%.(.+)$') + if not isValidFuncName(first) then + error('luaLookup: invalid name \'' .. tostring(first) .. '\'', 3) + end + if tbl[first] == nil then + if set then + tbl[first] = {} + else + return nil + end + end + return luaLookup(tbl[first], rest, set, val) + elseif name:find(':') then + local first, rest = name:match('^([^%.:]+):(.*)$') + if rest:find('[%.:]') then + error('luacall: cannot have : or . after :', 3) + end + if not isValidFuncName(first) then + error('luacall: invalid name \'' .. tostring(first) .. '\'', 3) + end + if not isValidFuncName(rest) then + error('luacall: invalid method name \'' .. tostring(first) .. '\'', 3) + end + if not tbl[first] then + error('luacall: no object named \'' .. rest .. '\'', 3) + end + if tbl[first][rest] == nil then + error('luacall: no method named \'' .. rest .. '\'', 3) + end + return function(...) + tbl[first][rest](tbl[first], ...) + end + else + if set then + tbl[name] = val + else + return tbl[name] + end + end end -- Todo: similar deep access for luaget and luaset function _bllua_call(fname, ...) - local args = arglistFromTs(fname:lower(), {...}) -- todo: separate lua from ts func names? - local func = luaLookup(_G, fname) - if not func then - error('luacall: no global in lua named \''..fname..'\'', 2) end - local res = func(unpack(args)) - return valToTs(res) + local args = arglistFromTs(fname:lower(), { ... }) -- todo: separate lua from ts func names? + local func = luaLookup(_G, fname) + if not func then + error('luacall: no global in lua named \'' .. fname .. '\'', 2) + end + local res = func(unpack(args)) + return valToTs(res) end -function _bllua_getvar(vname) - return valToTs(luaLookup(_G, vname)) -end -function _bllua_setvar(vname, valS) - return valToTs(luaLookup(_G, vname, true, valFromTs(valS, vname))) -- todo: separate lua from ts var names? -end -function _bllua_eval(code) return loadstring(code)() end -function _bllua_exec(fn) return dofile(fn, 2) end +function _bllua_getvar(vname) + return valToTs(luaLookup(_G, vname)) +end + +function _bllua_setvar(vname, valS) + return valToTs(luaLookup(_G, vname, true, valFromTs(valS, vname))) -- todo: separate lua from ts var names? +end + +function _bllua_eval(code) return loadstring(code)() end + +function _bllua_exec(fn) return dofile(fn, 2) end -- bl.schedule: Use TS's schedule function to schedule lua calls -- bl.schedule(time, function, args...) -bl._scheduleTable = bl._scheduleTable or {} +bl._scheduleTable = bl._scheduleTable or {} bl._scheduleNextId = bl._scheduleNextId or 1 local function cancelTsSched(sched) - if not (sched and sched.handle) then - error('schedule:cancel() - invalid object', 2) - end - _bllua_ts.call('cancel', sched.handle) - bl._scheduleTable[id] = nil + if not (sched and sched.handle) then + error('schedule:cancel() - invalid object', 2) + end + _bllua_ts.call('cancel', sched.handle) + bl._scheduleTable[id] = nil end function bl.schedule(time, cb, ...) - local id = bl._scheduleNextId - bl._scheduleNextId = bl._scheduleNextId+1 - local args = {...} - local handle = tonumber(_bllua_ts.call('schedule', - time, 0, '_bllua_luacall', '_bllua_schedule_callback', id)) - local sch = { - callback = cb, - args = args, - handle = handle, - cancel = cancelTsSched, - } - bl._scheduleTable[id] = sch - return sch + local id = bl._scheduleNextId + bl._scheduleNextId = bl._scheduleNextId + 1 + local args = { ... } + local handle = tonumber(_bllua_ts.call('schedule', + time, 0, '_bllua_luacall', '_bllua_schedule_callback', id)) + local sch = { + callback = cb, + args = args, + handle = handle, + cancel = cancelTsSched, + } + bl._scheduleTable[id] = sch + return sch end + function _bllua_schedule_callback(idS) - local id = tonumber(idS) or - error('_bllua_schedule_callback: invalid id: '..tostring(idS)) - local sch = bl._scheduleTable[id] - if not sch then error('_bllua_schedule_callback: no schedule with id '..id) end - bl._scheduleTable[id] = nil - sch.callback(unpack(sch.args)) + local id = tonumber(idS) or + error('_bllua_schedule_callback: invalid id: ' .. tostring(idS)) + local sch = bl._scheduleTable[id] + if not sch then error('_bllua_schedule_callback: no schedule with id ' .. id) end + bl._scheduleTable[id] = nil + sch.callback(unpack(sch.args)) end -- serverCmd and clientCmd bl._cmds = bl._cmds or {} function _bllua_process_cmd(cmdS, ...) - local cmd = cmdS:lower() - local args = arglistFromTs(cmd, {...}) - local func = bl._cmds[cmd] - if not func then error('_bllua_process_cmd: no cmd named \''..cmd..'\'') end - func(unpack(args)) --pcall(func, unpack(args)) + local cmd = cmdS:lower() + local args = arglistFromTs(cmd, { ... }) + local func = bl._cmds[cmd] + if not func then error('_bllua_process_cmd: no cmd named \'' .. cmd .. '\'') end + func(unpack(args)) --pcall(func, unpack(args)) end + local function addCmd(cmd, func) - if not isValidFuncName(cmd) then - error('addCmd: invalid function name \''..tostring(cmd)..'\'') end - bl._cmds[cmd] = func - _bllua_ts.eval('function '..cmd..'('..tsArgsLocal..'){'.. - '_bllua_luacall(_bllua_process_cmd,"'..cmd..'",'..tsArgsLocal..');}') + if not isValidFuncName(cmd) then + error('addCmd: invalid function name \'' .. tostring(cmd) .. '\'') + end + bl._cmds[cmd] = func + _bllua_ts.eval('function ' .. cmd .. '(' .. tsArgsLocal .. '){' .. + '_bllua_luacall(_bllua_process_cmd,"' .. cmd .. '",' .. tsArgsLocal .. ');}') end function bl.addServerCmd(name, func) - name = name:lower() - addCmd('servercmd'..name, func) - bl._forceType['servercmd'..name..':1'] = 'object' + name = name:lower() + addCmd('servercmd' .. name, func) + bl._forceType['servercmd' .. name .. ':1'] = 'object' end + function bl.addClientCmd(name, func) - name = name:lower() - addCmd('clientcmd'..name, func) + name = name:lower() + addCmd('clientcmd' .. name, func) end -- commandToServer and commandToClient function bl.commandToServer(cmd, ...) - _bllua_ts.call('commandToServer', - _bllua_ts.call('addTaggedString', cmd), - unpack(arglistToTs({...}))) + _bllua_ts.call('commandToServer', + _bllua_ts.call('addTaggedString', cmd), + unpack(arglistToTs({ ... }))) end + function bl.commandToClient(client, cmd, ...) - _bllua_ts.call('commandToClient', - valToTs(client), - _bllua_ts.call('addTaggedString', cmd), - unpack(arglistToTs({...}))) + _bllua_ts.call('commandToClient', + valToTs(client), + _bllua_ts.call('addTaggedString', cmd), + unpack(arglistToTs({ ... }))) end + function bl.commandToAll(cmd, ...) - _bllua_ts.call('commandToAll', - _bllua_ts.call('addTaggedString', cmd), - unpack(arglistToTs({...}))) + _bllua_ts.call('commandToAll', + _bllua_ts.call('addTaggedString', cmd), + unpack(arglistToTs({ ... }))) end -- Hooks (using TS packages) local function isPackageActive(pkg) - local numpkgs = tonumber(_bllua_ts.call('getNumActivePackages')) - for i = 0, numpkgs-1 do - local apkg = _bllua_ts.call('getActivePackage', tostring(i)) - if apkg==pkg then return true end - end - return false + local numpkgs = tonumber(_bllua_ts.call('getNumActivePackages')) + for i = 0, numpkgs - 1 do + local apkg = _bllua_ts.call('getActivePackage', tostring(i)) + if apkg == pkg then return true end + end + return false end local function activatePackage(pkg) - if not isPackageActive(pkg) then - _bllua_ts.call('activatePackage', pkg) - end + if not isPackageActive(pkg) then + _bllua_ts.call('activatePackage', pkg) + end end local function deactivatePackage(pkg) - if isPackageActive(pkg) then - _bllua_ts.call('deactivatePackage', pkg) - end + if isPackageActive(pkg) then + _bllua_ts.call('deactivatePackage', pkg) + end end bl._hooks = bl._hooks or {} function _bllua_process_hook_before(pkgS, nameS, ...) - local args = arglistFromTs(nameS, {...}) - local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and - bl._hooks[pkgS][nameS].before - if not func then - error('_bllua_process_hook_before: no hook for '..pkgs..':'..nameS) end - _bllua_ts.setvar('_bllua_hook_abort', '0') - func(args) --pcall(func, args) - if args._return then - _bllua_ts.setvar('_bllua_hook_abort', '1') - _bllua_ts.setvar('_bllua_hook_return', valToTs(args._return)) - end - for i=1,tsMaxArgs do - _bllua_ts.setvar('_bllua_hook_arg'..i, valToTs(args[i])) - end + local args = arglistFromTs(nameS, { ... }) + local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and + bl._hooks[pkgS][nameS].before + if not func then + error('_bllua_process_hook_before: no hook for ' .. pkgs .. ':' .. nameS) + end + _bllua_ts.setvar('_bllua_hook_abort', '0') + func(args) --pcall(func, args) + if args._return then + _bllua_ts.setvar('_bllua_hook_abort', '1') + _bllua_ts.setvar('_bllua_hook_return', valToTs(args._return)) + end + for i = 1, tsMaxArgs do + _bllua_ts.setvar('_bllua_hook_arg' .. i, valToTs(args[i])) + end end + function _bllua_process_hook_after(pkgS, nameS, resultS, ...) - nameS = nameS:lower() - local args = arglistFromTs(nameS, {...}) - args._return = valFromTs(resultS, nameS) - local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and - bl._hooks[pkgS][nameS].after - if not func then - error('_bllua_process_hook_after: no hook for '..pkgs..':'..nameS) end - func(args) --pcall(func, args) - return valToTs(args._return) + nameS = nameS:lower() + local args = arglistFromTs(nameS, { ... }) + args._return = valFromTs(resultS, nameS) + local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and + bl._hooks[pkgS][nameS].after + if not func then + error('_bllua_process_hook_after: no hook for ' .. pkgs .. ':' .. nameS) + end + func(args) --pcall(func, args) + return valToTs(args._return) end + local function updateHook(pkg, name, hk) - local beforeCode = hk.before and - ('_bllua_luacall("_bllua_process_hook_before","'..pkg..'","'..name.. - '",'..tsArgsLocal..');') or '' - local arglist = (hk.before and tsArgsGlobal or tsArgsLocal) - local parentCode = - tsIsFunctionNsname(name) and -- only call parent if it exists - (hk.before and - 'if($_bllua_hook_abort)return $_bllua_hook_return;else ' or '').. - ((hk.after and '%result=' or 'return ').. - 'parent::'..name:match('[^:]+$').. - '('..arglist..');') or '' - local afterCode = hk.after and - ('return _bllua_luacall("_bllua_process_hook_after","'..pkg..'","'..name..'",%result,'.. - arglist..');') or '' - local code = - 'package '..pkg..'{'.. - 'function '..name..'('..tsArgsLocal..'){'.. - beforeCode..parentCode..afterCode.. - '}'.. - '};' - print('bl.hook eval output: [['..code..']]') - _bllua_ts.eval(code) + local beforeCode = hk.before and + ('_bllua_luacall("_bllua_process_hook_before","' .. pkg .. '","' .. name .. + '",' .. tsArgsLocal .. ');') or '' + local arglist = (hk.before and tsArgsGlobal or tsArgsLocal) + local parentCode = + tsIsFunctionNsname(name) and -- only call parent if it exists + (hk.before and + 'if($_bllua_hook_abort)return $_bllua_hook_return;else ' or '') .. + ((hk.after and '%result=' or 'return ') .. + 'parent::' .. name:match('[^:]+$') .. + '(' .. arglist .. ');') or '' + local afterCode = hk.after and + ('return _bllua_luacall("_bllua_process_hook_after","' .. pkg .. '","' .. name .. '",%result,' .. + arglist .. ');') or '' + local code = + 'package ' .. pkg .. '{' .. + 'function ' .. name .. '(' .. tsArgsLocal .. '){' .. + beforeCode .. parentCode .. afterCode .. + '}' .. + '};' + print('bl.hook eval output: [[' .. code .. ']]') + _bllua_ts.eval(code) end function bl.hook(pkg, name, time, func) - if not isValidFuncName(pkg) then - error('bl.hook: argument #1: invalid package name \''..tostring(pkg)..'\'', 2) end - if not isValidFuncNameNs(name) then - error('bl.hook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end - if time~='before' and time~='after' then - error('bl.hook: argument #3: time must be \'before\' or \'after\'', 2) end - if type(func)~='function' then - error('bl.hook: argument #4: expected a function', 2) end + if not isValidFuncName(pkg) then + error('bl.hook: argument #1: invalid package name \'' .. tostring(pkg) .. '\'', 2) + end + if not isValidFuncNameNs(name) then + error('bl.hook: argument #2: invalid function name \'' .. tostring(name) .. '\'', 2) + end + if time ~= 'before' and time ~= 'after' then + error('bl.hook: argument #3: time must be \'before\' or \'after\'', 2) + end + if type(func) ~= 'function' then + error('bl.hook: argument #4: expected a function', 2) + end - bl._hooks[pkg] = bl._hooks[pkg] or {} - bl._hooks[pkg][name] = bl._hooks[pkg][name] or {} - bl._hooks[pkg][name][time] = func + bl._hooks[pkg] = bl._hooks[pkg] or {} + bl._hooks[pkg][name] = bl._hooks[pkg][name] or {} + bl._hooks[pkg][name][time] = func - updateHook(pkg, name, bl._hooks[pkg][name]) - activatePackage(pkg) + updateHook(pkg, name, bl._hooks[pkg][name]) + activatePackage(pkg) end + local function tableEmpty(t) - return next(t)~=nil + return next(t) ~= nil end function bl.unhook(pkg, name, time) - if not isValidFuncName(pkg) then - error('bl.unhook: argument #1: invalid package name \''..tostring(pkg)..'\'', 2) end - if name and not isValidFuncNameNs(name) then - error('bl.unhook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end - if time and time~='before' and time~='after' then - error('bl.unhook: argument #3: time must be \'before\' or \'after\'', 2) end + if not isValidFuncName(pkg) then + error('bl.unhook: argument #1: invalid package name \'' .. tostring(pkg) .. '\'', 2) + end + if name and not isValidFuncNameNs(name) then + error('bl.unhook: argument #2: invalid function name \'' .. tostring(name) .. '\'', 2) + end + if time and time ~= 'before' and time ~= 'after' then + error('bl.unhook: argument #3: time must be \'before\' or \'after\'', 2) + end - if not name then - if bl._hooks[pkg] then - for name,hk in pairs(bl._hooks[pkg]) do - updateHook(pkg, name, {}) - end - bl._hooks[pkg] = nil - else - --error('bl.unhook: no hooks registered under package name \''.. - -- pkg..'\'', 2) - end - deactivatePackage(pkg) - else - if bl._hooks[pkg][name] then - if not time then - bl._hooks[pkg][name] = nil - if table.empty(bl._hooks[pkg]) then - bl._hooks[pkg] = nil - deactivatePackage(pkg) - end - updateHook(pkg, name, {}) - else - if time~='before' and time~='after' then - error('bl.unhook: argument #3: time must be nil, \'before\', or \'after\'', 2) end - bl._hooks[pkg][name][time] = nil - if tableEmpty(bl._hooks[pkg][name]) then - bl._hooks[pkg][name] = nil - end - if tableEmpty(bl._hooks[pkg]) then - bl._hooks[pkg] = nil - updateHook(pkg, name, {}) - deactivatePackage(pkg) - else - updateHook(pkg, name, bl._hooks[pkg][name]) - end - end - else - --error('bl.unhook: no hooks registered for function \''..name.. - -- '\' under package name \''..pkg..'\'', 2) - end - end + if not name then + if bl._hooks[pkg] then + for name, hk in pairs(bl._hooks[pkg]) do + updateHook(pkg, name, {}) + end + bl._hooks[pkg] = nil + else + --error('bl.unhook: no hooks registered under package name \''.. + -- pkg..'\'', 2) + end + deactivatePackage(pkg) + else + if bl._hooks[pkg][name] then + if not time then + bl._hooks[pkg][name] = nil + if table.empty(bl._hooks[pkg]) then + bl._hooks[pkg] = nil + deactivatePackage(pkg) + end + updateHook(pkg, name, {}) + else + if time ~= 'before' and time ~= 'after' then + error('bl.unhook: argument #3: time must be nil, \'before\', or \'after\'', 2) + end + bl._hooks[pkg][name][time] = nil + if tableEmpty(bl._hooks[pkg][name]) then + bl._hooks[pkg][name] = nil + end + if tableEmpty(bl._hooks[pkg]) then + bl._hooks[pkg] = nil + updateHook(pkg, name, {}) + deactivatePackage(pkg) + else + updateHook(pkg, name, bl._hooks[pkg][name]) + end + end + else + --error('bl.unhook: no hooks registered for function \''..name.. + -- '\' under package name \''..pkg..'\'', 2) + end + end end -- Container search/raycast local function vecToTs(v) - if not isTsVector(v) then - error('vecToTs: argument is not a vector', 3) end - return table.concat(v, ' ') + if not isTsVector(v) then + error('vecToTs: argument is not a vector', 3) + end + return table.concat(v, ' ') end local function maskToTs(mask) - if type(mask)=='string' then - local val = tsTypesByName[mask:lower()] - if not val then - error('maskToTs: invalid mask \''..mask..'\'', 3) end - return tostring(val) - elseif type(mask)=='table' then - local tval = 0 - local seen = {} - for i,v in ipairs(mask) do - if not seen[v] then - local val = tsTypesByName[v:lower()] - if not val then - error('maskToTs: invalid mask \''..v.. - '\' at index '..i..' in mask list', 3) end - tval = tval + val - seen[v] = true - end - end - return tostring(tval) - else - error('maskToTs: mask must be a string or table', 3) - end + if type(mask) == 'string' then + local val = tsTypesByName[mask:lower()] + if not val then + error('maskToTs: invalid mask \'' .. mask .. '\'', 3) + end + return tostring(val) + elseif type(mask) == 'table' then + local tval = 0 + local seen = {} + for i, v in ipairs(mask) do + if not seen[v] then + local val = tsTypesByName[v:lower()] + if not val then + error('maskToTs: invalid mask \'' .. v .. + '\' at index ' .. i .. ' in mask list', 3) + end + tval = tval + val + seen[v] = true + end + end + return tostring(tval) + else + error('maskToTs: mask must be a string or table', 3) + end end local function objToTs(obj) - if type(obj)=='number' or type(obj)=='string' then - return tostring(obj) - elseif type(obj)=='table' and obj._tsObjectId then - return tostring(obj._tsObjectId) - else - error('objToTs: invalid object \''..tostring(obj)..'\'', 3) - end + if type(obj) == 'number' or type(obj) == 'string' then + return tostring(obj) + elseif type(obj) == 'table' and obj._tsObjectId then + return tostring(obj._tsObjectId) + else + error('objToTs: invalid object \'' .. tostring(obj) .. '\'', 3) + end end function bl.raycast(start, stop, mask, ignores) - local startS = vecToTs(start) - local stopS = vecToTs(start) - local maskS = maskToTs(mask) - local ignoresS = {} - for _,v in ipairsNilable(ignores) do - table.insert(ignoresS, objToTs(v)) - end + local startS = vecToTs(start) + local stopS = vecToTs(start) + local maskS = maskToTs(mask) + local ignoresS = {} + for _, v in ipairsNilable(ignores) do + table.insert(ignoresS, objToTs(v)) + end - local retS = _bllua_ts.call('containerRaycast', startS, stopS, maskS, unpack(ignoresS)) + local retS = _bllua_ts.call('containerRaycast', startS, stopS, maskS, unpack(ignoresS)) - if retS=='0' then - return nil - else - local hitS, pxS,pyS,pzS, nxS,nyS,nzS = retS:match('^([0-9]+) '.. - '(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+) '.. - '(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') - local hit = toTsObject(hitS) - local pos = vector{tonumber(pxS),tonumber(pyS),tonumber(pzS)} - local norm = vector{tonumber(nxS),tonumber(nyS),tonumber(nzS)} - return hit, pos, norm - end + if retS == '0' then + return nil + else + local hitS, pxS, pyS, pzS, nxS, nyS, nzS = retS:match('^([0-9]+) ' .. + '(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+) ' .. + '(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') + local hit = toTsObject(hitS) + local pos = vector { tonumber(pxS), tonumber(pyS), tonumber(pzS) } + local norm = vector { tonumber(nxS), tonumber(nyS), tonumber(nzS) } + return hit, pos, norm + end end + local function tsContainerSearchIterator() - local retS = _bllua_ts.call('containerSearchNext') - if retS=='0' then - return nil - else - return toTsObject(retS) - end + local retS = _bllua_ts.call('containerSearchNext') + if retS == '0' then + return nil + else + return toTsObject(retS) + end end function bl.boxSearch(pos, size, mask) - local posS = vecToTs(pos) - local sizeS = vecToTs(size) - local maskS = maskToTs(mask) + local posS = vecToTs(pos) + local sizeS = vecToTs(size) + local maskS = maskToTs(mask) - _bllua_ts.call('initContainerBoxSearch', posS, sizeS, maskS) - return tsContainerSearchIterator + _bllua_ts.call('initContainerBoxSearch', posS, sizeS, maskS) + return tsContainerSearchIterator end -function bl.radiusSearch(pos, radius, mask) - local posS = vecToTs(pos) - if type(radius)~='number' then - error('bl.radiusSearch: argument #2: radius must be a number', 2) end - local radiusS = tostring(radius) - local maskS = maskToTs(mask) - _bllua_ts.call('initContainerRadiusSearch', posS, radiusS, maskS) - return tsContainerSearchIterator +function bl.radiusSearch(pos, radius, mask) + local posS = vecToTs(pos) + if type(radius) ~= 'number' then + error('bl.radiusSearch: argument #2: radius must be a number', 2) + end + local radiusS = tostring(radius) + local maskS = maskToTs(mask) + + _bllua_ts.call('initContainerRadiusSearch', posS, radiusS, maskS) + return tsContainerSearchIterator end -- Print/Talk/Echo local maxTsArgLen = 8192 local function valsToString(vals) - local strs = {} - for i,v in ipairsNilable(vals) do - local tstr = table.tostring(v) - if #tstr>maxTsArgLen then - tstr = tostring(v) - end - strs[i] = tstr - end - return table.concat(strs, ' ') + local strs = {} + for i, v in ipairsNilable(vals) do + local tstr = table.tostring(v) + if #tstr > maxTsArgLen then + tstr = tostring(v) + end + strs[i] = tstr + end + return table.concat(strs, ' ') end bl.echo = function(...) - local str = valsToString({...}) - _bllua_ts.call('echo', str) + local str = valsToString({ ... }) + _bllua_ts.call('echo', str) end print = bl.echo bl.talk = function(...) - local str = valsToString({...}) - _bllua_ts.call('echo', str) - _bllua_ts.call('talk', str) + local str = valsToString({ ... }) + _bllua_ts.call('echo', str) + _bllua_ts.call('talk', str) end -- bl.new and bl.datablock local function createTsObj(keyword, class, name, inherit, props) - local propsT = {} - if props then - for k,v in pairs(props) do - if not isValidFuncName(k) then - error('bl.new/bl.datablock: invalid property name \''..k..'\'') end - table.insert(propsT, k..'="'..valToTs(v)..'";') - end - end + local propsT = {} + if props then + for k, v in pairs(props) do + if not isValidFuncName(k) then + error('bl.new/bl.datablock: invalid property name \'' .. k .. '\'') + end + table.insert(propsT, k .. '="' .. valToTs(v) .. '";') + end + end - local objS = _bllua_ts.eval( - 'return '..keyword..' '..class..'('.. - (name or '')..(inherit and (':'..inherit) or '')..'){'.. - table.concat(propsT)..'};') - local obj = toTsObject(objS) - if not obj then - error('bl.new/bl.datablock: failed to create object', 3) end + local objS = _bllua_ts.eval( + 'return ' .. keyword .. ' ' .. class .. '(' .. + (name or '') .. (inherit and (':' .. inherit) or '') .. '){' .. + table.concat(propsT) .. '};') + local obj = toTsObject(objS) + if not obj then + error('bl.new/bl.datablock: failed to create object', 3) + end - return obj + return obj end local function parseTsDecl(decl) - local class, name, inherit - if decl:find(' ') then -- class ... - local cl, rest = decl:match('^([^ ]+) ([^ ]+)$') - class = cl - if rest:find(':') then -- class name:inherit - name, inherit = rest:match('^([^:]*):([^:]+)$') - if not name then class = nil end -- error - if name=='' then name = nil end -- class :inherit - else - name = rest - end - else -- class - class = decl - end - if not ( - isValidFuncName(class) and - (name==nil or isValidFuncName(name)) and - (inherit==nil or isValidFuncName(inherit)) ) then - error('bl.new/bl.datablock: invalid decl \''..decl..'\'\n'.. - 'must be of the format: \'className\', \'className name\', '.. - '\'className :inherit\', or \'className name:inherit\'', 3) end - return class, name, inherit + local class, name, inherit + if decl:find(' ') then -- class ... + local cl, rest = decl:match('^([^ ]+) ([^ ]+)$') + class = cl + if rest:find(':') then -- class name:inherit + name, inherit = rest:match('^([^:]*):([^:]+)$') + if not name then class = nil end -- error + if name == '' then name = nil end -- class :inherit + else + name = rest + end + else -- class + class = decl + end + if not ( + isValidFuncName(class) and + (name == nil or isValidFuncName(name)) and + (inherit == nil or isValidFuncName(inherit))) then + error('bl.new/bl.datablock: invalid decl \'' .. decl .. '\'\n' .. + 'must be of the format: \'className\', \'className name\', ' .. + '\'className :inherit\', or \'className name:inherit\'', 3) + end + return class, name, inherit end function bl.new(decl, props) - local class, name, inherit = parseTsDecl(decl) - return createTsObj('new', class, name, inherit, props) + local class, name, inherit = parseTsDecl(decl) + return createTsObj('new', class, name, inherit, props) end + function bl.datablock(decl, props) - local class, name, inherit = parseTsDecl(decl) - if not name then error('bl.datablock: must specify a name', 2) end - return createTsObj('datablock', class, name, inherit, props) + local class, name, inherit = parseTsDecl(decl) + if not name then error('bl.datablock: must specify a name', 2) end + return createTsObj('datablock', class, name, inherit, props) end setmetatable(bl, tsMeta) -- 2.49.1 From 15f67e0eef1c20c17db7c4d1184414c3a845709e Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Mon, 8 Dec 2025 03:12:03 -0500 Subject: [PATCH 15/22] Fix invalid escape sequence for 5.1 --- src/util/libbl.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/libbl.lua b/src/util/libbl.lua index e4d46c5..2b1432f 100644 --- a/src/util/libbl.lua +++ b/src/util/libbl.lua @@ -205,7 +205,7 @@ local function valFromTs(val, name, name2) local vec = multinumericFromTs(val) if vec then return vec end -- net string - if val:sub(1, 1) == '\x01' then + if val:sub(1, 1) == '\001' then return _bllua_ts.call('getTaggedString', val) end -- string -- 2.49.1 From 4f42801da66a7d00c8c310040cae527292e46940 Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Mon, 8 Dec 2025 03:22:47 -0500 Subject: [PATCH 16/22] Update compiling.md --- compiling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiling.md b/compiling.md index 0dd00b0..d49d0d4 100644 --- a/compiling.md +++ b/compiling.md @@ -48,5 +48,5 @@ You should see `architecture: i386` in the output. ### Notes -- Ensure you installed the i686 (32-bit) variants of the packages; x86_64 packages won’t work for a 32-bit build. +- Ensure you installed the i686 (32-bit) variants of the packages; x86_64 packages won't work for a 32-bit build. - If the linker cannot find `-llua5.1`, confirm `mingw-w64-i686-lua51` is installed and you are using the `mingw32` toolchain (not `x86_64`). -- 2.49.1 From f6bf18efaac563ede0dff9d003ceb9c1512a4e5e Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Mon, 8 Dec 2025 03:30:54 -0500 Subject: [PATCH 17/22] Fix md formatting + remove trailing whitespace --- readme.md | 347 +++++++++++++++++++++++++++++------------------------- 1 file changed, 184 insertions(+), 163 deletions(-) diff --git a/readme.md b/readme.md index 525343a..3d7a0db 100644 --- a/readme.md +++ b/readme.md @@ -1,219 +1,240 @@ - # BlockLua Lua scripting for Blockland ## How to Install -- Install RedBlocklandLoader -- Copy `lua5.1.dll` into your Blockland install folder, next to `Blockland.exe` -- Copy `BlockLua.dll` into the `modules` folder within the Blockland folder +- Install RedBlocklandLoader +- Copy `lua5.1.dll` into your Blockland install folder, next to `Blockland.exe` +- Copy `BlockLua.dll` into the `modules` folder within the Blockland folder ## Quick Reference ### From TorqueScript -`'print('hello world')` - Execute Lua in the console by prepending a `'` (single quote) -`luaeval("code");` - Execute Lua code -`luacall("funcName", %args...);` - Call a Lua function (supports indexing tables and object methods) -`luaexec("fileName");` - Execute a Lua file. Path rules are the same as when executing .cs files, relative paths are allowed. -`luaget("varName");` - Read a Lua global variable (supports indexing tables) -`luaset("varName", %value);` - Write a Lua global variable (supports indexing tables) + +`'print('hello world')` - Execute Lua in the console by prepending a `'` (single quote) +`luaeval("code");` - Execute Lua code +`luacall("funcName", %args...);` - Call a Lua function (supports indexing tables and object methods) +`luaexec("fileName");` - Execute a Lua file. Path rules are the same as when executing .cs files, relative paths are allowed. +`luaget("varName");` - Read a Lua global variable (supports indexing tables) +`luaset("varName", %value);` - Write a Lua global variable (supports indexing tables) ### From Lua -`bl.eval('code')` - Eval TorqueScript code -`bl.funcName(args)` - Call a TorqueScript function -`bl.varName` - Read a TorqueScript global variable -`bl['varName']` - Read a TorqueScript global variable (i.e. with special characters in the name, or from an array) -`bl.set('varName', value)` - Write a TorqueScript global variable + +`bl.eval('code')` - Eval TorqueScript code +`bl.funcName(args)` - Call a TorqueScript function +`bl.varName` - Read a TorqueScript global variable +`bl['varName']` - Read a TorqueScript global variable (i.e. with special characters in the name, or from an array) +`bl.set('varName', value)` - Write a TorqueScript global variable `bl['namespaceName::funcName'](args)` - Call a namespaced TorqueScript function ### Accessing Torque Objects from Lua -`bl.objectName` - Access a Torque object by name -`bl[objectID]` - Access a Torque object by ID (or name) -`object.fieldOrKey` - Read a field or Lua key from a Torque object -`object:set('field', value)` - Write a field on a Torque object -`object.key = value` - Associate Lua data with a Torque object -`object:method(args)` - Call a Torque object method -`object[index]` - Access a member of a Torque set or group + +`bl.objectName` - Access a Torque object by name +`bl[objectID]` - Access a Torque object by ID (or name) +`object.fieldOrKey` - Read a field or Lua key from a Torque object +`object:set('field', value)` - Write a field on a Torque object +`object.key = value` - Associate Lua data with a Torque object +`object:method(args)` - Call a Torque object method +`object[index]` - Access a member of a Torque set or group `for child in object:members() do` - Iterate objects within of a Torque set or group. Indices start at 0 like in Torque. -`bl.isObject(object, objectID, or 'objectName')` - Check if an object exists -`object:exists()` - Check if an object exists +`bl.isObject(object, objectID, or 'objectName')` - Check if an object exists +`object:exists()` - Check if an object exists ### Timing/Schedules -`sched = bl.schedule(timeMs, function, args...)` - Schedule a Lua function to be called later, similar to schedule in Torque -`sched:cancel()` - Cancel a previously scheduled timer + +`sched = bl.schedule(timeMs, function, args...)` - Schedule a Lua function to be called later, similar to schedule in Torque +`sched:cancel()` - Cancel a previously scheduled timer ### Raycasts and Searches -`hitObject, hitPos, hitNormal = bl.raycast(vector{startPosX,y,z}, vector{endPosX,y,z}, 'objtype'/{'objtypes',...}, ignoreObjects...?)` - Cast a ray in the world over objects of the specified type(s) (possibly excluding some objects), and return the object hit, the position of the hit, and the normal vector to the surface hit. See the Types section for a list of valid object types. -`for object in bl.boxSearch(vector{centerX,y,z}, vector{sizeX,y,z}, 'objtype'/{'objtypes',...}) do` - Find all objects in the world of the specified type(s) whose bounding box overlaps with the specified box. See the Types section for a list of valid object types. -`for object in bl.radiusSearch(vector{centerX,y,z}, radius, 'objtype'/{'objtypes',...}) do` - Find all objects of the specified type(s) whose bounding box overlaps with the specified sphere. See the Types section for a list of valid object types. + +`hitObject, hitPos, hitNormal = bl.raycast(vector{startPosX,y,z}, vector{endPosX,y,z}, 'objtype'/{'objtypes',...}, ignoreObjects...?)` - Cast a ray in the world over objects of the specified type(s) (possibly excluding some objects), and return the object hit, the position of the hit, and the normal vector to the surface hit. See the Types section for a list of valid object types. +`for object in bl.boxSearch(vector{centerX,y,z}, vector{sizeX,y,z}, 'objtype'/{'objtypes',...}) do` - Find all objects in the world of the specified type(s) whose bounding box overlaps with the specified box. See the Types section for a list of valid object types. +`for object in bl.radiusSearch(vector{centerX,y,z}, radius, 'objtype'/{'objtypes',...}) do` - Find all objects of the specified type(s) whose bounding box overlaps with the specified sphere. See the Types section for a list of valid object types. ### List of Object Classes (for raycasts and searches) -`'all'` - Any object -`'player'` - Players or bots -`'item'` - Items -`'vehicle'` - Vehicles -`'projectile'` - Projectiles -`'brick'` - Bricks with raycasting enabled -`'brickalways'` - All bricks including those with raycasting disabled -Other types: `'static'`, `'environment'`, `'terrain'`, `'water'`, `'trigger'`, `'marker'`, `'gamebase'`, `'shapebase'`, `'camera'`, `'staticshape'`, `'vehicleblocker'`, `'explosion'`, `'corpse'`, `'debris'`, `'physicalzone'`, `'staticts'`, `'staticrendered'`, `'damagableitem'` + +`'all'` - Any object +`'player'` - Players or bots +`'item'` - Items +`'vehicle'` - Vehicles +`'projectile'` - Projectiles +`'brick'` - Bricks with raycasting enabled +`'brickalways'` - All bricks including those with raycasting disabled +Other types: `'static'`, `'environment'`, `'terrain'`, `'water'`, `'trigger'`, `'marker'`, `'gamebase'`, `'shapebase'`, `'camera'`, `'staticshape'`, `'vehicleblocker'`, `'explosion'`, `'corpse'`, `'debris'`, `'physicalzone'`, `'staticts'`, `'staticrendered'`, `'damagableitem'` ### Server-Client Communication -`bl.addServerCmd('commandName', function(client, args...) ... end)` - Register a /command on the server -`bl.addClientCmd('commandName', function(args...) ... end)` - Register a client command on the client -`bl.commandToServer('commandName', args...)` - As a client, execute a server command -`bl.commandToClient(client, 'commandName', args...)` - As the server, execute a client command on a specific client -`bl.commandToAll('commandName', args...)` - As the server, execute a client command on all clients + +`bl.addServerCmd('commandName', function(client, args...) ... end)` - Register a /command on the server +`bl.addClientCmd('commandName', function(args...) ... end)` - Register a client command on the client +`bl.commandToServer('commandName', args...)` - As a client, execute a server command +`bl.commandToClient(client, 'commandName', args...)` - As the server, execute a client command on a specific client +`bl.commandToAll('commandName', args...)` - As the server, execute a client command on all clients ### Packages/Hooks -`bl.hook('packageName', 'functionName', 'before'/'after', function(args) ... end)` - Hook a Torque function with a Lua function. -`args` is an array containing the arguments provided to the function. If the hook is `before`, these can be modified before being passed to the parent function. -If `args._return` is set to anything other than nil by a `before` hook, the parent function will not be called, and the function will simply return that value. Also in this case, any `after` hook will not be executed. -In an `after` hook, `args._return` is set to the value returned by the parent function, and can be modified. -`bl.unhook('packageName', 'functionName', 'before'/'after')` - Remove a previously defined hook -`bl.unhook('packageName', 'functionName')` - Remove any previously defined hooks on the function within the package -`bl.unhook('packageName')` - Remove any previously defined hooks within the package + +`bl.hook('packageName', 'functionName', 'before'/'after', function(args) ... end)` - Hook a Torque function with a Lua function. +`args` is an array containing the arguments provided to the function. If the hook is `before`, these can be modified before being passed to the parent function. +If `args._return` is set to anything other than nil by a `before` hook, the parent function will not be called, and the function will simply return that value. Also in this case, any `after` hook will not be executed. +In an `after` hook, `args._return` is set to the value returned by the parent function, and can be modified. +`bl.unhook('packageName', 'functionName', 'before'/'after')` - Remove a previously defined hook +`bl.unhook('packageName', 'functionName')` - Remove any previously defined hooks on the function within the package +`bl.unhook('packageName')` - Remove any previously defined hooks within the package ### Modules and Dependencies -`dofile('Add-Ons/Path/file.lua')` - Execute a Lua file. Relative paths (`./file.lua`) are allowed. `..` is not allowed. - -`require('modulePath.moduleName')` - Load a Lua file or external library. -`require` replaces `.` with `/` in the path, and then searches for files in the following order: -- `./modulePath/moduleName.lua` -- `./modulePath/moduleName/init.lua` -- `modulePath/moduleName.lua` (Relative to game directory) -- `modulePath/moduleName/init.lua` (Relative to game directory) -- `modules/lualib/modulePath/moduleName.lua` -- `modules/lualib/modulePath/moduleName/init.lua` -- `modules/lualib/modulePath/moduleName.dll` - C libraries for Lua can be loaded - -Like in standard Lua, modules loaded using `require` are only executed the first time `require` is called with that path (from anywhere). Subsequent calls simply return the result from the initial execution. To allow hot reloading, use `dofile`. + +`dofile('Add-Ons/Path/file.lua')` - Execute a Lua file. Relative paths (`./file.lua`) are allowed. `..` is not allowed. + +`require('modulePath.moduleName')` - Load a Lua file or external library. +`require` replaces `.` with `/` in the path, and then searches for files in the following order: + +- `./modulePath/moduleName.lua` +- `./modulePath/moduleName/init.lua` +- `modulePath/moduleName.lua` (Relative to game directory) +- `modulePath/moduleName/init.lua` (Relative to game directory) +- `modules/lualib/modulePath/moduleName.lua` +- `modules/lualib/modulePath/moduleName/init.lua` +- `modules/lualib/modulePath/moduleName.dll` - C libraries for Lua can be loaded + +Like in standard Lua, modules loaded using `require` are only executed the first time `require` is called with that path (from anywhere). Subsequent calls simply return the result from the initial execution. To allow hot reloading, use `dofile`. ### File I/O -Lua's builtin file I/O is emulated, and is confined to the same directories as TorqueScript file I/O. -Relative paths (`./`) are allowed. `..` is not allowed. -`file = io.open('./file.txt', 'r'/'w'/'a'/'rb'/'wb'/'ab')` - Open a file -`file:read(numberOfChars/'*a')` - Read an opened file (must be opened in 'r' (read) or 'rb' (read binary) mode) -`file:write(string)` - Write an opened file (must be opened in 'w' (write), 'a' (append), 'wb' or 'ab' mode) -`file:close()` - Close an opened file -Reading files from ZIPs is supported, with caveats. Null characters are not allowed, and \r\n becomes \n. Generally, text formats work, and binary formats don't. + +Lua's builtin file I/O is emulated, and is confined to the same directories as TorqueScript file I/O. +Relative paths (`./`) are allowed. `..` is not allowed. +`file = io.open('./file.txt', 'r'/'w'/'a'/'rb'/'wb'/'ab')` - Open a file +`file:read(numberOfChars/'*a')` - Read an opened file (must be opened in 'r' (read) or 'rb' (read binary) mode) +`file:write(string)` - Write an opened file (must be opened in 'w' (write), 'a' (append), 'wb' or 'ab' mode) +`file:close()` - Close an opened file +Reading files from ZIPs is supported, with caveats. Null characters are not allowed, and \r\n becomes \n. Generally, text formats work, and binary formats don't. When reading from outside ZIPs, binary files are fully supported. ### Object Creation -`bl.new('className')` - Create a new Torque object -`bl.new('className', {fieldName = value, ...})` - Create a new Torque object with the given fields -`bl.new('className objectName', fields?)` - Create a new named Torque object -`bl.new('className objectName:parentName', fields?)` - Create a new named Torque object with inheritance -`bl.datablock('datablockClassName datablockName', fields?)` - Create a new datablock -`bl.datablock('datablockClassName datablockName:parentDatablockName', fields?)` - Create a new datablock with inheritance + +`bl.new('className')` - Create a new Torque object +`bl.new('className', {fieldName = value, ...})` - Create a new Torque object with the given fields +`bl.new('className objectName', fields?)` - Create a new named Torque object +`bl.new('className objectName:parentName', fields?)` - Create a new named Torque object with inheritance +`bl.datablock('datablockClassName datablockName', fields?)` - Create a new datablock +`bl.datablock('datablockClassName datablockName:parentDatablockName', fields?)` - Create a new datablock with inheritance ### Classes and Types -`bl.type('varName', 'type')` - Register the type of a Torque global variable, for conversion when accessing from Lua. Valid types are 'boolean', 'object', 'string' (prevents automatic conversion), and nil (default, applies automatic conversion). -`bl.type('funcName', 'type')` - Register the return type of a Torque function, for conversion when calling from Lua. Valid types are 'bool', 'object', and nil - all other conversion is automatic. Already done for all default functions. -`bl.type('className::funcName', 'type')` - Register the return type of a Torque object method. -`bl.class('className')` - Register an existing Torque class to be used from Lua. Already done for all built-in classes. -`bl.class('className', 'parentClassName')` - Same as above, with inheritance -`bl.boolean(arg)` - Manually convert a Torque boolean (0 or 1) into a Lua boolean. -`bl.object(arg)` - Manually convert a Torque object reference (object ID or name) into a Lua object. -`bl.string(arg)` - Manually convert any automatically-converted Torque value back into a string. This is not as reliable as using `bl.type` to specify the type as a string beforehand. + +`bl.type('varName', 'type')` - Register the type of a Torque global variable, for conversion when accessing from Lua. Valid types are 'boolean', 'object', 'string' (prevents automatic conversion), and nil (default, applies automatic conversion). +`bl.type('funcName', 'type')` - Register the return type of a Torque function, for conversion when calling from Lua. Valid types are 'bool', 'object', and nil - all other conversion is automatic. Already done for all default functions. +`bl.type('className::funcName', 'type')` - Register the return type of a Torque object method. +`bl.class('className')` - Register an existing Torque class to be used from Lua. Already done for all built-in classes. +`bl.class('className', 'parentClassName')` - Same as above, with inheritance +`bl.boolean(arg)` - Manually convert a Torque boolean (0 or 1) into a Lua boolean. +`bl.object(arg)` - Manually convert a Torque object reference (object ID or name) into a Lua object. +`bl.string(arg)` - Manually convert any automatically-converted Torque value back into a string. This is not as reliable as using `bl.type` to specify the type as a string beforehand. ### Vector -`vec = vector{x,y,z}` - Create a vector. Can have any number of elements -`vec1 + vec2` - Add -`vec1 - vec2` - Subtract -`vec * number` - Scale (x\*n, y\*n, z\*n) -`vec / number` - Scale (x/n, y/n, z/n) -`vec ^ number` - Exponentiate (x^n, y^n, z^n) -`vec1 * vec2` - Multiply elements piecewise (x1\*x2, y1\*y2, z1\*z2) -`vec1 / vec2` - Divide elements piecewise (x1/x2, y1/y2, z1/z2) -`vec1 ^ vec2` - Exponentiate elements piecewise (x1^x2, y1^y2, z1^z2) -`-vec` - Negate/invert -`vec1 == vec2` - Compare by value -`vec:length()` - Length -`vec:normalize()` - Preserve direction but scale so magnitude is 1 -`vec:floor()` - Floor each element -`vec:ceil()` - Ceil each element -`vec:abs()` - Absolute value each element -`vec1:dot(vec2)` - Dot product -`vec1:cross(vec2)` - Cross product (Only defined for two vectors of length 3) -`vec:rotateZ(radians)` - Rotate counterclockwise about the Z (vertical) axis -`vec:rotateByAngleId(0/1/2/3)` - Rotate counterclockwise about the Z (vertical) axis in increments of 90 degrees -`vec:tsString()` - Convert to string usable by Torque. Done automatically when a vector is passed to Torque. -`vec1:distance(vec2)` - Distance between two points + +`vec = vector{x,y,z}` - Create a vector. Can have any number of elements +`vec1 + vec2` - Add +`vec1 - vec2` - Subtract +`vec * number` - Scale (x\*n, y\*n, z\*n) +`vec / number` - Scale (x/n, y/n, z/n) +`vec ^ number` - Exponentiate (x^n, y^n, z^n) +`vec1 * vec2` - Multiply elements piecewise (x1\*x2, y1\*y2, z1\*z2) +`vec1 / vec2` - Divide elements piecewise (x1/x2, y1/y2, z1/z2) +`vec1 ^ vec2` - Exponentiate elements piecewise (x1^x2, y1^y2, z1^z2) +`-vec` - Negate/invert +`vec1 == vec2` - Compare by value +`vec:length()` - Length +`vec:normalize()` - Preserve direction but scale so magnitude is 1 +`vec:floor()` - Floor each element +`vec:ceil()` - Ceil each element +`vec:abs()` - Absolute value each element +`vec1:dot(vec2)` - Dot product +`vec1:cross(vec2)` - Cross product (Only defined for two vectors of length 3) +`vec:rotateZ(radians)` - Rotate counterclockwise about the Z (vertical) axis +`vec:rotateByAngleId(0/1/2/3)` - Rotate counterclockwise about the Z (vertical) axis in increments of 90 degrees +`vec:tsString()` - Convert to string usable by Torque. Done automatically when a vector is passed to Torque. +`vec1:distance(vec2)` - Distance between two points `vec2 = vec:copy()` - Clone a vector so its elements can be modified without affecting the original. Usually not needed - the builtin vector functions never modify vectors in-place. ### Matrix + WIP ### Extended Standard Lua Library -`str[index]` -`str[{start,stop}]` -`string.split(str, separator='' (splits into chars), noregex=false)` -`string.bytes(str)` -`string.trim(str, charsToTrim=' \t\r\n')` -`table.empty` -`table.map(func, ...)` -`table.mapk(func, ...)` -`table.map_list(func, ...)` -`table.mapi_list(func, ...)` -`table.swap(tbl)` -`table.reverse(list)` -`table.islist(list)` -`table.append(list, ...)` -`table.join(...)` -`table.contains(tbl, val)` -`table.contains_list(list, val)` -`table.copy(tbl)` -`table.copy_list(list)` -`table.sortcopy(tbl, sortFunction?)` -`table.removevalue(tbl, val)` -`table.removevalue_list(tbl, val)` -`table.tostring(tbl)` -`for char in string.chars(str) do` -`string.escape(str, escapes={['\n']='\\n', etc. (C standard)})` -`string.unescape(str, escapeChar='\\', unescapes={['\\n']='\n', etc.})` -`io.readall(filename)` -`io.writeall(filename, str)` -`math.round(num)` -`math.mod(divisor, modulus)` -`math.clamp(num, min, max)` + +`str[index]` +`str[{start,stop}]` +`string.split(str, separator='' (splits into chars), noregex=false)` +`string.bytes(str)` +`string.trim(str, charsToTrim=' \t\r\n')` +`table.empty` +`table.map(func, ...)` +`table.mapk(func, ...)` +`table.map_list(func, ...)` +`table.mapi_list(func, ...)` +`table.swap(tbl)` +`table.reverse(list)` +`table.islist(list)` +`table.append(list, ...)` +`table.join(...)` +`table.contains(tbl, val)` +`table.contains_list(list, val)` +`table.copy(tbl)` +`table.copy_list(list)` +`table.sortcopy(tbl, sortFunction?)` +`table.removevalue(tbl, val)` +`table.removevalue_list(tbl, val)` +`table.tostring(tbl)` +`for char in string.chars(str) do` +`string.escape(str, escapes={['\n']='\\n', etc. (C standard)})` +`string.unescape(str, escapeChar='\\', unescapes={['\\n']='\n', etc.})` +`io.readall(filename)` +`io.writeall(filename, str)` +`math.round(num)` +`math.mod(divisor, modulus)` +`math.clamp(num, min, max)` ## Type Conversion -When a TorqueScript function is called from Lua or vice-versa, the arguments and return value must be converted between the two languages' type systems. -TorqueScript stores no type information; all values in TorqueScript are strings. So it's necessary to make some inferences when converting values between the two languages. + +When a TorqueScript function is called from Lua or vice-versa, the arguments and return value must be converted between the two languages' type systems. +TorqueScript stores no type information; all values in TorqueScript are strings. So it's necessary to make some inferences when converting values between the two languages. ### From Lua to TorqueScript -- `nil` becomes the empty string "" -- `true` and `false` become "1" and "0" respectively -- A Torque object container becomes its object ID -- A `vector` becomes a string containing three numbers separated by spaces -- A table of two `vector`s becomes a string containing six numbers separated by spaces -- (WIP) A `matrix` is converted into an axis-angle (a "transform"), a string containing seven numbers separated by spaces -- Any `string` is passed directly as a string -- Tables cannot be passed and will throw an error + +- `nil` becomes the empty string "" +- `true` and `false` become "1" and "0" respectively +- A Torque object container becomes its object ID +- A `vector` becomes a string containing three numbers separated by spaces +- A table of two `vector`s becomes a string containing six numbers separated by spaces +- (WIP) A `matrix` is converted into an axis-angle (a "transform"), a string containing seven numbers separated by spaces +- Any `string` is passed directly as a string +- Tables cannot be passed and will throw an error ### From TorqueScript to Lua -- The empty string "" becomes `nil` -- Any numeric value becomes a Lua `number`, except as specified with `bl.type`, which may convert a value into a `boolean` or a Torque object container. -- A string containing two or three numbers separated by single spaces becomes a `vector` -- A string containing six numbers separated by single spaces becomes a table of two vectors, usually defining the corners a bounding box -- (WIP) A string containing seven numbers separated by single spaces is treated as an axis-angle (a "transform"), and is converted into a `matrix` representing the translation and rotation -- Any other string is passed directly as a `string` - -For scenarios where the automatic TorqueScript->Lua conversion rules are insufficient or incorrect, use `bl.type`. + +- The empty string "" becomes `nil` +- Any numeric value becomes a Lua `number`, except as specified with `bl.type`, which may convert a value into a `boolean` or a Torque object container. +- A string containing two or three numbers separated by single spaces becomes a `vector` +- A string containing six numbers separated by single spaces becomes a table of two vectors, usually defining the corners a bounding box +- (WIP) A string containing seven numbers separated by single spaces is treated as an axis-angle (a "transform"), and is converted into a `matrix` representing the translation and rotation +- Any other string is passed directly as a `string` + +For scenarios where the automatic TorqueScript->Lua conversion rules are insufficient or incorrect, use `bl.type`. To convert things by hand, use `bl.object`, `bl.boolean`, or `bl.string`. ## I/O and Safety -All Lua code is sandboxed, and file access is confined to the default directories in the same way TorqueScript is. -BlockLua also has access to any C libraries installed in the `modules/lualib` folder, so be careful throwing things in there. + +All Lua code is sandboxed, and file access is confined to the default directories in the same way TorqueScript is. +BlockLua also has access to any C libraries installed in the `modules/lualib` folder, so be careful throwing things in there. + ### Unsafe Mode -BlockLua can be built in Unsafe Mode by specifying the `-DBLLUA_UNSAFE` compiler flag. This removes the sandboxing of Lua code, allowing it to access any file and use any library, including ffi. -A more limited option is `-DBLLUA_ALLOWFFI`, which allows the use of the `ffi` library. This can still be exploited to grant all the same access as full unsafe mode. -Please do not publish add-ons that require either of these. + +BlockLua can be built in Unsafe Mode by specifying the `-DBLLUA_UNSAFE` compiler flag. This removes the sandboxing of Lua code, allowing it to access any file and use any library, including ffi. +A more limited option is `-DBLLUA_ALLOWFFI`, which allows the use of the `ffi` library. This can still be exploited to grant all the same access as full unsafe mode. +Please do not publish add-ons that require either of these. ## Compiling -With any *32-bit* variant of GCC installed (such as MinGW or MSYS2), run the following command in the repo directory: -`g++ src/bllua4.cpp -o BlockLua.dll -m32 -shared -static-libgcc -Isrc -Iinc/tsfuncs -Iinc/lua -lpsapi -L. -llua5.1` - -LuaJIT (lua5.1.dll) can be obtained from https://luajit.org/ +With any _32-bit_ variant of GCC installed (such as MinGW or MSYS2), run the following command in the repo directory: +`g++ src/bllua4.cpp -o BlockLua.dll -m32 -shared -static-libgcc -Isrc -Iinc/tsfuncs -Iinc/lua -lpsapi -L. -llua5.1` + +LuaJIT (lua5.1.dll) can be obtained from https://luajit.org/ -- 2.49.1 From 33f5ec9bbe45bc842afaf5522fd65017a14cfbbd Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Mon, 8 Dec 2025 03:32:12 -0500 Subject: [PATCH 18/22] Ensure fn is a string path --- src/lua-env-safe.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lua-env-safe.lua b/src/lua-env-safe.lua index 2249c07..079bd8a 100644 --- a/src/lua-env-safe.lua +++ b/src/lua-env-safe.lua @@ -62,6 +62,9 @@ local disallowed_exts = tmap { -- Return: clean file path if allowed (or nil if disallowed), -- error string (or nil if allowed) local function safe_path(fn, readonly) + if type(fn) ~= 'string' then + return nil, 'Filename must be a string' + end fn = fn:gsub('\\', '/') fn = fn:gsub('^ +', '') fn = fn:gsub(' +$', '') -- 2.49.1 From a7db0d8e814e635538e1bf80e25ad4075d846cb1 Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Mon, 8 Dec 2025 03:32:31 -0500 Subject: [PATCH 19/22] Update lua-env.lua --- src/lua-env.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lua-env.lua b/src/lua-env.lua index 573088d..309ea69 100644 --- a/src/lua-env.lua +++ b/src/lua-env.lua @@ -18,6 +18,8 @@ end -- Called when pcall fails on a ts->lua call, used to print detailed error info function _bllua_on_error(err) + -- Convert error to string if it's not already + err = tostring(err) err = err:match(': (.+)$') or err local tracelines = { err } local level = 2 @@ -25,7 +27,7 @@ function _bllua_on_error(err) local info = debug.getinfo(level) if not info then break end local filename = debug.getfilename(level) or info.short_src - local funcname = info.name + local funcname = info.name or '' if funcname == 'dofile' then break end table.insert(tracelines, string.format('%s:%s in function \'%s\'', filename, -- 2.49.1 From d494f02fe39354d44daeb08081fcd35689b4aafa Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Mon, 8 Dec 2025 03:32:56 -0500 Subject: [PATCH 20/22] fix table.empty being inverted --- src/util/std.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/std.lua b/src/util/std.lua index 1774162..1a40232 100644 --- a/src/util/std.lua +++ b/src/util/std.lua @@ -4,7 +4,7 @@ -- Table / List -- Whether a table contains no keys function table.empty(t) - return next(t) ~= nil + return next(t) == nil end -- Apply a function to each key in a table -- 2.49.1 From c5dc8b15f9f6d1f79c94c7056eb80c5a2b6a8575 Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Mon, 8 Dec 2025 15:26:53 -0500 Subject: [PATCH 21/22] Delete BlockLua.dll --- BlockLua.dll | Bin 379980 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 BlockLua.dll diff --git a/BlockLua.dll b/BlockLua.dll deleted file mode 100644 index e50fcb0886aa87405e149e92442cdbdd372bf20d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 379980 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℘Dty}4+BILNl9-OaVC-8E1k)2 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ⓈI4<6CD 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 OEo1Pb1%mkm_x}gR1ovJ5 -- 2.49.1 From 71b73c816bdc663f5b5fde96dcb9b7b9a0fbb9bd Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Mon, 8 Dec 2025 15:29:41 -0500 Subject: [PATCH 22/22] ... --- CMakeLists.txt | 8 ++++---- make.bat => build.bat | 2 +- compile.bat | 13 ------------- compiling.md | 2 +- 4 files changed, 6 insertions(+), 19 deletions(-) rename make.bat => build.bat (86%) delete mode 100644 compile.bat diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d38335..58cdc8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,13 +5,13 @@ project(BlockLua CXX) # Export compile_commands.json for VSCode IntelliSense set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -# Output directories to mirror compile.bat's build folder +# Output directories to mirror build.bat's build folder set(OUTPUT_DIR ${CMAKE_SOURCE_DIR}/build) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${OUTPUT_DIR}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${OUTPUT_DIR}) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${OUTPUT_DIR}) -# Global compile options to mirror compile.bat +# Global build options to mirror build.bat add_compile_options( -Wall -Werror @@ -27,14 +27,14 @@ include_directories( ${CMAKE_SOURCE_DIR}/inc/lua ) -# Link directories (for -L.) and libraries from compile.bat +# Link directories (for -L.) and libraries from build.bat link_directories( ${CMAKE_SOURCE_DIR} ) # Safe DLL add_library(BlockLua SHARED src/bllua4.cpp) -# Ensure output name matches compile.bat +# Ensure output name matches build.bat set_target_properties(BlockLua PROPERTIES OUTPUT_NAME "BlockLua") # Linker flags and libraries if(MSVC) diff --git a/make.bat b/build.bat similarity index 86% rename from make.bat rename to build.bat index 1884ee2..3025f37 100644 --- a/make.bat +++ b/build.bat @@ -1,7 +1,7 @@ @echo off cd /d %~dp0 -REM Ensure MinGW32 toolchain is first in PATH (matches compile.bat) +REM Ensure MinGW32 toolchain is first in PATH set "PATH=C:\msys64\mingw32\bin;%PATH%" REM Configure CMake (generate into build/) diff --git a/compile.bat b/compile.bat deleted file mode 100644 index 660075f..0000000 --- a/compile.bat +++ /dev/null @@ -1,13 +0,0 @@ -@echo off -cd /d %~dp0 - -set "PATH=C:\msys64\mingw32\bin;%PATH%" - -if not exist build mkdir build - -set buildargs=-Wall -Werror -m32 -shared -Isrc -Iinc/tsfuncs -Iinc/lua -lpsapi -L. -llua5.1 -static-libgcc -static-libstdc++ - -echo on -g++ src/bllua4.cpp %buildargs% -o build\BlockLua.dll -g++ -DBLLUA_UNSAFE src/bllua4.cpp %buildargs% -o build\BlockLua-Unsafe.dll -@echo off diff --git a/compiling.md b/compiling.md index d49d0d4..8bf75dd 100644 --- a/compiling.md +++ b/compiling.md @@ -27,7 +27,7 @@ What these packages are for: - Run the script: ```powershell -compile.bat +build.bat ``` What the script does: -- 2.49.1