From 8b9cb56857c282b800659a88d30e7684e1e86909 Mon Sep 17 00:00:00 2001 From: Ulrich Block Date: Wed, 27 Nov 2013 20:26:19 +0100 Subject: [PATCH] #29 Replace Quakestat --- web/images/games/icons/sauerbratenremod.png | Bin 0 -> 1918 bytes web/images/games/icons/ut2004.png | Bin 0 -> 2366 bytes web/images/games/icons/ut99.png | Bin 0 -> 2566 bytes web/install/update_411-420.php | 73 ++ web/statuscheck.php | 702 +++++--------- web/stuff/images.php | 92 +- web/stuff/tables_add.php | 10 +- web/stuff/tables_repair.php | 6 +- web/template/default/admin_images_add.tpl | 13 +- web/template/default/admin_images_md.tpl | 13 +- web/third_party/gameq/GameQ.php | 871 ++++++++++++++++++ web/third_party/gameq/LICENSE | 674 ++++++++++++++ web/third_party/gameq/gameq/buffer.php | 378 ++++++++ web/third_party/gameq/gameq/filters.php | 34 + web/third_party/gameq/gameq/filters/core.php | 55 ++ .../gameq/gameq/filters/normalise.php | 193 ++++ .../gameq/gameq/filters/stripcolor.php | 76 ++ web/third_party/gameq/gameq/protocols.php | 37 + web/third_party/gameq/gameq/protocols/aa.php | 30 + web/third_party/gameq/gameq/protocols/aa3.php | 30 + .../gameq/gameq/protocols/aa3pre32.php | 425 +++++++++ .../gameq/gameq/protocols/alienswarm.php | 28 + web/third_party/gameq/gameq/protocols/aoc.php | 28 + .../gameq/gameq/protocols/armedassault.php | 30 + .../gameq/gameq/protocols/armedassault2.php | 44 + .../gameq/gameq/protocols/armedassault2oa.php | 30 + .../gameq/gameq/protocols/armedassault3.php | 33 + web/third_party/gameq/gameq/protocols/ase.php | 148 +++ web/third_party/gameq/gameq/protocols/avp.php | 30 + .../gameq/gameq/protocols/avp2.php | 32 + .../gameq/gameq/protocols/bf1942.php | 30 + web/third_party/gameq/gameq/protocols/bf2.php | 56 ++ .../gameq/gameq/protocols/bf2142.php | 30 + web/third_party/gameq/gameq/protocols/bf3.php | 329 +++++++ .../gameq/gameq/protocols/bfbc2.php | 249 +++++ web/third_party/gameq/gameq/protocols/bfv.php | 30 + .../gameq/gameq/protocols/brink.php | 28 + web/third_party/gameq/gameq/protocols/cod.php | 30 + .../gameq/gameq/protocols/cod2.php | 30 + .../gameq/gameq/protocols/cod4.php | 30 + .../gameq/gameq/protocols/codmw3.php | 30 + .../gameq/gameq/protocols/coduo.php | 30 + .../gameq/gameq/protocols/codwaw.php | 30 + .../gameq/gameq/protocols/core.php | 637 +++++++++++++ .../gameq/gameq/protocols/crysis.php | 30 + .../gameq/gameq/protocols/crysis2.php | 30 + .../gameq/gameq/protocols/crysiswarhead.php | 30 + .../gameq/gameq/protocols/crysiswars.php | 30 + .../gameq/gameq/protocols/cs16.php | 49 + .../gameq/gameq/protocols/cscz.php | 49 + .../gameq/gameq/protocols/csgo.php | 28 + web/third_party/gameq/gameq/protocols/css.php | 28 + .../gameq/gameq/protocols/cube2.php | 175 ++++ .../gameq/gameq/protocols/dayz.php | 30 + web/third_party/gameq/gameq/protocols/dod.php | 28 + .../gameq/gameq/protocols/dods.php | 28 + .../gameq/gameq/protocols/doom3.php | 163 ++++ web/third_party/gameq/gameq/protocols/et.php | 30 + .../gameq/gameq/protocols/etqw.php | 225 +++++ .../gameq/gameq/protocols/fear.php | 30 + web/third_party/gameq/gameq/protocols/ffe.php | 28 + .../gameq/gameq/protocols/ffow.php | 246 +++++ .../gameq/gameq/protocols/gamespy.php | 216 +++++ .../gameq/gameq/protocols/gamespy2.php | 261 ++++++ .../gameq/gameq/protocols/gamespy3.php | 446 +++++++++ .../gameq/gameq/protocols/gamespy4.php | 30 + .../gameq/gameq/protocols/gmod.php | 28 + .../gameq/gameq/protocols/gore.php | 30 + .../gameq/gameq/protocols/graw.php | 30 + .../gameq/gameq/protocols/graw2.php | 30 + .../gameq/gameq/protocols/hl2dm.php | 28 + .../gameq/gameq/protocols/hldm.php | 28 + .../gameq/gameq/protocols/homefront.php | 28 + .../gameq/gameq/protocols/http.php | 42 + .../gameq/gameq/protocols/insurgency.php | 28 + .../gameq/gameq/protocols/killingfloor.php | 78 ++ web/third_party/gameq/gameq/protocols/l4d.php | 28 + .../gameq/gameq/protocols/l4d2.php | 28 + .../gameq/gameq/protocols/minecraft.php | 43 + .../gameq/gameq/protocols/minequery.php | 133 +++ .../gameq/gameq/protocols/mohaa.php | 30 + .../gameq/gameq/protocols/mohsh.php | 30 + .../gameq/gameq/protocols/mohwf.php | 131 +++ web/third_party/gameq/gameq/protocols/mta.php | 30 + web/third_party/gameq/gameq/protocols/ns.php | 28 + web/third_party/gameq/gameq/protocols/ns2.php | 37 + .../gameq/gameq/protocols/quake2.php | 205 +++++ .../gameq/gameq/protocols/quake3.php | 200 ++++ .../gameq/gameq/protocols/quake4.php | 44 + .../gameq/gameq/protocols/redeclipse.php | 212 +++++ .../gameq/gameq/protocols/redfaction.php | 121 +++ .../gameq/gameq/protocols/redorchestra.php | 30 + .../gameq/gameq/protocols/redorchestra2.php | 32 + .../gameq/gameq/protocols/rtcw.php | 30 + .../gameq/gameq/protocols/samp.php | 239 +++++ .../gameq/gameq/protocols/sof2.php | 30 + .../gameq/gameq/protocols/soldat.php | 30 + .../gameq/gameq/protocols/source.php | 453 +++++++++ .../gameq/gameq/protocols/stalker.php | 30 + .../gameq/gameq/protocols/teamspeak2.php | 338 +++++++ .../gameq/gameq/protocols/teamspeak3.php | 372 ++++++++ .../gameq/gameq/protocols/teeworlds.php | 109 +++ .../gameq/gameq/protocols/terraria.php | 48 + web/third_party/gameq/gameq/protocols/tf2.php | 28 + web/third_party/gameq/gameq/protocols/tfc.php | 28 + .../gameq/gameq/protocols/tribes2.php | 204 ++++ .../gameq/gameq/protocols/tshock.php | 131 +++ .../gameq/gameq/protocols/unreal2.php | 302 ++++++ web/third_party/gameq/gameq/protocols/ut.php | 30 + .../gameq/gameq/protocols/ut2004.php | 30 + web/third_party/gameq/gameq/protocols/ut3.php | 77 ++ .../gameq/gameq/protocols/ventrilo.php | 400 ++++++++ .../gameq/gameq/protocols/warsow.php | 71 ++ .../gameq/gameq/protocols/zombiemaster.php | 28 + web/third_party/gameq/gameq/protocols/zps.php | 28 + web/third_party/gameq/gameq/result.php | 117 +++ 116 files changed, 12251 insertions(+), 508 deletions(-) create mode 100644 web/images/games/icons/sauerbratenremod.png create mode 100644 web/images/games/icons/ut2004.png create mode 100644 web/images/games/icons/ut99.png create mode 100644 web/third_party/gameq/GameQ.php create mode 100644 web/third_party/gameq/LICENSE create mode 100644 web/third_party/gameq/gameq/buffer.php create mode 100644 web/third_party/gameq/gameq/filters.php create mode 100644 web/third_party/gameq/gameq/filters/core.php create mode 100644 web/third_party/gameq/gameq/filters/normalise.php create mode 100644 web/third_party/gameq/gameq/filters/stripcolor.php create mode 100644 web/third_party/gameq/gameq/protocols.php create mode 100644 web/third_party/gameq/gameq/protocols/aa.php create mode 100644 web/third_party/gameq/gameq/protocols/aa3.php create mode 100644 web/third_party/gameq/gameq/protocols/aa3pre32.php create mode 100644 web/third_party/gameq/gameq/protocols/alienswarm.php create mode 100644 web/third_party/gameq/gameq/protocols/aoc.php create mode 100644 web/third_party/gameq/gameq/protocols/armedassault.php create mode 100644 web/third_party/gameq/gameq/protocols/armedassault2.php create mode 100644 web/third_party/gameq/gameq/protocols/armedassault2oa.php create mode 100644 web/third_party/gameq/gameq/protocols/armedassault3.php create mode 100644 web/third_party/gameq/gameq/protocols/ase.php create mode 100644 web/third_party/gameq/gameq/protocols/avp.php create mode 100644 web/third_party/gameq/gameq/protocols/avp2.php create mode 100644 web/third_party/gameq/gameq/protocols/bf1942.php create mode 100644 web/third_party/gameq/gameq/protocols/bf2.php create mode 100644 web/third_party/gameq/gameq/protocols/bf2142.php create mode 100644 web/third_party/gameq/gameq/protocols/bf3.php create mode 100644 web/third_party/gameq/gameq/protocols/bfbc2.php create mode 100644 web/third_party/gameq/gameq/protocols/bfv.php create mode 100644 web/third_party/gameq/gameq/protocols/brink.php create mode 100644 web/third_party/gameq/gameq/protocols/cod.php create mode 100644 web/third_party/gameq/gameq/protocols/cod2.php create mode 100644 web/third_party/gameq/gameq/protocols/cod4.php create mode 100644 web/third_party/gameq/gameq/protocols/codmw3.php create mode 100644 web/third_party/gameq/gameq/protocols/coduo.php create mode 100644 web/third_party/gameq/gameq/protocols/codwaw.php create mode 100644 web/third_party/gameq/gameq/protocols/core.php create mode 100644 web/third_party/gameq/gameq/protocols/crysis.php create mode 100644 web/third_party/gameq/gameq/protocols/crysis2.php create mode 100644 web/third_party/gameq/gameq/protocols/crysiswarhead.php create mode 100644 web/third_party/gameq/gameq/protocols/crysiswars.php create mode 100644 web/third_party/gameq/gameq/protocols/cs16.php create mode 100644 web/third_party/gameq/gameq/protocols/cscz.php create mode 100644 web/third_party/gameq/gameq/protocols/csgo.php create mode 100644 web/third_party/gameq/gameq/protocols/css.php create mode 100644 web/third_party/gameq/gameq/protocols/cube2.php create mode 100644 web/third_party/gameq/gameq/protocols/dayz.php create mode 100644 web/third_party/gameq/gameq/protocols/dod.php create mode 100644 web/third_party/gameq/gameq/protocols/dods.php create mode 100644 web/third_party/gameq/gameq/protocols/doom3.php create mode 100644 web/third_party/gameq/gameq/protocols/et.php create mode 100644 web/third_party/gameq/gameq/protocols/etqw.php create mode 100644 web/third_party/gameq/gameq/protocols/fear.php create mode 100644 web/third_party/gameq/gameq/protocols/ffe.php create mode 100644 web/third_party/gameq/gameq/protocols/ffow.php create mode 100644 web/third_party/gameq/gameq/protocols/gamespy.php create mode 100644 web/third_party/gameq/gameq/protocols/gamespy2.php create mode 100644 web/third_party/gameq/gameq/protocols/gamespy3.php create mode 100644 web/third_party/gameq/gameq/protocols/gamespy4.php create mode 100644 web/third_party/gameq/gameq/protocols/gmod.php create mode 100644 web/third_party/gameq/gameq/protocols/gore.php create mode 100644 web/third_party/gameq/gameq/protocols/graw.php create mode 100644 web/third_party/gameq/gameq/protocols/graw2.php create mode 100644 web/third_party/gameq/gameq/protocols/hl2dm.php create mode 100644 web/third_party/gameq/gameq/protocols/hldm.php create mode 100644 web/third_party/gameq/gameq/protocols/homefront.php create mode 100644 web/third_party/gameq/gameq/protocols/http.php create mode 100644 web/third_party/gameq/gameq/protocols/insurgency.php create mode 100644 web/third_party/gameq/gameq/protocols/killingfloor.php create mode 100644 web/third_party/gameq/gameq/protocols/l4d.php create mode 100644 web/third_party/gameq/gameq/protocols/l4d2.php create mode 100644 web/third_party/gameq/gameq/protocols/minecraft.php create mode 100644 web/third_party/gameq/gameq/protocols/minequery.php create mode 100644 web/third_party/gameq/gameq/protocols/mohaa.php create mode 100644 web/third_party/gameq/gameq/protocols/mohsh.php create mode 100644 web/third_party/gameq/gameq/protocols/mohwf.php create mode 100644 web/third_party/gameq/gameq/protocols/mta.php create mode 100644 web/third_party/gameq/gameq/protocols/ns.php create mode 100644 web/third_party/gameq/gameq/protocols/ns2.php create mode 100644 web/third_party/gameq/gameq/protocols/quake2.php create mode 100644 web/third_party/gameq/gameq/protocols/quake3.php create mode 100644 web/third_party/gameq/gameq/protocols/quake4.php create mode 100644 web/third_party/gameq/gameq/protocols/redeclipse.php create mode 100644 web/third_party/gameq/gameq/protocols/redfaction.php create mode 100644 web/third_party/gameq/gameq/protocols/redorchestra.php create mode 100644 web/third_party/gameq/gameq/protocols/redorchestra2.php create mode 100644 web/third_party/gameq/gameq/protocols/rtcw.php create mode 100644 web/third_party/gameq/gameq/protocols/samp.php create mode 100644 web/third_party/gameq/gameq/protocols/sof2.php create mode 100644 web/third_party/gameq/gameq/protocols/soldat.php create mode 100644 web/third_party/gameq/gameq/protocols/source.php create mode 100644 web/third_party/gameq/gameq/protocols/stalker.php create mode 100644 web/third_party/gameq/gameq/protocols/teamspeak2.php create mode 100644 web/third_party/gameq/gameq/protocols/teamspeak3.php create mode 100644 web/third_party/gameq/gameq/protocols/teeworlds.php create mode 100644 web/third_party/gameq/gameq/protocols/terraria.php create mode 100644 web/third_party/gameq/gameq/protocols/tf2.php create mode 100644 web/third_party/gameq/gameq/protocols/tfc.php create mode 100644 web/third_party/gameq/gameq/protocols/tribes2.php create mode 100644 web/third_party/gameq/gameq/protocols/tshock.php create mode 100644 web/third_party/gameq/gameq/protocols/unreal2.php create mode 100644 web/third_party/gameq/gameq/protocols/ut.php create mode 100644 web/third_party/gameq/gameq/protocols/ut2004.php create mode 100644 web/third_party/gameq/gameq/protocols/ut3.php create mode 100644 web/third_party/gameq/gameq/protocols/ventrilo.php create mode 100644 web/third_party/gameq/gameq/protocols/warsow.php create mode 100644 web/third_party/gameq/gameq/protocols/zombiemaster.php create mode 100644 web/third_party/gameq/gameq/protocols/zps.php create mode 100644 web/third_party/gameq/gameq/result.php diff --git a/web/images/games/icons/sauerbratenremod.png b/web/images/games/icons/sauerbratenremod.png new file mode 100644 index 0000000000000000000000000000000000000000..5c8393c629b59877496cacf585924b7557a1bd62 GIT binary patch literal 1918 zcmV-^2Z8vBP)+Jh zyUh)UcX8)!`}pRuLma*7TK4VP!tR~(T(*6I`3((PQ#Gt~l!BGOGUN332GBL_D{BI* zOzhs)be(ON%yQ|%B%4~}$qMPj07LyD1|lKCK_9xV9bFU96ZEY%Dmi8*Yb>{$OpZE~ z>;z8PVfD&IovADYwUnh?G_lQL zMrtNU_Ra9ay9&H^f0}n5wK)0h2!A-9;rDm zx~gF4DvBb%E`;bjFCaD;e$+N&6s!d0qDkILP{|Lmf49RkM`HZqiy_{)HO%Wb_HyFD zAg^rG`N4$X-l)WF0fqgsURK9!w#|;_Gp8}(J?mFD$LjOER2_E zP1T6@htXAmdbSut6DW%Ej;`zebpza;#w}JRhMkRLXA|Ty!;}jd8daMeTjyC`m|||G zPHU>p(oBsFVcjPToq;9C4VPs?wgW(Vng`R?A_fqXYw9T!w}wWOP+v=qf^yQ55Bbs;Z^`*7K17 zphF1Z?e=+&5A}ym7wr_qT#|yFqE@kK*7M}e7}?|i@xdMj`T_*IyD)SWLlftozc?cl z7WD6Tt;^GSw6Dj1 zswdDzms>~IwO?tPR=&`zivj>0k|arrqO=V|KcZ>cK_Nu+Vsb7T@c)`m0RIMa`s^#1 z$A(`3000_vMObt}b#!QNasX9qWnp9>Q+acAWo>gTAW3dxF3BA}b^rhX07*qoM6N<$ Eg3@cbPXGV_ literal 0 HcmV?d00001 diff --git a/web/images/games/icons/ut2004.png b/web/images/games/icons/ut2004.png new file mode 100644 index 0000000000000000000000000000000000000000..96538bb6c0028b8ab84e532c03436aa0ef0d11dc GIT binary patch literal 2366 zcmV-E3BmS>P)5fMQ^ zi^`#(c0CZUdKE;aEmVq#cdaO1h-dAhuC=aMYm2VkwQKFzX|+^a?PXQ|`@CbjZFgp; zvon48UcQ&#@8O&IetrP_e$V+`;eQDHe+m$Zq_H7FAz7_birp?xZlZRy0~1SL zI#HOv_E=fTx)ZtCH5<(-wgRGv*sPx*#dPvphvTKQwzLUzA{63eYhrjs zie8e%W09D@SwJR}`e%6DNek!CS=+v9?gw?{>mOxiTt$}eE{co3L}C6zcw9fgYJP;2 zS%X90Ma!G7~Q?;Pnr=03Ai8;SoKq?UjWM}()Z|vN1 z_Qr+YA9k*shvJ+;U75*Wcq4b6%v+uTAp(C+{(E6Sak^V7X_#kvVUrdG3!|Wo}n+UpLqJk#ZK&eu^IVU zH{fwUf!XvOJn4^MvwjVY<~9loKgRL?f8w)?z1TJ{8}X`nkcFRvM0y%_d)J6DIoWHD zX&D&dPav`pUp z+KSwRd=6RgGnY;baHczarv`Rkd+?8=xOa6Q?q5HQdslYi%Xc?EIk$V+Q@d#ugrS|_ z@m9lSU4pBF+i>r%ZTNJc0fQYS&_v7!o3jWUP8~R$YA_g8FdD0dwQ-ubs!H$V3&&e- zx@`KqpA?9W*2L`H)qU~Pq1Q3+YBjbuSK;p`TXFxrZMZ+Q0q3{Rf<2)gJpOVp8I{oM z=3?+rAHI682X{|3;jNBxsN^%i<}3i4T?z&x2Q+#)^t!T70V`1;x&e08}8=XcD3HNFBYRt@OXJm_={ zIDMoa4?jGB`$L^L)6)Q@WD?k%2JrbspwsQ3P&1*4%KN6cxM<1qvnt1ZFxYmhds*fM zKLCDQE+UUya%<=E^w0h148|C$`-FVsv=$=7vI%Ve?8q$43+u{G@2_= zD&K@AdJnd4*o5|KHTs$i*jleevf2VF)dwc46bXg~^u5xE!%Ga<-(>9PfdY&xEWVLyPEK>D#mr9B2x_UM0H)u$Q@c)YX@2g8Rz~v`7y2rirkCCk zhjk%bei{j)F|Ko-6R+dG=kGh2tvGu7yvsz#F91`5>!GTDI`O&JEd7UQEG1-RU9 zN8h{zNCg%UNfrSqRxs zn3ui4ZC&^vRJ0c&Q4c~wUIdrB7-6Cpu>F-4qX%CwV}D~Dw$`d)jC6uR%>aesLTq#~ zj&7WX+xsWu_TEw)d%*&!&;mN$g5Y2Y$V56^cKdy6YRUv2n@VEPh_ryQW5xjR^Sjnx zb?4RR6Ne(DG@aXB)D#=r`7lJ-27zEPIGk#*n9~s&)`+f_WlwtQHP|~>gYGIhk|J!N zP@EuBJcx-Z#-WZ`81C}mqis1jzSIN}R|^6`1~yADYB%bU?@jBaP{_1rzTv)}h9jGr z>_g!KqRy8&u2HLPdcbDag3GM~hcg9y{!9o%XJfl~Or(S{=5b6B`wEza)i z!biI%;mUdgu5Pg5^!g0+wpQX$*Ao1x%=LvfQc*{z(TG1OK&J$RxsB2T1s-jK$(WEZ zv1Hm+63GfG)dLE}1&J^lGpEeK@Zo0M>~Y}zmyLM8!;0bVOx)_9flu~U;L3UfjxLJ9 z+bxOs^9mg1`2t!B9B?VQ4+?9EXXfdrj4EW zJ%Nw{GQ|cq(+j7;gcXzG@a9T023IBGe5(o9I&HYwla8U5Sgaf$h9XlaS}G&aU8hF- zw5X@GS&`TEYEg|qAfWtWk`W1k+!#4?hEBz4@)KuSx3PtK;7Aq12 zf*658QUnJ{KqZSI^KWE&^EaAIsfFQkg@{Nbjrp~$`OJdPCI^Z`XcT{c z{{RMqPE)B=T4Qp`jEoH5ZkNk@!fy8rIPBgdZgDX=Urx>cV=W?2ABa<1_m>TB`^d5aREUM zLTFJ+6e18MwT`BtmXexVu9=5`BXn}f@|errJN;cG_y4t?IeNcGZ{;xki^SU7#cT_t46vi|%El4mqON zNo4>$0I>fH0D$D_Bu$-JlyT{;lJxT%7LU1pe&^eC=cg0Ydh;v#;pktfar;8sj@r3* z*DafNWBGz<*Vit4`=_~YPQMiSMC2yMnDKuC0Kgc7WpKDp{fen+mn{Lt6#$-=G^*Fv zPEEh@<=={E%iK&`ZrXq|I<58t##k|cYycwxWB^D75Dq}wZ2$nAz#(>WcKC(Y3e%5> zqBt6W4*(H>yOT~+K4xIpHM5WRM*xxmxB*bu^}z$+I##ze0D$ovd;HCuz8A;lB=2$ z4GMnpo|*-=`kk9wqrx7mW(!mXQ1GW-9yxM;A=Vm2-SAnOtU zK$M(A<=`;CfA3gXW?Q#p)^&fAX$Ao2?q)2CvY3Gb;+ywvT}~fPN};ms#5kCJUYC!2x`s*$^P6>&lm|fV7(_0Uh4LJy@3e;?3S!py z{QQ=0Pd89$Y9C4nH8qQZPzoT+Am`7h1pa*h^7{aSAm|hdMbv$&cEuY3j1PB}x9pld zl1?ApWy>0#en{YjL5wlAMy1T_XYu>ahhU14Cp_E=znX1@`=0s_`{799XLInRBtF3(C$wK;V2bPat}{a)+P zl%x}L@{)eD`un^Gz!YxrtBQ_{+#VendF0UkeYP9l)X}BSU!`ZFe5t3G^9BH}9TgTu z@%4y6&oDf+G` z$}#{*PLg8u?zP1?Z(lq}x6gb+XEzs9(aqP6E~3qI)2Vq&3C+&#Lxm~U?*Rnav+#V} z-|7ADJ}aWLpOjL?OL0`c>RCz+b|IZsS_nX?RjbFZnlavXVEH(jlpH_;$L#>10RZPn zZ}ss>X_l)1OaOR+VYru@^FnJ-uU%1ntrs@cRobpLtfAA@x%9)XGAbNkrVvm4egN(r z!c!~cV~^Jq+0Jd6NW13@rk(GOpv=A=q?dFH04M>(OdFSZ?!f8^RFo7%Dv>{Kzg=)Q z>t&}ZiiWod9LoiuPz!A2%R_=2cdnjlyLxgb-8#F2emcCE8f!A?`>lmEF5W~|AJFI5a>tCTm^OI?7c>)a!aUq>ny8wV3fTWU(Q}#e4b@~Elqc{)=wf-ZbkLgV_ID9FcSI{@jA z;l(AsGdBLWQ?(Q5(EJqIG9!V8SzSn{xeveqgqJ>_c4fk_2vYLw>ka^Co?-9U1QY#Z zO{q;TlNAH-R*BrJJ|52N1I(tTk7o?BovfQkO?5BOxsBs!Oso(2d${g(6fGF{)QsGy zdnc=l=wL-MZJC)!34sQZG@AJxoezNfW4-s)qP$>FDBgnm_B+ z+dR+bxoE}0k`dOUU#xub*W(`+(0AKr(b)|J^!?T{%8v3Oi_x_Kfb$;!EOz|R&|i+N z&ZC3#2GGXW`cbT(o-``WI}QL^PZ#Hpr#u}?l8mpi$A%2Rq!^>xwzDjX&eV>h$*+vP z$#YyXfH-GKQM0>ZknO_OVru?-KAqc;PZNh(X-FUA4*)aSHQyKAuBV1&*;GF} zp4OK{P^3{y8kO1+K<%X0*A*m(l2YW#I<6rD(9c`1XgR!i80}dxo`QP@)B~`fed@&& z3=RJFO8vWZs`@!PS(#0{7v)g(Zu0G0k8wtX#9~-PWt$d`|o3#M6e-XsVqM zP5n$d>Y>v)0%%04V^J_GiAsrMP7Egaq31W(MtjJCRPT8BTBHM9_(K zqv*h@(X{aS2r5YEMZp2)pC=~yw;fuTO;yhXQr*ijk)qX--_eBX?boieK5vKDSi8>Ryg0iawZ6C0E=*-|?o z=a7Uifae&nO(Af1%m#%`k_&ej;|lE%4;Y)S{GK-h<_xmB-wZRm{m5}#)&s=tKn8o( z-sr-z*)#3Kz-qN}u`SH(d7I~0J{+uAmjDhBMg~j?@zPyP2y|-IC}bNsj*oHt?dqJ# z1Fnj(eoB$w)IY$fb#R2`vP=}-K)b>D(>D(_l=e26tq{0>hImLnhx-^>{akw-;5lvz zfH;8p0npoz#TP)LDDcxPzHSF|o(gU)7#e*y#BBV6G4?C~*M|cC7jvlu&<8-7z_7>U zqHl=^@NNtBakcgKFx)dctAF8982(`nS3c#fL~V3hq`?ZdL}7CQ+acA cWo>gTAW3dxF3BA}b^rhX07*qoM6N<$g8q}!kpKVy literal 0 HcmV?d00001 diff --git a/web/install/update_411-420.php b/web/install/update_411-420.php index a38a59af..438daae5 100644 --- a/web/install/update_411-420.php +++ b/web/install/update_411-420.php @@ -95,6 +95,79 @@ Unfortunately errors have slipped in 4.10. In addition the update revealed that $query = $sql->prepare("INSERT INTO `modules` (`id`,`file`,`get`,`sub`,`type`,`active`) VALUES (5,'','le','','C','N') ON DUPLICATE KEY UPDATE `active`=VALUES(`active`)"); $query->execute(); } + + $query = $sql->prepare("ALTER TABLE `servertypes` ADD COLUMN `gameq` varchar(255) NULL AFTER `qstat`"); + $query->execute(); + + // Most accurate based on appID + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='css' WHERE `appID`=232330 LIMIT 1"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='dods' WHERE `appID`=232290 LIMIT 1"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='l4d' WHERE `appID`=550 LIMIT 1"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='l4d2' WHERE `appID`=222860 LIMIT 1"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='aoc' WHERE `appID`=17515 LIMIT 1"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='hl2dm' WHERE `appID`=232370 LIMIT 1"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='insurgency' WHERE `appID`=17705 LIMIT 1"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='tf2' WHERE `appID`=232250 LIMIT 1"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='csgo' WHERE `appID`=740 LIMIT 1"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='killingfloor' WHERE `appID`=215360 LIMIT 1"); + $query->execute(); + + // Accurate, based on easy-wi/qstat query + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='minecraft' WHERE `qstat`='minecraft'"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='samp' WHERE `qstat`='gtasamp'"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='Mta' WHERE `qstat`='mtasa'"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='teeworlds' WHERE `qstat`='teeworlds'"); + $query->execute(); + + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='warsow' WHERE `qstat`='warsows'"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='et' WHERE `qstat`='woets'"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='ut' WHERE `qstat`='uns'"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='ut2004' WHERE `qstat`='ut2004s'"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='ut3' WHERE `qstat`='ut2s'"); + $query->execute(); + + // Less accurate, based on shorten + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='dod' WHERE `shorten`='dod' LIMIT 1"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='cs16' WHERE `shorten`='cstrike' LIMIT 1"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='cscz' WHERE `shorten`='czero' LIMIT 1"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='tfc' WHERE `shorten`='tfc' LIMIT 1"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='cod' WHERE `shorten`='cod' LIMIT 1"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='cod2' WHERE `shorten`='cod2' LIMIT 1"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='cod4' WHERE `shorten`='cod4' LIMIT 1"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='codmw3' WHERE `shorten`='codmw3' LIMIT 1"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='coduo' WHERE `shorten`='coduo' LIMIT 1"); + $query->execute(); + $query = $sql->prepare("UPDATE `servertypes` SET `gameq`='codwaw' WHERE `shorten`='codwaw' LIMIT 1"); + $query->execute(); + + + $query = $sql->prepare("DROP TABLE `qstatshorten`"); + $query->execute(); + } else { echo "Error: this file needs to be included by the updater!
"; } \ No newline at end of file diff --git a/web/statuscheck.php b/web/statuscheck.php index 8ede8ca0..2777d745 100644 --- a/web/statuscheck.php +++ b/web/statuscheck.php @@ -64,6 +64,7 @@ include(EASYWIDIR . '/stuff/settings.php'); include(EASYWIDIR . '/stuff/ssh_exec.php'); include(EASYWIDIR . '/stuff/class_voice.php'); include(EASYWIDIR . '/stuff/queries.php'); +include(EASYWIDIR . '/third_party/gameq/GameQ.php'); include(EASYWIDIR . '/stuff/keyphrasefile.php'); set_time_limit($timelimit); @@ -83,23 +84,30 @@ if (!isset($ip) or $ui->escaped('SERVER_ADDR', 'server') == $ip or in_array($ip, $vosprache = getlanguagefile('voice','uk',0); $sprache = getlanguagefile('gserver','uk',0); + // lendmodul active ? + $query = $sql->prepare("SELECT `active` FROM `modules` WHERE `id`=5 LIMIT 1"); + $query->execute(); + $lendActive = $query->fetchColumn(); + $lendActive = (active_check($lendActive)) ? $lendActive : 'Y'; + # Pick up Reseller and Lend Settings $resellersettings = array(); $query = $sql->prepare("SELECT `brandname`,`noservertag`,`nopassword`,`tohighslots`,`down_checks`,`resellerid` FROM `settings`"); - $query2 = $sql->prepare("SELECT `active`,`shutdownempty`,`shutdownemptytime`,`lastcheck`,`oldcheck` FROM `lendsettings` WHERE `resellerid`=? LIMIT 1"); + $query2 = $sql->prepare("SELECT `shutdownempty`,`shutdownemptytime`,`lastcheck`,`oldcheck` FROM `lendsettings` WHERE `resellerid`=? LIMIT 1"); $query->execute(); foreach ($query->fetchall(PDO::FETCH_ASSOC) as $row) { - unset($active); + unset($shutdownempty); $resellerid = $row['resellerid']; $query2->execute(array($resellerid)); foreach ($query2->fetchall(PDO::FETCH_ASSOC) as $row2) { - $active = $row2['active']; $shutdownempty = $row2['shutdownempty']; $shutdownemptytime = $row2['shutdownemptytime']; $firstcheck='00-00-'.round(2*(strtotime($row2['lastcheck'])-strtotime($row2['oldcheck']))/60); $firstchecktime=date('d-G-i'); } - if (isset($active)) $resellersettings[$resellerid] = array('active' => $active,'shutdownempty' => $shutdownempty,'shutdownemptytime' => $shutdownemptytime,'firstchecktime' => $firstchecktime,'firstcheck' => $firstcheck,'brandname' => $row['brandname'], 'noservertag' => $row['noservertag'], 'nopassword' => $row['nopassword'], 'tohighslots' => $row['tohighslots'], 'down_checks' => $row['down_checks']); + if (isset($shutdownempty)) { + $resellersettings[$resellerid] = array('shutdownempty' => $shutdownempty,'shutdownemptytime' => $shutdownemptytime,'firstchecktime' => $firstchecktime,'firstcheck' => $firstcheck,'brandname' => $row['brandname'], 'noservertag' => $row['noservertag'], 'nopassword' => $row['nopassword'], 'tohighslots' => $row['tohighslots'], 'down_checks' => $row['down_checks']); + } } $query = $sql->prepare("UPDATE `lendsettings` SET `oldcheck`=`lastcheck`,`lastcheck`=NOW()"); $query->execute(); @@ -107,188 +115,7 @@ if (!isset($ip) or $ui->escaped('SERVER_ADDR', 'server') == $ip or in_array($ip, # Game Server if ($checkTypeOfServer == 'all' or $checkTypeOfServer == 'gs') { - function statushandle() { - - global $userid, $resellersettings, $resellerid, $serverid, $logdate, $aeskey, $address, $gametype, $war, $status, $password, $lendserver, $elapsed, $shutdownemptytime, $numplayers, $maxplayers, $slots, $brandname, $name, $map, $secnotified, $notified, $sql, $ssprache, $lid; - - list($serverip, $port) = explode(':', $address); - - $returnCmd = array(); - - - - // Check lendserver specific settings - if ($lendserver == 'Y') { - - // Running but no lend information in temp table - if ($status == 'UP') { - $query = $sql->prepare("SELECT 1 FROM `lendedserver` WHERE `id`=? LIMIT 1"); - $query->execute(array($lid)); - - if ($query->rowCount() == 0) { - print "Will stop lendserver $address with lendID $lid because not lendet\r\n"; - $stopserver = true; - } - - if (!isset($stopserver) and $lendserver == 'Y' and $resellersettings[$resellerid]['active'] == 'Y' and $resellersettings[$resellerid]['shutdownempty'] == 'Y' and $elapsed > $shutdownemptytime and $numplayers == 0 and $maxplayers != 0 and $slots != 0) { - print "Will stop server $address after $elapsed minutes, because it is empty and threshold is $shutdownemptytime minutes \r\n"; - $stopserver = true; - } - } - - // Expected to be running but is not, so remove from temp table - if (isset($stopserver) or $status != 'UP') { - - if (!isset($stopserver)) { - print "Will remove lendserver $address with lendID $lid because it is lendet but stopped \r\n"; - } - - $doNotRestart = true; - - $query = $sql->prepare("DELETE FROM `lendedserver` WHERE `id`=? LIMIT 1"); - $query->execute(array($lid)); - } - } - - if ($status == 'UP') { - - $rulebreak = array(); - - if ($war == 'Y' and $password == 'N') { - - $rulebreak[] = $ssprache->nopassword; - - if ($resellersettings[$resellerid]['nopassword'] == 1) { - $stopserver = true; - print "Will stop server $address because running without password. The name converted to ISO-8859-1 is ".iconv('UTF-8','ISO-8859-1//TRANSLIT', $name).".\r\n"; - - } else { - print "Server with address $address is running as $gametype and illegal without password. The name converted to ISO-8859-1 is ".iconv('UTF-8','ISO-8859-1//TRANSLIT', $name).".\r\n"; - } - } - - if ($maxplayers > $slots) { - - $rulebreak[] = $ssprache->tohighslots; - - if ($resellersettings[$resellerid]['tohighslots'] == 1) { - $stopserver = true; - print "Will stop server $address because running with to much slots. The name converted to ISO-8859-1 is ".iconv('UTF-8','ISO-8859-1//TRANSLIT', $name).".\r\n"; - } else { - print "Server $address is running as $gametype and with illegal slotamount. The name converted to ISO-8859-1 is ".iconv('UTF-8','ISO-8859-1//TRANSLIT', $name).".\r\n"; - } - } - - if ($brandname == 'Y' and $resellersettings[$resellerid]['brandname'] != '' and strpos(strtolower($name),strtolower($resellersettings[$resellerid]['brandname'])) === false) { - - $rulebreak[] = $ssprache->noservertag; - - if ($resellersettings[$resellerid]['noservertag'] == 1) { - $stopserver = true; - print "Will stop server $address because running without servertag. The name converted to ISO-8859-1 is ".iconv('UTF-8','ISO-8859-1//TRANSLIT', $name).".\r\n"; - } else { - print "Server $address is running as $gametype and illegal without servertag. The name converted to ISO-8859-1 is ".iconv('UTF-8','ISO-8859-1//TRANSLIT', $name).".\r\n"; - } - } - - if (count($rulebreak) == 0 and !isset($stopserver)) { - print "Server $address is running as $gametype. The name converted to ISO-8859-1 is ".iconv('UTF-8','ISO-8859-1//TRANSLIT', $name).".\r\n"; - } - - if ($secnotified == 'N' and count($rulebreak) > 0) { - - if ($resellerid==0) { - $query = $sql->prepare("SELECT `id`,`mail_securitybreach` FROM `userdata` WHERE `id`=? OR (`resellerid`=0 AND `accounttype`='a')"); - $query->execute(array($userid)); - - } else { - $query = $sql->prepare("SELECT `id`,`mail_securitybreach` FROM `userdata` WHERE `id`=? OR (`id`=? AND `accounttype`='r')"); - $query->execute(array($userid, $resellerid)); - } - - foreach ($query->fetchall(PDO::FETCH_ASSOC) as $row) { - if ($row['mail_securitybreach'] == 'Y') { - sendmail('emailsecuritybreach', $row['id'], $address, implode('
', $rulebreak)); - } - } - - $query = $sql->prepare("UPDATE `gsswitch` SET `secnotified`='Y' WHERE `serverip`=? AND `port`=? LIMIT 1"); - $query->execute(array($serverip, $port)); - - } - - if ($secnotified == 'Y' and count($rulebreak) == 0) { - $query = $sql->prepare("UPDATE `gsswitch` SET `secnotified`='N' WHERE `serverip`=? AND `port`=? LIMIT 1"); - $query->execute(array($serverip, $port)); - } - - if (isset($stopserver)) { - - $numplayers = 0; - $map = ''; - - $tmp = gsrestart($serverid,'so', $aeskey, $resellerid); - if (is_array($tmp)) { - foreach($tmp as $t) { - $returnCmd[] = $t; - } - } - - $query = $sql->prepare("DELETE FROM `lendedserver` WHERE `serverid`=? AND `resellerid`=? AND `servertype`='g' LIMIT 1"); - $query->execute(array($serverid, $resellerid)); - } - - if ($notified > 0) { - $query = $sql->prepare("UPDATE `gsswitch` SET `notified`=0 WHERE `serverip`=? AND `port`=? LIMIT 1"); - $query->execute(array($serverip, $port)); - } - - } else { - $name = 'OFFLINE'; - $numplayers = 0; - $maxplayers = 0; - $map = ''; - $password = 'Y'; - - if (!isset($doNotRestart)) { - - $notified++; - - $query = $sql->prepare("SELECT `autoRestart` FROM `gsswitch` WHERE `serverip`=? and `port`=? LIMIT 1"); - $query->execute(array($serverip, $port)); - - if ($query->fetchColumn() == 'Y' and $notified >= $resellersettings[$resellerid]['down_checks']) { - print "Restarting: $address\r\n"; - - $tmp = gsrestart($serverid, 're', $aeskey, $resellerid); - if (is_array($tmp)) { - foreach($tmp as $t) { - $returnCmd[] = $t; - } - } - - } else { - print "Not Restarting: $address\r\n"; - } - - if ($notified == $resellersettings[$resellerid]['down_checks']) { - $query = $sql->prepare("SELECT `mail_serverdown` FROM `userdata` WHERE `id`=? LIMIT 1"); - $query->execute(array($userid)); - foreach ($query->fetchall(PDO::FETCH_ASSOC) as $row) { - if ($row['mail_serverdown'] == 'Y') { - sendmail('emaildownrestart', $userid, $address,''); - } - } - } - } - } - - $query = $sql->prepare("UPDATE `gsswitch` SET `queryName`=?,`queryNumplayers`=?,`queryMaxplayers`=?,`queryMap`=?,`queryPassword`=?,`queryUpdatetime`=?,`notified`=? WHERE `serverip`=? and `port`=? LIMIT 1"); - $query->execute(array($name, $numplayers, $maxplayers, $map, $password, $logdate, $notified, $serverip, $port)); - - return $returnCmd; - } - + // Lend server stopping. // We want only one socket per root server. Collect the to be stopped lendservers in an array and sort by root ID $rtmp = array(); @@ -334,303 +161,280 @@ if (!isset($ip) or $ui->escaped('SERVER_ADDR', 'server') == $ip or in_array($ip, } } + + // Define basic variables for GS status checks $other = array(); $i = 1; - $totalcount = 0; - $queries = array(); + $totalCount = 0; + $serverBatchArray = array(); + $allServersArray = array(); + $shellCmds = array(); - // Get the list of servers which are active and are not stopped - $query = $sql->prepare("SELECT g.`id`,g.`serverid`,g.`serverip`,g.`port`,t.`qstat` FROM `gsswitch` g INNER JOIN `serverlist` s ON g.`serverid`=s.`id` INNER JOIN `servertypes` t ON s.`servertype`=t.`id` WHERE g.`stopped`='N' AND g.`active`='Y'"); + // Get the list of servers which are active and are not stopped. The array to be created will support batch mode. + $query = $sql->prepare("SELECT g.`id`,g.`rootID`,g.`serverid`,g.`serverip`,g.`port`,g.`port2`,t.`gameq` FROM `gsswitch` g INNER JOIN `serverlist` s ON g.`serverid`=s.`id` INNER JOIN `servertypes` t ON s.`servertype`=t.`id` WHERE g.`stopped`='N' AND g.`active`='Y'"); $query->execute(); foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $row) { - $qstat = $row['qstat']; - $serverip = $row['serverip']; - $port = $row['port']; - $server = $serverip . ':' . $port; - if (!in_array($qstat, array('', null, false))) { + // without the gameq value we cannot query. So this results need to be sorted out. + if (!in_array($row['gameq'], array('', null, false))) { - if (in_array($qstat, array('minecraft', 'tm', 'gtasamp', 'teeworlds', 'mtasa'))) { - $other[] = array('qstat' => $qstat, 'switchID' => $row['id']); - } else { - $queries[] = '-' . $qstat . ' ' . $server; - $i++; - } + $serverBatchArray[] = array('id' => $row['id'], 'type' => $row['gameq'], 'host' => $row['serverip'] . ':' . $row['port']); + $i++; if ($i == 50) { - $querry_array[] = implode(' ', $queries); - $queries = array(); + $allServersArray[] = $serverBatchArray; + $serverBatchArray = array(); $i = 1; } - $totalcount++; + $totalCount++; } } - $querry_array[] = implode(' ', $queries); + $allServersArray[] = $serverBatchArray; - print "Checking $totalcount server\r\n"; - $shellCmds = array(); - foreach ($querry_array as $querystring) { + print "Checking $totalCount server(s) with GameQ query\r\n"; - $xml = array(); + foreach ($allServersArray as $servers) { + $gq = new GameQ(); + $gq->setOption('timeout', 3); - unset($xmlquakestring); - - print "The Quakestat Querystring is: ".$querystring . "\r\n"; - - ob_start(); - if ($querystring != '') { - passthru(escapeshellcmd("/usr/bin/quakestat -xml -R -utf8 $querystring -sort i")); - $xmlquakestring = ob_get_contents(); - } - ob_end_clean(); - - if (isset($xmlquakestring)) { - $xml = @simplexml_load_string($xmlquakestring); + if (isset($dbConnect['debug']) and $dbConnect['debug'] == 1) { + $gq->setOption('debug', true); } - if (!is_array($xml) and !is_object($xml)) { - $xml = array(); - } + $gq->setFilter('normalise'); + $gq->addServers($servers); - unset($badstatus); - unset($badquery); - unset($badxml); - unset($badquerystring); + foreach($gq->requestData() as $switchID => $v) { - foreach ($xml as $xml2) { - $address = $xml2['address']; - list($ip, $port) = explode(':', $address); - - $query = $sql->prepare("SELECT t.`qstat` FROM `gsswitch` g INNER JOIN `serverlist` s ON g.`serverid`=s.`id` INNER JOIN `servertypes` t ON s.`servertype`=t.`id` WHERE g.`serverip`=? AND g.`port`=? AND g.`active`='Y' LIMIT 1"); - $query->execute(array($ip, $port)); - $qstat = $query->fetchColumn(); - - if ($xml2['status'] == 'DOWN' or $xml2['status'] == 'TIMEOUT') { - if (isset($badquery)) { - $badquery .= ' -' . $qstat . ' ' . $address; - } else { - $badquery = ' -' . $qstat . ' ' . $address; - } - } - } - - $badstatus = array(); - if (isset($badquery) and $badquery != '') { - print "The recheck Querystring is: $badquery\r\n"; - ob_start(); - passthru(escapeshellcmd("/usr/bin/quakestat -xml -R -utf8 $badquery -sort i")); - $badquerystring=ob_get_contents(); - ob_end_clean(); - $badxml=simplexml_load_string($badquerystring); - foreach ($badxml as $badxml2) { - if (isset($badxml2['address']) and isip($badxml2['address'], 'ipx')) { - $address = $badxml2['address']; - $status = $badxml2['status']; - $badstatus[$address] = array('status' => $status); - if ($badxml2['status'] == 'UP') { - if ($badxml2['type'] != 'A2S') { - $gametype = $badxml2->gametype; - } - foreach ($badxml2->rules->rule as $rule) { - switch((string) $rule['name']) { - case 'gamename': - $gametype = $rule; - break; - } - } - $name = $badxml2->name; - $numplayers = $badxml2->numplayers; - $maxplayers = $badxml2->maxplayers; - $map = $badxml2->map; - $badstatus[$address] = array('gametype' => $gametype,'name' => $name,'numplayers' => $numplayers,'maxplayers' => $maxplayers,'map' => $map,'rules' => $badxml2->rules->rule); - } - } - } - } - if (!is_array($xml) and !is_object($xml)) $xml = array(); - foreach ($xml as $xml2) { + unset($userid); $lid = 0; - unset($war); - $address = $xml2['address']; - $password = ''; - $addressarray = explode(':', $address); - $ip = $addressarray[0]; - $port = $addressarray[1]; - $query = $sql->prepare("SELECT g.*,t.`shorten`,t.`qstat` FROM `gsswitch` g INNER JOIN `serverlist` s ON g.`serverid`=s.`id` INNER JOIN `servertypes` t ON s.`servertype`=t.`id` WHERE g.`serverip`=? AND g.`port`=? LIMIT 1"); - $query->execute(array($ip, $port)); + $elapsed = 0; + $shutdownemptytime = 0; + + $query = $sql->prepare("SELECT s.`id`,t.`description`,g.`serverip`,g.`port`,g.`port2`,g.`slots`,g.`war`,g.`brandname`,g.`secnotified`,g.`notified`,g.`lendserver`,g.`userid`,g.`resellerid`,g.`rootID` FROM `gsswitch` g INNER JOIN `serverlist` s ON g.`serverid`=s.`id` INNER JOIN `servertypes` t ON s.`servertype`=t.`id` WHERE g.`id`=? LIMIT 1"); + $query2 = $sql->prepare("SELECT `id`,`started` FROM `lendedserver` WHERE `serverid`=? LIMIT 1"); + $query->execute(array($switchID)); foreach ($query->fetchall(PDO::FETCH_ASSOC) as $row) { - $serverid = $row['id']; - $rootID = $row['rootID']; - $resellerid = $row['resellerid']; - $lendserver = $row['lendserver']; + $serverip = $row['serverip']; + $port = $row['port']; + $address = $row['serverip'] . ':' . $row['port']; + $gametype = $row['description']; $notified = $row['notified']; $secnotified = $row['secnotified']; - $userid = $row['userid']; - $qstat = $row['qstat']; - $shorten = $row['shorten']; - $brandname = $row['brandname']; + $lendserver = $row['lendserver']; $slots = $row['slots']; + $userid = $row['userid']; + $resellerid = $row['resellerid']; + $brandname = $row['brandname']; + $rootID = $row['rootID']; $war = $row['war']; - if ($row['tvenable'] == 'Y') { - $slots++; - } - if ($lendserver == 'Y' and $resellersettings[$resellerid]['active'] == 'Y' and $resellersettings[$resellerid]['shutdownempty'] == 'Y') { + + if ($lendserver == 'Y' and $lendActive == 'Y' and $resellersettings[$resellerid]['shutdownempty'] == 'Y') { $shutdownemptytime = $resellersettings[$resellerid]['shutdownemptytime']; - $query2 = $sql->prepare("SELECT `id`,`started` FROM `lendedserver` WHERE `serverid`=? LIMIT 1"); - $query2->execute(array($serverid)); + $query2->execute(array($row['id'])); foreach ($query2->fetchall(PDO::FETCH_ASSOC) as $row2) { $lid = $row2['id']; - $elapsed = round((strtotime('now')-strtotime($row2['started']))/60); + $elapsed = round((strtotime('now') - strtotime($row2['started'])) / 60); } } } - $query = $sql->prepare("SELECT `qstatpassparam` FROM `servertypes` WHERE `shorten`=? LIMIT 1"); - $query->execute(array($shorten)); - foreach ($query->fetchall(PDO::FETCH_ASSOC) as $row) { - $qstatpassparam = $row['qstatpassparam']; - $passparams = explode(':', $qstatpassparam); - } - unset($password, $rulebreak, $maxplayers, $name); - if ((!isset($xml2['status']) or $xml2['status'] == 'DOWN' or $xml2['status'] == 'TIMEOUT') and (!isset($badstatus[$address]['status']) or $badstatus[$address]['status'] == 'DOWN' or $badstatus[$address]['status'] == 'TIMEOUT')) { - $status='DOWN'; - $name = ''; - $numplayers = 0; - $maxplayers = 0; - $map = ''; - $gametype = ''; - print "recheck status for $address is still: $status\r\n"; - } else if ((!$xml2['status'] or $xml2['status'] == 'DOWN' or $xml2['status'] == 'TIMEOUT') and isset($badstatus[$address]['status']) and $badstatus[$address]['status'] == 'UP') { - $status = 'UP'; - foreach ($badstatus[$address]['rules'] as $rule) { - switch((string) $rule['name']) { - case $passparams[0]: - if ($rule == $passparams[1]) $password = 'Y'; - else $password = 'N'; - break; - } - } - $name = $badstatus[$address]['name']; - $numplayers = $badstatus[$address]['numplayers']; - $maxplayers = $badstatus[$address]['maxplayers']; - $map = $badstatus[$address]['map']; - $gametype = $badstatus[$address]['gametype']; + + if ($v['gq_online'] == 1) { + $name = $v['gq_hostname']; + $numplayers = $v['gq_numplayers']; + $maxplayers = $v['gq_maxplayers']; + $map = $v['gq_mapname']; + $password = ($v['gq_password'] == 1) ? 'Y' : 'N'; } else { - $status = 'UP'; - $name = $xml2->name; - $numplayers = $xml2->numplayers; - $maxplayers = $xml2->maxplayers; - $map = $xml2->map; - $type = $xml2['type']; - if ($type != 'A2S') { - $gametype = $xml2->gametype; - } - foreach ($xml2->rules->rule as $rule) { - switch((string) $rule['name']) { - case 'gamename': - $gametype = $rule; - break; - case $passparams[0]: - if ($rule == $passparams[1]) $password = 'Y'; - else $password = 'N'; - break; - } - } - } - if (!isset($password) or $password == '') { - $password = 'N'; - } - if (!isset($elapsed)) { - $elapsed = 0; - } - if (!isset($shutdownemptytime)) { - $shutdownemptytime = 0; - } - if (isset($war)) { - $tmp = statushandle(); - if (is_array($tmp)) { - foreach($tmp as $t) { - $shellCmds[$rootID][] = $t; - } - } - } - } - } - print "Checking Gameserver with Easy-Wi query:\r\n"; - foreach ($other as $array) { - unset($userid, $serverid); - $lid = 0; - $qstat = $array['qstat']; - $serverid = $array['switchID']; - $query = $sql->prepare("SELECT s.`id`,t.`description`,g.`serverip`,g.`port`,g.`port2`,g.`slots`,g.`war`,g.`brandname`,g.`secnotified`,g.`notified`,g.`lendserver`,g.`userid`,g.`resellerid`,g.`rootID` FROM `gsswitch` g INNER JOIN `serverlist` s ON g.`serverid`=s.`id` INNER JOIN `servertypes` t ON s.`servertype`=t.`id` WHERE g.`id`=? LIMIT 1"); - $query2 = $sql->prepare("SELECT `id`,`started` FROM `lendedserver` WHERE `serverid`=? LIMIT 1"); - $query->execute(array($serverid)); - foreach ($query->fetchall(PDO::FETCH_ASSOC) as $row) { - $serverip = $row['serverip']; - $port = $row['port']; - $address = $row['serverip'] . ':' . $row['port']; - $gametype = $row['description']; - $notified = $row['notified']; - $secnotified = $row['secnotified']; - $lendserver = $row['lendserver']; - $slots = $row['slots']; - $userid = $row['userid']; - $resellerid = $row['resellerid']; - $brandname = $row['brandname']; - $rootID = $row['rootID']; - $war = $row['war']; - if ($lendserver == 'Y' and $resellersettings[$resellerid]['active'] == 'Y' and $resellersettings[$resellerid]['shutdownempty'] == 'Y') { - $shutdownemptytime = $resellersettings[$resellerid]['shutdownemptytime']; - $query2->execute(array($row['id'])); - foreach ($query2->fetchall(PDO::FETCH_ASSOC) as $row2) { - $lid = $row2['id']; - $elapsed = round((strtotime('now') - strtotime($row2['started'])) / 60); - } - } - } - if (isset($userid) and isset($serverid) and isset($port) and isset($qstat) and isset($serverip)) { - $status = 'UP'; - if (in_array($qstat, array('gtasamp', 'minecraft', 'teeworlds', 'mtasa'))) { - echo "$qstat\r\n"; - $query = ($qstat == 'mtasa') ? serverQuery($serverip, ($port + 123), $qstat) : serverQuery($serverip, $port, $qstat); - if (is_array($query)) { - $name = $query['hostname']; - $password = ($query['password'] == 1) ? 'Y' : 'N'; - $numplayers = $query['players']; - $maxplayers = $query['slots']; - $map = $query['map']; - } else { - $status = 'DOWN'; - $name = $gametype . 'OFFLINE'; - $numplayers = 0; - $maxplayers = 0; - $map = ''; - $password = 'Y'; - } - } else { - $brandname = 'N'; - $name = $gametype . 'ONLINE'; + $name = 'OFFLINE'; $numplayers = 0; $maxplayers = 0; $map = ''; $password = 'Y'; } - if (!isset($elapsed)) { - $elapsed = 0; - } - if (!isset($shutdownemptytime)) { - $shutdownemptytime = 0; - } - $tmp = statushandle(); - if (is_array($tmp)) { - foreach($tmp as $t) { - $shellCmds[$rootID][] = $t; + + $returnCmd = array(); + + // Check lendserver specific settings + if (isset($userid) and $lendserver == 'Y') { + + // Running but no lend information in temp table + if ($v['gq_online'] == 1) { + $query = $sql->prepare("SELECT 1 FROM `lendedserver` WHERE `id`=? LIMIT 1"); + $query->execute(array($lid)); + + if ($query->rowCount() == 0) { + print "Will stop lendserver $address with lendID $lid because not lendet\r\n"; + $stopserver = true; + } + + if (!isset($stopserver) and $lendserver == 'Y' and $lendActive == 'Y' and $resellersettings[$resellerid]['shutdownempty'] == 'Y' and $elapsed > $shutdownemptytime and $numplayers == 0 and $maxplayers != 0 and $slots != 0) { + print "Will stop server $address after $elapsed minutes, because it is empty and threshold is $shutdownemptytime minutes \r\n"; + $stopserver = true; + } } + + // Expected to be running but is not, so remove from temp table + if (isset($stopserver) or $v['gq_online'] != 1) { + + if (!isset($stopserver)) { + print "Will remove lendserver $address with lendID $lid because it is lendet but stopped \r\n"; + } + + $doNotRestart = true; + + $query = $sql->prepare("DELETE FROM `lendedserver` WHERE `id`=? LIMIT 1"); + $query->execute(array($lid)); + } + } + + if (isset($userid) and $v['gq_online'] == 1) { + + $rulebreak = array(); + + if ($war == 'Y' and $password == 'N') { + + $rulebreak[] = $ssprache->nopassword; + + if ($resellersettings[$resellerid]['nopassword'] == 1) { + $stopserver = true; + print "Will stop server $address because running without password. The name converted to ISO-8859-1 is ".iconv('UTF-8','ISO-8859-1//TRANSLIT', $name).".\r\n"; + + } else { + print "Server with address $address is running as $gametype and illegal without password. The name converted to ISO-8859-1 is ".iconv('UTF-8','ISO-8859-1//TRANSLIT', $name).".\r\n"; + } + } + + if ($maxplayers > $slots) { + + $rulebreak[] = $ssprache->tohighslots; + + if ($resellersettings[$resellerid]['tohighslots'] == 1) { + $stopserver = true; + print "Will stop server $address because running with to much slots. The name converted to ISO-8859-1 is ".iconv('UTF-8','ISO-8859-1//TRANSLIT', $name).".\r\n"; + } else { + print "Server $address is running as $gametype and with illegal slotamount. The name converted to ISO-8859-1 is ".iconv('UTF-8','ISO-8859-1//TRANSLIT', $name).".\r\n"; + } + } + + if ($brandname == 'Y' and $resellersettings[$resellerid]['brandname'] != '' and strpos(strtolower($name),strtolower($resellersettings[$resellerid]['brandname'])) === false) { + + $rulebreak[] = $ssprache->noservertag; + + if ($resellersettings[$resellerid]['noservertag'] == 1) { + $stopserver = true; + print "Will stop server $address because running without servertag. The name converted to ISO-8859-1 is ".iconv('UTF-8','ISO-8859-1//TRANSLIT', $name).".\r\n"; + } else { + print "Server $address is running as $gametype and illegal without servertag. The name converted to ISO-8859-1 is ".iconv('UTF-8','ISO-8859-1//TRANSLIT', $name).".\r\n"; + } + } + + if (count($rulebreak) == 0 and !isset($stopserver)) { + print "Server $address is running as $gametype. The name converted to ISO-8859-1 is ".iconv('UTF-8','ISO-8859-1//TRANSLIT', $name).".\r\n"; + } + + if ($secnotified == 'N' and count($rulebreak) > 0) { + + if ($resellerid==0) { + $query = $sql->prepare("SELECT `id`,`mail_securitybreach` FROM `userdata` WHERE `id`=? OR (`resellerid`=0 AND `accounttype`='a')"); + $query->execute(array($userid)); + + } else { + $query = $sql->prepare("SELECT `id`,`mail_securitybreach` FROM `userdata` WHERE `id`=? OR (`id`=? AND `accounttype`='r')"); + $query->execute(array($userid, $resellerid)); + } + + foreach ($query->fetchall(PDO::FETCH_ASSOC) as $row) { + if ($row['mail_securitybreach'] == 'Y') { + sendmail('emailsecuritybreach', $row['id'], $address, implode('
', $rulebreak)); + } + } + + $query = $sql->prepare("UPDATE `gsswitch` SET `secnotified`='Y' WHERE `id`=? LIMIT 1"); + $query->execute(array($switchID)); + + } + + if ($secnotified == 'Y' and count($rulebreak) == 0) { + $query = $sql->prepare("UPDATE `gsswitch` SET `secnotified`='N' WHERE `id`=? LIMIT 1"); + $query->execute(array($switchID)); + } + + if (isset($stopserver)) { + + $numplayers = 0; + $map = ''; + + $tmp = gsrestart($switchID, 'so', $aeskey, $resellerid); + if (is_array($tmp)) { + foreach($tmp as $t) { + $returnCmd[] = $t; + } + } + + $query = $sql->prepare("DELETE FROM `lendedserver` WHERE `serverid`=? AND `resellerid`=? AND `servertype`='g' LIMIT 1"); + $query->execute(array($switchID, $resellerid)); + } + + if ($notified > 0) { + $query = $sql->prepare("UPDATE `gsswitch` SET `notified`=0 WHERE `id`=? LIMIT 1"); + $query->execute(array($switchID)); + } + + } else if (isset($userid)) { + $name = 'OFFLINE'; + $numplayers = 0; + $maxplayers = 0; + $map = ''; + $password = 'Y'; + + if (!isset($doNotRestart)) { + + $notified++; + + $query = $sql->prepare("SELECT `autoRestart` FROM `gsswitch` WHERE `id`=? LIMIT 1"); + $query->execute(array($switchID)); + + if ($query->fetchColumn() == 'Y' and $notified >= $resellersettings[$resellerid]['down_checks']) { + print "Restarting: $address\r\n"; + + $tmp = gsrestart($switchID, 're', $aeskey, $resellerid); + if (is_array($tmp)) { + foreach($tmp as $t) { + $returnCmd[] = $t; + } + } + + } else { + print "Not Restarting: $address\r\n"; + } + + if ($notified == $resellersettings[$resellerid]['down_checks']) { + $query = $sql->prepare("SELECT `mail_serverdown` FROM `userdata` WHERE `id`=? LIMIT 1"); + $query->execute(array($userid)); + foreach ($query->fetchall(PDO::FETCH_ASSOC) as $row) { + if ($row['mail_serverdown'] == 'Y') { + sendmail('emaildownrestart', $userid, $address,''); + } + } + } + } + } + + $query = $sql->prepare("UPDATE `gsswitch` SET `queryName`=?,`queryNumplayers`=?,`queryMaxplayers`=?,`queryMap`=?,`queryPassword`=?,`queryUpdatetime`=?,`notified`=? WHERE `id`=? LIMIT 1"); + $query->execute(array($name, $numplayers, $maxplayers, $map, $password, $logdate, $notified, $switchID)); + + foreach($returnCmd as $t) { + $shellCmds[$rootID][] = $t; } } } + + unset($gq); + foreach($shellCmds as $k => $v) { ssh2_execute('gs', $k, $v); } @@ -679,8 +483,8 @@ if (!isset($ip) or $ui->escaped('SERVER_ADDR', 'server') == $ip or in_array($ip, # https://github.com/easy-wi/developer/issues/70 $sshkey = removePub($row['keyname']); - $pubkey='keys/'.$sshkey.'.pub'; - $key='keys/'.$sshkey; + $pubkey = 'keys/' . $sshkey . '.pub'; + $key = 'keys/' . $sshkey; if (file_exists($pubkey) and file_exists($key)) { $ssh2_2 = @ssh2_connect($row['ssh2ip'], $row['decryptedssh2port'], array('hostkey' => 'ssh-rsa')); @@ -710,8 +514,10 @@ if (!isset($ip) or $ui->escaped('SERVER_ADDR', 'server') == $ip or in_array($ip, $folders = $folders . ' && '; } $ssh2cmd = $folders.'function r () { if [ "`ps fx | grep '.$tsdnsbin.' | grep -v grep`" == "" ]; then ./'.$tsdnsbin.' > /dev/null & else ./'.$tsdnsbin.' --update > /dev/null & fi }; r& '; - echo $ssh2cmd . "\r\n"; - echo ssh2_exec($ssh2_2, $ssh2cmd) . "\r\n"; + if (isset($dbConnect['debug']) and $dbConnect['debug'] == 1) { + echo $ssh2cmd . "\r\n"; + } + ssh2_exec($ssh2_2, $ssh2cmd); $ssh2_2 = null; } else { print "Error: Bad logindata for external tsdns ".$row['ssh2ip'] . "\r\n"; @@ -856,12 +662,16 @@ if (!isset($ip) or $ui->escaped('SERVER_ADDR', 'server') == $ip or in_array($ip, $ssh2cmd2 = $tsdnsFolders.'function r () { if [ "`ps fx | grep '.$tsdnsbin.' | grep -v grep`" == "" ]; then ./'.$tsdnsbin.' > /dev/null & else ./'.$tsdnsbin.' --update > /dev/null & fi }; r& '; } if ($tsdown == true) { - echo $ssh2cmd . "\r\n"; - echo ssh2_exec($ssh2, $ssh2cmd); + if (isset($dbConnect['debug']) and $dbConnect['debug'] == 1) { + echo $ssh2cmd . "\r\n"; + } + ssh2_exec($ssh2, $ssh2cmd); } if ($tsdnsdown == true) { - echo $ssh2cmd2 . "\r\n"; - echo ssh2_exec($ssh2, $ssh2cmd2); + if (isset($dbConnect['debug']) and $dbConnect['debug'] == 1) { + echo $ssh2cmd2 . "\r\n"; + } + ssh2_exec($ssh2, $ssh2cmd2); } print 'Restarting: '.$restartreturn . "\r\n"; } else { diff --git a/web/stuff/images.php b/web/stuff/images.php index 390c588b..94e2ae82 100644 --- a/web/stuff/images.php +++ b/web/stuff/images.php @@ -40,6 +40,7 @@ if ((!isset($admin_id) or $main != 1) or (isset($admin_id) and !$pa['gimages'])) header('Location: admin.php'); die('No acces'); } +include(EASYWIDIR . '/third_party/gameq/GameQ.php'); include(EASYWIDIR . '/stuff/keyphrasefile.php'); $sprache = getlanguagefile('images', $user_language, $reseller_id); @@ -111,10 +112,9 @@ if ($ui->w('action', 4, 'post') and !token(true)) { $configs = $ui->startparameter('configs', 'post'); $configedit = $ui->startparameter('configedit', 'post'); $modcmds = $ui->startparameter('modcmds', 'post'); - $qstat = $ui->w('qstat', 20, 'post'); + $gameq = $ui->w('gameq', 255, 'post'); $gamemod = $ui->active('gamemod', 'post'); $gamemod2 = $ui->w('gamemod2', 10, 'post'); - $qstatpassparam = $ui->startparameter('qstatpassparam', 'post'); $portMax = ($ui->id('portMax', 1, 'post')) ? $ui->id('portMax', 1, 'post') : 1; $portStep = ($ui->id('portStep',4, 'post')) ? $ui->id('portStep',4, 'post') : 100; $portOne = ($ui->id('portOne',5, 'post')) ? $ui->id('portOne',5, 'post') : 27015; @@ -132,17 +132,59 @@ if ($ui->w('action', 4, 'post') and !token(true)) { if (!$ui->smallletters('action', 2, 'post') or $ui->id('import', 1, 'post') == 1) { + // Protocol list code taken from https://github.com/Austinb/GameQ/blob/v2/examples/list.php + $protocols_path = GAMEQ_BASE."gameq/protocols/"; + + // Grab the dir with all the classes available + $dir = dir($protocols_path); + + $protocols = array(); + + // Now lets loop the directories + while (false !== ($entry = $dir->read())) + { + if(!is_file($protocols_path.$entry)) + { + continue; + } + + // Figure out the class name + $class_name = 'GameQ_Protocols_'.ucfirst(pathinfo($entry, PATHINFO_FILENAME)); + + // Lets get some info on the class + $reflection = new ReflectionClass($class_name); + + // Check to make sure we can actually load the class + if(!$reflection->IsInstantiable()) + { + continue; + } + + // Load up the class so we can get info + $class = new $class_name; + + // Add it to the list + $protocols[$class->name()] = $class->name_long(); + + // Unset the class + unset($class); + } + + // Close the directory + unset($dir); + + ksort($protocols); + + // GameQ protocol listing done. Easy-WI Code again. if ($ui->st('d', 'get') == 'ad') { - $token=token(); + $token = token(); + $table = array(); + + // Collect the shorten we need for game modification $query = $sql->prepare("SELECT DISTINCT(`shorten`) FROM `servertypes` WHERE `resellerid`=?"); $query->execute(array($reseller_id)); - $table = array(); - foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $row) $table[] = array('shorten' => $row['shorten']); - $query = $sql->prepare("SELECT `qstat`,`description` FROM `qstatshorten` ORDER BY `description`"); - $query->execute(); - $table2 = array(); foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $row) { - $table2[] = array('qstat' => $row['qstat'], 'description' => $row['description']); + $table[] = array('shorten' => $row['shorten']); } if ($ui->id('import', 1, 'post') == 1 and $_FILES['file']['error']==0 and $_FILES['file']['type'] == 'text/xml') { @@ -192,8 +234,8 @@ if ($ui->w('action', 4, 'post') and !token(true)) { if ($node->nodeName == 'tic') { $tic = $node->nodeValue; } - if ($node->nodeName == 'qstat') { - $qstat = $node->nodeValue; + if ($node->nodeName == 'gameq') { + $gameq = $node->nodeValue; } if ($node->nodeName == 'gamemod') { $gamemod = $node->nodeValue; @@ -207,9 +249,6 @@ if ($ui->w('action', 4, 'post') and !token(true)) { if ($node->nodeName == 'configedit') { $configedit = $node->nodeValue; } - if ($node->nodeName == 'qstatpassparam') { - $qstatpassparam = $node->nodeValue; - } if ($node->nodeName == 'portStep') { $portStep = $node->nodeValue; } @@ -262,13 +301,6 @@ if ($ui->w('action', 4, 'post') and !token(true)) { } } - $query = $sql->prepare("SELECT `qstat`,`description` FROM `qstatshorten`"); - $query->execute(); - $table3 = array(); - foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $row) { - $selected = (isset($qstat) and $qstat == $row['qstat']) ? ' selected="selected"' : ''; - $table3[] = array('option' => ''); - } $template_file = 'admin_images_add.tpl'; } else if ($ui->st('d', 'get') == 'md' and $id) { @@ -288,12 +320,11 @@ if ($ui->w('action', 4, 'post') and !token(true)) { $cmd = $row['cmd']; $modcmds = $row['modcmds']; $tic = $row['tic']; - $qstat = $row['qstat']; + $gameq = $row['gameq']; $gamemod = $row['gamemod']; $gamemod2 = $row['gamemod2']; $configs = $row['configs']; $configedit = $row['configedit']; - $qstatpassparam = $row['qstatpassparam']; $appID = $row['appID']; $portMax = $row['portMax']; $portStep = $row['portStep']; @@ -318,13 +349,6 @@ if ($ui->w('action', 4, 'post') and !token(true)) { $table[] = array('shorten' => $row['shorten']); } - $query = $sql->prepare("SELECT `qstat`,`description` FROM `qstatshorten`"); - $query->execute(); - $table3 = array(); - foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $row) { - $selected = (isset($qstat) and $qstat == $row['qstat']) ? ' selected="selected"' : ''; - $table3[] = array('option' => ''); - } $template_file = 'admin_images_md.tpl'; } else { $template_file = 'admin_404.tpl'; @@ -378,15 +402,15 @@ if ($ui->w('action', 4, 'post') and !token(true)) { if ($ui->st('action', 'post') == 'ad' and $reseller_id == 0) { - $query = $sql->prepare("INSERT INTO `servertypes` (`iptables`,`protectedSaveCFGs`,`steamgame`,`updates`,`shorten`,`description`,`type`,`gamebinary`,`binarydir`,`modfolder`,`map`,`mapGroup`,`cmd`,`modcmds`,`qstat`,`gamemod`,`gamemod2`,`configs`,`configedit`,`qstatpassparam`,`appID`,`portMax`,`portStep`,`portOne`,`portTwo`,`portThree`,`portFour`,`portFive`,`protected`,`ramLimited`,`ftpAccess`,`os`,`resellerid`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); - $query->execute(array($iptables, $protectedSaveCFGs, $steamgame, $updates, $shorten, $description, 'gserver', $gamebinary, $binarydir, $modfolder, $map, $mapGroup, $cmd, $modcmds, $qstat, $gamemod, $gamemod2, $configs, $configedit, $qstatpassparam, $appID, $portMax, $portStep, $portOne, $portTwo, $portThree, $portFour, $portFive, $protected, $ramLimited, $ftpAccess, $os,$reseller_id)); + $query = $sql->prepare("INSERT INTO `servertypes` (`iptables`,`protectedSaveCFGs`,`steamgame`,`updates`,`shorten`,`description`,`type`,`gamebinary`,`binarydir`,`modfolder`,`map`,`mapGroup`,`cmd`,`modcmds`,`gameq`,`gamemod`,`gamemod2`,`configs`,`configedit`,`appID`,`portMax`,`portStep`,`portOne`,`portTwo`,`portThree`,`portFour`,`portFive`,`protected`,`ramLimited`,`ftpAccess`,`os`,`resellerid`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); + $query->execute(array($iptables, $protectedSaveCFGs, $steamgame, $updates, $shorten, $description, 'gserver', $gamebinary, $binarydir, $modfolder, $map, $mapGroup, $cmd, $modcmds, $gameq, $gamemod, $gamemod2, $configs, $configedit, $appID, $portMax, $portStep, $portOne, $portTwo, $portThree, $portFour, $portFive, $protected, $ramLimited, $ftpAccess, $os,$reseller_id)); $rowCount = $query->rowCount(); $loguseraction = '%add% %template% ' . $shorten; } else if ($ui->st('action', 'post') == 'md') { - $query = $sql->prepare("UPDATE `servertypes` SET `iptables`=?,`protectedSaveCFGs`=?,`steamgame`=?,`updates`=?,`shorten`=?,`description`=?,`gamebinary`=?,`binarydir`=?,`modfolder`=?,`map`=?,`mapGroup`=?,`cmd`=?,`modcmds`=?,`qstat`=?,`gamemod`=?,`gamemod2`=?,`configs`=?,`configedit`=?,`qstatpassparam`=?,`appID`=?,`portMax`=?,`portStep`=?,`portOne`=?,`portTwo`=?,`portThree`=?,`portFour`=?,`portFive`=?,`protected`=?,`ramLimited`=?,`ftpAccess`=?,`os`=? WHERE `id`=? AND `resellerid`=? LIMIT 1"); - $query->execute(array($iptables, $protectedSaveCFGs, $steamgame, $updates, $shorten, $description, $gamebinary, $binarydir, $modfolder, $map, $mapGroup, $cmd, $modcmds, $qstat, $gamemod, $gamemod2, $configs, $configedit, $qstatpassparam, $appID, $portMax, $portStep, $portOne, $portTwo, $portThree, $portFour, $portFive, $protected, $ramLimited, $ftpAccess, $os, $ui->id('id', 10, 'get'), $reseller_id)); + $query = $sql->prepare("UPDATE `servertypes` SET `iptables`=?,`protectedSaveCFGs`=?,`steamgame`=?,`updates`=?,`shorten`=?,`description`=?,`gamebinary`=?,`binarydir`=?,`modfolder`=?,`map`=?,`mapGroup`=?,`cmd`=?,`modcmds`=?,`gameq`=?,`gamemod`=?,`gamemod2`=?,`configs`=?,`configedit`=?,`appID`=?,`portMax`=?,`portStep`=?,`portOne`=?,`portTwo`=?,`portThree`=?,`portFour`=?,`portFive`=?,`protected`=?,`ramLimited`=?,`ftpAccess`=?,`os`=? WHERE `id`=? AND `resellerid`=? LIMIT 1"); + $query->execute(array($iptables, $protectedSaveCFGs, $steamgame, $updates, $shorten, $description, $gamebinary, $binarydir, $modfolder, $map, $mapGroup, $cmd, $modcmds, $gameq, $gamemod, $gamemod2, $configs, $configedit, $appID, $portMax, $portStep, $portOne, $portTwo, $portThree, $portFour, $portFive, $protected, $ramLimited, $ftpAccess, $os, $ui->id('id', 10, 'get'), $reseller_id)); $rowCount = $query->rowCount(); $loguseraction = '%mod% %template% ' . $shorten; } diff --git a/web/stuff/tables_add.php b/web/stuff/tables_add.php index 86c74b40..a0f3cb50 100644 --- a/web/stuff/tables_add.php +++ b/web/stuff/tables_add.php @@ -627,15 +627,6 @@ $query = "CREATE TABLE IF NOT EXISTS `page_terms_used` ( $add = $sql->prepare($query); $add->execute(); -$query = "CREATE TABLE IF NOT EXISTS `qstatshorten` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `qstat` varchar(15) NOT NULL, - `description` varchar(50) NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB"; -$add = $sql->prepare($query); -$add->execute(); - $query = "CREATE TABLE IF NOT EXISTS `resellerdata` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `useractive` enum('Y','N') NOT NULL DEFAULT 'Y', @@ -834,6 +825,7 @@ $query = "CREATE TABLE IF NOT EXISTS `servertypes` ( `cmd` text NOT NULL, `modcmds` text, `tic` varchar(5) DEFAULT NULL, + `gameq` varchar(255) DEFAULT NULL, `qstat` varchar(30) DEFAULT NULL, `gamemod` enum('Y','N') NOT NULL DEFAULT 'N', `gamemod2` varchar(15) DEFAULT NULL, diff --git a/web/stuff/tables_repair.php b/web/stuff/tables_repair.php index 5f86a772..b419cc50 100644 --- a/web/stuff/tables_repair.php +++ b/web/stuff/tables_repair.php @@ -549,11 +549,6 @@ $defined['page_terms_used'] = array('page_id' => array("Type"=>"int(10) unsigned 'resellerid' => array("Type"=>"int(10) unsigned","Null"=>"YES","Key"=>"MUL","Default"=>"0","Extra"=>"") ); -$defined['qstatshorten'] = array('id' => array("Type"=>"int(10) unsigned","Null"=>"NO","Key"=>"PRI","Default"=>"","Extra"=>"auto_increment"), - 'qstat' => array("Type"=>"varchar(15)","Null"=>"NO","Key"=>"","Default"=>"","Extra"=>""), - 'description' => array("Type"=>"varchar(50)","Null"=>"NO","Key"=>"","Default"=>"","Extra"=>"") -); - $defined['resellerdata'] = array('id' => array("Type"=>"int(10) unsigned","Null"=>"NO","Key"=>"PRI","Default"=>"","Extra"=>"auto_increment"), 'useractive' => array("Type"=>"enum('Y','N')","Null"=>"NO","Key"=>"","Default"=>"Y","Extra"=>""), 'ips' => array("Type"=>"text","Null"=>"YES","Key"=>"","Default"=>"","Extra"=>""), @@ -659,6 +654,7 @@ $defined['servertypes'] = array('id' => array("Type"=>"int(10) unsigned","Null"= 'cmd' => array("Type"=>"text","Null"=>"NO","Key"=>"","Default"=>"","Extra"=>""), 'modcmds' => array("Type"=>"text","Null"=>"YES","Key"=>"","Default"=>"","Extra"=>""), 'tic' => array("Type"=>"varchar(5)","Null"=>"YES","Key"=>"","Default"=>"","Extra"=>""), + 'gameq' => array("Type"=>"varchar(255)","Null"=>"YES","Key"=>"","Default"=>"","Extra"=>""), 'qstat' => array("Type"=>"varchar(30)","Null"=>"YES","Key"=>"","Default"=>"","Extra"=>""), 'gamemod' => array("Type"=>"enum('Y','N')","Null"=>"NO","Key"=>"","Default"=>"N","Extra"=>""), 'gamemod2' => array("Type"=>"varchar(15)","Null"=>"YES","Key"=>"","Default"=>"","Extra"=>""), diff --git a/web/template/default/admin_images_add.tpl b/web/template/default/admin_images_add.tpl index 7744ba7c..a78f8867 100644 --- a/web/template/default/admin_images_add.tpl +++ b/web/template/default/admin_images_add.tpl @@ -126,10 +126,13 @@
- +
- + + $v){ ?> + +
@@ -157,10 +160,6 @@
-
- -
-
diff --git a/web/template/default/admin_images_md.tpl b/web/template/default/admin_images_md.tpl index d76adf8e..007ae938 100644 --- a/web/template/default/admin_images_md.tpl +++ b/web/template/default/admin_images_md.tpl @@ -106,10 +106,13 @@
- +
- + + $v){ ?> + +
@@ -137,10 +140,6 @@
-
- -
-
diff --git a/web/third_party/gameq/GameQ.php b/web/third_party/gameq/GameQ.php new file mode 100644 index 00000000..cb34007a --- /dev/null +++ b/web/third_party/gameq/GameQ.php @@ -0,0 +1,871 @@ +. + */ + +/* + * Init some stuff + */ +// Figure out where we are so we can set the proper references +define('GAMEQ_BASE', realpath(dirname(__FILE__)).DIRECTORY_SEPARATOR); + +// Define the autoload so we can require files easy +spl_autoload_register(array('GameQ', 'auto_load')); + +/** + * Base GameQ Class + * + * This class should be the only one that is included when you use GameQ to query + * any games servers. All necessary sub-classes are loaded as needed. + * + * Requirements: See wiki or README for more information on the requirements + * - PHP 5.2+ (Recommended 5.3+) + * * Bzip2 - http://www.php.net/manual/en/book.bzip2.php + * * Zlib - http://www.php.net/manual/en/book.zlib.php + * + * @author Austin Bischoff + */ +class GameQ +{ + /* + * Constants + */ + const VERSION = '2.0.1'; + + /* + * Server array keys + */ + const SERVER_TYPE = 'type'; + const SERVER_HOST = 'host'; + const SERVER_ID = 'id'; + const SERVER_OPTIONS = 'options'; + + /* Static Section */ + protected static $instance = NULL; + + /** + * Create a new instance of this class + */ + public static function factory() + { + // Create a new instance + self::$instance = new self(); + + // Return this new instance + return self::$instance; + } + + /** + * Attempt to auto-load a class based on the name + * + * @param string $class + * @throws GameQException + */ + public static function auto_load($class) + { + try + { + // Transform the class name into a path + $file = str_replace('_', '/', strtolower($class)); + + // Find the file and return the full path, if it exists + if ($path = self::find_file($file)) + { + // Load the class file + require $path; + + // Class has been found + return TRUE; + } + + // Class is not in the filesystem + return FALSE; + } + catch (Exception $e) + { + throw new GameQException($e->getMessage(), $e->getCode(), $e); + die; + } + } + + /** + * Try to find the file based on the class passed. + * + * @param string $file + */ + public static function find_file($file) + { + $found = FALSE; // By default we did not find anything + + // Create a partial path of the filename + $path = GAMEQ_BASE.$file.'.php'; + + // Is a file so we can include it + if(is_file($path)) + { + $found = $path; + } + + return $found; + } + + /* Dynamic Section */ + + /** + * Defined options by default + * + * @var array() + */ + protected $options = array( + 'debug' => FALSE, + 'timeout' => 3, // Seconds + 'filters' => array(), + + // Advanced settings + 'stream_timeout' => 200000, // See http://www.php.net/manual/en/function.stream-select.php for more info + 'write_wait' => 500, // How long (in micro-seconds) to pause between writting to server sockets, helps cpu usage + ); + + /** + * Array of servers being queried + * + * @var array + */ + protected $servers = array(); + + /** + * Holds the list of active sockets. This array is automaically cleaned as needed + * + * @var array + */ + protected $sockets = array(); + + /** + * Make new class and check for requirements + * + * @throws GameQException + * @return boolean + */ + public function __construct() + { + // @todo: Add PHP version check? + } + + /** + * Get an option's value + * + * @param string $option + */ + public function __get($option) + { + return isset($this->options[$option]) ? $this->options[$option] : NULL; + } + + /** + * Set an option's value + * + * @param string $option + * @param mixed $value + * @return boolean + */ + public function __set($option, $value) + { + $this->options[$option] = $value; + + return TRUE; + } + + /** + * Chainable call to __set, uses set as the actual setter + * + * @param string $var + * @param mixed $value + * @return GameQ + */ + public function setOption($var, $value) + { + // Use magic + $this->{$var} = $value; + + return $this; // Make chainable + } + + /** + * Set an output filter. + * + * @param string $name + * @param array $params + * @return GameQ + */ + public function setFilter($name, $params = array()) + { + // Create the proper filter class name + $filter_class = 'GameQ_Filters_'.$name; + + try + { + // Pass any parameters and make the class + $this->options['filters'][$name] = new $filter_class($params); + } + catch (GameQ_FiltersException $e) + { + // We catch the exception here, thus the filter is not applied + // but we issue a warning + error_log($e->getMessage(), E_USER_WARNING); + } + + return $this; // Make chainable + } + + /** + * Remove a global output filter. + * + * @param string $name + * @return GameQ + */ + public function removeFilter($name) + { + unset($this->options['filters'][$name]); + + return $this; // Make chainable + } + + /** + * Add a server to be queried + * + * Example: + * $this->addServer(array( + * // Required keys + * 'type' => 'cs', + * 'host' => '127.0.0.1:27015', '127.0.0.1' or 'somehost.com:27015' + * Port not required, but will use the default port in the class which may not be correct for the + * specific server being queried. + * + * // Optional keys + * 'id' => 'someServerId', // By default will use pased host info (i.e. 127.0.0.1:27015) + * 'options' => array('timeout' => 5), // By default will use global options + * )); + * + * @param array $server_info + * @throws GameQException + * @return boolean|GameQ + */ + public function addServer(Array $server_info=NULL) + { + // Check for server type + if(!key_exists(self::SERVER_TYPE, $server_info) || empty($server_info[self::SERVER_TYPE])) + { + throw new GameQException("Missing server info key '".self::SERVER_TYPE."'"); + return FALSE; + } + + // Check for server host + if(!key_exists(self::SERVER_HOST, $server_info) || empty($server_info[self::SERVER_HOST])) + { + throw new GameQException("Missing server info key '".self::SERVER_HOST."'"); + return FALSE; + } + + // Check for server id + if(!key_exists(self::SERVER_ID, $server_info) || empty($server_info[self::SERVER_ID])) + { + // Make an id so each server has an id when returned + $server_info[self::SERVER_ID] = $server_info[self::SERVER_HOST]; + } + + // Check for options + if(!key_exists(self::SERVER_OPTIONS, $server_info) + || !is_array($server_info[self::SERVER_OPTIONS]) + || empty($server_info[self::SERVER_OPTIONS])) + { + // Make an id so each server has an id when returned + $server_info[self::SERVER_OPTIONS] = array(); + } + + // Define these + $server_id = $server_info[self::SERVER_ID]; + $server_ip = '127.0.0.1'; + $server_port = FALSE; + + // We have an IPv6 address (and maybe a port) + if(substr_count($server_info[self::SERVER_HOST], ':') > 1) + { + // See if we have a port, input should be in the format [::1]:27015 or similar + if(strstr($server_info[self::SERVER_HOST], ']:')) + { + // Explode to get port + $server_addr = explode(':', $server_info[self::SERVER_HOST]); + + // Port is the last item in the array, remove it and save + $server_port = array_pop($server_addr); + + // The rest is the address, recombine + $server_ip = implode(':', $server_addr); + + unset($server_addr); + } + + // Just the IPv6 address, no port defined + else + { + $server_ip = $server_info[self::SERVER_HOST]; + } + + // Now let's validate the IPv6 value sent, remove the square brackets ([]) first + if(!filter_var(trim($server_ip, '[]'), FILTER_VALIDATE_IP, array( + 'flags' => FILTER_FLAG_IPV6, + ))) + { + throw new GameQException("The IPv6 address '{$server_ip}' is invalid."); + return FALSE; + } + } + + // IPv4 + else + { + // We have a port defined + if(strstr($server_info[self::SERVER_HOST], ':')) + { + list($server_ip, $server_port) = explode(':', $server_info[self::SERVER_HOST]); + } + + // No port, just IPv4 + else + { + $server_ip = $server_info[self::SERVER_HOST]; + } + + // Validate the IPv4 value, if FALSE is not a valid IP, maybe a hostname. Try to resolve + if(!filter_var($server_ip, FILTER_VALIDATE_IP, array( + 'flags' => FILTER_FLAG_IPV4, + ))) + { + // When gethostbyname() fails it returns the original string + // so if ip and the result from gethostbyname() are equal this failed. + if($server_ip === gethostbyname($server_ip)) + { + throw new GameQException("The host '{$server_ip}' is unresolvable to an IP address."); + return FALSE; + } + } + } + + // Create the class so we can reference it properly later + $protocol_class = 'GameQ_Protocols_'.ucfirst($server_info[self::SERVER_TYPE]); + + // Create the new instance and add it to the servers list + $this->servers[$server_id] = new $protocol_class( + $server_ip, + $server_port, + array_merge($this->options, $server_info[self::SERVER_OPTIONS]) + ); + + return $this; // Make calls chainable + } + + /** + * Add multiple servers at once + * + * @param array $servers + * @return GameQ + */ + public function addServers(Array $servers=NULL) + { + // Loop thru all the servers and add them + foreach($servers AS $server_info) + { + $this->addServer($server_info); + } + + return $this; // Make calls chainable + } + + /** + * Clear all the added servers. Creates clean instance. + * + * @return GameQ + */ + public function clearServers() + { + // Reset all the servers + $this->servers = array(); + $this->sockets = array(); + + return $this; // Make Chainable + } + + /** + * Make all the data requests (i.e. challenges, queries, etc...) + * + * @return multitype:Ambigous + */ + public function requestData() + { + // Data returned array + $data = array(); + + // Init the query array + $queries = array( + 'multi' => array( + 'challenges' => array(), + 'info' => array(), + ), + 'linear' => array(), + ); + + // Loop thru all of the servers added and categorize them + foreach($this->servers AS $server_id => $instance) + { + // Check to see what kind of server this is and how we can send packets + if($instance->packet_mode() == GameQ_Protocols::PACKET_MODE_LINEAR) + { + $queries['linear'][$server_id] = $instance; + } + else // We can send this out in a multi request + { + // Check to see if we should issue a challenge first + if($instance->hasChallenge()) + { + $queries['multi']['challenges'][$server_id] = $instance; + } + + // Add this instance to do info query + $queries['multi']['info'][$server_id] = $instance; + } + } + + // First lets do the faster, multi queries + if(count($queries['multi']['info']) > 0) + { + $this->requestMulti($queries['multi']); + } + + // Now lets do the slower linear queries. + if(count($queries['linear']) > 0) + { + $this->requestLinear($queries['linear']); + } + + // Now let's loop the servers and process the response data + foreach($this->servers AS $server_id => $instance) + { + // Lets process this and filter + $data[$server_id] = $this->filterResponse($instance); + } + + // Send back the data array, could be empty if nothing went to plan + return $data; + } + + /* Working Methods */ + + /** + * Apply all set filters to the data returned by gameservers. + * + * @param GameQ_Protocols $protocol_instance + * @return array + */ + protected function filterResponse(GameQ_Protocols $protocol_instance) + { + // Let's pull out the "raw" data we are going to filter + $data = $protocol_instance->processResponse(); + + // Loop each of the filters we have attached + foreach($this->options['filters'] AS $filter_name => $filter_instance) + { + // Overwrite the data with the "filtered" data + $data = $filter_instance->filter($data, $protocol_instance); + } + + return $data; + } + + /** + * Process "linear" servers. Servers that do not support multiple packet calls at once. So Slow! + * This method also blocks the socket, you have been warned!! + * + * @param array $servers + * @return boolean + */ + protected function requestLinear($servers=array()) + { + // Loop thru all the linear servers + foreach($servers AS $server_id => $instance) + { + // First we need to get a socket and we need to block because this is linear + if(($socket = $this->socket_open($instance, TRUE)) === FALSE) + { + // Skip it + continue; + } + + // Socket id + $socket_id = (int) $socket; + + // See if we have challenges to send off + if($instance->hasChallenge()) + { + // Now send off the challenge packet + fwrite($socket, $instance->getPacket('challenge')); + + // Read in the challenge response + $instance->challengeResponse(array(fread($socket, 4096))); + + // Now we need to parse and apply the challenge response to all the packets that require it + $instance->challengeVerifyAndParse(); + } + + // Invoke the beforeSend method + $instance->beforeSend(); + + // Grab the packets we need to send, minus the challenge packet + $packets = $instance->getPacket('!challenge'); + + // Now loop the packets, begin the slowness + foreach($packets AS $packet_type => $packet) + { + // Add the socket information so we can retreive it easily + $this->sockets = array( + $socket_id => array( + 'server_id' => $server_id, + 'packet_type' => $packet_type, + 'socket' => $socket, + ) + ); + + // Write the packet + fwrite($socket, $packet); + + // Get the responses from the query + $responses = $this->sockets_listen(); + + // Lets look at our responses + foreach($responses AS $socket_id => $response) + { + // Save the response from this packet + $instance->packetResponse($packet_type, $response); + } + } + } + + // Now close all the socket(s) and clean up any data + $this->sockets_close(); + + return TRUE; + } + + /** + * Process the servers that support multi requests. That means multiple packets can be sent out at once. + * + * @param array $servers + * @return boolean + */ + protected function requestMulti($servers=array()) + { + // See if we have any challenges to send off + if(count($servers['challenges']) > 0) + { + // Now lets send off all the challenges + $this->sendChallenge($servers['challenges']); + + // Now let's process the challenges + // Loop thru all the instances + foreach($servers['challenges'] AS $server_id => $instance) + { + $instance->challengeVerifyAndParse(); + } + } + + // Send out all the query packets to get data for + $this->queryServerInfo($servers['info']); + + return TRUE; + } + + /** + * Send off needed challenges and get the response + * + * @param array $instances + * @return boolean + */ + protected function sendChallenge(Array $instances=NULL) + { + // Loop thru all the instances we need to send out challenges for + foreach($instances AS $server_id => $instance) + { + // Make a new socket + if(($socket = $this->socket_open($instance)) === FALSE) + { + // Skip it + continue; + } + + // Now write the challenge packet to the socket. + fwrite($socket, $instance->getPacket(GameQ_Protocols::PACKET_CHALLENGE)); + + // Add the socket information so we can retreive it easily + $this->sockets[(int) $socket] = array( + 'server_id' => $server_id, + 'packet_type' => GameQ_Protocols::PACKET_CHALLENGE, + 'socket' => $socket, + ); + + // Let's sleep shortly so we are not hammering out calls rapid fire style hogging cpu + usleep($this->write_wait); + } + + // Now we need to listen for challenge response(s) + $responses = $this->sockets_listen(); + + // Lets look at our responses + foreach($responses AS $socket_id => $response) + { + // Back out the server_id we need to update the challenge response for + $server_id = $this->sockets[$socket_id]['server_id']; + + // Now set the proper response for the challenge because we will need it later + $this->servers[$server_id]->challengeResponse($response); + } + + // Now close all the socket(s) and clean up any data + $this->sockets_close(); + + return TRUE; + } + + /** + * Query the server for actual server information (i.e. info, players, rules, etc...) + * + * @param array $instances + * @return boolean + */ + protected function queryServerInfo(Array $instances=NULL) + { + // Loop all the server instances + foreach($instances AS $server_id => $instance) + { + // Invoke the beforeSend method + $instance->beforeSend(); + + // Get all the non-challenge packets we need to send + $packets = $instance->getPacket('!challenge'); + + if(count($packets) == 0) + { + // Skip nothing else to do for some reason. + continue; + } + + // Now lets send off the packets + foreach($packets AS $packet_type => $packet) + { + // Make a new socket + if(($socket = $this->socket_open($instance)) === FALSE) + { + // Skip it + continue; + } + + // Now write the packet to the socket. + fwrite($socket, $packet); + + // Add the socket information so we can retreive it easily + $this->sockets[(int) $socket] = array( + 'server_id' => $server_id, + 'packet_type' => $packet_type, + 'socket' => $socket, + ); + + // Let's sleep shortly so we are not hammering out calls raipd fire style + usleep($this->write_wait); + } + } + + // Now we need to listen for packet response(s) + $responses = $this->sockets_listen(); + + // Lets look at our responses + foreach($responses AS $socket_id => $response) + { + // Back out the server_id + $server_id = $this->sockets[$socket_id]['server_id']; + + // Back out the packet type + $packet_type = $this->sockets[$socket_id]['packet_type']; + + // Save the response from this packet + $this->servers[$server_id]->packetResponse($packet_type, $response); + } + + // Now close all the socket(s) and clean up any data + $this->sockets_close(); + + return TRUE; + } + + /* Sockets/streams stuff */ + + /** + * Open a new socket based on the instance information + * + * @param GameQ_Protocols $instance + * @param bool $blocking + * @throws GameQException + * @return boolean|resource + */ + protected function socket_open(GameQ_Protocols $instance, $blocking=FALSE) + { + // Create the remote address + $remote_addr = sprintf("%s://%s:%d", $instance->transport(), $instance->ip(), $instance->port()); + + // Create context + $context = stream_context_create(array( + 'socket' => array( + 'bindto' => '0:0', // Bind to any available IP and OS decided port + ), + )); + + // Create the socket + if(($socket = @stream_socket_client($remote_addr, $errno = NULL, $errstr = NULL, $this->timeout, STREAM_CLIENT_CONNECT, $context)) !== FALSE) + { + // Set the read timeout on the streams + stream_set_timeout($socket, $this->timeout); + + // Set blocking mode + stream_set_blocking($socket, $blocking); + } + else // Throw an error + { + // Check to see if we are in debug mode, if so throw the exception + if($this->debug) + { + throw new GameQException(__METHOD__." Error creating socket to server {$remote_addr}. Error: ".$errstr, $errno); + } + + // We didnt create so we need to return false. + return FALSE; + } + + unset($context, $remote_addr); + + // return the socket + return $socket; + } + + /** + * Listen to all the created sockets and return the responses + * + * @return array + */ + protected function sockets_listen() + { + // Set the loop to active + $loop_active = TRUE; + + // To store the responses + $responses = array(); + + // To store the sockets + $sockets = array(); + + // Loop and pull out all the actual sockets we need to listen on + foreach($this->sockets AS $socket_id => $socket_data) + { + // Append the actual socket we are listening to + $sockets[$socket_id] = $socket_data['socket']; + } + + // Init some variables + $read = $sockets; + $write = NULL; + $except = NULL; + + // This is when it should stop + $time_stop = microtime(TRUE) + $this->timeout; + + // Let's loop until we break something. + while ($loop_active && microtime(TRUE) < $time_stop) + { + // Now lets listen for some streams, but do not cross the streams! + $streams = stream_select($read, $write, $except, 0, $this->stream_timeout); + + // We had error or no streams left, kill the loop + if($streams === FALSE || ($streams <= 0)) + { + $loop_active = FALSE; + break; + } + + // Loop the sockets that received data back + foreach($read AS $socket) + { + // See if we have a response + if(($response = stream_socket_recvfrom($socket, 8192)) === FALSE) + { + continue; // No response yet so lets continue. + } + + // Check to see if the response is empty, if so we are done + // @todo: Verify that this does not affect other protocols, added for Minequery + // Initial testing showed this change did not affect any of the other protocols + if(strlen($response) == 0) + { + // End the while loop + $loop_active = FALSE; + break; + } + + // Add the response we got back + $responses[(int) $socket][] = $response; + } + + // Because stream_select modifies read we need to reset it each + // time to the original array of sockets + $read = $sockets; + } + + // Free up some memory + unset($streams, $read, $write, $except, $sockets, $time_stop, $response); + + return $responses; + } + + /** + * Close all the open sockets + */ + protected function sockets_close() + { + // Loop all the existing sockets, valid or not + foreach($this->sockets AS $socket_id => $data) + { + fclose($data['socket']); + unset($this->sockets[$socket_id]); + } + + return TRUE; + } +} + +/** + * GameQ Exception Class + * + * Thrown when there is any kind of internal configuration error or + * some unhandled or unexpected error or response. + * + * @author Austin Bischoff + */ +class GameQException extends Exception {} diff --git a/web/third_party/gameq/LICENSE b/web/third_party/gameq/LICENSE new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/web/third_party/gameq/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/web/third_party/gameq/gameq/buffer.php b/web/third_party/gameq/gameq/buffer.php new file mode 100644 index 00000000..1462923b --- /dev/null +++ b/web/third_party/gameq/gameq/buffer.php @@ -0,0 +1,378 @@ +. + * + * + */ + +/** + * Provide an interface for easy manipulation of a server response + * + * @author Aidan Lister + * @author Tom Buskens + * @version $Revision: 1.4 $ + */ +class GameQ_Buffer +{ + /** + * The original data + * + * @var string + * @access public + */ + private $data; + + /** + * The original data + * + * @var string + * @access public + */ + private $length; + + + /** + * Position of pointer + * + * @var string + * @access public + */ + private $index = 0; + + + /** + * Constructor + * + * @param string|array $response The data + */ + public function __construct($data) + { + $this->data = $data; + $this->length = strlen($data); + } + + /** + * Return all the data + * + * @return string|array The data + */ + public function getData() + { + return $this->data; + } + + /** + * Return data currently in the buffer + * + * @return string|array The data currently in the buffer + */ + public function getBuffer() + { + return substr($this->data, $this->index); + } + + /** + * Returns the number of bytes in the buffer + * + * @return int Length of the buffer + */ + public function getLength() + { + return max($this->length - $this->index, 0); + } + + /** + * Read from the buffer + * + * @param int $length Length of data to read + * @return string The data read + */ + public function read($length = 1) + { + if (($length + $this->index) > $this->length) { + throw new GameQ_ProtocolsException('Unable to read length={$length} from buffer. Bad protocol format or return?'); + } + + $string = substr($this->data, $this->index, $length); + $this->index += $length; + + return $string; + } + + /** + * Read the last character from the buffer + * + * Unlike the other read functions, this function actually removes + * the character from the buffer. + * + * @return string The data read + */ + public function readLast() + { + $len = strlen($this->data); + $string = $this->data{strlen($this->data) - 1}; + $this->data = substr($this->data, 0, $len - 1); + $this->length -= 1; + + return $string; + } + + /** + * Look at the buffer, but don't remove + * + * @param int $length Length of data to read + * @return string The data read + */ + public function lookAhead($length = 1) + { + $string = substr($this->data, $this->index, $length); + + return $string; + } + + /** + * Skip forward in the buffer + * + * @param int $length Length of data to skip + * @return void + */ + public function skip($length = 1) + { + $this->index += $length; + } + + /** + * Jump to a specific position in the buffer, + * will not jump past end of buffer + * + * @param int $index Position to go to + * @return void + */ + public function jumpto($index) + { + $this->index = min($index, $this->length - 1); + } + + /** + * Get the current pointer position + * + * @return int The current pointer position + */ + public function getPosition() + { + return $this->index; + } + + /** + * Read from buffer until delimiter is reached + * + * If not found, return everything + * + * @param string $delim Read until this character is reached + * @return string The data read + */ + public function readString($delim = "\x00") + { + // Get position of delimiter + $len = strpos($this->data, $delim, min($this->index, $this->length)); + + // If it is not found then return whole buffer + if ($len === false) { + return $this->read(strlen($this->data) - $this->index); + } + + // Read the string and remove the delimiter + $string = $this->read($len - $this->index); + ++$this->index; + + return $string; + } + + /** + * Reads a pascal string from the buffer + * + * @paran int $offset Number of bits to cut off the end + * @param bool $read_offset True if the data after the offset is + * to be read + * @return string The data read + */ + public function readPascalString($offset = 0, $read_offset = false) + { + // Get the proper offset + $len = $this->readInt8(); + $offset = max($len - $offset, 0); + + // Read the data + if ($read_offset) { + return $this->read($offset); + } + else { + return substr($this->read($len), 0, $offset); + } + } + + /** + * Read from buffer until any of the delimiters is reached + * + * If not found, return everything + * + * @param array $delims Read until these characters are reached + * @return string The data read + */ + public function readStringMulti($delims, &$delimfound = null) + { + // Get position of delimiters + $pos = array(); + foreach ($delims as $delim) { + if ($p = strpos($this->data, $delim, min($this->index, $this->length))) { + $pos[] = $p; + } + } + + // If none are found then return whole buffer + if (empty($pos)) { + return $this->read(strlen($this->data) - $this->index); + } + + // Read the string and remove the delimiter + sort($pos); + $string = $this->read($pos[0] - $this->index); + $delimfound = $this->read(); + + return $string; + } + + /** + * Read a 32-bit unsigned integer + */ + public function readInt32() + { + $int = unpack('Lint', $this->read(4)); + return $int['int']; + } + + /** + * Read a 32-bit signed integer + */ + public function readInt32Signed() + { + $int = unpack('lint', $this->read(4)); + + return $int['int']; + } + + /** + * Read a 16-bit unsigned integer + */ + public function readInt16() + { + $int = unpack('Sint', $this->read(2)); + return $int['int']; + } + + /** + * Read a 16-big signed integer + */ + public function readInt16Signed() + { + $int = unpack('sint', $this->read(2)); + return $int['int']; + } + + /** + * Read an int8 from the buffer + * + * @return int The data read + */ + public function readInt8() + { + return ord($this->read(1)); + } + + /** + * Read an float32 from the buffer + * + * @return int The data read + */ + public function readFloat32() + { + $float = unpack('ffloat', $this->read(4)); + return $float['float']; + } + + /** + * Conversion to float + * + * @access public + * @param string $string String to convert + * @return float 32 bit float + */ + public function toFloat($string) + { + // Check length + if (strlen($string) !== 4) { + return false; + } + + // Convert + $float = unpack('ffloat', $string); + return $float['float']; + } + + /** + * Conversion to integer + * + * @access public + * @param string $string String to convert + * @param int $bits Number of bits + * @return int Integer according to type + */ + public function toInt($string, $bits = 8) + { + // Check length + if (strlen($string) !== ($bits / 8)) { + return false; + } + + // Convert + switch($bits) { + + // 8 bit unsigned + case 8: + $int = ord($string); + break; + + // 16 bit unsigned + case 16: + $int = unpack('Sint', $string); + $int = $int['int']; + break; + + // 32 bit unsigned + case 32: + $int = unpack('Lint', $string); + $int = $int['int']; + break; + + // Invalid type + default: + $int = false; + break; + } + + return $int; + } +} diff --git a/web/third_party/gameq/gameq/filters.php b/web/third_party/gameq/gameq/filters.php new file mode 100644 index 00000000..4acf7cff --- /dev/null +++ b/web/third_party/gameq/gameq/filters.php @@ -0,0 +1,34 @@ +. + */ + +/** + * Generic function to make extending shorter + * + * @author Austin Bischoff + */ +abstract class GameQ_Filters extends GameQ_Filters_Core {} + +/** + * GameQ Filters Exception + * + * Allows for a level of exception handling incase there is an issue/error within + * a filter or a required dependency has not been met. + * + * @author Austin Bischoff + */ +class GameQ_FiltersException extends Exception {} diff --git a/web/third_party/gameq/gameq/filters/core.php b/web/third_party/gameq/gameq/filters/core.php new file mode 100644 index 00000000..1d7f7d7a --- /dev/null +++ b/web/third_party/gameq/gameq/filters/core.php @@ -0,0 +1,55 @@ +. + */ + +/** + * Abstract class which all filters must inherit + * + * @author Austin Bischoff + */ +abstract class GameQ_Filters_Core +{ + protected $params = array(); + + /** + * Constructor, receives parameters + * + * @param array $params Filter parameters + */ + function __construct($params) + { + if(is_array($params)) + { + foreach ($params as $key => $param) + { + $this->params[$key] = $param; + } + } + else + { + $this->params = $params; + } + } + + /** + * Actually apply the filter to the passed results + * + * @param array $results + * @param GameQ_Protocols_Core $protocol_instance + */ + abstract public function filter($results, GameQ_Protocols_Core $protocol_instance); +} diff --git a/web/third_party/gameq/gameq/filters/normalise.php b/web/third_party/gameq/gameq/filters/normalise.php new file mode 100644 index 00000000..7999b82f --- /dev/null +++ b/web/third_party/gameq/gameq/filters/normalise.php @@ -0,0 +1,193 @@ +. + */ + +/** + * This filter makes sure a fixed set of properties (i.e. gq_) is always available regardless of protocol + * + * @author Austin Bischoff + */ +class GameQ_Filters_Normalise extends GameQ_Filters +{ + /** + * Default normalization items. Can be overwritten on a protocol basis. + * + * @var array + */ + protected $normalize = array( + // General + 'general' => array( + // target => source + 'dedicated' => array('listenserver', 'dedic', 'bf2dedicated', 'netserverdedicated', 'bf2142dedicated'), + 'gametype' => array('ggametype', 'sigametype', 'matchtype'), + 'hostname' => array('svhostname', 'servername', 'siname', 'name'), + 'mapname' => array('map', 'simap'), + 'maxplayers' => array('svmaxclients', 'simaxplayers', 'maxclients'), + 'mod' => array('game', 'gamedir', 'gamevariant'), + 'numplayers' => array('clients', 'sinumplayers'), + 'password' => array('protected', 'siusepass', 'sineedpass', 'pswrd', 'gneedpass', 'auth'), + 'players' => array('players'), + 'teams' => array('team'), + ), + + // Indvidual + 'player' => array( + 'name' => array('nick', 'player', 'playername', 'name'), + 'kills' => array('kills'), + 'deaths' => array('deaths'), + 'score' => array('kills', 'frags', 'skill', 'score'), + 'ping' => array('ping'), + ), + + // Team + 'team' => array( + 'name' => array('name', 'teamname', 'team_t'), + 'score' => array('score', 'score_t'), + ), + ); + + /** + * Normalize the server data + * @see GameQ_Filters_Core::filter() + */ + public function filter($data, GameQ_Protocols_Core $protocol_instance) + { + $result = array(); + + // No data passed so something bad happened + if(empty($data)) + { + return $result; + } + + // Here we check to see if we override these defaults. + if(($normalize = $protocol_instance->getNormalize()) !== FALSE) + { + // Merge this stuff in + $this->normalize = array_merge_recursive($this->normalize, $normalize); + } + + // normalize the general items + $result = $this->normalize($data, 'general'); + + // normalize players + if (isset($result['gq_players']) && is_array($result['gq_players'])) + { + // Don't rename the players array + $result['players'] = $result['gq_players']; + + foreach ($result['players'] as $key => $player) + { + $result['players'][$key] = array_merge($player, $this->normalize($player, 'player')); + } + + $result['gq_numplayers'] = count($result['players']); + } + else + { + $result['players'] = array(); + } + + // normalize teams + if (isset($result['gq_teams']) && is_array($result['gq_teams'])) + { + // Don't rename the teams array + $result['teams'] = $result['gq_teams']; + + foreach ($result['teams'] as $key => $team) + { + $result['teams'][$key] = array_merge($team, $this->normalize($team, 'team')); + } + + $result['gq_numteams'] = count($result['teams']); + } + else + { + $result['teams'] = array(); + } + + unset($result['gq_players'], $result['gq_teams']); + + + // Merge and sort array + $result = (array_merge($data, $result)); + + ksort($result); + + return $result; + } + + + /** + * normalize an array + * + * @param array $data The data to normalize + * @param array $properties The properties we want to normalize + * @return array A normalized array + */ + private function normalize($data, $properties) + { + // Make sure this is not empty + if(!isset($this->normalize[$properties])) + { + // We just return empty array + return array(); + } + + $props = $this->normalize[$properties]; + + // Create a new array, with all the specified variables + $new = $this->fill($props); + + foreach ($data as $var => $value) + { + // normalize values + $stripped = strtolower(str_replace('_', '', $var)); + + foreach ($props as $target => $sources) + { + if ($target == $stripped or in_array($stripped, $sources)) + { + $new['gq_' . $target] = $value; + //unset($vars[$target]); + break; + } + } + } + + return $new; + } + + /** + * Fill array with array keys + * + * @param array $vars The array keys + * @param mixed $val Value of each key + * @return array An array filled with keys + */ + private function fill($vars, $val = false) + { + $data = array(); + + foreach ($vars as $target => $source) + { + $data['gq_' . $target] = $val; + } + + return $data; + } +} diff --git a/web/third_party/gameq/gameq/filters/stripcolor.php b/web/third_party/gameq/gameq/filters/stripcolor.php new file mode 100644 index 00000000..2656a7af --- /dev/null +++ b/web/third_party/gameq/gameq/filters/stripcolor.php @@ -0,0 +1,76 @@ +. + */ + +/** + * Strip color codes from specific protocol types. This code was adapted from the original filter class + * + * @author Austin Bischoff + */ +class GameQ_Filters_Stripcolor extends GameQ_Filters +{ + /** + * Strip all the color junk from returns + * @see GameQ_Filters_Core::filter() + */ + public function filter($data, GameQ_Protocols_Core $protocol_instance) + { + // Check the type of protocol + switch($protocol_instance->protocol()) + { + case 'quake2': + case 'quake3': + case 'doom3': + array_walk_recursive($data, array($this, 'stripQuake')); + break; + + case 'unreal2': + case 'ut3': + case 'gamespy3': //not sure if gamespy3 supports ut colors but won't hurt + case 'gamespy2': + array_walk_recursive($data, array($this, 'stripUT')); + break; + + default: + break; + } + + return $data; + } + + /** + * Strips quake color tags + * + * @param $string string String to strip + * @param $key string Array key + */ + protected function stripQuake(&$string, $key) + { + $string = preg_replace('#(\^.)#', '', $string); + } + + /** + * Strip UT color tags + * + * @param $string string String to strip + * @param $key string Array key + */ + protected function stripUT(&$string, $key) + { + $string = preg_replace('/\x1b.../', '', $string); + } +} diff --git a/web/third_party/gameq/gameq/protocols.php b/web/third_party/gameq/gameq/protocols.php new file mode 100644 index 00000000..57a1ae20 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols.php @@ -0,0 +1,37 @@ +. + */ + +/** + * Generic function to make extending shorter + * + * @author Austin Bischoff + */ +abstract class GameQ_Protocols extends GameQ_Protocols_Core +{ + +} + +/** + * GameQ Protocol Exception + * + * Allows for another level of exception handling when doing loops. Makes it possible to recover and continue + * when there is an exception within one of the protocol classes. + * + * @author Austin Bischoff + */ +class GameQ_ProtocolsException extends Exception {} diff --git a/web/third_party/gameq/gameq/protocols/aa.php b/web/third_party/gameq/gameq/protocols/aa.php new file mode 100644 index 00000000..b9079d47 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/aa.php @@ -0,0 +1,30 @@ +. + */ + +/** + * America's Army 1/2 Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Aa extends GameQ_Protocols_Gamespy2 +{ + protected $name = "aa"; + protected $name_long = "America's Army"; + + protected $port = 1717; +} diff --git a/web/third_party/gameq/gameq/protocols/aa3.php b/web/third_party/gameq/gameq/protocols/aa3.php new file mode 100644 index 00000000..da00b2e7 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/aa3.php @@ -0,0 +1,30 @@ +. + */ + +/** + * America's Army 3 Protocol Class (Version 3.2+) + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Aa3 extends GameQ_Protocols_Source +{ + protected $name = "aa3"; + protected $name_long = " America's Army 3 (> 3.2)"; + + protected $port = 27020; +} diff --git a/web/third_party/gameq/gameq/protocols/aa3pre32.php b/web/third_party/gameq/gameq/protocols/aa3pre32.php new file mode 100644 index 00000000..c2d8945f --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/aa3pre32.php @@ -0,0 +1,425 @@ +. + */ + +/** + * America's Army 3 Protocol Class (Version < 3.2) + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Aa3pre32 extends GameQ_Protocols +{ + /** + * This class is no longer valid + * + * @var int + */ + protected $state = self::STATE_DEPRECATED; + + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_ALL => "\x4A\x35\xFF\xFF\x02\x00\x02\x00\x01\x00%s", + ); + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_all", + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 39300; // Default port, used if not set when instanced + + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'aa3pre32'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'aa3pre32'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "America's Army 3 (< 3.2)"; + + /* + * Internal methods + */ + + /** + * Called before the $this->packets are sent. + * + * @see GameQ_Protocols_Core::beforeSend() + */ + public function beforeSend() + { + // Encrypt the data we want to send + $enc_data = $this->ssc_crypt("\x0A\x00playerName\x06\x06\x00query\x00", TRUE); + + // Apply this to the packet + $this->packets[self::PACKET_ALL] = sprintf($this->packets[self::PACKET_ALL], $enc_data); + + return TRUE; + } + + protected function preProcess_all($packets=array()) + { + // Check to make sure we have zlib installed + if(!function_exists('gzuncompress')) + { + throw new GameQ_ProtocolsException('Zlib is not installed. See http://www.php.net/manual/en/book.zlib.php for more info.', 0); + return FALSE; + } + + // We only got one packet + if(count($packets) == 1) + { + // @todo: Looking for example to test and verify + + $packets[0] = substr($packets[0], 10); + } + else // Multiple Packets + { + $packets_sorted = array(); + + // We need to sort the packets to make sure they are in the proper order + foreach($packets AS $packet) + { + $packets_sorted[ord($packet[10])] = substr($packet, 14); + } + + // Key sort the packets + ksort($packets_sorted); + $packets = $packets_sorted; + + unset($packet, $packets_sorted); + } + + // Merge all the packets and decypt the data + $data = $this->ssc_crypt(trim(implode("", $packets)), FALSE); + + // Decompress and return + return gzuncompress($data); + } + + + protected function process_all() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_ALL)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Let's preprocess the rules + $data = $this->preProcess_all($this->packets_response[self::PACKET_ALL]); + + // Lets parse out all the data + if(preg_match('/attributeNames(.+)attributeValues(.+)resultCode(.*)/ism', $data, $m) === FALSE) + { + throw new GameQ_ProtocolsException("AA3 Packet response is not in a valid format"); + return array(); + } + + // Init temp array + $tmp = array( + "keys" => array(), + "values" => array(), + ); + + // Pull the array into named vars + list($all, $keys, $values, $resultcode) = $m; + + // Lets look at all the keys + $buf = new GameQ_Buffer($keys); + + // Skip + $buf->skip(4); + + while($buf->getLength()) + { + // Pull out the string and strip out any junk + $str = preg_replace('/[^[:alnum:][:punct:]\s]/', '', trim($buf->readString(), "\x00..\x1F")); + + // Do not continue on empty strings + if(strlen($str) == 0) + { + continue; + } + + // Add to the temp list. We will clean this up later + $tmp['keys'][] = $str; + } + + // Now lets loop the values + $buf = new GameQ_Buffer($values); + + // Skip + $buf->skip(4); + + while($buf->getLength()) + { + $str = preg_replace('/[^[:alnum:][:punct:]\s]/', '', trim($buf->readString(), "\x00..\x1F")); + + // Do not continue on empty strings + if(strlen($str) == 0) + { + continue; + } + + // Add to the temp list. We will clean this up later + $tmp['values'][] = $str; + } + + // Combine the keys and values + $tmp = array_combine($tmp['keys'], $tmp['values']); + + $teams = array(); + $team = FALSE; + + // Let's parse the combined array and make the result. + foreach($tmp AS $key => $value) + { + // Is player + if(preg_match('/^player(\D+)(\d+)$/', $key, $matches)) + { + $result->addPlayer($matches[1], $value); + + // See if this is a team value + if($matches[1] == 'Team') + { + $team = $value; + } + elseif ($matches[1] == 'TeamIndex' && !array_key_exists($value, $teams)) + { + $teams[$value] = $team; + } + } + else // Is server var + { + $result->add(substr($key, 6), $value); + } + } + + // Do the teams + foreach ($teams AS $teamIndex => $teamName) + { + $result->addTeam('name', $teamName); + $result->addTeam('index', $teamIndex); + } + + unset($buf, $tmp, $data, $keys, $values, $resultcode, $matches, $teams); + + return $result->fetch(); + } + + /** + * encrypt|decrypt the buffer. + * + * @param string $buffer + * @param bool $encrypt + */ + protected function ssc_crypt($buffer, $encrypt = FALSE) + { + $master_key = pack("H*", + "f5c5914b27235dc0dc274200ddd187c32fe02aed5fc5c079518f49208e4c5548aaef313c5d2e7c91dc580d3cd9e1aec577595325d3c5c84b44a020802becb17e". + "7d6b6b87e8a4ebc8e4cafbaf5720f9600818b334ad2695ba0f19e1fbd48d0139f05e9059e98a15c79ebabb4f3aa8039d8720aef2bf1b4693a67a20a114b8505b". + "693cf5b24a236503582ecdb8109a7d89a8d90d660b96435b4656ecec3fff2086e94c54988843d2aa55adefb2d47fc804c0024a7897e993b2326e8990e425f7c8". + "38aef55f2002f22d84479f43849de260a8a2de6a7de09225c275a172729e65be687182bde68cb17b3fd77bf513c8045f0b6696d3a501b255db0632e36c0e7806". + "c5c193b5b9a9c621f0ac9a0ee72196edbb336e7431b75eba95d02191048ab7c3874578218d79a2623e308184fdac98a1568c09b8907d8411e29c53823a3a68bc". + "c785547ebb29401822da7fa59c6fc412cf2a9201f31336bcdffe78501058b1d7814e920ceee7aca8fa798f10f0a8ba19a1deae864e1c77f974880e5571a4380b". + "52d3357ec8cbf8ff6ff7e8f3fa6223f923e4a7bb1918054bcd2a115e466307f39d964c051983f8b2e5db0b39332ec08c94d9b36a4594ab5e868bc888e4586687". + "b6e62b2bb06ad0903544e379d744896f95346a0238b2b72c6d38ed1bf011185bad1910812cfe2c5b38db10433088f2e5a3746e7302467d35e8f07722fad1f7d4". + "283fbea23fa6f50f710491b1f0a8dd3a187939e7f344de57c256ffb063791fc556d3791570a873537c3f05f8ca08aa1eb2e3f641e0fb46fde7394f8fb4c216d7". + "55c020b405a21b8e4340136fc9583800afd87a677d3d9b6b95585ba502d6db2dec504f25b612340e29be64700682f4f012908e2672916ba83d35deb58d826d83". + "d75a61f726876747d78df10a31f6acb36cb64dec47b7da11c7e7177dcc097965a50065e8e5f91732e20647604c00c0fa451f7ee140d93515b7b5e6f9e0c92ad0". + "29648ab1e0ea363c5a19d12832c54c0ae67baa7e029217ede5f97cd07ebf3aaf14c020f4646e3792e2472409299868b9ee1ce7a69a30203218289523d848a2ee". + "42b96edf05f24182491dfb048c17f815aa8983d9ab72723defbe9750cd694bc1318c92862ed7b7ab1e37472b986a7f4745224fd723e4e6ef53ff6d5f51f1b8cd". + "34b32b9ac92968e5ec8b631aa750e7cec51e7fddca5da1cdc836c0243ab2a2f86d072479c117738fafba4d72db6fee13274d652a7c76ff962c1389b32f95f3c0". + "04d178b71646fe084507e7dd4b4db98405cb72399f78f989c188fb2ed6e18e5aa417adae504d33ad8414f9e3a6e466837062e8ea91664f63134539679b119d6b". + "3918f833ceddc249933b0ae83e0965b38fb86d3da02622d02f57c7282e5f0cdb18f71e7450c538ddca55588575f80754dd0c89840bcf7e246e8f041309069f15". + "a49c27fa0a5913c72be881ae27ff6b0332701d96dc295576d2a9bc0fd266f5604da647f78d1c2ced95c4cf8a929c55bf524198898b444c67040d7c7debcc3cc9". + "7cab1a8fe190f4db097beaccea9a34e38380b43bd2b2bf98f471c02894aaaf3944680988497aa74d293238d503a4df19d90af204fdcbb1875170a96b7f3e288c". + "0f24e1c8b9ce4f77f2b03944c2abbacba69331a244923c38f731f368d10eca82dd503bdece016064c68cb38a4e3408712959cb5216dc42bf5365eb789c484bcc". + "5813a1f1680fc5606e8da06bd5a68a73bd593fcd4aeb9aca06bb258f84a38dd0d4c6c0c355c4d5e0e1a97abaa11869f26285a99db4dfb8eab0b0f53e80d2486b". + "9a6cc63affac0b830b12434ddbc1c4ef3ee46af67fcc711b88a352d2b324c0acfb35bfbe74865afd7f3293a944cd9f69230a206c5112ed9858497ddc118c0338". + "63f1a974b033a225c74e83c9d1bec1a3e6a7b2b7ddab58aec40fe4bed9e2fd1beaded608c695dafaf4d683fdf3b9175d1283d7d99b47c40209a555c317e29bad". + "574ac49e78ae91896b527d27f04d89b10d5f754b953d1218bf01fc06086c031ff334eab692e9c6fb221ac0f3027283ac5350d860f2d6125d31edf4b7ac806f21". + "abeb04f84230e8c17455e54a27d6862cfb3279370eae1cdb1f84c10209e89241182c307b45a6b97520a62bc263c66f78d27b52ad9728f5d78c1626297b1d1cdd". + "e47fd67d9f1f4846a3643810359f2cc6b22a662683836eb48f6e1605be3a830fe29f0c54412e7d82aefff9748a2fddb368dd0103161e2a17da69216e22adf6b5". + "7ce255e400279188655820eedd5a1935aa3d8cf621fa312bab89cbb3071bfbe7e0635126de8217bd5c342f35824511769ac6b72de09b87012cd85f2cbef53e11". + "9aba484771b15bddda183501230ae6a16fcde55a161df16f178e04478a3711437dc91eeabe92e14b44d2f49036532be42c425346df9d91288aa409a63272e061". + "baaaca491cc04c44b2ac739290baa76d9fdc7b66733548af6411a6ba790c4962ddf033e63fab462bc0ccbfa45d45ce377d32f4c7e905cab5fbbb524f8c2907d0". + "41b304d1f38f348efd34a7d51c118445d05353b5f0449f368450782df457ca55169bdfa817a94e1082faf4115cf3d6d890481affb2feb95145691f152485465d". + "0f8dba4cde2079784574fadbe805222e3a132934f1a419cda032b310fd7dfa2830d3f3385d646ba0c373cba4d624a6267300014cdd2dd5e87999aa5b0e5df0a8". + "de50f3473918474ccf82f9c8ab9f31379a9d8d00bead3bc8b9d00f4ebba9c7b0ea882454e3a785e096d7887b3a507f089dba88925df12c633241ed2f9f68905b". + "66775d1d0ca3cc312f7be8641856be8de24248e55dd737df8410e23e9457024f534261f09ab278821b1c89da824f7f546a4163f4d53ccf07ee9bd59adb673822". + "87092b94a7847141a796a6abf90f7bfa5d8967bfba2275283863bfc3f8283f0e5b223748a55dff04f3c6bf228bb1e0bfd2c80289abf5819e165268b4e687bcf4". + "a33f1c42c47a6236ca14c26778ad2cbe013c20807e45276d49a4e0df7df7c42d2c73f298f61fc8e778ba953a71c6b7d1779624552df0f3896a790671a3a981fa". + "17914d856321d0997ff4b2d05944335ceac60b63b1d827eab5ef7483990e9bd1b5453a473e1efd476ba1e093466cb21dc72e35dc12bb8c8d3bb29db420251590". + "32441b8a7e9458cad9cdc1551ce52312bb27d858a8ae319e525b38f20242a60933b2a21bd858e147cc6ee702983c84bf535d1575a54dc46c03cdb42a39d1a64e". + "433d9bea41f9915f7d9d462d4308baccb19bb1adc3e0125715950f7c7f8b54312826204fd512386da587bad7bf81069dc554fd8fd77153832225e56a7fa4046b". + "d588ed258dc7e54ccf1c021f9800376376bdcfc62116555ab0e06b3161b3b7a6a7a87de2371215207c43fce54c82feddc5d444b08f6a30c0095007d526da1b02". + "41563a9360f86ef3b824294bd174679f4dee74912acdeb00ac96a713ad86dc212a544b7420fa6c83d5dec48400e1f11f8163e20c932bc893820a8261939e0f85". + "fdb416c6a0a18cc0182d675702a8362694f23ce686962150f862357fe84a0b572068c7e0578909d7f82c87cd17e7ef50e5566eab694ac76edb4b6d8a85cd2910". + "0b93272b0a524a24db8db7d4622fae63d982e4090fb519e30736d5b5152d58a234919d216d0294628841cba91ed72d985ba92f7cc548378e7ddf812816ad99dd". + "27adffdf5b6d762a79a942d8af9a8f0ac81afc98869dcdcc06835478947ced5ccbb22d02624e207c774042fa8c133221c362bef69582c52ca9c014db1ec2d351". + "a1d72bb01c06e32ca0a4ecfe923737f0f7145b27c943a9be1f174dd46d3af58e7a2f612177affd11ae7e1b9231aadb46bcb732ee79de7e62f467721f06d8e9e5". + "59b526bb702ddbc0f0b46a2162458c15c0154cbb1b1edad3fa198a0781279ecc5e5391269c335bc94b2f21da781cf943cd0e700206128fe1f1e3af4e70bfbaec". + "1c7ae4884c7e7544050036b001f87fc2f10762888701c160010e7691ea2b53b646d22178ebf1a56eb9cba86ffa2b570d846e231037d403298103c61732b04113". + "ff7ec74e0a671332f7df9da231f995c1fb53523c17c23105312b7d8ab63e5f6a0e7b9d106f3ce575d14befb3a5803aabcc9edb5f1ddf9dcabff4efbd785b169d". + "f7fb1b991faf63f064b5fc8f2c7fcac4b35a61f19c92dec36a6aadf02dc3942dde51d7225aefeaf6b7527183c2adc832c6bc8735bc7be2c18ad3d70653f91581". + "ce42a275ef6715932ae7513d0ecb726be54c167cc89445a08cb8e12fc583aee815b3947bd1ac781fcbfbdda25fe3e931a21c47058197ceffbe9bd2ac6394b2d5". + "95c3e10076c3aceba33b1556029edfbc04849e0d66713f7beeb1517dcd43279a5073ec9fa221bfaceef0f639e771a44156778cbb696af28e2437eea3fc025d27". + "70b1409d978e4ec808c58288d525ac977db0ace80d9554925bf8767b8e91a9bf1ed25deabdbb93315ca08f711ae3f768a911eeacd93bfa6db3957da83c0fd945". + "a7e596b66530aa7347e04590fd31db6b49485a9ea8208c0aab4068f482b185aaed6ee69e32f9ff7b882763da34f6e3bce94c79353ef6849d47e6345d8727e076". + "f1aa0133c2399e4d777525fe9aa29e75d23df6e829f9058580413d5c24f85568beb1343430f393adee28ab54e220b4c884fa6ebc2825705f863ba7d82977f653". + "edb2088abd84ad52a1810a52abc6e7c3b5687f3bf4744941ce48c876205f2497b641e6e4bb565ab816425c348e1f034104efda9a21723b00cdadc6ed2af6b225". + "524ae512afba6bc19c471e14bbba042dba641424005a816f25aee44ee84cf2f729b79b1b9d58218f0274d92168c9bb1cd1c141b5f8341a3a4dc78c0ddf08dfd4". + "110b4eb0b71b265fe70aa5a4b2186cafad5ff94dafd5b4b4560bac45cb47c4c863274ac2d84af46b75bfde496d39984ff0af8ab7d98bc12c02ce782b23268d03". + "864826b0201d8d1e0c09c9ab229a2f7fe1504795bafa8b8ae13fb046a2f35233a49b772b57862ada835951742439693ed9f3a080aea7a1309de4ae04b1ce3d78". + "72cdd85a3544906afaf55aff8255bdb2367c7ecf184c91c8f4c60a1301b80f8bb9f0ff6d80ac6e1c9d6c9fafbc65199790e0a9c323e68b105f5c56eed2f60294". + "5ab59d79698829ba092cc97f37dd023595d3fa014e718cda23d6bdbbfd70c2c6cc1b9121d22eae0bde7b94277dc8e5e096d60351f2740ddb986c7e10e0af8a40". + "e9bd526f863cde028dd253e18013d3c76c2006a9ab9ec3e7b6b1aca865b2ace8c8debb50ae1efbc0e49dd69f128c28bd02d79f22717e2679d5142540733cb278". + "0969944106122d5f2baf97f7e09ef67b894cd191411126ad962e4b9c5a0bbe83215563662ce5f063ce2a76c2e09613539fbb094d389e739ca0a3fc34bd1692ba". + "f0601e2122a70fdf68ede6c431090896622362c59801000727718f4b551f32340fc5f740e15fc0a023791aa57a6cc97af3077f5d71d33cbc864049b30cb11ea5". + "23c15141ea5ac620aec5f81e6661bf8f01a3c817ac1ab592570b63764402e4934d776df03cadae448c5d9082c30c00737e4bbe5c184a1167507d9b99bdd05592". + "456ac25dadb5beafe282028611db969c44db7bfb2cad349c0ecbebc281a00ad4f70cfd889b3533833ab845f86403e6a1970da6b5c8b8e82e9f42a82c7c14e535". + "16b3d9efbaae6ca6b9c93977f17f58ec29a1a8bb188fb15f377bf50d37e84781ca1716052f657a361cbe44eb227002a57390873e54b8695f76fe0f84f873e021". + "c92945f3d7b54861be3c237701c140c3a4e1b84fa4bab910cd265393e0172293d6fc40fa1872e175d7d3f06153a9eca3f8db85c2166f68415eda3bf4aee35adc". + "0231cd6cfe5d3a23b51fb0105176b9cdadc28304d27fef698cf4155235d07ecfaf5a2c5f8610a63ee809b0e0260251c33873dceebdda1ec3725d1376031e45cc". + "731a870b39edc97b549b96624c891984acf7a422584bc56f2104256f15da552d0a8376a546b6966153728ca1f38514df0d458375e99bc01fa498b07abb33803f". + "da07c4149e6e5773f9ec65ac3c87ca7c515f263de3cda2d53edbc20c47486ee33f9810c8226bbc9c52fcadb1f01fe28bf099b8afb9f1798e0b9815210c559187". + "c562b5e45350a5d0708c2fb96bad405ef4b8b535066ed02da198e4a3a4eaf075450c87f6d9840c8e00b8e316bcc7a5c6113fefbd72b0c7f6860fcecc8a3f33fb". + "a2999e4f3f3e3da5d7bfcf5d22a93f4d16ae6dd053685dfc7223628f92086735d09551bd29e8d0f537d06f33536fce8360d7443f583e9079685efce0347c1ffa". + "fedd0b7d1125f0dfc9bb21460079f286abbbeb549bb744aeea0b7a6bc66a272c8af945621b57b8380d40fa067c3060b9d44b79bd4333ec96d47632124a9aad0a". + "2df287eda9312f70f12f544fd7bdef9e6cc5e110effb8dbdebb821571f0fa95301db9da0bb60b77af6d5b7de00ca26039f1dda92f7a777c75d02fc340f1b81b5". + "e7c5efc6aaa6ffe3b77db348b7a5973a9465cb1e01841fa10f398318bfb73a4f8f53a4bded656f35db0ef00685826d8eac3aa0941623b3401ffdaba927bc91f4". + "808818548a60f653e9f340f79e40d666525923c4847ac3c0a9b36f3069620b0aea677ee7afa2c333987d9a5afade1b0e1e22ef7470228b07c9f482a6c343a37c". + "462a749c02d4cc86447cc16c3c68955afa80e63a3a41aaa1375c7ca0cffa0335e96e599e1b6841ae5693b5fa6ff437c3c1dca20075b7a58aafa81845af0aa8f6". + "30520d89a362d667447045c2b39f88f573f6b76b95ea4a98950ad797570b841975e9841306223dbefd21a4f092d69452c4539c664e27e110622ae7a7db5073d6". + "17eb023b36f28a13eeeebdbd964df63dcb18762950b6bd3eeead2a25b9bba48060ac8b82af3f41ecafbb7134140ca8cc687b92eded8bdabd9567e50950ed617a". + "a114d3db8648f9ab48a622456aec56fe79cfa6225fc7fd3fb0607f9dbc1bd861b316600fc10163fe8098ea685bc3fe06435f51cb1ce7ffebae67b3114fadf8c8". + "808a4044bb06638d05bc9a73c44c5b1eb7c83cdb4bde51ffa85413a97fbd534ddb17dc899fc4e2ced6ed81eeb117b4c77f9ecd03251367649a5649ec58567907". + "4fc8c2702dc42a58308f4023fb2cd30c79ecb9a952cde77dfcf92d8ef234811c327112abd568c49d4bf693f611d07e433fcd0a396530c6a279eb3ba567d780b7". + "271b6bfc7f1683a6b9159e143788662e8c5f73dd25ab623633efe781edd647b32003c9f3eaf236d968244e4561bc855848b839bfb93af2ea3e230a30089230c4". + "2e593ed3b9be53d677a7c9da744ee1961aaccac237f9e0bc1f886a92d5f335c6c0b0250ea76fbdcd85ae9cf6afe7ab25fd6b4753be6505b986757b003b94a089". + "d6a42b1fb24d2249ec917bb0ad50c8bd31265f82071a0816c3f8985edf0311205f83eaf8ff5587a3c7c24938a3f0cf9ff438b567d71407a51292e6d7e3f939e6". + "cdbecd49e913793f73cb964406934907ca4d48f44bec301bdf0110986757fcac6c2cca84eb7c5fad1662d1a833d24fa356771d6b772759a4837d9872d23ff1ab". + "219597aadc062f317d6cbc044bf65dc5ddda95ddc34d68584b7db991c8441a43e0511f71b88dda141f36b7cb326650c3244b989f1b992d2baa318e2a76dd1c34". + "a946c843255f65c6896eac3a6774ceff50b6f66b752672f5ce8dc84149ba6b227da844254d01bf470f6c987e8b5df2168414bcee11ad8c131d16e43addbdd493". + "595117f4f211c5d6460ee1be41e72b42c21252ce6dcd9838e53b0e1fd8d1864c2d3d219b82d42d0446865848431658732a78f0d9348f8044fa7f576d11562d25". + "d7b681f714c4b43532543d27069a21d1d152e646c56d75229bb198f87676108306e68fa49751f3b1d678bbf1ea38b2e0712d896882b5ea1494136f23a7e1d528". + "ca456c6c2a2cfc8cb6b6e7e6526aaa1da082653492b624936213569892706d8f9c6496b1193ec5a4294e3c1da14b25c24337cf9bb3490ea3f8a54e0a5b9f77af". + "fc70fe8dcb7687a9f45c7ae3ee8f2a94fa58e6c920cce1f447fd60526fa71b6f1048a3dcc7680e3b20ac66d78290bfc3878e72d4876e014036b0b80b6be4bf2e". + "a358125bea811b51af76a0077b3a615750a9ca3368d1d17e060a0d37bfd3b13c91412ca83298b06aea3048607f718c04667dcfc7faa4ac5a594be1c1551140ba". + "9c1ea7cebc074b1fbd338eef831fa3eb1f39088bcf1cf13bf706b1d287e12b165f4fb3e6c4586067c5e2f461c4cc86400b456428e8767c1b57a7bc3e64a8abe6". + "d253646f8796763b2a33de35c6f1667d06f30bb12c0fd0e28e4859ebdc2f96236af4a895d9a7d6fb90cbb60084db28a0c628faf7653c316ec69b5c5103aea495". + "792efd58ec42bc950f8608d5fa6834aab7bd2aaece33b3e16756f518a5410e8957dd534437e8c152451d86beb20124e8fb9e672d13fb7e98e153c124fdb2eaf7". + "f94a23efffeea25ec31f821e492d9de00a6d056c67e565f734f864d425035bb13620b7a1f44ec02ab7a6b1c4a38511b6902cfcf199d3918eb07da11d634add44". + "0860d123fa2b8003f87270777c6415e32f1b34dd6e1e22df3a78684e1169fce84b61cf461544f4e891fcd9d1f5a1e5fef148aeddbfcc922f5d7bfd3bd2480e8a". + "3318c75ce0afc24ca179fc0e832ab64368c174407bf2cd45a72cd5c9e7dd0b9def7500cec54d4d692938a1bb18289189d4b2445640d8abc9a0b70c3ffc8ba3c8". + "d483119a4f63851a57cf30f48c88616785a5ee00cb9221db45dd8dff118ca33bb4ae254937891f2c971edc8614fa3fc43e56f297a44a234fb1737f23d44a15f0". + "6a9e364fe1daa8e28bf72927526296202713f76dc8342e3843483b479ff793697b11a934bdc206905dd020e2f321cf8d65c245a8e7c4275f87301211800f0751". + "4e9cb59b88540f5441e6b09b4b73112d855ba0dffd4affd670c4f76ec11ac07a6cc2201ac65c83b3b3e4dc10d991ef4424cd001d34f0393dc262957df641469a". + "e00f74c527f8c99f50432c5ff4c4260ec6998b7ef2a0223290762126542d8aa89bfd241ac59e3a9a6c6f13afc9d69a771d124d16359525e4b374605b699e32bd". + "fb393d9397767bce32ab2d5557d05c33fa54183b0d5facc73a097441aa34abf7d6ac36fb35d6be7f19d0c26c7ad564c06f8a4f616ff4819c53e8b29e782b8791". + "c4039e5d049bd36819ae6d01a113eae6260e25150b935ee364011558dea97e1ee0e7f2938b7368ad9a5a86bae4f89a9ffbd06638566a785cb6ad3982b133ce6a". + "3edb13aa2c4ad4db7052ac646fcf336b375efb6a360d448862f2b711db3d8e657a706c14013664beae06b1a067fd078b0a8800c01dd610d583bee4fa4634e4f3". + "5251372b8144a7194ed60dc2539283ce909e7d65338a9050b09b66b647f30b6d595d7e03d9a77029afce140df7717f64949ae1362f94602dc2e70840e3117ab1". + "a26cc8e8ffd068ec225f0b75b2de63e3511f4485c87fb0087e4421675f3754bc4bc9c0a38db6392661e8a59802d83f887cf81aa99ed13a10b4b8a176144f76ce". + "3a192cc77b09e3f8a087db488f3d304d048623f46a031ba9251896cd08ff601dd0b933f5110b4cc9d943b5705b2435fa1c0adaad6c3aed88022f57cc3d71048f". + "9d5f420cfaf737b8a9f2434601b296b14384618fa9b76e6acbf1b55ad7130f582f36920a5aff71e15d120b11d6e0dd374554803538f3b12305512cf24322ed52". + "cd7ce5f409efd2f2752684bc326bf4548fa17169028c819ba342ee672682860a6de09752f509caad897484160895dc712b70bd05d588fe218fd85718b9b833ff". + "2c18e2566416ce1e52c3d7dc696cca1ad02b9b99e2953f92d8fe7ac0e4d75bd2ae2834b9ad8e87f179cdaf5e75609abdf1236787fe366347c32991f20c7faf41". + "b65da4ed5edc3cab1134a4ee0a3b565cab7c6dcd6f93feb528ddf0a1e992f6ad4814e51d338433dc5b52fddd8e780a312d12c80c4dbdaf8818b1c84883d8be41". + "186de5fdeeb9c7b7542a8429e53645a313cd8c9a53c3790b9fcf0143421da3bb586762790c91b0110f68b5fd111338560437d7d77457fb5587efb40a90ed1c02". + "838ba4e83b0c6adb175d94b6e14767a4f4a127e80f79be7741f4dc446c520176fd5b0412cc4d7a8f3d293e438d50e4e79e52bbc2c3bc6707d97b6289f1b39733". + "48c9351b66be55b2152bee9b76c42dc057d12134180488f45aee9491fe72f8634e3beeda8006869a829d2d58614150ab489dca7af268c09dde668cc20428ff88". + "366a3c0119446bdba29c39b0723fcd639393d397d138ab241c187beac647d8f73e5e42b3468e3958e0e73908c081ce0b6c894f0409f3bd321807a1633860a8e7". + "49cb4a10875a65b3f0a073f48f141747c88afe9039ef0795752dbd07ef51a2dadb40bb09bb9d4fcb328f68af28f8d76085fccaef4afe848a93c4cac43f55863a". + "21b540e6d408eb55fdfbd2a0c13fbae6fdf68e51423737f6966105d1ed57570bb521adb9576b06988d7d5a6445fe77d177076d47ca45b437a9780b376d49689e". + "6b0be983d90f46dbf935e14b53f3bf7ac7aec7fc1b92c14f161e59ae2620f7552206f22a365c91476943b8b51e920661efc19d040070407ba1cf011d3a0e072e". + "68d10e064619aa2184d7e848729b254af6b83db15fca2134d0d54efc761fff25c1169d608ed2434de8ae3cafb8c3af0b5b23a16183b5ead5dc5d175c955f4db5". + "454623d611244c462776118992ba03e8e20e6e1d9d6101d2286d7e040d5a56f22d6e3ae86bd6a0605c8b34d7a385fee5f3c9b6d0cf550f7aa67f338d8a014dfd". + "639cade855e8d25df73ea01bc5635bb5e032269b2a10f6b2baea7c4a88ede42caf91d7c9d3b2802608fdc361e23ee8cdcc1c954da86f929e9721130ef6d74e99". + "180f8c8c2263b41f538e105bc5f411f8dd1c2d3e0dc4540ff9cbdb9a6c44524ebcdfe37d9427a43dc24fd28c2fc25baef96490ae847b435ef4eea87db030829d". + "06b4c5d9271c8ffda114c336f5d82f9e6ca0d140112f364b1613cfe84c6e924629cba51a7d21f92ce26802bda0651340a8aad0c1ef439acc5552634304321cf6". + "02851751630d671a8cce7028f1cc6fdbce64f762c8ed522c2a81c2886986999a85d41a87d2ba5281dcbc2dbd728559470017e12fd70a97a771de499d2953c49b". + "0e60abac5ced203dd26bb75df922938723b1341bb07b0250d7af1bf91788994f8ed193221dd829e6665b114763e490fd8482955b097ac3b5b124bf92ae8ce902". + "1897b67db820cbfd646fe2c61e63baa972651a47bb1aae56f5e623a1167beff84166ea78cc9854b21a9478ebf3a1429226213c20a7a9ce8031eced508b937263". + "1357591069d5c482c0f6f99e4a6084f34fdab7b26399b4efcb0e5217e4e9115d0f6011bcfe55e0f05d3d8850febab0a6100bab8142a3913662a568f9d32367bf". + "5db46b6572cb76bd6a49d84bd567e1f834bbd705dd395c1609e9eba7fe8b9c59f1c4cb2561461204805c25a384140314e515f84050949529050279393884f8d0"); + + $game_key = "c6mw4it2kg7sz5o0813d9qyufenhj\x00"; + + $buffer_length = strlen($buffer); + $game_key_length = strlen($game_key); + + // We want to encrpt the data + if ($encrypt) + { + for ($i=1; $i<$buffer_length; $i++) + { + $buffer[$i] = chr(ord($buffer[$i]) ^ ord($buffer[$i-1])); + } + + for ($i=0; $i<$buffer_length; $i++) + { + $buffer[$i] = chr(ord($buffer[$i]) ^ ord($game_key[($i % 128) % $game_key_length]) ^ ord($master_key[$i % 128]) ^ ord($master_key[$i])); + } + } + else // We need to decrypt the data + { + for ($i=0; $i<$buffer_length; $i++) + { + $buffer[$i] = chr(ord($buffer[$i]) ^ ord($master_key[$i]) ^ ord($master_key[$i%128]) ^ ord($game_key[($i%128) % $game_key_length])); + } + + for ($i=($buffer_length-1); $i>0; $i--) + { + $buffer[$i] = chr(ord($buffer[$i]) ^ ord($buffer[$i-1])); + } + } + + return $buffer; + } +} diff --git a/web/third_party/gameq/gameq/protocols/alienswarm.php b/web/third_party/gameq/gameq/protocols/alienswarm.php new file mode 100644 index 00000000..6d814448 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/alienswarm.php @@ -0,0 +1,28 @@ +. + */ + +/** + * Alien Swarm Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Alienswarm extends GameQ_Protocols_Source +{ + protected $name = "alienswarm"; + protected $name_long = "Alien Swarm"; +} diff --git a/web/third_party/gameq/gameq/protocols/aoc.php b/web/third_party/gameq/gameq/protocols/aoc.php new file mode 100644 index 00000000..009dc669 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/aoc.php @@ -0,0 +1,28 @@ +. + */ + +/** + * Age of Chivalry Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Aoc extends GameQ_Protocols_Source +{ + protected $name = "aoc"; + protected $name_long = "Age of Chivalry"; +} diff --git a/web/third_party/gameq/gameq/protocols/armedassault.php b/web/third_party/gameq/gameq/protocols/armedassault.php new file mode 100644 index 00000000..48655f60 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/armedassault.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Armed Assault Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Armedassault extends GameQ_Protocols_Gamespy2 +{ + protected $name = "armedassault"; + protected $name_long = "Armed Assault"; + + protected $port = 2302; +} diff --git a/web/third_party/gameq/gameq/protocols/armedassault2.php b/web/third_party/gameq/gameq/protocols/armedassault2.php new file mode 100644 index 00000000..203ff1b6 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/armedassault2.php @@ -0,0 +1,44 @@ +. + */ + +/** + * Armed Assault 2 Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Armedassault2 extends GameQ_Protocols_Gamespy3 +{ + protected $name = "armedassault2"; + protected $name_long = "Armed Assault 2"; + + protected $port = 2302; + + protected function parsePlayerTeamInfoNew(GameQ_Buffer &$buf, GameQ_Result &$result) + { + // Read the buffer and replace the team_ sub-section under the players section becasue it is broke + $buf_fixed = preg_replace('/team_(.*)score_/m', 'score_', $buf->getBuffer()); + + // Replace the buffer with the "fixed" buffer + $buf = new GameQ_Buffer($buf_fixed); + + unset($buf_fixed); + + // Now we continue on with the parent + return parent::parsePlayerTeamInfo($buf, $result); + } +} diff --git a/web/third_party/gameq/gameq/protocols/armedassault2oa.php b/web/third_party/gameq/gameq/protocols/armedassault2oa.php new file mode 100644 index 00000000..afef445e --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/armedassault2oa.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Armed Assault 2: Operation Arrowhead Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Armedassault2oa extends GameQ_Protocols_Armedassault2 +{ + protected $name = "armedassault2oa"; + protected $name_long = "Armed Assault 2: Operation Arrowhead"; + + protected $port = 2302; +} diff --git a/web/third_party/gameq/gameq/protocols/armedassault3.php b/web/third_party/gameq/gameq/protocols/armedassault3.php new file mode 100644 index 00000000..660ab411 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/armedassault3.php @@ -0,0 +1,33 @@ +. + */ + +/** + * Armed Assault 2 Protocol Class + * + * Special thanks to firefly2442 for linking working python script that + * supported both GSv2&3 + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Armedassault3 extends GameQ_Protocols_Gamespy3 +{ + protected $name = "armedassault3"; + protected $name_long = "Armed Assault 3"; + + protected $port = 2302; +} diff --git a/web/third_party/gameq/gameq/protocols/ase.php b/web/third_party/gameq/gameq/protocols/ase.php new file mode 100644 index 00000000..5dedf830 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/ase.php @@ -0,0 +1,148 @@ +. + */ + +/** + * All-Seeing Eye Protocol Class + * + * This class is used as the basis for all game servers + * that use the All-Seeing Eye (ASE) protocol for querying + * server status. + * + * Most of the logic is taken from the original GameQ + * by Tom Buskens + * + * @author Marcel Bößendörfer + * @author Austin Bischoff + */ +abstract class GameQ_Protocols_ASE extends GameQ_Protocols +{ + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_ALL => "s", + ); + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_all", + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 1; // Default port, used if not set when instanced + + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'ase'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'ase'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "All-Seeing Eye"; + + /* + * Internal methods + */ + + protected function process_all() + { + if(!$this->hasValidResponse(self::PACKET_ALL)) + { + return array(); + } + $data = $this->packets_response[self::PACKET_ALL][0]; + $buf = new GameQ_Buffer($data); + $result = new GameQ_Result(); + if ($buf->read(4) !== 'EYE1') { + throw new GameQException($data); + } + // Variables + $result->add('gamename', $buf->readPascalString(1, true)); + $result->add('port', $buf->readPascalString(1, true)); + $result->add('servername', $buf->readPascalString(1, true)); + $result->add('gametype', $buf->readPascalString(1, true)); + $result->add('map', $buf->readPascalString(1, true)); + $result->add('version', $buf->readPascalString(1, true)); + $result->add('password', $buf->readPascalString(1, true)); + $result->add('num_players', $buf->readPascalString(1, true)); + $result->add('max_players', $buf->readPascalString(1, true)); + + // Key / value pairs + while ($buf->getLength()) { + // If we have an empty key, we've reached the end + $key = $buf->readPascalString(1, true); + if (empty($key)) break; + + // Otherwise, add the pair + $result->add( + $key, + $buf->readPascalString(1, true) + ); + } + + // Players + while ($buf->getLength()) { + // Get the flags + $flags = $buf->readInt8(); + + // Get data according to the flags + if ($flags & 1) { + $result->addPlayer('name', $buf->readPascalString(1, true)); + } + if ($flags & 2) { + $result->addPlayer('team', $buf->readPascalString(1, true)); + } + if ($flags & 4) { + $result->addPlayer('skin', $buf->readPascalString(1, true)); + } + if ($flags & 8) { + $result->addPlayer('score', $buf->readPascalString(1, true)); + } + if ($flags & 16) { + $result->addPlayer('ping', $buf->readPascalString(1, true)); + } + if ($flags & 32) { + $result->addPlayer('time', $buf->readPascalString(1, true)); + } + } + return $result->fetch(); + } +} diff --git a/web/third_party/gameq/gameq/protocols/avp.php b/web/third_party/gameq/gameq/protocols/avp.php new file mode 100644 index 00000000..5c0164f3 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/avp.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Aliens vs Preadtor Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Avp extends GameQ_Protocols_Gamespy +{ + protected $name = "avp"; + protected $name_long = "Aliens vs Preadtor"; + + protected $port = 27888; +} diff --git a/web/third_party/gameq/gameq/protocols/avp2.php b/web/third_party/gameq/gameq/protocols/avp2.php new file mode 100644 index 00000000..00204073 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/avp2.php @@ -0,0 +1,32 @@ +. + */ + +/** + * Aliens vs Predator 2 Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Avp2 extends GameQ_Protocols_Gamespy +{ + protected $name = "avp2"; + protected $name_long = "Aliens vs Predator 2"; + + protected $state = self::STATE_TESTING; + + protected $port = 27888; +} diff --git a/web/third_party/gameq/gameq/protocols/bf1942.php b/web/third_party/gameq/gameq/protocols/bf1942.php new file mode 100644 index 00000000..a9ce3ef3 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/bf1942.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Battlefield 1942 Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Bf1942 extends GameQ_Protocols_Gamespy +{ + protected $name = "bf1942"; + protected $name_long = "Battlefield 1942"; + + protected $port = 23000; +} diff --git a/web/third_party/gameq/gameq/protocols/bf2.php b/web/third_party/gameq/gameq/protocols/bf2.php new file mode 100644 index 00000000..42bcc724 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/bf2.php @@ -0,0 +1,56 @@ +. + */ + +/** + * Battlefield 2 Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Bf2 extends GameQ_Protocols_Gamespy3 +{ + protected $name = "bf2"; + protected $name_long = "Battlefield 2"; + + protected $port = 29900; + + /** + * Set the packet mode to multi, Gamespy v3 is by default a linear set of calls + * + * @var string + */ + protected $packet_mode = self::PACKET_MODE_MULTI; + + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_ALL => "\xFE\xFD\x00\x10\x20\x30\x40\xFF\xFF\xFF\x01", + ); + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_all", + ); +} diff --git a/web/third_party/gameq/gameq/protocols/bf2142.php b/web/third_party/gameq/gameq/protocols/bf2142.php new file mode 100644 index 00000000..05cd4030 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/bf2142.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Battlefield 2142 Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Bf2142 extends GameQ_Protocols_Gamespy3 +{ + protected $name = "bf2142"; + protected $name_long = "Battlefield 2142"; + + protected $port = 29900; +} diff --git a/web/third_party/gameq/gameq/protocols/bf3.php b/web/third_party/gameq/gameq/protocols/bf3.php new file mode 100644 index 00000000..503a4a5b --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/bf3.php @@ -0,0 +1,329 @@ +. + */ + +/** + * Battlefield 3 Protocol Class + * + * Good place for doc status and info is http://www.fpsadmin.com/forum/showthread.php?t=24134 + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Bf3 extends GameQ_Protocols +{ + /** + * Normalization for this protocol class + * + * @var array + */ + protected $normalize = array( + // General + 'general' => array( + 'dedicated' => array('dedicated'), + 'hostname' => array('hostname'), + 'password' => array('password'), + 'numplayers' => array('numplayers'), + 'maxplayers' => array('maxplayers'), + 'mapname' => array('map'), + 'gametype' => array('gametype'), + 'players' => array('players'), + 'teams' => array('team'), + ), + + // Player + 'player' => array( + 'score' => array('score'), + ), + + // Team + 'team' => array( + 'score' => array('tickets'), + ), + ); + + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_STATUS => "\x00\x00\x00\x00\x1b\x00\x00\x00\x01\x00\x00\x00\x0a\x00\x00\x00serverInfo\x00", + self::PACKET_VERSION => "\x00\x00\x00\x00\x18\x00\x00\x00\x01\x00\x00\x00\x07\x00\x00\x00version\x00", + self::PACKET_PLAYERS => "\x00\x00\x00\x00\x24\x00\x00\x00\x02\x00\x00\x00\x0b\x00\x00\x00listPlayers\x00\x03\x00\x00\x00\x61ll\x00", + ); + + /** + * Set the transport to use TCP + * + * @var string + */ + protected $transport = self::TRANSPORT_TCP; + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_status", + "process_version", + "process_players", + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 25200; // Default port, used if not set when instanced + + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'bf3'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'bf3'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Battlefield 3"; + + /* + * Internal methods + */ + protected function preProcess_status($packets=array()) + { + // Implode and return + return implode('', $packets); + } + + protected function process_status() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_STATUS)) + { + return array(); + } + + // Make buffer for data + $buf = new GameQ_Buffer($this->preProcess_status($this->packets_response[self::PACKET_STATUS])); + + $buf->skip(8); /* skip header */ + + // Decode the words into an array so we can use this data + $words = $this->decodeWords($buf); + + // Make sure we got OK + if (!isset ($words[0]) || $words[0] != 'OK') + { + throw new GameQ_ProtocolsException('Packet Response was not OK! Buffer:'.$buf->getBuffer()); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Server is always dedicated + $result->add('dedicated', TRUE); + + // No mods, as of yet + $result->add('mod', FALSE); + + // These are the same no matter what mode the server is in + $result->add('hostname', $words[1]); + $result->add('numplayers', $words[2]); + $result->add('maxplayers', $words[3]); + $result->add('gametype', $words[4]); + $result->add('map', $words[5]); + + $result->add('roundsplayed', $words[6]); + $result->add('roundstotal', $words[7]); + + // Figure out the number of teams + $num_teams = intval($words[8]); + + // Set the current index + $index_current = 9; + + // Loop for the number of teams found, increment along the way + for($id=1; $id<=$num_teams; $id++) + { + $result->addSub('teams', 'tickets', $words[$index_current]); + $result->addSub('teams', 'id', $id); + + // Increment + $index_current++; + } + + // Get and set the rest of the data points. + $result->add('targetscore', $words[$index_current]); + $result->add('online', TRUE); // Forced TRUE, it seems $words[$index_current + 1] is always empty + $result->add('ranked', $words[$index_current + 2] === 'true'); + $result->add('punkbuster', $words[$index_current + 3] === 'true'); + $result->add('password', $words[$index_current + 4] === 'true'); + $result->add('uptime', $words[$index_current + 5]); + $result->add('roundtime', $words[$index_current + 6]); + + // Added in R9 + $result->add('ip_port', $words[$index_current + 7]); + $result->add('punkbuster_version', $words[$index_current + 8]); + $result->add('join_queue', $words[$index_current + 9] === 'true'); + $result->add('region', $words[$index_current + 10]); + $result->add('pingsite', $words[$index_current + 11]); + $result->add('country', $words[$index_current + 12]); + + // Added in R29, No docs as of yet + $result->add('quickmatch', $words[$index_current + 13] === 'true'); // Guessed from research + + unset($buf, $words); + + return $result->fetch(); + } + + protected function preProcess_version($packets=array()) + { + // Implode and return + return implode('', $packets); + } + + protected function process_version() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_VERSION)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Make buffer for data + $buf = new GameQ_Buffer($this->preProcess_version($this->packets_response[self::PACKET_VERSION])); + + $buf->skip(8); /* skip header */ + + $words = $this->decodeWords($buf); + + // Not too important if version is missing + if (!isset ($words[0]) || $words[0] != 'OK') + { + return array(); + } + + $result->add('version', $words[2]); + + unset($buf, $words); + + return $result->fetch(); + } + + protected function preProcess_players($packets=array()) + { + // Implode and return + return implode('', $packets); + } + + protected function process_players() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_PLAYERS)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Make buffer for data + $buf = new GameQ_Buffer($this->preProcess_players($this->packets_response[self::PACKET_PLAYERS])); + + $buf->skip(8); /* skip header */ + + $words = $this->decodeWords($buf); + + // Not too important if players are missing. + if (!isset ($words[0]) || $words[0] != 'OK') + { + return array(); + } + + // Count the number of words and figure out the highest index. + $words_total = count($words)-1; + + // The number of player info points + $num_tags = $words[1]; + + // Pull out the tags, they start at index=3, length of num_tags + $tags = array_slice($words, 2, $num_tags); + + // Just incase this changed between calls. + $result->add('numplayers', $words[9]); + + // Loop until we run out of positions + for($pos=(3+$num_tags);$pos<=$words_total;$pos+=$num_tags) + { + // Pull out this player + $player = array_slice($words, $pos, $num_tags); + + // Loop the tags and add the proper value for the tag. + foreach($tags AS $tag_index => $tag) + { + $result->addPlayer($tag, $player[$tag_index]); + } + + // No pings in this game + $result->addPlayer('ping', FALSE); + } + + // @todo: Add some team definition stuff + + unset($buf, $tags, $words, $player); + + return $result->fetch(); + } + + /** + * Decode words from the response + * + * @param GameQ_Buffer $buf + */ + protected function decodeWords(GameQ_Buffer &$buf) + { + $result = array(); + + $num_words = $buf->readInt32(); + + for ($i = 0; $i < $num_words; $i++) + { + $len = $buf->readInt32(); + $result[] = $buf->read($len); + $buf->read(1); /* 0x00 string ending */ + } + + return $result; + } +} diff --git a/web/third_party/gameq/gameq/protocols/bfbc2.php b/web/third_party/gameq/gameq/protocols/bfbc2.php new file mode 100644 index 00000000..54bc9c69 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/bfbc2.php @@ -0,0 +1,249 @@ +. + */ + +/** + * Battlefield Bad Company 2 Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Bfbc2 extends GameQ_Protocols +{ + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_STATUS => "\x00\x00\x00\x00\x1b\x00\x00\x00\x01\x00\x00\x00\x0a\x00\x00\x00serverInfo\x00", + self::PACKET_VERSION => "\x00\x00\x00\x00\x18\x00\x00\x00\x01\x00\x00\x00\x07\x00\x00\x00version\x00", + self::PACKET_PLAYERS => "\x00\x00\x00\x00\x24\x00\x00\x00\x02\x00\x00\x00\x0b\x00\x00\x00listPlayers\x00\x03\x00\x00\x00\x61ll\x00", + ); + + /** + * Set the transport to use TCP + * + * @var string + */ + protected $transport = self::TRANSPORT_TCP; + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_status", + "process_version", + "process_players", + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 48888; // Default port, used if not set when instanced + + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'bfbc2'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'bfbc2'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Battlefield Bad Company 2"; + + + /* + * Internal methods + */ + + protected function preProcess_status($packets=array()) + { + // Implode and return + return implode('', $packets); + } + + protected function process_status() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_STATUS)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Make buffer for data + $buf = new GameQ_Buffer($this->preProcess_status($this->packets_response[self::PACKET_STATUS])); + + $buf->skip(8); /* skip header */ + + $words = $this->decodeWords($buf); + + if (!isset ($words[0]) || $words[0] != 'OK') + { + throw new GameQ_ProtocolsException('Packet Response was not OK! Buffer:'.$buf->getBuffer()); + } + + $result->add('hostname', $words[1]); + $result->add('numplayers', $words[2]); + $result->add('maxplayers', $words[3]); + $result->add('gametype', $words[4]); + $result->add('map', $words[5]); + + // @todo: Add some team definition stuff + + unset($buf); + + return $result->fetch(); + } + + protected function preProcess_version($packets=array()) + { + // Implode and return + return implode('', $packets); + } + + protected function process_version() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_VERSION)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Make buffer for data + $buf = new GameQ_Buffer($this->preProcess_version($this->packets_response[self::PACKET_VERSION])); + + $buf->skip(8); /* skip header */ + + $words = $this->decodeWords($buf); + + // Not too important if version is missing + if (!isset ($words[0]) || $words[0] != 'OK') + { + return array(); + } + + $result->add('version', $words[2]); + + unset($buf); + + return $result->fetch(); + } + + protected function preProcess_players($packets=array()) + { + // Implode and return + return implode('', $packets); + } + + protected function process_players() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_PLAYERS)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Make buffer for data + $buf = new GameQ_Buffer($this->preProcess_players($this->packets_response[self::PACKET_PLAYERS])); + + $buf->skip(8); /* skip header */ + + $words = $this->decodeWords($buf); + + // Not too important if players are missing. + if (!isset ($words[0]) || $words[0] != 'OK') + { + return array(); + } + + // The number of player info points + $num_tags = $words[1]; + $position = 2; + $tags = array(); + + for (; $position < $num_tags + 2 ; $position++) + { + $tags[] = $words[$position]; + } + + $num_players = $words[$position]; + $position++; + $start_position = $position; + + for (; $position < $num_players * $num_tags + $start_position; + $position += $num_tags) + { + for ($a = $position, $b = 0; $a < $position + $num_tags; + $a++, $b++) + { + $result->addPlayer($tags[$b], $words[$a]); + } + } + + // @todo: Add some team definition stuff + + unset($buf); + + return $result->fetch(); + } + + /** + * Decode words from the response + * + * @param GameQ_Buffer $buf + */ + protected function decodeWords(GameQ_Buffer &$buf) + { + $result = array(); + + $num_words = $buf->readInt32(); + + for ($i = 0; $i < $num_words; $i++) + { + $len = $buf->readInt32(); + $result[] = $buf->read($len); + $buf->read(1); /* 0x00 string ending */ + } + + return $result; + } +} diff --git a/web/third_party/gameq/gameq/protocols/bfv.php b/web/third_party/gameq/gameq/protocols/bfv.php new file mode 100644 index 00000000..73df1007 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/bfv.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Battlefield Vietnam Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Bfv extends GameQ_Protocols_Gamespy2 +{ + protected $name = "bfv"; + protected $name_long = "Battlefield Vietnam"; + + protected $port = 23000; +} diff --git a/web/third_party/gameq/gameq/protocols/brink.php b/web/third_party/gameq/gameq/protocols/brink.php new file mode 100644 index 00000000..ab5c038f --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/brink.php @@ -0,0 +1,28 @@ +. + */ + +/** + * Brink Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Brink extends GameQ_Protocols_Source +{ + protected $name = "brink"; + protected $name_long = "Brink"; +} diff --git a/web/third_party/gameq/gameq/protocols/cod.php b/web/third_party/gameq/gameq/protocols/cod.php new file mode 100644 index 00000000..6ad6f1c3 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/cod.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Call of Duty Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Cod extends GameQ_Protocols_Quake3 +{ + protected $name = "cod"; + protected $name_long = "Call of Duty"; + + protected $port = 28960; +} diff --git a/web/third_party/gameq/gameq/protocols/cod2.php b/web/third_party/gameq/gameq/protocols/cod2.php new file mode 100644 index 00000000..144b4675 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/cod2.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Call of Duty 2 Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Cod2 extends GameQ_Protocols_Quake3 +{ + protected $name = "cod2"; + protected $name_long = "Call of Duty 2"; + + protected $port = 28960; +} diff --git a/web/third_party/gameq/gameq/protocols/cod4.php b/web/third_party/gameq/gameq/protocols/cod4.php new file mode 100644 index 00000000..5cd49752 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/cod4.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Call of Duty 4 Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Cod4 extends GameQ_Protocols_Quake3 +{ + protected $name = "cod4"; + protected $name_long = "Call of Duty 4"; + + protected $port = 28960; +} diff --git a/web/third_party/gameq/gameq/protocols/codmw3.php b/web/third_party/gameq/gameq/protocols/codmw3.php new file mode 100644 index 00000000..2fcecedb --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/codmw3.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Call of Duty: Modern Warfare 3 Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Codmw3 extends GameQ_Protocols_Source +{ + protected $name = "codmw3"; + protected $name_long = "Call of Duty: Modern Warfare 3"; + + protected $port = 27015; +} diff --git a/web/third_party/gameq/gameq/protocols/coduo.php b/web/third_party/gameq/gameq/protocols/coduo.php new file mode 100644 index 00000000..2d180461 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/coduo.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Call of Duty: United Offensive Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Coduo extends GameQ_Protocols_Quake3 +{ + protected $name = "coduo"; + protected $name_long = "Call of Duty: United Offensive"; + + protected $port = 28960; +} diff --git a/web/third_party/gameq/gameq/protocols/codwaw.php b/web/third_party/gameq/gameq/protocols/codwaw.php new file mode 100644 index 00000000..fcc1a819 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/codwaw.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Call of Duty: World at War Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Codwaw extends GameQ_Protocols_Quake3 +{ + protected $name = "codwaw"; + protected $name_long = "Call of Duty: World at War"; + + protected $port = 28960; +} diff --git a/web/third_party/gameq/gameq/protocols/core.php b/web/third_party/gameq/gameq/protocols/core.php new file mode 100644 index 00000000..27605669 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/core.php @@ -0,0 +1,637 @@ +. + * + * + */ + +/** + * Handles the core functionality for the protocols + * + * @author Austin Bischoff + */ +abstract class GameQ_Protocols_Core +{ + /* + * Constants for class states + */ + const STATE_TESTING = 1; + const STATE_BETA = 2; + const STATE_STABLE = 3; + const STATE_DEPRECATED = 4; + + /* + * Constants for packet keys + */ + const PACKET_ALL = 'all'; // Some protocols allow all data to be sent back in one call. + const PACKET_BASIC = 'basic'; + const PACKET_CHALLENGE = 'challenge'; + const PACKET_CHANNELS = 'channels'; // Voice servers + const PACKET_DETAILS = 'details'; + const PACKET_INFO = 'info'; + const PACKET_PLAYERS = 'players'; + const PACKET_STATUS = 'status'; + const PACKET_RULES = 'rules'; + const PACKET_VERSION = 'version'; + + /* + * Transport constants + */ + const TRANSPORT_UDP = 'udp'; + const TRANSPORT_TCP = 'tcp'; + + /** + * Can only send one packet at a time, slower + * + * @var string + */ + const PACKET_MODE_LINEAR = 'linear'; + + /** + * Can send multiple packets at once and get responses, after challenge request (if required) + * + * @var string + */ + const PACKET_MODE_MULTI = 'multi'; + + /** + * Current version of this class + * + * @var string + */ + protected $version = '2.0'; + + /** + * Short name of the protocol + * + * @var string + */ + protected $name = 'unnamed'; + + /** + * The longer, fancier name for the protocol + * + * @var string + */ + protected $name_long = 'unnamed'; + + /** + * IP address of the server we are querying. + * + * @var string + */ + protected $ip = '127.0.0.1'; + + /** + * Port of the server we are querying. + * + * @var mixed FALSE|int + */ + protected $port = NULL; + + /** + * The trasport method to use to actually send the data + * Default is UDP + * + * @var string UDP|TCP + */ + protected $transport = self::TRANSPORT_UDP; + + /** + * The protocol type used when querying the server + * + * @var string + */ + protected $protocol = 'unknown'; + + /** + * Packets Mode is multi by default since most games support it + * + * @var string + */ + protected $packet_mode = self::PACKET_MODE_MULTI; + + /** + * Holds the valid packet types this protocol has available. + * + * @var array + */ + protected $packets = array(); + + /** + * Holds the list of methods to run when parsing the packet response(s) data. These + * methods should provide all the return information. + * + * @var array() + */ + protected $process_methods = array(); + + /** + * The packet responses received + * + * @var array + */ + protected $packets_response = array(); + + /** + * Holds the instance of the result class + * + * @var GameQ_Result + */ + protected $result = NULL; + + /** + * Options for this protocol + * + * @var array + */ + protected $options = array(); + + /** + * Holds the challenge response, if there is a challenge needed. + * + * @var array + */ + protected $challenge_response = NULL; + + /** + * Holds the challenge buffer. + * + * @var GameQ_Buffer + */ + protected $challenge_buffer = NULL; + + /** + * Holds the result of the challenge, if any + * Will hold the error here + * + * @var mixed + */ + protected $challenge_result = TRUE; + + /** + * Define the state of this class + * + * @var int + */ + protected $state = self::STATE_STABLE; + + /** + * Holds and changes we want to make to the normailze filter + * + * @var array + */ + protected $normalize = FALSE; + + /** + * Create the instance. + * + * @param string $ip + * @param mixed $port false|int + * @param array $options + */ + public function __construct($ip = FALSE, $port = FALSE, $options = array()) + { + $this->ip($ip); + + // We have a specific port set so let's set it. + if($port !== FALSE) + { + $this->port($port); + } + + // We have passed options so let's set them + if(!empty($options)) + { + $this->options($options); + } + } + + /** + * String name of this class + */ + public function __toString() + { + return $this->name; + } + + /** + * Get an option's value + * + * @param string $option + * @return mixed + */ + public function __get($option) + { + return isset($this->options[$option]) ? $this->options[$option] : NULL; + } + + /** + * Set an option's value + * + * @param string $option + * @param mixed $value + * @return boolean + */ + public function __set($option, $value) + { + $this->options[$option] = $value; + + return TRUE; + } + + /** + * Short (callable) name of this class + * + * @return string + */ + public function name() + { + return $this->name; + } + + /** + * Long name of this class + */ + public function name_long() + { + return $this->name_long; + } + + /** + * Return the status of this Protocol Class + */ + public function state() + { + return $this->state; + } + + /** + * Return the packet mode for this protocol + */ + public function packet_mode() + { + return $this->packet_mode; + } + + /** + * Return the protocol property + * + */ + public function protocol() + { + return $this->protocol; + } + + /** + * Get/set the ip address of the server + * + * @param string $ip + */ + public function ip($ip = FALSE) + { + // Act as setter + if($ip !== FALSE) + { + $this->ip = $ip; + } + + return $this->ip; + } + + /** + * Get/set the port of the server + * + * @param int $port + */ + public function port($port = FALSE) + { + // Act as setter + if($port !== FALSE) + { + $this->port = $port; + } + + return $this->port; + } + + /** + * Get/set the transport type for this protocol + * + * @param string $type + */ + public function transport($type = FALSE) + { + // Act as setter + if($type !== FALSE) + { + $this->transport = $type; + } + + return $this->transport; + } + + /** + * Set the options for the protocol call + * + * @param array $options + */ + public function options($options = Array()) + { + // Act as setter + if(!empty($options)) + { + $this->options = $options; + } + + return $this->options; + } + + /** + * Determine whether or not this protocol has some kind of challenge + */ + public function hasChallenge() + { + return (isset($this->packets[self::PACKET_CHALLENGE]) && !empty($this->packets[self::PACKET_CHALLENGE])); + } + + /** + * See if the challenge was ok + */ + public function challengeOK() + { + return ($this->challenge_result === TRUE); + } + + /** + * Get/set the challenge response + * + * @param array $response + */ + public function challengeResponse($response = Array()) + { + // Act as setter + if(!empty($response)) + { + $this->challenge_response = $response; + } + + return $this->challenge_response; + } + + /** + * Get/set the challenge result + * + * @param string $result + */ + public function challengeResult($result = FALSE) + { + // Act as setter + if(!empty($result)) + { + $this->challenge_result = $result; + } + + return $this->challenge_result; + } + + /** + * Get/set the challenge buffer + * + * @param GameQ_Buffer $buffer + */ + public function challengeBuffer($buffer = NULL) + { + // Act as setter + if(!empty($buffer)) + { + $this->challenge_buffer = $buffer; + } + + return $this->challenge_buffer; + } + + /** + * Verify the challenge response and parse it + */ + public function challengeVerifyAndParse() + { + // Check to make sure the response exists + if(!isset($this->challenge_response[0])) + { + // Set error and skip + $this->challenge_result = 'Challenge Response Empty'; + return FALSE; + } + + // Challenge is good to go + $this->challenge_result = TRUE; + + // Now let's create a new buffer with this response + $this->challenge_buffer = new GameQ_Buffer($this->challenge_response[0]); + + // Now parse the challenge and apply it + return $this->parseChallengeAndApply(); + } + + /** + * Get/set the packet response + * + * @param string $packet_type + * @param array $response + */ + public function packetResponse($packet_type, $response = Array()) + { + // Act as setter + if(!empty($response)) + { + $this->packets_response[$packet_type] = $response; + } + + return $this->packets_response[$packet_type]; + } + + /** + * Return specific packet(s) + * + * @param mixed $type array|string + */ + public function getPacket($type = array()) + { + // We want an array of packets back + if(is_array($type) && !empty($type)) + { + $packets = array(); + + // Loop the packets + foreach($this->packets AS $packet_type => $packet_data) + { + // We want this packet + if(in_array($packet_type, $type)) + { + $packets[$packet_type] = $packet_data; + } + } + + return $packets; + } + elseif($type == '!challenge') + { + $packets = array(); + + // Loop the packets + foreach($this->packets AS $packet_type => $packet_data) + { + // Dont want challenge packets + if($packet_type == self::PACKET_CHALLENGE) + { + continue; + } + + $packets[$packet_type] = $packet_data; + } + + return $packets; + } + elseif(is_string($type)) + { + return $this->packets[$type]; + } + + // Return all the packets + return $this->packets; + } + + /* Begin working methods */ + + /** + * Process the response and return the raw data as an array. + * + * @throws GameQException + */ + public function processResponse() + { + // Init the array + $results = array(); + + // Let's loop all the requred methods to get all the data we want/need. + foreach ($this->process_methods AS $method) + { + // Lets make sure the data method defined exists. + if(!method_exists($this, $method)) + { + // We should never get here in a production environment + throw new GameQException('Unable to load method '.__CLASS__.'::'.$method); + return FALSE; + } + + // Setup a catch for protocol level errors + try + { + // Call the proper process method. All methods should return an array of data. + // Preprocessing should be handled by these methods internally as well. + // Merge in the results when done. + $results = array_merge($results, call_user_func_array(array($this, $method), array())); + + } + catch (GameQ_ProtocolsException $e) + { + // Check to see if we are in debug, if so bubble up the exception + if($this->debug) + { + throw new GameQException($e->getMessage(), $e->getCode(), $e); + return FALSE; + } + + // We ignore this and continue + continue; + } + + } + + // Now add some default stuff + $results['gq_online'] = (count($results) > 0); + $results['gq_address'] = $this->ip; + $results['gq_port'] = $this->port; + $results['gq_protocol'] = $this->protocol; + $results['gq_type'] = (string) $this; + $results['gq_transport'] = $this->transport; + + // Return the raw results + return $results; + } + + /** + * This method is called before the actual query packets are sent to the server. This allows + * the class to modify any changes before being sent. + * + * @return boolean + */ + public function beforeSend() + { + return TRUE; + } + + /** + * Get the normalize property + */ + public function getNormalize() + { + return $this->normalize; + } + + /** + * Apply the challenge string to all the packets that need it. + * + * @param string $challenge_string + */ + protected function challengeApply($challenge_string) + { + // Let's loop thru all the packets and append the challenge where it is needed + foreach($this->packets AS $packet_type => $packet) + { + $this->packets[$packet_type] = sprintf($packet, $challenge_string); + } + + return TRUE; + } + + /** + * Parse the challenge buffer and get the proper challenge string out + */ + protected function parseChallengeAndApply() + { + return TRUE; + } + + /** + * Determine whether or not the response is valid for a specific packet type + * + * @param string $packet_type + */ + protected function hasValidResponse($packet_type) + { + // Check for valid packet. All packet responses should have atleast 1 array key (0). + if(isset($this->packets_response[$packet_type][0]) + && !empty($this->packets_response[$packet_type][0]) + ) + { + return TRUE; + } + + return FALSE; + } +} diff --git a/web/third_party/gameq/gameq/protocols/crysis.php b/web/third_party/gameq/gameq/protocols/crysis.php new file mode 100644 index 00000000..dcbe6094 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/crysis.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Crysis Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Crysis extends GameQ_Protocols_Gamespy3 +{ + protected $name = "crysis"; + protected $name_long = "Crysis"; + + protected $port = 64087; +} diff --git a/web/third_party/gameq/gameq/protocols/crysis2.php b/web/third_party/gameq/gameq/protocols/crysis2.php new file mode 100644 index 00000000..9aa06162 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/crysis2.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Crysis 2 Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Crysis2 extends GameQ_Protocols_Gamespy3 +{ + protected $name = "crysis2"; + protected $name_long = "Crysis 2"; + + protected $port = 64000; +} diff --git a/web/third_party/gameq/gameq/protocols/crysiswarhead.php b/web/third_party/gameq/gameq/protocols/crysiswarhead.php new file mode 100644 index 00000000..c3b42299 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/crysiswarhead.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Crysis Warhead Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Crysiswarhead extends GameQ_Protocols_Gamespy3 +{ + protected $name = "crysiswarhead"; + protected $name_long = "Crysis Warhead"; + + protected $port = 64100; +} diff --git a/web/third_party/gameq/gameq/protocols/crysiswars.php b/web/third_party/gameq/gameq/protocols/crysiswars.php new file mode 100644 index 00000000..871dab4a --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/crysiswars.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Crysis Wars Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Crysiswars extends GameQ_Protocols_Gamespy3 +{ + protected $name = "crysiswars"; + protected $name_long = "Crysis Wars"; + + protected $port = 64100; +} diff --git a/web/third_party/gameq/gameq/protocols/cs16.php b/web/third_party/gameq/gameq/protocols/cs16.php new file mode 100644 index 00000000..dc4f820b --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/cs16.php @@ -0,0 +1,49 @@ +. + */ + +/** + * Counter-Strike 1.6 Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Cs16 extends GameQ_Protocols_Source +{ + protected $name = "cs16"; + protected $name_long = "Counter-Strike 1.6"; + + /** + * We have to overload this function to cheat the rules processing because of some wierdness, old ass game! + * + * @see GameQ_Protocols_Source::preProcess_rules() + */ + protected function preProcess_rules($packets) + { + $engine_orig = $this->source_engine; + + // Override the engine type for rules, not sure why its like that + $this->source_engine = self::GOLDSOURCE_ENGINE; + + // Now process the rules + $ret = parent::preProcess_rules($packets); + + // Reset the engine type + $this->source_engine = $engine_orig; + + return $ret; + } +} diff --git a/web/third_party/gameq/gameq/protocols/cscz.php b/web/third_party/gameq/gameq/protocols/cscz.php new file mode 100644 index 00000000..822e6fe3 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/cscz.php @@ -0,0 +1,49 @@ +. + */ + +/** + * Counter-Strike: Condition Zero Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Cscz extends GameQ_Protocols_Source +{ + protected $name = "cscz"; + protected $name_long = "Counter-Strike: Condition Zero"; + + /** + * We have to overload this function to cheat the rules processing because of some wierdness, old ass game! + * + * @see GameQ_Protocols_Source::preProcess_rules() + */ + protected function preProcess_rules($packets) + { + $engine_orig = $this->source_engine; + + // Override the engine type for rules, not sure why its like that + $this->source_engine = self::GOLDSOURCE_ENGINE; + + // Now process the rules + $ret = parent::preProcess_rules($packets); + + // Reset the engine type + $this->source_engine = $engine_orig; + + return $ret; + } +} diff --git a/web/third_party/gameq/gameq/protocols/csgo.php b/web/third_party/gameq/gameq/protocols/csgo.php new file mode 100644 index 00000000..8360f28a --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/csgo.php @@ -0,0 +1,28 @@ +. + */ + +/** + * Counter-Strike: Global Offensive Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Csgo extends GameQ_Protocols_Source +{ + protected $name = "csgo"; + protected $name_long = "Counter-Strike: Global Offensive"; +} diff --git a/web/third_party/gameq/gameq/protocols/css.php b/web/third_party/gameq/gameq/protocols/css.php new file mode 100644 index 00000000..b1d70e89 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/css.php @@ -0,0 +1,28 @@ +. + */ + +/** + * Counter-Strike: Source Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Css extends GameQ_Protocols_Source +{ + protected $name = "css"; + protected $name_long = "Counter-Strike: Source"; +} diff --git a/web/third_party/gameq/gameq/protocols/cube2.php b/web/third_party/gameq/gameq/protocols/cube2.php new file mode 100644 index 00000000..afbaa71e --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/cube2.php @@ -0,0 +1,175 @@ +. + */ + +/** + * Cube 2: Sauerbraten Protocol Class + * + * References: + * https://qstat.svn.sourceforge.net/svnroot/qstat/trunk/qstat2/cube2.c + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Cube2 extends GameQ_Protocols +{ + protected $state = self::STATE_BETA; + + protected $normalize = array( + // General + 'general' => array( + 'hostname' => array('servername'), + 'numplayers' => array('num_players'), + 'maxplayers' => array('max_players'), + 'mapname' => array('map'), + 'gametype' => array('gametype'), + ), + ); + + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_STATUS => "server", + ); + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_status", + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 28802; // Default port, used if not set when instanced + + /** + * The query protocol used to make the call + * + * @var string + */ + protected $protocol = 'cube2'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'cube2'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Cube 2: Sauerbraten"; + + /** + * Pre-process the server status data that was returned. + * + * @param array $packets + */ + protected function preProcess_status($packets) + { + // Process the packets + return implode('', $packets); + } + + /** + * Handles processing the status data into a usable format + * + * @throws GameQ_ProtocolsException + */ + protected function process_status() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_STATUS)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Let's preprocess the rules + $data = $this->preProcess_status($this->packets_response[self::PACKET_STATUS]); + + // Create a new buffer + $buf = new GameQ_Buffer($data); + + // Check the header, should be the same response as the packet we sent + if($buf->read(6) != $this->packets[self::PACKET_STATUS]) + { + throw new GameQ_ProtocolsException("Data for ".__METHOD__." does not have the proper header type (should be {$this->packets[self::PACKET_STATUS]})."); + return array(); + } + + // NOTE: the following items were figured out using some source and trial and error + + $result->add('num_players', $this->readInt($buf)); + $result->add('version', $this->readInt($buf)); + $result->add('protocol', $this->readInt($buf)); + $result->add('mode', $this->readInt($buf)); + $result->add('time_remaining', $this->readInt($buf)); + $result->add('max_players', $this->readInt($buf)); + $result->add('mastermode', $this->readInt($buf)); + + // @todo: Sometimes there is an extra char here before the map string. Not sure what causes it or how + // to even check for its existance. + + $result->add('map', $buf->readString()); + $result->add('servername', $buf->readString()); + + unset($buf, $data); + + return $result->fetch(); + } + + /** + * Function to check for varying int values in the responses. Makes life a little easier + * + * @param GameQ_Buffer $buf + * @return number + */ + protected function readInt(GameQ_Buffer &$buf) + { + // Look ahead and see if 32-bit int + if($buf->lookAhead(1) == "\x81") + { + $buf->skip(1); + return $buf->readInt32(); + } + // Look ahead and see if 16-bit int + elseif($buf->lookAhead(1) == "\x80") + { + $buf->skip(1); + return $buf->readInt16(); + } + else // 8-bit + { + return $buf->readInt8(); + } + } +} diff --git a/web/third_party/gameq/gameq/protocols/dayz.php b/web/third_party/gameq/gameq/protocols/dayz.php new file mode 100644 index 00000000..883b749a --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/dayz.php @@ -0,0 +1,30 @@ +. + */ + +/** + * DayZ Mod Protocol Class + * + * @author Marcel Bößendörfer + */ +class GameQ_Protocols_Dayz extends GameQ_Protocols_Armedassault2 +{ + protected $name = "dayz"; + protected $name_long = "DayZ Mod"; + + protected $port = 2302; +} diff --git a/web/third_party/gameq/gameq/protocols/dod.php b/web/third_party/gameq/gameq/protocols/dod.php new file mode 100644 index 00000000..844bedaf --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/dod.php @@ -0,0 +1,28 @@ +. + */ + +/** + * Day of Defeat Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Dod extends GameQ_Protocols_Source +{ + protected $name = "dod"; + protected $name_long = "Day of Defeat"; +} diff --git a/web/third_party/gameq/gameq/protocols/dods.php b/web/third_party/gameq/gameq/protocols/dods.php new file mode 100644 index 00000000..0aeff64b --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/dods.php @@ -0,0 +1,28 @@ +. + */ + +/** + * Day of Defeat: Source Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Dods extends GameQ_Protocols_Source +{ + protected $name = "dods"; + protected $name_long = "Day of Defeat: Source"; +} diff --git a/web/third_party/gameq/gameq/protocols/doom3.php b/web/third_party/gameq/gameq/protocols/doom3.php new file mode 100644 index 00000000..3fdeec01 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/doom3.php @@ -0,0 +1,163 @@ +. + */ + +/** + * Doom3 Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Doom3 extends GameQ_Protocols +{ + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_ALL => "\xFF\xFFgetInfo\x00PiNGPoNG\x00", + ); + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_all", + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 27666; // Default port, used if not set when instanced + + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'doom3'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'doom3'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Doom 3"; + + + /* + * Internal methods + */ + + protected function preProcess_all($packets=array()) + { + // Implode and return + return implode('', $packets); + } + + protected function process_all() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_ALL)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Parse the response + $data = $this->preProcess_all($this->packets_response[self::PACKET_ALL]); + + // Create a new buffer + $buf = new GameQ_Buffer($data); + + // Header + if ($buf->readInt16() !== 65535 or $buf->readString() !== 'infoResponse') + { + throw new GameQ_ProtocolsException('Header for response does not match. Buffer:'.$this->packets_response[self::PACKET_ALL]); + return array(); + } + + $result->add('version', $buf->readInt8() . '.' . $buf->readInt8()); + + // Var / value pairs, delimited by an empty pair + while ($buf->getLength()) + { + $var = $buf->readString(); + $val = $buf->readString(); + + // Something is empty so we are done + if (empty($var) && empty($val)) + { + break; + } + + $result->add($var, $val); + } + + // Now lets parse the players + $this->parsePlayers($buf, $result); + + unset($buf, $data); + + // Return the result + return $result->fetch(); + } + + /** + * Parse the players. Set as its own method so it can be overloaded. + * + * @param GameQ_Buffer $buf + * @param GameQ_Result $result + */ + protected function parsePlayers(GameQ_Buffer &$buf, GameQ_Result &$result) + { + // There is no way to see the number of players so we have to increment + // a variable and do it that way. + $players = 0; + + + // Loop thru the buffer until we run out of data + while (($id = $buf->readInt8()) != 32) + { + $result->addPlayer('id', $id); + $result->addPlayer('ping', $buf->readInt16()); + $result->addPlayer('rate', $buf->readInt32()); + $result->addPlayer('name', $buf->readString()); + + $players++; + } + + // Add the number of players to the result + $result->add('numplayers', $players); + + return TRUE; + } +} diff --git a/web/third_party/gameq/gameq/protocols/et.php b/web/third_party/gameq/gameq/protocols/et.php new file mode 100644 index 00000000..9ffd29c0 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/et.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Wolfenstein Enemy Territory Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Et extends GameQ_Protocols_Quake3 +{ + protected $name = "et"; + protected $name_long = "Wolfenstein Enemy Territory"; + + protected $port = 27960; +} diff --git a/web/third_party/gameq/gameq/protocols/etqw.php b/web/third_party/gameq/gameq/protocols/etqw.php new file mode 100644 index 00000000..25bc34e6 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/etqw.php @@ -0,0 +1,225 @@ +. + */ + +/** + * Enemy Territory: Quake Wars Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Etqw extends GameQ_Protocols +{ + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_STATUS => "\xFF\xFFgetInfoEx\x00\x00\x00\x00", + //self::PACKET_STATUS => "\xFF\xFFgetInfo\x00\x00\x00\x00\x00", + ); + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_status", + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 27733; // Default port, used if not set when instanced + + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'etqw'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'etqw'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Enemy Territory: Quake Wars"; + + /* + * Internal methods + */ + + protected function preProcess_status($packets) + { + // Should only be one packet + if (count($packets) > 1) + { + throw new GameQ_ProtocolsException('Enemy Territor: Quake Wars status has more than 1 packet'); + } + + // Make buffer so we can check this out + $buf = new GameQ_Buffer($packets[0]); + + // Grab the header + $header = $buf->readString(); + + // Now lets verify the header + if(!strstr($header, 'infoExResponse')) + { + throw new GameQ_ProtocolsException('Unable to match Enemy Territor: Quake Wars response header. Header: '. $header); + return FALSE; + } + + // Return the data with the header stripped, ready to go. + return $buf->getBuffer(); + } + + /** + * Process the server status + * + * @throws GameQ_ProtocolsException + */ + protected function process_status() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_STATUS)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Lets pre process and make sure these things are in the proper order by id + $data = $this->preProcess_status($this->packets_response[self::PACKET_STATUS]); + + // Make buffer + $buf = new GameQ_Buffer($data); + + // Now burn the challenge, version and size + $buf->skip(16); + + // Key / value pairs + while ($buf->getLength()) + { + $var = str_replace('si_', '', $buf->readString()); + $val = $buf->readString(); + + if (empty($var) && empty($val)) + { + break; + } + + // Add the server prop + $result->add($var, $val); + } + + // Now let's do the basic player info + $this->parsePlayers($buf, $result); + + // Now grab the rest of the server info + $result->add('osmask', $buf->readInt32()); + $result->add('ranked', $buf->readInt8()); + $result->add('timeleft', $buf->readInt32()); + $result->add('gamestate', $buf->readInt8()); + $result->add('servertype', $buf->readInt8()); + + // 0: regular server + if ($result->get('servertype') == 0) + { + $result->add('interested_clients', $buf->readInt8()); + } + // 1: tv server + else + { + $result->add('connected_clients', $buf->readInt32()); + $result->add('max_clients', $buf->readInt32()); + } + + // Now let's parse the extended player info + $this->parsePlayersExtra($buf, $result); + + // Free some memory + unset($sections, $buf, $data); + + // Return the result + return $result->fetch(); + } + + /** + * Parse the players and add them to the return. + * + * @param GameQ_Buffer $buf + * @param GameQ_Result $result + */ + protected function parsePlayers(GameQ_Buffer &$buf, GameQ_Result &$result) + { + $players = 0; + + while (($id = $buf->readInt8()) != 32) + { + $result->addPlayer('id', $id); + $result->addPlayer('ping', $buf->readInt16()); + $result->addPlayer('name', $buf->readString()); + $result->addPlayer('clantag_pos', $buf->readInt8()); + $result->addPlayer('clantag', $buf->readString()); + $result->addPlayer('bot', $buf->readInt8()); + + $players++; + } + + // Let's add in the current players as a result + $result->add('numplayers', $players); + + // Free some memory + unset($id); + } + + /** + * Parse the players extra info and add them to the return. + * + * @param GameQ_Buffer $buf + * @param GameQ_Result $result + */ + protected function parsePlayersExtra(GameQ_Buffer &$buf, GameQ_Result &$result) + { + while (($id = $buf->readInt8()) != 32) + { + $result->addPlayer('total_xp', $buf->readFloat32()); + $result->addPlayer('teamname', $buf->readString()); + $result->addPlayer('total_kills', $buf->readInt32()); + $result->addPlayer('total_deaths', $buf->readInt32()); + } + + // @todo: Add team stuff + + // Free some memory + unset($id); + } +} diff --git a/web/third_party/gameq/gameq/protocols/fear.php b/web/third_party/gameq/gameq/protocols/fear.php new file mode 100644 index 00000000..74482392 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/fear.php @@ -0,0 +1,30 @@ +. + */ + +/** + * F.E.A.R. Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Fear extends GameQ_Protocols_Gamespy2 +{ + protected $name = "fear"; + protected $name_long = "F.E.A.R."; + + protected $port = 27888; +} diff --git a/web/third_party/gameq/gameq/protocols/ffe.php b/web/third_party/gameq/gameq/protocols/ffe.php new file mode 100644 index 00000000..f28cb241 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/ffe.php @@ -0,0 +1,28 @@ +. + */ + +/** + * Fortress Forever Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Ffe extends GameQ_Protocols_Source +{ + protected $name = "ffe"; + protected $name_long = "Fortress Forever"; +} diff --git a/web/third_party/gameq/gameq/protocols/ffow.php b/web/third_party/gameq/gameq/protocols/ffow.php new file mode 100644 index 00000000..068259b2 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/ffow.php @@ -0,0 +1,246 @@ +. + */ + +/** + * Frontlines: Fuel of War Protocol Class + * + * Class is incomplete due to the lack of servers with players active. + * + * http://wiki.hlsw.net/index.php/FFOW_Protocol + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Ffow extends GameQ_Protocols +{ + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_CHALLENGE => "\xFF\xFF\xFF\xFF\x57", + self::PACKET_RULES => "\xFF\xFF\xFF\xFF\x56%s", + //self::PACKET_PLAYERS => "\xFF\xFF\xFF\xFF\x55%s", + self::PACKET_INFO => "\xFF\xFF\xFF\xFF\x46\x4C\x53\x51", + ); + + protected $state = self::STATE_TESTING; + + /** + * Set the packet mode to linear + * + * @var string + */ + protected $packet_mode = self::PACKET_MODE_LINEAR; + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_info", + "process_rules", + //"process_players", + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 5478; // Default port, used if not set when instanced + + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'ffow'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'ffow'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Frontlines: Fuel of War"; + + /* + * Internal methods + */ + + /** + * Parse the challenge response and apply it to all the packet types + * that require it. + * + * @see GameQ_Protocols_Core::parseChallengeAndApply() + */ + protected function parseChallengeAndApply() + { + // Skip the header + $this->challenge_buffer->skip(5); + + // Apply the challenge and return + return $this->challengeApply($this->challenge_buffer->read(4)); + } + + /** + * Preprocess the server info packet(s) + * + * @param unknown_type $packets + */ + protected function preProcess_info($packets=array()) + { + // Implode and return + return implode('', $packets); + } + + protected function process_info() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_INFO)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Parse the response + $data = $this->preProcess_info($this->packets_response[self::PACKET_INFO]); + + // Create a new buffer + $buf = new GameQ_Buffer($data); + + // Skip Header + $buf->skip(6); + + $result->add('servername', $buf->readString()); + $result->add('mapname', $buf->readString()); + $result->add('modname', $buf->readString()); + $result->add('gamemode', $buf->readString()); + $result->add('description', $buf->readString()); + $result->add('version', $buf->readString()); + $result->add('port', $buf->readInt16()); + $result->add('num_players', $buf->readInt8()); + $result->add('max_players', $buf->readInt8()); + $result->add('dedicated', $buf->readInt8()); + $result->add('os', $buf->readInt8()); + $result->add('password', $buf->readInt8()); + $result->add('anticheat', $buf->readInt8()); + $result->add('average_fps', $buf->readInt8()); + $result->add('round', $buf->readInt8()); + $result->add('max_rounds', $buf->readInt8()); + $result->add('time_left', $buf->readInt16()); + + unset($buf, $data); + + // Return the result + return $result->fetch(); + } + + /** + * Preprocess the rule packets returned. Not sure if this is final, need server to test against. + * + * @param array $packets + */ + protected function preProcess_rules($packets=array()) + { + // Implode and return + return implode('', $packets); + } + + /** + * Process the rules and return the data result + */ + protected function process_rules() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_RULES)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Parse the response + $data = $this->preProcess_rules($this->packets_response[self::PACKET_RULES]); + + // Create a new buffer + $buf = new GameQ_Buffer($data); + + // Skip Header + $buf->skip(6); + + while($buf->getLength()) + { + $key = $buf->readString(); + + if(strlen($key) == 0) + { + break; + } + + // Check for map + if(strstr($key, "Map:")) + { + $result->addSub("maplist", "name", $buf->readString()); + } + else // Regular rule + { + $result->add($key, $buf->readString()); + } + } + + unset($buf, $data); + + // Return the result + return $result->fetch(); + } + + /** + * Pre process the player packets, Not final. Need server to test against + * + * @param array $packets + */ + protected function preProcess_players($packets=array()) + { + // Implode and return + return implode('', $packets); + } + + protected function process_players() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_PLAYERS)) + { + return array(); + } + + return array(); + } +} diff --git a/web/third_party/gameq/gameq/protocols/gamespy.php b/web/third_party/gameq/gameq/protocols/gamespy.php new file mode 100644 index 00000000..cd7a85a1 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/gamespy.php @@ -0,0 +1,216 @@ +. + */ + +/** + * GameSpy Protocol Class + * + * This class is used as the basis for all game servers + * that use the GameSpy protocol for querying + * server status. + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Gamespy extends GameQ_Protocols +{ + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * Note: We only send the status packet since that has all the information we ever need. + * The other packets are left for reference purposes + * + * @var array + */ + protected $packets = array( + self::PACKET_STATUS => "\x5C\x73\x74\x61\x74\x75\x73\x5C", + //self::PACKET_PLAYERS => "\x5C\x70\x6C\x61\x79\x65\x72\x73\x5C", + //self::PACKET_DETAILS => "\x5C\x69\x6E\x66\x6F\x5C", + //self::PACKET_BASIC => "\x5C\x62\x61\x73\x69\x63\x5C", + //self::PACKET_RULES => "\x5C\x72\x75\x6C\x65\x73\x5C", + ); + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_status", + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 1; // Default port, used if not set when instanced + + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'gamespy'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'gamespy'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Gamespy"; + + /* + * Internal methods + */ + + protected function preProcess($packets) + { + // Only one packet so its in order + if (count($packets) == 1) + { + return $packets[0]; + } + + // Holds the new list of packets, which will be stripped of queryid and ordered properly. + $packets_ordered = array(); + + // Loop thru the packets + foreach ($packets as $packet) + { + // Check to see if we had a preg_match error + if(preg_match("#^(.*)\\\\queryid\\\\([^\\\\]+)(\\\\|$)#", $packet, $matches) === FALSE) + { + throw new GameQ_ProtocolsException('An error occured while parsing the status packets'); + return $packets_ordered; + } + + // Lets make the key proper incase of decimal points + if(strstr($matches[2], '.')) + { + list($req_id, $req_num) = explode('.', $matches[2]); + + // Now lets put back the number but make sure we pad the req_num so it is correct + // Should make sure the length is always 4 digits past the decimal point + // For some reason the req_num is 1->12.. instead of 01->12 ... so it doesnt ksort properly + $key = $req_id . sprintf(".%04s", $req_num); + } + else + { + $key = $matches[2]; + } + + // Add this stripped queryid to the new array with the id as the key + $packets_ordered[$key] = $matches[1]; + } + + // Sort the new array to make sure the keys (query ids) are in the proper order + ksort($packets_ordered, SORT_NUMERIC); + + // Implode and return only the values as we dont care about the keys anymore + return implode('', array_values($packets_ordered)); + } + + /** + * Process the server status + * + * @throws GameQ_ProtocolsException + */ + protected function process_status() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_STATUS)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Lets pre process and make sure these things are in the proper order by id + $data = $this->preProcess($this->packets_response[self::PACKET_STATUS]); + + // Create a new buffer + $buf = new GameQ_Buffer($data); + + // Lets peek and see if the data starts with a \ + if($buf->lookAhead(1) == '\\') + { + // Burn the first one + $buf->skip(1); + } + + // Explode the data + $data = explode('\\', $buf->getBuffer()); + + // Remove the last 2 "items" as it should be final\ + array_pop($data); + array_pop($data); + + // Init some vars + $num_players = 0; + $num_teams = 0; + + // Now lets loop the array + for($x=0;$x_ variable (i.e players) + if(($suffix = strrpos($key, '_')) !== FALSE && is_numeric(substr($key, $suffix+1))) + { + // See if this is a team designation + if(substr($key, 0, $suffix) == 'teamname') + { + $result->addTeam('teamname', $val); + $num_teams++; + } + else // Its a player + { + if(substr($key, 0, $suffix) == 'playername') + { + $num_players++; + } + + $result->addPlayer(substr($key, 0, $suffix), $val); + + } + } + else // Regular variable so just add the value. + { + $result->add($key, $val); + } + } + + // Add the player and team count + $result->add('num_players', $num_players); + $result->add('num_teams', $num_teams); + + unset($buf, $data, $key, $val, $suffix, $x); + + return $result->fetch(); + } +} diff --git a/web/third_party/gameq/gameq/protocols/gamespy2.php b/web/third_party/gameq/gameq/protocols/gamespy2.php new file mode 100644 index 00000000..a8ddb3da --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/gamespy2.php @@ -0,0 +1,261 @@ +. + */ + +/** + * GameSpy2 Protocol Class + * + * This class is used as the basis for all game servers + * that use the GameSpy2 protocol for querying + * server status. + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Gamespy2 extends GameQ_Protocols +{ + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_DETAILS => "\xFE\xFD\x00\x43\x4F\x52\x59\xFF\x00\x00", + self::PACKET_PLAYERS => "\xFE\xFD\x00\x43\x4F\x52\x59\x00\xFF\xFF", + ); + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_details", + "process_players", + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 1; // Default port, used if not set when instanced + + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'gamespy2'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'gamespy2'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Gamespy2"; + + /* + * Internal methods + */ + + /** + * Pre-process the server details data that was returned. + * + * @param array $packets + */ + protected function preProcess_details($packets) + { + return $packets[0]; + } + + /** + * Process the server details + * + * @throws GameQ_ProtocolsException + */ + protected function process_details() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_DETAILS)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Let's preprocess the rules + $data = $this->preProcess_details($this->packets_response[self::PACKET_DETAILS]); + + // Create a new buffer + $buf = new GameQ_Buffer($data); + + // Make sure the data is formatted properly + if($buf->lookAhead(5) != "\x00\x43\x4F\x52\x59") + { + throw new GameQ_ProtocolsException("Data for ".__METHOD__." does not have the proper header. Header: ".$buf->lookAhead(5)); + return false; + } + + // Now verify the end of the data is correct + if($buf->readLast() !== "\x00") + { + throw new GameQ_ProtocolsException("Data for ".__METHOD__." does not have the proper ending. Ending: ".$buf->readLast()); + return false; + } + + // Skip the header + $buf->skip(5); + + // Loop thru all of the settings and add them + while ($buf->getLength()) + { + // Temp vars + $key = $buf->readString(); + $val = $buf->readString(); + + // Check to make sure there is a valid pair + if(!empty($key)) + { + $result->add($key, $val); + } + } + + unset($buf, $data, $key, $var); + + return $result->fetch(); + } + + /** + * Pre-process the player data that was returned. + * + * @param array $packets + */ + protected function preProcess_players($packets) + { + return $packets[0]; + } + + /** + * Process the player data + * + * @throws GameQ_ProtocolsException + */ + protected function process_players() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_PLAYERS)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Let's preprocess the rules + $data = $this->preProcess_players($this->packets_response[self::PACKET_PLAYERS]); + + // Create a new buffer + $buf = new GameQ_Buffer($data); + + // Make sure the data is formatted properly + if($buf->lookAhead(6) != "\x00\x43\x4F\x52\x59\x00") + { + throw new GameQ_ProtocolsException("Data for ".__METHOD__." does not have the proper header. Header: ".$buf->lookAhead(6)); + return false; + } + + // Now verify the end of the data is correct + if($buf->readLast() !== "\x00") + { + throw new GameQ_ProtocolsException("Data for ".__METHOD__." does not have the proper ending. Ending: ".$buf->readLast()); + return false; + } + + // Skip the header + $buf->skip(6); + + // Players are first + $this->parse_playerteam('players', $buf, $result); + + // Teams are next + $this->parse_playerteam('teams', $buf, $result); + + unset($buf, $data); + + return $result->fetch(); + } + + /** + * Parse the player/team info returned from the player call + * + * @param string $type + * @param GameQ_Buffer $buf + * @param GameQ_Result $result + */ + protected function parse_playerteam($type, &$buf, &$result) + { + // Do count + $result->add('num_'.$type, $buf->readInt8()); + + // Variable names + $varnames = array(); + + // Loop until we run out of length + while ($buf->getLength()) + { + $varnames[] = str_replace('_', '', $buf->readString()); + + if ($buf->lookAhead() === "\x00") + { + $buf->skip(); + break; + } + } + + // Check if there are any value entries + if ($buf->lookAhead() == "\x00") + { + $buf->skip(); + return; + } + + // Get the values + while ($buf->getLength() > 4) + { + foreach ($varnames as $varname) + { + $result->addSub($type, $varname, $buf->readString()); + } + if ($buf->lookAhead() === "\x00") + { + $buf->skip(); + break; + } + } + + return; + } +} diff --git a/web/third_party/gameq/gameq/protocols/gamespy3.php b/web/third_party/gameq/gameq/protocols/gamespy3.php new file mode 100644 index 00000000..ae86a2b0 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/gamespy3.php @@ -0,0 +1,446 @@ +. + */ + +/** + * GameSpy3 Protocol Class + * + * This class is used as the basis for all game servers + * that use the GameSpy3 protocol for querying + * server status. + * + * Note: UT3 and Crysis2 have known issues with GSv3 responses + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Gamespy3 extends GameQ_Protocols +{ + /** + * Set the packet mode to linear + * + * @var string + */ + protected $packet_mode = self::PACKET_MODE_LINEAR; + + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_CHALLENGE => "\xFE\xFD\x09\x10\x20\x30\x40", + self::PACKET_ALL => "\xFE\xFD\x00\x10\x20\x30\x40%s\xFF\xFF\xFF\x01", + ); + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_all", + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 1; // Default port, used if not set when instanced + + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'gamespy3'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'gamespy3'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Gamespy3"; + + /** + * Parse the challenge response and apply it to all the packet types + * that require it. + * + * @see GameQ_Protocols_Core::parseChallengeAndApply() + */ + protected function parseChallengeAndApply() + { + // Pull out the challenge + $challenge = substr(preg_replace( "/[^0-9\-]/si", "", $this->challenge_buffer->getBuffer()), 1); + + $challenge_result = sprintf( + "%c%c%c%c", + ( $challenge >> 24 ), + ( $challenge >> 16 ), + ( $challenge >> 8 ), + ( $challenge >> 0 ) + ); + + // Apply the challenge and return + return $this->challengeApply($challenge_result); + } + + /* + * Internal methods + */ + protected function preProcess_all($packets) + { + $return = array(); + + // Get packet index, remove header + foreach ($packets as $index => $packet) + { + // Make new buffer + $buf = new GameQ_Buffer($packet); + + // Skip the header + $buf->skip(14); + + // Get the current packet and make a new index in the array + $return[$buf->readInt16()] = $buf->getBuffer(); + } + + unset($buf); + + // Sort packets, reset index + ksort($return); + + // Grab just the values + $return = array_values($return); + + // Compare last var of current packet with first var of next packet + // On a partial match, remove last var from current packet, + // variable header from next packet + for ($i = 0, $x = count($return); $i < $x - 1; $i++) + { + // First packet + $fst = substr($return[$i], 0, -1); + + // Second packet + $snd = $return[$i+1]; + + // Get last variable from first packet + $fstvar = substr($fst, strrpos($fst, "\x00")+1); + + // Get first variable from last packet + $snd = substr($snd, strpos($snd, "\x00")+2); + $sndvar = substr($snd, 0, strpos($snd, "\x00")); + + // Check if fstvar is a substring of sndvar + // If so, remove it from the first string + if (strpos($sndvar, $fstvar) !== false) + { + $return[$i] = preg_replace("#(\\x00[^\\x00]+\\x00)$#", "\x00", $return[$i]); + } + } + + // Now let's loop the return and remove any dupe prefixes + for($x = 1; $x < count($return); $x++) + { + $buf = new GameQ_Buffer($return[$x]); + + $prefix = $buf->readString(); + + // Check to see if the return before has the same prefix present + if($prefix != null && strstr($return[($x-1)], $prefix)) + { + // Update the return by removing the prefix plus 2 chars + $return[$x] = substr(str_replace($prefix, '', $return[$x]), 2); + } + + unset($buf); + } + + unset($x, $i, $snd, $sndvar, $fst, $fstvar); + + // Implode into a string and return + return implode("", $return); + } + + protected function process_all() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_ALL)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Parse the response + $data = $this->preProcess_all($this->packets_response[self::PACKET_ALL]); + + // Create a new buffer + $buf = new GameQ_Buffer($data); + + // We go until we hit an empty key + while($buf->getLength()) + { + $key = $buf->readString(); + + if (strlen($key) == 0) + { + break; + } + + $result->add($key, $buf->readString()); + } + + // Now we need to offload to parse the remaining data, player and team information + $this->parsePlayerTeamInfo($buf, $result); + + // Return the result + return $result->fetch(); + } + + protected function delete_result(&$result, $array) + { + foreach($array as $key) + { + unset($result[$key]); + } + + return TRUE; + } + + protected function move_result(&$result, $old, $new) + { + if (isset($result[$old])) + { + $result[$new] = $result[$old]; + unset($result[$old]); + } + + return TRUE; + } + + /** + * Parse the player and team information but do it smartly. Update to the old parseSub method. + * + * @param GameQ_Buffer $buf + * @param GameQ_Result $result + */ + protected function parsePlayerTeamInfo(GameQ_Buffer &$buf, GameQ_Result &$result) + { + /* + * Explode the data into groups. First is player, next is team (item_t) + * + * Each group should be as follows: + * + * [0] => item_ + * [1] => information for item_ + * ... + */ + $data = explode("\x00\x00", $buf->getBuffer()); + + // By default item_group is blank, this will be set for each loop thru the data + $item_group = ''; + + // By default the item_type is blank, this will be set on each loop + $item_type = ''; + + // Loop through all of the $data for information and pull it out into the result + for($x=0; $x < count($data)-1; $x++) + { + // Pull out the item + $item = $data[$x]; + + // If this is an empty item, move on + if($item == '' || $item == "\x00") + { + continue; + } + + /* + * Left as reference: + * + * Each block of player_ and team_t have preceeding junk chars + * + * player_ is actually \x01player_ + * team_t is actually \x00\x02team_t + * + * Probably a by-product of the change to exploding the data from the original. + * + * For now we just strip out these characters + */ + + // Check to see if $item has a _ at the end, this is player info + if(substr($item, -1) == '_') + { + // Set the item group + $item_group = 'players'; + + // Set the item type, rip off any trailing stuff and bad chars + $item_type = rtrim(str_replace("\x01", '', $item), '_'); + } + // Check to see if $item has a _t at the end, this is team info + elseif(substr($item, -2) == '_t') + { + // Set the item group + $item_group = 'teams'; + + // Set the item type, rip off any trailing stuff and bad chars + $item_type = rtrim(str_replace(array("\x00", "\x02"), '', $item), '_t'); + } + // We can assume it is data belonging to a previously defined item + else + { + // Make a temp buffer so we have easier access to the data + $buf_temp = new GameQ_Buffer($item); + + // Get the values + while ($buf_temp->getLength()) + { + // No value so break the loop, end of string + if (($val = $buf_temp->readString()) === '') + { + break; + } + + // Add the value to the proper item in the correct group + $result->addSub($item_group, $item_type, trim($val)); + } + + // Unset out buffer + unset($buf_temp); + } + } + + // Free up some memory + unset($data, $item, $item_group, $item_type, $val); + } + + /** + * Parse the player and team info + * + * @param GameQ_Buffer $buf + * @param GameQ_Result $result + * @throws GameQ_ProtocolsException + * @return boolean + */ + protected function parsePlayerTeamInfoNew(GameQ_Buffer &$buf, GameQ_Result &$result) + { + /** + * Player info is always first, team info (if defined) is second. + * + * Reference: + * + * Player info is preceeded by a hex code of \x01 + * Team info is preceeded by a hex code of \x02 + */ + + // Check the header to make sure the player data is proper + if($buf->read(1) != "\x01") + { + //throw new GameQ_ProtocolsException("First character in player buffer != '\x01'"); + return FALSE; + } + + // Offload the player parsing + $this->parseSubInfo('players', $buf->readString("\x00\x00\x00"), $result); + + // Check to make sure we have team information + if($buf->getLength() >= 6) + { + // Burn chars + $buf->skip(2); + + // Check the header to make sure the data is proper + if($buf->read(1) != "\x02") + { + //throw new GameQ_ProtocolsException("First character in team buffer != '\x02'"); + return FALSE; + } + + // Offload the team parsing + $this->parseSubInfo('teams', $buf->readString("\x00\x00\x00"), $result); + } + + return TRUE; + } + + /** + * Parse the sub-item information for players and teams + * + * @param string $section + * @param string $data + * @param GameQ_Result $result + * @return boolean + */ + protected function parseSubInfo($section, $data, GameQ_Result &$result) + { + /* + * Explode the items so we can iterate easier + * + * Items should split up as follows: + * + * [0] => item_ + * [1] => data for item_ + * [2] => item2_ + * [3] => data for item2_ + * ... + */ + $items = explode("\x00\x00", $data); + + print_r($items); + + // Loop through all of the items + for($x = 0; $x < count($items); $x += 2) + { + // $x is always the key for the item (i.e. player_, ping_, team_, score_, etc...) + $item_type = rtrim($items[$x], '_,_t'); + + // $x+1 is always the data for the above item + // Make a temp buffer so we have easier access to the data + $buf_temp = new GameQ_Buffer($items[$x+1]); + + // Get the values + while ($buf_temp->getLength()) + { + // No value so break the loop, end of string + if (($val = $buf_temp->readString()) === '') + { + break; + } + + // Add the value to the proper item in the correct group + $result->addSub($section, $item_type, trim($val)); + } + + // Unset out buffer + unset($buf_temp, $val); + } + + unset($x, $items, $item_type); + + return TRUE; + } +} diff --git a/web/third_party/gameq/gameq/protocols/gamespy4.php b/web/third_party/gameq/gameq/protocols/gamespy4.php new file mode 100644 index 00000000..838ecfb1 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/gamespy4.php @@ -0,0 +1,30 @@ +. + */ + +/** + * GameSpy4 Protocol Class + * + * By all accounts GameSpy 4 seems to be GameSpy 3. + * + * References: + * http://www.deletedscreen.com/?p=951 + * http://pastebin.com/2zZFDuTd + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Gamespy4 extends GameQ_Protocols_Gamespy3 {} diff --git a/web/third_party/gameq/gameq/protocols/gmod.php b/web/third_party/gameq/gameq/protocols/gmod.php new file mode 100644 index 00000000..f732e9c0 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/gmod.php @@ -0,0 +1,28 @@ +. + */ + +/** + * Garry's Mod Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Gmod extends GameQ_Protocols_Source +{ + protected $name = "gmod"; + protected $name_long = "Garry's Mod"; +} diff --git a/web/third_party/gameq/gameq/protocols/gore.php b/web/third_party/gameq/gameq/protocols/gore.php new file mode 100644 index 00000000..cecd0359 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/gore.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Gore Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Gore extends GameQ_Protocols_Gamespy +{ + protected $name = "gore"; + protected $name_long = "Gore"; + + protected $port = 27778; +} diff --git a/web/third_party/gameq/gameq/protocols/graw.php b/web/third_party/gameq/gameq/protocols/graw.php new file mode 100644 index 00000000..bfff2f0e --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/graw.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Ghost Recon: Advanced Warfighter Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Graw extends GameQ_Protocols_Gamespy2 +{ + protected $name = "graw"; + protected $name_long = "Ghost Recon: Advanced Warfighter"; + + protected $port = 15250; +} diff --git a/web/third_party/gameq/gameq/protocols/graw2.php b/web/third_party/gameq/gameq/protocols/graw2.php new file mode 100644 index 00000000..662d4814 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/graw2.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Ghost Recon: Advanced Warfighter 2 Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Graw2 extends GameQ_Protocols_Gamespy2 +{ + protected $name = "graw2"; + protected $name_long = "Ghost Recon: Advanced Warfighter 2"; + + protected $port = 16250; +} diff --git a/web/third_party/gameq/gameq/protocols/hl2dm.php b/web/third_party/gameq/gameq/protocols/hl2dm.php new file mode 100644 index 00000000..0adec40b --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/hl2dm.php @@ -0,0 +1,28 @@ +. + */ + +/** + * Half Life 2: Deathmatch Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Hl2dm extends GameQ_Protocols_Source +{ + protected $name = "hl2dm"; + protected $name_long = "Half Life 2: Deathmatch"; +} diff --git a/web/third_party/gameq/gameq/protocols/hldm.php b/web/third_party/gameq/gameq/protocols/hldm.php new file mode 100644 index 00000000..7eb6d0aa --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/hldm.php @@ -0,0 +1,28 @@ +. + */ + +/** + * Half Life Deathmatch Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Hldm extends GameQ_Protocols_Source +{ + protected $name = "hldm"; + protected $name_long = "Half Life: Deathmatch"; +} diff --git a/web/third_party/gameq/gameq/protocols/homefront.php b/web/third_party/gameq/gameq/protocols/homefront.php new file mode 100644 index 00000000..7887d4ae --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/homefront.php @@ -0,0 +1,28 @@ +. + */ + +/** + * Homefront Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Homefront extends GameQ_Protocols_Source +{ + protected $name = "homefront"; + protected $name_long = "Homefront"; +} diff --git a/web/third_party/gameq/gameq/protocols/http.php b/web/third_party/gameq/gameq/protocols/http.php new file mode 100644 index 00000000..5a03983f --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/http.php @@ -0,0 +1,42 @@ +. + */ + +/** + * Http Protocol Class + * + * Used for making actual http requests to servers for information + * + * @author Austin Bischoff + */ +abstract class GameQ_Protocols_Http extends GameQ_Protocols +{ + /** + * Set the transport to use TCP + * + * @var string + */ + protected $transport = self::TRANSPORT_TCP; + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 80; // Default port, used if not set when instanced + +} \ No newline at end of file diff --git a/web/third_party/gameq/gameq/protocols/insurgency.php b/web/third_party/gameq/gameq/protocols/insurgency.php new file mode 100644 index 00000000..cf74ed67 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/insurgency.php @@ -0,0 +1,28 @@ +. + */ + +/** + * Insurgency Mod Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Insurgency extends GameQ_Protocols_Source +{ + protected $name = "insurgency"; + protected $name_long = "Insurgency"; +} diff --git a/web/third_party/gameq/gameq/protocols/killingfloor.php b/web/third_party/gameq/gameq/protocols/killingfloor.php new file mode 100644 index 00000000..08336846 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/killingfloor.php @@ -0,0 +1,78 @@ +. + */ + +/** + * Killing floor Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Killingfloor extends GameQ_Protocols_Unreal2 +{ + protected $name = "killingfloor"; + protected $name_long = "Killing Floor"; + + protected $port = 7708; + + /** + * Overloaded for Killing Floor servername issue, could be all unreal2 games though + * + * @see GameQ_Protocols_Unreal2::process_details() + */ + protected function process_details() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_DETAILS)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Let's preprocess the rules + $data = $this->preProcess_details($this->packets_response[self::PACKET_DETAILS]); + + // Create a buffer + $buf = new GameQ_Buffer($data); + + $result->add('serverid', $buf->readInt32()); // 0 + $result->add('serverip', $buf->readPascalString(1)); // empty + $result->add('gameport', $buf->readInt32()); + $result->add('queryport', $buf->readInt32()); // 0 + + // We burn the first char since it is not always correct with the hostname + $buf->skip(1); + + // Read as a regular string since the length is incorrect (what we skipped earlier) + $result->add('servername', $buf->readString()); + + // The rest is read as normal + $result->add('mapname', $buf->readPascalString(1)); + $result->add('gametype', $buf->readPascalString(1)); + $result->add('playercount', $buf->readInt32()); + $result->add('maxplayers', $buf->readInt32()); + $result->add('ping', $buf->readInt32()); // 0 + + // @todo: There is extra data after this point (~9 bytes), cant find any reference on what it is + + unset($buf); + + // Return the result + return $result->fetch(); + } +} diff --git a/web/third_party/gameq/gameq/protocols/l4d.php b/web/third_party/gameq/gameq/protocols/l4d.php new file mode 100644 index 00000000..e1370055 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/l4d.php @@ -0,0 +1,28 @@ +. + */ + +/** + * Left 4 Dead Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_L4d extends GameQ_Protocols_Source +{ + protected $name = "l4d"; + protected $name_long = "Left 4 Dead"; +} diff --git a/web/third_party/gameq/gameq/protocols/l4d2.php b/web/third_party/gameq/gameq/protocols/l4d2.php new file mode 100644 index 00000000..a7aebdcf --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/l4d2.php @@ -0,0 +1,28 @@ +. + */ + +/** + * Left 4 Dead 2 Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_L4d2 extends GameQ_Protocols_Source +{ + protected $name = "l4d2"; + protected $name_long = "Left 4 Dead 2"; +} diff --git a/web/third_party/gameq/gameq/protocols/minecraft.php b/web/third_party/gameq/gameq/protocols/minecraft.php new file mode 100644 index 00000000..39cf7e5e --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/minecraft.php @@ -0,0 +1,43 @@ +. + */ + +/** + * Mincraft Protocol Class + * + * Thanks to https://github.com/xPaw/PHP-Minecraft-Query for helping me realize this is + * Gamespy 3 Protocol. Make sure you enable the items below for it to work. + * + * Information from original author: + * Instructions + * + * Before using this class, you need to make sure that your server is running GS4 status listener. + * + * Look for those settings in server.properties: + * + * enable-query=true + * query.port=25565 + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Minecraft extends GameQ_Protocols_Gamespy4 +{ + protected $name = "minecraft"; + protected $name_long = "Minecraft"; + + protected $port = 25565; +} diff --git a/web/third_party/gameq/gameq/protocols/minequery.php b/web/third_party/gameq/gameq/protocols/minequery.php new file mode 100644 index 00000000..88d6a9b1 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/minequery.php @@ -0,0 +1,133 @@ +. + */ + +/** + * Minequery Protocol Class + * + * This class is used as the basis for all game servers + * that use the Minequery protocol for querying + * server status. + * + * Make sure you have Minequery running. Check the GameQ github wiki for specifics. + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Minequery extends GameQ_Protocols +{ + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_STATUS => "QUERY_JSON\n", + ); + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_status", + ); + + /** + * Set the transport to use TCP + * + * @var string + */ + protected $transport = self::TRANSPORT_TCP; + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 25566; // Default port, used if not set when instanced + + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'minequery'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'minequery'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Minequery"; + + /* + * Internal methods + */ + + /** + * Process the server status + * + * @throws GameQ_ProtocolsException + */ + protected function process_status() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_STATUS)) + { + return array(); + } + + // The response should be a single string so just combine all the packets into a single string + $response = implode('', $this->packets_response[self::PACKET_STATUS]); + + // Check to see if this is valid JSON. + if(($data = json_decode($response)) === NULL) + { + throw new GameQ_ProtocolsException('Unable to decode the JSON data for Minequery'); + return FALSE; + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Server is always dedicated + $result->add('dedicated', TRUE); + + // Add the address and port info + $result->add('serverport', $data->serverPort); + + $result->add('numplayers', $data->playerCount); + $result->add('maxplayers', $data->maxPlayers); + + // Do the players + foreach($data->playerList AS $i => $name) + { + $result->addPlayer('name', $name); + } + + return $result->fetch(); + } +} diff --git a/web/third_party/gameq/gameq/protocols/mohaa.php b/web/third_party/gameq/gameq/protocols/mohaa.php new file mode 100644 index 00000000..b4e47f39 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/mohaa.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Medal of Honor: Allied Assault Protocol Class + * + * @author Marcel Bößendörfer + */ +class GameQ_Protocols_Mohaa extends GameQ_Protocols_Gamespy +{ + protected $name = "mohaa"; + protected $name_long = "Medal of Honor: Allied Assault"; + + protected $port = 12300; +} diff --git a/web/third_party/gameq/gameq/protocols/mohsh.php b/web/third_party/gameq/gameq/protocols/mohsh.php new file mode 100644 index 00000000..ba3234f9 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/mohsh.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Medal of Honor: Spearhead Protocol Class + * + * @author Marcel Bößendörfer + */ +class GameQ_Protocols_Mohsh extends GameQ_Protocols_Gamespy +{ + protected $name = "mohsh"; + protected $name_long = "Medal of Honor: Spearhead"; + + protected $port = 12300; +} diff --git a/web/third_party/gameq/gameq/protocols/mohwf.php b/web/third_party/gameq/gameq/protocols/mohwf.php new file mode 100644 index 00000000..c9f2846f --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/mohwf.php @@ -0,0 +1,131 @@ +. + */ + +/** + * Medal of Honor Warfighter Protocol Class + * + * MOHWF is the same as BF3 minus some quirks in the status query hence the extension + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Mohwf extends GameQ_Protocols_Bf3 +{ + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'mohwf'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'mohwf'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Medal of Honor Warfighter"; + + /* + * Internal Methods + */ + protected function process_status() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_STATUS)) + { + return array(); + } + + // Make buffer for data + $buf = new GameQ_Buffer($this->preProcess_status($this->packets_response[self::PACKET_STATUS])); + + $buf->skip(8); /* skip header */ + + // Decode the words into an array so we can use this data + $words = $this->decodeWords($buf); + + // Make sure we got OK + if (!isset ($words[0]) || $words[0] != 'OK') + { + throw new GameQ_ProtocolsException('Packet Response was not OK! Buffer:'.$buf->getBuffer()); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Server is always dedicated + $result->add('dedicated', TRUE); + + // No mods, as of yet + $result->add('mod', FALSE); + + // These are the same no matter what mode the server is in + $result->add('hostname', $words[1]); + $result->add('numplayers', $words[2]); + $result->add('maxplayers', $words[3]); + $result->add('gametype', $words[4]); + $result->add('map', $words[5]); + + $result->add('roundsplayed', $words[6]); + $result->add('roundstotal', $words[7]); + + // Figure out the number of teams + $num_teams = intval($words[8]); + + // Set the current index + $index_current = 9; + + // Loop for the number of teams found, increment along the way + for($id=1; $id<=$num_teams; $id++) + { + $result->addSub('teams', 'tickets', $words[$index_current]); + $result->addSub('teams', 'id', $id); + + // Increment + $index_current++; + } + + // Get and set the rest of the data points. + $result->add('targetscore', $words[$index_current]); + $result->add('online', TRUE); // Forced TRUE, it seems $words[$index_current + 1] is always empty + $result->add('ranked', $words[$index_current + 2] === 'true'); + $result->add('punkbuster', $words[$index_current + 3] === 'true'); + $result->add('password', $words[$index_current + 4] === 'true'); + $result->add('uptime', $words[$index_current + 5]); + $result->add('roundtime', $words[$index_current + 6]); + + // The next 3 are empty in MOHWF, kept incase they start to work some day + $result->add('ip_port', $words[$index_current + 7]); + $result->add('punkbuster_version', $words[$index_current + 8]); + $result->add('join_queue', $words[$index_current + 9] === 'true'); + + $result->add('region', $words[$index_current + 10]); + $result->add('pingsite', $words[$index_current + 11]); + $result->add('country', $words[$index_current + 12]); + + unset($buf, $words); + + return $result->fetch(); + } +} diff --git a/web/third_party/gameq/gameq/protocols/mta.php b/web/third_party/gameq/gameq/protocols/mta.php new file mode 100644 index 00000000..45d2542f --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/mta.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Multi Theft Auto Protocol Class + * + * @author Marcel Bößendörfer + */ +class GameQ_Protocols_Mta extends GameQ_Protocols_ASE +{ + protected $name = "Mta"; + protected $name_long = "Multi Theft Auto"; + + protected $port = 22126; +} diff --git a/web/third_party/gameq/gameq/protocols/ns.php b/web/third_party/gameq/gameq/protocols/ns.php new file mode 100644 index 00000000..2b246f36 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/ns.php @@ -0,0 +1,28 @@ +. + */ + +/** + * Natural Selection Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Ns extends GameQ_Protocols_Source +{ + protected $name = "ns"; + protected $name_long = "Natural Selection"; +} diff --git a/web/third_party/gameq/gameq/protocols/ns2.php b/web/third_party/gameq/gameq/protocols/ns2.php new file mode 100644 index 00000000..f57f5cc0 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/ns2.php @@ -0,0 +1,37 @@ +. + */ + +/** + * Natural Selection 2 Protocol Class + * + * Note that the query port is the server connect port + 1 + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Ns2 extends GameQ_Protocols_Source +{ + protected $name = "ns2"; + protected $name_long = "Natural Selection 2"; + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 27016; +} diff --git a/web/third_party/gameq/gameq/protocols/quake2.php b/web/third_party/gameq/gameq/protocols/quake2.php new file mode 100644 index 00000000..8d2f25e3 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/quake2.php @@ -0,0 +1,205 @@ +. + */ + +/** + * Quake 2 Protocol Class + * + * This class is used as the basis for all game servers + * that use the Quake 2 protocol for querying + * server status. + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Quake2 extends GameQ_Protocols +{ + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_STATUS => "\xFF\xFF\xFF\xFFstatus\x00", + ); + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_status", + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 27910; // Default port, used if not set when instanced + + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'quake2'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'quake2'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Quake 2"; + + /* + * Internal methods + */ + + protected function preProcess_status($packets) + { + // Should only be one packet + if (count($packets) > 1) + { + throw new GameQ_ProtocolsException('Quake 2 status has more than 1 packet'); + } + + // Make buffer so we can check this out + $buf = new GameQ_Buffer($packets[0]); + + // Grab the header + $header = $buf->read(11); + + // Now lets verify the header + if($header != "\xFF\xFF\xFF\xFFprint\x0a\\") + { + throw new GameQ_ProtocolsException('Unable to match Gamespy 2 status response header. Header: '. $header); + return FALSE; + } + + // Return the data with the header stripped, ready to go. + return $buf->getBuffer(); + } + + /** + * Process the server status + * + * @throws GameQ_ProtocolsException + */ + protected function process_status() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_STATUS)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Lets pre process and make sure these things are in the proper order by id + $data = $this->preProcess_status($this->packets_response[self::PACKET_STATUS]); + + // Make buffer + $buf = new GameQ_Buffer($data); + + // First section is the server info, the rest is player info + $server_info = $buf->readString("\x0A"); + $player_info = $buf->getBuffer(); + + unset($buf); + + // Make a new buffer for the server info + $buf_server = new GameQ_Buffer($server_info); + + // Key / value pairs + while ($buf_server->getLength()) + { + $result->add( + $buf_server->readString('\\'), + $buf_server->readStringMulti(array('\\', "\x0a"), $delimfound) + ); + + if ($delimfound === "\x0a") + { + break; + } + } + + // Now send the rest to players + $this->parsePlayers($result, $player_info); + + // Free some memory + unset($sections, $player_info, $server_info, $delimfound, $buf_server, $data); + + // Return the result + return $result->fetch(); + } + + /** + * Parse the players and add them to the return. + * + * This is overloadable because it seems that different games return differen info. + * + * @param GameQ_Result $result + * @param string $players_info + */ + protected function parsePlayers(GameQ_Result &$result, $players_info) + { + // Explode the arrays out + $players = explode("\x0A", $players_info); + + // Remove the last array item as it is junk + array_pop($players); + + // Add total number of players + $result->add('num_players', count($players)); + + // Loop the players + foreach($players AS $player_info) + { + $buf = new GameQ_Buffer($player_info); + + // Add player info + $result->addPlayer('frags', $buf->readString("\x20")); + $result->addPlayer('ping', $buf->readString("\x20")); + + // Skip first " + $buf->skip(1); + + // Add player name + $result->addPlayer('name', trim($buf->readString('"'))); + + // Skip first " + $buf->skip(2); + + // Add address + $result->addPlayer('address', trim($buf->readString('"'))); + } + + // Free some memory + unset($buf, $players, $player_info); + } +} diff --git a/web/third_party/gameq/gameq/protocols/quake3.php b/web/third_party/gameq/gameq/protocols/quake3.php new file mode 100644 index 00000000..c2f40326 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/quake3.php @@ -0,0 +1,200 @@ +. + */ + +/** + * Quake 3 Protocol Class + * + * This class is used as the basis for all game servers + * that use the Quake 3 protocol for querying + * server status. + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Quake3 extends GameQ_Protocols +{ + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_STATUS => "\xFF\xFF\xFF\xFF\x67\x65\x74\x73\x74\x61\x74\x75\x73\x0A", + //self::PACKET_DETAILS => "\xFF\xFF\xFF\xFFgetinfo\x00", + ); + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_status", + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 27960; // Default port, used if not set when instanced + + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'quake3'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'quake3'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Quake 3"; + + /* + * Internal methods + */ + + protected function preProcess_status($packets) + { + // Should only be one packet + if (count($packets) > 1) + { + throw new GameQ_ProtocolsException('Quake 3 status has more than 1 packet'); + } + + // Make buffer so we can check this out + $buf = new GameQ_Buffer($packets[0]); + + // Grab the header + $header = $buf->read(20); + + // Now lets verify the header + if($header != "\xFF\xFF\xFF\xFFstatusResponse\x0A\x5C") + { + throw new GameQ_ProtocolsException('Unable to match Gamespy 3 challenge response header. Header: '. $header); + return FALSE; + } + + // Return the data with the header stripped, ready to go. + return $buf->getBuffer(); + } + + /** + * Process the server status + * + * @throws GameQ_ProtocolsException + */ + protected function process_status() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_STATUS)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Lets pre process and make sure these things are in the proper order by id + $data = $this->preProcess_status($this->packets_response[self::PACKET_STATUS]); + + // Make buffer + $buf = new GameQ_Buffer($data); + + // First section is the server info, the rest is player info + $server_info = $buf->readString("\x0A"); + $player_info = $buf->getBuffer(); + + unset($buf); + + // Make a new buffer for the server info + $buf_server = new GameQ_Buffer($server_info); + + // Key / value pairs + while ($buf_server->getLength()) + { + $result->add( + $buf_server->readString('\\'), + $buf_server->readStringMulti(array('\\', "\x0a"), $delimfound) + ); + + if ($delimfound === "\x0a") + { + break; + } + } + + // Now send the rest to players + $this->parsePlayers($result, $player_info); + + // Free some memory + unset($sections, $player_info, $server_info, $delimfound, $buf_server, $data); + + // Return the result + return $result->fetch(); + } + + /** + * Parse the players and add them to the return. + * + * This is overloadable because it seems that different games return differen info. + * + * @param GameQ_Result $result + * @param string $players_info + */ + protected function parsePlayers(GameQ_Result &$result, $players_info) + { + // Explode the arrays out + $players = explode("\x0A", $players_info); + + // Remove the last array item as it is junk + array_pop($players); + + // Add total number of players + $result->add('num_players', count($players)); + + // Loop the players + foreach($players AS $player_info) + { + $buf = new GameQ_Buffer($player_info); + + // Add player info + $result->addPlayer('frags', $buf->readString("\x20")); + $result->addPlayer('ping', $buf->readString("\x20")); + + // Skip first " + $buf->skip(1); + + // Add player name + $result->addPlayer('name', trim($buf->readString('"'))); + } + + // Free some memory + unset($buf, $players, $player_info); + } +} diff --git a/web/third_party/gameq/gameq/protocols/quake4.php b/web/third_party/gameq/gameq/protocols/quake4.php new file mode 100644 index 00000000..12b61669 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/quake4.php @@ -0,0 +1,44 @@ +. + */ + +/** + * Quake 4 Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Quake4 extends GameQ_Protocols_Doom3 +{ + protected $name = "quake4"; + protected $name_long = "Quake 4"; + + protected $port = 28004; + + protected function parsePlayers(GameQ_Buffer &$buf, GameQ_Result &$result) + { + while (($id = $buf->readInt8()) != 32) + { + $result->addPlayer('id', $id); + $result->addPlayer('ping', $buf->readInt16()); + $result->addPlayer('rate', $buf->readInt32()); + $result->addPlayer('name', $buf->readString()); + $result->addPlayer('clantag', $buf->readString()); + } + + return true; + } +} diff --git a/web/third_party/gameq/gameq/protocols/redeclipse.php b/web/third_party/gameq/gameq/protocols/redeclipse.php new file mode 100644 index 00000000..fc91bfb9 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/redeclipse.php @@ -0,0 +1,212 @@ +. + */ + +/** + * Red Eclipse + * + * This game is based off of Cube 2 but the protocol response is way + * different than Cube 2 + * + * Thanks to Poil for information to help build out this protocol class + * + * References: + * https://github.com/stainsby/redflare/blob/master/poller.js + * https://github.com/stainsby/redflare/blob/master/lib/protocol.js + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Redeclipse extends GameQ_Protocols_Cube2 +{ + /** + * The query protocol used to make the call + * + * @var string + */ + protected $protocol = 'redeclipse'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'redeclipse'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Red Eclipse"; + + /** + * Defined Game mutators + * + * @var array + */ + protected $mutators = array( + 'multi' => 1, + 'ffa' => 2, + 'coop' => 4, + 'insta' => 8, + 'medieval' => 16, + 'kaboom' => 32, + 'duel' => 64, + 'survivor' => 128, + 'classic' => 256, + 'onslaught' => 512, + 'jetpack' => 1024, + 'vampire' => 2048, + 'expert' => 4096, + 'resize' => 8192, + ); + + /** + * Defined Master modes (i.e access restrictions) + * + * @var array + */ + protected $mastermodes = array( + 'open', // 0 + 'veto', // 1 + 'locked', // 2 + 'private', // 3 + 'password', // 4 + ); + + /** + * Defined Game modes + * + * @var array + */ + protected $gamemodes = array( + 'demo', // 0 + 'edit', // 1 + 'deathmatch', // 2 + 'capture-the-flag', // 3 + 'defend-the-flag', // 4 + 'bomberball', // 5 + 'time-trial', // 6 + 'gauntlet' // 7 + ); + + /** + * Process the status result. This result is different from the parent + * + * @see GameQ_Protocols_Cube2::process_status() + */ + protected function process_status() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_STATUS)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Let's preprocess the rules + $data = $this->preProcess_status($this->packets_response[self::PACKET_STATUS]); + + // Create a new buffer + $buf = new GameQ_Buffer($data); + + // Check the header, should be the same response as the packet we sent + if($buf->read(6) != $this->packets[self::PACKET_STATUS]) + { + throw new GameQ_ProtocolsException("Data for ".__METHOD__." does not have the proper header type (should be {$this->packets[self::PACKET_STATUS]})."); + return array(); + } + + /** + * Reference chart for ints by position + * + * 0 - Num players + * 1 - Number of items to follow (i.e. 8), not used yet + * 2 - Version + * 3 - gamemode (dm, ctf, etc...) + * 4 - mutators (sum of power of 2) + * 5 - Time remaining + * 6 - max players + * 7 - Mastermode (open, password, etc) + * 8 - variable count + * 9 - modification count + */ + + $result->add('num_players', $this->readInt($buf)); + + $items = $this->readInt($buf); // We dump this as we dont use it for now + + $result->add('version', $this->readInt($buf)); + $result->add('gamemode', $this->gamemodes[$this->readInt($buf)]); + + // This is a sum of power's of 2 (2^1, 1 << 1) + $mutators_number = $this->readInt($buf); + + $mutators = array(); + + foreach($this->mutators AS $mutator => $flag) + { + if($flag & $mutators_number) + { + $mutators[] = $mutator; + } + } + + $result->add('mutators', $mutators); + $result->add('mutators_number', $mutators_number); + + $result->add('time_remaining', $this->readInt($buf)); + $result->add('max_players', $this->readInt($buf)); + + $mastermode = $this->readInt($buf); + + $result->add('mastermode', $this->mastermodes[$mastermode]); + + $result->add('password', ((in_array($mastermode, array(4)))?TRUE:FALSE)); + + // @todo: No idea what these next 2 are used for + $result->add('variableCount', $this->readInt($buf)); + $result->add('modificationCount', $this->readInt($buf)); + + $result->add('map', $buf->readString()); + $result->add('servername', $buf->readString()); + + // The rest from here is player information, we read until we run out of strings (\x00) + while($raw = $buf->readString()) + { + // Items seem to be seperated by \xc + $items = explode("\xc", $raw); + + // Indexes 0, 1 & 5 seem to be junk + // Indexes 2, 3, 4 seem to have something of use, not sure about #3 + $result->addPlayer('guid', (int) trim($items[2], "[]")); + + // Index 4 has the player name with some kind int added on to the front, icon or something? + // Anyway remove it for now... + if(preg_match('/(\[[0-9]+\])(.*)/i', $items[4], $name)) + { + $result->addPlayer('name', $name[2]); + } + } + + unset($buf, $data); + + return $result->fetch(); + } +} diff --git a/web/third_party/gameq/gameq/protocols/redfaction.php b/web/third_party/gameq/gameq/protocols/redfaction.php new file mode 100644 index 00000000..dcb5cf21 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/redfaction.php @@ -0,0 +1,121 @@ +. + */ + +/** + * Red Faction Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Redfaction extends GameQ_Protocols +{ + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_STATUS => "\x00\x00\x00\x00", + ); + + protected $state = self::STATE_TESTING; + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_status", + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 7755; // Default port, used if not set when instanced + + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'redfaction'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'redfaction'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Red Faction"; + + /* + * Internal methods + */ + + /** + * Process the server status + * + * @throws GameQ_ProtocolsException + */ + protected function process_status() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_STATUS)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Header, we're being carefull here + if ($buf->read() !== "\x00") + { + throw new GameQ_ProtocolsException('Header error in Red Faction'); + return FALSE; + } + + // Dunno + while ($buf->read() !== "\x00") {} + $buf->read(); + + // Data + $result->add('servername', $buf->readString()); + $result->add('gametype', $buf->readInt8()); + $result->add('num_players', $buf->readInt8()); + $result->add('max_players', $buf->readInt8()); + $result->add('map', $buf->readString()); + $buf->read(); + $result->add('dedicated', $buf->readInt8()); + + // Free some memory + unset($sections, $buf, $data); + + // Return the result + return $result->fetch(); + } +} diff --git a/web/third_party/gameq/gameq/protocols/redorchestra.php b/web/third_party/gameq/gameq/protocols/redorchestra.php new file mode 100644 index 00000000..d63c8e74 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/redorchestra.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Red Orchestra: Ostfront 41-45 Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Redorchestra extends GameQ_Protocols_Gamespy +{ + protected $name = "redorchestra"; + protected $name_long = "Red Orchestra: Ostfront 41-45"; + + protected $port = 7767; +} diff --git a/web/third_party/gameq/gameq/protocols/redorchestra2.php b/web/third_party/gameq/gameq/protocols/redorchestra2.php new file mode 100644 index 00000000..01b59a1e --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/redorchestra2.php @@ -0,0 +1,32 @@ +. + */ + +/** + * Red Orchestra 2 Protocol Class + * + * Thanks to http://forums.tripwireinteractive.com/showthread.php?t=72439 for information about the protocol + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Redorchestra2 extends GameQ_Protocols_Source +{ + protected $name = "redorchestra2"; + protected $name_long = "Red Orchestra 2"; + + protected $port = 27015; +} diff --git a/web/third_party/gameq/gameq/protocols/rtcw.php b/web/third_party/gameq/gameq/protocols/rtcw.php new file mode 100644 index 00000000..3a9addef --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/rtcw.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Return to Castle Wolfenstein Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Rtcw extends GameQ_Protocols_Quake3 +{ + protected $name = "rtcw"; + protected $name_long = "Return to Castle Wolfenstein"; + + protected $port = 27960; +} diff --git a/web/third_party/gameq/gameq/protocols/samp.php b/web/third_party/gameq/gameq/protocols/samp.php new file mode 100644 index 00000000..ebb04705 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/samp.php @@ -0,0 +1,239 @@ +. + */ + +/** + * San Andreas Multiplayer Protocol Class + * + * This class holds the query info and processing for SAMP + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Samp extends GameQ_Protocols +{ + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_STATUS => "SAMP%s%si", + self::PACKET_PLAYERS => "SAMP%s%sd", + self::PACKET_RULES => "SAMP%s%sr", + ); + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_status", + "process_players", + "process_rules", + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 7777; // Default port, used if not set when instanced + + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'samp'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'samp'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "San Andreas Multiplayer"; + + /* + * Internal methods + */ + + /** + * We need to modify the packets before they are sent for this protocol + * + * @see GameQ_Protocols_Core::beforeSend() + */ + public function beforeSend() + { + // We need to repack the IP address of the server + $address = implode('', array_map('chr', explode('.', $this->ip))); + + // Repack the server port + $port = pack ("S", $this->port); + + // Let's loop the packets and set the proper pieces + foreach($this->packets AS $packet_type => $packet) + { + // Fill out the packet with the server info + $this->packets[$packet_type] = sprintf($packet, $address, $port); + } + + // Free up some memory + unset($address, $port); + + return TRUE; + } + + protected function preProcess($packets) + { + // Make buffer so we can check this out + $buf = new GameQ_Buffer(implode('', $packets)); + + // Grab the header + $header = $buf->read(11); + + // Now lets verify the header + if(substr($header, 0, 4) != "SAMP") + { + throw new GameQ_ProtocolsException('Unable to match SAMP response header. Header: '. $header); + return FALSE; + } + + // Return the data with the header stripped, ready to go. + return $buf->getBuffer(); + } + + /** + * Process the server status + * + * @throws GameQ_ProtocolsException + */ + protected function process_status() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_STATUS)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Always dedicated + $result->add('dedicated', TRUE); + + // Preprocess and make buffer + $buf = new GameQ_Buffer($this->preProcess($this->packets_response[self::PACKET_STATUS])); + + // Pull out the server information + $result->add('password', (bool) $buf->readInt8()); + $result->add('num_players', $buf->readInt16()); + $result->add('max_players', $buf->readInt16()); + + // These are read differently for these last 3 + $result->add('servername', $buf->read($buf->readInt32())); + $result->add('gametype', $buf->read($buf->readInt32())); + $result->add('map', $buf->read($buf->readInt32())); + + // Free some memory + unset($buf); + + // Return the result + return $result->fetch(); + } + + /** + * Process server rules + */ + protected function process_rules() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_RULES)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Preprocess and make buffer + $buf = new GameQ_Buffer($this->preProcess($this->packets_response[self::PACKET_RULES])); + + // Number of rules + $result->add('num_rules', $buf->readInt16()); + + // Run until we run out of buffer + while ($buf->getLength()) + { + $result->add($buf->readPascalString(), $buf->readPascalString()); + } + + // Free some memory + unset($buf); + + // Return the result + return $result->fetch(); + } + + /** + * Process the players + * + * NOTE: There is a restriction on the SAMP server side that if there are too many players + * the player return will be empty. Nothing can really be done about this unless you bug + * the game developers to fix it. + */ + protected function process_players() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_PLAYERS)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Preprocess and make buffer + $buf = new GameQ_Buffer($this->preProcess($this->packets_response[self::PACKET_PLAYERS])); + + // Number of players + $result->add('num_players', $buf->readInt16()); + + // Run until we run out of buffer + while ($buf->getLength()) + { + $result->addPlayer('id', $buf->readInt8()); + $result->addPlayer('name', $buf->readPascalString()); + $result->addPlayer('score', $buf->readInt32()); + $result->addPlayer('ping', $buf->readInt32()); + } + + // Free some memory + unset($buf); + + // Return the result + return $result->fetch(); + } +} diff --git a/web/third_party/gameq/gameq/protocols/sof2.php b/web/third_party/gameq/gameq/protocols/sof2.php new file mode 100644 index 00000000..fda9d208 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/sof2.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Soldier of Fortune 2 Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Sof2 extends GameQ_Protocols_Quake3 +{ + protected $name = "sof2"; + protected $name_long = "Soldier of Fortune 2"; + + protected $port = 20100; +} diff --git a/web/third_party/gameq/gameq/protocols/soldat.php b/web/third_party/gameq/gameq/protocols/soldat.php new file mode 100644 index 00000000..e22fd3f7 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/soldat.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Soldat Protocol Class + * + * @author Marcel Bößendörfer + */ +class GameQ_Protocols_Soldat extends GameQ_Protocols_ASE +{ + protected $name = "Soldat"; + protected $name_long = "Soldat"; + + protected $port = 23196; +} diff --git a/web/third_party/gameq/gameq/protocols/source.php b/web/third_party/gameq/gameq/protocols/source.php new file mode 100644 index 00000000..c7895eb6 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/source.php @@ -0,0 +1,453 @@ +. + */ + +/** + * Valve Source Engine Protocol Class + * + * This class is used as the basis for all other source based servers + * that rely on the source protocol for game querying + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Source extends GameQ_Protocols +{ + /* + * Source engine type constants + */ + const SOURCE_ENGINE = 0; + const GOLDSOURCE_ENGINE = 1; + + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_CHALLENGE => "\xFF\xFF\xFF\xFF\x56\x00\x00\x00\x00", + self::PACKET_DETAILS => "\xFF\xFF\xFF\xFFTSource Engine Query\x00", + self::PACKET_PLAYERS => "\xFF\xFF\xFF\xFF\x55%s", + self::PACKET_RULES => "\xFF\xFF\xFF\xFF\x56%s", + ); + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_details", + "process_players", + "process_rules", + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 27015; // Default port, used if not set when instanced + + /** + * The query protocol used to make the call + * + * @var string + */ + protected $protocol = 'source'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'source'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Source Server"; + + /** + * Define the Source engine type. By default it is assumed to be Source + * + * @var int + */ + protected $source_engine = self::SOURCE_ENGINE; + + /** + * Parse the challenge response and apply it to all the packet types + * that require it. + * + * @see GameQ_Protocols_Core::parseChallengeAndApply() + */ + protected function parseChallengeAndApply() + { + // Skip the header + $this->challenge_buffer->skip(5); + + // Apply the challenge and return + return $this->challengeApply($this->challenge_buffer->read(4)); + } + + /* + * Internal methods + */ + + /** + * Pre-process the server details data that was returned. + * + * @param array $packets + */ + protected function preProcess_details($packets) + { + // Process the packets + return $this->process_packets($packets); + } + + /** + * Handles processing the details data into a usable format + * + * @throws GameQ_ProtocolsException + */ + protected function process_details() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_DETAILS)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Let's preprocess the rules + $data = $this->preProcess_details($this->packets_response[self::PACKET_DETAILS]); + + // Create a new buffer + $buf = new GameQ_Buffer($data); + + // Skip the header (0xFF0xFF0xFF0xFF) + $buf->skip(4); + + // Get the type + $type = $buf->read(1); + + // Make sure the data is formatted properly + // Source is 0x49, Goldsource is 0x6d, 0x44 I am not sure about + if(!in_array($type, array("\x49", "\x44", "\x6d"))) + { + throw new GameQ_ProtocolsException("Data for ".__METHOD__." does not have the proper header type (should be 0x49|0x44|0x6d). Header type: 0x".bin2hex($type)); + return array(); + } + + // Update the engine type for other calls and other methods, if necessary + if(bin2hex($type) == '6d') + { + $this->source_engine = self::GOLDSOURCE_ENGINE; + } + + // Check engine type + if ($this->source_engine == self::GOLDSOURCE_ENGINE) + { + $result->add('address', $buf->readString()); + } + else + { + $result->add('protocol', $buf->readInt8()); + } + + $result->add('hostname', $buf->readString()); + $result->add('map', $buf->readString()); + $result->add('game_dir', $buf->readString()); + $result->add('game_descr', $buf->readString()); + + // Check engine type + if ($this->source_engine != self::GOLDSOURCE_ENGINE) + { + $result->add('steamappid', $buf->readInt16()); + } + + $result->add('num_players', $buf->readInt8()); + $result->add('max_players', $buf->readInt8()); + + // Check engine type + if ($this->source_engine == self::GOLDSOURCE_ENGINE) + { + $result->add('version', $buf->readInt8()); + } + else + { + $result->add('num_bots', $buf->readInt8()); + } + + $result->add('dedicated', $buf->read()); + $result->add('os', $buf->read()); + $result->add('password', $buf->readInt8()); + + // Check engine type + if ($this->source_engine == self::GOLDSOURCE_ENGINE) + { + $result->add('ismod', $buf->readInt8()); + } + + $result->add('secure', $buf->readInt8()); + + // Check engine type + if ($this->source_engine == self::GOLDSOURCE_ENGINE) + { + $result->add('num_bots', $buf->readInt8()); + } + else + { + $result->add('version', $buf->readInt8()); + } + + // Add extra data flag check here, only for source games (not goldsource) + // https://developer.valvesoftware.com/wiki/Server_Queries#Source_servers_2 + + unset($buf); + + return $result->fetch(); + } + + /** + * Pre-process the player data sent + * + * @param array $packets + */ + protected function preProcess_players($packets) + { + // Process the packets + return $this->process_packets($packets); + } + + /** + * Handles processing the player data into a useable format + * + * @throws GameQ_ProtocolsException + */ + protected function process_players() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_PLAYERS)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Let's preprocess the rules + $data = $this->preProcess_players($this->packets_response[self::PACKET_PLAYERS]); + + // Create a new buffer + $buf = new GameQ_Buffer($data); + + // Make sure the data is formatted properly + if(($header = $buf->read(5)) != "\xFF\xFF\xFF\xFF\x44") + { + throw new GameQ_ProtocolsException("Data for ".__METHOD__." does not have the proper header (should be 0xFF0xFF0xFF0xFF0x44). Header: ".bin2hex($header)); + return array(); + } + + // Pull out the number of players + $num_players = $buf->readInt8(); + + // Player count + $result->add('num_players', $num_players); + + // No players so no need to look any further + if($num_players == 0) + { + return $result->fetch(); + } + + // Players list + while ($buf->getLength()) + { + $result->addPlayer('id', $buf->readInt8()); + $result->addPlayer('name', $buf->readString()); + $result->addPlayer('score', $buf->readInt32Signed()); + $result->addPlayer('time', $buf->readFloat32()); + } + + unset($buf); + + return $result->fetch(); + } + + /** + * Pre process the rules data that was returned. Make sure the return + * data is in a single string + * + * @param array $packets + */ + protected function preProcess_rules($packets) + { + // Process the packets + return $this->process_packets($packets); + } + + /** + * Handles processing the rules data into a usable format + * + * @throws GameQ_ProtocolsException + */ + protected function process_rules() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_RULES)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Let's preprocess the rules + $data = $this->preProcess_rules($this->packets_response[self::PACKET_RULES]); + + $buf = new GameQ_Buffer($data); + + // Make sure the data is formatted properly + if(($header = $buf->read(5)) != "\xFF\xFF\xFF\xFF\x45") + { + throw new GameQ_ProtocolsException("Data for ".__METHOD__." does not have the proper header (should be 0xFF0xFF0xFF0xFF0x45). Header: ".bin2hex($header)); + return array(); + } + + // Count the number of rules + $num_rules = $buf->readInt16Signed(); + + // Add the count of the number of rules this server has + $result->add('num_rules', $num_rules); + + // Rules + while ($buf->getLength()) + { + $result->add($buf->readString(), $buf->readString()); + } + + unset($buf); + + return $result->fetch(); + } + + /** + * Process the packets to make sure we combine and decompress as needed + * + * @param array $packets + * @throws GameQ_ProtocolsException + * @return string + */ + protected function process_packets($packets) + { + // Make a buffer to see if we should have multiple packets + $buffer = new GameQ_Buffer($packets[0]); + + // First we need to see if the packet is split + // -2 = split packets + // -1 = single packet + $packet_type = $buffer->readInt32Signed(); + + // This is one packet so just return the rest of the buffer + if($packet_type == -1) + { + // Free some memory + unset($buffer); + + // We always return the packet as expected, with null included + return $packets[0]; + } + + // Free some memory + unset($buffer); + + // Init array so we can order + $packs = array(); + + // We have multiple packets so we need to get them and order them + foreach($packets AS $packet) + { + // Make a buffer so we can read this info + $buffer = new GameQ_Buffer($packet); + + // Pull some info + $packet_type = $buffer->readInt32Signed(); + $request_id = $buffer->readInt32Signed(); + + // Check to see if this is compressed + if($request_id & 0x80000000) + { + // Check to see if we have Bzip2 installed + if(!function_exists('bzdecompress')) + { + throw new GameQ_ProtocolsException('Bzip2 is not installed. See http://www.php.net/manual/en/book.bzip2.php for more info.', 0); + return FALSE; + } + + // Get some info + $num_packets = $buffer->readInt8(); + $cur_packet = $buffer->readInt8(); + $packet_length = $buffer->readInt32(); + $packet_checksum = $buffer->readInt32(); + + // Try to decompress + $result = bzdecompress($buffer->getBuffer()); + + // Now verify the length + if(strlen($result) != $packet_length) + { + throw new GameQ_ProtocolsException("Checksum for compressed packet failed! Length expected {$packet_length}, length returned".strlen($result)); + } + + // Set the new packs + $packs[$cur_packet] = $result; + } + else // Normal packet + { + // Gold source does things a bit different + if($this->source_engine == self::GOLDSOURCE_ENGINE) + { + $packet_number = $buffer->readInt8(); + } + else // New source + { + $packet_number = $buffer->readInt16Signed(); + $split_length = $buffer->readInt16Signed(); + } + + // Now add the rest of the packet to the new array with the packet_number as the id so we can order it + $packs[$packet_number] = $buffer->getBuffer(); + } + + unset($buffer); + } + + // Free some memory + unset($packets, $packet); + + // Sort the packets by packet number + ksort($packs); + + // Now combine the packs into one and return + return implode("", $packs); + } +} diff --git a/web/third_party/gameq/gameq/protocols/stalker.php b/web/third_party/gameq/gameq/protocols/stalker.php new file mode 100644 index 00000000..b3b82eba --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/stalker.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Stalker Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Stalker extends GameQ_Protocols_Gamespy2 +{ + protected $name = "stalker"; + protected $name_long = "S.T.A.L.K.E.R: Shadow of Chernobyl"; + + protected $port = 5445; +} diff --git a/web/third_party/gameq/gameq/protocols/teamspeak2.php b/web/third_party/gameq/gameq/protocols/teamspeak2.php new file mode 100644 index 00000000..53052595 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/teamspeak2.php @@ -0,0 +1,338 @@ +. + */ + +/** + * Teamspeak 2 Protocol Class + * + * This class provides some functionality for getting status information for Teamspeak 2 + * servers. + * + * This code ported from GameQ v1. Credit to original author(s) as I just updated it to + * work within this new system. + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Teamspeak2 extends GameQ_Protocols +{ + /** + * Normalization for this protocol class + * + * @var array + */ + protected $normalize = array( + // General + 'general' => array( + 'dedicated' => array('dedicated'), + 'hostname' => array('servername'), + 'password' => array('serverpassword'), + 'numplayers' => array('servercurrentusers'), + 'maxplayers' => array('servermaxusers'), + 'players' => array('players'), + 'teams' => array('teams'), + ), + + // Player + 'player' => array( + 'id' => array('pid'), + 'team' => array('cid'), + ), + + // Team + 'team' => array( + 'id' => array('id'), + ), + ); + + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_DETAILS => "sel %d\x0Asi\x0A", + self::PACKET_PLAYERS => "sel %d\x0Apl\x0A", + self::PACKET_CHANNELS => "sel %d\x0Acl\x0A", + ); + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_details", + "process_channels", + "process_players", + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 8767; // Default port, used if not set when instanced + + /** + * Because Teamspeak is run like a master server we have to know what port we are really querying + * + * @var int + */ + protected $master_server_port = 51234; + + /** + * We have to use TCP connection + * + * @var string + */ + protected $transport = self::TRANSPORT_TCP; + + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'teamspeak2'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'teamspeak2'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Teamspeak 2"; + + /** + * We need to affect the packets we are sending before they are sent + * + * @see GameQ_Protocols_Core::beforeSend() + */ + public function beforeSend() + { + // Let's loop the packets and set the proper pieces + foreach($this->packets AS $packet_type => $packet) + { + // Update the query port for the server + $this->packets[$packet_type] = sprintf($packet, $this->port); + } + + // Set the port we are connecting to the master port + $this->port = $this->master_server_port; + + return TRUE; + } + + + /* + * Internal methods + */ + + protected function preProcess($packets=array()) + { + // Create a buffer + $buffer = new GameQ_Buffer(implode("", $packets)); + + // Verify the header + $this->verify_header($buffer); + + return $buffer; + } + + /** + * Process the server information + */ + protected function process_details() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_DETAILS)) + { + return array(); + } + + // Let's preprocess the status + $buffer = $this->preProcess($this->packets_response[self::PACKET_DETAILS]); + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Always dedicated + $result->add('dedicated', TRUE); + + // Let's loop until we run out of data + while($buffer->getLength()) + { + // Grab the row, which is an item + // Check for end of packet + if(($row = trim($buffer->readString("\n"))) == 'OK') + { + break; + } + + // Split out the information + list($key, $value) = explode('=', $row, 2); + + // Add this to the result + $result->add($key, $value); + } + + unset($buffer, $row, $key, $value); + + return $result->fetch(); + } + + /** + * Process the channel listing + */ + protected function process_channels() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_CHANNELS)) + { + return array(); + } + + // Let's preprocess the status + $buffer = $this->preProcess($this->packets_response[self::PACKET_CHANNELS]); + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // The first line holds the column names, data returned is in column/row format + $columns = explode("\t", trim($buffer->readString("\n")), 9); + + // Loop thru the rows until we run out of information + while($buffer->getLength()) + { + // Grab the row, which is a tabbed list of items + // Check for end of packet + if(($row = trim($buffer->readString("\n"))) == 'OK') + { + break; + } + + // Explode and merge the data with the columns, then parse + $data = array_combine($columns, explode("\t", $row, 9)); + + foreach($data AS $key => $value) + { + // Now add the data to the result + $result->addTeam($key, $value); + } + } + + unset($data, $buffer, $row, $columns, $key, $value); + + return $result->fetch(); + } + + /** + * Process the players response + */ + protected function process_players() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_PLAYERS)) + { + return array(); + } + + // Let's preprocess the status + $buffer = $this->preProcess($this->packets_response[self::PACKET_PLAYERS]); + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // The first line holds the column names, data returned is in column/row format + $columns = explode("\t", trim($buffer->readString("\n")), 16); + + // Loop thru the rows until we run out of information + while($buffer->getLength()) + { + // Grab the row, which is a tabbed list of items + // Check for end of packet + if(($row = trim($buffer->readString("\n"))) == 'OK') + { + break; + } + + // Explode and merge the data with the columns, then parse + $data = array_combine($columns, explode("\t", $row, 16)); + + foreach($data AS $key => $value) + { + // Now add the data to the result + $result->addPlayer($key, $value); + } + } + + unset($data, $buffer, $row, $columns, $key, $value); + + return $result->fetch(); + } + + + /** + * Verify the header of the returned response packet + * + * @param GameQ_Buffer $buffer + * @throws GameQ_ProtocolsException + */ + protected function verify_header(GameQ_Buffer &$buffer) + { + // Check length + if($buffer->getLength() < 6) + { + throw new GameQ_ProtocolsException(__METHOD__.": Length of buffer is not long enough"); + return FALSE; + } + + // Check to make sure the header is correct + if(($type = trim($buffer->readString("\n"))) != '[TS]') + { + throw new GameQ_ProtocolsException(__METHOD__.": Header returned did not match. Returned type {$type}"); + return FALSE; + } + + // Verify the response and return + return $this->verify_response(trim($buffer->readString("\n"))); + } + + /** + * Verify the response for the specific entity + * + * @param string $response + * @throws GameQ_ProtocolsException + */ + protected function verify_response($response) + { + // Check the response + if($response != 'OK') + { + throw new GameQ_ProtocolsException(__METHOD__.": Header return response was no 'OK'. Returned response {$response}"); + return FALSE; + } + + return TRUE; + } +} diff --git a/web/third_party/gameq/gameq/protocols/teamspeak3.php b/web/third_party/gameq/gameq/protocols/teamspeak3.php new file mode 100644 index 00000000..17e7ee88 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/teamspeak3.php @@ -0,0 +1,372 @@ +. + */ + +/** + * Teamspeak 3 Protocol Class + * + * This class provides some functionality for getting status information for Teamspeak 3 + * servers. + * + * This code ported from GameQ v1. Credit to original author(s) as I just updated it to + * work within this new system. + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Teamspeak3 extends GameQ_Protocols +{ + /** + * Normalization for this protocol class + * + * @var array + */ + protected $normalize = array( + // General + 'general' => array( + 'dedicated' => array('dedicated'), + 'hostname' => array('virtualservername'), + 'password' => array('virtualserverflagpassword'), + 'numplayers' => array('virtualserverclientsonline'), + 'maxplayers' => array('virtualservermaxclients'), + 'players' => array('players'), + 'teams' => array('teams'), + ), + + // Player + 'player' => array( + 'name' => array('clientnickname'), + 'team' => array('clid'), + ), + + // Team + 'team' => array( + 'name' => array('channelname'), + ), + ); + + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_DETAILS => "use port=%d\x0Aserverinfo\x0A", + self::PACKET_PLAYERS => "use port=%d\x0Aclientlist\x0A", + self::PACKET_CHANNELS => "use port=%d\x0Achannellist -topic\x0A", + ); + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_details", + "process_channels", + "process_players", + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 9987; // Default port, used if not set when instanced + + /** + * Because Teamspeak is run like a master server we have to know what port we are really querying + * + * @var int + */ + protected $master_server_port = 10011; + + /** + * We have to use TCP connection + * + * @var string + */ + protected $transport = self::TRANSPORT_TCP; + + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'teamspeak3'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'teamspeak3'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Teamspeak 3"; + + /** + * Define the items being replaced to fix the return + * + * @var array + */ + protected $string_replace = array( + "\\\\" => "\\", + "\\/" => "/", + "\\s" => " ", + "\\p" => "|", + "\\;" => ";", + "\\a" => "\a", + "\\b" => "\b", + "\\f" => "\f", + "\\n" => "\n", + "\\r" => "\r", + "\\t" => "\t" + ); + + /** + * We need to affect the packets we are sending before they are sent + * + * @see GameQ_Protocols_Core::beforeSend() + */ + public function beforeSend() + { + // Let's loop the packets and set the proper pieces + foreach($this->packets AS $packet_type => $packet) + { + // Update the query port for the server + $this->packets[$packet_type] = sprintf($packet, $this->port); + } + + // Set the port we are connecting to the master port + $this->port = $this->master_server_port; + + return TRUE; + } + + + /* + * Internal methods + */ + + protected function preProcess($packets=array()) + { + // Create a buffer + $buffer = new GameQ_Buffer(implode("", $packets)); + + // Verify the header + $this->verify_header($buffer); + + return $buffer; + } + + /** + * Process the server information + */ + protected function process_details() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_DETAILS)) + { + return array(); + } + + // Let's preprocess the status + $buffer = $this->preProcess($this->packets_response[self::PACKET_DETAILS]); + + // Process the buffer response + $data = $this->parse_response($buffer); + + // Shift off the first item + $data = array_shift($data); + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Always dedicated + $result->add('dedicated', TRUE); + + // Loop the data and add it to the result + foreach($data AS $key => $value) + { + $result->add($key, $value); + } + + unset($data, $buffer, $key, $value); + + return $result->fetch(); + } + + /** + * Process the channel listing + */ + protected function process_channels() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_CHANNELS)) + { + return array(); + } + + // Let's preprocess the status + $buffer = $this->preProcess($this->packets_response[self::PACKET_CHANNELS]); + + // Process the buffer response + $data = $this->parse_response($buffer); + + // Set the result to a new result instance + $result = new GameQ_Result(); + + foreach ($data AS $channel) + { + $channel['channel_name'] = htmlentities($channel['channel_name'], ENT_QUOTES, "UTF-8"); + foreach ($channel AS $key => $value) + { + $result->addTeam($key, $value); + } + } + + unset($data, $buffer, $channel, $key, $value); + + return $result->fetch(); + } + + protected function process_players() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_PLAYERS)) + { + return array(); + } + + // Let's preprocess the status + $buffer = $this->preProcess($this->packets_response[self::PACKET_PLAYERS]); + + // Process the buffer response + $data = $this->parse_response($buffer); + + // Set the result to a new result instance + $result = new GameQ_Result(); + + foreach ($data AS $player) + { + // filter out query clients + if ($player['client_type'] == 1) + { + continue; + } + + $player['client_nickname'] = htmlentities($player['client_nickname'], ENT_QUOTES, "UTF-8"); + foreach ($player AS $key => $value) + { + $result->addPlayer($key, $value); + } + } + + unset($data, $buffer, $player, $key, $value); + + return $result->fetch(); + } + + + /** + * Verify the header of the returned response packet + * + * @param GameQ_Buffer $buffer + * @throws GameQ_ProtocolsException + */ + protected function verify_header(GameQ_Buffer &$buffer) + { + // Check length + if($buffer->getLength() < 6) + { + throw new GameQ_ProtocolsException(__METHOD__.": Length of buffer is not long enough"); + return FALSE; + } + + // Check to make sure the header is correct + if(($type = $buffer->readString("\n")) != 'TS3') + { + throw new GameQ_ProtocolsException(__METHOD__.": Header returned did not match. Returned {$type}"); + return FALSE; + } + + // Burn the welcome msg + $buffer->readString("\n"); + + // Verify the response and return + return $this->verify_response(trim($buffer->readString("\n"))); + } + + /** + * Verify the response for the specific entity + * + * @param string $response + * @throws GameQ_ProtocolsException + */ + protected function verify_response($response) + { + // Check the response + if($response != 'error id=0 msg=ok') + { + throw new GameQ_ProtocolsException(__METHOD__.": Header response was not ok. Response {$response}"); + return FALSE; + } + + return TRUE; + } + + /** + * Parse the buffer response into an array and return it + * + * @param GameQ_Buffer $buffer + */ + protected function parse_response(GameQ_Buffer &$buffer) + { + // The data is in the first block + $data = explode ('|', trim($buffer->readString("\n"))); + + // The response is the last block + $this->verify_response(trim($buffer->readString("\n"))); + + $return = array(); + + foreach ($data as $part) + { + $variables = explode (' ', $part); + + $info = array(); + + foreach ($variables as $variable) + { + // Explode and make sure we always have 2 items in the array + list($key, $value) = array_pad(explode('=', $variable, 2), 2, ''); + + $info[$key] = str_replace(array_keys($this->string_replace), array_values($this->string_replace), $value); + } + + // Add this to the return + $return[] = $info; + } + + return $return; + } +} diff --git a/web/third_party/gameq/gameq/protocols/teeworlds.php b/web/third_party/gameq/gameq/protocols/teeworlds.php new file mode 100644 index 00000000..27e6fd06 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/teeworlds.php @@ -0,0 +1,109 @@ +. + */ + +/** + * Teeworlds Protocol + * + * @author Marcel Bößendörfer + */ +class GameQ_Protocols_Teeworlds extends GameQ_Protocols { + + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_ALL => "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x67\x69\x65\x33\x05", + // 0.5 Packet (not compatible, maybe some wants to implement "Teeworldsold") + //self::PACKET_STATUS => "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFFgief", + ); + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_all" + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 8303; // Default port, used if not set when instanced + + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'teeworlds'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'teeworlds'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Teeworlds"; + + /* + * Internal methods + */ + + protected function process_all() { + if(!$this->hasValidResponse(self::PACKET_ALL)) + { + return array(); + } + $data = $this->packets_response[self::PACKET_ALL][0]; + $buf = new GameQ_Buffer($data); + $result = new GameQ_Result(); + $buf->readString(); + $result->add('version', $buf->readString()); + $result->add('hostname', $buf->readString()); + $result->add('map', $buf->readString()); + $result->add('game_descr', $buf->readString()); + $result->add('flags', $buf->readString()); // not use about that + $result->add('num_players', $buf->readString()); + $result->add('maxplayers', $buf->readString()); + $result->add('num_players_total', $buf->readString()); + $result->add('maxplayers_total', $buf->readString()); + + // Players + while ($buf->getLength()) { + $result->addPlayer('name', $buf->readString()); + $result->addPlayer('clan', $buf->readString()); + $result->addPlayer('flag', $buf->readString()); + $result->addPlayer('score', $buf->readString()); + $result->addPlayer('team', $buf->readString()); + } + return $result->fetch(); + } +} diff --git a/web/third_party/gameq/gameq/protocols/terraria.php b/web/third_party/gameq/gameq/protocols/terraria.php new file mode 100644 index 00000000..b1c4fe22 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/terraria.php @@ -0,0 +1,48 @@ +. + */ + +/** + * Terraria Protocol Class + * + * This class utilizes the Tshock protocol + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Terraria extends GameQ_Protocols_Tshock +{ + /** + * Default port for this server type + * + * @var int + */ + protected $port = 7878; // Default port, used if not set when instanced + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'terraria'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Terraria"; +} diff --git a/web/third_party/gameq/gameq/protocols/tf2.php b/web/third_party/gameq/gameq/protocols/tf2.php new file mode 100644 index 00000000..317a1bcb --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/tf2.php @@ -0,0 +1,28 @@ +. + */ + +/** + * Team Fortress 2 Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Tf2 extends GameQ_Protocols_Source +{ + protected $name = "tf2"; + protected $name_long = "Team Fortress 2"; +} diff --git a/web/third_party/gameq/gameq/protocols/tfc.php b/web/third_party/gameq/gameq/protocols/tfc.php new file mode 100644 index 00000000..62e3caba --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/tfc.php @@ -0,0 +1,28 @@ +. + */ + +/** + * Team Fortress Classic Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Tfc extends GameQ_Protocols_Source +{ + protected $name = "tfc"; + protected $name_long = "Team Fortress Classic"; +} diff --git a/web/third_party/gameq/gameq/protocols/tribes2.php b/web/third_party/gameq/gameq/protocols/tribes2.php new file mode 100644 index 00000000..2748d989 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/tribes2.php @@ -0,0 +1,204 @@ +. + */ + +/** + * Tribes 2 Protocol Class + * + * Code adapted from the original tribes2 class from GameQ v1 + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Tribes2 extends GameQ_Protocols +{ + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_INFO => "\x0E\x02\x01\x02\x03\x04", + self::PACKET_STATUS => "\x12\x02\x01\x02\x03\x04", + ); + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_info", + "process_status", + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 28000; // Default port, used if not set when instanced + + /** + * The query protocol used to make the call + * + * @var string + */ + protected $protocol = 'tribes2'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'tribes2'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Tribes 2"; + + /** + * Pre-process the server info data that was returned. + * + * @param array $packets + */ + protected function preProcess_info($packets) + { + // Process the packets + return implode('', $packets); + } + + /** + * Handles processing the info data into a usable format + * + * @throws GameQ_ProtocolsException + */ + protected function process_info() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_INFO)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Let's preprocess the rules + $data = $this->preProcess_info($this->packets_response[self::PACKET_INFO]); + + // Create a new buffer + $buf = new GameQ_Buffer($data); + + // Skip the header + $buf->skip(6); + + $result->add('version', $buf->readPascalString()); + + // We skip this next part but it contains protocol and build information, man I wish I had some docs... + $buf->skip(12); + + $result->add('hostname', $buf->readPascalString()); + + unset($buf, $data); + + return $result->fetch(); + } + + /** + * Pre-process the server status data that was returned. + * + * @param array $packets + */ + protected function preProcess_status($packets) + { + // Process the packets + return implode('', $packets); + } + + /** + * Handles processing the status data into a usable format + * + * @throws GameQ_ProtocolsException + */ + protected function process_status() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_STATUS)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Let's preprocess the rules + $data = $this->preProcess_status($this->packets_response[self::PACKET_STATUS]); + + // Create a new buffer + $buf = new GameQ_Buffer($data); + + // Skip the header + $buf->skip(6); + + $result->add('mod', $buf->readPascalString()); + $result->add('gametype', $buf->readPascalString()); + $result->add('map', $buf->readPascalString()); + + // Grab the flag + $flag = $buf->read(); + + $bit = 1; + foreach (array('dedicated', 'password', 'linux', + 'tournament', 'no_alias') as $var) + { + $value = ($flag & $bit) ? 1 : 0; + $result->add($var, $value); + $bit *= 2; + } + + $result->add('num_players', $buf->readInt8()); + $result->add('max_players', $buf->readInt8()); + $result->add('num_bots', $buf->readInt8()); + $result->add('cpu', $buf->readInt16()); + $result->add('info', $buf->readPascalString()); + + $buf->skip(2); + + // Do teams + $num_teams = $buf->read(); + $result->add('num_teams', $num_teams); + + $buf->skip(); + + for ($i = 0; $i < $num_teams; $i++) + { + $result->addTeam('name', $buf->readString("\x09")); + $result->addTeam('score', $buf->readString("\x0a")); + } + + // Do players + // @todo: No code here to do players, no docs either, need example server with players + + unset($buf, $data); + + return $result->fetch(); + } +} diff --git a/web/third_party/gameq/gameq/protocols/tshock.php b/web/third_party/gameq/gameq/protocols/tshock.php new file mode 100644 index 00000000..4e27ac65 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/tshock.php @@ -0,0 +1,131 @@ +. + */ + +/** + * Tshock Protocol Class + * + * Result from this call should be a header + JSON response + * + * References: + * - https://tshock.atlassian.net/wiki/display/TSHOCKPLUGINS/REST+API+Endpoints#RESTAPIEndpoints-/status + * - http://tshock.co/xf/index.php?threads/rest-tshock-server-status-image.430/ + * + * Special thanks to intradox and Ruok2bu for game & protocol references + * + * @author Austin Bischoff + */ +abstract class GameQ_Protocols_Tshock extends GameQ_Protocols_Http +{ + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_STATUS => "GET /status HTTP/1.0\r\nAccept: */*\r\n\r\n", + ); + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_status", + ); + + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'tshock'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'tshock'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Tshock"; + + /* + * Internal methods + */ + protected function preProcess_status($packets=array()) + { + // Implode and rip out the JSON + preg_match('/\{(.*)\}/ms', implode('', $packets), $m); + + return $m[0]; + } + + protected function process_status() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_STATUS)) + { + return array(); + } + + // Return should be JSON, let's validate + if(($json = json_decode($this->preProcess_status($this->packets_response[self::PACKET_STATUS]))) === NULL) + { + throw new GameQ_ProtocolsException("JSON response from Tshock protocol is invalid."); + } + + // Check the status response + if($json->status != 200) + { + throw new GameQ_ProtocolsException("JSON status from Tshock protocol response was '{$json->status}', expected '200'."); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Server is always dedicated + $result->add('dedicated', TRUE); + + // No mods, as of yet + $result->add('mod', FALSE); + + // These are the same no matter what mode the server is in + $result->add('hostname', $json->name); + $result->add('game_port', $json->port); + $result->add('numplayers', $json->playercount); + $result->add('maxplayers', 0); + + // Players are a comma(space) seperated list + $players = explode(', ', $json->players); + + // Do the players + foreach($players AS $player) + { + $result->addPlayer('name', $player); + } + + return $result->fetch(); + } +} diff --git a/web/third_party/gameq/gameq/protocols/unreal2.php b/web/third_party/gameq/gameq/protocols/unreal2.php new file mode 100644 index 00000000..dda98ed6 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/unreal2.php @@ -0,0 +1,302 @@ +. + */ + +/** + * Unreal 2 Protocol Class + * + * @author Austin Bischoff + */ +abstract class GameQ_Protocols_Unreal2 extends GameQ_Protocols +{ + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_DETAILS => "\x79\x00\x00\x00\x00", + self::PACKET_RULES => "\x79\x00\x00\x00\x01", + self::PACKET_PLAYERS => "\x79\x00\x00\x00\x02", + ); + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_details", + "process_rules", + "process_players", + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 1; // Default port, used if not set when instanced + + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'unreal2'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'unreal2'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Unreal 2"; + + /* + * Internal methods + */ + + /** + * Preprocess the server details packet(s) + * + * @param array $packets + */ + protected function preProcess_details($packets=array()) + { + // Only one return so no need for work + if(count($packets) == 1) + { + return substr($packets[0], 5); + } + + // Loop all the packets and rip off the header + foreach($packets AS $id => $packet) + { + $packets[$id] = substr($packet, 5); + } + + // Return the data appended + return implode('', $packets); + } + + protected function process_details() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_DETAILS)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Let's preprocess the rules + $data = $this->preProcess_details($this->packets_response[self::PACKET_DETAILS]); + + // Create a buffer + $buf = new GameQ_Buffer($data); + + $result->add('serverid', $buf->readInt32()); // 0 + $result->add('serverip', $buf->readPascalString(1)); // empty + $result->add('gameport', $buf->readInt32()); + $result->add('queryport', $buf->readInt32()); // 0 + $result->add('servername', $buf->readPascalString(1)); + $result->add('mapname', $buf->readPascalString(1)); + $result->add('gametype', $buf->readPascalString(1)); + $result->add('playercount', $buf->readInt32()); + $result->add('maxplayers', $buf->readInt32()); + $result->add('ping', $buf->readInt32()); // 0 + + unset($buf); + + // Return the result + return $result->fetch(); + } + + /** + * Preprocess the rules packet(s) + * + * @param array $packets + */ + protected function preProcess_rules($packets=array()) + { + // Only one return so no need for work + if(count($packets) == 1) + { + return substr($packets[0], 5); + } + + // Loop all the packets and rip off the header + foreach($packets AS $id => $packet) + { + $packets[$id] = substr($packet, 5); + } + + // Return the data appended + return implode('', $packets); + } + + /** + * Process the Rules packet(s) + */ + protected function process_rules() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_RULES)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Let's preprocess the rules + $data = $this->preProcess_rules($this->packets_response[self::PACKET_RULES]); + + // Make a new buffer + $buf = new GameQ_Buffer($data); + + // Named values + $i = -1; + while ($buf->getLength()) + { + $key = $buf->readPascalString(1); + + // Make sure mutators don't overwrite each other + if ($key === 'Mutator') + { + $key .= ++$i; + } + + $result->add($key, $buf->readPascalString(1)); + } + + unset($buf, $i, $key); + + // Return the result + return $result->fetch(); + } + + /** + * Preprocess the player packet(s) returned + * + * @param array $packets + */ + protected function preProcess_players($packets=array()) + { + // Only one return so no need for work + if(count($packets) == 1) + { + return substr($packets[0], 5); + } + + // Loop all the packets and rip off the header + foreach($packets AS $id => $packet) + { + $packets[$id] = substr($packet, 5); + } + + // Return the data appended + return implode('', $packets); + } + + /** + * Process the player return data + */ + protected function process_players() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_PLAYERS)) + { + return array(); + } + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Let's preprocess the rules + $data = $this->preProcess_players($this->packets_response[self::PACKET_PLAYERS]); + + // Make a new buffer + $buf = new GameQ_Buffer($data); + + // Parse players + while ($buf->getLength()) + { + + // Player id + if (($id = $buf->readInt32()) === 0) + { + break; + } + + $result->addPlayer('id', $id); + $result->addPlayer('name', $this->_readUnrealString($buf)); + $result->addPlayer('ping', $buf->readInt32()); + $result->addPlayer('score', $buf->readInt32()); + $buf->skip(4); + } + + unset($buf, $id); + + // Return the result + return $result->fetch(); + } + + /** + * Read an Unreal Engine 2 string + * + * Adapted from original GameQ code + * + * @param GameQ_Buffer $buf + * @return string + */ + private function _readUnrealString(GameQ_Buffer &$buf) + { + // Normal pascal string + if (ord($buf->lookAhead(1)) < 129) + { + return $buf->readPascalString(1); + } + + // UnrealEngine2 color-coded string + $length = ($buf->readInt8() - 128) * 2 - 3; + $encstr = $buf->read($length); + $buf->skip(3); + + // Remove color-code tags + $encstr = preg_replace('~\x5e\\0\x23\\0..~s', '', $encstr); + + // Remove every second character + // The string is UCS-2, this approximates converting to latin-1 + $str = ''; + for ($i = 0, $ii = strlen($encstr); $i < $ii; $i += 2) + { + $str .= $encstr{$i}; + } + + return $str; + } +} diff --git a/web/third_party/gameq/gameq/protocols/ut.php b/web/third_party/gameq/gameq/protocols/ut.php new file mode 100644 index 00000000..a51285b8 --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/ut.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Unreal Tournament Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Ut extends GameQ_Protocols_Gamespy +{ + protected $name = "ut"; + protected $name_long = "Unreal Tournament"; + + protected $port = 7778; +} diff --git a/web/third_party/gameq/gameq/protocols/ut2004.php b/web/third_party/gameq/gameq/protocols/ut2004.php new file mode 100644 index 00000000..12ad1cce --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/ut2004.php @@ -0,0 +1,30 @@ +. + */ + +/** + * Unreal Tournament 2004 Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Ut2004 extends GameQ_Protocols_Unreal2 +{ + protected $name = "ut2004"; + protected $name_long = "Unreal Tournament 2004"; + + protected $port = 7778; +} diff --git a/web/third_party/gameq/gameq/protocols/ut3.php b/web/third_party/gameq/gameq/protocols/ut3.php new file mode 100644 index 00000000..ad2918dd --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/ut3.php @@ -0,0 +1,77 @@ +. + */ + +/** + * Unreal Tournament 3 Protocol Class + * + * NOTE: The return from UT3 via the GameSpy 3 protocol is anything but consistent. You may + * notice different results even on the same server queried at different times. No real way to fix + * this problem currently. + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Ut3 extends GameQ_Protocols_Gamespy3 +{ + protected $name = "ut3"; + protected $name_long = "Unreal Tournament 3"; + + protected $port = 6500; + + /** + * Process all the data at once + * @see GameQ_Protocols_Gamespy3::process_all() + */ + protected function process_all() + { + // Run the parent but we need to change some data + $result = parent::process_all(); + + // Move some stuff around + $this->move_result($result, 'hostname', 'OwningPlayerName'); + $this->move_result($result, 'p1073741825', 'mapname'); + $this->move_result($result, 'p1073741826', 'gametype'); + $this->move_result($result, 'p1073741827', 'servername'); + $this->move_result($result, 'p1073741828', 'custom_mutators'); + $this->move_result($result, 'gamemode', 'open'); + $this->move_result($result, 's32779', 'gamemode'); + $this->move_result($result, 's0', 'bot_skill'); + $this->move_result($result, 's6', 'pure_server'); + $this->move_result($result, 's7', 'password'); + $this->move_result($result, 's8', 'vs_bots'); + $this->move_result($result, 's10', 'force_respawn'); + $this->move_result($result, 'p268435704', 'frag_limit'); + $this->move_result($result, 'p268435705', 'time_limit'); + $this->move_result($result, 'p268435703', 'numbots'); + $this->move_result($result, 'p268435717', 'stock_mutators'); + + // Put custom mutators into an array + if(isset($result['custom_mutators'])) + { + $result['custom_mutators'] = explode("\x1c", $result['custom_mutators']); + } + + // Delete some unknown stuff + $this->delete_result($result, array('s1','s9','s11','s12','s13','s14')); + + // Return the result + return $result; + } + + // UT3 Hack, yea I know it doesnt belong here. UT3 is such a mess it needs its own version of GSv3 + //$data = str_replace(array("\x00p1073741829\x00", "p1073741829\x00", "p268435968\x00"), '', $data); +} diff --git a/web/third_party/gameq/gameq/protocols/ventrilo.php b/web/third_party/gameq/gameq/protocols/ventrilo.php new file mode 100644 index 00000000..d7015bff --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/ventrilo.php @@ -0,0 +1,400 @@ +. + */ + +/** + * Ventrilo Protocol Class + * + * This class provides some functionality for getting status information for ventrilo + * servers. Note that a password is not required for versions >= 3.0.3 + * + * This code ported from GameQ v1. Credit to original author(s) as I just updated it to + * work within this new system. + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Ventrilo extends GameQ_Protocols +{ + /** + * Normalization for this protocol class + * + * @var array + */ + protected $normalize = array( + // General + 'general' => array( + 'dedicated' => array('dedicated'), + 'hostname' => array('name'), + 'password' => array('auth'), + 'numplayers' => array('clientcount'), + 'maxplayers' => array('maxclients'), + 'players' => array('players'), + 'teams' => array('teams'), + ), + + // Player + 'player' => array( + //'score' => array('score'), + ), + + // Team + 'team' => array( + //'score' => array('tickets'), + ), + ); + + /** + * Array of packets we want to look up. + * Each key should correspond to a defined method in this or a parent class + * + * @var array + */ + protected $packets = array( + self::PACKET_STATUS => "V\xc8\xf4\xf9`\xa2\x1e\xa5M\xfb\x03\xccQN\xa1\x10\x95\xaf\xb2g\x17g\x812\xfbW\xfd\x8e\xd2\x22r\x034z\xbb\x98", + ); + + /** + * Methods to be run when processing the response(s) + * + * @var array + */ + protected $process_methods = array( + "process_status", + ); + + /** + * Default port for this server type + * + * @var int + */ + protected $port = 3784; // Default port, used if not set when instanced + + /** + * Set to run in linear mode + * + * @var string + */ + protected $packet_mode = self::PACKET_MODE_LINEAR; + + /** + * The protocol being used + * + * @var string + */ + protected $protocol = 'ventrilo'; + + /** + * String name of this protocol class + * + * @var string + */ + protected $name = 'ventrilo'; + + /** + * Longer string name of this protocol class + * + * @var string + */ + protected $name_long = "Ventrilo"; + + /** + * Encryption table for the header + * + * @var array + */ + protected $head_encrypt_table = array( + 0x80, 0xe5, 0x0e, 0x38, 0xba, 0x63, 0x4c, 0x99, 0x88, 0x63, 0x4c, 0xd6, 0x54, 0xb8, 0x65, 0x7e, + 0xbf, 0x8a, 0xf0, 0x17, 0x8a, 0xaa, 0x4d, 0x0f, 0xb7, 0x23, 0x27, 0xf6, 0xeb, 0x12, 0xf8, 0xea, + 0x17, 0xb7, 0xcf, 0x52, 0x57, 0xcb, 0x51, 0xcf, 0x1b, 0x14, 0xfd, 0x6f, 0x84, 0x38, 0xb5, 0x24, + 0x11, 0xcf, 0x7a, 0x75, 0x7a, 0xbb, 0x78, 0x74, 0xdc, 0xbc, 0x42, 0xf0, 0x17, 0x3f, 0x5e, 0xeb, + 0x74, 0x77, 0x04, 0x4e, 0x8c, 0xaf, 0x23, 0xdc, 0x65, 0xdf, 0xa5, 0x65, 0xdd, 0x7d, 0xf4, 0x3c, + 0x4c, 0x95, 0xbd, 0xeb, 0x65, 0x1c, 0xf4, 0x24, 0x5d, 0x82, 0x18, 0xfb, 0x50, 0x86, 0xb8, 0x53, + 0xe0, 0x4e, 0x36, 0x96, 0x1f, 0xb7, 0xcb, 0xaa, 0xaf, 0xea, 0xcb, 0x20, 0x27, 0x30, 0x2a, 0xae, + 0xb9, 0x07, 0x40, 0xdf, 0x12, 0x75, 0xc9, 0x09, 0x82, 0x9c, 0x30, 0x80, 0x5d, 0x8f, 0x0d, 0x09, + 0xa1, 0x64, 0xec, 0x91, 0xd8, 0x8a, 0x50, 0x1f, 0x40, 0x5d, 0xf7, 0x08, 0x2a, 0xf8, 0x60, 0x62, + 0xa0, 0x4a, 0x8b, 0xba, 0x4a, 0x6d, 0x00, 0x0a, 0x93, 0x32, 0x12, 0xe5, 0x07, 0x01, 0x65, 0xf5, + 0xff, 0xe0, 0xae, 0xa7, 0x81, 0xd1, 0xba, 0x25, 0x62, 0x61, 0xb2, 0x85, 0xad, 0x7e, 0x9d, 0x3f, + 0x49, 0x89, 0x26, 0xe5, 0xd5, 0xac, 0x9f, 0x0e, 0xd7, 0x6e, 0x47, 0x94, 0x16, 0x84, 0xc8, 0xff, + 0x44, 0xea, 0x04, 0x40, 0xe0, 0x33, 0x11, 0xa3, 0x5b, 0x1e, 0x82, 0xff, 0x7a, 0x69, 0xe9, 0x2f, + 0xfb, 0xea, 0x9a, 0xc6, 0x7b, 0xdb, 0xb1, 0xff, 0x97, 0x76, 0x56, 0xf3, 0x52, 0xc2, 0x3f, 0x0f, + 0xb6, 0xac, 0x77, 0xc4, 0xbf, 0x59, 0x5e, 0x80, 0x74, 0xbb, 0xf2, 0xde, 0x57, 0x62, 0x4c, 0x1a, + 0xff, 0x95, 0x6d, 0xc7, 0x04, 0xa2, 0x3b, 0xc4, 0x1b, 0x72, 0xc7, 0x6c, 0x82, 0x60, 0xd1, 0x0d, + ); + + /** + * Encryption table for the data + * + * @var array + */ + protected $data_encrypt_table = array( + 0x82, 0x8b, 0x7f, 0x68, 0x90, 0xe0, 0x44, 0x09, 0x19, 0x3b, 0x8e, 0x5f, 0xc2, 0x82, 0x38, 0x23, + 0x6d, 0xdb, 0x62, 0x49, 0x52, 0x6e, 0x21, 0xdf, 0x51, 0x6c, 0x76, 0x37, 0x86, 0x50, 0x7d, 0x48, + 0x1f, 0x65, 0xe7, 0x52, 0x6a, 0x88, 0xaa, 0xc1, 0x32, 0x2f, 0xf7, 0x54, 0x4c, 0xaa, 0x6d, 0x7e, + 0x6d, 0xa9, 0x8c, 0x0d, 0x3f, 0xff, 0x6c, 0x09, 0xb3, 0xa5, 0xaf, 0xdf, 0x98, 0x02, 0xb4, 0xbe, + 0x6d, 0x69, 0x0d, 0x42, 0x73, 0xe4, 0x34, 0x50, 0x07, 0x30, 0x79, 0x41, 0x2f, 0x08, 0x3f, 0x42, + 0x73, 0xa7, 0x68, 0xfa, 0xee, 0x88, 0x0e, 0x6e, 0xa4, 0x70, 0x74, 0x22, 0x16, 0xae, 0x3c, 0x81, + 0x14, 0xa1, 0xda, 0x7f, 0xd3, 0x7c, 0x48, 0x7d, 0x3f, 0x46, 0xfb, 0x6d, 0x92, 0x25, 0x17, 0x36, + 0x26, 0xdb, 0xdf, 0x5a, 0x87, 0x91, 0x6f, 0xd6, 0xcd, 0xd4, 0xad, 0x4a, 0x29, 0xdd, 0x7d, 0x59, + 0xbd, 0x15, 0x34, 0x53, 0xb1, 0xd8, 0x50, 0x11, 0x83, 0x79, 0x66, 0x21, 0x9e, 0x87, 0x5b, 0x24, + 0x2f, 0x4f, 0xd7, 0x73, 0x34, 0xa2, 0xf7, 0x09, 0xd5, 0xd9, 0x42, 0x9d, 0xf8, 0x15, 0xdf, 0x0e, + 0x10, 0xcc, 0x05, 0x04, 0x35, 0x81, 0xb2, 0xd5, 0x7a, 0xd2, 0xa0, 0xa5, 0x7b, 0xb8, 0x75, 0xd2, + 0x35, 0x0b, 0x39, 0x8f, 0x1b, 0x44, 0x0e, 0xce, 0x66, 0x87, 0x1b, 0x64, 0xac, 0xe1, 0xca, 0x67, + 0xb4, 0xce, 0x33, 0xdb, 0x89, 0xfe, 0xd8, 0x8e, 0xcd, 0x58, 0x92, 0x41, 0x50, 0x40, 0xcb, 0x08, + 0xe1, 0x15, 0xee, 0xf4, 0x64, 0xfe, 0x1c, 0xee, 0x25, 0xe7, 0x21, 0xe6, 0x6c, 0xc6, 0xa6, 0x2e, + 0x52, 0x23, 0xa7, 0x20, 0xd2, 0xd7, 0x28, 0x07, 0x23, 0x14, 0x24, 0x3d, 0x45, 0xa5, 0xc7, 0x90, + 0xdb, 0x77, 0xdd, 0xea, 0x38, 0x59, 0x89, 0x32, 0xbc, 0x00, 0x3a, 0x6d, 0x61, 0x4e, 0xdb, 0x29, + ); + + /* + * Internal methods + */ + + /** + * Pre-process the server status data that was returned. + * + * @param array $packets + * @throws GameQ_ProtocolsException + */ + public function preProcess_status($packets=array()) + { + $packets_return = array(); + + foreach ($packets AS $packet) + { + # Header : + $header = substr ($packet, 0, 20); + $header_items = array (); + + $key = array_shift (unpack ("n1", $header)); + $chars = unpack ("C*", substr ($header, 2)); + + $a1 = $key & 0xFF; + $a2 = $key >> 8; + + if ($a1 == 0) + { + throw new GameQ_ProtocolsException(__METHOD__.": Header key is invalid"); + } + + $table = $this->head_encrypt_table; + + + $key = 0; + for( $i = 1; $i <= count( $chars ); $i++ ) + { + $chars[$i] -= ( $table[$a2] + (( $i - 1 ) % 5 )) & 0xFF; + $a2 = ($a2 + $a1) & 0xFF; + if ( ( $i % 2 ) == 0 ) + { + $short_array = unpack( "n1", pack( "C2", $chars[$i - 1], $chars[$i] )); + $header_items[$key] = $short_array[1]; + ++$key; + } + } + + $header_items = array_combine(array( + 'zero', + 'cmd', + 'id', + 'totlen', + 'len', + 'totpck', + 'pck', + 'datakey', + 'crc' + ), $header_items); + + // Check to make sure the number of packets match + if ($header_items['totpck'] != count($packets)) + { + throw new GameQ_ProtocolsException(__METHOD__.": Too less packets recieved"); + } + + # Data : + $table = $this->data_encrypt_table; + + $a1 = $header_items['datakey'] & 0xFF; + $a2 = $header_items['datakey'] >> 8; + + if ( $a1 == 0 ) + { + throw new GameQ_ProtocolsException(__METHOD__.": Data key is invalid"); + } + + $chars = unpack( "C*", substr ($packet, 20) ); + $data = ""; + for( $i = 1; $i <= count( $chars ); $i++ ) + { + $chars[$i] -= ($table[$a2] + (( $i - 1 ) % 72 )) & 0xFF; + $a2 = ($a2 + $a1) & 0xFF; + $data .= chr($chars[$i]); + } + + //@todo: Check CRC ??? + + $packets_return[$header_items['pck']] = $data; + } + + return implode('', $packets_return); + } + + /** + * Process the server status information + * + * @throws GameQ_ProtocolsException + */ + protected function process_status() + { + // Make sure we have a valid response + if(!$this->hasValidResponse(self::PACKET_STATUS)) + { + return array(); + } + + // Let's preprocess the status + $data = $this->preProcess_status($this->packets_response[self::PACKET_STATUS]); + + // Set the result to a new result instance + $result = new GameQ_Result(); + + // Explode the data into lines + $lines = explode("\n", $data); + + // Always dedicated + $result->add('dedicated', TRUE); + + // Loop the lines + foreach($lines AS $line) + { + // Trim all the outlying space + $line = trim($line); + + // We dont have anything in this line + if(strlen($line) == 0) + { + continue; + } + + /** + * Everything is in this format: ITEM: VALUE + * + * Example: + * ... + * MAXCLIENTS: 175 + * VOICECODEC: 3,Speex + * VOICEFORMAT: 31,32 KHz%2C 16 bit%2C 9 Qlty + * UPTIME: 9167971 + * PLATFORM: Linux-i386 + * VERSION: 3.0.6 + * ... + */ + + // Check to see if we have a colon, every line should + if(($colon_pos = strpos($line, ":")) !== FALSE && $colon_pos > 0) + { + // Split the line into key/value pairs + list($key, $value) = explode(':', $line, 2); // Only return 2 items in the array incase of colon in $value + + // Lower the font of the key + $key = strtolower($key); + + // Trim the value of extra space + $value = trim($value); + + // Switch and offload items as needed + switch($key) + { + case 'client': + $this->client($value, $result); + break; + + case 'channel': + $this->channel($value, $result); + break; + + // Ignore these + case 'channelfields': + case 'clientfields': + break; + + // By default we just add they key as an item + default: + $result->add($key, $this->convertSpecialChars($value)); + break; + } + } + } + + unset($key, $value, $line, $lines, $data); + + return $result->fetch(); + } + + /** + * Convert the special characters within a value + * + * @param string $data + * @return mixed + */ + protected function convertSpecialChars($data) + { + return preg_replace_callback( + '|%([0-9A-F]{2})|', + create_function( + '$matches', + 'return chr (hexdec ($matches[0]));' + ), + $data); + } + + /** + * Break up the channel items into usable parts + * + * @param string $data + * @param GameQ_Result $result + */ + protected function channel($data, GameQ_Result &$result) + { + $items = explode (",", $data); + foreach ($items as $item) + { + $temp = explode("=", $item); + $key = strtolower($temp[0]); + $value = $temp[1]; + $result->addTeam($key, $this->convertSpecialChars($value)); + } + } + + /** + * Break up the clients into usable parts + * + * @param string $data + * @param GameQ_Result $result + */ + protected function client($data, GameQ_Result &$result) + { + $items = explode(",", $data); + + foreach ($items as $item) + { + $temp = explode("=", $item); + $key = strtolower($temp[0]); + $value = $temp[1]; + $result->addPlayer($key, $this->convertSpecialChars($value)); + } + } +} diff --git a/web/third_party/gameq/gameq/protocols/warsow.php b/web/third_party/gameq/gameq/protocols/warsow.php new file mode 100644 index 00000000..c23207ec --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/warsow.php @@ -0,0 +1,71 @@ +. + */ + +/** + * Warsow Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Warsow extends GameQ_Protocols_Quake3 +{ + protected $name = "warsow"; + protected $name_long = "Warsow"; + + protected $port = 44400; + + /** + * Overload the parse players because the data coming back is different + * @see GameQ_Protocols_Quake3::parsePlayers() + */ + protected function parsePlayers(GameQ_Result &$result, $players_info) + { + // Explode the arrays out + $players = explode("\x0A", $players_info); + + // Remove the last array item as it is junk + array_pop($players); + + // Add total number of players + $result->add('num_players', count($players)); + + // Loop the players + foreach($players AS $player_info) + { + $buf = new GameQ_Buffer($player_info); + + // Add player info + $result->addPlayer('frags', $buf->readString("\x20")); + $result->addPlayer('ping', $buf->readString("\x20")); + + // Skip first " + $buf->skip(1); + + // Add player name + $result->addPlayer('name', trim($buf->readString('"'))); + + // Skip space + $buf->skip(1); + + // Add team + $result->addPlayer('team', $buf->read()); + } + + // Free some memory + unset($buf, $players, $player_info); + } +} diff --git a/web/third_party/gameq/gameq/protocols/zombiemaster.php b/web/third_party/gameq/gameq/protocols/zombiemaster.php new file mode 100644 index 00000000..b3d5357f --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/zombiemaster.php @@ -0,0 +1,28 @@ +. + */ + +/** + * Zombie Master Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Zombiemaster extends GameQ_Protocols_Source +{ + protected $name = "zombiemaster"; + protected $name_long = "Zombie Master"; +} diff --git a/web/third_party/gameq/gameq/protocols/zps.php b/web/third_party/gameq/gameq/protocols/zps.php new file mode 100644 index 00000000..994c3e7a --- /dev/null +++ b/web/third_party/gameq/gameq/protocols/zps.php @@ -0,0 +1,28 @@ +. + */ + +/** + * Zombie Panic Source Protocol Class + * + * @author Austin Bischoff + */ +class GameQ_Protocols_Zps extends GameQ_Protocols_Source +{ + protected $name = "zps"; + protected $name_long = "Zombie Panic Source"; +} diff --git a/web/third_party/gameq/gameq/result.php b/web/third_party/gameq/gameq/result.php new file mode 100644 index 00000000..362b0d2b --- /dev/null +++ b/web/third_party/gameq/gameq/result.php @@ -0,0 +1,117 @@ +. + */ + +/** + * Provide an interface for easy storage of a parsed server response + * + * @author Aidan Lister + * @author Tom Buskens + */ +class GameQ_Result +{ + /** + * Formatted server response + * + * @var array + */ + protected $result = array(); + + /** + * Adds variable to results + * + * @param string $name Variable name + * @param string $value Variable value + */ + public function add($name, $value) + { + $this->result[$name] = $value; + } + + /** + * Adds player variable to output + * + * @param string $name Variable name + * @param string $value Variable value + */ + public function addPlayer($name, $value) + { + $this->addSub('players', $name, $value); + } + + /** + * Adds player variable to output + * + * @param string $name Variable name + * @param string $value Variable value + */ + public function addTeam($name, $value) + { + $this->addSub('teams', $name, $value); + } + + /** + * Add a variable to a category + * + * @param $sub string The category + * @param $key string The variable name + * @param $value string The variable value + */ + public function addSub($sub, $key, $value) + { + // Nothing of this type yet, set an empty array + if (!isset($this->result[$sub]) or !is_array($this->result[$sub])) { + $this->result[$sub] = array(); + } + + // Find the first entry that doesn't have this variable + $found = false; + for ($i = 0; $i != count($this->result[$sub]); $i++) { + if (!isset($this->result[$sub][$i][$key])) { + $this->result[$sub][$i][$key] = $value; + $found = true; + break; + } + } + + // Not found, create a new entry + if (!$found) { + $this->result[$sub][][$key] = $value; + } + } + + /** + * Return all stored results + * + * @return mixed All results + */ + public function fetch() + { + return $this->result; + } + + /** + * Return a single variable + * + * @param string $var The variable name + * @return mixed The variable value + */ + public function get($var) + { + return isset($this->result[$var]) ? $this->result[$var] : null; + } +}