-- CraftOS+ Client by Sirharry0077

-- Variables
OSversion = "6.0"
noconnection = "true"
version = "recommended"
newVersion = 0
skip = "false"
-- Get Variable
version = "recommended"
rootFolder = "https://raw.github.com/Sirharry0077/ComputerCraft/master/"..version.."/"

-- Functions

local function zfill(N)
	N=string.format("%X",N)
	Zs=""
	if #N==1 then
		Zs="0"
	end
	return Zs..N
end

local function serializeImpl(t)	
	local sType = type(t)
	if sType == "table" then
		local lstcnt=0
		for k,v in pairs(t) do
			lstcnt = lstcnt + 1
		end
		local result = "{"
		local aset=1
		for k,v in pairs(t) do
			if k==aset then
				result = result..serializeImpl(v)..","
				aset=aset+1
			else
				result = result..("["..serializeImpl(k).."]="..serializeImpl(v)..",")
			end
		end
		result = result.."}"
		return result
	elseif sType == "string" then
		return string.format("%q",t)
	elseif sType == "number" or sType == "boolean" or sType == "nil" then
		return tostring(t)
	elseif sType == "function" then
		local status,data=pcall(string.dump,t)
		if status then
			return 'func('..string.format("%q",data)..')'
		else
			error()
		end
	else
		error()
	end
end

local function split(T,func)
	if func then
		T=func(T)
	end
	local Out={}
	if type(T)=="table" then
		for k,v in pairs(T) do
			Out[split(k)]=split(v)
		end
	else
		Out=T
	end
	return Out
end

local function serialize( t )
	t=split(t)
	return serializeImpl( t, tTracking )
end

local function unserialize( s )
	local func, e = loadstring( "return "..s, "serialize" )
	local funcs={}
	if not func then
		return e
	end
	setfenv( func, {
		func=function(S)
			local new={}
			funcs[new]=S
			return new
		end,
	})
	return split(func(),function(val)
		if funcs[val] then
			return loadstring(funcs[val])
		else
			return val
		end
	end)
end

local function sure(N,n)
	if (l2-n)<1 then N="0" end
	return N
end

local function splitnum(S)
	Out=""
	for l1=1,#S,2 do
		l2=(#S-l1)+1
		CNum=tonumber("0x"..sure(string.sub(S,l2-1,l2-1),1) .. sure(string.sub(S,l2,l2),0))
		Out=string.char(CNum)..Out
	end
	return Out
end

local function wrap(N)
	return N-(math.floor(N/256)*256)
end

function checksum(S,num)
	local sum=0
	for char in string.gmatch(S,".") do
		for l1=1,(num or 1) do
			math.randomseed(string.byte(char)+sum)
			sum=sum+math.random(0,9999)
		end
	end
	math.randomseed(sum)
	return sum
end

local function genkey(len,psw)
	checksum(psw)
	local key={}
	local tKeys={}
	for l1=1,len do
		local num=math.random(1,len)
		while tKeys[num] do
			num=math.random(1,len)
		end
		tKeys[num]=true
		key[l1]={num,math.random(0,255)}
	end
	return key
end

function encrypt(data,psw)
	data=serialize(data)
	local chs=checksum(data)
	local key=genkey(#data,psw)
	local out={}
	local cnt=1
	for char in string.gmatch(data,".") do
		table.insert(out,key[cnt][1],zfill(wrap(string.byte(char)+key[cnt][2])),chars)
		cnt=cnt+1
	end
	return string.sub(serialize({chs,table.concat(out)}),2,-3)
end

function decrypt(data,psw)
	local oData=data
	data=unserialize("{"..data.."}")
	if type(data)~="table" then
		return oData
	end
	local chs=data[1]
	data=data[2]
	local key=genkey((#data)/2,psw)
	local sKey={}
	for k,v in pairs(key) do
		sKey[v[1]]={k,v[2]}
	end
	local str=splitnum(data)
	local cnt=1
	local out={}
	for char in string.gmatch(str,".") do
		table.insert(out,sKey[cnt][1],string.char(wrap(string.byte(char)-sKey[cnt][2])))
		cnt=cnt+1
	end
	out=table.concat(out)
	if checksum(out or "")==chs then
		return unserialize(out)
	end
	return oData,out,chs
end

function openRednet()
 x = 1
 repeat
  if x == 1 then
   side = "right"
  elseif x == 2 then
   side = "left"
  elseif x == 3 then
   side = "top"
  elseif x == 4 then
   side = "bottom"
  elseif x == 5 then
   side = "back"
  end
  if rednet.isOpen(side) == nil then
   rednet.open(side)
  end
  x = x + 1
 until x == 6
end

function download(url, path)
 http.request(url)
 local requesting = true
 while requesting do
  local event, url, sourceText = os.pullEvent()
  if event == "http_success" then
   local respondedText = sourceText.readAll()
   requesting = false
   local getFile = http.get(url)
   local text = getFile.readAll()
   getFile.close()
   file = fs.open(path, "w")
   file.write(text)
   file.close()
   success = true
  elseif event == "http_failure" then
   requesting = false
   success = false
  end
 end
end

function setServerID()
 file = fs.open("bin/server", "r")
 serverid = file.readLine()
 file.close()
 serverid = tonumber(serverid)
end

function checkNewerVersion()
 download(rootFolder.."programs/version", "bin/version")
 file = fs.open("bin/version", "r")
 newVersion = file.readLine()
 file.close()
 fs.delete("bin/version")
 if success == false then
  newVersion = "0"
 end
 vnumber = tonumber(OSversion)
 newvnumber = tonumber(newVersion)
 if newvnumber > vnumber then
  shell.run("clear")
  print("Update ", newversion, " Available")
  print("---------------------")
  download(rootFolder.."programs/changelog", "bin/changelog")
  file = fs.open("bin/changelog", "r")
  changelog = file.readAll()
  file.close()
  if success == false then
   print("Did Not Receive Change Log")
  else
   print("Change Log: ")
   print(changelog)
  end
  write("Update Y/N?: ")
  answer = read()
  shell.run("clear")
  if answer == "y" then
   shell.run("install")
  elseif answer == "n" then
   print("Loading...")
  elseif answer == "Y" then
   shell.run("install")
  elseif answer == "N" then
   print("Loading...")
  else
   print("Unrecognized Answer")
   print("Update Canceled")
   sleep(2)
  end  
 end
end

function testServerConnection()
 rednet.send(serverid, "testconnection")
 id, connection = rednet.receive(3)
 if id == nil then
  noconnection = "true"
 elseif id == serverid then
  noconnection = "false"
 end
end

local function checkFirstTime()
 if fs.exists("bin") == false then
  print("Welcome To CraftOS+")
  print("-------------------")
  print("Just Press ENTER To Proceed")
  read()
  shell.run("clear")
 end

  if fs.exists("bin/server") == false then
  print("Choose Server")
  print("-------------")
  print("This Is The ID of The Server That You Would Like To Use")
  print("The ID Must Be A Number")
  write("Server: ")
  newserver = read()
  fs.makeDir("bin")
  file = fs.open("bin/server", "w")
  file.write(newserver)
  file.close()
  shell.run("clear")
 end
 
 if fs.exists("bin/username") == false then
  print("Create Username")
  print("---------------")
  write("Username: ")
  newusername = read()
  fs.makeDir("bin")
  file = fs.open("bin/username", "w")
  file.write(newusername)
  file.close()
  shell.run("clear")
 end

 if fs.exists("bin/password") == false then
  print("Create Password")
  print("---------------")
  write("Password: ")
  newpassword = read("*")
  newpassword = encrypt(newpassword, newpassword)
  fs.makeDir("bin")
  file = fs.open("bin/password", "w")
  file.write(newpassword)
  file.close()
  shell.run("clear")
 end
end

function fetchComputerID()
computerid = os.computerID()
end

function screenClear()
  shell.run("clear")
  print("CraftOS+ ", OSversion)
  print("------------")
end

function writeLoginSreen() 
 shell.run("clear")
 term.setCursorPos(1, 1)
 print("Computer #", computerid)
 term.setCursorPos(20, 6)
 print("CraftOS+ ", OSversion)
 term.setCursorPos(20, 7)
 print("------------")
 term.setCursorPos(16, 8)
 print("Username: ")
 term.setCursorPos(16, 9)
 print("Password: ")
 term.setCursorPos(1, 1)
 if noconnection == "true" then
  print("No Server Connection")
 end

 term.setCursorPos(26, 8)
 file = fs.open("bin/username", "r")
 user = file.readLine()
 file.close()
 print(user)

 term.setCursorPos(26, 9)
 file = fs.open("bin/password", "r")
 pass = file.readLine()
 file.close()
 pass2 = read("*")
 pass1 = encrypt(pass2, pass2)
 if pass1 == pass then
  screenClear()
 else
  writeLoginSreen()
 end
end

local function setComputerLabel()
 file = fs.open("bin/username", "r")
 user = file.readLine()
 file.close()
 cid = os.getComputerID()
 label = user.."'s Computer | ID "..cid
 os.setComputerLabel(label)
end

function commandUpload()
 shell.run("clear")
 print("Upload To Server")
 print("----------------")
 write("File: ")
 filename = read()
 if fs.exists(filename) == false then
  print("File Doesn't Exist")
  sleep(2)
  commandUpload()
  return
 end
 write("Destination: ")
 destination = read()
 file = fs.open(filename, "r")
 filetext = file.readAll()
 file.close()
 rednet.send(serverid, "upload")
 sleep(1)
 rednet.send(serverid, destination)
 sleep(1)
 rednet.send(serverid, filetext)
 id, msg = rednet.receive(5)
 if msg == "success" then
  print("Upload Successful")
 elseif id == nil then
  print("No Server Response")
 else
  print("Upload Unsuccessful")
 end
 sleep(2)
 screenClear()
end

function commandDownload()
 shell.run("clear")
 print("Download")
 print("--------")
 write("File: ")
 filename = read()
 write("Location: ")
 filename2 = read()
 rednet.send(serverid, "download")
 sleep(1)
 rednet.send(serverid, filename2)
 id, filetext = rednet.receive(5)
 if id == serverid and id ~= nil then
  print("File Received")
  file = fs.open(filename, "w")
  file.write(filetext)
  file.close()
 else
  print("No Server Response")
 end
 sleep(2)
 screenClear()
end

function stopServer()
 shell.run("clear")
 print("Stopping Server...")
 print("------------------")
 rednet.send(serverid, "stop")
 id, msg = rednet.receive(5)
 if msg == "stopped" then
  print("Server Stopped")
 elseif id == nil then
  print("No Server Response")
 end
 sleep(2)
 screenClear()
end

function commandEncrypt()
	if #arguments == 3 then
		program = arguments[2]
		password = arguments[3]
		if fs.exists(program) == true then
			print("Encrypting "..program)
			file = fs.open(program, "r")
			data = file.readAll()
			file.close()
			data = encrypt(data, password)
			fs.delete(program)
			file = fs.open(program, "w")
			file.write(data)
			file.close()
			print("Encrypted "..program)
		else
			print("File Does Not Exist")
		end
	else
		print("Usage: encrypt <file> <password>")
	end
end

function commandDecrypt()
	if #arguments == 3 then
		program = arguments[2]
		password = arguments[3]
		if fs.exists(program) == true then
			print("Decrypting "..program)
			file = fs.open(program, "r")
			data = file.readAll()
			file.close()
			data1 = decrypt(data, password)
			fs.delete(program)
			file = fs.open(program, "w")
			file.write(data1)
			file.close()
			if encrypt(data1, password) == data then
				print("Decrypted "..program)
			else
				print("Incorrect Decryption Password")
			end
		else
			print("File Does Not Exist")
		end
	else
		print("Usage: decrypt <file> <password>")
	end
end

function decryptFile(program, password)
	if fs.exists(program) == true then
		file = fs.open(program, "r")
		data = file.readAll()
		file.close()
		data = decrypt(data, password)
		fs.delete(program)
		file = fs.open(program, "w")
		file.write(data)
		file.close()
	else
		print("File Does Not Exist")
	end
end

function encryptFile(program, password)
	if fs.exists(program) == true then
		file = fs.open(program, "r")
		data = file.readAll()
		file.close()
		data = encrypt(data, password)
		fs.delete(program)
		file = fs.open(program, "w")
		file.write(data)
		file.close()
	else
		print("File Does Not Exist")
	end
end

-- Code
openRednet()
shell.run("clear")
oldpullEvent = os.pullEvent
os.pullEvent = os.pullEventRaw

checkFirstTime()
print("Loading...")

checkNewerVersion()
setServerID()
testServerConnection()
fetchComputerID()
setComputerLabel()
writeLoginSreen()
screenClear()

-- Read Entry and Do Appropiate Action
arguments = {}
invalidcommands = {}
fail = false
while true do
 oldpullEvent = os.pullEvent
 os.pullEvent = os.pullEventRaw
 
 write("> ")
 local command = read()
 for match in string.gmatch(command, "[^ \t]+") do
   table.insert( arguments, match )
 end
 
 for i,v in ipairs(invalidcommands) do
  if arguments[1] == v then
   print("Invalid command.")
   sleep(1)
   fail = true
  end
 end
 
 if not fail then
  if arguments[1] == "clear" then
   clearSreen()
  elseif arguments[1] == "upload" then
   commandUpload()
  elseif arguments[1] == "download" then
   commandDownload()
  elseif arguments[1] == "encrypt" then
   commandEncrypt()
  elseif arguments[1] == "decrypt" then
   commandDecrypt()
  elseif arguments[1] == "firewolf" then
   shell.run("firewolf")
   screenClear()
   openRednet()
  elseif arguments[1] == "stop" then
   stopServer()
  else
   if #arguments == 1 then
    shell.run(arguments[1])
   elseif #arguments == 2 then
    shell.run(arguments[1], arguments[2])
   elseif #arguments == 3 then
    shell.run(arguments[1], arguments[2], arguments[3])
   elseif #arguments == 4 then
    shell.run(arguments[1], arguments[2], arguments[3], arguments[4])
   elseif #arguments == 5 then
    shell.run(arguments[1], arguments[2], arguments[3], arguments[4], arguments[5])
   elseif #arguments > 5 then
    print("Too Many Arguments")
   end
  end
 end
 fail = false
 arguments = {}
 
 os.pullEvent = oldpullEvent
end