641 lines
16 KiB
Lua
641 lines
16 KiB
Lua
-- LevelOS
|
|
|
|
term.clear()
|
|
term.setCursorPos(1,1)
|
|
|
|
if not fs.exists("LevelOS") then
|
|
--Let the bios know the system is broken
|
|
fs.delete("/real_startup.lua")
|
|
os.reboot()
|
|
return
|
|
end
|
|
for k,v in pairs(colors) do
|
|
if type(v) == "number" then
|
|
term.setPaletteColor(v,term.native().getPaletteColor(v))
|
|
end
|
|
end
|
|
|
|
if lOS then
|
|
error("LevelOS is already running, silly!")
|
|
end
|
|
|
|
_G.lOS = {}
|
|
lOS.path = shell.getRunningProgram()
|
|
|
|
|
|
local chunkSize = 32768
|
|
if jit then
|
|
chunkSize = 4092
|
|
end
|
|
|
|
local function encodeUTF8(asciiText)
|
|
local utf8Text = ""
|
|
|
|
for i=1, #asciiText, chunkSize do
|
|
utf8Text = utf8Text..utf8.char(string.byte(asciiText, i, math.min(i + (chunkSize-1), #asciiText)))
|
|
end
|
|
|
|
return utf8Text
|
|
end
|
|
|
|
local function decodeUTF8(utf8Text)
|
|
local asciiText = ""
|
|
|
|
local ok, t = pcall(function()
|
|
return string.char(utf8.codepoint(utf8Text, 1, #utf8Text))
|
|
end)
|
|
|
|
if ok then
|
|
asciiText = t
|
|
else
|
|
local ok2, err = pcall(function()
|
|
for _, codepoint in utf8.codes(utf8Text) do
|
|
if codepoint < 256 then
|
|
asciiText = asciiText .. string.char(codepoint)
|
|
else
|
|
asciiText = asciiText .. "?"
|
|
end
|
|
end
|
|
end)
|
|
|
|
if not ok2 then
|
|
_G.debugCurrentText = asciiText
|
|
_G.debugTextInput = utf8Text
|
|
|
|
error(err, 2)
|
|
end
|
|
end
|
|
|
|
return asciiText
|
|
end
|
|
|
|
|
|
local function encodeAll(...)
|
|
local tbl = table.pack(...)
|
|
for k,v in pairs(tbl) do
|
|
if type(v) == "string" and not utf8.len(v) then
|
|
tbl[k] = encodeUTF8(v)
|
|
end
|
|
end
|
|
|
|
return table.unpack(tbl, 1, tbl.n)
|
|
end
|
|
|
|
local function decodeAll(...)
|
|
local tbl = table.pack(...)
|
|
for k,v in pairs(tbl) do
|
|
if type(v) == "string" then
|
|
tbl[k] = decodeUTF8(v)
|
|
end
|
|
end
|
|
|
|
return table.unpack(tbl, 1, tbl.n)
|
|
end
|
|
|
|
lOS.utf8 = {
|
|
decode = decodeUTF8,
|
|
encode = encodeUTF8,
|
|
decodeAll = decodeAll,
|
|
encodeAll = encodeAll,
|
|
}
|
|
|
|
local function extractVersion(str)
|
|
local version = str:match("ComputerCraft (%d+%.%d+%.%d+)")
|
|
return version
|
|
end
|
|
|
|
local function isVersionAbove(version1, version2)
|
|
local function splitVersion(version)
|
|
local parts = {}
|
|
for part in version:gmatch("(%d+)") do
|
|
table.insert(parts, tonumber(part))
|
|
end
|
|
return parts
|
|
end
|
|
|
|
local v1Parts = splitVersion(version1)
|
|
local v2Parts = splitVersion(version2)
|
|
|
|
for i = 1, math.max(#v1Parts, #v2Parts) do
|
|
local v1 = v1Parts[i] or 0
|
|
local v2 = v2Parts[i] or 0
|
|
if v1 > v2 then
|
|
return true
|
|
elseif v1 < v2 then
|
|
return false
|
|
end
|
|
end
|
|
|
|
return true -- They are equal if all parts are equal
|
|
end
|
|
|
|
if isVersionAbove(extractVersion(_HOST), "1.109") then
|
|
local fopen = fs.open
|
|
|
|
function fs.open(path, mode)
|
|
local f = fopen(path, mode)
|
|
if not f then return nil end
|
|
|
|
local customHandle = {}
|
|
|
|
for k,v in pairs(f) do
|
|
if mode:find("b") then
|
|
customHandle[k] = function(...) return v(...) end
|
|
else
|
|
customHandle[k] = function(...) return decodeAll(v(encodeAll(...))) end
|
|
end
|
|
end
|
|
|
|
return customHandle
|
|
end
|
|
end
|
|
|
|
|
|
--enable mouse if no color
|
|
if not rtype then
|
|
_G.rtype = type
|
|
_G.type = function(obj)
|
|
local mt = getmetatable(obj)
|
|
if rtype(mt) == "table" and mt.__type then
|
|
if rtype(mt.__type) == "string" then
|
|
return mt.__type
|
|
elseif rtype(mt.__type) == "function" then
|
|
return mt.__type(obj)
|
|
end
|
|
else
|
|
return rtype(obj)
|
|
end
|
|
end
|
|
end
|
|
|
|
function _G.pairs(t)
|
|
local mt = getmetatable(t)
|
|
if mt and type(mt.__pairs) == "function" then
|
|
return mt.__pairs(t)
|
|
else
|
|
return next, t, nil
|
|
end
|
|
end
|
|
|
|
if not hardreboot then
|
|
_G.hardreboot = os.reboot
|
|
end
|
|
|
|
if fs.combine("a","b","c") == fs.combine("a","b") then
|
|
local ocombine = fs.combine
|
|
function fs.combine(path,...)
|
|
local parts = {...}
|
|
for p=1,#parts do
|
|
path = ocombine(path,parts[p])
|
|
end
|
|
return path
|
|
end
|
|
end
|
|
|
|
local w,h = term.getSize()
|
|
if h < 19 then return end
|
|
local newwin = false
|
|
|
|
local therealOGterm = term.current()
|
|
local function tokenise( ... )
|
|
local sLine = table.concat( { ... }, " " )
|
|
local tWords = {}
|
|
local bQuoted = false
|
|
for match in string.gmatch( sLine .. "\"", "(.-)\"" ) do
|
|
if bQuoted then
|
|
table.insert( tWords, match )
|
|
else
|
|
for m in string.gmatch( match, "[^ \t]+" ) do
|
|
table.insert( tWords, m )
|
|
end
|
|
end
|
|
bQuoted = not bQuoted
|
|
end
|
|
return tWords
|
|
end
|
|
|
|
if fs.exists("AppData") == false then
|
|
fs.makeDir("AppData")
|
|
end
|
|
|
|
if fs.exists("User") == false then
|
|
fs.makeDir("User")
|
|
fs.makeDir("User/Documents")
|
|
fs.makeDir("User/Images")
|
|
fs.makeDir("User/Scripts")
|
|
fs.makeDir("User/Downloads")
|
|
end
|
|
|
|
if not fs.exists("bigfont") then
|
|
--Let the bios know the system is broken
|
|
fs.delete("/real_startup.lua")
|
|
os.reboot()
|
|
return
|
|
end
|
|
|
|
local to_colors, to_blit = {}, {}
|
|
for i = 1, 16 do
|
|
to_blit[2^(i-1)] = ("0123456789abcdef"):sub(i, i)
|
|
to_colors[("0123456789abcdef"):sub(i, i)] = 2^(i-1)
|
|
end
|
|
|
|
|
|
|
|
local function toColor(theblit)
|
|
return to_colors[theblit] or nil
|
|
end
|
|
|
|
|
|
|
|
local function toBlit(thecolor)
|
|
return to_blit[thecolor] or nil
|
|
end
|
|
|
|
local function render(spr,x,y)
|
|
local format = "lImg"
|
|
if format == "lImg" then
|
|
local sW,sH = #spr[1][1],#spr
|
|
local w,h = term.getSize()
|
|
for l=1,#spr do
|
|
if not y then
|
|
term.setCursorPos(math.ceil(w/2)-math.floor(sW/2),(math.ceil(h/2)-math.floor(sH/2)+(l-1))+x)
|
|
else
|
|
term.setCursorPos(x,y+(l-1))
|
|
end
|
|
local bl = {}
|
|
bl[1] = spr[l][1]
|
|
bl[2] = string.gsub(spr[l][2],"T",toBlit(term.getBackgroundColor()))
|
|
bl[3] = string.gsub(spr[l][3],"T",toBlit(term.getBackgroundColor()))
|
|
term.blit(unpack(bl))
|
|
end
|
|
elseif format == "nfp" or format == "nfg" then
|
|
local b,e = string.find(spr,"\n")
|
|
local sW,sH
|
|
local w,h = term.getSize()
|
|
local lines,sW = getLines(spr)
|
|
sH = #lines
|
|
for l=1,sH do
|
|
term.setCursorPos(math.ceil(w/2)-math.floor(sW/2),math.ceil(h/2)-math.floor(sH/2)+(l-1))
|
|
term.blit(string.rep(" ",#lines[l]),lines[l],lines[l])
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local function fread(file)
|
|
local f = fs.open(file,"r")
|
|
local o = f.readAll()
|
|
f.close()
|
|
return o
|
|
end
|
|
|
|
local function fwrite(file,content)
|
|
local f = fs.open(file,"w")
|
|
f.write(content)
|
|
f.close()
|
|
return true
|
|
end
|
|
|
|
local loadingico = textutils.unserialize(fread("LevelOS/assets/loading.limg"))
|
|
|
|
|
|
_G.bigfont = loadfile("bigfont",_ENV)()
|
|
|
|
|
|
local progress = 0
|
|
|
|
local function centerText(text,customY,customLen) -- i tried to put indentation but pastebin is being stupid for some reason
|
|
local x,y = term.getSize()
|
|
local x2,y2 = term.getCursorPos()
|
|
if customY then y2 = customY end
|
|
local len = customLen or text:len()
|
|
term.setCursorPos((math.ceil(x / 2) - math.floor(len / 2)), y2)
|
|
term.write(text)
|
|
term.setCursorPos(x2,y2+1)
|
|
end
|
|
|
|
local doUpdate
|
|
local bootText = "Initializing"
|
|
local dots = 1
|
|
local frame = 1
|
|
|
|
local function bootscreen()
|
|
term.setBackgroundColor(colors.black)
|
|
term.setTextColor(colors.cyan)
|
|
term.clear()
|
|
local w,h = term.getSize()
|
|
if h > 19 then
|
|
if fs.exists("LevelOS/assets/logo_christmas.limg") and os.date("%m") == "12" then
|
|
render(textutils.unserialize(fread("LevelOS/assets/logo_christmas.limg"))[1],math.ceil(w/2)-4,h/2-8)
|
|
elseif fs.exists("LevelOS/assets/logo_pride.limg") then
|
|
render(textutils.unserialize(fread("LevelOS/assets/logo_pride.limg"))[1],math.ceil(w/2)-4,h/2-8)
|
|
else
|
|
bigfont.writeOn(term.current(),2,"B",nil,h/2-8)
|
|
end
|
|
else
|
|
if fs.exists("LevelOS/assets/logo_christmas.limg") and os.date("%m") == "12" then
|
|
render(textutils.unserialize(fread("LevelOS/assets/logo_christmas.limg"))[1],math.ceil(w/2)-4,h/2-5)
|
|
elseif fs.exists("LevelOS/assets/logo_pride.limg") then
|
|
render(textutils.unserialize(fread("LevelOS/assets/logo_pride.limg"))[1],math.ceil(w/2)-4,h/2-5)
|
|
else
|
|
bigfont.writeOn(term.current(),2,"B",nil,h/2-5)
|
|
end
|
|
end
|
|
while true do
|
|
if doUpdate then
|
|
term.setBackgroundColor(colors.blue)
|
|
term.setTextColor(colors.white)
|
|
term.clear()
|
|
local w,h = term.getSize()
|
|
term.setCursorPos(1,math.ceil(h/2))
|
|
centerText("Getting ready for updates")
|
|
centerText("Do not turn off your computer")
|
|
local init = false
|
|
while true do
|
|
render(loadingico[frame],-5)
|
|
if progress > 0 then
|
|
if not init then
|
|
term.setBackgroundColor(colors.blue)
|
|
term.setTextColor(colors.white)
|
|
term.clear()
|
|
render(loadingico[frame],-5)
|
|
init = true
|
|
end
|
|
term.setCursorPos(1,math.ceil(h/2))
|
|
centerText("Working on updates")
|
|
centerText(math.floor(progress + 0.5).."% complete")
|
|
centerText("Do not turn off your computer")
|
|
end
|
|
frame = frame+1
|
|
if frame > #loadingico then
|
|
frame = 1
|
|
end
|
|
os.sleep(0.1)
|
|
end
|
|
end
|
|
if h > 19 then
|
|
render(loadingico[frame],10)
|
|
if bootText then
|
|
centerText(" "..bootText..string.rep(".",dots).." ",h/2+14,#(" "..bootText.." "))
|
|
end
|
|
else
|
|
render(loadingico[frame],7)
|
|
end
|
|
os.pullEvent()
|
|
end
|
|
end
|
|
|
|
local function loadIco()
|
|
while true do
|
|
frame = frame+1
|
|
if frame > #loadingico then
|
|
frame = 1
|
|
end
|
|
dots = dots+0.5
|
|
if dots > 3 then dots = 0 end
|
|
os.sleep(0.1)
|
|
end
|
|
end
|
|
|
|
local function download(pth)
|
|
if fs.getDir(path) ~= "" then
|
|
fs.makeDir(fs.getDir(path))
|
|
end
|
|
|
|
fs.move("tmp/" .. pth, pth)
|
|
os.sleep(fs.getSize(pth) / 10000)
|
|
end
|
|
|
|
local step = 0.05
|
|
local function update()
|
|
bootText = "Connecting to server"
|
|
os.sleep(step)
|
|
bootText = "Checking client version"
|
|
os.sleep(step)
|
|
bootText = "Looking for updates"
|
|
os.sleep(step)
|
|
bootText = "Processing"
|
|
os.sleep(step)
|
|
|
|
local tree = {}
|
|
local timestamp = 0
|
|
tree["LevelOS/system.lua"] = {timestamp=timestamp}
|
|
tree["LevelOS/startup/lUtils.lua"] = {timestamp=timestamp}
|
|
tree["LevelOS/SystemUI.lua"] = {timestamp=timestamp}
|
|
tree["LevelOS/assets/Compact_Icons.limg"] = {timestamp=timestamp}
|
|
tree["LevelOS/Login_screen.sgui"] = {timestamp=timestamp}
|
|
tree["LevelOS/desktop.lua"] = {timestamp=timestamp}
|
|
tree["LevelOS/assets/Desktop_Icons.limg"] = {timestamp=timestamp}
|
|
tree["LevelOS/Task_Manager.lua"] = {timestamp=timestamp}
|
|
tree["LevelOS/Clock.lua"] = {timestamp=timestamp}
|
|
tree["Program_Files/LevelOS/Explorer/main.lua"] = {timestamp=timestamp}
|
|
tree["blittle"] = {timestamp=timestamp}
|
|
tree["Program_Files/LevelOS/Explorer/icon.limg"] = {timestamp=timestamp}
|
|
tree["Program_Files/LevelOS/Notepad/main.lua"] = {timestamp=timestamp}
|
|
tree["Program_Files/LevelOS/Notepad/icon.limg"] = {timestamp=timestamp}
|
|
tree["Program_Files/LevelOS/Lua/main.lua"] = {timestamp=timestamp}
|
|
tree["Program_Files/LevelOS/Lua/icon.limg"] = {timestamp=timestamp}
|
|
local tFiles = #tree
|
|
|
|
|
|
if not fs.exists("LevelOS/data/version.txt") then
|
|
if not searchFolder("") then return false end
|
|
|
|
for k,v in pairs(tree) do
|
|
if download(k,root,v.timestamp) == true then
|
|
progress = progress+(100/tFiles)
|
|
end
|
|
end
|
|
fwrite("LevelOS/data/version.txt", "Befator Inc")
|
|
fs.delete("tmp")
|
|
os.sleep(1)
|
|
os.reboot()
|
|
end
|
|
bootText = "Loading system"
|
|
os.sleep(step)
|
|
end
|
|
|
|
|
|
parallel.waitForAny(update,bootscreen,loadIco)
|
|
|
|
local expect = require "cc.expect".expect
|
|
|
|
local function wrap(txt,width)
|
|
local lines = {}
|
|
for line in txt:gmatch("([^\n]*)\n?") do
|
|
table.insert(lines,"")
|
|
for word in line:gmatch("%S*%s?") do
|
|
if #lines[#lines]+#word > width and #lines[#lines] > 0 then
|
|
lines[#lines+1] = ""
|
|
end
|
|
if #lines[#lines]+#word > width then
|
|
local tWord = word
|
|
while #lines[#lines]+#tWord > width do
|
|
print(tWord:sub(1,width))
|
|
lines[#lines] = tWord:sub(1,width)
|
|
table.insert(lines,"")
|
|
tWord = tWord:sub(width+1)
|
|
end
|
|
lines[#lines] = tWord
|
|
else
|
|
lines[#lines] = lines[#lines]..word
|
|
end
|
|
end
|
|
end
|
|
if txt:sub(#txt) == "\n" then
|
|
table.insert(lines,"")
|
|
end
|
|
return lines
|
|
end
|
|
|
|
local function bPrint(txt)
|
|
local x,y = term.getCursorPos()
|
|
local w,h = term.getSize()
|
|
|
|
local text = wrap(txt,w-(x-1))
|
|
for t=1,#text do
|
|
term.write(text[t])
|
|
term.setCursorPos(x,y+t)
|
|
end
|
|
end
|
|
|
|
local function wordwrap(str)
|
|
local x,y = term.getCursorPos()
|
|
local tW,tH = term.getSize()
|
|
for w in str:gmatch("%S+") do
|
|
local x1,y1 = term.getCursorPos()
|
|
if x1+(#w*3) >= tW then
|
|
bigfont.bigPrint(" ")
|
|
local x2,y2 = term.getCursorPos()
|
|
term.setCursorPos(x,y2)
|
|
end
|
|
bigfont.bigWrite(w.." ")
|
|
end
|
|
end
|
|
|
|
|
|
if fs.exists("LevelOS/lStore.lua") then
|
|
shell.setAlias("lStore","LevelOS/lStore.lua")
|
|
|
|
local completion = require "cc.shell.completion"
|
|
local function completelStorePut(shell, text, previous)
|
|
if previous[2] == "put" then
|
|
return fs.complete(text, "User/Cloud", true, false)
|
|
end
|
|
end
|
|
|
|
shell.setCompletionFunction("LevelOS/lStore.lua", completion.build(
|
|
{ completion.choice, { "put ", "get ", "run " } },
|
|
completelStorePut
|
|
))
|
|
end
|
|
|
|
if jit then
|
|
shell.run("lStore get JITAlert LevelOS/startup/JITAlert2.lua")
|
|
if fs.exists("LevelOS/startup/JITAlert2.lua") then
|
|
if fs.exists("LevelOS/startup/JITAlert.lua") then
|
|
fs.delete("LevelOS/startup/JITAlert.lua")
|
|
end
|
|
fs.move("LevelOS/startup/JITAlert2.lua","LevelOS/startup/JITAlert.lua")
|
|
end
|
|
elseif fs.exists("LevelOS/startup/JITAlert.lua") then
|
|
fs.delete("LevelOS/startup/JITAlert.lua")
|
|
end
|
|
|
|
local u
|
|
|
|
local u = {pcall(loadfile("LevelOS/system.lua",_ENV))}
|
|
_G.whatitreturn = u
|
|
local link
|
|
local copied = false
|
|
local crashwin
|
|
function bsodRender()
|
|
term.redirect(therealOGterm)
|
|
term.setPaletteColor(colors.blue,0,120/255,215/255)
|
|
term.setPaletteColor(colors.white,1,1,1)
|
|
term.setCursorPos(4,4)
|
|
term.setBackgroundColor(colors.blue)
|
|
term.clear()
|
|
local w,h = term.getSize()
|
|
if w > 140 then
|
|
crashwin = window.create(therealOGterm,12,8,w/2+10,h-12)
|
|
bigfont.writeOn(therealOGterm,3,"L",w-40,math.ceil(h/2)-10)
|
|
else
|
|
crashwin = window.create(therealOGterm,4,5,w-8,h-5)
|
|
end
|
|
term.redirect(crashwin)
|
|
term.setBackgroundColor(colors.blue)
|
|
term.clear()
|
|
if w > 110 and h > 40 and fs.exists("LevelOS/assets/QR_Code.limg") then
|
|
local qrcode = textutils.unserialize(fread("LevelOS/assets/QR_Code.limg"))
|
|
bigfont.hugePrint(":(")
|
|
wordwrap("Your PC ran into a problem and needs to restart. Please press space to continue.")
|
|
print("\n\n\n\n\n\n")
|
|
local x,y = term.getCursorPos()
|
|
render(qrcode[1],x,y)
|
|
term.setCursorPos(x+22,y)
|
|
bPrint("For more information about this issue and possible fixes, visit ")
|
|
local txt = "https://discord.gg/vBsjGqy99U"
|
|
if copied then
|
|
txt = txt.." (copied to clipboard!)"
|
|
end
|
|
local tx,ty = term.getCursorPos()
|
|
local tw,th = term.getSize()
|
|
if tx+(#txt-1) > tw then
|
|
ty = ty+1
|
|
tx = x+22
|
|
end
|
|
if ccemux and ccemux.setClipboard then
|
|
link = {x=tx,y=ty,w=#txt,h=1,txt="https://discord.gg/vBsjGqy99U"}
|
|
if copied then
|
|
term.setTextColor(colors.cyan)
|
|
else
|
|
term.setTextColor(colors.lightBlue)
|
|
term.setCursorPos(link.x,link.y+1)
|
|
term.write(string.rep("\131",link.w))
|
|
end
|
|
end
|
|
term.setCursorPos(tx,ty)
|
|
term.write(txt)
|
|
term.setTextColor(colors.white)
|
|
term.setCursorPos(x+22,y+(#qrcode[1]-3))
|
|
bPrint("If you contact support, give them this info:")
|
|
term.setCursorPos(x+22,y+(#qrcode[1]-1))
|
|
bPrint(u[2])
|
|
else
|
|
bigfont.bigPrint(":(")
|
|
print("\nYour PC ran into a problem and needs to restart. Please press space to continue.")
|
|
print("\nError:")
|
|
print(u[2])
|
|
end
|
|
end
|
|
bsodRender()
|
|
os.sleep(1)
|
|
while true do
|
|
local e = {os.pullEventRaw()}
|
|
if e[1] == "mouse_click" then
|
|
local x,y = crashwin.getPosition()
|
|
e[3] = e[3]-(x-1)
|
|
e[4] = e[4]-(y-1)
|
|
end
|
|
if e[1] == "key" and e[2] == keys.space then
|
|
hardreboot()
|
|
elseif e[1] == "term_resize" then
|
|
bsodRender()
|
|
elseif link and e[1] == "mouse_click" and e[3] >= link.x and e[4] == link.y and e[3] <= link.x+(link.w-1) then
|
|
ccemux.setClipboard(link.txt)
|
|
copied = true
|
|
bsodRender()
|
|
elseif link and e[1] == "mouse_click" then
|
|
--[[term.setCursorPos(1,1)
|
|
print(textutils.serialize(link))
|
|
print(textutils.serialize(e))]]
|
|
elseif e[1] == "terminate" then
|
|
term.redirect(therealOGterm)
|
|
break
|
|
end
|
|
end
|
|
|
|
-- TEMP FOR DEBUGGING
|
|
term.setBackgroundColor(colors.black) term.clear() term.setCursorPos(1,1)
|
|
shell.run("shell") |