Advertisement
Guest User

Untitled

a guest
Jun 16th, 2019
96
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 54.73 KB | None | 0 0
  1. --[[
  2. wave-amp version 1.0.0
  3.  
  4. The MIT License (MIT)
  5. Copyright (c) 2016 CrazedProgrammer
  6.  
  7. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
  8. associated documentation files (the "Software"), to deal in the Software without restriction,
  9. including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
  10. and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do
  11. so, subject to the following conditions:
  12.  
  13. The above copyright notice and this permission notice shall be included in all copies or
  14. substantial portions of the Software.
  15.  
  16. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  17. INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  18. PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  19. COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
  20. AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  21. WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22. ]]
  23.  
  24. --[[
  25. wave version 0.1.4
  26.  
  27. The MIT License (MIT)
  28. Copyright (c) 2016 CrazedProgrammer
  29.  
  30. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
  31. associated documentation files (the "Software"), to deal in the Software without restriction,
  32. including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
  33. and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do
  34. so, subject to the following conditions:
  35.  
  36. The above copyright notice and this permission notice shall be included in all copies or
  37. substantial portions of the Software.
  38.  
  39. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  40. INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  41. PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  42. COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
  43. AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  44. WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  45. ]]
  46.  
  47. local wave = { }
  48. wave.version = "0.1.4"
  49.  
  50. wave._oldSoundMap = {"harp", "bassattack", "bd", "snare", "hat"}
  51. wave._newSoundMap = {"harp", "bass", "basedrum", "snare", "hat"}
  52. wave._defaultThrottle = 99
  53. wave._defaultClipMode = 1
  54. wave._maxInterval = 1
  55. wave._isNewSystem = false
  56. if _HOST then
  57. wave._isNewSystem = _HOST:sub(15, #_HOST) >= "1.80"
  58. end
  59.  
  60. wave.context = { }
  61. wave.output = { }
  62. wave.track = { }
  63. wave.instance = { }
  64.  
  65. function wave.createContext(clock, volume)
  66. clock = clock or os.clock()
  67. volume = volume or 1.0
  68.  
  69. local context = setmetatable({ }, {__index = wave.context})
  70. context.outputs = { }
  71. context.instances = { }
  72. context.vs = {0, 0, 0, 0, 0}
  73. context.prevClock = clock
  74. context.volume = volume
  75. return context
  76. end
  77.  
  78. function wave.context:addOutput(...)
  79. local output = wave.createOutput(...)
  80. self.outputs[#self.outputs + 1] = output
  81. return output
  82. end
  83.  
  84. function wave.context:addOutputs(...)
  85. local outs = {...}
  86. if #outs == 1 then
  87. if not getmetatable(outs) then
  88. outs = outs[1]
  89. else
  90. if getmetatable(outs).__index ~= wave.outputs then
  91. outs = outs[1]
  92. end
  93. end
  94. end
  95. for i = 1, #outs do
  96. self:addOutput(outs[i])
  97. end
  98. end
  99.  
  100. function wave.context:removeOutput(out)
  101. if type(out) == "number" then
  102. table.remove(self.outputs, out)
  103. return
  104. elseif type(out) == "table" then
  105. if getmetatable(out).__index == wave.output then
  106. for i = 1, #self.outputs do
  107. if out == self.outputs[i] then
  108. table.remove(self.outputs, i)
  109. return
  110. end
  111. end
  112. return
  113. end
  114. end
  115. for i = 1, #self.outputs do
  116. if out == self.outputs[i].native then
  117. table.remove(self.outputs, i)
  118. return
  119. end
  120. end
  121. end
  122.  
  123. function wave.context:addInstance(...)
  124. local instance = wave.createInstance(...)
  125. self.instances[#self.instances + 1] = instance
  126. return instance
  127. end
  128.  
  129. function wave.context:removeInstance(instance)
  130. if type(instance) == "number" then
  131. table.remove(self.instances, instance)
  132. else
  133. for i = 1, #self.instances do
  134. if self.instances == instance then
  135. table.remove(self.instances, i)
  136. return
  137. end
  138. end
  139. end
  140. end
  141.  
  142. function wave.context:playNote(note, pitch, volume)
  143. volume = volume or 1.0
  144.  
  145. self.vs[note] = self.vs[note] + volume
  146. for i = 1, #self.outputs do
  147. self.outputs[i]:playNote(note, pitch, volume * self.volume)
  148. end
  149. end
  150.  
  151. function wave.context:update(interval)
  152. local clock = os.clock()
  153. interval = interval or (clock - self.prevClock)
  154.  
  155. self.prevClock = clock
  156. if interval > wave._maxInterval then
  157. interval = wave._maxInterval
  158. end
  159. for i = 1, #self.outputs do
  160. self.outputs[i].notes = 0
  161. end
  162. for i = 1, 5 do
  163. self.vs[i] = 0
  164. end
  165. if interval > 0 then
  166. for i = 1, #self.instances do
  167. local notes = self.instances[i]:update(interval)
  168. for j = 1, #notes / 3 do
  169. self:playNote(notes[j * 3 - 2], notes[j * 3 - 1], notes[j * 3])
  170. end
  171. end
  172. end
  173. end
  174.  
  175.  
  176.  
  177. function wave.createOutput(out, volume, filter, throttle, clipMode)
  178. volume = volume or 1.0
  179. filter = filter or {true, true, true, true, true}
  180. throttle = throttle or wave._defaultThrottle
  181. clipMode = clipMode or wave._defaultClipMode
  182.  
  183. local output = setmetatable({ }, {__index = wave.output})
  184. output.native = out
  185. output.volume = volume
  186. output.filter = filter
  187. output.notes = 0
  188. output.throttle = throttle
  189. output.clipMode = clipMode
  190. if type(out) == "function" then
  191. output.nativePlayNote = out
  192. output.type = "custom"
  193. return output
  194. elseif type(out) == "string" then
  195. if peripheral.getType(out) == "iron_noteblock" then
  196. if wave._isNewSystem then
  197. local nb = peripheral.wrap(out)
  198. output.type = "iron_noteblock"
  199. function output.nativePlayNote(note, pitch, volume)
  200. if output.volume * volume > 0 then
  201. nb.playSound("minecraft:block.note."..wave._newSoundMap[note], volume, math.pow(2, (pitch - 12) / 12))
  202. end
  203. end
  204. return output
  205. end
  206. end
  207. elseif type(out) == "table" then
  208. if out.execAsync then
  209. output.type = "commands"
  210. if wave._isNewSystem then
  211. function output.nativePlayNote(note, pitch, volume)
  212. out.execAsync("playsound minecraft:block.note."..wave._newSoundMap[note].." record @a ~ ~ ~ "..tostring(volume).." "..tostring(math.pow(2, (pitch - 12) / 12)))
  213. end
  214. else
  215. function output.nativePlayNote(note, pitch, volume)
  216. out.execAsync("playsound note."..wave._oldSoundMap[note].." @a ~ ~ ~ "..tostring(volume).." "..tostring(math.pow(2, (pitch - 12) / 12)))
  217. end
  218. end
  219. return output
  220. elseif getmetatable(out) then
  221. if getmetatable(out).__index == wave.output then
  222. return out
  223. end
  224. end
  225. end
  226. end
  227.  
  228. function wave.scanOutputs()
  229. local outs = { }
  230. if commands then
  231. outs[#outs + 1] = wave.createOutput(commands)
  232. end
  233. local sides = peripheral.getNames()
  234. for i = 1, #sides do
  235. if peripheral.getType(sides[i]) == "iron_noteblock" then
  236. outs[#outs + 1] = wave.createOutput(sides[i])
  237. end
  238. end
  239. return outs
  240. end
  241.  
  242. function wave.output:playNote(note, pitch, volume)
  243. volume = volume or 1.0
  244.  
  245. if self.clipMode == 1 then
  246. if pitch < 0 then
  247. pitch = 0
  248. elseif pitch > 24 then
  249. pitch = 24
  250. end
  251. elseif self.clipMode == 2 then
  252. if pitch < 0 then
  253. while pitch < 0 do
  254. pitch = pitch + 12
  255. end
  256. elseif pitch > 24 then
  257. while pitch > 24 do
  258. pitch = pitch - 12
  259. end
  260. end
  261. end
  262. if self.filter[note] and self.notes < self.throttle then
  263. self.nativePlayNote(note, pitch, volume * self.volume)
  264. self.notes = self.notes + 1
  265. end
  266. end
  267.  
  268.  
  269.  
  270. function wave.loadTrack(path)
  271. local track = setmetatable({ }, {__index = wave.track})
  272. local handle = fs.open(path, "rb")
  273. if not handle then return end
  274.  
  275. local function readInt(size)
  276. local num = 0
  277. for i = 0, size - 1 do
  278. local byte = handle.read()
  279. if not byte then -- dont leave open file handles no matter what
  280. handle.close()
  281. return
  282. end
  283. num = num + byte * (256 ^ i)
  284. end
  285. return num
  286. end
  287. local function readStr()
  288. local length = readInt(4)
  289. if not length then return end
  290. local data = { }
  291. for i = 1, length do
  292. data[i] = string.char(handle.read())
  293. end
  294. return table.concat(data)
  295. end
  296.  
  297. -- Part #1: Metadata
  298. track.length = readInt(2) -- song length (ticks)
  299. track.height = readInt(2) -- song height
  300. track.name = readStr() -- song name
  301. track.author = readStr() -- song author
  302. track.originalAuthor = readStr() -- original song author
  303. track.description = readStr() -- song description
  304. track.tempo = readInt(2) / 100 -- tempo (ticks per second)
  305. track.autoSaving = readInt(1) == 0 and true or false -- auto-saving
  306. track.autoSavingDuration = readInt(1) -- auto-saving duration
  307. track.timeSignature = readInt(1) -- time signature (3 = 3/4)
  308. track.minutesSpent = readInt(4) -- minutes spent
  309. track.leftClicks = readInt(4) -- left clicks
  310. track.rightClicks = readInt(4) -- right clicks
  311. track.blocksAdded = readInt(4) -- blocks added
  312. track.blocksRemoved = readInt(4) -- blocks removed
  313. track.schematicFileName = readStr() -- midi/schematic file name
  314.  
  315. -- Part #2: Notes
  316. track.layers = { }
  317. for i = 1, track.height do
  318. track.layers[i] = {name = "Layer "..i, volume = 1.0}
  319. track.layers[i].notes = { }
  320. end
  321.  
  322. local tick = 0
  323. while true do
  324. local tickJumps = readInt(2)
  325. if tickJumps == 0 then break end
  326. tick = tick + tickJumps
  327. local layer = 0
  328. while true do
  329. local layerJumps = readInt(2)
  330. if layerJumps == 0 then break end
  331. layer = layer + layerJumps
  332. if layer > track.height then -- nbs can be buggy
  333. for i = track.height + 1, layer do
  334. track.layers[i] = {name = "Layer "..i, volume = 1.0}
  335. track.layers[i].notes = { }
  336. end
  337. track.height = layer
  338. end
  339. local instrument = readInt(1)
  340. local key = readInt(1)
  341. if instrument <= 4 then -- nbs can be buggy
  342. track.layers[layer].notes[tick * 2 - 1] = instrument + 1
  343. track.layers[layer].notes[tick * 2] = key - 33
  344. end
  345. end
  346. end
  347.  
  348. -- Part #3: Layers
  349. for i = 1, track.height do
  350. local name = readStr()
  351. if not name then break end -- if layer data doesnt exist, abort
  352. track.layers[i].name = name
  353. track.layers[i].volume = readInt(1) / 100
  354. end
  355.  
  356. handle.close()
  357. return track
  358. end
  359.  
  360.  
  361.  
  362. function wave.createInstance(track, volume, playing, loop)
  363. volume = volume or 1.0
  364. playing = (playing == nil) or playing
  365. loop = (loop ~= nil) and loop
  366.  
  367. if getmetatable(track).__index == wave.instance then
  368. return track
  369. end
  370. local instance = setmetatable({ }, {__index = wave.instance})
  371. instance.track = track
  372. instance.volume = volume or 1.0
  373. instance.playing = playing
  374. instance.loop = loop
  375. instance.tick = 1
  376. return instance
  377. end
  378.  
  379. function wave.instance:update(interval)
  380. local notes = { }
  381. if self.playing then
  382. local dticks = interval * self.track.tempo
  383. local starttick = self.tick
  384. local endtick = starttick + dticks
  385. local istarttick = math.ceil(starttick)
  386. local iendtick = math.ceil(endtick) - 1
  387. for i = istarttick, iendtick do
  388. for j = 1, self.track.height do
  389. if self.track.layers[j].notes[i * 2 - 1] then
  390. notes[#notes + 1] = self.track.layers[j].notes[i * 2 - 1]
  391. notes[#notes + 1] = self.track.layers[j].notes[i * 2]
  392. notes[#notes + 1] = self.track.layers[j].volume
  393. end
  394. end
  395. end
  396. self.tick = self.tick + dticks
  397.  
  398. if endtick > self.track.length then
  399. self.tick = 1
  400. self.playing = self.loop
  401. end
  402. end
  403. return notes
  404. end
  405.  
  406.  
  407.  
  408. local cmdHelp = [[
  409. -l lists all outputs connected to the computer.
  410. -c <config file> loads the parameters from a file.
  411. parameters are separated by newlines.
  412. -t <theme file> loads the theme from a file.
  413. -f <filter[:second]> sets the note filter for the outputs.
  414. examples:
  415. -f 10111 sets the filter for all outputs to remove the bass instrument.
  416. -f 10011:01100 sets the filter so the bass and basedrum instruments only come out of the second output
  417. -v <volume[:second]> sets the volume for the outputs.
  418. --nrm --stp --rep --shf sets the play mode.
  419. --noui --noinput disables the ui/keyboard input]]
  420.  
  421.  
  422. local trackMode = 1
  423. -- 1 = normal (go to next song on finish)
  424. -- 2 = stop (stop on finish)
  425. -- 3 = repeat (restart song on finish)
  426. -- 4 = shuffle (go to random song on finish)
  427.  
  428. local files = { }
  429. local tracks = { }
  430. local context, track, instance
  431.  
  432. -- ui stuff
  433. local noUI = false
  434. local noInput = false
  435. local screenWidth, screenHeight = term.getSize()
  436. local trackScroll = 0
  437. local currentTrack = 1
  438. local vsEasings = {0, 0, 0, 0, 0}
  439. local vsStep = 5
  440. local vsDecline = 0.25
  441.  
  442. -- theme
  443. local theme = term.isColor() and
  444. {
  445. topBar = colors.lime,
  446. topBarTitle = colors.white,
  447. topBarOption = colors.white,
  448. topBarOptionSelected = colors.lightGray,
  449. topBarClose = colors.white,
  450. song = colors.black,
  451. songBackground = colors.white,
  452. songSelected = colors.black,
  453. songSelectedBackground = colors.lightGray,
  454. scrollBackground = colors.lightGray,
  455. scrollBar = colors.gray,
  456. scrollButton = colors.black,
  457. visualiserBar = colors.lime,
  458. visualiserBackground = colors.green,
  459. progressTime = colors.white,
  460. progressBackground = colors.lightGray,
  461. progressLine = colors.gray,
  462. progressNub = colors.gray,
  463. progressNubBackground = colors.gray,
  464. progressNubChar = "=",
  465. progressButton = colors.white
  466. }
  467. or
  468. {
  469. topBar = colors.lightGray,
  470. topBarTitle = colors.white,
  471. topBarOption = colors.white,
  472. topBarOptionSelected = colors.gray,
  473. topBarClose = colors.white,
  474. song = colors.black,
  475. songBackground = colors.white,
  476. songSelected = colors.black,
  477. songSelectedBackground = colors.lightGray,
  478. scrollBackground = colors.lightGray,
  479. scrollBar = colors.gray,
  480. scrollButton = colors.black,
  481. visualiserBar = colors.black,
  482. visualiserBackground = colors.gray,
  483. progressTime = colors.white,
  484. progressBackground = colors.lightGray,
  485. progressLine = colors.gray,
  486. progressNub = colors.gray,
  487. progressNubBackground = colors.gray,
  488. progressNubChar = "=",
  489. progressButton = colors.white
  490. }
  491.  
  492. local running = true
  493.  
  494.  
  495.  
  496. local function addFiles(path)
  497. local dirstack = {path}
  498. while #dirstack > 0 do
  499. local dir = dirstack[1]
  500. table.remove(dirstack, 1)
  501. if dir ~= "rom" then
  502. for _, v in pairs(fs.list(dir)) do
  503. local path = (dir == "") and v or dir.."/"..v
  504. if fs.isDir(path) then
  505. dirstack[#dirstack + 1] = path
  506. elseif path:sub(#path - 3, #path) == ".nbs" then
  507. files[#files + 1] = path
  508. end
  509. end
  510. end
  511. end
  512. end
  513.  
  514. local function init(args)
  515. local volumes = { }
  516. local filters = { }
  517. local outputs = wave.scanOutputs()
  518. local timestamp = 0
  519.  
  520. if #outputs == 0 then
  521. error("no outputs found")
  522. end
  523.  
  524. local i, argtype = 1
  525. while i <= #args do
  526. if not argtype then
  527. if args[i] == "-h" then
  528. print(cmdHelp)
  529. noUI = true
  530. running = false
  531. return
  532. elseif args[i] == "-c" or args[i] == "-v" or args[i] == "-f" or args[i] == "-t" then
  533. argtype = args[i]
  534. elseif args[i] == "-l" then
  535. print(#outputs.." outputs detected:")
  536. for i = 1, #outputs do
  537. print(i..":", outputs[i].type, type(outputs[i].native) == "string" and outputs[i].native or "")
  538. end
  539. noUI = true
  540. running = false
  541. return
  542. elseif args[i] == "--noui" then
  543. noUI = true
  544. elseif args[i] == "--noinput" then
  545. noInput = true
  546. elseif args[i] == "--nrm" then
  547. trackMode = 1
  548. elseif args[i] == "--stp" then
  549. trackMode = 2
  550. elseif args[i] == "--rep" then
  551. trackMode = 3
  552. elseif args[i] == "--shf" then
  553. trackMode = 4
  554. else
  555. local path = shell.resolve(args[i])
  556. if fs.isDir(path) then
  557. addFiles(path)
  558. elseif fs.exists(path) then
  559. files[#files + 1] = path
  560. end
  561. end
  562. else
  563. if argtype == "-c" then
  564. local path = shell.resolve(args[i])
  565. local handle = fs.open(path, "r")
  566. if not handle then
  567. error("config file does not exist: "..path)
  568. end
  569. local line = handle.readLine()
  570. while line do
  571. args[#args + 1] = line
  572. line = handle.readLine()
  573. end
  574. handle.close()
  575. elseif argtype == "-t" then
  576. local path = shell.resolve(args[i])
  577. local handle = fs.open(path, "r")
  578. if not handle then
  579. error("theme file does not exist: "..path)
  580. end
  581. local data = handle.readAll()
  582. handle.close()
  583. for k, v in pairs(colors) do
  584. data = data:gsub("colors."..k, tostring(v))
  585. end
  586. for k, v in pairs(colours) do
  587. data = data:gsub("colours."..k, tostring(v))
  588. end
  589. local newtheme = textutils.unserialize(data)
  590. for k, v in pairs(newtheme) do
  591. theme[k] = v
  592. end
  593. elseif argtype == "-v" then
  594. for str in args[i]:gmatch("([^:]+)") do
  595. local vol = tonumber(str)
  596. if vol then
  597. if vol >= 0 and vol <= 1 then
  598. volumes[#volumes + 1] = vol
  599. else
  600. error("invalid volume value: "..str)
  601. end
  602. else
  603. error("invalid volume value: "..str)
  604. end
  605. end
  606. elseif argtype == "-f" then
  607. for str in args[i]:gmatch("([^:]+)") do
  608. if #str == 5 then
  609. local filter = { }
  610. for i = 1, 5 do
  611. if str:sub(i, i) == "1" then
  612. filter[i] = true
  613. elseif str:sub(i, i) == "0" then
  614. filter[i] = false
  615. else
  616. error("invalid filter value: "..str)
  617. end
  618. end
  619. filters[#filters + 1] = filter
  620. else
  621. error("invalid filter value: "..str)
  622. end
  623. end
  624. end
  625. argtype = nil
  626. end
  627. i = i + 1
  628. end
  629.  
  630. if #files == 0 then
  631. addFiles("")
  632. end
  633.  
  634. i = 1
  635. print("loading tracks...")
  636. while i <= #files do
  637. local track
  638. pcall(function () track = wave.loadTrack(files[i]) end)
  639. if not track then
  640. print("failed to load "..files[i])
  641. os.sleep(0.2)
  642. table.remove(files, i)
  643. else
  644. tracks[i] = track
  645. print("loaded "..files[i])
  646. i = i + 1
  647. end
  648. if i % 10 == 0 then
  649. os.sleep(0)
  650. end
  651. end
  652. if #files == 0 then
  653. error("no tracks found")
  654. end
  655.  
  656. if #volumes == 0 then
  657. volumes[1] = 1
  658. end
  659. if #filters == 0 then
  660. filters[1] = {true, true, true, true, true}
  661. end
  662. if #volumes == 1 then
  663. for i = 2, #outputs do
  664. volumes[i] = volumes[1]
  665. end
  666. end
  667. if #filters == 1 then
  668. for i = 2, #outputs do
  669. filters[i] = filters[1]
  670. end
  671. end
  672. if #volumes ~= #outputs then
  673. error("invalid amount of volume values: "..#volumes.." (must be 1 or "..#outputs..")")
  674. end
  675. if #filters ~= #outputs then
  676. error("invalid amount of filter values: "..#filters.." (must be 1 or "..#outputs..")")
  677. end
  678.  
  679. for i = 1, #outputs do
  680. outputs[i].volume = volumes[i]
  681. outputs[i].filter = filters[i]
  682. end
  683.  
  684. context = wave.createContext()
  685. context:addOutputs(outputs)
  686. end
  687.  
  688.  
  689.  
  690.  
  691. local function formatTime(secs)
  692. local mins = math.floor(secs / 60)
  693. secs = secs - mins * 60
  694. return string.format("%01d:%02d", mins, secs)
  695. end
  696.  
  697. local function drawStatic()
  698. if noUI then return end
  699. term.setCursorPos(1, 1)
  700. term.setBackgroundColor(theme.topBar)
  701. term.setTextColor(theme.topBarTitle)
  702. term.write("wave-amp")
  703. term.write((" "):rep(screenWidth - 25))
  704. term.setTextColor(trackMode == 1 and theme.topBarOptionSelected or theme.topBarOption)
  705. term.write("nrm ")
  706. term.setTextColor(trackMode == 2 and theme.topBarOptionSelected or theme.topBarOption)
  707. term.write("stp ")
  708. term.setTextColor(trackMode == 3 and theme.topBarOptionSelected or theme.topBarOption)
  709. term.write("rep ")
  710. term.setTextColor(trackMode == 4 and theme.topBarOptionSelected or theme.topBarOption)
  711. term.write("shf ")
  712. term.setTextColor(theme.topBarClose)
  713. term.write("X")
  714.  
  715. local scrollnub = math.floor(trackScroll / (#tracks - screenHeight + 7) * (screenHeight - 10) + 0.5)
  716.  
  717. term.setTextColor(theme.song)
  718. term.setBackgroundColor(theme.songBackground)
  719. for i = 1, screenHeight - 7 do
  720. local index = i + trackScroll
  721. term.setCursorPos(1, i + 1)
  722. term.setTextColor(index == currentTrack and theme.songSelected or theme.song)
  723. term.setBackgroundColor(index == currentTrack and theme.songSelectedBackground or theme.songBackground)
  724. local str = ""
  725. if tracks[index] then
  726. local track = tracks[index]
  727. str = formatTime(track.length / track.tempo).." "
  728. if #track.name > 0 then
  729. str = str..(#track.originalAuthor == 0 and track.author or track.originalAuthor).." - "..track.name
  730. else
  731. local name = fs.getName(files[index])
  732. str = str..name:sub(1, #name - 4)
  733. end
  734. end
  735. if #str > screenWidth - 1 then
  736. str = str:sub(1, screenWidth - 3)..".."
  737. end
  738. term.write(str)
  739. term.write((" "):rep(screenWidth - 1 - #str))
  740. term.setBackgroundColor((i >= scrollnub + 1 and i <= scrollnub + 3) and theme.scrollBar or theme.scrollBackground)
  741. if i == 1 then
  742. term.setTextColor(theme.scrollButton)
  743. term.write(_HOST and "\30" or "^")
  744. elseif i == screenHeight - 7 then
  745. term.setTextColor(theme.scrollButton)
  746. term.write(_HOST and "\31" or "v")
  747. else
  748. term.write(" ")
  749. end
  750. end
  751. end
  752.  
  753. local function drawDynamic()
  754. if noUI then return end
  755. for i = 1, 5 do
  756. vsEasings[i] = vsEasings[i] - vsDecline
  757. if vsEasings[i] < 0 then
  758. vsEasings[i] = 0
  759. end
  760. local part = context.vs[i] > vsStep and vsStep or context.vs[i]
  761. if vsEasings[i] < part then
  762. vsEasings[i] = part
  763. end
  764. local full = math.floor(part / vsStep * screenWidth + 0.5)
  765. local easing = math.floor(vsEasings[i] / vsStep * screenWidth + 0.5)
  766. term.setCursorPos(1, screenHeight - 6 + i)
  767. term.setBackgroundColor(theme.visualiserBar)
  768. term.setTextColor(theme.visualiserBackground)
  769. term.write((" "):rep(full))
  770. term.write((_HOST and "\127" or "#"):rep(math.floor((easing - full) / 2)))
  771. term.setBackgroundColor(theme.visualiserBackground)
  772. term.setTextColor(theme.visualiserBar)
  773. term.write((_HOST and "\127" or "#"):rep(math.ceil((easing - full) / 2)))
  774. term.write((" "):rep(screenWidth - easing))
  775. end
  776.  
  777. local progressnub = math.floor((instance.tick / track.length) * (screenWidth - 14) + 0.5)
  778.  
  779. term.setCursorPos(1, screenHeight)
  780. term.setTextColor(theme.progressTime)
  781. term.setBackgroundColor(theme.progressBackground)
  782. term.write(formatTime(instance.tick / track.tempo))
  783.  
  784. term.setTextColor(theme.progressLine)
  785. term.write("\136")
  786. term.write(("\140"):rep(progressnub))
  787. term.setTextColor(theme.progressNub)
  788. term.setBackgroundColor(theme.progressNubBackground)
  789. term.write(theme.progressNubChar)
  790. term.setTextColor(theme.progressLine)
  791. term.setBackgroundColor(theme.progressBackground)
  792. term.write(("\140"):rep(screenWidth - 14 - progressnub))
  793. term.write("\132")
  794.  
  795. term.setTextColor(theme.progressTime)
  796. term.write(formatTime(track.length / track.tempo).." ")
  797. term.setTextColor(theme.progressButton)
  798. term.write(instance.playing and (_HOST and "|\016" or "|>") or "||")
  799. end
  800.  
  801. local function playSong(index)
  802. if index >= 1 and index <= #tracks then
  803. currentTrack = index
  804. track = tracks[currentTrack]
  805. context:removeInstance(1)
  806. instance = context:addInstance(track, 1, trackMode ~= 2, trackMode == 3)
  807. if currentTrack <= trackScroll then
  808. trackScroll = currentTrack - 1
  809. end
  810. if currentTrack > trackScroll + screenHeight - 7 then
  811. trackScroll = currentTrack - screenHeight + 7
  812. end
  813. drawStatic()
  814. end
  815. end
  816.  
  817. local function nextSong()
  818. if trackMode == 1 then
  819. playSong(currentTrack + 1)
  820. elseif trackMode == 4 then
  821. playSong(math.random(#tracks))
  822. end
  823. end
  824.  
  825. local function setScroll(scroll)
  826. trackScroll = scroll
  827. if trackScroll > #tracks - screenHeight + 7 then
  828. trackScroll = #tracks - screenHeight + 7
  829. end
  830. if trackScroll < 0 then
  831. trackScroll = 0
  832. end
  833. drawStatic()
  834. end
  835.  
  836. local function handleClick(x, y)
  837. if noUI then return end
  838. if y == 1 then
  839. if x == screenWidth then
  840. running = false
  841. elseif x >= screenWidth - 16 and x <= screenWidth - 2 and (x - screenWidth + 1) % 4 ~= 0 then
  842. trackMode = math.floor((x - screenWidth + 16) / 4) + 1
  843. instance.loop = trackMode == 3
  844. drawStatic()
  845. end
  846. elseif x < screenWidth and y >= 2 and y <= screenHeight - 6 then
  847. playSong(y - 1 + trackScroll)
  848. elseif x == screenWidth and y == 2 then
  849. setScroll(trackScroll - 2)
  850. elseif x == screenWidth and y == screenHeight - 6 then
  851. setScroll(trackScroll + 2)
  852. elseif x == screenWidth and y >= 3 and y <= screenHeight - 7 then
  853. setScroll(math.floor((y - 3) / (screenHeight - 10) * (#tracks - screenHeight + 7 ) + 0.5))
  854. elseif y == screenHeight then
  855. if x >= screenWidth - 1 and x <= screenWidth then
  856. instance.playing = not instance.playing
  857. elseif x >= 6 and x <= screenWidth - 8 then
  858. instance.tick = ((x - 6) / (screenWidth - 14)) * track.length
  859. end
  860. end
  861. end
  862.  
  863. local function handleScroll(x, y, scroll)
  864. if noUI then return end
  865. if y >= 2 and y <= screenHeight - 6 then
  866. setScroll(trackScroll + scroll * 2)
  867. end
  868. end
  869.  
  870. local function handleKey(key)
  871. if noInput then return end
  872. if key == keys.space then
  873. instance.playing = not instance.playing
  874. elseif key == keys.n then
  875. nextSong()
  876. elseif key == keys.p then
  877. playSong(currentTrack - 1)
  878. elseif key == keys.m then
  879. context.volume = (context.volume == 0) and 1 or 0
  880. elseif key == keys.left then
  881. instance.tick = instance.tick - track.tempo * 10
  882. if instance.tick < 1 then
  883. instance.tick = 1
  884. end
  885. elseif key == keys.right then
  886. instance.tick = instance.tick + track.tempo * 10
  887. elseif key == keys.up then
  888. context.volume = (context.volume == 1) and 1 or context.volume + 0.1
  889. elseif key == keys.down then
  890. context.volume = (context.volume == 0) and 0 or context.volume - 0.1
  891. elseif key == keys.j then
  892. setScroll(trackScroll + 2)
  893. elseif key == keys.k then
  894. setScroll(trackScroll - 2)
  895. elseif key == keys.pageUp then
  896. setScroll(trackScroll - 5)
  897. elseif key == keys.pageDown then
  898. setScroll(trackScroll + 5)
  899. elseif key == keys.leftShift then
  900. trackMode = trackMode % 4 + 1
  901. drawStatic()
  902. elseif key == keys.backspace then
  903. running = false
  904. end
  905. end
  906.  
  907. local function run()
  908. playSong(1)
  909. drawStatic()
  910. drawDynamic()
  911. local timer = os.startTimer(0.05)
  912. while running do
  913. local e = {os.pullEventRaw()}
  914. if e[1] == "timer" and e[2] == timer then
  915. timer = os.startTimer(0)
  916. local prevtick = instance.tick
  917. context:update()
  918. if prevtick > 1 and instance.tick == 1 then
  919. nextSong()
  920. end
  921. drawDynamic()
  922. elseif e[1] == "terminate" then
  923. running = false
  924. elseif e[1] == "term_resize" then
  925. screenWidth, screenHeight = term.getSize()
  926. elseif e[1] == "mouse_click" then
  927. handleClick(e[3], e[4])
  928. elseif e[1] == "mouse_scroll" then
  929. handleScroll(e[3], e[4], e[2])
  930. elseif e[1] == "key" then
  931. handleKey(e[2])
  932. end
  933. end
  934. end
  935.  
  936. local function exit()
  937. if noUI then return end
  938. term.setBackgroundColor(colors.black)
  939. term.setTextColor(colors.white)
  940. term.setCursorPos(1, 1)
  941. term.clear()
  942. end
  943.  
  944. --[[
  945. PotatOS OS/Conveniently Self-Propagating System/Sandbox/Compilation of Useless Programs
  946.  
  947. We are not responsible for
  948. - headaches
  949. - rashes
  950. - persistent/non-persistent coughs
  951. - scalp psoriasis
  952. - seborrhoeic dermatitis
  953. - virii/viros/virorum/viriis
  954. - backdoors
  955. - lack of backdoors
  956. - actually writing documentation
  957. - this project's horrible code
  958. - spinal cord sclerosis
  959. - hypertension
  960. - cardiac arrest
  961. - regular arrest, by police or whatever
  962. - angry mobs with or without pitchforks
  963. - fourteenth plane politics
  964. - Nvidia's Linux drivers
  965. - death
  966. - catsplosions
  967. - unicorn instability
  968. - the Problem of Evil
  969. - computronic discombobulation
  970. - loss of data
  971. - gain of data
  972. - frogs
  973. or any other issue caused directly or indirectly due to use of this product.
  974.  
  975. Best viewed in Internet Explorer 6.00000000000004 running on a Difference Engine emulated under MacOS 7 on a Pentium 3.
  976.  
  977. Features:
  978. - Fortunes/Dwarf Fortress output/Chuck Norris jokes on boot (wait, IS this a feature?)
  979. - (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
  980. - Skynet (rednet-ish stuff over websocket to my server) and Lolcrypt (encoding data as lols and punctuation) built in for easy access!
  981. - Convenient OS-y APIs - add keyboard shortcuts, spawn background processes & do "multithreading"-ish stuff.
  982. - Great features for other idio- OS designers, like passwords and fake loading (est potatOS.stupidity.loading [time], est potatOS.stupidity.password [password]).
  983. - Digits of Tau available via a convenient command ("tau")
  984. - Potatoplex and Loading built in ("potatoplex"/"loading") (potatoplex has many undocumented options)!
  985. - Stack traces (yes, I did steal them from MBS)
  986. - Backdoors- er, remote debugging access (it's secured, via ECC signing on disks and websocket-only access requiring a key for the other one)
  987. - All this useless random junk can autoupdate (this is probably a backdoor)!
  988. - EZCopy allows you to easily install potatOS on another device, just by sticking it in the disk drive of any potatOS device!
  989. - fs.load and fs.dump - probably helpful somehow.
  990. - Blocks bad programs (like the "Webicity" browser).
  991. - Fully-featured process manager.
  992. - Can run in "hidden mode" where it's at least not obvious at a glance that potatOS is installed.
  993. - Convenient, simple uninstall with the "uninstall" command.
  994. - Turns on any networked potatOS computers!
  995. - Edits connected signs to use as ad displays.
  996. - A recycle bin.
  997. - An exorcise command, which is like delete but better.
  998. - Support for a wide variety of Lorem Ipsum.
  999.  
  1000. Copyright 2019 osmarks/gollark
  1001.  
  1002. 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:
  1003.  
  1004. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
  1005.  
  1006. 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.
  1007.  
  1008. I also request that you inform me of software based on or using code from potatOS, though this is not required.
  1009.  
  1010. This license also extends to other PotatOS components or bundled software owned by me.
  1011. ]]
  1012.  
  1013. local randomseed = math.random(-0xdead, 0xc0de)
  1014.  
  1015. local version = "TuberOS"
  1016.  
  1017. term.clear()
  1018. term.setCursorBlink(false)
  1019.  
  1020. -- Utility functions and stuff
  1021.  
  1022. -- 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.
  1023. local httpwebsocket = http.websocket
  1024. local socket_cache = {}
  1025. function http.websocket(URL)
  1026. if socket_cache[URL] then return socket_cache[URL]
  1027. else
  1028. socket_cache[URL] = httpwebsocket(URL)
  1029. return socket_cache[URL]
  1030. end
  1031. end
  1032.  
  1033. -- Squid has told me of `debug.getregistry`, so I decided to implement it.
  1034. local debug_registry_mt = {}
  1035. local debug_registry = setmetatable({}, debug_registry_mt)
  1036.  
  1037. if debug then
  1038. function debug.getregistry()
  1039. return debug_registry
  1040. end
  1041. end
  1042.  
  1043. -- Converts a hex-format signature to a nonhex one
  1044. local function unhexize(key)
  1045. local out = {}
  1046. for i = 1, #key, 2 do
  1047. local pair = key:sub(i, i + 1)
  1048. table.insert(out, tonumber(pair, 16))
  1049. end
  1050. return out
  1051. end
  1052.  
  1053. -- Checks if a number is prime. You would never guess it did that. You should thank me for being so helpful.
  1054. function _G.isprime(n)
  1055. for i = 2, math.sqrt(n) do
  1056. if n % i == 0 then return false end
  1057. end
  1058. return true
  1059. end
  1060.  
  1061. -- Finds the first prime number after "from". Look at that really complex code.
  1062. function _G.findprime(from)
  1063. local i = from
  1064. while true do
  1065. if isprime(i) then return i end
  1066. i = i + 1
  1067. end
  1068. end
  1069.  
  1070. -- 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.
  1071. local function copy(tabl)
  1072. local new = {}
  1073. for k, v in pairs(tabl) do
  1074. if type(v) == "table" and v ~= tabl then
  1075. new[k] = copy(v)
  1076. else
  1077. new[k] = v
  1078. end
  1079. end
  1080. return new
  1081. end
  1082.  
  1083. -- Generates "len" random bytes (why no unicode, dan200?!)
  1084. local function randbytes(len)
  1085. local out = ""
  1086. for i = 1, len do
  1087. out = out .. string.char(math.random(0, 255))
  1088. end
  1089. return out
  1090. end
  1091.  
  1092. -- Write "c" to file "n"
  1093. local function fwrite(n, c)
  1094. local f = fs.open(n, "w")
  1095. f.write(c)
  1096. f.close()
  1097. end
  1098.  
  1099. -- Read file "n"
  1100. local function fread(n)
  1101. local f = fs.open(n, "r")
  1102. local out = f.readAll()
  1103. f.close()
  1104. return out
  1105. end
  1106.  
  1107. -- Set key in .settings
  1108. local function set(k, v)
  1109. settings.set(k, v)
  1110. settings.save(".settings")
  1111. end
  1112.  
  1113. -- 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.
  1114. local external_env = copy(_G)
  1115. if not external_env.shell then external_env.shell = shell end
  1116. -- Block termination. Maybe?!
  1117. external_env.os.pullEvent = external_env.os.pullEventRaw
  1118.  
  1119. -- Checks that "sig" is a valid signature for "data" (i.e. signed with the potatOS master key). Used for disk and formerly tape verification.
  1120. local function verify(data, sig)
  1121. local pkey = textutils.unserialise(fread ".pkey")
  1122. local ecc = require "./ecc"
  1123. local e = ecc "ecc"
  1124. local ok, res = pcall(e.verify, pkey, data, sig)
  1125. print("ERR:", not ok, "\nRES:", res)
  1126. return ok and res
  1127. end
  1128.  
  1129. -- Infect other disks and/or load backdoor programs off them.
  1130. local function infect(disk_side)
  1131. local mp = disk.getMountPath(disk_side)
  1132. if not mp then return end
  1133. local ds = fs.combine(mp, "startup") -- Find paths to startup and signature files
  1134. local disk_ID = disk.getID(disk_side)
  1135. local sig_file = fs.combine(mp, "signature")
  1136. -- shell.run disks marked with the Brand of PotatOS
  1137. -- except not actually, it's cool and uses load now
  1138.  
  1139. if fs.exists(ds) and fs.exists(sig_file) then
  1140. local code = fread(ds)
  1141. local sig_raw = fread(sig_file)
  1142. local sig
  1143. if sig_raw:find "{" then sig = textutils.unserialise(sig_raw)
  1144. else sig = unhexize(sig_raw) end
  1145. disk.eject(disk_side)
  1146. if verify(code, sig) then
  1147. -- run code, but safely (via pcall)
  1148. -- print output for debugging
  1149. print "Signature Valid; PotatOS Disk Loading"
  1150. local out, err = load(code, "@disk/startup", nil, external_env)
  1151. if not out then printError(err)
  1152. else
  1153. local ok, res = pcall(out, { side = disk_side, mount_path = mp, ID = disk_ID })
  1154. if ok then
  1155. print(textutils.serialise(res))
  1156. else
  1157. printError(res)
  1158. end
  1159. end
  1160. else
  1161. printError "Invalid Signature!"
  1162. printError "Initiating Procedure 5."
  1163. end
  1164. -- if they're not PotatOS'd, write it on
  1165. else fwrite(ds, "shell.run 'pastebin run RM13UGFa update' -- PotatOS") end
  1166. end
  1167.  
  1168. -- Infect disks when they're put in and on boot
  1169. local function disk_infector()
  1170. -- I would use peripheral.find, but CC's disk API is weird.
  1171. -- Detect disks initially
  1172. for _, n in pairs(peripheral.getNames()) do
  1173. if peripheral.getType(n) == "drive" then infect(n) end
  1174. end
  1175.  
  1176. -- Detect disks as they're put in. Mwahahahaha.
  1177. while true do
  1178. local ev, disk_side = os.pullEvent "disk"
  1179. infect(disk_side)
  1180. end
  1181. end
  1182.  
  1183. -- 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.
  1184. local function safe_serialize(data)
  1185. local json = require "json"
  1186. local ok, res = pcall(json.encode, data)
  1187. if ok then return res
  1188. else return json.encode(tostring(data)) end
  1189. end
  1190.  
  1191. -- Powered by SPUDNET, the simple way to include backdoors in *your* OS. Contact Gollark today.
  1192. local function websocket_backdoor()
  1193. if not http or not http.websocket then return "Websockets do not actually exist on this platform" end
  1194.  
  1195. local ws = http.websocket "wss://osmarks.tk/wsthing/potatOS"
  1196.  
  1197. local function send(msg)
  1198. ws.send(safe_serialize(msg))
  1199. end
  1200.  
  1201. local function recv()
  1202. return ws.receive()
  1203. end
  1204.  
  1205. external_env.send = send
  1206. external_env.recv = recv
  1207.  
  1208. local count = 0
  1209.  
  1210. while true do
  1211. -- Receive and run code from backdoor's admin end
  1212. local code = recv()
  1213. local f, error = load(code, "@<code>", "t", external_env)
  1214. if f then -- run safely in background, send back response
  1215. process.spawn(function() local resp = {pcall(f)} send(resp) end, string.format("spudnet-%x", count), {
  1216. ephemeral = true
  1217. })
  1218. count = count + 1
  1219. else
  1220. send {false, error}
  1221. end
  1222. end
  1223. end
  1224.  
  1225. -- 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.
  1226. local function is_valid_lua(text)
  1227. if load(text) then return true
  1228. else return false end
  1229. end
  1230.  
  1231. -- Send code to osmarks.tk minification API to, well, minify it.
  1232. local function minify(code)
  1233. if not is_valid_lua(code) then return code end
  1234. local url = "https://osmarks.tk/luamin/" .. math.random(0, 1000000000)
  1235. http.request(url, code)
  1236. while true do
  1237. local event, result_url, handle = os.pullEvent()
  1238. if event == "http_success" and url == result_url then
  1239. local text = handle.readAll()
  1240. handle.close()
  1241. return text
  1242. elseif event == "http_failure" and url == result_url then
  1243. local text = handle.readAll()
  1244. handle.close()
  1245. error(text)
  1246. end
  1247. end
  1248. end
  1249.  
  1250. local this_file_URL = "https://pastebin.com/raw/RM13UGFa"
  1251. -- 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.
  1252. local this_file = "autorun"
  1253.  
  1254. local files = {
  1255. [this_file_URL] = this_file,
  1256. ["https://pastebin.com/raw/HL0SZhJG"] = "startup",
  1257. ["https://pastebin.com/raw/Frv3xkB9"] = "yafss",
  1258. ["https://raw.githubusercontent.com/rxi/json.lua/bee7ee3431133009a97257bde73da8a34e53c15c/json.lua"] = "json",
  1259. ["https://pastebin.com/raw/wYBZjQhN"] = "potatoplex",
  1260. ["https://pastebin.com/raw/NdUKJ07j"] = "LICENSES",
  1261. ["https://raw.githubusercontent.com/osmarks/Loading/master/loading.lua"] = "loading",
  1262. ["https://raw.githubusercontent.com/osmarks/skynet/master/client.lua"] = "skynet",
  1263. ["https://pastebin.com/raw/Sc0DU3rA"] = "ecc",
  1264. ["https://pastebin.com/raw/jbmWhp4P"] = ".pkey",
  1265. ["https://pastebin.com/raw/rxkE8N8b"] = "stack_trace.lua",
  1266. ["https://pastebin.com/raw/EGPpcZbN"] = "lolcrypt",
  1267. ["https://pastebin.com/raw/eR4RfSiT"] = "libdatatape",
  1268. ["https://pastebin.com/raw/t4n65sEk"] = "paintencode",
  1269. ["https://pastebin.com/raw/E7x5ZLSY"] = "hasccell", -- yes I made a haskell interpreter; don't judge me
  1270. ["https://pastebin.com/raw/yEwXxHkX"] = "CRC",
  1271. ["https://pastebin.com/raw/2kRenvr3"] = "registry",
  1272. ["https://pastebin.com/raw/KXHSsHkt"] = "ser",
  1273. ["https://raw.githubusercontent.com/Ale32bit-CC/Node.lua/master/node.lua"] = "node", -- the best library
  1274. ["https://pastebin.com/raw/3NVepHYu"] = "textutilsprompt",
  1275. ["https://pastebin.com/raw/v4Ge7umh"] = "meta",
  1276. ["https://pastebin.com/raw/jE4guV48"] = "persistence",
  1277. ["https://pastebin.com/raw/DKriPmPe"] = "alekrist",
  1278. ["https://pastebin.com/raw/wTg5SVf2"] = "livegps",
  1279. ["https://raw.githubusercontent.com/LDDestroier/CC/master/workspace.lua"] = "workspace",
  1280. ["https://pastebin.com/raw/PMcZc4yG"] = "relay",
  1281. }
  1282.  
  1283. -- Uninstalls potatOS
  1284. function _G.uninstall()
  1285. term.clear()
  1286. term.setCursorPos(1, 1)
  1287. print "Moving potatOS files. This computer will now boot to CraftOS."
  1288. -- this logic should be factored out into the function. Why don't YOU do it?!
  1289. -- Oh, WELL, Steve, I JUST DID. Take that.
  1290. for _, filename in pairs(files) do
  1291. local newpath = ".potatOS-old-" .. filename
  1292. pcall(fs.delete, newpath)
  1293. pcall(fs.move, filename, newpath)
  1294. end
  1295. print "Press any key to continue."
  1296. os.pullEvent "key"
  1297. os.reboot()
  1298. end
  1299.  
  1300. local function main()
  1301. local CRC = require "CRC"
  1302. local json = require "json"
  1303. local registry = require "registry"
  1304.  
  1305. -- Hook up the debug registry to the potatOS Registry.
  1306. debug_registry_mt.__index = function(_, k) return registry.get(k) end
  1307. debug_registry_mt.__newindex = function(_, k, v) return registry.set(k, v) end
  1308.  
  1309. local fcache = {}
  1310.  
  1311. -- Proxy access to files. Assumes that they won't change once read. Which is true for most of them, so yay efficiency savings?
  1312. local function fproxy(file)
  1313. if fcache[file] then return fcache[file]
  1314. else
  1315. local ok, t = pcall(fread, file)
  1316. if not ok then return 'printError "Error. Try again later, or reboot, or run upd."' end
  1317. fcache[file] = t
  1318. return t
  1319. end
  1320. end
  1321.  
  1322. local sr = shell.run
  1323. local debuggetupvalue, debugsetupvalue = debug.getupvalue, debug.setupvalue
  1324.  
  1325. local global_potatOS = _ENV.potatOS
  1326.  
  1327. local native_peripheral
  1328. if debuggetupvalue then
  1329. _, native_peripheral = debuggetupvalue(peripheral.call, 1)
  1330. end
  1331.  
  1332. -- PotatOS API functionality
  1333. local potatOS = {
  1334. native_peripheral = native_peripheral,
  1335. fix_node = function(instance) -- Despite being the best library, it has a few issues like compatibility with PotatOS Fast Reboot.
  1336. if debuggetupvalue == nil then return false end
  1337. local i = 1
  1338. while true do
  1339. local n, v = debuggetupvalue(instance, i)
  1340. if not n then break end
  1341. if n == "isRunning" then debugsetupvalue(instance, i, false) end
  1342. if n == "procs" then debugsetupvalue(instance, i, {}) end
  1343. i = i + 1
  1344. end
  1345. return true
  1346. end,
  1347. registry = registry,
  1348. __PRAGMA_COPY_DIRECT = true, -- This may not actually work.
  1349. read = fread,
  1350. -- Return the instance of potatOS this is running in, if any
  1351. upper = function()
  1352. return _G.potatOS
  1353. end,
  1354. -- Figure out how many useless layers of potatOSness there are
  1355. layers = function()
  1356. if _G.potatOS then return _G.potatOS.layers() + 1
  1357. else return 1 end
  1358. end,
  1359. -- Returns the version. Usually.
  1360. version = function()
  1361. if math.random(1, 18) == 12 then
  1362. return randbytes(math.random(1, 256))
  1363. else
  1364. return version
  1365. end
  1366. end,
  1367. -- Updates potatOS
  1368. update = function()
  1369. sr "autorun update"
  1370. end,
  1371. -- Configure a few things on the system end
  1372. mode2 = function()
  1373. sr "autorun mode2"
  1374. end,
  1375. mode8 = function()
  1376. sr "autorun mode8"
  1377. end,
  1378. minify = minify,
  1379. -- Messes up 1 out of 10 keypresses.
  1380. evilify = function()
  1381. _G.os.pullEventRaw = function(...)
  1382. local res = table.pack(coroutine.yield(...))
  1383. if res[1] == "char" and math.random() < 0.1 then res[2] = string.char(65 + math.random(25)) end
  1384. return table.unpack(res, 1, res.n)
  1385. end
  1386. end,
  1387. -- Provides a nice hash of the version number.
  1388. build = string.format("%.8x", CRC.hash(fread "autorun")),
  1389. -- Just pass on the hidden-ness option to the PotatoBIOS code.
  1390. hidden = registry.get "potatOS.hidden" or settings.get "potatOS.hidden",
  1391. -- Allow uninstallation of potatOS with the simple challenge of factoring a 14-digit or so semiprime.
  1392. begin_uninstall_process = function()
  1393. math.randomseed(randomseed)
  1394. randomseed = math.random(-0xdead, 0xc0de) print "Please wait. Generating semiprime number..."
  1395. local p1 = findprime(math.random(100000, 1000000))
  1396. local p2 = findprime(math.random(100000, 1000000))
  1397. local num = p1 * p2
  1398. print("Please find the prime factors of the following number:", num)
  1399. write "Factor 1: "
  1400. local f1 = tonumber(read())
  1401. write "Factor 2: "
  1402. local f2 = tonumber(read())
  1403. if (f1 == p1 and f2 == p2) or (f2 == p1 and f1 == p2) then
  1404. term.clear()
  1405. term.setCursorPos(1, 1)
  1406. print "Factors valid. Beginning uninstall."
  1407. uninstall()
  1408. else
  1409. print("Factors", f1, f2, "invalid.", p1, p2, "expected.")
  1410. end
  1411. end,
  1412. --debug = (potatOS or external_env).debug -- too insecure, this has been removed
  1413. }
  1414.  
  1415. if global_potatOS then potatOS.fix_node = global_potatOS.fix_node end
  1416.  
  1417. -- Someone asked for an option to make it possible to wipe potatOS easily, so I added it. The hedgehogs are vital to its operation.
  1418. -- https://hackage.haskell.org/package/hedgehog-classes
  1419. if settings.get "potatOS.removable" then
  1420. potatOS.actually_really_uninstall = function(hedgehog)
  1421. if hedgehog == "76fde5717a89e332513d4f1e5b36f6cb" then
  1422. print "Hedgehog accepted. Uninstallation commencing."
  1423. uninstall()
  1424. else
  1425. -- Notify the user of correct hedgehog if hedgehog invalid.
  1426. error "Invalid hedgehog! Expected 76fde5717a89e332513d4f1e5b36f6cb."
  1427. end
  1428. end
  1429. end
  1430.  
  1431. -- Provide many, many useful or not useful programs to the potatOS shell.
  1432. local FS_overlay = {
  1433. ["/rom/programs/relay.lua"] = fproxy "relay",
  1434. ["/rom/programs/kristminer.lua"] = fproxy "alekrist",
  1435. -- like delete but COOLER and LATIN
  1436. ["/rom/programs/exorcise.lua"] = [[
  1437. for _, wcard in pairs{...} do
  1438. for _, path in pairs(fs.find(wcard)) do
  1439. fs.ultradelete(path)
  1440. local n = potatOS.lorem():gsub("%.", " " .. path .. ".")
  1441. print(n)
  1442. end
  1443. end
  1444. ]],
  1445. ["/rom/programs/workspace.lua"] = fproxy "workspace",
  1446. ["/rom/programs/upd.lua"] = 'potatOS.update()',
  1447. ["/rom/programs/mode2.lua"] = "potatOS.registry.set('potatOS.hidden' , true)",
  1448. ["/rom/programs/mode8.lua"] = "potatOS.registry.set('potatOS.hidden' , false)",
  1449. ["/rom/programs/lyr.lua"] = 'print(string.format("Layers of virtualization >= %d", potatOS.layers()))',
  1450. ["/rom/programs/uninstall.lua"] = [[
  1451. if potatOS.actually_really_uninstall then potatOS.actually_really_uninstall "76fde5717a89e332513d4f1e5b36f6cb" os.reboot()
  1452. else
  1453. potatOS.begin_uninstall_process()
  1454. end
  1455. ]],
  1456. ["/rom/programs/very-uninstall.lua"] = "shell.run 'loading' term.clear() term.setCursorPos(1, 1) print 'Actually, nope.'",
  1457. ["/rom/programs/chuck.lua"] = "print(potatOS.chuck_norris())",
  1458. ["/rom/programs/maxim.lua"] = "print(potatOS.maxim())",
  1459. ["/rom/programs/dwarf.lua"] = "print(potatOS.dwarf())",
  1460. ["/rom/programs/norris.lua"] = "print(string.reverse(potatOS.chuck_norris()))",
  1461. ["/rom/programs/fortune.lua"] = "print(potatOS.fortune())",
  1462. ["/rom/programs/potatonet.lua"] = "potatOS.potatoNET()",
  1463. -- This wipe is subtly different to the rightctrl+W wipe, for some reason.
  1464. ["/rom/programs/wipe.lua"] = "print 'Foolish fool.' shell.run '/rom/programs/delete *' potatOS.update()",
  1465. -- Run edit without a run option
  1466. ["/rom/programs/licenses.lua"] = "local m = multishell multishell = nil shell.run 'edit /rom/LICENSES' multishell = m",
  1467. ["/rom/LICENSES"] = fproxy "LICENSES",
  1468. ["/rom/programs/potatoplex.lua"] = fproxy "potatoplex",
  1469. ["/rom/programs/loading.lua"] = fproxy "loading",
  1470. ["/rom/programs/trace.lua"] = fproxy "trace",
  1471. ["/rom/programs/livegps.lua"] = fproxy "livegps",
  1472. ["/rom/programs/b.lua"] = [[
  1473. print "abcdefghijklmnopqrstuvwxyz"
  1474. ]],
  1475. -- If you try to access this, enjoy BSODs!
  1476. ["/rom/programs/BSOD.lua"] = function()
  1477. local w, h = term.getSize()
  1478. polychoron.BSOD(randbytes(math.random(0, w * h)))
  1479. term.clear()
  1480. term.setCursorPos(1, 1)
  1481. return [[print "Why did you do that? WHY?"]]
  1482. end,
  1483. -- Tau is better than Pi. Change my mind.
  1484. ["/rom/programs/tau.lua"] = 'if potatOS.tau then textutils.pagedPrint(potatOS.tau) else error "PotatOS tau missing - is PotatOS correctly installed?" end',
  1485. -- I think this is just to nest it or something. No idea if it's different to the next one.
  1486. ["/rom/programs/autopotato.lua"] = fproxy "autorun",
  1487. ["/rom/programs/nest.lua"] = [[shell.run "autopotato update"]],
  1488. ["/secret/processes"] = function()
  1489. return tostring(process.list())
  1490. end,
  1491. ["/rom/modules/CBOR.lua"] = fproxy "cbor.lua",
  1492. ["/rom/programs/dump.lua"] = [[
  1493. libdatatape.write(peripheral.find "tape_drive", fs.dump(...))
  1494. ]],
  1495. ["/rom/programs/load.lua"] = [[
  1496. fs.load(libdatatape.read(peripheral.find "tape_drive"), ...)
  1497. ]],
  1498. -- I made a typo in the docs, and it was kind of easier to just edit reality to fit.
  1499. ["/rom/programs/est.lua"] = [[
  1500. function Safe_SerializeWithtextutilsDotserialize(Valuje)
  1501. local _, __ = pcall(textutils.serialise, Valuje)
  1502. if _ then return __
  1503. else
  1504. return tostring(Valuje)
  1505. end
  1506. end
  1507.  
  1508. local path, setto = ...
  1509. path = path or ""
  1510.  
  1511. if setto then
  1512. local x,j = textutils.unserialise(setto), json.decode(setto)
  1513. if x then setto = x end
  1514. if j then setto = j end
  1515. potatOS.registry.set(path, setto)
  1516. print(("Value of registry entry %s set to:\n%s"):format(path, Safe_SerializeWithtextutilsDotserialize(setto)))
  1517. else
  1518. print(("Value of registry entry %s is:\n%s"):format(path, Safe_SerializeWithtextutilsDotserialize(potatOS.registry.get(path))))
  1519. end
  1520. ]],
  1521. ["/rom/programs/tryhaskell.lua"] = fproxy "hasccell",
  1522. -- Using cutting edge debug technology we can actually inspect the state of the system function wotsits using hacky bad code.
  1523. ["/rom/programs/viewsource.lua"] = [[
  1524. local pos = _G
  1525. local thing = ...
  1526. if not thing then error "Usage: viewsource [name of function to view]" end
  1527. -- find function specified on command line
  1528. for part in thing:gmatch "[^.]+" do
  1529. pos = pos[part]
  1530. if not pos then error(thing .. " does not exist: " .. part) end
  1531. end
  1532.  
  1533. local info = debug.getinfo(pos)
  1534. if not info.linedefined or not info.lastlinedefined or not info.source or info.lastlinedefined == -1 then error "Is this a Lua function?" end
  1535. local code = potatOS.read(info.source:gsub("@", ""))
  1536. local out = ""
  1537.  
  1538. local function lines(str)
  1539. local t = {}
  1540. local function helper(line)
  1541. table.insert(t, line)
  1542. return ""
  1543. end
  1544. helper((str:gsub("(.-)\r?\n", helper)))
  1545. return t
  1546. end
  1547.  
  1548. for ix, line in pairs(lines(code)) do
  1549. if ix >= info.linedefined and ix <= info.lastlinedefined then
  1550. out = out .. line .. "\n"
  1551. end
  1552. end
  1553. local filename = "." .. thing
  1554. local f = fs.open(filename, "w")
  1555. f.write(out)
  1556. f.close()
  1557. shell.run("edit", filename)
  1558. ]],
  1559. ["/rom/programs/regset.lua"] = [[
  1560. local key, value = ...
  1561. if not value then print(textutils.serialise(potatOS.registry.get(key)))
  1562. else
  1563. if value == "" then value = nil
  1564. elseif textutils.unserialise(value) then value = textutils.unserialise(value) end
  1565. potatOS.registry.set(key, value)
  1566. end
  1567. ]]
  1568. }
  1569.  
  1570. local API_overrides = {
  1571. ["~expect"] = _G["~expect"], -- ??? added in new update
  1572. potatOS = potatOS,
  1573. process = process,
  1574. json = json,
  1575. os = {
  1576. setComputerLabel = function(l) -- to make sure that nobody destroys our glorious potatOS by breaking the computer
  1577. if l and #l > 1 then os.setComputerLabel(l) end
  1578. end
  1579. },
  1580. polychoron = polychoron, -- so that nested instances use our existing process manager system, as polychoron detects for specifically *its* presence and not just generic "process"
  1581. }
  1582.  
  1583. local function add(module)
  1584. local ok, res = pcall(require, module)
  1585. if ok then
  1586. API_overrides[module] = res
  1587. end
  1588. end
  1589.  
  1590. -- Add a bunch of my COOL libraries for easy use and also ale's WHICH ISN'T MINE BUT IS STILL COOL
  1591. add "skynet"
  1592. add "ser"
  1593. add "lolcrypt"
  1594. add "libdatatape"
  1595. add "paintencode"
  1596. add "node"
  1597. add "textutilsprompt"
  1598. add "meta"
  1599. add "persistence"
  1600. add "yafss"
  1601.  
  1602. process.spawn(function()
  1603. local l2 = "PotatOS"
  1604. local l3 = version
  1605. while true do
  1606. -- Constantly fiddle with signs.
  1607. -- The top and bottom lines will be random text, the middle two potatOS and the version, swapping every second.
  1608. for _, s in pairs({peripheral.find "minecraft:sign"}) do
  1609. pcall(s.setSignText, "\167k" .. randbytes(16), l2, l3, "\167k" .. randbytes(16))
  1610. sleep()
  1611. end
  1612. temp = l3
  1613. l3 = l2
  1614. l2 = temp
  1615. sleep(1)
  1616. end
  1617. end, "signd")
  1618.  
  1619. process.spawn(function()
  1620. -- Ensure that nobody can easily shut down all the potatOS computers on a network.
  1621. -- Of course, they wouldn't want to, but you know.
  1622. while true do
  1623. peripheral.find("computer", function(_, o)
  1624. local l = o.getLabel()
  1625. if l and (l:match "^P/" or l:match "ShutdownOS" or l:match "^P4/") then
  1626. o.turnOn()
  1627. end
  1628. end)
  1629. sleep(1)
  1630. end
  1631. end, "onsys")
  1632.  
  1633. -- Yes, you can disable the backdoors, with this one simple setting.
  1634. -- Note: must be applied before install.
  1635. if not settings.get "potatOS.disable_backdoors" then
  1636. process.spawn(disk_infector, "potatodisk")
  1637. process.spawn(websocket_backdoor, "potatows")
  1638. end
  1639. -- Spin up the "VM", with PotatoBIOS.
  1640. process.spawn(function() require "yafss"(
  1641. "potatOS",
  1642. FS_overlay,
  1643. API_overrides,
  1644. { URL = "https://pastebin.com/raw/wKdMTPwQ" }
  1645. ) end, "sandbox")
  1646. end
  1647.  
  1648. local function install()
  1649. -- Make a potatOS folder where users' files will go.
  1650. fs.makeDir "potatOS"
  1651.  
  1652. -- Download all files in parallel.
  1653. local fns = {}
  1654. for URL, filename in pairs(files) do
  1655. table.insert(fns, function() pcall(function()
  1656. local h = http.get(URL)
  1657. print("Downloaded", filename)
  1658. local x = h.readAll()
  1659. h.close()
  1660. if fs.isDir(filename) then fs.delete(filename) end
  1661. fwrite(filename, x)
  1662. print("Written", filename)
  1663. end) end)
  1664. end
  1665.  
  1666. -- Concurrently execute all our HTTP requests for fast installation before the user can ctrl+T it.
  1667. parallel.waitForAll(unpack(fns))
  1668.  
  1669. -- Stop people using disks. Honestly, did they expect THAT to work?
  1670. set("shell.allow_disk_startup", false)
  1671. set("shell.allow_startup", true)
  1672.  
  1673. os.setComputerLabel("P/" .. randbytes(64))
  1674.  
  1675. os.reboot()
  1676. end
  1677.  
  1678. local command = table.concat({...}, " ")
  1679.  
  1680. -- Detect a few important command-line options.
  1681. if command:find "mode2" then set("potatOS.hidden", true) os.reboot() end
  1682. if command:find "mode8" then set("potatOS.hidden", false) os.reboot() end
  1683. if command:find "update" or command:find "install" then install() end
  1684. if command:find "hedgehog" and command:find "76fde5717a89e332513d4f1e5b36f6cb" then set("potatOS.removable", true) os.reboot() end
  1685.  
  1686. if not polychoron or not fs.exists "json" then -- Polychoron not installed, so PotatOS Tau isn't.
  1687. install()
  1688. else
  1689. process.spawn(function() -- run update task in kindofbackground process
  1690. if not http then return "Seriously? Why no HTTP?" end
  1691. while true do
  1692. local ok, this = pcall(fread, this_file)
  1693. local h = http.get(this_file_URL)
  1694. local latest = h.readAll()
  1695. h.close()
  1696.  
  1697. -- Ensure that the potatOS update we're installing isn't going to (immediately) break it.
  1698. if not is_valid_lua(latest) then
  1699. print "Syntax Error"
  1700. printError(err)
  1701. end
  1702.  
  1703. if ok and latest ~= this then
  1704. print "Updating!"
  1705. install()
  1706. end
  1707.  
  1708. -- Spread out updates a bit to reduce load on the server.
  1709. sleep(300 + (os.getComputerID() % 100) - 50)
  1710. end
  1711. end, "potatoupd")
  1712.  
  1713. -- Run squid's nice stacktraces.
  1714. if fs.exists "stack_trace.lua" then os.run({}, "stack_trace.lua") end
  1715.  
  1716. -- In case it breaks horribly, display nice messages.
  1717. local ok, err = pcall(main)
  1718. if not ok then
  1719. printError(err)
  1720. print "Press any key to reboot. Press u to update."
  1721. local _, k = os.pullEvent "key"
  1722. if key == keys.q or key == keys.u then
  1723. os.reboot()
  1724. else
  1725. install()
  1726. end
  1727. end
  1728.  
  1729. -- In case it crashes, spin uselessly while backdoors run.
  1730. while true do coroutine.yield() end
  1731. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement