From b5590af9bd6826e793e2f0f1af0cc60099d1d88b Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 3 Dec 2016 00:45:36 -0500 Subject: [PATCH] Add files via upload --- client/Libs/Utils.lua | 797 ++++++++++++++++++++++++++++++++++++++++ client/main.lua | 93 ++++- client/net/chatting.lua | 14 +- client/net/email.lua | 53 +++ client/net/identity.lua | 192 ++++++++++ client/net/init.lua | 483 ++++++++++++++++++++++++ client/net/sft.lua | 179 +++++++++ client/them.png | Bin 0 -> 29749 bytes client/you.png | Bin 0 -> 29655 bytes 9 files changed, 1797 insertions(+), 14 deletions(-) create mode 100644 client/Libs/Utils.lua create mode 100644 client/net/email.lua create mode 100644 client/net/identity.lua create mode 100644 client/net/init.lua create mode 100644 client/net/sft.lua create mode 100644 client/them.png create mode 100644 client/you.png diff --git a/client/Libs/Utils.lua b/client/Libs/Utils.lua new file mode 100644 index 0000000..5b2b486 --- /dev/null +++ b/client/Libs/Utils.lua @@ -0,0 +1,797 @@ +-- os Additions +function os.getSystemBit() + if (os.getenv('PROCESSOR_ARCHITEW6432')=='AMD64' or os.getenv('PROCESSOR_ARCHITECTURE')=='AMD64') then + return 64 + else + return 32 + end +end +function os.sleep(n) + if not n then n=0 end + local t0 = os.clock() + while os.clock() - t0 <= n do end +end +function os.pause(msg) + if msg ~= nil then + print(msg) + end + io.read() +end +function os.batCmd(cmd) + io.mkFile('temp.bat',cmd) + local temp = os.execute([[temp.bat]]) + io.delFile('temp.bat') + return temp +end +function os._getOS() + if package.config:sub(1,1)=='\\' then + return 'windows' + else + return 'unix' + end +end +function os.getOS(t) + if not t then + return os._getOS() + end + if os._getOS()=='unix' then + fh,err = io.popen('uname -o 2>/dev/null','r') + if fh then + osname = fh:read() + end + if osname then return osname end + end + local winver='Unknown Version' + local a,b,c=os.capture('ver'):match('(%d+).(%d+).(%d+)') + local win=a..'.'..b..'.'..c + if type(t)=='string' then + win=t + end + if win=='4.00.950' then + winver='95' + elseif win=='4.00.1111' then + winver='95 OSR2' + elseif win=='4.00.1381' then + winver='NT 4.0' + elseif win=='4.10.1998' then + winver='98' + elseif win=='4.10.2222' then + winver='98 SE' + elseif win=='4.90.3000' then + winver='ME' + elseif win=='5.00.2195' then + winver='2000' + elseif win=='5.1.2600' then + winver='XP' + elseif win=='5.2.3790' then + winver='Server 2003' + elseif win=='6.0.6000' then + winver='Vista/Windows Server 2008' + elseif win=='6.0.6002' then + winver='Vista SP2' + elseif win=='6.1.7600' then + winver='7/Windows Server 2008 R2' + elseif win=='6.1.7601' then + winver='7 SP1/Windows Server 2008 R2 SP1' + elseif win=='6.2.9200' then + winver='8/Windows Server 2012' + elseif win=='6.3.9600' then + winver='8.1/Windows Server 2012' + elseif win=='6.4.9841' then + winver='10 Technical Preview 1' + elseif win=='6.4.9860' then + winver='10 Technical Preview 2' + elseif win=='6.4.9879' then + winver='10 Technical Preview 3' + elseif win=='10.0.9926' then + winver='10 Technical Preview 4' + end + return 'Windows '..winver +end +function os.getLuaArch() + return (#tostring({})-7)*4 +end +if os.getOS()=='windows' then + function os.sleep(n) + if n > 0 then os.execute('ping -n ' .. tonumber(n+1) .. ' localhost > NUL') end + end +else + function os.sleep(n) + os.execute('sleep ' .. tonumber(n)) + end +end +function os.capture(cmd, raw) + local f = assert(io.popen(cmd, 'r')) + local s = assert(f:read('*a')) + f:close() + if raw then return s end + s = string.gsub(s, '^%s+', '') + s = string.gsub(s, '%s+$', '') + s = string.gsub(s, '[\n\r]+', ' ') + return s +end +function os.getCurrentUser() + return os.getenv('$USER') or os.getenv('USERNAME') +end +-- string Additions +function string.trim(s) + local from = s:match"^%s*()" + return from > #s and "" or s:match(".*%S", from) +end +function string.random(n) + local str = '' + strings = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','1','2','3','4','5','6','7','8','9','0','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'} + for i=1,n do + h = math.random(1,#strings) + str = str..''..strings[h] + end + return str +end +function string.linesToTable(s) + local t = {} + local i = 0 + while true do + i = string.find(s, '\n', i+1) + if i == nil then return t end + table.insert(t, i) + end +end +function string.lines(str) + local t = {} + local function helper(line) table.insert(t, line) return '' end + helper((str:gsub('(.-)\r?\n', helper))) + return t +end +function string.split(str, pat) + local t = {} -- NOTE: use {n = 0} in Lua-5.0 + local fpat = '(.-)' .. pat + local last_end = 1 + local s, e, cap = str:find(fpat, 1) + while s do + if s ~= 1 or cap ~= '' then + table.insert(t,cap) + end + last_end = e+1 + s, e, cap = str:find(fpat, last_end) + end + if last_end <= #str then + cap = str:sub(last_end) + table.insert(t, cap) + end + return t +end +function string.shuffle(inputStr) + math.randomseed(os.time()); + local outputStr = ''; + local strLength = string.len(inputStr); + while (strLength ~=0) do + local pos = math.random(strLength); + outputStr = outputStr..string.sub(inputStr,pos,pos); + inputStr = inputStr:sub(1, pos-1) .. inputStr:sub(pos+1); + strLength = string.len(inputStr); + end + return outputStr; +end +function string.genKeys(chars,a,f,s,GG) + if GG then + chars=string.rep(chars,a) + end + if s then + chars=string.shuffle(chars) + end + b=#chars + if a==0 then return end + local taken = {} local slots = {} + for i=1,a do slots[i]=0 end + for i=1,b do taken[i]=false end + local index = 1 + local tab={} + for i=1,#chars do + table.insert(tab,chars:sub(i,i)) + end + while index > 0 do repeat + repeat slots[index] = slots[index] + 1 + until slots[index] > b or not taken[slots[index]] + if slots[index] > b then + slots[index] = 0 + index = index - 1 + if index > 0 then + taken[slots[index]] = false + end + break + else + taken[slots[index]] = true + end + if index == a then + local tt={} + for i=1,a do + table.insert(tt,tab[slots[i]]) + end + f(table.concat(tt)) + taken[slots[index]] = false + break + end + index = index + 1 + until true end +end +-- io Additions +function io.getInput(msg) + if msg ~= nil then + io.write(msg) + end + return io.read() +end +function io.scanDir(directory) + directory=directory or io.getDir() + local i, t, popen = 0, {}, io.popen + if os.getOS()=='unix' then + for filename in popen('ls -a \''..directory..'\''):lines() do + i = i + 1 + t[i] = filename + end + else + for filename in popen('dir \''..directory..'\' /b'):lines() do + i = i + 1 + t[i] = filename + end + end + return t +end +function io.buildFromTree(tbl, indent,folder) + if not indent then indent = 0 end + if not folder then folder = '' end + for k, v in pairs(tbl) do + formatting = string.rep(' ', indent) .. k .. ':' + if type(v) == 'table' then + if not(io.dirExists(folder..string.sub(formatting,1,-2))) then + io.mkDir(folder..string.sub(formatting,1,-2)) + end + io.buildFromTree(v,0,folder..string.sub(formatting,1,-2)..'\\') + else + a=string.find(tostring(v),':',1,true) + if a then + file=string.sub(tostring(v),1,a-1) + data=string.sub(tostring(v),a+1) + io.mkFile(folder..file,data,'wb') + else + io.mkFile(folder..v,'','wb') + end + end + end +end +function io.cpFile(path,topath) + if os.getOS()=='unix' then + os.execute('cp '..file1..' '..file2) + else + os.execute('Copy '..path..' '..topath) + end +end +function io.delDir(directoryname) + if os.getOS()=='unix' then + os.execute('rm -rf '..directoryname) + else + os.execute('rmdir '..directoryname..' /s /q') + end +end +function io.delFile(path) + os.remove(path) +end +function io.mkDir(dirname) + os.execute('mkdir "' .. dirname..'"') +end +function io.mkFile(filename,data,tp) + if not(tp) then tp='wb' end + if not(data) then data='' end + file = io.open(filename, tp) + if file==nil then return end + file:write(data) + file:close() +end +function io.movFile(path,topath) + io.cpFile(path,topath) + io.delFile(path) +end +function io.listFiles(dir) + if not(dir) then dir='' end + local f = io.popen('dir \''..dir..'\'') + if f then + return f:read('*a') + else + print('failed to read') + end +end +function io.getDir(dir) + if not dir then return io.getWorkingDir() end + if os.getOS()=='unix' then + return os.capture('cd '..dir..' ; cd') + else + return os.capture('cd '..dir..' & cd') + end +end +function io.getWorkingDir() + return io.popen'cd':read'*l' +end +function io.fileExists(path) + g=io.open(path or '','r') + if path =='' then + p='empty path' + return nil + end + if g~=nil and true or false then + p=(g~=nil and true or false) + end + if g~=nil then + io.close(g) + else + return false + end + return p +end +function io.fileCheck(file_name) + if not file_name then print('No path inputed') return false end + local file_found=io.open(file_name, 'r') + if file_found==nil then + file_found=false + else + file_found=true + end + return file_found +end +function io.dirExists(strFolderName) + strFolderName = strFolderName or io.getDir() + local fileHandle, strError = io.open(strFolderName..'\\*.*','r') + if fileHandle ~= nil then + io.close(fileHandle) + return true + else + if string.match(strError,'No such file or directory') then + return false + else + return true + end + end +end +function io.getAllItems(dir) + local t=os.capture("cd \""..dir.."\" & dir /a-d | find",true):lines() + return t +end +function io.listItems(dir) + if io.dirExists(dir) then + temp=io.listFiles(dir) -- current directory if blank + if io.getDir(dir)=='C:\\\n' then + a,b=string.find(temp,'C:\\',1,true) + a=a+2 + else + a,b=string.find(temp,'..',1,true) + end + temp=string.sub(temp,a+2) + list=string.linesToTable(temp) + temp=string.sub(temp,1,list[#list-2]) + slist=string.lines(temp) + table.remove(slist,1) + table.remove(slist,#slist) + temp={} + temp2={} + for i=1,#slist do + table.insert(temp,string.sub(slist[i],40,-1)) + end + return temp + else + return nil + end +end +function io.getDirectories(dir,l) + if dir then + dir=dir..'\\' + else + dir='' + end + local temp2=io.scanDir(dir) + for i=#temp2,1,-1 do + if io.fileExists(dir..temp2[i]) then + table.remove(temp2,i) + elseif l then + temp2[i]=dir..temp2[i] + end + end + return temp2 +end +function io.getFiles(dir,l) + if dir then + dir=dir..'\\' + else + dir='' + end + local temp2=io.scanDir(dir) + for i=#temp2,1,-1 do + if io.dirExists(dir..temp2[i]) then + table.remove(temp2,i) + elseif l then + temp2[i]=dir..temp2[i] + end + end + return temp2 +end +function io.getFullName(name) + local temp=name or arg[0] + if string.find(temp,'\\',1,true) or string.find(temp,'/',1,true) then + temp=string.reverse(temp) + a,b=string.find(temp,'\\',1,true) + if not(a) or not(b) then + a,b=string.find(temp,'/',1,true) + end + return string.reverse(string.sub(temp,1,b-1)) + end + return temp +end +function io.getName(file) + local name=io.getFullName(file) + name=string.reverse(name) + a,b=string.find(name,'.',1,true) + name=string.sub(name,a+1,-1) + return string.reverse(name) +end +function io.readFile(file) + local f = io.open(file, 'rb') + local content = f:read('*all') + f:close() + return content +end +function io.getExtension(file) + local file=io.getFullName(file) + file=string.reverse(file) + local a,b=string.find(file,'.',0,true) + local temp=string.sub(file,1,b) + return string.reverse(temp) +end +function io.pathToTable(path) + local p=io.splitPath(path) + local temp={} + temp[p[1]]={} + local last=temp[p[1]] + for i=2,#p do + snd=last + last[p[i]]={} + last=last[p[i]] + end + return temp,last,snd +end +function io.splitPath(str) + return string.split(str,'[\\/]+') +end + +function io.parseDir(dir,t) + io.tempFiles={} + function _p(dir) + local dirs=io.getDirectories(dir,true) + local files=io.getFiles(dir,true) + for i=1,#files do + p,l,s=io.pathToTable(files[i]) + if t then + s[io.getFullName(files[i])]=io.readFile(files[i]) + else + s[io.getFullName(files[i])]=io.open(files[i],'r+') + end + table.merge(io.tempFiles,p) + end + for i=1,#dirs do + table.merge(io.tempFiles,io.pathToTable(dirs[i])) + _p(dirs[i],t) + end + end + _p(dir) + return io.tempFiles +end +function io.parsedir(dir,f) + io.tempFiles={} + function _p(dir,f) + local dirs=io.getDirectories(dir,true) + local files=io.getFiles(dir,true) + for i=1,#files do + if not f then + table.insert(io.tempFiles,files[i]) + else + f(files[i]) + end + end + for i=1,#dirs do + _p(dirs[i],f) + end + end + _p(dir,f) + return io.tempFiles +end +function io.driveReady(drive) + drive=drive:upper() + if not(drive:find(':',1,true)) then + drive=drive..':' + end + drives=io.getDrives() + for i=1,#drives do + if drives[i]==drive then + return true + end + end + return false +end +function io.getDrives() + if os.getOS()=='windows' then + local temp={} + local t1=os.capture('wmic logicaldisk where drivetype=2 get deviceid, volumename',true) + local t2=os.capture('wmic logicaldisk where drivetype=3 get deviceid, volumename',true) + for drive,d2 in t1:gmatch('(.:)%s-(%w+)') do + if #d2>1 then + table.insert(temp,drive) + end + end + for drive in t2:gmatch('(.:)') do + table.insert(temp,drive) + end + return temp + end + error('Command is windows only!') +end +-- table Additions +function table.dump(t,indent) + local names = {} + if not indent then indent = '' end + for n,g in pairs(t) do + table.insert(names,n) + end + table.sort(names) + for i,n in pairs(names) do + local v = t[n] + if type(v) == 'table' then + if(v==t) then + print(indent..tostring(n)..': <-') + else + print(indent..tostring(n)..':') + table.dump(v,indent..' ') + end + else + if type(v) == 'function' then + print(indent..tostring(n)..'()') + else + print(indent..tostring(n)..': '..tostring(v)) + end + end + end +end +function table.alphanumsort(o) + local function padnum(d) local dec, n = string.match(d, '(%.?)0*(.+)') + return #dec > 0 and ('%.12f'):format(d) or ('%s%03d%s'):format(dec, #n, n) + end + table.sort(o, function(a,b) return tostring(a):gsub('%.?%d+',padnum)..('%3d'):format(#b)< tostring(b):gsub('%.?%d+',padnum)..('%3d'):format(#a) end) + return o +end +function table.foreach(t,f) + for i,v in pairs(t) do + f(v) + end +end +function table.merge(t1, t2) + for k,v in pairs(t2) do + if type(v) == 'table' then + if type(t1[k] or false) == 'table' then + table.merge(t1[k] or {}, t2[k] or {}) + else + t1[k] = v + end + else + t1[k] = v + end + end + return t1 +end +function table.print(tbl, indent) + if not indent then indent = 0 end + for k, v in pairs(tbl) do + formatting = string.rep(' ', indent) .. k .. ': ' + if type(v) == 'table' then + print(formatting) + table.print(v, indent+1) + else + print(formatting .. tostring(v)) + end + end +end +function table.merge(t1, t2) + for k,v in pairs(t2) do + if type(v) == 'table' then + if type(t1[k] or false) == 'table' then + table.merge(t1[k] or {}, t2[k] or {}) + else + t1[k] = v + end + else + t1[k] = v + end + end + return t1 +end +function table.clear(t) + for k in pairs (t) do + t[k] = nil + end +end +function table.copy(t) + function deepcopy(orig) + local orig_type = type(orig) + local copy + if orig_type == 'table' then + copy = {} + for orig_key, orig_value in next, orig, nil do + copy[deepcopy(orig_key)] = deepcopy(orig_value) + end + setmetatable(copy, deepcopy(getmetatable(orig))) + else -- number, string, boolean, etc + copy = orig + end + return copy + end + return deepcopy(t) +end +function table.swap(tab,i1,i2) + tab[i1],tab[i2]=tab[i2],tab[i1] +end +function table.append(t1, ...) + t1,t2= t1 or {},{...} + for k,v in pairs(t2) do + t1[#t1+1]=t2[k] + end + return t1 +end +function table.compare(t1, t2,d) + if d then + return table.deepCompare(t1,t2) + end + --if #t1 ~= #t2 then return false end + if #t2>#t1 then + for i=1,#t2 do + if t1[i] ~= t2[i] then + return false,t2[i] + end + end + else + for i=1,#t1 do + if t1[i] ~= t2[i] then + return false,t2[i] + end + end + end + return true +end +function table.deepCompare(t1,t2) + if t1==t2 then return true end + if (type(t1)~='table') then return false end + local mt1 = getmetatable(t1) + local mt2 = getmetatable(t2) + if( not table.deepCompare(mt1,mt2) ) then return false end + for k1,v1 in pairs(t1) do + local v2 = t2[k1] + if( not table.deepCompare(v1,v2) ) then return false end + end + for k2,v2 in pairs(t2) do + local v1 = t1[k2] + if( not table.deepCompare(v1,v2) ) then return false end + end + return true +end +function table.has(t,_v) + for i,v in pairs(t) do + if v==_v then + return true + end + end + return false +end +function table.reverse(tab) + local size = #tab + local newTable = {} + for i,v in ipairs (tab) do + newTable[size-i] = v + end + for i=1,#newTable do + tab[i]=newTable[i] + end +end +-- Math Additions +local Y = function(g) local a = function(f) return f(f) end return a(function(f) return g(function(x) local c=f(f) return c(x) end) end) end +local F = function(f) return function(n)if n == 0 then return 1 else return n*f(n-1) end end end +math.factorial = Y(F) +math.fib={} +math.fib.fibL={} +setmetatable(math.fib,{__call=function(self,n) + if n<=2 then + return 1 + else + if self.fibL[n] then + return self.fibL[n] + else + local t=math.fib(n-1)+math.fib(n-2) + self.fibL[n]=t + return t + end + end +end}) +local floor,insert = math.floor, table.insert +function math.basen(n,b) + n = floor(n) + if not b or b == 10 then return tostring(n) end + local digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + local t = {} + local sign = '' + if n < 0 then + sign = '-' + n = -n + end + repeat + local d = (n % b) + 1 + n = floor(n / b) + insert(t, 1, digits:sub(d,d)) + until n == 0 + return sign .. table.concat(t,'') +end +function math.convbase(n,b,tb) + return math.basen(tonumber(tostring(n),b),tb) +end +if BigNum then + function BigNum.mod(a,b) + return a-((a/b)*b) + end + local floor,insert = math.floor, table.insert + function math.basen(n,b) + n = BigNum.new(n) + if not b or b == 10 then return tostring(n) end + local digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + local t = {} + local sign = '' + if n < BigNum.new(0) then + sign = '-' + n = -n + end + repeat + local d = BigNum.mod(n , b) + 1 + n = n/b + d=tonumber(tostring(d)) + insert(t, 1, digits:sub(d,d)) + until tonumber(tostring(n)) == 0 + return sign .. table.concat(t,'') + end + function math.to10(n,b) + local num=tostring(n) + local sum=BigNum.new() + local digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + for i=1,#num do + local v=digits:find(num:sub(i,i),1,true) + sum=sum+BigNum.new(tonumber(v)-1)*BigNum.pow(BigNum.new(b),BigNum.new(#num-i)) + end + return sum + end + function math.convbase(n,b,tb) + return math.basen(math.to10(n,b),tb) + end +end +function math.numfix(n,x) + local str=tostring(n) + if #str255 then self.C1=254 self.C1m=-1 end + if self.C2>255 then self.C2=254 self.C2m=-1 end + if self.C3>255 then self.C3=254 self.C3m=-1 end + if self.C1<0 then self.C1=1 self.C1m=1 end + if self.C2<0 then self.C2=1 self.C2m=1 end + if self.C3<0 then self.C3=1 self.C3m=1 end + self.Color=Color.new(self.C1,self.C2,self.C3) +end) +function love.wheelmoved(x, y) -- handle scrolling + if mainapp then else return end + if y > 0 then + Chat.bubbles:SetDualDim(nil,Chat.bubbles.y+10) + elseif y < 0 then + Chat.bubbles:SetDualDim(nil,Chat.bubbles.y-50) + end + if Chat.bubbles.offset.pos.y>0 then + Chat.bubbles.offset.pos.y=0 + end + print(Chat.bubbles.offset.pos.y) +end +gui.ff:OnUpdate(func) function InitEngine() mainapp=gui:newFrame("",0,0,0,0,0,0,1,1) -- creates a frame that takes up the enitre window - mainapp.header=mainapp:newTextLabel("Chat Program",0,0,0,20,0,0,1) -- creates the header for the app - mainapp.header.Tween=-3 -- moves the text up by a factor of 3 pixels + mainapp.Color=Color.Lighten(Color.Brown,.25) + mainapp.BorderSize=0 + mainapp.header=mainapp:newTextLabel("Now chatting as: "..MY_Name,1,-10,-2,30,0,0,1) -- creates the header for the app + mainapp.header:setRoundness(15,15,360) + mainapp.header.Tween=7 -- moves the text up by a factor of 3 pixels mainapp.header.Color=Color.Lighten(Color.Blue,.25) -- Sets the Color of the header object to light blue - mainapp.chatFrame=mainapp:newTextLabel("",0,20,0,-20,0,0,1,1) -- creates the chat frame where users can see the text that is sent - mainapp.chatFrame.Color=Color.Lighten(Color.Brown,.15) -- Color stuff - mainapp.chatFrame.Tween=-3 -- text positioning - mainapp.chatFrame.TextFormat="left" -- changes the text format to left hand side of screen + mainapp.chatFrame=mainapp:newFrame("",0,21,0,-20,0,0,1,1) -- creates the chat frame where users can see the text that is sent + mainapp.chatFrame.Color=Color.Lighten(Color.Brown,.25) -- Color stuff + mainapp.chatFrame.ClipDescendants=true + mainapp.chatFrame.bubbles=mainapp.chatFrame:newFrame("",0,0,0,0,0,0,1) + mainapp.chatFrame.bubbles.Visibility=0 + Chat=mainapp.chatFrame + Chat.lastBubbleHeight=0 + function Chat:AddChatBubble(user,txt,isself) + local msg=user..": "..txt + local bubble=self.bubbles:newTextLabel(msg,0,self.lastBubbleHeight+5,math.floor(mainapp.width/2),(math.ceil(#msg/24)*12)+5) + bubble.TextFormat="left" + bubble.XTween=2 + bubble.Tween=-4 + bubble:setRoundness(5,5,360) + if isself then + bubble:anchorRight(1) + end + self.lastBubbleHeight=self.lastBubbleHeight+(math.ceil(#msg/24)*12)+13 + if self.lastBubbleHeight>mainapp.height-60 then + self.bubbles.offset.pos.y=-(self.lastBubbleHeight-(mainapp.height-60)) + end + end mainapp.textbox=mainapp:newTextBox("",5,-35,-10,30,0,1,1) -- creates a textbox that allows the user to be able to send messages mainapp.textbox.TextFormat="left" -- sets the format to left hand side of screen mainapp.textbox.Color=Color.tan mainapp.textbox:setRoundness(5,5,360) mainapp.textbox:OnEnter(function(self,txt) -- handles the send event - client:sendChat("NAMENOSPACE",txt) -- sends the data to the server + client:sendChat(MY_Name,txt) -- sends the data to the server self.text="" self.Color=Color.tan end) @@ -38,5 +110,4 @@ function InitEngine() mainapp.textbox.ClearOnFocus=true mainapp.textbox.XTween=2 mainapp.textbox.Tween=2 -- positions the text in the text box - --This displays the chatting frame and allows connection to the server... Look at server.lua to see the servers code end diff --git a/client/net/chatting.lua b/client/net/chatting.lua index 09393df..74f58f3 100644 --- a/client/net/chatting.lua +++ b/client/net/chatting.lua @@ -21,7 +21,14 @@ function net.chatting:init() -- calling this initilizes the library and binds it msg=msg } self.OnChatRecieved:Fire(struct) -- trigger the chat event - self:sendAll("!chatting! "..struct.user.." '"..struct.msg.."'") + for i,v in pairs(self.ips) do + if ip==v then + print("Self: "..struct.user) + self:send(v,"!chatting! 1 "..struct.user.." '"..struct.msg.."'") + else + self:send(v,"!chatting! 0 "..struct.user.." '"..struct.msg.."'") + end + end end end) s.rooms={} @@ -34,10 +41,11 @@ function net.chatting:init() -- calling this initilizes the library and binds it net.OnClientCreated:connect(function(c) c.OnDataRecieved:connect(function(self,data) -- when the client recieves data this method is triggered --First Lets make sure we are getting chatting data - local user,msg = data:match("!chatting! (%S-) '(.+)'") + local isself, user,msg = data:match("!chatting! (%d) (%S-) '(.+)'") if user and msg then --This is the client so our job here is done - self.OnChatRecieved:Fire(user,msg) -- trigger the chat event + print(isself) + self.OnChatRecieved:Fire(user,msg,({["1"]=true, ["0"]=false})[isself]) -- trigger the chat event end end) function c:sendChat(user,msg) diff --git a/client/net/email.lua b/client/net/email.lua new file mode 100644 index 0000000..18de805 --- /dev/null +++ b/client/net/email.lua @@ -0,0 +1,53 @@ +require("net.identity") +net:registerModule("email",{1,0,0}) +smtp = require 'socket.smtp' +ssl = require 'ssl' + +function net.email.init(from,user,pass) + net.OnServerCreated:connect(function(s) + s.from=from + s.user=user + s.pass=pass + function s:sendMessage(subject, body, dTable) + local msg = { + headers = { + from = '<'..dTable.email..'>' + to = dTable.nick..' <'..dTable.email..'>', + subject = subject + }, + body = body + } + local ok, err = smtp.send { + from = '<'..self.from..'>', + rcpt = '<'..dTable.email..'>', + source = smtp.message(msg), + user = self.user, + password = self.pass, + server = 'smtp.gmail.com', + port = 465, + create = net.sslCreate + } + if not ok then + print("Mail send failed", err) -- better error handling required + end + end + end) +end +function net.sslCreate() + local sock = socket.tcp() + return setmetatable({ + connect = function(_, host, port) + local r, e = sock:connect(host, port) + if not r then return r, e end + sock = ssl.wrap(sock, {mode='client', protocol='tlsv1'}) + return sock:dohandshake() + end + }, { + __index = function(t,n) + return function(_, ...) + return sock[n](sock, ...) + end + end + }) +end + diff --git a/client/net/identity.lua b/client/net/identity.lua new file mode 100644 index 0000000..2cf9d0f --- /dev/null +++ b/client/net/identity.lua @@ -0,0 +1,192 @@ +require("net") +--General Stuff +--[[ What this module does! +Adds +net.identity:init() + +]] +net:registerModule("identity",{1,0,0}) +function net.hash(text,n) + n=n or 16 + return bin.new(text.."jgmhktyf"):getHash(n) +end +function net.identity:init() -- calling this initilizes the library and binds it to the servers and clients created + --Server Stuff + net.OnServerCreated:connect(function(s) + s.userFolder="./" + print("The identity Module has been loaded onto the server!") + function s:_isRegistered(user) + return io.fileExists(self.userFolder..net.hash(user)..".dat") + end + function s:getUserData(user) + local userdata=bin.load(self.userFolder..net.hash(user)..".dat") + local nick,dTable=userdata:match("%S-|%S-|(%S-)|(.+)") + return nick,loadstring("return "..(dTable or "{}"))() + end + function s:getUserCred(user) + local userdata=bin.load(self.userFolder..net.hash(user)..".dat") + return userdata:match("%S-|(%S-)|") + end + function s:userLoggedIn(cid) + for i,v in pairs(self.loggedIn) do + if v.cid==cid then + return i + end + end + return false + end + function s:setDataLocation(loc) + self.userFolder=loc + end + function s:loginUserOut(user) + self.loggedIn[user]=nil + end + function s:loginUserIn(user,cid) + local nick,dTable=self:getUserData(user) + self.loggedIn[user]={} + table.merge(self.loggedIn[user],dTable or {}) + self.loggedIn[user].cid=cid + self.loggedIn[user].nick=nick + return self:getUserDataHandle(user) + end + function s:getUserDataHandle(user) + return self.loggedIn[user] + end + function s:syncUserData(user,ip,port) + local handle=self:getUserDataHandle(user) + self:send(ip,"!identity! SYNC <-|"..bin.ToStr(handle).."|->",port) + end + s.loggedIn={} + s.OnUserRegistered=multi:newConnection() + s.OnUserLoggedIn=multi:newConnection() + s.OnUserLoggerOut=multi:newConnection() + s.OnAlreadyLoggedIn=multi:newConnection() + s.OnPasswordForgotten=multi:newConnection() + s.OnDataRecieved:connect(function(self,data,cid,ip,port) -- when the server recieves data this method is triggered + local cmd,arg1,arg2,arg3,arg4 = data:match("!identity! (%S-) '(.-)' '(.-)' '(.-)' <%-|(.+)|%->") + if cmd=="register" then + local user,pass,nick,dTable = arg1,arg2,arg3,arg4 + if self:_isRegistered(user) then + self:send(ip,"!identity! REGISTERED <-|"..user.."|->",port) + else + if not(self.userFolder:sub(-1,-1)=="/" or self.userFolder:sub(-1,-1)=="\\") then + self.userFolder=self.userFolder.."/" + end + local rets=self.OnUserRegistered:Fire(user,pass,nick,loadstring("return "..(dTable or "{}"))()) + for i=1,#rets do + if rets[i][1]==false then + print("Server refused to accept registration request!") + self:send(ip,"!identity! REGISTERREFUSED <-|NIL|->",port) + return + end + end + bin.new(string.format("%s|%s|%s|%s\n",user,pass,nick,dTable)):tofile(self.userFolder..net.hash(user)..".dat") + self:send(ip,"!identity! REGISTEREDGOOD <-|"..user.."|->",port) + end + return + elseif cmd=="login" then + local user,pass = arg1,arg2 + local _pass=s:getUserCred(user) + if not(self:_isRegistered(user)) then + self:send(ip,"!identity! LOGINBAD <-|nil|->",port) + return + end + print(pass,_pass) + if pass==_pass then + if self:userLoggedIn(cid) then + self.OnAlreadyLoggedIn:Fire(self,user,cid,ip,port) + self:send(ip,"!identity! ALREADYLOGGEDIN <-|nil|->",port) + return + end + local handle=self:loginUserIn(user,cid) -- binds the cid to username + self:send(ip,"!identity! LOGINGOOD <-|"..bin.ToStr(handle).."|->",port) + self.OnUserLoggedIn:Fire(user,cid,ip,port) + return + else + self:send(ip,"!identity! LOGINBAD <-|nil|->",port) + return + end + elseif cmd=="logout" then + self:loginUserOut(user) + self.OnClientClosed:Fire(self,"User logged out!",cid,ip,port) + elseif cmd=="sync" then + local dTable = loadstring("return "..(arg4 or "{}"))() + local handle = self:getUserDataHandle(self:userLoggedIn(cid)) + table.merge(handle,dTable) + elseif cmd=="pass" then + local user=arg1 + if self:_isRegistered(user) then + self.OnPasswordForgotten:Fire(arg1,cid) + self:send(ip,"!identity! PASSREQUESTHANDLED <-|NONE|->",port) + else + self:send(ip,"!identity! NOUSER <-|"..user.."|->",port) + end + end + end) + s.OnClientClosed:connect(function(self,reason,cid,ip,port) + self.OnUserLoggerOut:Fire(self,self:userLoggedIn(cid),cid,reason) + end) + end) + --Client Stuff + net.OnClientCreated:connect(function(c) + c.userdata={} + c.OnUserLoggedIn=multi:newConnection() + c.OnBadLogin=multi:newConnection() + c.OnUserAlreadyRegistered=multi:newConnection() + c.OnUserAlreadyLoggedIn=multi:newConnection() + c.OnUserRegistered=multi:newConnection() + c.OnNoUserWithName=multi:newConnection() + c.OnPasswordRequest=multi:newConnection() + c.OnUserRegisterRefused=multi:newConnection() + function c:logout() + self:send("!identity! logout 'NONE' 'NONE' 'NONE' <-|nil|->") + end + c.OnDataRecieved:connect(function(self,data) -- when the client recieves data this method is triggered + local cmd,arg1 = data:match("!identity! (%S-) <%-|(.+)|%->") + if cmd=="REGISTERED" then + self.OnUserAlreadyRegistered:Fire(self,arg1) + elseif cmd=="REGISTEREDGOOD" then + self.OnUserRegistered:Fire(self,arg1) + elseif cmd=="REGISTERREFUSED" then + self.OnUserRegisterRefused:Fire(self,arg1) + elseif cmd=="ALREADYLOGGEDIN" then + self.OnUserAlreadyLoggedIn:Fire(self,arg1) + elseif cmd=="LOGINBAD" then + self.OnBadLogin:Fire(self) + elseif cmd=="LOGINGOOD" then + local dTable=loadstring("return "..(arg1 or "{}"))() + table.merge(self.userdata,dTable) + self.OnUserLoggedIn:Fire(self,self.userdata) + elseif cmd=="SYNC" then + local dTable=loadstring("return "..(arg1 or "{}"))() + table.merge(self.userdata,dTable) + elseif cmd=="NOUSER" then + self.OnNoUserWithName:Fire(self,arg1) + elseif cmd=="PASSREQUESTHANDLED" then + self.OnPasswordRequest:Fire(self) + end + end) + function c:syncUserData() + self:send(string.format("!identity! sync 'NONE' 'NONE' 'NONE' <-|%s|->",bin.ToStr(dTable))) + end + function c:forgotPass(user) + self:send(string.format("!identity! pass '%s' 'NONE' 'NONE' <-|nil|->",user)) + end + function c:getUserDataHandle() + return self.userdata + end + function c:logIn(user,pass) + self:send(string.format("!identity! login '%s' '%s' 'NONE' <-|nil|->",user,net.hash(pass))) + end + function c:register(user,pass,nick,dTable) + if dTable then + self:send(string.format("!identity! register '%s' '%s' '%s' <-|%s|->",user,net.hash(pass),nick,bin.ToStr(dTable))) + else + self:send(string.format("!identity! register '%s' '%s' '%s' <-|nil|->",user,net.hash(pass),nick)) + end + end + end) +end +if net.autoInit then + net.identity:init() +end diff --git a/client/net/init.lua b/client/net/init.lua new file mode 100644 index 0000000..0b67ca1 --- /dev/null +++ b/client/net/init.lua @@ -0,0 +1,483 @@ +function string.trim(s) + local from = s:match"^%s*()" + return from > #s and "" or s:match(".*%S", from) +end +socket=require("socket") +net={} +net.Version={1,0,0} +net.OnServerCreated=multi:newConnection() +net.OnClientCreated=multi:newConnection() +net.loadedModules={} +net.autoInit=true +function net:registerModule(mod,version) + table.insert(self.loadedModules,mod) + net[mod]={} + if version then + net[mod].Version=version + else + net[mod].Version={1,0,0} + end +end +function net.getModuleVersion(ext) + if not ext then + return string.format("%d.%d.%d",net.Version[1],net.Version[2],net.Version[3]) + end + return string.format("%d.%d.%d",net[ext].Version[1],net[ext].Version[2],net[ext].Version[3]) +end +function net.resolveID(obj) + local num=math.random(10000000,99999999) + if obj[tostring(num)] then + return net.resolveID(obj) + end + obj.ids[tostring(num)]=true + return tostring(num) +end +function net.inList(list,dat) + for i,v in pairs(list) do + if v==dat then + return true + end + end + return false +end +function net.setTrigger(funcW,funcE) + multi:newTrigger(func) +end +-- UDP Stuff +function net:newServer(port,servercode) + local c={} + c.udp=assert(socket.udp()) + c.udp:settimeout(0) + c.udp:setsockname("*", port) + c.ips={} + c.Type="udp" + c.port=port + c.ids={} + c.servercode=servercode + c.bannedIPs={} + c.bannedCIDs={} + function c:setUpdateRate(n) + print("Not needed in a udp server!") + end + function c:banCID(cid) + table.insert(self.bannedCIDs,cid) + end + function c:banIP(ip) + table.insert(self.bannedIPs,cid) + end + function c:send(ip,data,port,cid) + if self.servercode then + cid=cid or self:CIDFrom(ip,port) + if not self.ips[cid] then + print("Can't determine cid from client... sending the client a new one!") + local cid=net.resolveID(self) + print("Sending unique cid to client: "..cid) + self.ips[cid]={ip,port,0,self.servercode==nil} + print(ip) + self.udp:sendto("I!"..cid,ip,port) + if self.servercode then + self.udp:sendto("S!",ip,port) + end + return + end + if net.inList(self.bannedIPs,ip) or net.inList(self.bannedCIDs,cid) then + self.udp:sendto("BANNED CLIENT", ip, port or self.port) + elseif self.ips[cid][4] then + self.udp:sendto(data, ip, port or self.port) + elseif self.ips[cid][4]==false then + self.udp:sendto("Make sure your server code is correct!", ip, port) + end + else + self.udp:sendto(data, ip, port or self.port) + end + end + function c:pollClientModules(ip,port) + self:send(ip,"L!",port) + end + function c:CIDFrom(ip,port) + for i,v in pairs(self.ips) do + if(ip==v[1] and v[2]==port) then + return i + end + end + end + function c:sendAll(data) + for i,v in pairs(self.ips) do + self:send(v[1],data,v[2],i) + end + end + function c:sendAllBut(data,cid) + for i,v in pairs(self.ips) do + if i~=cid then + self:send(v[1],data,v[2],i) + end + end + end + function c:clientRegistered(cid) + return self.ips[cid] + end + function c:clientLoggedIn(cid) + if not self.clientRegistered(cid) then + return nil + end + return self.ips[cid][4] + end + function c:update() + local data,ip,port=self.udp:receivefrom() + if net.inList(self.bannedIPs,ip) or net.inList(self.bannedCIDs,cid) then + print("We will ingore data from a banned client!") + return + end + if data then + if data:sub(1,4)=="pong" then + print("Recieved pong from: "..data:sub(5,-1)) + self.ips[data:sub(5,-1)][3]=os.clock() + elseif data:sub(1,2)=="S!" then + local cid=self:CIDFrom(ip,port) + if data:sub(3,-1)==self.servercode then + print("Servercode Accepted: "..self.servercode) + if self.ips[cid] then + self.ips[cid][4]=true + else + print("Server can't keep up! CID: "..cid.." has been skipped! Sending new CID to the client!") + local cid=net.resolveID(self) + print("Sending unique cid to client: "..cid) + self.ips[cid]={ip,port,0,self.servercode==nil} + print(ip) + self.udp:sendto("I!"..cid,ip,port) + if self.servercode then + self.udp:sendto("S!",ip,port) + end + end + else + self.udp:sendto("Make sure your server code is correct!", ip, port) + end + elseif data:sub(1,2)=="C!" then + self.OnDataRecieved:Fire(self,data:sub(11,-1),data:sub(3,10),ip,port) + elseif data:sub(1,2)=="E!" then + self.ips[data:sub(3,10)]=nil + obj.ids[data:sub(3,10)]=false + self.OnClientClosed:Fire(self,"Client Closed Connection!",data:sub(3,10),ip,port) + elseif data=="I!" then + local cid=net.resolveID(self) + print("Sending unique cid to client: "..cid) + self.ips[cid]={ip,port,os.clock(),self.servercode==nil} + print(ip) + self.udp:sendto("I!"..cid,ip,port) + if self.servercode then + self.udp:sendto("S!",ip,port) + end + elseif data:sub(1,2)=="L!" then + cid,cList=data:sub(3,10),data:sub(11,-1) + local list={} + for m,v in cList:gmatch("(%S-):(%S-)|") do + list[m]=v + end + self.OnClientsModulesList:Fire(list,cid,ip,port) + end + end + for cid,dat in pairs(self.ips) do + if not((os.clock()-dat[3])<65) then + self.ips[cid]=nil + self.OnClientClosed:Fire(self,"Client lost Connection: ping timeout",cid,ip,port) + end + end + end + c.OnClientsModulesList=multi:newConnection() + c.OnDataRecieved=multi:newConnection() + c.OnPongRecieved=multi:newConnection() + c.OnClientClosed=multi:newConnection() + c.connectiontest=multi:newAlarm(30) + c.connectiontest.link=c + c.connectiontest:OnRing(function(alarm) + print("pinging clients!") + alarm.link:sendAll("ping") + alarm:Reset() + end) + multi:newLoop(function() + c:update() + end) + net.OnServerCreated:Fire(c) + return c +end + +function net:newClient(host,port,servercode,nonluaServer) + local c={} + c.ip=assert(socket.dns.toip(host)) + c.udp=assert(socket.udp()) + c.udp:setpeername(c.ip, port) + c.udp:settimeout(0) + c.cid="NIL" + c.lastPing=0 + c.Type="udp" + c.servercode=servercode + c.autoReconnect=true + function c:pollPing(n) + return not((os.clock()-self.lastPing)<(n or 60)) + end + function c:send(data) + self.udp:send("C!"..self.cid..data) + end + function c:sendRaw(data) + self.udp:send(data) + end + function c:getCID() + if self:IDAssigned() then + return self.cid + end + end + function c:close() + self:send("E!") + end + function c:IDAssigned() + return self.cid~="NIL" + end + function c:update() + local data=self.udp:receive() + if data then + if data:sub(1,2)=="I!" then + self.cid=data:sub(3,-1) + self.OnClientReady:Fire(self) + elseif data=="S!" then + self.udp:send("S!"..(self.servercode or "")) + elseif data=="L!" then + local mods="" + local m="" + for i=1,#net.loadedModules do + m=net.loadedModules[i] + mods=mods..m..":"..net.getModuleVersion(m).."|" + end + self.udp:send("L!"..self.cid..mods) + elseif data=="ping" then + self.lastPing=os.clock() + self.OnPingRecieved:Fire(self) + self.udp:send("pong"..self.cid) + else + self.OnDataRecieved:Fire(self,data) + end + end + end + function c:reconnect() + if not nonluaServer then + self.cid="NIL" + c.udp:send("I!") + end + end + c.pingEvent=multi:newEvent(function(self) return self.link:pollPing() end) + c.pingEvent:OnEvent(function(self) + if self.link.autoReconnect then + self.link.OnServerNotAvailable:Fire("Connection to server lost: ping timeout! Attempting to reconnect...") + self.link:reconnect() + else + self.link.OnServerNotAvailable:Fire("Connection to server lost: ping timeout!") + end + end) + c.pingEvent.link=c + c.OnPingRecieved=multi:newConnection() + c.OnDataRecieved=multi:newConnection() + c.OnServerNotAvailable=multi:newConnection() + c.OnClientReady=multi:newConnection() + c.notConnected=multi:newFunction(function(self) + self:hold(3) + if self.link:IDAssigned()==false then + self.link.OnServerNotAvailable:Fire("Can't connect to the server: no response from server") + end + end) + c.notConnected.link=c + if not nonluaServer then + c.udp:send("I!") + end + multi:newLoop(function() + c:update() + end) + multi:newJob(function() c.notConnected() end) + net.OnClientCreated:Fire(c) + return c +end +--TCP Stuff +function net:newTCPServer(port) + local c={} + c.tcp=assert(socket.bind("*", port)) + c.tcp:settimeout(0) + c.ip,c.port=c.tcp:getsockname() + c.ips={} + c.port=port + c.ids={} + c.bannedIPs={} + c.Type="tcp" + c.rMode="*l" + c.sMode="*l" + c.updaterRate=1 + function c:setUpdateRate(n) + self.updaterRate=n + end + function c:setReceiveMode(mode) + self.rMode=mode + end + function c:setSendMode(mode) + self.rMode=mode + end + function c:banCID(cid) + print("Function not supported on a tcp server!") + end + function c:banIP(ip) + table.insert(self.bannedIPs,cid) + end + function c:send(handle,data) + if self.sMode=="*l" then + handle:send(data.."\n") + else + handle:send(data) + end + end + function c:pollClientModules(ip,port) + self:send(ip,"L!",port) + end + function c:CIDFrom(ip,port) + print("Method not supported when using a TCP Server!") + return "CIDs in TCP work differently!" + end + function c:sendAll(data) + for i,v in pairs(self.ips) do + self:send(v,data) + end + end + function c:sendAllBut(data,cid) + for i,v in pairs(self.ips) do + if not(cid==i) then + self:send(v,data) + end + end + end + function c:clientRegistered(cid) + return self.ips[cid] + end + function c:clientLoggedIn(cid) + return self.ips[cid] + end + function c:update() + local client = self.tcp:accept(self.rMode) + if not client then return end + table.insert(self.ips,client) + client:settimeout(0) + --client:setoption('tcp-nodelay', true) + client:setoption('keepalive', true) + ip,port=client:getpeername() + if ip and port then + print("Got connection from: ",ip,port) + local updater=multi:newUpdater(skip) + updater:OnUpdate(function(self) + local data, err = self.client:receive(self.Link.rMode) + if err=="closed" then + for i=1,#self.Link.ips do + if self.Link.ips[i]==self.client then + table.remove(self.Link.ips,i) + end + end + self.Link.OnClientClosed:Fire(self.Link,"Client Closed Connection!",self.client,self.client,ip) + self:Destroy() + end + if data then + if net.inList(self.Link.bannedIPs,ip) then + print("We will ingore data from a banned client!") + return + end + self.Link.OnDataRecieved:Fire(self.Link,data,self.client,self.client,ip) + if data:sub(1,2)=="L!" then + cList=data + local list={} + for m,v in cList:gmatch("(%S-):(%S-)|") do + list[m]=v + end + self.Link.OnClientsModulesList:Fire(list,self.client,self.client,ip) + end + end + end) + updater:setSkip(self.updaterRate) + updater.client=client + updater.Link=self + end + end + c.OnClientsModulesList=multi:newConnection() + c.OnDataRecieved=multi:newConnection() + c.OnClientClosed=multi:newConnection() + multi:newLoop(function() + c:update() + end) + net.OnServerCreated:Fire(c) + return c +end + +function net:newTCPClient(host,port) + local c={} + c.ip=assert(socket.dns.toip(host)) + c.port=post + c.tcp=socket.connect(c.ip,port) + if not c.tcp then + print("Can't connect to the server: no response from server") + return false + end + c.tcp:settimeout(0) + c.tcp:setoption('tcp-nodelay', true) + c.tcp:setoption('keepalive', true) + c.Type="tcp" + c.autoReconnect=true + c.rMode="*l" + c.sMode="*l" + function c:setReceiveMode(mode) + self.rMode=mode + end + function c:setSendMode(mode) + self.sMode=mode + end + function c:send(data) + if self.sMode=="*l" then + self.tcp:send(data.."\n") + else + self.tcp:send(data) + end + end + function c:sendRaw(data) + self.tcp:send(data) + end + function c:getCID() + return "No Cid on a tcp client!" + end + function c:close() + self.tcp:close() + end + function c:IDAssigned() + return true + end + function c:update() + local data=self.tcp:receive() + if data then + self.OnDataRecieved:Fire(self,data) + end + end + function c:reconnect() + self.ip=assert(socket.dns.toip(host)) + self.tcp=socket.connect(self.ip,self.port) + if not self.tcp then + print("Can't connect to the server: no response from server") + return + end + self.tcp:settimeout(0) + self.tcp:setoption('tcp-nodelay', true) + self.tcp:setoption('keepalive', true) + end + c.event=multi:newEvent(function(event) + return event.link:IDAssigned() + end) + c.event:OnEvent(function(event) + event.link.OnClientReady:Fire(event.link) + end) + c.event.link=c + c.OnClientReady=multi:newConnection() + c.OnDataRecieved=multi:newConnection() + multi:newLoop(function() + c:update() + end) + net.OnClientCreated:Fire(c) + return c +end diff --git a/client/net/sft.lua b/client/net/sft.lua new file mode 100644 index 0000000..6679aa6 --- /dev/null +++ b/client/net/sft.lua @@ -0,0 +1,179 @@ +require("net") +--General Stuff +--[[ What this module does! +Adds + +]] +function io.fileExists(path) + g=io.open(path or '','r') + if path =='' then + p='empty path' + return nil + end + if g~=nil and true or false then + p=(g~=nil and true or false) + end + if g~=nil then + io.close(g) + else + return false + end + return p +end +net:registerModule("sft",{1,0,0}) +function net.sft:init() -- calling this initilizes the library and binds it to the servers and clients created + --Server Stuff + net.OnServerCreated:connect(function(s) + print("The sft(Simple File Transfer) Module has been loaded onto the server!") + if s.Type~="tcp" then + print("It is recomended that you use tcp to transfer files!") + end + s.transfers={} + s.OnUploadRequest=multi:newConnection() -- create a sft event + s.OnFileUploaded=multi:newConnection() -- create a sft event + s.OnDownloadRequest=multi:newConnection() + s.OnDataRecieved:connect(function(self,data,cid,ip,port) -- when the server recieves data this method is triggered + --First Lets make sure we are getting sft data + --filename,dat=data:match("!sft! (%S-) (%S+)") + local cmd,arg1,arg2=data:match("!sft! (%S-) (%S-) (.+)") + if cmd=="tstart" then + local rets=self.OnUploadRequest:Fire(self,cid,ip,port) + for i=1,#rets do + if rets[i][1]==false then + print("Server refused to accept upload request!") + self:send(ip,"!sft! CANTUPLOAD NIL NIL",port) + return + end + end + local ID,streamable=arg1:match("(.+)|(.+)") + local file,hash=arg2:match("(.+)|(.+)") + if streamable~="NIL" then + self.transfers[ID]={bin.stream(streamable,false),hash,file} + else + self.transfers[ID]={bin.new(""),hash,file} + end + return + elseif cmd=="transfer" then + if self.transfers[arg1]~=nil then + self.transfers[arg1][1]:tackE(bin.fromhex(arg2)) + --print(self.transfers[arg1][1]:getSize()) + end + return + elseif cmd=="tend" then + if self.transfers[arg1]~=nil then + if self.transfers[arg1][1]:getHash(32)==self.transfers[arg1][2] then + self.OnFileUploaded:Fire(self,self.transfers[arg1][1],self.transfers[arg1][3],"Hash Good!") + else + print("Hash Error!") + self.OnFileUploaded:Fire(self,self.transfers[arg1][1],self.transfers[arg1][3],"Hash Bad!") + end + self.transfers[arg1]=nil + end + return + end + local filename=cmd + local dat=arg1 + if filename==nil then return end + local rets=self.OnDownloadRequest:Fire(self,cid,ip,port) + for i=1,#rets do + if rets[i][1]==false then + print("Server refused to accept download request!") + self:send(ip,"!sft! CANTREQUEST NIL NIL",port) + return + end + end + if io.fileExists(filename) then + --Lets first load the file + local file=bin.stream(filename,false) + local size=file:getSize() + local pieceSize=512 + local pieces=math.ceil(size/pieceSize) + local step=multi:newStep(1,pieces) + step.TransferID=tostring(math.random(1000,9999)) + step.sender=self + step.ip=ip + step.port=port + step.pieceSize=pieceSize + step:OnStart(function(self) + self.sender:send(self.ip,"!sft! TSTART "..self.TransferID.."|"..dat.." "..filename.."|"..file:getHash(32),self.port) + end) + step:OnStep(function(pos,self) + self:hold(.01) + self.sender:send(self.ip,"!sft! TRANSFER "..self.TransferID.." "..bin.tohex(file:sub(((self.pieceSize*pos)+1)-self.pieceSize,self.pieceSize*pos)),self.port) + end) + step:OnEnd(function(self) + self.sender:send(self.ip,"!sft! TEND "..self.TransferID.." NIL",self.port) + end) + else + self:send(ip,"!sft! CANTREQUEST NIL NIL",port) + end + end) + end) + --Client Stuff + net.OnClientCreated:connect(function(c) + c.transfers={} + c.OnTransferStarted=multi:newConnection() -- create a sft event + c.OnTransferFinished=multi:newConnection() -- create a sft event + c.OnFileRequestFailed=multi:newConnection() -- create a sft event + c.OnFileUploadFailed=multi:newConnection() -- create a sft event + c.OnDataRecieved:connect(function(self,data) -- when the client recieves data this method is triggered + --First Lets make sure we are getting sft data + local cmd,arg1,arg2=data:match("!sft! (%S-) (%S-) (.+)") + if cmd=="TSTART" then + local ID,streamable=arg1:match("(.+)|(.+)") + local file,hash=arg2:match("(.+)|(.+)") + if streamable~="NIL" then + self.transfers[ID]={bin.stream(streamable,false),hash,file} + else + self.transfers[ID]={bin.new(""),hash,file} + end + self.OnTransferStarted:Fire(self) + elseif cmd=="TRANSFER" then + self.transfers[arg1][1]:tackE(bin.fromhex(arg2)) + elseif cmd=="TEND" then + if self.transfers[arg1][1]:getHash(32)==self.transfers[arg1][2] then + self.OnTransferFinished:Fire(self,self.transfers[arg1][1],self.transfers[arg1][3],"Hash Good!") + else + print("Hash Error!") + self.OnTransferFinished:Fire(self,self.transfers[arg1][1],self.transfers[arg1][3],"Hash Bad!") + end + self.transfers[arg1]=nil + elseif cmd=="CANTREQUEST" then + self.OnFileRequestFailed:Fire(self,"Could not request the file for some reason!") + elseif cmd=="CANTUPLOAD" then + self.OnFileUploadFailed:Fire(self,"Could not upload the file for some reason!") + end + end) + function c:uploadFile(filename) + if io.fileExists(filename) then + local file=bin.stream(filename,false) + local size=file:getSize() + local pieceSize=512 + local pieces=math.ceil(size/pieceSize) + local step=multi:newStep(1,pieces) + step.TransferID=tostring(math.random(1000,9999)) + step.sender=self + step.pieceSize=pieceSize + step:OnStart(function(self) + self.sender:send("!sft! tstart "..self.TransferID.."|NIL "..filename.."|"..file:getHash(32)) + end) + step:OnStep(function(pos,self) + self:hold(.01) + self.sender:send("!sft! transfer "..self.TransferID.." "..bin.tohex(file:sub(((self.pieceSize*pos)+1)-self.pieceSize,self.pieceSize*pos))) + end) + step:OnEnd(function(self) + print("Request done!") + self.sender:send("!sft! tend "..self.TransferID.." NIL") + end) + else + self.OnFileUploadFailed:Fire(self,filename,"File does not exist!") + end + end + function c:requestFile(filename) + self:send("!sft! "..filename.." NIL NIL NIL") + end + end) +end +if net.autoInit then + net.sft.init() +end diff --git a/client/them.png b/client/them.png new file mode 100644 index 0000000000000000000000000000000000000000..29c29d83c327d5019c1292b556665e2d3382ebf1 GIT binary patch literal 29749 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