------------------------------------------------------------------------ ------------------------------------------------------------------------ -- FAR ----------------------------------------------------------------- ------------------------------------------------------------------------ ------------------------------------------------------------------------ local global_timeout = 1 local config_file_path = "/.settings" local primary_color = colors.yellow local connection local server_running = false local send_screen_next = false local last_sent_messages = 0 local far_version = "0.9.4" ------------------------------------------------------------------------ -- SHELL COMPLETIONS --------------------------------------------------- ------------------------------------------------------------------------ local function tabCompletionFunction( shell, parNumber, curText ) local ops = { "server", "client", "password", "version" } local results = {} if parNumber == 1 then for i=1, #ops do local word = ops[i] if word:sub(1, #curText) == curText then results[#results + 1] = word:sub(#curText + 1) end end end return results end shell.setCompletionFunction("far", tabCompletionFunction) ------------------------------------------------------------------------ -- UTILITY FUNCTIONS --------------------------------------------------- ------------------------------------------------------------------------ local function wrap(f, ...) local args = { ... } return function() f(unpack(args)) end end local function shallowCompare( t1, t2 ) for k, v in pairs( t1 ) do if t2[k] ~= t2[v] then return false end end return true end local function printPrimary( ... ) if term.isColor() then local oldColor = term.getTextColor() term.setTextColor( primary_color ) print( ... ) term.setTextColor( oldColor ) else print( ... ) end end local function printCentered( text, line ) local w, h = term.getSize() term.setCursorPos( math.floor( ( w - text:len() ) / 2 ) + 1, line ) print( text ) end -- Like parallel.waitForAny() except it only forwards terminate events to function1 local function waitForAny( eventPassthrough, ... ) local functions = { ... } local coroutines = {} for i = 1, #functions do if type( functions[i] ) ~= "function" then error( "Expected function, got "..type( functions[i] ), 3 ) end table.insert( coroutines, coroutine.create( functions[i] ) ) end local eventData = {} local filters = {} while true do for i = 1, #coroutines do local r = coroutines[i] if coroutine.status( r ) == "dead" then return end local ok, err local ePass if type( eventPassthrough[i] ) == "boolean" then ePass = eventPassthrough[i] elseif type( eventPassthrough[i] ) == "function" then ePass = eventPassthrough[i]() end if ePass or eventData[1] ~= "terminate" then ok, err = coroutine.resume( r, unpack( eventData ) ) else ok, err = coroutine.resume( r ) end if not ok then error( param, 0 ) end end eventData = { os.pullEventRaw() } end end ------------------------------------------------------------------------ -- PASSWORD HASHING ---------------------------------------------------- ------------------------------------------------------------------------ -- A HUGE thanks to GravityScore for this! -- -- Adaptation of the Secure Hashing Algorithm (SHA-244/256) -- Found Here: http://lua-users.org/wiki/SecureHashAlgorithm -- -- Using an adapted version of the bit library -- Found Here: https://bitbucket.org/Boolsheet/bslf/src/1ee664885805/bit.lua -- local MOD = 2^32 local MODM = MOD-1 local function memoize(f) local mt = {} local t = setmetatable({}, mt) function mt:__index(k) local v = f(k) t[k] = v return v end return t end local function make_bitop_uncached(t, m) local function bitop(a, b) local res,p = 0,1 while a ~= 0 and b ~= 0 do local am, bm = a % m, b % m res = res + t[am][bm] * p a = (a - am) / m b = (b - bm) / m p = p*m end res = res + (a + b) * p return res end return bitop end local function make_bitop(t) local op1 = make_bitop_uncached(t,2^1) local op2 = memoize(function(a) return memoize(function(b) return op1(a, b) end) end) return make_bitop_uncached(op2, 2 ^ (t.n or 1)) end local bxor1 = make_bitop({[0] = {[0] = 0,[1] = 1}, [1] = {[0] = 1, [1] = 0}, n = 4}) local function bxor(a, b, c, ...) local z = nil if b then a = a % MOD b = b % MOD z = bxor1(a, b) if c then z = bxor(z, c, ...) end return z elseif a then return a % MOD else return 0 end end local function band(a, b, c, ...) local z if b then a = a % MOD b = b % MOD z = ((a + b) - bxor1(a,b)) / 2 if c then z = bit32_band(z, c, ...) end return z elseif a then return a % MOD else return MODM end end local function bnot(x) return (-1 - x) % MOD end local function rshift1(a, disp) if disp < 0 then return lshift(a,-disp) end return math.floor(a % 2 ^ 32 / 2 ^ disp) end local function rshift(x, disp) if disp > 31 or disp < -31 then return 0 end return rshift1(x % MOD, disp) end local function lshift(a, disp) if disp < 0 then return rshift(a,-disp) end return (a * 2 ^ disp) % 2 ^ 32 end local function rrotate(x, disp) x = x % MOD disp = disp % 32 local low = band(x, 2 ^ disp - 1) return rshift(x, disp) + lshift(low, 32 - disp) end local k = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, } local function str2hexa(s) return (string.gsub(s, ".", function(c) return string.format("%02x", string.byte(c)) end)) end local function num2s(l, n) local s = "" for i = 1, n do local rem = l % 256 s = string.char(rem) .. s l = (l - rem) / 256 end return s end local function s232num(s, i) local n = 0 for i = i, i + 3 do n = n*256 + string.byte(s, i) end return n end local function preproc(msg, len) local extra = 64 - ((len + 9) % 64) len = num2s(8 * len, 8) msg = msg .. "\128" .. string.rep("\0", extra) .. len assert(#msg % 64 == 0) return msg end local function initH256(H) H[1] = 0x6a09e667 H[2] = 0xbb67ae85 H[3] = 0x3c6ef372 H[4] = 0xa54ff53a H[5] = 0x510e527f H[6] = 0x9b05688c H[7] = 0x1f83d9ab H[8] = 0x5be0cd19 return H end local function digestblock(msg, i, H) local w = {} for j = 1, 16 do w[j] = s232num(msg, i + (j - 1)*4) end for j = 17, 64 do local v = w[j - 15] local s0 = bxor(rrotate(v, 7), rrotate(v, 18), rshift(v, 3)) v = w[j - 2] w[j] = w[j - 16] + s0 + w[j - 7] + bxor(rrotate(v, 17), rrotate(v, 19), rshift(v, 10)) end local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] for i = 1, 64 do local s0 = bxor(rrotate(a, 2), rrotate(a, 13), rrotate(a, 22)) local maj = bxor(band(a, b), band(a, c), band(b, c)) local t2 = s0 + maj local s1 = bxor(rrotate(e, 6), rrotate(e, 11), rrotate(e, 25)) local ch = bxor (band(e, f), band(bnot(e), g)) local t1 = h + s1 + ch + k[i] + w[i] h, g, f, e, d, c, b, a = g, f, e, d + t1, c, b, a, t1 + t2 end H[1] = band(H[1] + a) H[2] = band(H[2] + b) H[3] = band(H[3] + c) H[4] = band(H[4] + d) H[5] = band(H[5] + e) H[6] = band(H[6] + f) H[7] = band(H[7] + g) H[8] = band(H[8] + h) end local function sha256(msg) msg = preproc(msg, #msg) local H = initH256({}) for i = 1, #msg, 64 do digestblock(msg, i, H) end return str2hexa(num2s(H[1], 4) .. num2s(H[2], 4) .. num2s(H[3], 4) .. num2s(H[4], 4) .. num2s(H[5], 4) .. num2s(H[6], 4) .. num2s(H[7], 4) .. num2s(H[8], 4)) end ------------------------------------------------------------------------ -- PASSWORD CHANGING --------------------------------------------------- ------------------------------------------------------------------------ local function changePassword() write("Enter a password: ") local passwd1 = read("*") write("Confirm password: ") local passwd2 = read("*") if passwd2 ~= passwd1 then printError("Passwords do not match!") else settings.set( "far.password", sha256( passwd2 ) ) settings.save( config_file_path ) printPrimary("Password changed.") end end ------------------------------------------------------------------------ -- PERIPHERAL FUNCTIONS ------------------------------------------------ ------------------------------------------------------------------------ local function findAndOpenPeripheral() local sides = { "front", "back", "top", "bottom", "left", "right" } for i = 1, #sides do local side = sides[i] if peripheral.isPresent( side ) and peripheral.getType( side ) == "modem" then rednet.open( side ) return true end end printError( "Error: Could not find a modem." ) return false end ------------------------------------------------------------------------ -- SERVER FUNCTIONS ---------------------------------------------------- ------------------------------------------------------------------------ local function waitForClientMessage() local timer = os.startTimer( global_timeout ) while true do local event, senderId, message, protocol = os.pullEvent() if event == "rednet_message" then if senderId == connection.client_id and protocol == "far" then return message else rednet.send( senderId, { message = "err", error_message = "Server busy: Another client is currently connected." }, "far" ) end elseif event == "timer" and senderId == timer then return end end end local function sendClientMessage( type, message ) message.a = type rednet.send( connection.client_id, message, "far" ) end local function handleNewConnection( senderId, message ) connection = {} connection.client_id = senderId --Check the password with the one in settings settings.load( config_file_path ) if settings.get( "far.password" ) == message.passwd then --Check if the client is compatible if term.current().isColor() and not message.use_color then rednet.send( senderId, { message = "err", error_message = "This computer uses color, you need a color monitor to connect." }, "far" ) else connection.auth = true connection.use_color = term.current().isColor() connection.screen_updates = {} local w, h = term.getSize() rednet.send( senderId, { message = "ok:welcome", screen_w = w, screen_h = h }, "far" ) return true --Otherwise the session will be discarded end else rednet.send( senderId, { message = "err", error_message = "Incorrect password." }, "far" ) end end local function addScreenUpdate( contents ) if send_screen_next and not connection.screen_updates[1] then send_screen_next = false sendClientMessage( 1, { m = { contents } } ) else table.insert( connection.screen_updates, contents ) end end local function runSession( rterm ) local oldterm = term.current() term.redirect( rterm ) term.clear() term.setCursorPos( 1, 1 ) local function handleMessages() local timed_out = 0 while server_running do local message = waitForClientMessage() if message and type( message ) == "table" and message.a then timed_out = 0 if message.a == 1 then if message.t == 0 then if last_sent_messages > 0 then for i = 1, last_sent_messages do table.remove( connection.screen_updates, 1 ) end last_sent_messages = 0 else sendClientMessage( 3, { t = 8 } ) end send_screen_next = true end elseif message.a == 2 then os.queueEvent( unpack( message ) ) elseif message.a == 3 then if message.t == 1 then break end end else if timed_out > 3 then sendClientMessage( 3, { t = 2 } ) break else timed_out = timed_out + 1 end end end end local function runScreenBuffer() if send_screen_next and #connection.screen_updates ~= 0 then last_sent_messages = #connection.screen_updates sendClientMessage( 1, { m = connection.screen_updates } ) send_screen_next = false end coroutine.yield() end local function handleScreenBuffer() while server_running do runScreenBuffer() end end local ok, err = pcall( waitForAny, { true }, wrap( shell.run, "/rom/programs/shell" ), handleMessages, handleScreenBuffer ) sendClientMessage( 3, { t = 1 } ) connection = nil term.redirect( oldterm ) if not ok then printError( "An error occurred, and the connection was lost.") printError(": " .. err) end end local function runServer( getRTerm ) server_running = true local function postClear() local w, h = term.getSize() if term.isColor() then term.setBackgroundColor( colors.blue ) else term.setBackgroundColor( colors.black ) end term.clear() term.setTextColor( colors.white ) term.setCursorBlink( false ) local lines if w >= 45 and h >=9 then local xline = "computer and run \"far client "..os.getComputerID().."\"" local pnum = math.ceil((43 - xline:len()) / 2) lines = { "+---------------- [ F A R ] ----------------+", "| No client is connected. |", "| To connect, download far on another |", "|"..string.rep(" ", math.ceil((43 - xline:len()) / 2) + 1)..xline..string.rep(" ", 45 - (math.ceil((43 - xline:len()) / 2) + 3) - xline:len()).."|", "+-------------------------------------------+", } for i = 1, #lines - 1 do table.insert( lines, i*2, "| |" ) end else lines = { "\187 FAR \171", "No client is connected" } end local startLine = math.floor( ( h - #lines ) / 2 ) for i = 1, #lines do printCentered( lines[i], startLine + i ) end term.setBackgroundColor( colors.black ) end postClear() while server_running do --Wait for a new connection local senderId, message, protocol = rednet.receive( "far", global_timeout ) if senderId then if type( message ) == "table" and message.message == "init_connection" then --Handle the new connection. If it fails, reset. if not handleNewConnection( senderId, message ) then connection = nil else runSession( getRTerm() ) postClear() end end end end end ------------------------------------------------------------------------ -- CLIENT FUNCTIONS ---------------------------------------------------- ------------------------------------------------------------------------ local hScroll local vScroll local old_term local parentWindow local function setupWindow() local w, h = term.getSize() if connection.screen_w < w then hScroll = math.ceil(( connection.screen_w - w ) / 2 ) else hScroll = 0 end if connection.screen_h < h then vScroll = math.ceil(( connection.screen_h - h ) / 2 ) else vScroll = 0 end old_term = term.current() parentWindow = window.create( old_term, 1 - hScroll, 1 - vScroll, connection.screen_w, connection.screen_h, true ) term.redirect( parentWindow ) end local function rectifyBoundaries() local w, h = old_term.getSize() if hScroll > connection.screen_w - w then hScroll = connection.screen_w - w elseif hScroll < 0 then hScroll = 0 end if vScroll > connection.screen_h - h then vScroll = connection.screen_h - h elseif vScroll < 0 then vScroll = 0 end end local function scrollWindow( x, y ) if hScroll < 0 then x = 0 end if vScroll < 0 then y = 0 end if x == 0 and y == 0 then return end hScroll = hScroll + x vScroll = vScroll + y rectifyBoundaries() parentWindow.reposition( 1 - hScroll, 1 - vScroll ) end local function waitForServerMessage() local timer = os.startTimer( global_timeout ) while true do local event, senderId, message, protocol = os.pullEvent() if event == "rednet_message" then if senderId == connection.server_id and protocol == "far" then return message end elseif event == "timer" and senderId == timer then return end end end local function sendServerMessage( type, message ) message.a = type rednet.send( connection.server_id, message, "far" ) end local function disconnected( message ) connection = nil term.setCursorBlink( false ) term.redirect( old_term ) tDisp = nil term.clear() term.setCursorPos( 1, 1 ) printPrimary( message ) end local function disconnect( message ) sendServerMessage( 3, { t = 1 } ) disconnected( message ) end local function runMessages() local color = term.isColor() local timed_out = 0 while connection do local message = waitForServerMessage() if message then timed_out = 0 if message.a == 1 then -- Term update for i = 1, #message.m do local message = message.m[i] if message.t == 1 then if color then term.setBackgroundColor( message.c ) end elseif message.t == 2 then if color then term.setTextColor( message.c ) end elseif message.t == 3 then term.setCursorBlink( message.b ) elseif message.t == 4 then term.setCursorPos( message.x, message.y ) elseif message.t == 5 then term.blit( message.tx, message.f, message.b ) elseif message.t == 6 then term.write( message.s ) elseif message.t == 7 then term.clearLine() elseif message.t == 8 then term.scroll( message.n ) elseif message.t == 9 then term.clear() end end sendServerMessage( 1, { t = 0 } ) elseif message.a == 3 then if message.t == 1 then return disconnected( "Disconnected from server." ) elseif message.t == 2 then return disconnected( "Connection closed. (Server Error)" ) end end else if timed_out < 3 then timed_out = timed_out + 1 else sendServerMessage( 3, { t = 1 } ) return disconnected( "Connection to server lost." ) end sendServerMessage( 1, { t = 0 } ) end end end local function runEvents() local lAltPressed = false local lShiftPressed = false local rAltPressed = false local rShiftPressed = false local forwardEvents = { key = true, char = true, key_up = true, paste = true, mouse_click = true, mouse_up = true, mouse_scroll = true, mouse_drag = true, monitor_touch = true, monitor_resize = true, terminate = true } local translateEvents = { mouse_click = true, mouse_up = true, mouse_scroll = true, mouse_drag = true, monitor_touch = true } while connection do local eventData = { os.pullEventRaw() } local ignoreEvent = false if forwardEvents[eventData[1]] then if translateEvents[eventData[1]] then eventData[3] = eventData[3] + hScroll eventData[4] = eventData[4] + vScroll elseif eventData[1] == "key" then if eventData[2] == 42 then lShiftPressed = true end if eventData[2] == 54 then rShiftPressed = true end if eventData[2] == 56 then lAltPressed = true end if eventData[2] == 184 then rAltPressed = true end if (lShiftPressed or rShiftPressed) and (lAltPressed or rAltPressed) then local x = 0 local y = 0 if eventData[2] == 203 then x = x - 1 end if eventData[2] == 205 then x = x + 1 end if eventData[2] == 208 then y = y + 1 end if eventData[2] == 200 then y = y - 1 end scrollWindow( x, y ) if not (x == 0 and y == 0) then ignoreEvent = true end end elseif eventData[1] == "key_up" then if eventData[2] == 42 then lShiftPressed = false end if eventData[2] == 54 then rShiftPressed = false end if eventData[2] == 29 then lAltPressed = false end if eventData[2] == 157 then rAltPressed = false end end if not ignoreEvent then sendServerMessage( 2, eventData ) end end end end local function connectToServer( id ) --Sanity checks local id = tonumber( id ) if not id then printError( "Invalid host ID!" ) return false end if id == os.getComputerID() then printError( "Server ID cannot be the same as the client ID!" ) return false end --Ask for a password write("Password: ") local password = read("*") local password_hashed = sha256( password ) --Initiate connection connection = {} connection.server_id = id rednet.send( id, { message = "init_connection", passwd = password_hashed, use_color = term.isColor(), far_version = far_version }, "far" ) local message local function loadingSpinner() local x, y = term.getCursorPos() local steps = { "-", "\\", "|", "/" } local i = 1 while true do if i > #steps then i = 1 end write("Connecting "..steps[i]) term.setCursorPos( 1, y ) sleep(0.1) term.clearLine() i = i + 1 end end local function getResponse() message = waitForServerMessage() end parallel.waitForAny( getResponse, loadingSpinner ) print("Connecting...") if (not message) or message.message == "err" then printError("Connection failed: "..((message and message.error_message) or "Timed out.")) return false elseif message.message ~= "ok:welcome" then printError("Connection failed: Server sent unexpected message") return false end print( "Connected to server!" ) connection.auth = true term.clear() term.setCursorPos( 1, 1 ) connection.screen_w = message.screen_w connection.screen_h = message.screen_h setupWindow() sendServerMessage( 1, { t = 0 } ) waitForAny( { true }, runEvents, runMessages ) end ------------------------------------------------------------------------ -- REDIRECTED TERMINAL ------------------------------------------------- ------------------------------------------------------------------------ local cterm = term.current() local function getRTerm() local term = term.current() local rterm = {} rterm.setBackgroundColor = function( color ) term.setBackgroundColor( color ) addScreenUpdate({ t = 1, c = color }) end rterm.setBackgroundColour = function( colour ) rterm.setBackgroundColor( colour ) end rterm.setTextColor = function( color ) term.setTextColor( color ) addScreenUpdate({ t = 2, c = color }) end rterm.setTextColour = function( colour ) rterm.setTextColor( colour ) end rterm.setCursorBlink = function( blink ) term.setCursorBlink( blink ) addScreenUpdate({ t = 3, b = blink }) end rterm.setCursorPos = function( x, y ) term.setCursorPos( x, y ) addScreenUpdate({ t = 4, x = x, y = y }) end rterm.blit = function( text, textColor, backgroundColor ) term.blit( text, textColor, backgroundColor ) addScreenUpdate({ t = 5, tx = text, f = textColor, b = backgroundColor }) end rterm.write = function( string ) term.write( string ) addScreenUpdate({ t = 6, s = string }) end rterm.clearLine = function() term.clearLine() addScreenUpdate({ t = 7 }) end rterm.scroll = function( n ) term.scroll( n ) addScreenUpdate({ t = 8, n = n }) end rterm.clear = function() term.clear() addScreenUpdate({ t = 9 }) end rterm.isColor = function() if connection then return connection.use_color else return term.isColor() end end rterm.isColour = function() return rterm.isColor() end for name, func in pairs( term ) do if type(func) == "function" then if not rterm[name] then rterm[name] = func end end end return rterm end ------------------------------------------------------------------------ -- HELP / USAGE -------------------------------------------------------- ------------------------------------------------------------------------ local function showVersionInfo() local w, h = term.getSize() if term.isColor() then term.setBackgroundColor( colors.blue ) else term.setBackgroundColor( colors.black ) end term.clear() term.setTextColor( colors.white ) term.setCursorBlink( false ) local lines if w >= 37 and h >=14 then lines = { "+-----------------------------------+", "| FAR version "..far_version.." |", "+-----------------------------------+", "| Credits / Thanks to: |", "| \007 GravityScore for his sha256 lib |", "| \007 Viluon for LuaExtended and LECC |", "| \007 The people of oeed/Silica and \172 |", "| oeed/CCJam-2016 for awesomeness |", "+-----------------------------------+", "| Made with \003 by InternetUnexplorer |", "| for CCJam 2016 - \169 2016 |", "+-----------------------------------+", "Press a key to continue" } local startLine = math.floor( ( h - #lines ) / 2 ) for i = 1, #lines do printCentered( lines[i], startLine + i ) end os.pullEvent("key") os.pullEvent("char") else lines = { { "\187 FAR v"..far_version.." \171", "", "Made with \003 by", "InternetUnexplorer", "for CCJam 2016", "\169 2016" }, { "\007 Thanks To \007", "", "GravityScore for", "his sha256 lib" }, { "\007 Thanks To \007", "", "Viluon for", "LuaExtended & LECC" }, { "\007 Thanks To \007", "", "The people of", "oeed/Silica and", "oeed/CCJam-2016", "for awesomeness" } } for i=1, #lines do local clines = lines[i] table.insert( clines, "" ) table.insert( clines, "Press a key" ) local startLine = math.floor( ( h - #clines ) / 2 ) for i = 1, #clines do printCentered( clines[i], startLine + i ) end os.pullEvent("key") os.pullEvent("char") term.clear() end end term.setBackgroundColor( colors.black ) term.setCursorPos( 1, 1 ) term.clear() end local function printUsage() local help = { ["client_<ID>"] = "connects to server at <ID>", ["server"] = "starts the server so other computers can connect", ["password"] = "changes the password to connect to this computer", ["version"] = "displays credits and version info", } local function pc() if term.isColor() then term.setTextColor( primary_color ) else term.setTextColor( colors.gray ) end end local function rc() term.setTextColor( colors.white ) end print("Usage: ") for k, v in pairs( help ) do pc() term.write( "> " ) rc() print( "far "..k:gsub("_", " ") ) pc() term.write( " * " ) rc() print( v ) end end ------------------------------------------------------------------------ -- PUBLIC STATIC VOID MAIN (Just Kidding) ------------------------------ ------------------------------------------------------------------------ local args = {...} if #args < 1 then printUsage() else if args[1] == "client" then if #args < 2 then printError( "Missing Server ID" ) printUsage() else if findAndOpenPeripheral() then connectToServer( args[2] ) end end elseif args[1] == "server" then if findAndOpenPeripheral() then runServer( getRTerm ) end elseif args[1] == "password" then changePassword() elseif args[1] == "version" then showVersionInfo() elseif args[1] ~= "help" then print( "Unknown argument '"..args[1].."'" ) printUsage() end end