First of all you all might wonder why another socket library that will eventually crash anyway, well I'm using the Boost.asio library here to help my self for cross platform compatibility.
Also what I didn't like about the other modules is that they don't provide individual callbacks per asynchronous operation such as reading, sending and so on. I've been working quite a while on this and wanted to make sure its not that buggy on the initial release. I also kinda struggled at the beginning with the callbacks as I was using multiple threads and the locking didn't work that well then I took a look at OOSocks Src and found out that it uses a Think hook to do polling and such, thanks for the hint!
[B]Examples[/B]
[URL="http://code.google.com/p/mattmodules/source/browse/trunk/gm_glsock/examples/acceptor.lua"]Acceptor Socket[/URL]
[URL="http://code.google.com/p/mattmodules/source/browse/trunk/gm_glsock/examples/tcp.lua"]TCP[/URL]
[URL="http://code.google.com/p/mattmodules/source/browse/trunk/gm_glsock/examples/read_until.lua"]Read Until
[/URL]
As you can see each call has pretty much its own callback which makes your code more dynamic. You aren't bound to single callback doing all decisions in it.
If you are using this on Linux and you get some error of glibc stuff missing then delete your libstdc++.6.so in your orangebox/bin directory, if your srcds fails after that then I can't/wont help :( I kinda hate Linux development so my apologies.
You will find documentation on the SVN Link below and the latest binaries. Also please report all bugs with a snippet of the used lua code so I can debug the library on my VM.
[URL="https://code.google.com/p/mattmodules/source/browse/trunk/gm_glsock/Release/"]https://code.google.com/p/mattmodules/source/browse/trunk/gm_glsock/Release/
[/URL]
[B]GM13 Beta Builds[/B]
Windows: [URL]https://dl.dropbox.com/u/29407915/gm_glsock_gm13.rar[/URL]
Linux: [URL="http://g.metastruct.uk.to/%7Esrcds/modules/gm_glsock_linux.dll"]http://g.metastruct.uk.to/~srcds/modules/gm_glsock_linux.dll[/URL]
Might break again and again and again, pretty much depends on garry.
Definitely an improvement. I will let you know if I have any problems.
Heres another example that is also being used and working.
[lua]
function EncodeLua(code)
local buf = ""
for i=1, #code do
buf = buf .. string.format("%02X", string.byte(code[i]))
end
return buf
end
function DecodeLua(code)
local buf = ""
local i = 1
while i < #code do
local hex = code[i] .. code[i + 1]
local n = tonumber(hex, 16)
buf = buf .. string.char(n)
i = i + 2
end
return buf
end
if SERVER then
require 'glsock'
function ParseHTTPField(header, field)
field = field .. ": "
local i = string.find(header, field)
if i == 0 then
return nil
end
local sub = string.sub(header, i + #field)
i = string.find(sub, "\r\n")
if i != 0 then
sub = string.sub(sub, 1, i)
end
return sub
end
function ProcessHeader(header)
--print(header)
local authtoken = ParseHTTPField(header, "Token")
local code = ParseHTTPField(header, "Code")
local ply = ParseHTTPField(header, "Player")
print("Player: "..ply)
print("Auth Token: "..authtoken)
if( !authtoken or !code or !ply ) then
return
end
ply = player.GetByUniqueID(string.Trim(ply))
if !ply then
print("Invalid Player")
return
end
if( !ply.LuaAuthToken or !luadev.IsPlayerAllowed(ply) ) then
print("No permissions")
return
end
if( tostring(ply.LuaAuthToken) != tostring(string.Trim(authtoken)) ) then
print("Auth Token mismatch (" .. tostring(ply.LuaAuthToken) .. " - " .. tostring(authtoken) .. ")")
return
end
code = DecodeLua(string.Trim(code))
print("LuaWebServer("..tostring(ply).."): "..code)
luadev.RunOnServer(code, ply)
end
function OnReadHeader(sck, data, errno)
if( errno == GLSOCK_ERROR_SUCCESS ) then
local read, headerdata = data:Read(data:Size())
ProcessHeader(headerdata)
end
end
function ProcessClient(client)
client:ReadUntil("\r\n\r\n", OnReadHeader)
end
function OnAccept(sck, client, errno)
if( errno == GLSOCK_ERROR_SUCCESS ) then
print("New Connection: "..tostring(client))
ProcessClient(client)
sck:Accept(OnAccept)
else
print("Accept failed: "..errno)
end
end
function OnListen(sck, errno)
if( errno == GLSOCK_ERROR_SUCCESS ) then
print("Socket Listening")
sck:Accept(OnAccept)
else
print("Failed to listen: "..errno)
end
end
function OnBind(sck, errno)
if( errno == GLSOCK_ERROR_SUCCESS ) then
print("Bound to port 6678")
sck:Listen(10, OnListen)
else
print("Failed to bind port 6678: "..errno)
end
end
local acceptor = GLSock(GLSOCK_TYPE_ACCEPTOR)
acceptor:Bind("", 6678, OnBind)
function _R.Player:SendLuaAuth()
local host = GetConVarString("ip")
local port = 6678
if( luadev.IsPlayerAllowed(self) ) then
self.LuaAuthToken = util.CRC(math.random()..self:UniqueID()..CurTime())
umsg.Start("luaauthtoken", self);
umsg.String(self.LuaAuthToken)
umsg.String(host..":"..port)
umsg.End()
else
self.LuaAuthToken = nil
end
end
timer.Simple(1, function()
for k,v in pairs(player.GetAll()) do
v:SendLuaAuth()
end
end)
else
usermessage.Hook("luaauthtoken", function(data)
local ply = LocalPlayer()
ply.LuaAuthToken = data:ReadString()
ply.LuaWebServer = data:ReadString()
print("Received Lua Auth: "..ply.LuaAuthToken .. "(" .. ply.LuaWebServer .. ")")
end);
function _R.Player:SendLuaCode(code)
local headers = "Accept: */*\r\n"
headers = headers .. "Token: " .. self.LuaAuthToken .. "\r\n"
headers = headers .. "Player: " .. self:UniqueID() .. "\r\n"
headers = headers .. "Code: " .. EncodeLua(code) .."\r\n"
if( self.LuaWebServer and self.LuaAuthToken ) then
http.Get(self.LuaWebServer, headers, print)
end
end
end
[/lua]
Well not really used but I did this to test how far I could go :)
Edit: I just fixed a critical bug when calling Close()/Cancel() when the socket was already closed/cancelled it was crashing. Thats now fixed, also I added Destroy() for instant cleanup, when called the socket handle is closed and will be no longer valid.
Oh cool, you did what I wanted to eventually do +1
[lua]module("http2", package.seeall)
require'glsock'
function ParseURL(url)
local i = 0
local urltype = ""
i = string.find(url, "://")
if( i ) then
urltype = string.sub(url, 1, i - 1)
url = string.sub(url, i + 3)
end
local host = ""
local port = ""
local path = ""
local user = ""
local pass = ""
i = string.find(url, "/")
if( i ) then
host = string.sub(url, 1, i - 1)
path = string.sub(url, i)
else
host = url
path = "/"
end
i = string.find(url, "@")
if( i ) then
local login = string.sub(url, 1, i - 1)
url = string.sub(url, i + 1)
i = string.find(login, ":")
if( i ) then
user = string.sub(login, 1, i - 1)
pass = string.sub(login, i + 1)
else
user = login
end
end
i = string.find(host, ":")
if( i ) then
port = string.sub(host, i + 1)
port = tonumber(port)
host = string.sub(host, 1, i - 1)
else
port = 80
end
local res = {
protocol = urltype,
user = user,
pass = pass,
host = host,
port = port,
path = path
}
return res
end
function Get(url, headers, cb, ...)
local sock = GLSock(GLSOCK_TYPE_TCP)
local urldata = ParseURL(url)
local cbargs = {...}
local buffer = ""
PrintTable(urldata)
function FinishCallback()
cb(buffer, #buffer, cbargs)
sock:Destroy()
end
local function OnDataRead(sck, data, errno)
if( data and data:Size() > 0 ) then
local len, buf = data:Read(data:Size())
//print(buf)
buffer = buffer .. buf
end
if( errno != GLSOCK_ERROR_SUCCESS ) then
FinishCallback()
return
end
sck:Read(1024, OnDataRead)
end
local function OnHeaderRead(sck, data, errno)
if( errno != GLSOCK_ERROR_SUCCESS ) then
FinishCallback()
return
end
//print("Received Header")
local len, headerdata = data:Read(data:Size())
local headers = string.Explode("\r\n", headerdata)
local statusline = headers[1]
if( !statusline ) then
FinishCallback()
return
end
/*
local statusversion = ""
local statuscode = ""
local statusmsg = ""
local i = 0
i = string.find(statusline, " ")
if( i ) then
statusversion = string.sub(statusline, 1, i - 1)
i = string.find(statusline, " ")
if( i ) then
statuscode = string.sub(statusline, i + 1)
i = string.find(statuscode, " ")
if( i ) then
statusmsg = string.sub(statuscode, i + 1)
statuscode = string.sub(statuscode, i - 1)
else
print("Status Msg parsing failed")
FinishCallback()
return
end
else
print("Status Code parsing failed")
FinishCallback()
return
end
else
print("Version parsing failed")
FinishCallback()
return
end
*/
// TODO: If 302 Moved then follow redirect
//print(statusversion.." "..statuscode.." "..statusmsg)
// Read Data
sck:Read(1024, OnDataRead)
end
local function OnSend(sck, size, errno)
if( errno != GLSOCK_ERROR_SUCCESS ) then
FinishCallback()
return
end
sck:ReadUntil("\r\n\r\n", OnHeaderRead)
end
local function OnConnect(sck, errno)
if( errno != GLSOCK_ERROR_SUCCESS ) then
FinishCallback()
return
end
print("Connected to Host")
local header = GLSockBuffer()
header:Write("GET ".. urldata.path .." HTTP/1.1\r\n")
header:Write("Host: ".. urldata.host ..":".. tostring(urldata.port) .. "\r\n")
header:Write("Connection: Close\r\n")
if( #headers > 0 ) then
header:Write(headers)
end
header:Write("\r\n")
sck:Send(header, OnSend)
end
sock:Connect(urldata.host, urldata.port, OnConnect)
end
Get("http://google.com", "", function(content, size)
print(size)
end)[/lua]
And haza55, maybe we can work together on this one, OOSocks was the initial reason why I started this.
Oh wait
I haven't posted in this thread yet saying this is awesome cakes.
It's awesome :D
[QUOTE=Zeh Matt;33268282]
And haza55, maybe we can work together on this one, OOSocks was the initial reason why I started this.[/QUOTE]
Yeah maybe. Was really disappointed with OOSocks, which I had to push it out in a few hours because my server depended on it at the time.
Can you post a example of UDP usage? Can't seem to get it to work.
Here you go, a damn messy but working server query script:
[lua]-- 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
My Android app will be a documented example of UDP in glsock.
You will be able to read the client code (Android side) as well.
As promised:
[thread=1144836]GLSock UDP example.[/thread]
I'm surprised no one else has chimed in this thread about how all the code posts are using globals when not necessary. This is bad and sloppy coding practice, ask any working professional.
Python seems to be the only one with at least a concept of the idea of local versus global.
[QUOTE=agmike;33563087]I'm surprised no one else has chimed in this thread about how all the code posts are using globals when not necessary. This is bad and sloppy coding practice, ask any working professional.
Python seems to be the only one with at least a concept of the idea of local versus global.[/QUOTE]
Theres no disadvantage using global functions, just tells me you dont know anything about lua internals.
[QUOTE=Zeh Matt;33563522]Theres no disadvantage using global functions, just tells me you dont know anything about lua internals.[/QUOTE]
maybe you should read up on what locals are.
[b]It is good programming style to use local variables whenever possible. Local variables help you avoid cluttering the global environment with unnecessary names. Moreover, the access to local variables is faster than to global ones.[/b]
[url]http://www.lua.org/pil/4.2.html[/url]
[QUOTE=_Chewgum;33563683]maybe you should read up on what locals are.
[b]It is good programming style to use local variables whenever possible. Local variables help you avoid cluttering the global environment with unnecessary names. Moreover, the access to local variables is faster than to global ones.[/b]
[url]http://www.lua.org/pil/4.2.html[/url][/QUOTE]
I'm aware of that, I just found the statement funny: "This is bad and sloppy coding practice, ask any working professional."
Also thanks for going totally off-topic, coding style wasnt really the discussion
[QUOTE=agmike;33563087]I'm surprised no one else has chimed in this thread about how all the code posts are using globals when not necessary. This is bad and sloppy coding practice, ask any working professional.[/quote]
It's a simple example showing how the module is to be used. Coding conventions are not very important in an example. Besides, the difference in performance is minimal and name clashing is rare. On top of that, one can assume that one who knows C++ can easily grasp the difference between global and local variables.
[QUOTE=agmike;33563087]
Python seems to be the only one with at least a concept of the idea of local versus global.[/QUOTE]
I don't think you know what you're talking about. Even if you do, you can not make such a bold statement over all Lua scripters just like that.
[editline]5th December 2011[/editline]
And the only thing he didn't localize was his functions. You're nitpicking.
[QUOTE=agmike;33563087]I'm surprised no one else has chimed in this thread about how all the code posts are using globals when not necessary. This is bad and sloppy coding practice, ask any working professional.
Python seems to be the only one with at least a concept of the idea of local versus global.[/QUOTE]
It's quite funny really, you post everywhere all the time about how X doesn't know Y or how Y is shit at doing X and sucks at coding and should kill himself. On top of that you treat yourself like a programming god. let me set something straight to you. THIS IS HOBBY PROGRAMMING, NOT PROFESSIONAL, NOBODY GIVES A FUCK ABOUT HOW GOOD YOU ARE AND HOW SHITTY EVERYONE ELSE IS.
For those that are using this, I updated it.
I did quite a few updates on the SVN without posting here whats going on. If you want to know what exactly changed look up the SVN History.
For now
[url]https://code.google.com/p/mattmodules/source/detail?r=55[/url]
Are you still working on this? If you bind to different IPs and use the same port then it complains that the address is in use.
Will add a optional reuse flag
Guys, how is it possible to create a UDP socket that replies whenever the data is received? For example, I've sent something like 'status' and what shall I do next? A json-encoded array should be returned (however, that's not big deal).
RecvFrom will trigger the callback if theres anything incomming, and you keep calling RecvFrom as long you want to receive more data then. Just look at all the plenty examples here.
No no no, I got that working. I've made the server read queries, how do I make it reply to them?
I'm trying to send data from PHP to a GMod server. It's working well when I'm using double quotes.
However, if I use single quotes I get a "GLSOCK_ERROR_ADDRESSFAMILYNOTSUPPORTED" error.
So my question is: Is this intentional or just a bug?
What the hell. This breaks some epic way in beta.
[code]
local asd,dat=data:Read(data:Size()-1)
print("WTF",asd,dat)
[/code]asd and dat are both numbers and are the same number and seem to be the string length, but where is the string now???
Seemslike the last update ruined some things, see tmysql3.
Could be that garry changed the interfaces, however if that is the case he would need to share them or I would need to reverse them (again) which sucks ass
As Wizard of Ass said, see [url=http://www.facepunch.com/threads/1184875?p=36008857&viewfull=1#post36008857]tmysql3[/url]
[url]http://code.google.com/p/gmodmodules/source/diff?spec=svn190&r=190&format=side&path=/trunk/secretheaders/ILuaObject.h[/url]
My glsock crashes in gmod 13. Does it have to do with Wizard of Ass' comment that some things are fucked up?
[url]http://dumps.garrysmod.com/?view=2816875439[/url]
The crash occurs when I send text through my Gmod Android app.
Recompile it with the given Interface above and it will work again, atm busy.
Sorry, you need to Log In to post a reply to this thread.