-- Checking for required components local function getComponentAddress(name) return component.list(name)() or error("Required " .. name .. " component is missing") end local EEPROMAddress, internetAddress, GPUAddress = getComponentAddress("eeprom"), getComponentAddress("internet"), getComponentAddress("gpu") -- Binding GPU to screen in case it's not done yet component.invoke(GPUAddress, "bind", getComponentAddress("screen")) local screenWidth, screenHeight = component.invoke(GPUAddress, "getResolution") local repositoryURL = "https://raw.githubusercontent.com/IgorTimofeev/MineOS/master/" local installerURL = "Installer/" local EFIURL = "EFI/Minified.lua" local installerPath = "/MineOS installer/" local installerPicturesPath = installerPath .. "Installer/Pictures/" local OSPath = "/" local temporaryFilesystemProxy, selectedFilesystemProxy -------------------------------------------------------------------------------- -- Working with components directly before system libraries are downloaded & initialized local function centrize(width) return math.floor(screenWidth / 2 - width / 2) end local function centrizedText(y, color, text) component.invoke(GPUAddress, "fill", 1, y, screenWidth, 1, " ") component.invoke(GPUAddress, "setForeground", color) component.invoke(GPUAddress, "set", centrize(#text), y, text) end local function title() local y = math.floor(screenHeight / 2 - 1) centrizedText(y, 0x2D2D2D, "MineOS") return y + 2 end local function progress(value) local width = 26 local x, y, part = centrize(width), title(), math.ceil(width * value) component.invoke(GPUAddress, "setForeground", 0x878787) component.invoke(GPUAddress, "set", x, y, string.rep("─", part)) component.invoke(GPUAddress, "setForeground", 0xC3C3C3) component.invoke(GPUAddress, "set", x + part, y, string.rep("─", width - part)) end local function filesystemPath(path) return path:match("^(.+%/).") or "" end local function filesystemName(path) return path:match("%/?([^%/]+%/?)$") end local function filesystemHideExtension(path) return path:match("(.+)%..+") or path end local function rawRequest(url, chunkHandler) local internetHandle, reason = component.invoke(internetAddress, "request", repositoryURL .. url:gsub("([^%w%-%_%.%~])", function(char) return string.format("%%%02X", string.byte(char)) end)) if internetHandle then local chunk, reason while true do chunk, reason = internetHandle.read(math.huge) if chunk then chunkHandler(chunk) else if reason then error("Internet request failed: " .. tostring(reason)) end break end end internetHandle.close() else error("Connection failed: " .. url) end end local function request(url) local data = "" rawRequest(url, function(chunk) data = data .. chunk end) return data end local function download(url, path) selectedFilesystemProxy.makeDirectory(filesystemPath(path)) local fileHandle, reason = selectedFilesystemProxy.open(path, "wb") if fileHandle then rawRequest(url, function(chunk) selectedFilesystemProxy.write(fileHandle, chunk) end) selectedFilesystemProxy.close(fileHandle) else error("File opening failed: " .. tostring(reason)) end end local function deserialize(text) local result, reason = load("return " .. text, "=string") if result then return result() else error(reason) end end -- Clearing screen component.invoke(GPUAddress, "setBackground", 0xE1E1E1) component.invoke(GPUAddress, "fill", 1, 1, screenWidth, screenHeight, " ") -- Checking minimum system requirements do local function warning(text) centrizedText(title(), 0x878787, text) local signal repeat signal = computer.pullSignal() until signal == "key_down" or signal == "touch" computer.shutdown() end if component.invoke(GPUAddress, "getDepth") ~= 8 then warning("Tier 3 GPU and screen are required") end if computer.totalMemory() < 1024 * 1024 * 2 then warning("At least 2x Tier 3.5 RAM modules are required") end -- Searching for appropriate temporary filesystem for storing libraries, images, etc for address in component.list("filesystem") do local proxy = component.proxy(address) if proxy.spaceTotal() >= 2 * 1024 * 1024 then temporaryFilesystemProxy, selectedFilesystemProxy = proxy, proxy break end end -- If there's no suitable HDDs found - then meow if not temporaryFilesystemProxy then warning("At least Tier 2 HDD is required") end end -- First, we need a big ass file list with localizations, applications, wallpapers progress(0) local files = deserialize(request(installerURL .. "Files.cfg")) -- After that we could download required libraries for installer from it for i = 1, #files.installerFiles do progress(i / #files.installerFiles) download(files.installerFiles[i], installerPath .. files.installerFiles[i]) end -- Initializing simple package system for loading system libraries package = {loading = {}, loaded = {}} function require(module) if package.loaded[module] then return package.loaded[module] elseif package.loading[module] then error("already loading " .. module .. ": " .. debug.traceback()) else package.loading[module] = true local handle, reason = temporaryFilesystemProxy.open(installerPath .. "Libraries/" .. module .. ".lua", "rb") if handle then local data, chunk = "" repeat chunk = temporaryFilesystemProxy.read(handle, math.huge) data = data .. (chunk or "") until not chunk temporaryFilesystemProxy.close(handle) local result, reason = load(data, "=" .. module) if result then package.loaded[module] = result() or true else error(reason) end else error("File opening failed: " .. tostring(reason)) end package.loading[module] = nil return package.loaded[module] end end -- Initializing system libraries local filesystem = require("Filesystem") filesystem.setProxy(temporaryFilesystemProxy) bit32 = bit32 or require("Bit32") local image = require("Image") local text = require("Text") local number = require("Number") local screen = require("Screen") screen.setGPUAddress(GPUAddress) local GUI = require("GUI") local system = require("System") local paths = require("Paths") -------------------------------------------------------------------------------- -- Creating main UI workspace local workspace = GUI.workspace() workspace:addChild(GUI.panel(1, 1, workspace.width, workspace.height, 0x1E1E1E)) -- Main installer window local window = workspace:addChild(GUI.window(1, 1, 80, 24)) window.localX, window.localY = math.ceil(workspace.width / 2 - window.width / 2), math.ceil(workspace.height / 2 - window.height / 2) window:addChild(GUI.panel(1, 1, window.width, window.height, 0xE1E1E1)) -- Top menu local menu = workspace:addChild(GUI.menu(1, 1, workspace.width, 0xF0F0F0, 0x787878, 0x3366CC, 0xE1E1E1)) local installerMenu = menu:addContextMenuItem("MineOS", 0x2D2D2D) installerMenu:addItem("🗘", "Reboot").onTouch = function() computer.shutdown(true) end installerMenu:addItem("⏻", "Shutdown").onTouch = function() computer.shutdown() end -- Main vertical layout local layout = window:addChild(GUI.layout(1, 1, window.width, window.height - 2, 1, 1)) local stageButtonsLayout = window:addChild(GUI.layout(1, window.height - 1, window.width, 1, 1, 1)) stageButtonsLayout:setDirection(1, 1, GUI.DIRECTION_HORIZONTAL) stageButtonsLayout:setSpacing(1, 1, 3) local function loadImage(name) return image.load(installerPicturesPath .. name .. ".pic") end local function newInput(width, ...) return GUI.input(1, 1, width, 1, 0xF0F0F0, 0x787878, 0xC3C3C3, 0xF0F0F0, 0x878787, "", ...) end local function newSwitchAndLabel(width, color, text, state) return GUI.switchAndLabel(1, 1, width, 6, color, 0xD2D2D2, 0xF0F0F0, 0xA5A5A5, text .. ":", state) end local function addTitle(color, text) return layout:addChild(GUI.text(1, 1, color, text)) end local function addImage(before, after, name) if before > 0 then layout:addChild(GUI.object(1, 1, 1, before)) end local picture = layout:addChild(GUI.image(1, 1, loadImage(name))) picture.height = picture.height + after return picture end local function addStageButton(text) local button = stageButtonsLayout:addChild(GUI.adaptiveRoundedButton(1, 1, 2, 0, 0xC3C3C3, 0x878787, 0xA5A5A5, 0x696969, text)) button.colors.disabled.background = 0xD2D2D2 button.colors.disabled.text = 0xB4B4B4 return button end local prevButton = addStageButton("<") local nextButton = addStageButton(">") local localization local stage = 1 local stages = {} local usernameInput = newInput(30, "") local passwordInput = newInput(30, "", false, "•") local passwordSubmitInput = newInput(30, "", false, "•") local usernamePasswordText = GUI.text(1, 1, 0xCC0040, "") local withoutPasswordSwitchAndLabel = newSwitchAndLabel(30, 0x66DB80, "", false) local wallpapersSwitchAndLabel = newSwitchAndLabel(30, 0xFF4980, "", true) local applicationsSwitchAndLabel = newSwitchAndLabel(30, 0x33DB80, "", true) local localizationsSwitchAndLabel = newSwitchAndLabel(30, 0x33B6FF, "", true) local acceptSwitchAndLabel = newSwitchAndLabel(30, 0x9949FF, "", false) local localizationComboBox = GUI.comboBox(1, 1, 26, 1, 0xF0F0F0, 0x969696, 0xD2D2D2, 0xB4B4B4) for i = 1, #files.localizations do localizationComboBox:addItem(filesystemHideExtension(filesystemName(files.localizations[i]))).onTouch = function() -- Obtaining localization table localization = deserialize(request(installerURL .. files.localizations[i])) -- Filling widgets with selected localization data usernameInput.placeholderText = localization.username passwordInput.placeholderText = localization.password passwordSubmitInput.placeholderText = localization.submitPassword withoutPasswordSwitchAndLabel.label.text = localization.withoutPassword wallpapersSwitchAndLabel.label.text = localization.wallpapers applicationsSwitchAndLabel.label.text = localization.applications localizationsSwitchAndLabel.label.text = localization.languages acceptSwitchAndLabel.label.text = localization.accept end end local function addStage(onTouch) table.insert(stages, function() layout:removeChildren() onTouch() workspace:draw() end) end local function loadStage() if stage < 1 then stage = 1 elseif stage > #stages then stage = #stages end stages[stage]() end local function checkUserInputs() local nameEmpty = #usernameInput.text == 0 local nameVaild = usernameInput.text:match("^%w[%w%s_]+$") local passValid = withoutPasswordSwitchAndLabel.switch.state or #passwordInput.text == 0 or #passwordSubmitInput.text == 0 or passwordInput.text == passwordSubmitInput.text if (nameEmpty or nameVaild) and passValid then usernamePasswordText.hidden = true nextButton.disabled = nameEmpty or not nameVaild or not passValid else usernamePasswordText.hidden = false nextButton.disabled = true if nameVaild then usernamePasswordText.text = localization.passwordsArentEqual else usernamePasswordText.text = localization.usernameInvalid end end end local function checkLicense() nextButton.disabled = not acceptSwitchAndLabel.switch.state end prevButton.onTouch = function() stage = stage - 1 loadStage() end nextButton.onTouch = function() stage = stage + 1 loadStage() end acceptSwitchAndLabel.switch.onStateChanged = function() checkLicense() workspace:draw() end withoutPasswordSwitchAndLabel.switch.onStateChanged = function() passwordInput.hidden = withoutPasswordSwitchAndLabel.switch.state passwordSubmitInput.hidden = withoutPasswordSwitchAndLabel.switch.state checkUserInputs() workspace:draw() end usernameInput.onInputFinished = function() checkUserInputs() workspace:draw() end passwordInput.onInputFinished = usernameInput.onInputFinished passwordSubmitInput.onInputFinished = usernameInput.onInputFinished -- Localization selection stage addStage(function() prevButton.disabled = true addImage(0, 1, "Languages") layout:addChild(localizationComboBox) workspace:draw() localizationComboBox:getItem(1).onTouch() end) -- Filesystem selection stage addStage(function() prevButton.disabled = false nextButton.disabled = false layout:addChild(GUI.object(1, 1, 1, 1)) addTitle(0x696969, localization.select) local diskLayout = layout:addChild(GUI.layout(1, 1, layout.width, 11, 1, 1)) diskLayout:setDirection(1, 1, GUI.DIRECTION_HORIZONTAL) diskLayout:setSpacing(1, 1, 1) local HDDImage = loadImage("HDD") local function select(proxy) selectedFilesystemProxy = proxy for i = 1, #diskLayout.children do diskLayout.children[i].children[1].hidden = diskLayout.children[i].proxy ~= selectedFilesystemProxy end end local function updateDisks() local function diskEventHandler(workspace, disk, e1) if e1 == "touch" then select(disk.proxy) workspace:draw() end end local function addDisk(proxy, picture, disabled) local disk = diskLayout:addChild(GUI.container(1, 1, 14, diskLayout.height)) disk.blockScreenEvents = true disk:addChild(GUI.panel(1, 1, disk.width, disk.height, 0xD2D2D2)) disk:addChild(GUI.button(1, disk.height, disk.width, 1, 0xCC4940, 0xE1E1E1, 0x990000, 0xE1E1E1, localization.erase)).onTouch = function() local list, path = proxy.list("/") for i = 1, #list do path = "/" .. list[i] if proxy.address ~= temporaryFilesystemProxy.address or path ~= installerPath then proxy.remove(path) end end updateDisks() end if disabled then picture = image.blend(picture, 0xFFFFFF, 0.4) disk.disabled = true end disk:addChild(GUI.image(4, 2, picture)) disk:addChild(GUI.label(2, 7, disk.width - 2, 1, disabled and 0x969696 or 0x696969, text.limit(proxy.getLabel() or proxy.address, disk.width - 2))):setAlignment(GUI.ALIGNMENT_HORIZONTAL_CENTER, GUI.ALIGNMENT_VERTICAL_TOP) disk:addChild(GUI.progressBar(2, 8, disk.width - 2, disabled and 0xCCDBFF or 0x66B6FF, disabled and 0xD2D2D2 or 0xC3C3C3, disabled and 0xC3C3C3 or 0xA5A5A5, math.floor(proxy.spaceUsed() / proxy.spaceTotal() * 100), true, true, "", "% " .. localization.used)) disk.eventHandler = diskEventHandler disk.proxy = proxy end diskLayout:removeChildren() for address in component.list("filesystem") do local proxy = component.proxy(address) if proxy.spaceTotal() >= 1 * 1024 * 1024 then addDisk( proxy, proxy.spaceTotal() < 1 * 1024 * 1024 and floppyImage or HDDImage, proxy.isReadOnly() or proxy.spaceTotal() < 2 * 1024 * 1024 ) end end select(selectedFilesystemProxy) end updateDisks() end) -- User profile setup stage addStage(function() checkUserInputs() addImage(0, 0, "User") addTitle(0x696969, localization.setup) layout:addChild(usernameInput) layout:addChild(passwordInput) layout:addChild(passwordSubmitInput) layout:addChild(usernamePasswordText) layout:addChild(withoutPasswordSwitchAndLabel) end) -- Downloads customization stage addStage(function() nextButton.disabled = false addImage(0, 0, "Settings") addTitle(0x696969, localization.customize) layout:addChild(wallpapersSwitchAndLabel) layout:addChild(applicationsSwitchAndLabel) layout:addChild(localizationsSwitchAndLabel) end) -- License acception stage addStage(function() checkLicense() local lines = text.wrap({request("LICENSE")}, layout.width - 2) local textBox = layout:addChild(GUI.textBox(1, 1, layout.width, layout.height - 3, 0xF0F0F0, 0x696969, lines, 1, 1, 1)) layout:addChild(acceptSwitchAndLabel) end) -- Downloading stage addStage(function() stageButtonsLayout:removeChildren() -- Creating user profile layout:removeChildren() addImage(1, 1, "User") addTitle(0x969696, localization.creating) workspace:draw() -- Renaming if possible if not selectedFilesystemProxy.getLabel() then selectedFilesystemProxy.setLabel("MineOS HDD") end local function switchProxy(runnable) filesystem.setProxy(selectedFilesystemProxy) runnable() filesystem.setProxy(temporaryFilesystemProxy) end -- Creating system paths local userSettings, userPaths switchProxy(function() paths.create(paths.system) userSettings, userPaths = system.createUser( usernameInput.text, localizationComboBox:getItem(localizationComboBox.selectedItem).text, not withoutPasswordSwitchAndLabel.switch.state and passwordInput.text or nil, wallpapersSwitchAndLabel.switch.state ) end) -- Downloading files layout:removeChildren() addImage(3, 2, "Downloading") local container = layout:addChild(GUI.container(1, 1, layout.width - 20, 2)) local progressBar = container:addChild(GUI.progressBar(1, 1, container.width, 0x66B6FF, 0xD2D2D2, 0xA5A5A5, 0, true, false)) local cyka = container:addChild(GUI.label(1, 2, container.width, 1, 0x969696, "")):setAlignment(GUI.ALIGNMENT_HORIZONTAL_CENTER, GUI.ALIGNMENT_VERTICAL_TOP) -- Creating final filelist of things to download local downloadList = {} local function getData(item) if type(item) == "table" then return item.path, item.id, item.version, item.shortcut else return item end end local function addToList(state, key) if state then local selectedLocalization, path, localizationName = localizationComboBox:getItem(localizationComboBox.selectedItem).text for i = 1, #files[key] do path = getData(files[key][i]) if filesystem.extension(path) == ".lang" then localizationName = filesystem.hideExtension(filesystem.name(path)) if -- If ALL loacalizations need to be downloaded localizationsSwitchAndLabel.switch.state or -- If it's required localization file localizationName == selectedLocalization or -- Downloading English "just in case" for non-english localizations selectedLocalization ~= "English" and localizationName == "English" then table.insert(downloadList, files[key][i]) end else table.insert(downloadList, files[key][i]) end end end end addToList(true, "required") addToList(true, "localizations") addToList(true, "requiredWallpapers") addToList(applicationsSwitchAndLabel.switch.state, "optional") addToList(wallpapersSwitchAndLabel.switch.state, "optionalWallpapers") -- Downloading files from created list local versions, path, id, version, shortcut = {} for i = 1, #downloadList do path, id, version, shortcut = getData(downloadList[i]) cyka.text = text.limit(localization.installing .. " \"" .. path .. "\"", container.width, "center") workspace:draw() -- Download file download(path, OSPath .. path) -- Adding system versions data if id then versions[id] = { path = OSPath .. path, version = version or 1, } end -- Create shortcut if possible if shortcut then switchProxy(function() system.createShortcut( userPaths.desktop .. filesystem.hideExtension(filesystem.name(filesystem.path(path))), OSPath .. filesystem.path(path) ) end) end progressBar.value = math.floor(i / #downloadList * 100) workspace:draw() end -- Flashing EEPROM layout:removeChildren() addImage(1, 1, "EEPROM") addTitle(0x969696, localization.flashing) workspace:draw() component.invoke(EEPROMAddress, "set", request(EFIURL)) component.invoke(EEPROMAddress, "setLabel", "MineOS EFI") component.invoke(EEPROMAddress, "setData", selectedFilesystemProxy.address) -- Saving system versions switchProxy(function() filesystem.writeTable(paths.system.versions, versions, true) end) -- Done info layout:removeChildren() addImage(1, 1, "Done") addTitle(0x969696, localization.installed) addStageButton(localization.reboot).onTouch = function() computer.shutdown(true) end workspace:draw() -- Removing temporary installer directory temporaryFilesystemProxy.remove(installerPath) end) -------------------------------------------------------------------------------- loadStage() workspace:start()