-- Aurora Speed Kit (unofficial) - Main.lua - kit version 1 -- -- Puts Marcus's faster-starting "speed build" of Aurora.xex (Aurora 0.7b.2 r1655; console test 2026-11-04: -- game list in 17 s instead of 96 s) into every Aurora instance it finds. Undo puts the original back. -- Nothing else changes: no databases, settings, skins or plugins. -- -- Safety rules -- * Files are recognised by size and SHA-1 only (pinned below). A file the kit does not recognise is never -- changed or deleted; the only exception is Repair, which you must pick and which only renames it to .bad. -- * The speed build is made on the console from your own original Aurora.xex plus a patch that is useless -- without it. Every file the kit writes is checked before it is used. -- * The original is copied to Aurora.xex.r1655.bak and checked before an instance changes. -- * All downloading and building happens first, in this kit's tmp folder. Then instances change one at a -- time, the one you are running last. Aurora.xex and .bad files are never deleted. -- * The kit deletes only files with its own names: Aurora.xex.r1655.bak, Aurora.xex.sk-new, Aurora.xex.sk-old* -- and SpeedKit folders. Your own copies (for example Aurora.xex.new or Aurora.xex.old) are only read. -- * Files it may add next to Aurora.xex: Aurora.xex.r1655.bak; Aurora.xex.sk-new (while it switches); -- Aurora.xex.sk-old (only if a plain switch is refused); Aurora.xex.bad (Repair). In each instance: -- User\Scripts\Utility\SpeedKit\ with Main.lua and the patch. In this folder: the patch, tmp\ (while it -- works), speedkit.log and speedkit.marker. -- -- If an instance will not start, see HELP below. Never turn the console off while the kit says so. -- -- For editors: the top level must hold only plain values and function definitions. Aurora runs it while it -- builds the Scripts list, without the string, table or Aurora libraries; a library call there hides the kit. scriptTitle = "Aurora Speed Kit (unofficial)" scriptAuthor = "Marcus" scriptVersion = 1 scriptDescription = "Unofficial: makes Aurora 0.7b.2 (r1655) start faster by putting a checked speed build in each instance, with backups and Undo." scriptPermissions = { "http", "filesystem" } -- ----- Pinned values (computed on the PC from the lab files; do not edit) ---------------------------------- local KIT_VERSION = 1 local KIT_TITLE = "Aurora Speed Kit (unofficial)" local BASEURL = "http://thatoneconsolemodder.app/" -- set by the release builder, for example http://thevault.example/ local PATCH_URL = "sk/v1/r1655-b1.xdp" -- added to BASEURL; the builder checks it against the kit version -- Stock Aurora 0.7b.2 r1655. Also CRC32 590CA44F, MD5 20638912B5685327460AB37529DC9638. local STOCK = { key = "original", text = "original Aurora.xex", size = 12333056, sha1 = "347613582AA0210C8B723A35D4658E91A4A90808" } -- Speed build ("Build B"). Also CRC32 8AA7B6B3, MD5 F7E7E558B163CF26FE94A9FC26B34F30. local SPEED = { key = "speed", text = "speed build", size = 12333056, sha1 = "1621A9DAF492682254A048E7C41967659629F127" } -- Patch = stock XOR speed build over RANGES, one range after the other. Also CRC32 5DBCD54D, -- MD5 76392B636236F3EEB6BDD8496D7424BF. It works both ways: stock XOR patch = speed, speed XOR patch = stock. local PATCH = { key = "patch", text = "speed patch", size = 9088864, sha1 = "655BA302B9C911065FD5A5D8D9E26942E47DFB70" } -- The only bytes that differ between the two builds: { file offset, length }; both lengths are multiples of 4. local RANGES = { { 0x20C, 4480 }, { 0x319220, 9084384 } } -- R2: the kit's working copies have names only the kit uses, so it never deletes a copy you made yourself. local XEX, BAK, NEW, OLD, BAD = "Aurora.xex", "Aurora.xex.r1655.bak", "Aurora.xex.sk-new", "Aurora.xex.sk-old", "Aurora.xex.bad" local MAIN_FILE, PATCH_FILE, TMP = "Main.lua", "r1655-b1.xdp", "tmp" local LOG_FILE, MARKER_FILE = "speedkit.log", "speedkit.marker" local KIT_REL = "User\\Scripts\\Utility\\SpeedKit" -- the kit's folder inside an instance local DEBUG_LOG = "Game:\\Data\\Logs\\debug.log" local CHUNK, WORDS = 65536, 1024 -- bytes per read; 32-bit words per pack/unpack call local LOW_SPACE = 157286400 -- warn below 150 MB free (advice only) local WRAP, PAGE = 60, 8 -- one-button message box: characters per line, lines per box local BOX_WRAP, BOX_LINES = 68, 11 -- two-button box: wraps by itself (about 72), shows about 12 lines local SKIP_DRIVES = { ["game:"] = true, ["system:"] = true, ["hdd0:"] = true, ["hddx:"] = true, ["sysext:"] = true, ["flash:"] = true, ["dvd:"] = true, ["hddvdplayer:"] = true, ["hddvdstorage:"] = true, ["transfercable:"] = true, ["transfercablexbox1:"] = true, ["connx:"] = true } local STATE_TEXT = { original = "original", speed = "speed build", missing = "missing" } local HELP = "1. Start another Aurora instance from your boot menu (for example qLoader) and run Scripts > Aurora Speed Kit there. Install or Undo repairs the broken instance.\n\n2. Or, in a working instance, open File Manager and go to the broken instance's Aurora folder. Rename the broken Aurora.xex to Aurora.xex.broken. Then rename Aurora.xex.r1655.bak (or Aurora.xex.sk-new or Aurora.xex.sk-old) to Aurora.xex. The next Install makes a new backup.\n\n3. If no Aurora starts at all, put the drive on a PC (FATXplorer) and do the same renames there.\n\n4. Never turn the console off while the kit says 'do not turn the console off'." local K = {} -- run-time state set in main() and scan(): base, mainSha, mainId, drives, unsure, multi -- ----- Small helpers (every Aurora call is protected; file results are checked by hash, never trusted) ----- local function int(x) return math.floor(tonumber(x) or 0) end local function stateText(s) return STATE_TEXT[s] or "unknown" end local function startsWith(s, p) return string.sub(s, 1, #p) == p end local function stamp() local okd, d = pcall(Aurora.GetDate) local okt, t = pcall(Aurora.GetTime) if not okd or type(d) ~= "table" then d = {} end if not okt or type(t) ~= "table" then t = {} end return string.format("%04d-%02d-%02d %02d:%02d:%02d.%03d", int(d.Year), int(d.Month), int(d.Day), int(t.Hour), int(t.Minute), int(t.Second), int(t.Milliseconds)) end -- Support log: "SpeedKit|" lines in Aurora's debug.log, plus speedkit.log in the kit folder. local function log(msg) msg = tostring(msg) print("SpeedKit| " .. msg) if not K.base then return end pcall(function() local f = io.open(K.base .. LOG_FILE, "ab") if f then f:write(stamp(), " ", msg, "\r\n") f:close() end end) end local function status(text, cur, total) pcall(Script.SetStatus, text) if cur and total and total > 0 then pcall(Script.SetProgress, cur, total) end end -- Lab fix F1: Aurora's one-button message box does not wrap text; every paragraph longer than one line is cut -- with "..." (lab test 2026-11-05: the HELP steps were unreadable). So the kit breaks long lines itself, at a -- space, or after a backslash inside a long path. local function wrap(text, width) local out = {} for line in string.gmatch(tostring(text) .. "\n", "(.-)\n") do while #line > width do local cut = nil for i = width + 1, 2, -1 do if string.sub(line, i, i) == " " then cut = i break end end if cut then out[#out + 1] = string.sub(line, 1, cut - 1) line = string.sub(line, cut + 1) else local b = width for i = width, 2, -1 do if string.sub(line, i, i) == "\\" then b = i break end end out[#out + 1] = string.sub(line, 1, b) line = string.sub(line, b + 1) end end out[#out + 1] = line end return table.concat(out, "\n") end -- The one-button box also shows only about 8 lines (lab test: step 3 of the HELP text was cut off), so a longer -- text is shown in parts, "(1/2)" and so on, with a Next button. Parts end between paragraphs where possible. -- Returns false when a part was closed with B (R17: then the rest is not shown). local function say(title, text) local paras, cur = {}, {} for l in string.gmatch(wrap(text, WRAP) .. "\n", "(.-)\n") do if l == "" then if #cur > 0 then paras[#paras + 1] = cur cur = {} end else cur[#cur + 1] = l end end if #cur > 0 then paras[#paras + 1] = cur end local pages, page = {}, {} for _, p in ipairs(paras) do if #page > 0 and #page + 1 + #p > PAGE then pages[#pages + 1] = page page = {} end if #page > 0 then page[#page + 1] = "" end for _, l in ipairs(p) do if #page >= PAGE then pages[#pages + 1] = page page = {} end page[#page + 1] = l end end if #page > 0 or #pages == 0 then pages[#pages + 1] = page end for i, pg in ipairs(pages) do local t = (#pages > 1) and (title .. " (" .. i .. "/" .. #pages .. ")") or title local ok, r = pcall(Script.ShowMessageBox, t, table.concat(pg, "\n"), (i < #pages) and "Next" or "OK") if ok and type(r) == "table" and r.Canceled == true then return false end end return true end -- Two-button box: 1 or 2 for the button picked, nil when it was closed with B. The first button has the focus. local function choose(title, text, first, second) local ok, r = pcall(Script.ShowMessageBox, title, text, first, second) if ok and type(r) == "table" and r.Canceled ~= true and (r.Button == 1 or r.Button == 2) then return r.Button end return nil end -- true only when the first button was chosen (B counts as No) local function ask(title, text, yes, no) return choose(title, text, yes or "Yes", no or "No") == 1 end -- R7: the two-button box shows only about 11 lines (lab test: with four instances and a download its end was -- cut off). A longer text is shown first with say(), in parts, and then only the short question is asked. local function confirm(title, head, body, question, yes, no) local text = body .. "\n\n" .. question if head ~= "" then text = head .. "\n\n" .. text end local n = 0 for _ in string.gmatch(wrap(text, BOX_WRAP) .. "\n", "(.-)\n") do n = n + 1 end if n <= BOX_LINES then return ask(title, text, yes, no) end if not say(title, body) then return false end return ask(title, ((head ~= "") and (head .. "\n\n") or "") .. question, yes, no) end -- number of the chosen line, or nil local function pick(title, items) local ok, r = pcall(Script.ShowPopupList, title, "Nothing to show.", items) if not ok or type(r) ~= "table" or r.Canceled == true or type(r.Selected) ~= "table" then return nil end return tonumber(r.Selected.Key) end local function canceled() local ok, c = pcall(Script.IsCanceled) return ok and c == true end local function isThere(p) local ok, r = pcall(FileSystem.FileExists, p) return ok and r == true end local function copyFile(a, b, overwrite) local ok, r = pcall(FileSystem.CopyFile, a, b, overwrite == true) return ok and r == true end local function moveFile(a, b) local ok, r = pcall(FileSystem.MoveFile, a, b, true) return ok and r == true end local function renameFile(a, b) local ok, r = pcall(FileSystem.Rename, a, b) return ok and r == true end -- Lab fix F5: size through io first. In the lab, FileSystem.GetFileSize returned 0 for a file CopyFile had just -- written (and for that file after a MoveFile), while io.open and the SHA-1 read it correctly, so every backup -- check failed. GetFileSize is only the fallback now; the SHA-1 check stays the authority. local function fileSize(p) local ok, r = pcall(function() local f = io.open(p, "rb") if not f then return nil end local n = f:seek("end") f:close() return n end) if ok and type(r) == "number" then return r end local ok2, r2 = pcall(FileSystem.GetFileSize, p) if ok2 and type(r2) == "number" then return r2 end return -1 end local function fileSha1(p) local ok, r = pcall(Aurora.Sha1HashFile, p) if ok and type(r) == "string" then return string.upper(r) end return "" end -- R10: Sha1HashFile returns "" for about 1 file in 256 (first digest byte 00), so the kit's own copies are -- recognised by size and CRC32 (always 8 digits) instead. local function kitId(p) local ok, c = pcall(Aurora.Crc32HashFile, p) if not ok or type(c) ~= "string" or c == "" then return "" end return fileSize(p) .. "/" .. string.upper(c) end local function matches(p, want) return isThere(p) and fileSize(p) == want.size and fileSha1(p) == want.sha1 end local function remove(p) if not isThere(p) then return true end pcall(FileSystem.DeleteFile, p) return not isThere(p) end local function makeDir(p) pcall(FileSystem.CreateDirectory, p) -- always returns true, so check afterwards return isThere(p) end local function removeDir(p) if isThere(p) then pcall(FileSystem.DeleteDirectory, p) end return not isThere(p) end -- first free name among stem, stem2 .. stem9 in dir local function freeName(dir, stem) for i = 1, 9 do local n = (i == 1) and stem or (stem .. i) if not isThere(dir .. "\\" .. n) then return n end end return nil end -- What a file is, from its content only: "missing", "original", "speed" or "unknown"; also its SHA-1. local function classify(p) if not isThere(p) then return "missing", "" end if fileSize(p) ~= STOCK.size then return "unknown", "" end local h = fileSha1(p) if h == STOCK.sha1 then return "original", h end if h == SPEED.sha1 then return "speed", h end return "unknown", h end -- TheVault address, or nil when the release builder did not fill it in local function baseUrl() local u = BASEURL if type(u) ~= "string" or not string.find(u, "^https?://[%w%-%.]+") then return nil end if string.sub(u, -1) ~= "/" then u = u .. "/" end return u end -- How an instance is named in plans and summaries: "XBLA", "XBLA (Usb0:)" when drives differ, plus "(running)". local function name(inst) local n = inst.label if K.multi and inst.where ~= "Game:" then n = n .. " (" .. inst.where .. ")" end if inst.running then n = n .. " (running)" end return n end -- R15: the drive as users know it ("Hdd1:"), not Aurora's internal drive name local function driveText(inst) if inst.where == "Game:" then return "this Aurora's drive:" end return inst.where end -- ----- Finding the instances ------------------------------------------------------------------------------ local function looksLikeInstance(root) return isThere(root .. "\\" .. XEX) or isThere(root .. "\\" .. BAK) or isThere(root .. "\\" .. NEW) or isThere(root .. "\\" .. OLD) end -- Reads one instance's state from file contents only. R2: the kit's own copies have kit-only names -- (.r1655.bak, .sk-new, .sk-old*); every other Aurora.xex.* file is yours and is only ever read. local function readInstance(inst) local dir = inst.op inst.state, inst.sha = classify(dir .. "\\" .. XEX) local f = { bak = classify(dir .. "\\" .. BAK), new = classify(dir .. "\\" .. NEW), olds = {}, bads = {}, others = {}, checked = {} } local lbak, lnew, lold, lbad = string.lower(BAK), string.lower(NEW), string.lower(OLD), string.lower(BAD) local ok, items = pcall(FileSystem.GetFiles, dir .. "\\*") for _, e in ipairs((ok and type(items) == "table") and items or {}) do local n = type(e) == "table" and e.Name or nil local l = type(n) == "string" and string.lower(n) or "" local isDir = type(e) == "table" and (int(e.Attributes) // 16) % 2 == 1 if not isDir and startsWith(l, "aurora.xex.") and l ~= lbak and l ~= lnew then if startsWith(l, lold) then f.olds[#f.olds + 1] = { name = n, state = classify(dir .. "\\" .. n) } elseif startsWith(l, lbad) then f.bads[#f.bads + 1] = n else f.others[#f.others + 1] = n -- yours, for example Aurora.xex.new or a dated backup; hashed only when needed end end end inst.files = f inst.hasKit = isThere(dir .. "\\" .. KIT_REL .. "\\" .. MAIN_FILE) -- Repair is offered only where a good copy made by the kit sits next to Aurora.xex (m11). local rep = f.bak == "original" or f.new == "original" or f.new == "speed" for _, o in ipairs(f.olds) do rep = rep or o.state == "original" or o.state == "speed" end inst.repairable = rep end -- Which found folder is the running instance (m10)? The kit writes a fresh token into its own marker file -- (under Game:) and reads the marker of every found folder: only an exact token match counts (R16). The -- marker stays in this folder and gets a new token each time, so other instances' folders are only read. local function markMine() local token = "SK1-" .. string.gsub(stamp(), "%D", "") .. "-" .. string.gsub(tostring({}), "%W", "") local mine = K.base .. MARKER_FILE pcall(FileSystem.WriteFile, mine, token) -- plain text without line breaks, so text mode is fine local ok, back = pcall(FileSystem.ReadFile, mine) if ok and back == token then return token end return nil end local function hasToken(kitDir, token) -- kitDir ends with \ local p = kitDir .. MARKER_FILE if not token or not isThere(p) then return false end local ok, t = pcall(FileSystem.ReadFile, p) return ok and t == token end -- R8: is this kit folder (given without a \ at the end) the very folder this kit runs from? local function sameAsMine(kitDir) return hasToken(kitDir .. "\\", markMine()) end local function identifyRunning(found) local rel = string.match(K.base, "^[Gg][Aa][Mm][Ee]:\\(.+)$") -- kit folder below Game:\, ends with \ local hits, token = {}, nil if rel then token = markMine() end if token then for i, f in ipairs(found) do if hasToken(f.root .. "\\" .. rel, token) then hits[#hits + 1] = i end end end return hits end -- Every \AuroraInstances\\Aurora and \Aurora, plus the running instance, always last. local function scan() status("Looking for Aurora instances...", 0, 1) local found, seen = {}, {} K.drives = {} local ok, drives = pcall(FileSystem.GetDrives) for _, d in ipairs((ok and type(drives) == "table") and drives or {}) do local mp = type(d) == "table" and d.MountPoint or nil if type(mp) == "string" and mp ~= "" then local key, dname, sys = string.lower(mp), tostring(d.Name or ""), string.lower(tostring(d.SystemPath or "")) K.drives[key] = dname local skip = SKIP_DRIVES[key] or string.lower(dname) == "aurora" or string.find(key, "cache", 1, true) ~= nil if not skip and sys ~= "" then skip = seen[sys] == true -- the same partition under a second name seen[sys] = true end if not skip then local roots = {} local okd, dirs = pcall(FileSystem.GetDirectories, mp .. "\\AuroraInstances\\*") for _, e in ipairs((okd and type(dirs) == "table") and dirs or {}) do local n = type(e) == "table" and e.Name or nil if type(n) == "string" and n ~= "." and n ~= ".." then roots[#roots + 1] = { label = n, root = mp .. "\\AuroraInstances\\" .. n .. "\\Aurora" } end end roots[#roots + 1] = { label = "Aurora", root = mp .. "\\Aurora" } for _, r in ipairs(roots) do if looksLikeInstance(r.root) then found[#found + 1] = { label = r.label, where = mp, root = r.root, op = r.root, drive = dname } end end end end end local hits, hit, list = identifyRunning(found), {}, {} for _, i in ipairs(hits) do hit[i] = true end for i, f in ipairs(found) do if not hit[i] then list[#list + 1] = f end -- every match is the running instance under another name end local runner if #hits == 1 then runner = found[hits[1]] runner.op = "Game:" else -- not exactly one match: Game: is its own entry, and doChange watches for doubles runner = { label = "This instance", where = "Game:", root = "Game:", op = "Game:", drive = K.drives["game:"] } end runner.running = true K.unsure = #hits ~= 1 list[#list + 1] = runner local wheres, labels, nw = {}, {}, 0 K.multi = false for _, inst in ipairs(list) do if inst.where ~= "Game:" and not wheres[inst.where] then wheres[inst.where] = true nw = nw + 1 end if labels[inst.label] then K.multi = true end labels[inst.label] = true end K.multi = K.multi or nw > 1 for i, inst in ipairs(list) do status("Checking " .. inst.label .. "...", i - 1, #list) readInstance(inst) end log("scan: " .. #list .. " instance(s); running instance " .. (K.unsure and ("not matched (" .. #hits .. " matches)") or runner.root)) for _, inst in ipairs(list) do log(" " .. inst.root .. ": " .. inst.state) end return list end -- Which build is running now, from this boot's debug.log (m2): the original logs "Table: GameListSorters" -- at start; the speed build logs "Loading User Sorts:" without it. Anything else is unknown. local function runningNow() local ok, res = pcall(function() local f = io.open(DEBUG_LOG, "rb") if not f then return "unknown" end local loading, sorters, tail, total = false, false, "", 0 while total < 16777216 do local s = f:read(CHUNK) if not s or #s == 0 then break end local buf = tail .. s loading = loading or string.find(buf, "Loading User Sorts:", 1, true) ~= nil sorters = sorters or string.find(buf, "Table: GameListSorters", 1, true) ~= nil tail, total = string.sub(buf, -64), total + #s end f:close() if sorters then return "original" end return loading and "speed" or "unknown" end) return (ok and type(res) == "string") and res or "unknown" end -- Free space in bytes, or nil when Aurora cannot tell (m3: an unknown drive name raises an error). local function freeBytes(dname) if type(dname) ~= "string" or dname == "" then return nil end local ok, v = pcall(FileSystem.GetPartitionFreeSpace, dname) return (ok and type(v) == "number") and v or nil end local function mb(v) return string.format("%.0f MB", v / 1048576) end -- ----- Getting a checked source file ------------------------------------------------------------------------ -- A place on the console holding a checked copy of `want` (cheapest first), or nil. Only read, never changed. local function findCopy(want, list) for _, inst in ipairs(list) do if inst.state == want.key then return inst.op .. "\\" .. XEX end end for _, inst in ipairs(list) do local f = inst.files if f.new == want.key then return inst.op .. "\\" .. NEW end if f.bak == want.key then return inst.op .. "\\" .. BAK end for _, o in ipairs(f.olds) do if o.state == want.key then return inst.op .. "\\" .. o.name end end end for _, inst in ipairs(list) do local f = inst.files for _, n in ipairs(f.others) do if f.checked[n] == nil then f.checked[n] = classify(inst.op .. "\\" .. n) end if f.checked[n] == want.key then return inst.op .. "\\" .. n end end end return nil end -- Downloads the patch into tmp\ and checks it. Returns its path, or nil and a message. local function download() local url = baseUrl() if not url then return nil, "This copy of the kit has no TheVault address, so it cannot download the patch.\n\nNothing was changed." end local okn, online = pcall(Aurora.HasInternetConnection) if not (okn and online == true) and not ask(KIT_TITLE, "Aurora says this console is not online.\n\nThe kit needs to download the speed patch (about 9 MB) from TheVault. Nothing has been changed yet.\n\nTry anyway?", "Try anyway", "Cancel") then return nil, "Download cancelled. Nothing was changed." end local rel = TMP .. "\\" .. PATCH_FILE local path = K.base .. rel remove(path) remove(path .. ".dl") -- R12: Http.Get has no progress callback, so the bar stands still until the download ends. status("Downloading the speed patch (about 9 MB). The bar does not move until it ends. Nothing has been changed yet...", 0, 1) log("download " .. url .. PATCH_URL) local ok, r = pcall(Http.Get, url .. PATCH_URL, rel) -- nil on network errors; redirects are not followed local problem if not ok or type(r) ~= "table" then problem = "TheVault did not answer (network error). Check the console's network and try again later." elseif r.Success ~= true then problem = (r.Canceled == true) and "The download was cancelled." or ("TheVault answered with an error" .. (r.ResponseCode and (" (HTTP " .. tostring(r.ResponseCode) .. ")") or "") .. ".") elseif not matches(path, PATCH) then problem = "The downloaded patch did not pass the check (" .. fileSize(path) .. " bytes, expected " .. PATCH.size .. "), so it was deleted." end if not problem then -- R13: keep the checked patch in the kit folder, so the kit copies spread it and Undo works offline later local keep = K.base .. PATCH_FILE if not matches(keep, PATCH) then moveFile(path, keep) end if matches(keep, PATCH) then log("download ok; patch kept as " .. keep) return keep end if matches(path, PATCH) then return path end problem = "The downloaded patch could not be kept." end log("download failed: " .. problem .. (ok and "" or (" " .. tostring(r)))) remove(path) remove(path .. ".dl") return nil, problem .. "\n\nNothing was changed." end -- out = in XOR patch over RANGES; every other byte is copied unchanged. Streams 64 KB at a time. local function xorBuild(inPath, patchPath, outPath) local pos, sum = 0, 0 for _, r in ipairs(RANGES) do -- m18: refuse a layout the loop below cannot handle if r[1] < pos or r[2] <= 0 or r[2] % 4 ~= 0 then return false, "Internal error: bad patch range." end pos, sum = r[1] + r[2], sum + r[2] end if pos > STOCK.size or sum ~= PATCH.size or fileSize(inPath) ~= STOCK.size or fileSize(patchPath) ~= PATCH.size then return false, "Internal error: the patch does not fit this file." end local fi, fp, fo = io.open(inPath, "rb"), io.open(patchPath, "rb"), io.open(outPath, "wb") local function closeAll() if fi then fi:close() end if fp then fp:close() end if fo then fo:close() end end if not (fi and fp and fo) then closeAll() remove(outPath) return false, "The kit could not open its work files.\n\nNothing was changed." end local full = "<" .. string.rep("i4", WORDS) -- built here, never at the top level (M6) local total, done, stopped = STOCK.size, 0, false local function step(n) done = done + n status("Building the new Aurora.xex... " .. (done // (total // 100)) .. "%", done, total) stopped = stopped or canceled() return not stopped end local function pass(n) -- copy n bytes unchanged while n > 0 do local k = math.min(n, CHUNK) local a = fi:read(k) if not a or #a ~= k or not fo:write(a) then return false end n = n - k if not step(k) then return false end end return true end local function mix(n) -- XOR n bytes with the next n bytes of the patch, as signed 32-bit words while n > 0 do local k = math.min(n, CHUNK) local a, b = fi:read(k), fp:read(k) if not a or not b or #a ~= k or #b ~= k then return false end local parts, p = {}, 1 while p <= k do local m = math.min(WORDS, (k - p + 1) // 4) if m < 1 then return false end local f = (m == WORDS) and full or ("<" .. string.rep("i4", m)) local wa, wb = { string.unpack(f, a, p) }, { string.unpack(f, b, p) } for i = 1, m do wa[i] = wa[i] ~ wb[i] end parts[#parts + 1] = string.pack(f, table.unpack(wa, 1, m)) p = p + 4 * m end if not fo:write(table.concat(parts)) then return false end n = n - k if not step(k) then return false end end return true end local ok = true pos = 0 for _, r in ipairs(RANGES) do ok = ok and pass(r[1] - pos) and mix(r[2]) pos = r[1] + r[2] end ok = ok and pass(total - pos) and fi:read(1) == nil and fp:read(1) == nil -- both inputs used up exactly closeAll() if ok then return true end remove(outPath) if stopped then return false, "Stopped. Nothing was changed." end return false, "Building the new file failed (read or write error).\n\nNothing was changed." end -- Makes tmp\source.xex, a checked copy of `want`. Any download happens here, before any instance is touched. local function prepareSource(want, other, list) local tmp = K.base .. TMP removeDir(tmp) if not makeDir(tmp) then return nil, "The kit could not create its work folder " .. tmp .. ".\n\nNothing was changed." end local out = tmp .. "\\source.xex" -- 1. A checked copy is already on the console (another instance, a waiting new file, a backup): no download (M3). local have = findCopy(want, list) if have then status("Copying the " .. want.text .. " (no download needed)...", 0, 1) if copyFile(have, out, true) and matches(out, want) then log("source: copy of " .. have) return out end remove(out) log("source: copy of " .. have .. " failed the check") end -- 2. Otherwise build it from a checked copy of the other build and the patch. local from = findCopy(other, list) if not from then return nil, "No checked copy of the " .. other.text .. " was found on this console.\n\nNothing was changed." end local patch = K.base .. PATCH_FILE -- USB package (or an earlier download): the patch sits in the kit folder if not matches(patch, PATCH) then -- Lab fix F4: say so when a USB-package patch is there but damaged (lab test: only the network error showed). local damaged = isThere(patch) local msg patch, msg = download() if not patch then if damaged then msg = "The patch file in the kit folder (" .. PATCH_FILE .. ") is damaged, so the kit tried to download it instead.\n\n" .. tostring(msg) end return nil, msg end end status("Copying the " .. other.text .. "...", 0, 1) local input = tmp .. "\\input.xex" if not (copyFile(from, input, true) and matches(input, other)) then return nil, "The copy of " .. from .. " did not check out.\n\nNothing was changed." end local ok, err = xorBuild(input, patch, out) remove(input) if not ok then return nil, err end status("Checking the new file...", 0, 1) if not matches(out, want) then remove(out) return nil, "The rebuilt file is not exactly the " .. want.text .. ", so it was deleted.\n\nNothing was changed." end log("source: built from " .. from .. " and " .. patch) return out end -- ----- Changing one instance ---------------------------------------------------------------------------------- -- Aurora.xex is missing: puts a checked `want` there, from the waiting new file or as a copy of `from`. local function putIn(dir, want, from) local xex, new = dir .. "\\" .. XEX, dir .. "\\" .. NEW if matches(new, want) and renameFile(new, xex) and matches(xex, want) then return true end if isThere(xex) then return false end return from ~= nil and matches(from, want) and copyFile(from, xex, false) and matches(xex, want) end -- Switches the checked Aurora.xex.sk-new in. before = what Aurora.xex was just before; from = another checked -- copy of `want` (or nil). Returns "done", "pending" or "failed" and a message for the summary. local function swapIn(dir, want, before, from) local xex, new = dir .. "\\" .. XEX, dir .. "\\" .. NEW local done = "now has the " .. want.text local moved = moveFile(new, xex) local now = classify(xex) if now == want.key then return "done", done end log((moved and "MoveFile said yes, but " or "MoveFile refused; ") .. xex .. " is " .. now) -- R4: Aurora.xex turned into something else (half written?): set it aside at once (never deleted). if now ~= before and now ~= "missing" then local bad = freeName(dir, BAD) if not (bad and renameFile(xex, dir .. "\\" .. bad)) then return "failed", "Aurora.xex did not check out after the switch and could not be set aside. Run the kit again to repair it" end log("set aside " .. xex .. " as " .. bad) now = "missing" end if now == "missing" then if putIn(dir, want, from) then return "done", done end return "failed", "SWAP FAILED: Aurora.xex is missing. Run the kit again now to repair it, or see Help" end -- Aurora.xex is as it was, so the switch was refused (R4: also when MoveFile said yes). if not matches(new, want) then return "failed", "the switch was refused and the checked new file is gone; Aurora.xex was not changed" end -- m13: set the current file aside, rename the new one in, and undo that if the second step fails. local old = freeName(dir, OLD) if not (old and renameFile(xex, dir .. "\\" .. old)) then return "pending", "could not be switched while in use; Aurora.xex was not changed. The checked new file waits as " .. NEW end local oldPath = dir .. "\\" .. old log("renamed " .. xex .. " to " .. old) if renameFile(new, xex) and matches(xex, want) then return "done", done .. " (previous file kept as " .. old .. ")" end if isThere(xex) then -- renamed in, but it does not check out: set it aside, never delete it local bad = freeName(dir, BAD) if not (bad and renameFile(xex, dir .. "\\" .. bad)) then return "failed", "Aurora.xex did not check out after the switch; the previous file is kept as " .. old .. ". Run the kit again to repair it" end end if renameFile(oldPath, xex) and classify(xex) == before then log("put back " .. xex) return "pending", "could not be switched; Aurora.xex was not changed" .. (matches(new, want) and (". The checked new file waits as " .. NEW) or "") end if putIn(dir, want, from) then return "done", done .. " (previous file kept as " .. old .. ")" end if not isThere(xex) and copyFile(oldPath, xex, false) and classify(xex) == before then return "pending", "could not be switched; the previous Aurora.xex was put back (a copy of " .. old .. ")" end return "failed", "SWAP FAILED: Aurora.xex is missing; the previous file is kept as " .. old .. ". Run the kit again now to repair it, or see Help" end -- Puts `want` into one instance. src = checked file in tmp\ (nil when every target has its own copy). -- Returns "done", "already", "left", "pending" or "failed" and a message. local function replaceOne(inst, want, src) local dir = inst.op local xex, new, bak = dir .. "\\" .. XEX, dir .. "\\" .. NEW, dir .. "\\" .. BAK local state = classify(xex) -- read again: never act on old information if state == want.key then return "already", "already has the " .. want.text end if (state == "unknown" or state == "missing") and not inst.repair then return "left", "left alone (Aurora.xex is " .. stateText(state) .. ")" end local from = src if want == STOCK and matches(bak, STOCK) then from = bak end -- Undo prefers this instance's own backup -- 1. Install: a checked copy of the original as Aurora.xex.r1655.bak (a good existing one is kept). In a -- repair the original may only be in the waiting new file, which step 2 replaces, so it is backed up too. if want == SPEED and not matches(bak, STOCK) then local orig = nil if state == "original" then orig = xex elseif matches(new, STOCK) then orig = new end if orig then status("Backing up " .. name(inst) .. " - do not turn the console off") if not (copyFile(orig, bak, true) and matches(bak, STOCK)) then remove(bak) return "failed", "the backup could not be made; Aurora.xex was not changed" end end end -- 2. Aurora.xex.sk-new: keep one that already checks out (M3); otherwise copy and check if not matches(new, want) then if not from then return "failed", "no checked source file; Aurora.xex was not changed" end status("Copying to " .. name(inst) .. " - do not turn the console off") remove(new) -- the kit's own name (R2): a leftover of an earlier run if not (copyFile(from, new, false) and matches(new, want)) then remove(new) return "failed", "the new file did not copy correctly; Aurora.xex was not changed" end end -- 3. Repair (m12): an unknown Aurora.xex is set aside as Aurora.xex.bad, never deleted local aside if state == "unknown" then aside = freeName(dir, BAD) if not aside or not renameFile(xex, dir .. "\\" .. aside) then return "failed", "the unknown Aurora.xex could not be set aside; it was not changed" end log("set aside " .. xex .. " as " .. aside) end -- 4. Switch status("Switching " .. name(inst) .. " - do not turn the console off") local code, msg = swapIn(dir, want, aside and "missing" or state, from) if aside and code == "done" then msg = msg .. "; the unknown file was kept as " .. aside elseif aside and not isThere(xex) and renameFile(dir .. "\\" .. aside, xex) then msg = "could not switch; the unknown Aurora.xex was put back and the checked new file waits as " .. NEW end return code, msg end -- R14: may this kit write its Main.lua at dst? nil = yes; "same" = it is already there; else why not. local function otherKit(dst) if not isThere(dst) then return nil end if kitId(dst) == K.mainId then return "same" end local ok, txt = pcall(FileSystem.ReadFile, dst) if not ok or type(txt) ~= "string" or not string.find(txt, "Aurora Speed Kit", 1, true) then return "another script is there" end local v = tonumber(string.match(txt, "\nscriptVersion%s*=%s*(%d+)") or "") if v and v > KIT_VERSION then return "a newer kit (v" .. v .. ") is there" end return nil end -- Copies this kit (and a checked patch) into every other instance, so Check, Undo and finishing a waiting -- switch work from any of them. Skips the running instance and files already in place (m9). local function copyKit(list) if K.mainId == "" then return log("kit copy skipped: this kit's Main.lua could not be read") end local mainSrc, patchSrc = K.base .. MAIN_FILE, K.base .. PATCH_FILE local withPatch = matches(patchSrc, PATCH) for _, inst in ipairs(list) do if not inst.running and (inst.state == "original" or inst.state == "speed") then local dir = inst.op .. "\\" .. KIT_REL local dst, pdst = dir .. "\\" .. MAIN_FILE, dir .. "\\" .. PATCH_FILE local result if K.unsure and sameAsMine(dir) then result = "skipped (the kit that is running)" -- R8 else local why = otherKit(dst) if why and why ~= "same" then result = "skipped: " .. why -- R14: never overwrite another script or a newer kit else status("Copying the kit to " .. name(inst) .. "...") local okMain = why == "same" or (makeDir(dir) and copyFile(mainSrc, dst, true) and kitId(dst) == K.mainId) if okMain and withPatch and not matches(pdst, PATCH) and not (copyFile(patchSrc, pdst, true) and matches(pdst, PATCH)) then remove(pdst) end result = okMain and "ok" or "FAILED" end end log("kit copy to " .. dir .. ": " .. result) end end end local function pickTargets(list, verb) local items = { "All instances (" .. #list .. ")" } for _, inst in ipairs(list) do items[#items + 1] = inst.label .. " (" .. inst.where .. ") - " .. stateText(inst.state) .. (inst.running and " - running now" or "") end local k = pick(verb .. ": which instances?", items) if k == 1 then return list end if k and list[k - 1] then return { list[k - 1] } end return nil end -- ----- Menu actions ----------------------------------------------------------------------------------------- local function details(inst) local f, xex = inst.files, inst.op .. "\\" .. XEX local t = { "Folder: " .. inst.root, "Aurora.xex: " .. stateText(inst.state) } if inst.state == "unknown" then -- size and SHA-1 prefix, so the user can report it t[2] = t[2] .. " (" .. fileSize(xex) .. " bytes, SHA-1 " .. string.sub(fileSha1(xex), 1, 8) .. ")" end t[#t + 1] = "Backup " .. BAK .. ": " .. ((f.bak == "original" and "good") or (f.bak == "missing" and "none") or "damaged or unexpected") if f.new ~= "missing" then t[#t + 1] = "Waiting " .. NEW .. ": " .. ((f.new == "unknown" and "damaged") or stateText(f.new)) end for _, o in ipairs(f.olds) do t[#t + 1] = "Previous file " .. o.name .. ": " .. stateText(o.state) end for _, n in ipairs(f.bads) do t[#t + 1] = "Kept " .. n .. " (the kit never deletes it)" end for _, n in ipairs(f.others) do t[#t + 1] = "Other copy: " .. n .. " (yours; only read)" end t[#t + 1] = "Kit in this instance: " .. ((inst.running or inst.hasKit) and "yes" or "no") if inst.repairable and (inst.state == "unknown" or inst.state == "missing") then t[#t + 1] = "Install or Undo can repair this instance." end if inst.running and K.unsure then t[#t + 1] = "This is the Aurora you started (Game:)." end return table.concat(t, "\n") end -- Check status: reads files only, never the network (m6). It writes only in its own folder (log, marker). local function doCheck() local list = scan() local now, runner = runningNow(), list[#list] local items, map = { "Running now: " .. (STATE_TEXT[now] or "unknown") .. " (from this boot's Aurora log)" }, {} if (now == "original" or now == "speed") and (runner.state == "original" or runner.state == "speed") and now ~= runner.state then items[#items + 1] = "Restart Aurora to start the " .. stateText(runner.state) .. " now on disk" -- F3: list lines show ~70 characters end for _, inst in ipairs(list) do local f, notes = inst.files, "" if f.bak == "original" then notes = notes .. ", backup ok" end if f.new ~= "missing" then notes = notes .. ", new file waiting" end if #f.olds > 0 then notes = notes .. ", previous file kept" end if #f.bads > 0 then notes = notes .. ", .bad kept" end if inst.repairable and (inst.state == "unknown" or inst.state == "missing") then notes = notes .. ", can be repaired" end items[#items + 1] = inst.label .. ": " .. stateText(inst.state) .. notes .. (inst.running and ", running now" or "") .. " - " .. inst.root map[#items] = inst end local seen = {} for _, inst in ipairs(list) do if type(inst.drive) == "string" and inst.drive ~= "" and not seen[inst.drive] then seen[inst.drive] = true local v = freeBytes(inst.drive) items[#items + 1] = "Free space on " .. driveText(inst) .. " " .. (v and mb(v) or "unknown") end end -- Lab fix F3: popup list lines are cut after about 70 characters ("...is dama..."), so this is two short lines. local patchNote = "none" if isThere(K.base .. PATCH_FILE) then patchNote = matches(K.base .. PATCH_FILE, PATCH) and "good" or "damaged" end items[#items + 1] = "Kit v" .. KIT_VERSION .. ", Main.lua SHA-1 " .. (K.mainSha ~= "" and string.sub(K.mainSha, 1, 8) or "unknown") items[#items + 1] = "Patch file in the kit folder: " .. patchNote items[#items + 1] = "Help: if an instance will not start" local help = #items status("Status ready", 1, 1) while true do local k = pick(KIT_TITLE .. " - status", items) if not k then break end if map[k] then say(map[k].label, details(map[k])) elseif k == help then say("If an instance will not start", HELP) end end end -- Install (want = speed build) or Undo (want = original). Returns "restart" after the running instance changed. local function doChange(install) local want, other, title, verb = SPEED, STOCK, "Install speed fix", "Install" if not install then want, other, title, verb = STOCK, SPEED, "Undo (restore original)", "Undo" end local list = scan() local targets = pickTargets(list, verb) if not targets then return end -- 1. Repair is asked first, so the plan shows what will really happen (R7). local work, repairs = {}, {} for _, inst in ipairs(targets) do if inst.state == other.key then work[#work + 1] = inst elseif inst.state ~= want.key and inst.repairable then repairs[#repairs + 1] = inst end end if #repairs > 0 then local names = {} for _, inst in ipairs(repairs) do names[#names + 1] = name(inst) .. ": Aurora.xex is " .. stateText(inst.state) end -- R6: the safe answer ("No") is the first, focused button; only an explicit "Repair" repairs. if choose("Repair?", table.concat(names, "\n") .. "\n\nA checked copy made by the kit is next to it, so the kit can repair this. An unknown Aurora.xex is not deleted: it is renamed to Aurora.xex.bad.\n\nPick No if you changed Aurora.xex yourself (for example to a newer Aurora).", "No", "Repair") == 2 then for _, inst in ipairs(repairs) do inst.repair = true work[#work + 1] = inst end end end local plan = {} for _, inst in ipairs(targets) do local s, line = inst.state, nil if s == want.key then line = "already has the " .. want.text elseif s == other.key then line = stateText(s) .. " -> " .. want.text .. " (backup kept)" elseif inst.repair then line = stateText(s) .. " -> " .. want.text .. ((s == "unknown") and " (old file kept as .bad)" or "") else line = "left alone (Aurora.xex is " .. stateText(s) .. ")" end plan[#plan + 1] = name(inst) .. ": " .. line end if #work == 0 then return say(title, table.concat(plan, "\n") .. "\n\nNothing to do. Nothing was changed.") end -- The running instance always last. R8: when it was not matched to a folder, a folder holding the same -- Aurora.xex as Game: may be that same instance, so such folders come just before it. local ordered, late = {}, {} local gameSha = K.unsure and list[#list].sha or "" for _, inst in ipairs(work) do if not inst.running then if gameSha ~= "" and inst.sha == gameSha then late[#late + 1] = inst else ordered[#ordered + 1] = inst end end end for _, inst in ipairs(late) do ordered[#ordered + 1] = inst end for _, inst in ipairs(work) do if inst.running then ordered[#ordered + 1] = inst end end -- 2. Is a fresh source needed, and can the kit get one? local needSource, needDownload = false, false for _, inst in ipairs(ordered) do local f = inst.files needSource = needSource or (f.new ~= want.key and not (want == STOCK and f.bak == "original")) end if needSource and not findCopy(want, list) then if not findCopy(other, list) then return say(title, "No checked copy of the " .. other.text .. " or of the " .. want.text .. " was found on this console, so the kit cannot do this.\n\nNothing was changed.") end if not matches(K.base .. PATCH_FILE, PATCH) then if not baseUrl() then return say(title, "This copy of the kit has no TheVault address, so it cannot download the patch.\n\nUse the USB package: put " .. PATCH_FILE .. " into " .. K.base .. " and try again.\n\nNothing was changed.") end needDownload = true end end -- 3. Confirm. Lab fix F2: the warning comes first; R7: a long plan is shown in parts before the question. local notes = {} if install then notes[#notes + 1] = "It also puts a copy of the kit (and the 9 MB patch, if it has it) in your other instances." end if needDownload then notes[#notes + 1] = "First it downloads the 9 MB patch from TheVault and checks it; nothing changes before that. The bar does not move during the download. Only if nothing happens for 10 minutes may you turn the console off." end local body = table.concat(plan, "\n") if #notes > 0 then body = body .. "\n\n" .. table.concat(notes, " ") end if not confirm(title, "Do not turn the console off until the kit says it has finished.", body, "Go ahead?", "Yes, go ahead", "No") then return end -- 4. Free space (advice only: every write is checked anyway) local low, seenDrive, onDrives = {}, {}, { list[#list] } -- tmp\ lives on the running instance's drive for _, inst in ipairs(ordered) do onDrives[#onDrives + 1] = inst end for _, inst in ipairs(onDrives) do local n = inst.drive if type(n) == "string" and n ~= "" and not seenDrive[n] then seenDrive[n] = true local v = freeBytes(n) if v and v < LOW_SPACE then low[#low + 1] = driveText(inst) .. " " .. mb(v) .. " free" end end end if #low > 0 and not ask(title, "Free space looks low:\n" .. table.concat(low, "\n") .. "\n\nThe kit needs about 150 MB while it works. This is only a warning. Continue?", "Continue", "Stop") then return end -- 5. All network work and building first (m5), inside tmp\ only local src if needSource then pcall(Script.SetCancelEnable, true) local msg src, msg = prepareSource(want, other, list) pcall(Script.SetCancelEnable, false) if not src then return say(title, msg) end end if install then copyKit(list) end -- 6. Change the instances one at a time, the running one last local gameBefore = classify("Game:\\" .. XEX) local lines, restart, pending, aliasDone = {}, false, false, false for i, inst in ipairs(ordered) do local code, msg = "done", "done (same folder as above)" if not (inst.running and aliasDone) then status(verb .. ": " .. name(inst) .. " - do not turn the console off", i - 1, #ordered) code, msg = replaceOne(inst, want, src) restart = restart or (inst.running and code == "done") pending = pending or (inst.running and code == "pending") -- Running instance not matched to a folder: if changing this folder changed Game: too, it was the same one. if code == "done" and not inst.running and K.unsure and gameBefore ~= want.key and classify("Game:\\" .. XEX) == want.key then restart, aliasDone = true, true msg = msg .. " (this is the instance you are running)" end end log(verb .. " " .. inst.root .. " -> " .. code .. ": " .. msg) lines[#lines + 1] = name(inst) .. ": " .. msg end status(verb .. " finished", #ordered, #ordered) removeDir(K.base .. TMP) local summary = table.concat(lines, "\n") if pending then -- R3: only point to another instance when one exists that holds the kit local others = 0 for _, inst in ipairs(list) do local s = classify(inst.op .. "\\" .. XEX) if not inst.running and (s == "original" or s == "speed") and isThere(inst.op .. "\\" .. KIT_REL .. "\\" .. MAIN_FILE) then others = others + 1 end end if others > 0 then summary = summary .. "\n\nThe instance you are running could not be switched while it runs. Nothing is broken: it keeps its Aurora.xex, and the checked new file waits next to it. To finish, start one of your other Aurora instances from your boot menu and run this kit's " .. verb .. " there. No download is needed." else summary = summary .. "\n\nYour Aurora still works and nothing is broken. This console has no other Aurora copy with the kit, so the switch cannot finish. You can delete " .. NEW .. " with File Manager." end end if restart then say(title, summary .. "\n\nThe instance you are running has been changed, so Aurora restarts now.") return "restart" end say(title, summary) end -- Removes the kit's own files from another instance's SpeedKit folder, then the folder if it is empty. local function removeKitDir(dir) local main = dir .. "\\" .. MAIN_FILE if isThere(main) then local ok, txt = pcall(FileSystem.ReadFile, main) if kitId(main) == K.mainId or (ok and type(txt) == "string" and string.find(txt, "Aurora Speed Kit", 1, true)) then remove(main) end end for _, n in ipairs({ PATCH_FILE, LOG_FILE, MARKER_FILE, "README.txt" }) do remove(dir .. "\\" .. n) end removeDir(dir .. "\\" .. TMP) local ok, rest = pcall(FileSystem.GetFilesAndDirectories, dir .. "\\*") if ok and type(rest) == "table" and #rest == 0 then pcall(FileSystem.DeleteDirectory, dir) end return not isThere(dir) end -- m15: removes what the kit added, except what is still needed. R2: only files with the kit's own names; -- Aurora.xex, .bad files and your own Aurora.xex.* copies are never deleted. local function doRemove() local list = scan() local jobs, gone, kept, backups = {}, {}, {}, {} local function drop(path, isDir, what) jobs[#jobs + 1] = { path = path, isDir = isDir } gone[#gone + 1] = what end for _, inst in ipairs(list) do local dir, f, nm, s = inst.op, inst.files, name(inst), inst.state local healthy = s == "original" or s == "speed" local kd = dir .. "\\" .. KIT_REL if not inst.running and isThere(kd) then if K.unsure and sameAsMine(kd) then kept[#kept + 1] = nm .. ": the kit that is running" -- R8 else drop(kd, true, nm .. ": kit copy") end end local mine = {} if f.bak ~= "missing" then mine[#mine + 1] = { name = BAK, state = f.bak } end if f.new ~= "missing" then mine[#mine + 1] = { name = NEW, state = f.new } end for _, o in ipairs(f.olds) do mine[#mine + 1] = o end for _, c in ipairs(mine) do if not healthy then kept[#kept + 1] = nm .. ": " .. c.name .. " (needed for a repair)" elseif c.state == "original" and s == "speed" then backups[#backups + 1] = { inst = inst, name = c.name } -- the original next to a speed build: asked else drop(dir .. "\\" .. c.name, false, nm .. ": " .. c.name) end end end gone[#gone + 1] = "this kit's temporary files" local body = "The kit will remove:\n" .. table.concat(gone, "\n") if #kept > 0 then body = body .. "\n\nIt keeps:\n" .. table.concat(kept, "\n") end if #backups > 0 then body = body .. "\n\nCopies of the original next to a speed build: you are asked next." end if not confirm("Remove kit files", "", body, "Aurora.xex, .bad files and your own Aurora.xex.* copies are never deleted. Continue?", "Remove", "Cancel") then return end if #backups > 0 then local names = {} for _, b in ipairs(backups) do names[#names + 1] = name(b.inst) .. ": " .. b.name end -- Lab fix F6: the focused first button must be the safe answer. In the lab "Delete them" was focused, so one -- quick A press deleted every backup (and B did not close the box). Now only an explicit "Delete them" deletes. if choose("Remove backups too?", "These are copies of the original next to a speed build:\n" .. table.concat(names, "\n") .. "\n\nDelete them too? Without them, Undo needs another original copy on the console, or the patch (download or USB).", "Keep them", "Delete them") == 2 then for _, b in ipairs(backups) do jobs[#jobs + 1] = { path = b.inst.op .. "\\" .. b.name, isDir = false } end end end local count, failed = 0, {} for i, j in ipairs(jobs) do status("Removing kit files...", i - 1, #jobs) local ok = (j.isDir and removeKitDir(j.path)) or (not j.isDir and remove(j.path)) log("remove " .. j.path .. ": " .. (ok and "ok" or "failed")) if ok then count = count + 1 else failed[#failed + 1] = j.path end end removeDir(K.base .. TMP) status("Finished", 1, 1) local msg = "Removed " .. count .. " item(s)." if #failed > 0 then msg = msg .. "\nNot removed (other files inside, or in use):\n" .. table.concat(failed, "\n") end msg = msg .. "\n\nThe kit itself is still in this instance:\n" .. K.base .. "\nTo remove it too, delete that folder with Aurora's File Manager." local url = baseUrl() local host = url and string.match(url, "^https?://([^/:]+)") if host then msg = msg .. "\nIf you added TheVault to Homebrew Store, also delete Game:\\User\\Scripts\\Utility\\HomebrewStore\\Repos\\" .. host .. ".ini" end say("Remove kit files", msg) end -- ----- Entry point ------------------------------------------------------------------------------------------ function main() local okb, b = pcall(Script.GetBasePath) if not okb or type(b) ~= "string" or b == "" then return say(KIT_TITLE, "The kit could not find its own folder, so it stopped. Nothing was changed.") end if string.sub(b, -1) ~= "\\" then b = b .. "\\" end K.base = b if fileSize(b .. LOG_FILE) > 262144 then remove(b .. LOG_FILE) end log("start: kit v" .. KIT_VERSION .. " in " .. b) local okv, v = pcall(Aurora.GetDashVersion) if not okv or type(v) ~= "table" or v.Major ~= 0 or v.Minor ~= 7 or v.Type ~= 2 or v.Revision ~= 1655 then local got = "unknown" if okv and type(v) == "table" then got = tostring(v.Major) .. "." .. tostring(v.Minor) .. " type " .. tostring(v.Type) .. " revision " .. tostring(v.Revision) end log("wrong Aurora version: " .. got) return say(KIT_TITLE, "This kit only works with Aurora 0.7b.2 (revision 1655).\nThis Aurora reports version " .. got .. ".\n\nNothing was changed.") end K.mainSha = fileSha1(b .. MAIN_FILE) K.mainId = kitId(b .. MAIN_FILE) removeDir(b .. TMP) -- leftovers of an interrupted run local menu = { "Check status", "Install speed fix", "Undo (restore original Aurora)", "Remove kit files", "Exit" } local actions = { doCheck, function() return doChange(true) end, function() return doChange(false) end, doRemove } while true do local k = pick(KIT_TITLE .. " v" .. KIT_VERSION, menu) if not k or not actions[k] then break end local ok, res = pcall(actions[k]) removeDir(b .. TMP) if not ok then log("ERROR: " .. tostring(res)) say(KIT_TITLE, "Something unexpected went wrong, so the kit stopped:\n" .. tostring(res) .. "\n\nRun Check status to see where things are.") elseif res == "restart" then log("restarting Aurora") status("Restarting Aurora...", 1, 1) pcall(Aurora.Restart) -- after the running instance's file was switched: restart without asking (m13) return end end log("end") end