Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- helps --
- require'glsock'
- local errd={}
- for k,v in pairs(_G) do
- if k:find("GLSOCK_") then
- errd[v]=k:match("GLSOCK_ERROR_(.+)") or k
- end
- end
- local mod = math.fmod
- local floor = math.floor
- local function rshift(x,n)
- return floor((x%4294967296)/2^n)
- end
- local function band(x,y)
- local z,i,j = 0,1
- for j = 0,31 do
- if (mod(x,2)==1 and mod(y,2)==1) then
- z = z + i
- end
- x = rshift(x,1)
- y = rshift(y,1)
- i = i*2
- end
- return z
- end
- local function err(id) local name=errd[id]
- name=name and name or "UNKNOWN???"
- return name..'('..tostring(id)..')' end
- local Err=function(...)
- Msg"[SrvQuery]"print(...)
- end
- local function TOIP(x)
- return x and x[5] and tonumber(x[5]) and x[1].."."..x[2].."."..x[3].."."..x[4]..":"..x[5]
- end
- local c=string.char
- local A2S_SERVERQUERY_GETCHALLENGE=c(0xFF)..c(0xFF)..c(0xFF)..c(0xFF)..c(0x57)
- local A2S_SERVERQUERY_GETCHALLENGE_HACK=c(0xFF)..c(0xFF)..c(0xFF)..c(0xFF)..c(0x55)..c(0xFF)..c(0xFF)..c(0xFF)..c(0xFF)
- local A2S_INFO=c(0xFF)..c(0xFF)..c(0xFF)..c(0xFF)..c(0x54).."Source Engine Query"..c(0x00)
- local A2A_PING=c(0x69)
- local A2S_PLAYER=c(0xFF)..c(0xFF)..c(0xFF)..c(0xFF)..c(0x55)
- local A2S_RULES=c(0xFF)..c(0xFF)..c(0xFF)..c(0xFF)..c(0x56)
- local valid={
- ["A2S_SERVERQUERY_GETCHALLENGE"]=A2S_SERVERQUERY_GETCHALLENGE,
- ["A2S_INFO"]=A2S_INFO,
- ["A2A_PING"]=A2A_PING,
- ["A2S_PLAYER"]=A2S_PLAYER,
- ["A2S_RULES"]=A2S_RULES
- }
- ServerInfo = ServerInfo or {Queries=valid}
- local socket
- if ServerInfo.socket then
- print"Resetting ServerInfo"
- ServerInfo.socket:Cancel()
- socket=ServerInfo.socket
- end
- ------------------------------
- ServerInfo.Servers=ServerInfo.Servers or {}
- ------------------------------
- local srvinfo=ServerInfo.Servers
- local function SrvData(ip,port)
- if not port then
- ip,port = ip:match("(%d+.%d+.%d+.%d+)%:?(%d*)")
- end
- port = tonumber(port) or 27015
- if not ip then error"invalid ip" end
- if port<=0 or port>=65535 then error"invalid port" end
- srvinfo[port]=srvinfo[port] or {}
- srvinfo[port][ip] = srvinfo[port][ip] or {ip,port,{},{}}
- local data = srvinfo[port][ip]
- return data
- end
- local function GetSrvData(ip,port)
- if not port then
- ip,port = ip:match("(%d+.%d+.%d+.%d+)%:?(%d*)")
- if not ip then error"invalid ip" end
- end
- port = tonumber(port) or 27015
- if port<=0 or port>=65535 then error"invalid port" end
- return srvinfo[port] and srvinfo[port][ip]
- end
- ServerInfo.Find=GetSrvData
- ------------------------------
- local function ProcessPing(buf,ip,port,data)
- print("ProcessPing for",ip,port)
- end
- local minusone=c(0xFF)..c(0xFF)..c(0xFF)..c(0xFF)
- local function GetSendBuffer(info,challenge)
- info = info == A2S_SERVERQUERY_GETCHALLENGE and A2S_SERVERQUERY_GETCHALLENGE_HACK or info
- local buffer = GLSockBuffer()
- local sanity = buffer:Write(info)
- assert((info):len()==sanity)
- if info==A2S_SERVERQUERY_GETCHALLENGE_HACK then
- return buffer
- end
- if ( info==A2S_RULES or info==A2S_PLAYER ) then
- print("\tAppending challenge ("..challenge..") to SendBuffer")
- if challenge then
- buffer:WriteLong(challenge)
- else
- buffer:Write(minusone)
- end
- end
- return buffer
- end
- local function ProcessChallenge(buf,ip,port,data)
- print("ProcessChallenge for",ip,port)
- local _,challenge=buf:ReadLong()
- print("\t","Challenge="..challenge)
- if data.challenge then
- print("\t","Old Challenge="..data.challenge)
- end
- data.challenge = challenge
- end
- local function ProcessInfo(buf,ip,port,data)
- print("ProcessInfo for",ip,port)
- local _,version = buf:ReadByte( )
- local _,hostname = buf:ReadString( )
- local _,map = buf:ReadString( )
- local _,gamedirectory = buf:ReadString( )
- local _,gamedescription = buf:ReadString( )
- local _,shortappid = buf:ReadShort( )
- local _,numplayers = buf:ReadByte( )
- local _,maxplayers = buf:ReadByte( )
- local _,numofbots = buf:ReadByte( )
- local _,dedicated = buf:ReadByte( )
- local _,os = buf:ReadByte( )
- local _,password = buf:ReadByte( )
- local _,secure = buf:ReadByte( )
- local _,gameversion = buf:ReadString( )
- local _,extra = buf:ReadByte( )
- local _,gameport = band(extra,0x80)==0x80
- if gameport then _,gameport = buf:ReadShort( ) end
- local steamid = band(extra,0x10)==0x10
- if steamid then steamid = {select(2,buf:ReadLong( )),select(2,buf:ReadLong( ))} end
- local spectport = band(extra,0x40)==0x40
- if spectport then _,spectport = buf:ReadShort( ) end
- local spectname = band(extra,0x40)==0x40
- if spectname then _,spectname = buf:ReadString( ) end
- local gametag = band(extra,0x20)==0x20
- if gametag then _,gametag = buf:ReadString( ) end
- local gameid = band(extra,0x01)==0x01
- if gameid then gameid = {select(2,buf:ReadLong( )),select(2,buf:ReadLong( ))} end
- local PSSQ_INFO_REPLY = {
- ["version"] = version,
- ["hostname"] = hostname,
- ["map"] = map,
- ["gamedirectory"] = gamedirectory,
- ["gamedescription"] = gamedescription,
- ["shortappid"] = shortappid,
- ["numplayers"] = numplayers,
- ["maxplayers"] = maxplayers,
- ["numofbots"] = numofbots,
- ["dedicated"] = c(dedicated),
- ["os"] = c(os),
- ["password"] = password,
- ["secure"] = secure,
- ["gameversion"] = gameversion,
- ["extra"] = extra,
- ["gameport"] = gameport,
- ["steamid"] = steamid,
- ["spectport"] = spectport,
- ["spectname"] = spectname,
- ["gametag"] = gametag,
- ["gameid"] = gameid,
- }
- Msg"PSSQ_INFO_REPLY = "
- PrintTable(PSSQ_INFO_REPLY)
- end
- local function ProcessPlayers(buf,ip,port,data)
- print("ProcessPlayers for",ip,port)
- local _,NumPlayers=buf:ReadShort()
- print("\tNumPlayers="..NumPlayers)
- local i=1
- local playerstbl={}
- while i<=NumPlayers do
- local _,id=buf:ReadByte()
- local _,name=buf:ReadString()
- local _,kills=buf:ReadLong()
- local _,playtime=buf:ReadFloat()
- if _<1 then Err"players parsing failed, too little data?" break end
- playerstbl[i]={
- id=id,
- name=name,
- kills=kills,
- playtime=playtime
- }
- i=i+1
- end
- print("\tplayers tbl="..table.Count(playerstbl))
- Msg"players = "
- PrintTable(playerstbl)
- end
- local function ProcessRules(buf,ip,port,data)
- print("ProcessRules for",ip,port)
- local _,rules=buf:ReadShort()
- print("\tRules="..rules)
- local i=1
- local rulestbl={}
- while i<=rules do
- local _,k=buf:ReadString()
- local _,v=buf:ReadString()
- if _<1 then Err"rules parsing failed, too little data?" break end
- rulestbl[k]=v
- i=i+1
- end
- print("\tRules tbl="..table.Count(rulestbl))
- MsgN"Rules: "
- for k,v in pairs(rulestbl) do
- if k:find("toolmode",1,true) then continue end
- MsgN(k.." = "..v)
- end
- end
- local PROCESS_RECEIVED_STUFF=PROCESS_RECEIVED_STUFF
- local function JumpTo(b5,...)
- if b5==0x41 then
- return ProcessChallenge(...) or true
- elseif b5==0x49 then
- return ProcessInfo(...) or true
- elseif b5==0x44 then
- return ProcessPlayers(...) or true
- elseif b5==0x45 then
- return ProcessRules(...) or true
- end
- return false
- end
- local function CombineSplit(split)
- local buf = GLSockBuffer()
- local lastk=0
- for k,v in pairs(split) do
- Msg"."
- if k-1!=lastk then
- Err"CombineSplit received missing packets. Aborting!"
- return
- end
- local safe=0
- while safe and safe<8192 do safe=safe+1
- local read,byte=v:ReadByte()
- if read==0 then
- break
- end
- local wrote=buf:WriteByte(byte)
- assert(wrote>0)
- end
- lastk=k
- end
- buf:Seek(0,GLSOCKBUFFER_SEEK_SET)
- return buf
- end
- local function ProcessSplit(buf,ip,port,data)
- -- print("Split from",ip)
- local _,b2=buf:ReadByte()
- local _,b3=buf:ReadByte()
- local _,b4=buf:ReadByte()
- if b2!=0xFF or b3!=0xFF or b4!=0xFF then
- Err("Split packet weird data from",ip,port,"Bytes: ",0xFE,b2,b3,b4 )
- end
- local _,ReqID=buf:ReadLong()
- if _<0 then Err"Split packet Unexpected EOF" return end
- local bz2=ReqID>=32768
- if bz2 then
- --ReqID=ReqID-32768
- end
- local _,NumPackets=buf:ReadByte()
- if _<0 then Err"Split packet Unexpected EOF" return end
- local _,Packet=buf:ReadByte()
- if _<0 then Err"Split packet Unexpected EOF" return end
- local _,SplitLen=buf:ReadShort()
- if _<0 then Err"Split packet Unexpected EOF" return end
- /*Err(" DBG: "..
- " ID: "..ReqID..(bz2 and " (COMPRESSED) " or "")..
- " Packet: "..(Packet+1)..
- "/"..NumPackets..
- " Len: "..SplitLen
- )*/
- if bz2 then
- -- TODO -- :(
- Err("Response from ",ip,port," uses compression. No BZ2 in GMod. Byebye!")
- return
- end
- local splits=data[3]
- if Packet==0 then
- splits[ReqID]={}
- end
- local split=splits[ReqID]
- split[Packet+1]=buf
- if Packet+1==NumPackets then
- print("\tReceived final split packet, combining and sending to processor")
- local buf = CombineSplit(split)
- --splits[ReqID]=nil
- PROCESS_RECEIVED_STUFF(buf,ip,port,data,true)
- end
- end
- function PROCESS_RECEIVED_STUFF(buf,ip,port,data,processing_split)
- --MsgN("RECV SIZE ",buf:Size())
- if buf:Size()<1 then
- Err("Received weird buffer",buf:Size(),"from",ip,port)
- return
- end
- local _,b1=buf:ReadByte()
- if b1==0x6A then
- return ProcessPing(buf,ip,port,data)
- elseif b1==0xFE then
- return ProcessSplit(buf,ip,port,data)
- elseif b1==0xFF then
- -- normal
- else
- print("Potential weirdness incoming! byte 1 was",b1)
- end
- local _,b2=buf:ReadByte()
- local _,b3=buf:ReadByte()
- local _,b4=buf:ReadByte()
- if buf:EOB() then
- Err("Invalid Buffer size (",buf:Size(),") for this packet type from",ip,port,"Bytes: ",b1,b2,b3,b4 )
- return
- end
- if b2!=0xFF or b3!=0xFF or b4!=0xFF then
- Err("Received weird data from",ip,port,"Bytes: ",b1,b2,b3,b4 )
- end
- if b4!=0xFF then
- Err("Received weird data from",ip,port,"Bytes (4th is faulty): ",b1,b2,b3,b4 )
- return
- end
- local _,b5=buf:ReadByte()
- if JumpTo(b5,buf,ip,port,data) then return end
- Err("Received unknown respose from",ip,port,"ID: ",b5 )
- end
- local function onread(self, address, port, buffer, errno)
- if errno ~= GLSOCK_ERROR_SUCCESS then Err("READ ERROR: " .. err(errno)) return end
- --print("Received data: ",address,port)
- local data = GetSrvData(address,port)
- if not data then
- Err("No data found for ",address,port)
- end
- PROCESS_RECEIVED_STUFF(buffer,address,port,data)
- socket:ReadFrom(1500, onread)
- end
- local SendData = function (data,info,ip,port,challenge)
- local buffer = GetSendBuffer(info,challenge)
- socket:SendTo(buffer, ip, port, function(self, bytes, errno)
- if errno==GLSOCK_ERROR_SUCCESS then return end
- Err("Send failed: "..err(errno))
- end)
- end
- local startedreading=false
- ------------------------------------------------
- function ServerInfo.Get (server,info)
- ------------------------------------------------
- -- init socket
- if not socket then
- ServerInfo.socket = ServerInfo.socket or GLSock(GLSOCK_TYPE_UDP)
- socket = ServerInfo.socket
- end
- info=info==true and A2S_INFO or info
- local infotype for k,v in pairs(ServerInfo.Queries) do if v==info then infotype = k break end end
- if not info or not infotype then
- error("invalid info data parameter",2)
- end
- local ip,port = (server or ""):match("(%d+.%d+.%d+.%d+)%:?(%d*)")
- if not ip then error"invalid address" end
- local port = tonumber(port) or 27015
- if port<=0 or port>=65535 then error"invalid port" end
- print("Sending ",infotype,"to",ip..":"..port)
- local data = GetSrvData(ip,port)
- local challenge = data and data.challenge
- if ( info==A2S_RULES or info==A2S_PLAYER ) and not challenge then
- Err("Tried to request data without valid challenge, cancelling")
- return false
- end
- if not data then
- print"\tAssigning data for new server"
- data = SrvData(ip,port)
- end
- assert(data!=nil)
- assert(GetSrvData(ip,port)!=nil)
- assert(data==GetSrvData(ip,port))
- if not startedreading then
- startedreading = true
- --print"SOCK: starting reading"
- socket:ReadFrom(1500, onread)
- end
- SendData(data,info,ip,port,challenge)
- return true
- end
Advertisement
Add Comment
Please, Sign In to add comment