Advertisement
smilerryan

[LUA] ComputerCraft VNC Server

Sep 25th, 2018
164
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 23.14 KB | None | 0 0
  1. -- ComputerCraft VNC Server
  2. -- Connect to a central computer.
  3. -- Install: pastebin get WBj6r83H /startup
  4. -- Clients: change "host" to the computer ID.
  5.  
  6. local args = { "host" }
  7.  
  8. local connections = {}
  9. local nshAPI = {connList = connections}
  10. local bufferDirs = {"/","/LyqydOS/","/usr/apis/","/disk/"}
  11.  
  12. local function rawSend(id, msg)
  13. if term.current then
  14. return rednet.send(id, msg, "tror")
  15. else
  16. return rednet.send(id, msg)
  17. end
  18. end
  19.  
  20. local function rawRecv(id, timeout)
  21. if type(timeout) == "number" then timeout = os.startTimer(timeout) end
  22. while true do
  23. event = {os.pullEvent()}
  24. if event[1] == "rednet_message" and (id == nil and true or event[2] == id) and (not term.current and true or event[4] == "tror") then
  25. return event[3]
  26. elseif event[1] == "timer" and event[2] == timeout then
  27. return nil
  28. end
  29. end
  30. end
  31.  
  32.  
  33. nshAPI.getRemoteID = function()
  34. for cNum, cInfo in pairs(nshAPI.connList) do
  35. if cInfo and type(cInfo) == "table" and cInfo.thread == coroutine.running() then
  36. if cNum == "localShell" then
  37. --if we are a client running on the server, return the remote server ID.
  38. if nshAPI.serverNum then
  39. return nshAPI.serverNum
  40. else
  41. return nil
  42. end
  43. end
  44. return cNum
  45. end
  46. end
  47. --client running without local server, return remote server ID.
  48. if nshAPI.serverNum then return nshAPI.serverNum end
  49. return nil
  50. end
  51.  
  52. nshAPI.send = function(msg)
  53. local id = nshAPI.getRemoteID()
  54. if id then
  55. return rawSend(id, msg)
  56. end
  57. return nil
  58. end
  59.  
  60. nshAPI.receive = function(timeout)
  61. return rawRecv(nshAPI.getRemoteID(), timeout)
  62. end
  63.  
  64. nshAPI.getClientCapabilities = function()
  65. if nshAPI.clientCapabilities then return nshAPI.clientCapabilities end
  66. nshAPI.send("SP:;clientCapabilities")
  67. return nshAPI.receive(1)
  68. end
  69.  
  70. nshAPI.getRemoteConnections = function()
  71. local remotes = {}
  72. for cNum, cInfo in pairs(nshAPI.connList) do
  73. table.insert(remotes, cNum)
  74. if cInfo.outbound then
  75. table.insert(remotes, cInfo.outbound)
  76. end
  77. end
  78. return remotes
  79. end
  80.  
  81. nshAPI.packFile = function(path)
  82. local data = {}
  83. local count = 0
  84. local handle = io.open(path, "rb")
  85. if handle then
  86. local byte = handle:read()
  87. repeat
  88. data[#data + 1] = byte
  89. count = count + 1
  90. if count % 1000 == 0 then
  91. os.queueEvent("yield")
  92. os.pullEvent("yield")
  93. end
  94. byte = handle:read()
  95. until not byte
  96. handle:close()
  97. else
  98. return false
  99. end
  100. local outputTable = {}
  101. for i = 1, #data, 3 do
  102. local num1, num2, num3 = data[i], data[i + 1] or 0, data[i + 2] or 0
  103. table.insert(outputTable, string.char(bit.band(bit.brshift(num1, 2), 63)))
  104. table.insert(outputTable, string.char(bit.bor(bit.band(bit.blshift(num1, 4), 48), bit.band(bit.brshift(num2, 4), 15))))
  105. table.insert(outputTable, string.char(bit.bor(bit.band(bit.blshift(num2, 2), 60), bit.band(bit.brshift(num3, 6), 3))))
  106. table.insert(outputTable, string.char(bit.band(num3, 63)))
  107. end
  108. --mark non-data (invalid) bytes
  109. if #data % 3 == 1 then
  110. outputTable[#outputTable] = "="
  111. outputTable[#outputTable - 1] = "="
  112. elseif #data % 3 == 2 then
  113. outputTable[#outputTable] = "="
  114. end
  115. return table.concat(outputTable, "")
  116. end
  117.  
  118. nshAPI.unpackAndSaveFile = function(path, data)
  119. local outputTable = {}
  120. for i=1, #data, 4 do
  121. local char1, char2, char3, char4 = string.byte(string.sub(data, i, i)), string.byte(string.sub(data, i + 1, i + 1)), string.byte(string.sub(data, i + 2, i + 2)), string.byte(string.sub(data, i + 3, i + 3))
  122. table.insert(outputTable, bit.band(bit.bor(bit.blshift(char1, 2), bit.brshift(char2, 4)), 255))
  123. table.insert(outputTable, bit.band(bit.bor(bit.blshift(char2, 4), bit.brshift(char3, 2)), 255))
  124. table.insert(outputTable, bit.band(bit.bor(bit.blshift(char3, 6), char4), 255))
  125. end
  126. --clean invalid bytes if marked
  127. if string.sub(data, #data, #data) == "=" then
  128. table.remove(outputTable)
  129. if string.sub(data, #data - 1, #data - 1) == "=" then
  130. table.remove(outputTable)
  131. end
  132. end
  133. local handle = io.open(path, "wb")
  134. if handle then
  135. for i = 1, #outputTable do
  136. handle:write(outputTable[i])
  137. if i % 10 == 0 then
  138. os.startTimer(0.1)
  139. os.pullEvent("timer")
  140. end
  141. end
  142. handle:close()
  143. end
  144. end
  145.  
  146. local packetConversion = {
  147. query = "SQ",
  148. response = "SR",
  149. data = "SP",
  150. close = "SC",
  151. fileQuery = "FQ",
  152. fileSend = "FS",
  153. fileResponse = "FR",
  154. fileHeader = "FH",
  155. fileData = "FD",
  156. fileEnd = "FE",
  157. textWrite = "TW",
  158. textCursorPos = "TC",
  159. textGetCursorPos = "TG",
  160. textGetSize = "TD",
  161. textInfo = "TI",
  162. textClear = "TE",
  163. textClearLine = "TL",
  164. textScroll = "TS",
  165. textBlink = "TB",
  166. textColor = "TF",
  167. textBackground = "TK",
  168. textIsColor = "TA",
  169. textTable = "TT",
  170. event = "EV",
  171. SQ = "query",
  172. SR = "response",
  173. SP = "data",
  174. SC = "close",
  175. FQ = "fileQuery",
  176. FS = "fileSend",
  177. FR = "fileResponse",
  178. FH = "fileHeader",
  179. FD = "fileData",
  180. FE = "fileEnd",
  181. TW = "textWrite",
  182. TC = "textCursorPos",
  183. TG = "textGetCursorPos",
  184. TD = "textGetSize",
  185. TI = "textInfo",
  186. TE = "textClear",
  187. TL = "textClearLine",
  188. TS = "textScroll",
  189. TB = "textBlink",
  190. TF = "textColor",
  191. TK = "textBackground",
  192. TA = "textIsColor",
  193. TT = "textTable",
  194. EV = "event",
  195. }
  196.  
  197. local function openModem()
  198. local modemFound = false
  199. for _, side in ipairs(rs.getSides()) do
  200. if peripheral.getType(side) == "modem" then
  201. if not rednet.isOpen(side) then rednet.open(side) end
  202. modemFound = true
  203. break
  204. end
  205. end
  206. return modemFound
  207. end
  208.  
  209. local function send(id, pType, message)
  210. if pType and message then
  211. return rawSend(id, packetConversion[pType]..":;"..message)
  212. end
  213. end
  214.  
  215. local function awaitResponse(id, time)
  216. id = tonumber(id)
  217. local listenTimeOut = nil
  218. local messRecv = false
  219. if time then listenTimeOut = os.startTimer(time) end
  220. while not messRecv do
  221. local event, p1, p2 = os.pullEvent()
  222. if event == "timer" and p1 == listenTimeOut then
  223. return false
  224. elseif event == "rednet_message" then
  225. sender, message = p1, p2
  226. if id == sender and message then
  227. if packetConversion[string.sub(message, 1, 2)] then packetType = packetConversion[string.sub(message, 1, 2)] end
  228. message = string.match(message, ";(.*)")
  229. messRecv = true
  230. end
  231. end
  232. end
  233. return packetType, message
  234. end
  235.  
  236. local function processText(conn, pType, value)
  237. if not pType then return false end
  238. if pType == "textWrite" and value then
  239. term.write(value)
  240. elseif pType == "textClear" then
  241. term.clear()
  242. elseif pType == "textClearLine" then
  243. term.clearLine()
  244. elseif pType == "textGetCursorPos" then
  245. local x, y = term.getCursorPos()
  246. send(conn, "textInfo", math.floor(x)..","..math.floor(y))
  247. elseif pType == "textCursorPos" then
  248. local x, y = string.match(value, "(%-?%d+),(%-?%d+)")
  249. term.setCursorPos(tonumber(x), tonumber(y))
  250. elseif pType == "textBlink" then
  251. if value == "true" then
  252. term.setCursorBlink(true)
  253. else
  254. term.setCursorBlink(false)
  255. end
  256. elseif pType == "textGetSize" then
  257. x, y = term.getSize()
  258. send(conn, "textInfo", x..","..y)
  259. elseif pType == "textScroll" and value then
  260. term.scroll(tonumber(value))
  261. elseif pType == "textIsColor" then
  262. send(conn, "textInfo", tostring(term.isColor()))
  263. elseif pType == "textColor" and value then
  264. value = tonumber(value)
  265. if (value == 1 or value == 32768) or term.isColor() then
  266. term.setTextColor(value)
  267. end
  268. elseif pType == "textBackground" and value then
  269. value = tonumber(value)
  270. if (value == 1 or value == 32768) or term.isColor() then
  271. term.setBackgroundColor(value)
  272. end
  273. elseif pType == "textTable" then
  274. local linesTable = textutils.unserialize(value)
  275. for i=1, linesTable.sizeY do
  276. term.setCursorPos(1,i)
  277. local lineEnd = false
  278. local offset = 1
  279. while not lineEnd do
  280. local textColorString = string.match(string.sub(linesTable.textColor[i], offset), string.sub(linesTable.textColor[i], offset, offset).."*")
  281. local backColorString = string.match(string.sub(linesTable.backColor[i], offset), string.sub(linesTable.backColor[i], offset, offset).."*")
  282. term.setTextColor(2 ^ tonumber(string.sub(textColorString, 1, 1), 16))
  283. term.setBackgroundColor(2 ^ tonumber(string.sub(backColorString, 1, 1), 16))
  284. term.write(string.sub(linesTable.text[i], offset, offset + math.min(#textColorString, #backColorString) - 1))
  285. offset = offset + math.min(#textColorString, #backColorString)
  286. if offset > linesTable.sizeX then lineEnd = true end
  287. end
  288. end
  289. term.setCursorPos(linesTable.cursorX, linesTable.cursorY)
  290. term.setCursorBlink(linesTable.cursorBlink)
  291. end
  292. return
  293. end
  294.  
  295. local function textRedirect(id)
  296. local textTable = {}
  297. textTable.id = id
  298. textTable.write = function(text)
  299. return send(textTable.id, "textWrite", text)
  300. end
  301. textTable.clear = function()
  302. return send(textTable.id, "textClear", "nil")
  303. end
  304. textTable.clearLine = function()
  305. return send(textTable.id, "textClearLine", "nil")
  306. end
  307. textTable.getCursorPos = function()
  308. send(textTable.id, "textGetCursorPos", "nil")
  309. local pType, message = awaitResponse(textTable.id, 2)
  310. if pType and pType == "textInfo" then
  311. local x, y = string.match(message, "(%-?%d+),(%-?%d+)")
  312. return tonumber(x), tonumber(y)
  313. end
  314. end
  315. textTable.setCursorPos = function(x, y)
  316. return send(textTable.id, "textCursorPos", math.floor(x)..","..math.floor(y))
  317. end
  318. textTable.setCursorBlink = function(b)
  319. if b then
  320. return send(textTable.id, "textBlink", "true")
  321. else
  322. return send(textTable.id, "textBlink", "false")
  323. end
  324. end
  325. textTable.getSize = function()
  326. send(textTable.id, "textGetSize", "nil")
  327. local pType, message = awaitResponse(textTable.id, 2)
  328. if pType and pType == "textInfo" then
  329. local x, y = string.match(message, "(%d+),(%d+)")
  330. return tonumber(x), tonumber(y)
  331. end
  332. end
  333. textTable.scroll = function(lines)
  334. return send(textTable.id, "textScroll", lines)
  335. end
  336. textTable.isColor = function()
  337. send(textTable.id, "textIsColor", "nil")
  338. local pType, message = awaitResponse(textTable.id, 2)
  339. if pType and pType == "textInfo" then
  340. if message == "true" then
  341. return true
  342. end
  343. end
  344. return false
  345. end
  346. textTable.isColour = textTable.isColor
  347. textTable.setTextColor = function(color)
  348. return send(textTable.id, "textColor", tostring(color))
  349. end
  350. textTable.setTextColour = textTable.setTextColor
  351. textTable.setBackgroundColor = function(color)
  352. return send(textTable.id, "textBackground", tostring(color))
  353. end
  354. textTable.setBackgroundColour = textTable.setBackgroundColor
  355. return textTable
  356. end
  357.  
  358. local function getServerID(server)
  359. if tonumber(server) then
  360. return tonumber(server)
  361. elseif term.current then
  362. return rednet.lookup("tror", args[1])
  363. end
  364. end
  365.  
  366. local function resumeThread(conn, event)
  367. local cInfo = connections[conn]
  368. if connections[conn] and (not connections[conn].filter or event[1] == connections[conn].filter) then
  369. connections[conn].filter = nil
  370. local _oldTerm = term.redirect(connections[conn].target)
  371. local passback = {coroutine.resume(connections[conn].thread, unpack(event))}
  372. if passback[1] and passback[2] then
  373. connections[conn].filter = passback[2]
  374. end
  375. if coroutine.status(connections[conn].thread) == "dead" then
  376. send(conn, "close", "disconnect")
  377. connections[conn] = false
  378. end
  379. if _oldTerm then
  380. term.redirect(_oldTerm)
  381. else
  382. term.restore()
  383. end
  384. if connections[conn] and conn ~= "localShell" and framebuffer and connections[conn].target.changed then
  385. send(conn, "textTable", textutils.serialize(connections[conn].target.buffer))
  386. connections[conn].target.changed = false
  387. end
  388. end
  389. end
  390.  
  391. local eventFilter = {
  392. key = true,
  393. char = true,
  394. mouse_click = true,
  395. mouse_drag = true,
  396. mouse_scroll = true,
  397. }
  398.  
  399. local function newSession(conn, x, y, color)
  400. local session = {}
  401. local path = "/rom/programs/shell /startup2"
  402. if #args >= 2 and shell.resolveProgram(args[2]) then path = shell.resolveProgram(args[2]) end
  403. session.thread = coroutine.create(function() shell.run(path) end)
  404. if framebuffer then
  405. local target = {}
  406. local _target = framebuffer.new(x, y, color)
  407. for k, v in pairs(_target) do
  408. if type(k) == "string" and type(v) == "function" then
  409. target[k] = function(...)
  410. target.changed = true
  411. return _target[k](...)
  412. end
  413. else
  414. target[k] = _target[k]
  415. end
  416. end
  417. session.target = target
  418. else
  419. session.target = textRedirect(conn)
  420. end
  421. session.status = "open"
  422. _oldTerm = term.redirect(session.target)
  423. coroutine.resume(session.thread)
  424. if _oldTerm then
  425. term.redirect(_oldTerm)
  426. else
  427. term.restore()
  428. end
  429. if framebuffer then
  430. send(conn, "textTable", textutils.serialize(session.target.buffer))
  431. session.target.changed = false
  432. end
  433. return session
  434. end
  435.  
  436. if #args >= 1 and args[1] == "host" then
  437. _G.nsh = nshAPI
  438. if not openModem() then return end
  439. if term.current then
  440. if args[4] then
  441. rednet.host("tror", args[4])
  442. elseif os.getComputerLabel() then
  443. rednet.host("tror", os.getComputerLabel())
  444. else
  445. print("No label or hostname provided!")
  446. return
  447. end
  448. end
  449. local connInfo = {}
  450. connInfo.target = term.current and term.current() or term.native
  451. local path = "/rom/programs/shell /startup2"
  452. if #args >= 3 and shell.resolveProgram(args[3]) then path = shell.resolveProgram(args[3]) end
  453. connInfo.thread = coroutine.create(function() shell.run(path) end)
  454. connections.localShell = connInfo
  455. term.clear()
  456. term.setCursorPos(1,1)
  457. coroutine.resume(connections.localShell.thread)
  458.  
  459. while true do
  460. event = {os.pullEventRaw()}
  461. if event[1] == "rednet_message" then
  462. if type(event[3]) == "string" and packetConversion[string.sub(event[3], 1, 2)] then
  463. --this is a packet meant for us.
  464. conn = event[2]
  465. packetType = packetConversion[string.sub(event[3], 1, 2)]
  466. message = string.match(event[3], ";(.*)")
  467. if connections[conn] and connections[conn].status == "open" then
  468. if packetType == "event" or string.sub(packetType, 1, 4) == "text" then
  469. local eventTable = {}
  470. if packetType == "event" then
  471. eventTable = textutils.unserialize(message)
  472. else
  473. --we can pass the packet in raw, since this is not an event packet.
  474. eventTable = event
  475. end
  476. resumeThread(conn, eventTable)
  477. elseif packetType == "query" then
  478. local connType, color, x, y = string.match(message, "(%a+):(%a+);(%d+),(%d+)")
  479. if connType == "connect" or (connType == "resume" and (not framebuffer)) then
  480. --reset connection
  481. send(conn, "response", "OK")
  482. connections[conn] = newSession(conn, tonumber(x), tonumber(y), color == "true")
  483. elseif connType == "resume" and connections[conn] and tonumber(x) == connections[conn].target.buffer.sizeX and tonumber(y) == connections[conn].target.buffer.sizeY then
  484. --restore connection
  485. send(conn, "response", "OK")
  486. send(conn, "textTable", textutils.serialize(connections[conn].target.buffer))
  487. end
  488. elseif packetType == "close" then
  489. connections[conn] = nil
  490. send(conn, "close", "disconnect")
  491. --close connection
  492. else
  493. --we got a packet, have an open connection, but despite it being in the conversion table, don't handle it ourselves. Send it onward.
  494. resumeThread(conn, event)
  495. end
  496. elseif packetType ~= "query" then
  497. --usually, we would send a disconnect here, but this prevents one from hosting nsh and connecting to other computers. Pass these to all shells as well.
  498. for cNum, cInfo in pairs(connections) do
  499. resumeThread(cNum, event)
  500. end
  501. else
  502. --open new connection
  503. send(conn, "response", "OK")
  504. local color, x, y = string.match(message, "connect:(%a+);(%d+),(%d+)")
  505. local connInfo = newSession(conn, tonumber(x), tonumber(y), color == "true")
  506. connections[conn] = connInfo
  507. end
  508. else
  509. --rednet message, but not in the correct format, so pass to all shells.
  510. for cNum, cInfo in pairs(connections) do
  511. resumeThread(cNum, event)
  512. end
  513. end
  514. elseif eventFilter[event[1]] then
  515. --user interaction.
  516. coroutine.resume(connections.localShell.thread, unpack(event))
  517. if coroutine.status(connections.localShell.thread) == "dead" then
  518. for cNum, cInfo in pairs(connections) do
  519. if cNum ~= "localShell" then
  520. send(cNum, "close", "disconnect")
  521. end
  522. end
  523. return
  524. end
  525. else
  526. --dispatch all other events to all shells
  527. for cNum, cInfo in pairs(connections) do
  528. resumeThread(cNum, event)
  529. end
  530. end
  531. end
  532.  
  533. elseif #args <= 2 and nsh and nsh.getRemoteID() then
  534. print(nsh.getRemoteID())
  535. --forwarding mode
  536. local conns = nsh.getRemoteConnections()
  537. for i = 1, #conns do
  538. if conns[i] == serverNum then
  539. print("Cyclic connection refused.")
  540. return
  541. end
  542. end
  543. local fileTransferState = nil
  544. local fileData = nil
  545. local serverNum = getServerID(args[1])
  546. if not serverNum then
  547. print("Server Not Found")
  548. return
  549. end
  550. send(serverNum, "query", "connect")
  551. local pType, message = awaitResponse(serverNum, 2)
  552. if pType ~= "response" then
  553. print("Connection to ID "..args[1].." failed...")
  554. sleep(3)
  555. os.reboot()
  556. return
  557. else
  558. nsh.connList[nsh.getRemoteID()].outbound = serverNum
  559. term.clear()
  560. term.setCursorPos(1,1)
  561. end
  562. local clientID = nsh.getRemoteID()
  563. local serverID = tonumber(args[1])
  564. while true do
  565. event = {os.pullEvent()}
  566. if event[1] == "rednet_message" then
  567. if event[2] == clientID or event[2] == serverID then
  568. if event[2] == serverID and string.sub(event[3], 1, 2) == "SC" then break end
  569. rednet.send((event[2] == clientID and serverID or clientID), event[3])
  570. end
  571. elseif eventFilter[event[1]] then
  572. rednet.send(serverID, "EV:;"..textutils.serialize(event))
  573. end
  574. end
  575. nsh.connList[nsh.getRemoteID()].outbound = nil
  576. term.clear()
  577. term.setCursorPos(1, 1)
  578. print("Connection closed by server")
  579.  
  580. elseif #args >= 1 then --either no server running or we are the local shell on the server.
  581. if not openModem() then return end
  582. local serverNum = getServerID(args[1])
  583. if not serverNum then
  584. print("Server Not Found")
  585. return
  586. end
  587. if nsh then
  588. local conns = nsh.getRemoteConnections()
  589. for i = 1, #conns do
  590. if conns[i] == serverNum then
  591. print("Connection refused.")
  592. return
  593. end
  594. end
  595. end
  596. local fileTransferState = nil
  597. local fileData = nil
  598. local fileBinaryData = nil
  599. local unpackCo = {}
  600. local color = term.isColor()
  601. local x, y = term.getSize()
  602. if args[2] == "resume" then
  603. send(serverNum, "query", "resume:"..tostring(color)..";"..tostring(x)..","..tostring(y))
  604. else
  605. send(serverNum, "query", "connect:"..tostring(color)..";"..tostring(x)..","..tostring(y))
  606. end
  607. local timeout = os.startTimer(2)
  608. while true do
  609. local event = {os.pullEvent()}
  610. if event[1] == "timer" and event[2] == timeout then
  611. print("Connection to ID "..args[1].." failed...")
  612. sleep(3)
  613. os.shutdown()
  614. return
  615. elseif event[1] == "rednet_message" and event[2] == serverNum and type(event[3]) == "string" and string.sub(event[3], 1, 2) == "SR" then
  616. if nsh then nshAPI = nsh end
  617. if nshAPI.connList and nshAPI.connList.localShell then nshAPI.connList.localShell.outbound = serverNum end
  618. nshAPI.serverNum = serverNum
  619. nshAPI.clientCapabilities = "-fileTransfer-extensions-"
  620. term.clear()
  621. term.setCursorPos(1,1)
  622. break
  623. end
  624. end
  625.  
  626. while true do
  627. event = {os.pullEventRaw()}
  628. if #unpackCo > 0 then
  629. for i = #unpackCo, 1, -1 do
  630. if coroutine.status(unpackCo[i]) ~= "dead" then
  631. coroutine.resume(unpackCo[i], unpack(event))
  632. else
  633. table.remove(unpackCo, i)
  634. end
  635. end
  636. end
  637. if event[1] == "rednet_message" and event[2] == serverNum and type(event[3]) == "string" then
  638. if packetConversion[string.sub(event[3], 1, 2)] then
  639. packetType = packetConversion[string.sub(event[3], 1, 2)]
  640. message = string.match(event[3], ";(.*)")
  641. if string.sub(packetType, 1, 4) == "text" then
  642. processText(serverNum, packetType, message)
  643. elseif packetType == "data" then
  644. if message == "clientCapabilities" then
  645. rednet.send(serverNum, nshAPI.clientCapabilities)
  646. end
  647. elseif packetType == "fileQuery" then
  648. --send a file to the server
  649. local mode, file = string.match(message, "^(%a)=(.*)")
  650. if fs.exists(file) then
  651. send(serverNum, "fileHeader", file)
  652. if mode == "b" then
  653. local fileString = nshAPI.packFile(file)
  654. send(serverNum, "fileData", "b="..fileString)
  655. else
  656. local handle = io.open(file, "r")
  657. if handle then
  658. send(serverNum, "fileData", "t="..handle:read("*a"))
  659. handle:close()
  660. end
  661. end
  662. else
  663. send(serverNum, "fileHeader", "fileNotFound")
  664. end
  665. send(serverNum, "fileEnd", "end")
  666. elseif packetType == "fileSend" then
  667. --receive a file from the server, but don't overwrite existing files.
  668. local mode, file = string.match(message, "^(%a)=(.*)")
  669. if not fs.exists(file) then
  670. fileTransferState = "receive_wait:"..file
  671. send(serverNum, "fileResponse", "ok")
  672. if mode == "b" then
  673. fileBinaryData = ""
  674. fileData = nil
  675. else
  676. fileData = ""
  677. fileBinaryData = nil
  678. end
  679. else
  680. send(serverNum, "fileResponse", "reject")
  681. end
  682. elseif packetType == "fileHeader" then
  683. if message == "fileNotFound" then
  684. fileTransferState = nil
  685. end
  686. elseif packetType == "fileData" then
  687. if fileTransferState and string.match(fileTransferState, "(.-):") == "receive_wait" then
  688. if string.match(message, "^(%a)=") == "b" then
  689. fileBinaryData = fileBinaryData..string.match(message, "^b=(.*)")
  690. else
  691. fileData = fileData..string.match(message, "^t=(.*)")
  692. end
  693. end
  694. elseif packetType == "fileEnd" then
  695. if fileTransferState and string.match(fileTransferState, "(.-):") == "receive_wait" then
  696. if fileBinaryData then
  697. local co = coroutine.create(nshAPI.unpackAndSaveFile)
  698. coroutine.resume(co, string.match(fileTransferState, ":(.*)"), fileBinaryData)
  699. if coroutine.status(co) ~= "dead" then
  700. table.insert(unpackCo, co)
  701. end
  702. elseif fileData then
  703. local handle = io.open(string.match(fileTransferState, ":(.*)"), "w")
  704. if handle then
  705. handle:write(fileData)
  706. handle:close()
  707. end
  708. end
  709. fileTransferState = nil
  710. end
  711. elseif packetType == "close" then
  712. if term.isColor() then
  713. term.setBackgroundColor(colors.black)
  714. term.setTextColor(colors.white)
  715. end
  716. term.clear()
  717. term.setCursorPos(1, 1)
  718. print("Connection closed by server.")
  719. nshAPI.serverNum = nil
  720. if nshAPI.connList and nshAPI.connList.localShell then nshAPI.connList.localShell.outbound = nil end
  721. return
  722. end
  723. end
  724. elseif event[1] == "mouse_click" or event[1] == "mouse_drag" or event[1] == "mouse_scroll" or event[1] == "key" or event[1] == "char" then
  725. --pack up event
  726. send(serverNum, "event", textutils.serialize(event))
  727. elseif event[1] == "terminate" then
  728. nshAPI.serverNum = nil
  729. if nshAPI.localShell then nshAPI.localShell.outbound = nil end
  730. term.clear()
  731. term.setCursorPos(1, 1)
  732. print("Connection closed locally.")
  733. return
  734. end
  735. end
  736. else
  737. print("Usage: nsh <serverID> [resume]")
  738. print(" nsh host [remote [local [name]]]")
  739. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement