From a26f7a758aaf5c3f9719c6dcfc4a52f042c7a864 Mon Sep 17 00:00:00 2001 From: EduDicaseGameplay Date: Thu, 30 Jul 2026 19:34:50 -0300 Subject: [PATCH 1/4] Improve Homebrew Store --- UtilityScripts/HomebrewStore/Main.lua | 546 +++++++++++++------- UtilityScripts/HomebrewStore/MenuSystem.lua | 116 +++-- 2 files changed, 436 insertions(+), 226 deletions(-) diff --git a/UtilityScripts/HomebrewStore/Main.lua b/UtilityScripts/HomebrewStore/Main.lua index e4e21ea..fb737c1 100644 --- a/UtilityScripts/HomebrewStore/Main.lua +++ b/UtilityScripts/HomebrewStore/Main.lua @@ -1,81 +1,179 @@ scriptTitle = "Homebrew Store" -scriptAuthor = "Derf / Cheato" -scriptVersion = 5 -scriptDescription = "Download homebrew from ConsoleMods.org and other repos!" +scriptAuthor = "Derf / Cheato / Eduardo Henrique" +scriptVersion = 6 +scriptDescription = "Download homebrew from ConsoleMods.org and other repos! (Optimized Version)" scriptIcon = "icon.png" scriptPermissions = { "http", "sql", "filesystem" } --Built from AuroraRepo. Please be gentle :) require("MenuSystem"); local reloadRequired = false; -downloadsPath = "Downloads\\"; -gAbortedOperation = false; +local downloadsPath = "Downloads\\"; +local gAbortedOperation = false; +local absoluteDownloadsPath = ""; +local scanPathCache = nil; + +-- Unified temporary cleanup function (DRY / Reduced redundancy) +local function Cleanup() + if absoluteDownloadsPath ~= nil and absoluteDownloadsPath ~= "" then + FileSystem.DeleteDirectory(absoluteDownloadsPath); + end +end + +-- Local ScanPaths cache to avoid repeatedly executing heavy SQL queries +local function LoadScanPaths() + if scanPathCache ~= nil then + return scanPathCache + end + + scanPathCache = {} + + -- MountPoint cache by DeviceId for ultra-fast O(1) lookups + local mountPoints = {} + local devices = Sql.ExecuteFetchRows("SELECT DeviceId, MountPoint FROM MountedDevices") + if devices ~= nil then + for _, d in pairs(devices) do + if d.DeviceId ~= nil and d.MountPoint ~= nil then + mountPoints[d.DeviceId] = d.MountPoint + end + end + end + + -- Retrieve and map all ScanPaths in a single pass + local paths = Sql.ExecuteFetchRows("SELECT Path, DeviceId, ScriptData FROM ScanPaths") + if paths ~= nil then + for _, p in pairs(paths) do + local mountpoint = mountPoints[p.DeviceId] + if mountpoint ~= nil then + local fullPath = mountpoint .. p.Path .. "\\" + if p.ScriptData == "Applications" then + scanPathCache["App"] = fullPath + elseif p.ScriptData == "Homebrew" then + scanPathCache["Homebrew"] = fullPath + elseif p.ScriptData == "Emulators" then + scanPathCache["Emulator"] = fullPath + elseif p.ScriptData == "Games" then + scanPathCache["Game"] = fullPath + elseif p.ScriptData == "Live" then + scanPathCache["PublicProfile"] = fullPath + end + end + end + end + + return scanPathCache +end + +function GetScanPath(type) + local cache = LoadScanPaths() + return cache[type] +end -- Main entry point to script function main() if Aurora.HasInternetConnection() ~= true then - Script.ShowMessageBox("ERROR", "This script requires an active internet connection to work...\n\nPlease make sure you have internet to your console before running the script", "OK"); + Script.ShowMessageBox("ERROR", "This script requires an active internet connection to work...\n\nPlease make sure your console is connected to the internet before running the script.", "OK"); return; end print("-- " .. scriptTitle .. " started..."); - + if init() == false then goto scriptend; end - MakeMainMenu(); - DoShowMenu(); + local g_RestartMenu = true + while g_RestartMenu do + g_RestartMenu = false + Menu.ResetMenu(); + MakeMainMenu(); + DoShowMenu(); + end if reloadRequired and not gAbortedOperation then - local ret = Script.ShowMessageBox("Aurora Reload Required", "A Reload is required for your changes to take effect\n\nDo you want to reload Aurora now?", "Yes", "No"); + local ret = Script.ShowMessageBox( + "Aurora Reload Required", + "A reload is required for your changes to take effect...\n\nDo you want to reload Aurora now?", + "Yes", + "No" + ); + if ret.Button == 1 then Aurora.Restart(); end end - + ::mainend:: - FileSystem.DeleteDirectory(absoluteDownloadsPath); + Cleanup(); print("-- " .. scriptTitle .. " ended..."); ::scriptend:: end -function init() - -- Clear out unfinished downloads - absoluteDownloadsPath = Script.GetBasePath() .. downloadsPath; - FileSystem.DeleteDirectory(absoluteDownloadsPath); +-- Dynamic device prompt using the native Aurora FileSystem API +function PromptContentDrive() + local drives = FileSystem.GetDrives(true); -- Content-capable drives only + local names = {}; - -- Load last selected storage device - confPath = Script.GetBasePath() .. "homebrewstore.conf" - storageDeviceFromConf = FileSystem.ReadFile(confPath); - if (storageDeviceFromConf == nil) then - FileSystem.WriteFile(confPath, "hdd1:\\"); - storageDeviceFromConf = FileSystem.ReadFile(confPath); + for i, d in ipairs(drives) do + local label = d.MountPoint; + if d.Name ~= nil and d.Name ~= "" then + label = label .. " (" .. d.Name .. ")"; + end + names[i] = label; + end + + local pick = Script.ShowPopupList( + "Select the drive to install to", + "No content drives found", + names + ); + + if pick.Canceled then + return nil; end - storageDevice = storageDeviceFromConf - -- Update saved repos - Script.SetStatus("Updating repos..."); + local d = drives[pick.Selected.Key] + return d; +end + +function init() + -- Remove unfinished downloads + absoluteDownloadsPath = Script.GetBasePath() .. downloadsPath; + Cleanup(); + FileSystem.CreateDirectory(absoluteDownloadsPath); + + -- Update saved repositories + Script.SetStatus("Updating repositories..."); Script.SetProgress(5); local updatingIndex = 0; - local repos = FileSystem.GetFiles( Script.GetBasePath() .. "Repos\\*" ); - for i, repo in pairs(repos) do - local repoDisplayName = repo.Name:gsub("%.ini$", ""); - Script.SetStatus("Updating " .. repoDisplayName .. "..."); - updatingIndex = updatingIndex + 1; - if updatingIndex < 6 then - Script.SetProgress(5+(15*updatingIndex)); - end - - local remoteRepoIniToUpdate = IniFile.LoadFile( "Repos\\" .. repo.Name); - local remoteRepoIniSection = remoteRepoIniToUpdate:GetSection("update"); - - if remoteRepoIniSection ~= nil then - local repourl = remoteRepoIniSection.repourl; - if repourl ~= nil then - http = Http.Get(repourl, "\\Repos\\" .. repo.Name ); - if not http.Success then - Script.ShowMessageBox("ERROR","Could not connect to " .. repoDisplayName,"OK"); + local reposPath = Script.GetBasePath() .. "Repos" + if not FileSystem.FileExists(reposPath) then + FileSystem.CreateDirectory(reposPath) + end + + local repos = FileSystem.GetFiles(reposPath .. "\\*"); + if repos ~= nil then + for i, repo in pairs(repos) do + local repoDisplayName = repo.Name:gsub("%.ini$", ""); + Script.SetStatus("Updating " .. repoDisplayName .. "..."); + updatingIndex = updatingIndex + 1; + + if updatingIndex < 6 then + Script.SetProgress(5 + (15 * updatingIndex)); + end + + local remoteRepoIniToUpdate = IniFile.LoadFile("Repos\\" .. repo.Name); + if remoteRepoIniToUpdate ~= nil then + local remoteRepoIniSection = remoteRepoIniToUpdate:GetSection("update"); + + if remoteRepoIniSection ~= nil then + local repourl = remoteRepoIniSection.repourl; + if repourl ~= nil then + local http = Http.Get(repourl, "\\Repos\\" .. repo.Name); + if not http.Success then + Script.ShowMessageBox("ERROR", "Could not connect to " .. repoDisplayName, "OK"); + end + end end end end @@ -83,29 +181,41 @@ function init() end function MakeMainMenu() - Menu.SetTitle(scriptTitle .. " (" .. storageDevice:gsub( "\\", "") .. ")"); + Menu.SetTitle(scriptTitle); Menu.SetGoBackText(""); - local repos = FileSystem.GetFiles( Script.GetBasePath() .. "Repos\\*" ); - for i, repo in pairs(repos) do - remoteRepoIni = IniFile.LoadFile( "Repos\\" .. repo.Name); - remoteRepoIniSections = remoteRepoIni:GetAllSections(); - - if remoteRepoIniSections ~= nil then - for _, v in pairs(remoteRepoIniSections) do - local title = remoteRepoIni:ReadValue(v, "name", ""); - if title ~= "" then - Menu.AddMainMenuItem(Menu.MakeMenuItem(title, remoteRepoIni:GetSection(v))); + + local reposPath = Script.GetBasePath() .. "Repos" + if not FileSystem.FileExists(reposPath) then + FileSystem.CreateDirectory(reposPath) + end + + local repos = FileSystem.GetFiles(reposPath .. "\\*"); + if repos ~= nil then + for i, repo in pairs(repos) do + local remoteRepoIni = IniFile.LoadFile("Repos\\" .. repo.Name); + if remoteRepoIni ~= nil then + local remoteRepoIniSections = remoteRepoIni:GetAllSections(); + + if remoteRepoIniSections ~= nil then + for _, v in pairs(remoteRepoIniSections) do + local title = remoteRepoIni:ReadValue(v, "name", ""); + if title ~= "" then + Menu.AddMainMenuItem(Menu.MakeMenuItem(title, remoteRepoIni:GetSection(v))); + end + end end end end end - Menu.AddMainMenuItem(Menu.MakeMenuItem("Change Storage Device", { ["name"] = 'test2',["iniurl"] = 'CHANGE_STORAGE',} )); - Menu.AddMainMenuItem(Menu.MakeMenuItem("", { ["name"] = 'test',["iniurl"] = 'ENTER_URL',} )); + Menu.AddMainMenuItem(Menu.MakeMenuItem("", { + ["name"] = "test", + ["iniurl"] = "ENTER_URL", + })); end function DoShowMenu(menu) - if gAbortedOperation then + if gAbortedOperation or g_RestartMenu then return; end @@ -125,44 +235,45 @@ function DoShowMenu(menu) local http, iniurl; Script.SetProgress(0); - if (ret.iniurl == "CHANGE_STORAGE") then - -- Open menu to select storage device - Menu.AddSubMenuItem(menuItem, Menu.MakeMenuItem("hdd1:", "hdd1:\\")); - Menu.AddSubMenuItem(menuItem, Menu.MakeMenuItem("usb0:", "usb0:\\")); - Menu.AddSubMenuItem(menuItem, Menu.MakeMenuItem("usb1:", "usb1:\\")); - Menu.AddSubMenuItem(menuItem, Menu.MakeMenuItem("memunit0:", "memunit0:\\")); - Menu.AddSubMenuItem(menuItem, Menu.MakeMenuItem("memunit1:", "memunit1:\\")); - elseif (ret.iniurl == "ENTER_URL") then - -- Prompt user for URL to .ini file - local keyboardData = Script.ShowKeyboard( "Aurora Keyboard", "Enter the full URL to a valid .ini file", "https://", 0 ); - if keyboardData.Canceled == false then + if (ret.iniurl == "ENTER_URL") then + -- Prompt the user for the .ini file URL + local keyboardData = Script.ShowKeyboard( + "Aurora Keyboard", + "Enter the full URL to a valid .ini file", + "https://", + 0 + ); + + if keyboardData.Canceled == false then iniurl = keyboardData.Buffer; else return; end - iniRepoPath = Script.GetBasePath() .. "Repos\\"; - FileSystem.CreateDirectory( iniRepoPath ); - local newRepoName = string.match(iniurl,"^https?://([^/]+)"); - http = Http.Get(iniurl, "\\Repos\\" .. newRepoName .. ".ini" ); + local iniRepoPath = Script.GetBasePath() .. "Repos\\"; + FileSystem.CreateDirectory(iniRepoPath); + local newRepoName = string.match(iniurl, "^https?://([^/]+)"); + http = Http.Get(iniurl, "\\Repos\\" .. newRepoName .. ".ini"); + if http.Success then Script.ShowNotification(newRepoName .. " repo installed!"); else - Script.ShowMessageBox("ERROR", "Failed to download .ini file:\n\n" .. iniurl, "OK"); + Script.ShowMessageBox("ERROR", "Failed to download the .ini file:\n\n" .. iniurl, "OK"); end return else - -- Load .ini from Repo .ini entry + -- Load the .ini file from the repository entry http = Http.Get(ret.iniurl); if http.Success then Script.SetStatus("Processing listings..."); Script.SetProgress(50); + local ini = IniFile.LoadString(http.OutputData); - + for _, v in pairs(ini:GetAllSections()) do - local title = ini:ReadValue(v, "itemTitle", ""); - local ver = ini:ReadValue(v, "itemVersion", ""); + local title = ini:ReadValue(v, "itemTitle", ""); + local ver = ini:ReadValue(v, "itemVersion", ""); local author = ini:ReadValue(v, "itemAuthor", ""); if (title ~= "" and ver ~= "" and author ~= "") then @@ -172,21 +283,39 @@ function DoShowMenu(menu) end end else - Script.ShowMessageBox("ERROR", "An error occurred while downloading store data...\n\nPlease try again later", "OK"); + Script.ShowMessageBox( + "ERROR", + "An error occurred while downloading the store data...\n\nPlease try again later.", + "OK" + ); DoShowMenu(menu); return; end end end + if g_RestartMenu then + return; + end + if menuItem.SubMenu ~= nil then -- Open submenu DoShowMenu(menuItem.SubMenu); + + if g_RestartMenu then + return; + end + elseif not Menu.IsMainMenu(menu) then -- Content item selected HandleSelection(ret, menu.Parent.Data, menu); + else - Script.ShowMessageBox("ERROR", "An unknown error occurred!\n\nExiting...", "OK"); + Script.ShowMessageBox( + "ERROR", + "An unknown error occurred!\n\nExiting...", + "OK" + ); end end end @@ -194,20 +323,14 @@ end function HandleSelection(selection, repo, menu) local info = ""; - if (selection.itemTitle == nil) then - -- Change active storage device, save out to config file - FileSystem.WriteFile(confPath, selection); - storageDevice = FileSystem.ReadFile(confPath); - Script.ShowNotification("Storage device set to " .. storageDevice:gsub( "\\", "")); - return - end - - if not FileSystem.FileExists(storageDevice) then - Script.ShowMessageBox("ERROR","The selected storage device " .. storageDevice:gsub( "\\", "") .. " is not connected.","OK"); + -- Prompt the user to select a drive before showing the download confirmation + local drive = PromptContentDrive(); + if drive == nil then return nil; end - + info = info .. "Name: " .. selection.itemTitle .. "\n"; + if selection.itemVersion ~= nil and selection.itemVersion ~= "" then info = info .. "Version: " .. selection.itemVersion .. "\n"; end @@ -220,7 +343,9 @@ function HandleSelection(selection, repo, menu) info = info .. "Size: " .. selection.itemSize .. "\n"; end - local destinationPath = GetDestinationPath(selection.path, repo.type); + -- Compute the correct destination path using the selected drive mount point + local destinationPath = GetDestinationPath(selection.path, repo.type, drive.MountPoint); + if destinationPath ~= nil and destinationPath ~= "" then info = info .. "Path: " .. destinationPath .. "\n"; else @@ -231,8 +356,10 @@ function HandleSelection(selection, repo, menu) info = info .. "Description:\n" .. string.gsub(selection.itemDescription, "\\n", "\n") .. "\n\n"; end - info = info .. "\n\n\nDo you want to install this ".. repo.type .."?"; + info = info .. "\n\n\nDo you want to install this " .. repo.type .. " on " .. drive.MountPoint:gsub("\\", "") .. "?"; + local ret = Script.ShowMessageBox("", info, "Yes", "No"); + if ret.Button == 1 then if HandleInstallation(selection, destinationPath, repo.type) then if repo.reload == "true" then @@ -240,48 +367,41 @@ function HandleSelection(selection, repo, menu) end end end + DoShowMenu(menu); end -function GetDirectoryForScanPath(pathid, deviceid) - local mountpoint = nil; - if deviceid ~= nil then - for _, v in pairs(Sql.ExecuteFetchRows("SELECT MountPoint FROM MountedDevices WHERE DeviceId == \"" .. deviceid .. "\"")) do - mountpoint = v.MountPoint; - break; - end +function ReplaceMount(originalPath, mountPoint) + if mountPoint ~= nil and string.sub(mountPoint, -1) ~= "\\" then + mountPoint = mountPoint .. "\\" end - return mountpoint; -end -function GetScanPath(type) - for _, v in pairs(Sql.ExecuteFetchRows("SELECT Id,Path,DeviceId,ScriptData FROM ScanPaths")) do - local mountpoint = GetDirectoryForScanPath(v.Id, v.DeviceId); - if mountpoint ~= nil then - if type == "App" and v.ScriptData == "Applications" then - return mountpoint .. v.Path .. "\\"; - elseif type == "Homebrew" and v.ScriptData == "Homebrew" then - return mountpoint .. v.Path .. "\\"; - elseif type == "Emulator" and v.ScriptData == "Emulators" then - return mountpoint .. v.Path .. "\\"; - elseif type == "Game" and v.ScriptData == "Games" then - return mountpoint .. v.Path .. "\\"; - elseif type == "PublicProfile" and v.ScriptData == "Live" then - return mountpoint .. v.Path .. "\\"; - end - end + local pathOnly = originalPath:gsub("^[^:]+:[\\/]?", "") + + if string.sub(pathOnly, 1, 1) ~= "\\" then + pathOnly = "\\" .. pathOnly end + + if string.sub(pathOnly, -1) ~= "\\" then + pathOnly = pathOnly .. "\\" + end + + return mountPoint .. pathOnly end -function GetDestinationPath(path, type) - -- If ScanPaths not set in Aurora settings, then: +function GetDestinationPath(path, type, mountPoint) + -- If ScanPaths are not configured in Aurora settings: -- App - Installs to /Apps/ -- Game - Installs to /Games/ -- Emulator - Installs to /Emulators/ -- PublicProfile - Installs to /Content/0000000000000000/ - -- Profile - Installs to /Content// of signed-in user - -- Other - Full path specified in .ini - + -- Profile - Installs to /Content// of the signed-in user + -- Other - Uses the full path specified in the .ini file + + if mountPoint ~= nil and string.sub(mountPoint, -1) ~= "\\" then + mountPoint = mountPoint .. "\\" + end + local applicationsDirectory = GetScanPath("App"); local gamesDirectory = GetScanPath("Game"); local homebrewDirectory = GetScanPath("Homebrew"); @@ -289,48 +409,59 @@ function GetDestinationPath(path, type) if type == "App" then if applicationsDirectory ~= nil then - return applicationsDirectory .. path; + return ReplaceMount(applicationsDirectory, mountPoint) .. path; else - return storageDevice .. "Apps\\" .. path; + return mountPoint .. "Apps\\" .. path; end + elseif type == "Game" then if gamesDirectory ~= nil then - return gamesDirectory .. path; + return ReplaceMount(gamesDirectory, mountPoint) .. path; else - return storageDevice .. "Games\\" .. path; + return mountPoint .. "Games\\" .. path; end + elseif type == "Emulator" then if emulatorsDirectory ~= nil then - return emulatorsDirectory .. path; + return ReplaceMount(emulatorsDirectory, mountPoint) .. path; else - return storageDevice .. "Emulators\\" .. path; + return mountPoint .. "Emulators\\" .. path; end + elseif type == "Homebrew" then if homebrewDirectory ~= nil then - return homebrewDirectory .. path; + return ReplaceMount(homebrewDirectory, mountPoint) .. path; else - return storageDevice .. "Homebrew\\" .. path; + return mountPoint .. "Homebrew\\" .. path; end + elseif type == "PublicProfile" then - return storageDevice .. "Content\\0000000000000000\\" .. path; + return mountPoint .. "Content\\0000000000000000\\" .. path; + elseif type == "Profile" then - profileID = Profile.GetXUID(1); + local profileID = Profile.GetXUID(1); + if profileID == "0" then - Script.ShowMessageBox("ERROR", "You need to sign into a profile to download from this category.", "OK"); + Script.ShowMessageBox( + "ERROR", + "You need to sign in to a profile to download content from this category.", + "OK" + ); else if string.len(profileID) == 16 then - return storageDevice .. "Content\\" .. Profile.GetXUID(1) .. "\\" .. path; + return mountPoint .. "Content\\" .. Profile.GetXUID(1) .. "\\" .. path; else - -- When signed into Xbox Live, profile XUID changes to a 13 character string + -- When signed in to Xbox Live, the profile XUID becomes a 13-character string local profiles = Profile.EnumerateProfiles(); + for i, profile in pairs(profiles) do if profile.GamerTag == Profile.GetGamerTag(1) then - return storageDevice .. "Content\\" .. profile.XUID .. "\\" .. path; + return mountPoint .. "Content\\" .. profile.XUID .. "\\" .. path; end end end end - else + else return path; end end @@ -338,7 +469,7 @@ end function HandleInstallation(selection, destinationPath, type) if string.match(selection.path, "Usb0:") then if not FileSystem.FileExists("Usb0:\\") then - Script.ShowMessageBox("ERROR","This download requires a USB flash drive. Please plug one in and retry.","OK"); + Script.ShowMessageBox("ERROR", "This download requires a USB flash drive. Please plug one in and try again.", "OK"); return nil; end end @@ -346,12 +477,11 @@ function HandleInstallation(selection, destinationPath, type) local filename = selection.path; if FileSystem.FileExists(destinationPath) then if not HandleAlreadyExists(type, filename) then - return false; -- We're not going to continue trying this + return false; -- Installation canceled by the user end end - + FileSystem.CreateDirectory(destinationPath); - --Script.SetStatus("Downloading content..."); Script.SetProgress(10); local destinationFullPath = ""; @@ -360,11 +490,10 @@ function HandleInstallation(selection, destinationPath, type) local loadingProgress = 0; local installSuccess = false; gAbortedOperation = false; - - -- 7z files must be <350MB, otherwise the Aurora zip extractor breaks - -- Other files can be seemingly unlimited size - -- Add dataurl to table first + -- 7z files must be smaller than 350 MB, otherwise Aurora's extractor fails + + -- Add the main dataurl first local dataurls = {} for key in pairs(selection) do if string.match(key, "^dataurl$") then @@ -373,13 +502,15 @@ function HandleInstallation(selection, destinationPath, type) end end - -- Add dataurlparts to table sequentially + -- Add multipart data URLs sequentially local total_parts = 1; local dataurl_index = 1; local match_found = true; + while match_found do dataurl_index = dataurl_index + 1; match_found = false; + for key in pairs(selection) do if string.match(key, "^dataurlpart" .. dataurl_index .. "$") then table.insert(dataurls, key); @@ -392,39 +523,45 @@ function HandleInstallation(selection, destinationPath, type) local dataurl; local current_part_index = 0; + for key, dataurl_name in ipairs(dataurls) do current_part_index = current_part_index + 1; dataurl = selection[dataurl_name]; - -- If a file part, get part path + + -- If this is a multipart file, determine its destination path if string.match(dataurl_name, "part") then local partname = string.sub(dataurl_name, 8, -1); destinationFullPath = ""; + for key2, partpath in pairs(selection) do if string.match(key2, partname .. "path") then - destinationFullPath = GetDestinationPath(partpath, type); + destinationFullPath = GetDestinationPath(partpath, type, string.match(destinationPath, "^[^:]+:[\\/]*")); end end if destinationFullPath == "" then - -- If no part path specified, default to path - destinationFullPath = string.match(destinationPath, "^.+[\\]"); + -- No specific path defined, use the default destination + destinationFullPath = string.match(destinationPath, "^.+[\\\\]"); end else - -- This is dataurl and path - destinationFullPath = string.match(destinationPath, "^.+[\\]"); + -- Main dataurl uses the default destination + destinationFullPath = string.match(destinationPath, "^.+[\\\\]"); end + FileSystem.CreateDirectory(destinationFullPath); if updatingIndex < 8 then - loadingProgress = 10+(10*updatingIndex); + loadingProgress = 10 + (10 * updatingIndex); Script.SetProgress(loadingProgress); end + updatingIndex = updatingIndex + 1; - - -- Download files + + -- Download content local dlpath = ""; local successfulMove = false; - local tmpRandomString = math.random(1,100000000); + local tmpRandomString = math.random(1, 100000000); + if string.match(dataurl, ".7z") then dlpath = downloadsPath .. "tmp-" .. tmpRandomString .. ".7z"; else @@ -433,62 +570,84 @@ function HandleInstallation(selection, destinationPath, type) Script.SetStatus("Downloading content (" .. current_part_index .. "/" .. total_parts .. ")..."); local http = Http.GetEx(dataurl, HttpProgressRoutine, dlpath); - if gAbortedOperation == true then + + if gAbortedOperation == true then installSuccess = false; - Script.ShowNotification("Download cancelled"); + Script.ShowNotification("Download canceled"); Script.SetStatus("Exiting script..."); - FileSystem.DeleteDirectory(absoluteDownloadsPath); + Cleanup(); else if http.Success then - Script.SetProgress(loadingProgress+5); + Script.SetProgress(loadingProgress + 5); if string.match(dataurl, ".7z") then - -- Unzip files - local zip = ZipFile.OpenFile( dlpath ); + -- Extract archive + local zip = ZipFile.OpenFile(dlpath); + if zip == nil then - Script.ShowMessageBox("ERROR", "Could not open zip!", "OK"); + Script.ShowMessageBox("ERROR", "Could not open archive!", "OK"); return false; end - Script.SetStatus("Decompressing content (" .. current_part_index .. "/" .. total_parts .. ")..."); - local result = zip.Extract( zip, downloadsPath .. "tmp\\" ); + + Script.SetStatus("Extracting content (" .. current_part_index .. "/" .. total_parts .. ")..."); + local result = zip.Extract(zip, downloadsPath .. "tmp\\"); if result == false then Script.ShowMessageBox("ERROR", "Extraction failed!", "OK"); - FileSystem.DeleteDirectory(absoluteDownloadsPath); + Cleanup(); return false; else - Script.SetProgress(loadingProgress+7); + Script.SetProgress(loadingProgress + 7); Script.SetStatus("Installing content (" .. current_part_index .. "/" .. total_parts .. ")..."); - successfulMove = FileSystem.MoveDirectory(absoluteDownloadsPath .. "tmp\\", string.match(destinationPath, "^.+[\\]"), true, CopyProgressRoutine); - Script.SetProgress(loadingProgress+9); + + local source = absoluteDownloadsPath .. "tmp\\"; + local dest = string.match(destinationPath, "^.+[\\]"); + + successfulMove = FileSystem.MoveDirectory( + source, + dest, + true, + CopyProgressRoutine + ); + + Script.SetProgress(loadingProgress + 9); end else - -- Copy single file to destination - Script.SetProgress(loadingProgress+7); + -- Copy a single file to the destination + Script.SetProgress(loadingProgress + 7); Script.SetStatus("Moving content (" .. current_part_index .. "/" .. total_parts .. ")..."); + partFileName = string.match(dataurl, "^.*/([^/]+)$"); - successfulMove = FileSystem.CopyFile(absoluteDownloadsPath .. "tmp-" .. tmpRandomString .. ".bin", destinationFullPath .. partFileName, true, CopyProgressRoutine); - Script.SetProgress(loadingProgress+9); + + successfulMove = FileSystem.CopyFile( + absoluteDownloadsPath .. "tmp-" .. tmpRandomString .. ".bin", + destinationFullPath .. partFileName, + true, + CopyProgressRoutine + ); + + Script.SetProgress(loadingProgress + 9); end if gAbortedOperation == true then - Script.ShowNotification("Operation aborted!"); + Script.ShowNotification("Operation canceled!"); Script.SetStatus("Exiting script..."); - FileSystem.DeleteDirectory(absoluteDownloadsPath); + Cleanup(); return false; else Script.SetStatus(""); + if successfulMove == true and gAbortedOperation == false then installSuccess = true; else Script.ShowMessageBox("ERROR", "Installation failed!", "OK"); - FileSystem.DeleteDirectory(absoluteDownloadsPath); + Cleanup(); return false; end end else installSuccess = false; - Script.ShowMessageBox("ERROR", "Download failed\n\nPlease try again later...", "OK"); + Script.ShowMessageBox("ERROR", "Download failed.\n\nPlease try again later...", "OK"); end end end @@ -497,39 +656,44 @@ function HandleInstallation(selection, destinationPath, type) Script.ShowNotification(selection.itemTitle .. " installed"); end - FileSystem.DeleteDirectory(absoluteDownloadsPath); + Cleanup(); return true; end function HandleAlreadyExists(type, name) - local msg = "There is a folder that already installed with the name:\n\n" .. name .. "\n\nDo you want to overwrite/replace it?"; + local msg = "An item with the following name already exists:\n\n" .. + name .. + "\n\nDo you want to overwrite/replace it?"; + local ret = Script.ShowMessageBox("Item Already Exists", msg, "No", "Yes"); + if ret.Canceled or ret.Button ~= 2 then return false; end + return true; end -function HttpProgressRoutine( dwTotalFileSize, dwTotalBytesTransferred, dwReason ) +function HttpProgressRoutine(dwTotalFileSize, dwTotalBytesTransferred, dwReason) if Script.IsCanceled() then gAbortedOperation = true; - Script.SetStatus("Cancelling after this download..."); + Script.SetStatus("Canceling after this download..."); Script.SetProgress(dwTotalBytesTransferred, dwTotalFileSize); - return Cancel; - end + return Cancel; + end - Script.SetProgress(dwTotalBytesTransferred, dwTotalFileSize); + Script.SetProgress(dwTotalBytesTransferred, dwTotalFileSize); return 0; end -function CopyProgressRoutine( dwTotalFileSize, dwTotalBytesTransferred ) - if Script.IsCanceled() then - gAbortedOperation = true; - Script.SetStatus("Cancelling after this operation..."); +function CopyProgressRoutine(dwTotalFileSize, dwTotalBytesTransferred) + if Script.IsCanceled() then + gAbortedOperation = true; + Script.SetStatus("Canceling after this operation..."); Script.SetProgress(dwTotalBytesTransferred, dwTotalFileSize); return Cancel; - end + end - Script.SetProgress(dwTotalBytesTransferred, dwTotalFileSize); + Script.SetProgress(dwTotalBytesTransferred, dwTotalFileSize); return 0; -end +end \ No newline at end of file diff --git a/UtilityScripts/HomebrewStore/MenuSystem.lua b/UtilityScripts/HomebrewStore/MenuSystem.lua index dea1874..9659e43 100644 --- a/UtilityScripts/HomebrewStore/MenuSystem.lua +++ b/UtilityScripts/HomebrewStore/MenuSystem.lua @@ -8,25 +8,51 @@ local SortAlphaBetically = false; _ShowMenu = function(menuItem) local menu = {} + local displayToOriginal = {} -- Maps the displayed index to the original Lua table index + if SortAlphaBetically then table.sort(menuItem, function(a, b) return type(a) == "table" and type(b) == "table" and a.Name < b.Name; end); - end - for k, v in ipairs(menuItem) do - if type(v) == "table" then - menu[k] = v.Name; - else - if GoBackText ~= nil and GoBackText ~= "" then - menu[k] = GoBackText; -- Only show the "Go Back" option if we actually have it set to something + end + + -- Safely iterate using the highest numeric index to avoid ipairs issues when the table contains gaps + local maxIndex = 0 + for k, _ in pairs(menuItem) do + if type(k) == "number" and k > maxIndex then + maxIndex = k + end + end + + for k = 1, maxIndex do + local v = menuItem[k] + if v ~= nil then + if type(v) == "table" then + table.insert(menu, v.Name) + table.insert(displayToOriginal, k) + else + if GoBackText ~= nil and GoBackText ~= "" then + table.insert(menu, GoBackText) + table.insert(displayToOriginal, k) + end end end end + + -- If the generated menu is empty, add a placeholder to prevent an empty list or crashes + if #menu == 0 then + table.insert(menu, "No Options Available") + table.insert(displayToOriginal, 1) + end + local ret = Script.ShowPopupList(TitleText, EmptyText, menu); - if ret.Canceled == true or (ret.Selected.Key == 1 and ret.Selected.Value == GoBackText) then + + -- If the user canceled or selected "Go Back" + if ret.Canceled == true or (ret.Selected.Key ~= nil and menu[ret.Selected.Key] == GoBackText) then if ret.Canceled == true then if ExitOnCancel == true then return nil, menuItem, ret.Canceled, nil; end end + if menuItem.Parent == nil or menuItem.Parent.Parent == nil then return nil, menuItem, ret.Canceled, nil; else @@ -35,7 +61,14 @@ _ShowMenu = function(menuItem) return _ShowMenu(menuItem.Parent.Parent); end else - ret = menuItem[ret.Selected.Key]; + -- Use the mapping to retrieve the correct original item, preventing index mismatches + local originalKey = displayToOriginal[ret.Selected.Key] + if originalKey == nil then + originalKey = ret.Selected.Key + end + + ret = menuItem[originalKey]; + if ret.SubMenu == nil then return ret.Data, menuItem, false, ret; else @@ -46,57 +79,70 @@ _ShowMenu = function(menuItem) end Menu = { - ShowMenu = function(menuItem) - return _ShowMenu(menuItem); -- Call the actual function^ + ShowMenu = function(menuItem) + return _ShowMenu(menuItem); -- Calls the modified function end, + ShowMainMenu = function() - return _ShowMenu(TopLevelMenu.SubMenu); -- Show the main menu (TopLevelMenu.SubMenu) + return _ShowMenu(TopLevelMenu.SubMenu); -- Displays the main menu end, + ResetMenu = function() - TopLevelMenu.SubMenu = {} -- Reset the menu to be empty - TitleText = "Menu"; -- Reset title to the default one - EmptyText = "No Menu Available"; -- Reset empty text to the default one - ExitOnCancel = false; -- Reset ExitOnCancel to it's default value - GoBackText = "Go Back"; -- Reset GoBackText to it's default value + TopLevelMenu.SubMenu = {} -- Clears the menu + TitleText = "Menu"; -- Restores the default title + EmptyText = "No Menu Available"; -- Restores the default empty message + ExitOnCancel = false; -- Resets ExitOnCancel + GoBackText = "Go Back"; -- Restores the default Go Back text end, + MakeMenuItem = function(displayName, data) return { - Name = displayName; -- Set the Name Property to be displayed in the menu - Data = data; -- Set the data property, if any... + Name = displayName; -- Displayed menu item name + Data = data; -- Associated data } end, + AddSubMenuItem = function(menuItem, subMenuItem) - if menuItem.SubMenu == nil then -- Check if we have a SubMenu already or not - menuItem.SubMenu = {} -- Add the SubMenu table - menuItem.SubMenu[1] = GoBackText; -- Add a "Go Back" to the Sub Menu - menuItem.SubMenu.Parent = menuItem; -- Set the parent of the SubMenu to the menu we're adding it to + if menuItem.SubMenu == nil then -- Create the submenu if it doesn't exist + menuItem.SubMenu = {} + menuItem.SubMenu[1] = GoBackText; -- Add "Go Back" as the first item + menuItem.SubMenu.Parent = menuItem; -- Set submenu parent end - subMenuItem.Parent = menuItem.SubMenu; -- Set the parent of the subMenuItem to the SubMenu table so we can go further up the chain - table.insert(menuItem.SubMenu, subMenuItem); -- Insert the subMenuItem to the menu + + subMenuItem.Parent = menuItem.SubMenu; -- Set submenu item parent + table.insert(menuItem.SubMenu, subMenuItem); -- Add submenu item end, + AddMainMenuItem = function(menuItem) - if TopLevelMenu.SubMenu == nil then -- Check if we have a SubMenu already or not - TopLevelMenu.SubMenu = {} -- Add the SubMenu table - TopLevelMenu.SubMenu.Parent = TopLevelMenu; -- Set SubMenu parent to TopLevelMenu + if TopLevelMenu.SubMenu == nil then -- Create the main menu if necessary + TopLevelMenu.SubMenu = {} + TopLevelMenu.SubMenu.Parent = TopLevelMenu; end - menuItem.Parent = TopLevelMenu.SubMenu; -- Set the parent of the subMenuItem to the SubMenu table so we can go further up the chain - table.insert(TopLevelMenu.SubMenu, menuItem); -- Insert the menuItem to the menu + + menuItem.Parent = TopLevelMenu.SubMenu; + table.insert(TopLevelMenu.SubMenu, menuItem); -- Add main menu item end, + SetTitle = function(title) - TitleText = title; -- Set the TitleText + TitleText = title; end, + SetEmptyText = function(emptyText) - EmptyText = emptyText; -- Set the text to be shown if the menu is empty + EmptyText = emptyText; end, + SetExitOnCancel = function(exitOnCancel) - ExitOnCancel = exitOnCancel == true; -- Set the flag that tells the menu system to exit upon being canceled (B being pressed) + ExitOnCancel = exitOnCancel == true; end, + SetGoBackText = function(goBackText) - GoBackText = goBackText; -- Set the text for the return/back menu item + GoBackText = goBackText; end, + SetSortAlphaBetically = function(sortAlphaBetically) - SortAlphaBetically = sortAlphaBetically == true; -- Set the flag that tells us if we should sort alphabetically + SortAlphaBetically = sortAlphaBetically == true; end, + IsMainMenu = function(menu) return menu == TopLevelMenu.SubMenu; end From af75a697fc8f2562cfc8cd62e408ea4009038fff Mon Sep 17 00:00:00 2001 From: EduDicaseGameplay Date: Thu, 6 Aug 2026 21:43:58 -0300 Subject: [PATCH 2/4] Homebrew Store review feedback --- UtilityScripts/HomebrewStore/Main.lua | 78 ++++++++----- UtilityScripts/HomebrewStore/MenuSystem.lua | 116 ++++++-------------- 2 files changed, 88 insertions(+), 106 deletions(-) diff --git a/UtilityScripts/HomebrewStore/Main.lua b/UtilityScripts/HomebrewStore/Main.lua index fb737c1..3299010 100644 --- a/UtilityScripts/HomebrewStore/Main.lua +++ b/UtilityScripts/HomebrewStore/Main.lua @@ -322,41 +322,69 @@ end function HandleSelection(selection, repo, menu) local info = ""; + local destinationPath = ""; - -- Prompt the user to select a drive before showing the download confirmation - local drive = PromptContentDrive(); - if drive == nil then - return nil; - end + if repo.type == "Other" then + destinationPath = GetDestinationPath(selection.path, repo.type); + if destinationPath == nil or destinationPath == "" then + return nil; + end - info = info .. "Name: " .. selection.itemTitle .. "\n"; + info = info .. "Name: " .. selection.itemTitle .. "\n"; - if selection.itemVersion ~= nil and selection.itemVersion ~= "" then - info = info .. "Version: " .. selection.itemVersion .. "\n"; - end + if selection.itemVersion ~= nil and selection.itemVersion ~= "" then + info = info .. "Version: " .. selection.itemVersion .. "\n"; + end - if selection.itemAuthor ~= nil and selection.itemAuthor ~= "" then - info = info .. "Author: " .. selection.itemAuthor .. "\n"; - end + if selection.itemAuthor ~= nil and selection.itemAuthor ~= "" then + info = info .. "Author: " .. selection.itemAuthor .. "\n"; + end - if selection.itemSize ~= nil and selection.itemSize ~= "" then - info = info .. "Size: " .. selection.itemSize .. "\n"; - end + if selection.itemSize ~= nil and selection.itemSize ~= "" then + info = info .. "Size: " .. selection.itemSize .. "\n"; + end - -- Compute the correct destination path using the selected drive mount point - local destinationPath = GetDestinationPath(selection.path, repo.type, drive.MountPoint); + if selection.itemDescription ~= nil and selection.itemDescription ~= "" then + info = info .. "Description:\n" .. string.gsub(selection.itemDescription, "\\n", "\n") .. "\n\n"; + end - if destinationPath ~= nil and destinationPath ~= "" then - info = info .. "Path: " .. destinationPath .. "\n"; + info = info .. "Installation path:\n" .. destinationPath .. "\n\nThis package installs to a predefined location.\n\nDo you want to continue?"; else - return nil; - end + -- Prompt the user to select a drive before showing the download confirmation + local drive = PromptContentDrive(); + if drive == nil then + return nil; + end - if selection.itemDescription ~= nil and selection.itemDescription ~= "" then - info = info .. "Description:\n" .. string.gsub(selection.itemDescription, "\\n", "\n") .. "\n\n"; - end + info = info .. "Name: " .. selection.itemTitle .. "\n"; + + if selection.itemVersion ~= nil and selection.itemVersion ~= "" then + info = info .. "Version: " .. selection.itemVersion .. "\n"; + end + + if selection.itemAuthor ~= nil and selection.itemAuthor ~= "" then + info = info .. "Author: " .. selection.itemAuthor .. "\n"; + end + + if selection.itemSize ~= nil and selection.itemSize ~= "" then + info = info .. "Size: " .. selection.itemSize .. "\n"; + end + + -- Compute the correct destination path using the selected drive mount point + destinationPath = GetDestinationPath(selection.path, repo.type, drive.MountPoint); - info = info .. "\n\n\nDo you want to install this " .. repo.type .. " on " .. drive.MountPoint:gsub("\\", "") .. "?"; + if destinationPath ~= nil and destinationPath ~= "" then + info = info .. "Path: " .. destinationPath .. "\n"; + else + return nil; + end + + if selection.itemDescription ~= nil and selection.itemDescription ~= "" then + info = info .. "Description:\n" .. string.gsub(selection.itemDescription, "\\n", "\n") .. "\n\n"; + end + + info = info .. "\n\n\nDo you want to install this " .. repo.type .. " on " .. drive.MountPoint:gsub("\\", "") .. "?"; + end local ret = Script.ShowMessageBox("", info, "Yes", "No"); diff --git a/UtilityScripts/HomebrewStore/MenuSystem.lua b/UtilityScripts/HomebrewStore/MenuSystem.lua index 9659e43..dea1874 100644 --- a/UtilityScripts/HomebrewStore/MenuSystem.lua +++ b/UtilityScripts/HomebrewStore/MenuSystem.lua @@ -8,51 +8,25 @@ local SortAlphaBetically = false; _ShowMenu = function(menuItem) local menu = {} - local displayToOriginal = {} -- Maps the displayed index to the original Lua table index - if SortAlphaBetically then table.sort(menuItem, function(a, b) return type(a) == "table" and type(b) == "table" and a.Name < b.Name; end); - end - - -- Safely iterate using the highest numeric index to avoid ipairs issues when the table contains gaps - local maxIndex = 0 - for k, _ in pairs(menuItem) do - if type(k) == "number" and k > maxIndex then - maxIndex = k - end - end - - for k = 1, maxIndex do - local v = menuItem[k] - if v ~= nil then - if type(v) == "table" then - table.insert(menu, v.Name) - table.insert(displayToOriginal, k) - else - if GoBackText ~= nil and GoBackText ~= "" then - table.insert(menu, GoBackText) - table.insert(displayToOriginal, k) - end + end + for k, v in ipairs(menuItem) do + if type(v) == "table" then + menu[k] = v.Name; + else + if GoBackText ~= nil and GoBackText ~= "" then + menu[k] = GoBackText; -- Only show the "Go Back" option if we actually have it set to something end end end - - -- If the generated menu is empty, add a placeholder to prevent an empty list or crashes - if #menu == 0 then - table.insert(menu, "No Options Available") - table.insert(displayToOriginal, 1) - end - local ret = Script.ShowPopupList(TitleText, EmptyText, menu); - - -- If the user canceled or selected "Go Back" - if ret.Canceled == true or (ret.Selected.Key ~= nil and menu[ret.Selected.Key] == GoBackText) then + if ret.Canceled == true or (ret.Selected.Key == 1 and ret.Selected.Value == GoBackText) then if ret.Canceled == true then if ExitOnCancel == true then return nil, menuItem, ret.Canceled, nil; end end - if menuItem.Parent == nil or menuItem.Parent.Parent == nil then return nil, menuItem, ret.Canceled, nil; else @@ -61,14 +35,7 @@ _ShowMenu = function(menuItem) return _ShowMenu(menuItem.Parent.Parent); end else - -- Use the mapping to retrieve the correct original item, preventing index mismatches - local originalKey = displayToOriginal[ret.Selected.Key] - if originalKey == nil then - originalKey = ret.Selected.Key - end - - ret = menuItem[originalKey]; - + ret = menuItem[ret.Selected.Key]; if ret.SubMenu == nil then return ret.Data, menuItem, false, ret; else @@ -79,70 +46,57 @@ _ShowMenu = function(menuItem) end Menu = { - ShowMenu = function(menuItem) - return _ShowMenu(menuItem); -- Calls the modified function + ShowMenu = function(menuItem) + return _ShowMenu(menuItem); -- Call the actual function^ end, - ShowMainMenu = function() - return _ShowMenu(TopLevelMenu.SubMenu); -- Displays the main menu + return _ShowMenu(TopLevelMenu.SubMenu); -- Show the main menu (TopLevelMenu.SubMenu) end, - ResetMenu = function() - TopLevelMenu.SubMenu = {} -- Clears the menu - TitleText = "Menu"; -- Restores the default title - EmptyText = "No Menu Available"; -- Restores the default empty message - ExitOnCancel = false; -- Resets ExitOnCancel - GoBackText = "Go Back"; -- Restores the default Go Back text + TopLevelMenu.SubMenu = {} -- Reset the menu to be empty + TitleText = "Menu"; -- Reset title to the default one + EmptyText = "No Menu Available"; -- Reset empty text to the default one + ExitOnCancel = false; -- Reset ExitOnCancel to it's default value + GoBackText = "Go Back"; -- Reset GoBackText to it's default value end, - MakeMenuItem = function(displayName, data) return { - Name = displayName; -- Displayed menu item name - Data = data; -- Associated data + Name = displayName; -- Set the Name Property to be displayed in the menu + Data = data; -- Set the data property, if any... } end, - AddSubMenuItem = function(menuItem, subMenuItem) - if menuItem.SubMenu == nil then -- Create the submenu if it doesn't exist - menuItem.SubMenu = {} - menuItem.SubMenu[1] = GoBackText; -- Add "Go Back" as the first item - menuItem.SubMenu.Parent = menuItem; -- Set submenu parent + if menuItem.SubMenu == nil then -- Check if we have a SubMenu already or not + menuItem.SubMenu = {} -- Add the SubMenu table + menuItem.SubMenu[1] = GoBackText; -- Add a "Go Back" to the Sub Menu + menuItem.SubMenu.Parent = menuItem; -- Set the parent of the SubMenu to the menu we're adding it to end - - subMenuItem.Parent = menuItem.SubMenu; -- Set submenu item parent - table.insert(menuItem.SubMenu, subMenuItem); -- Add submenu item + subMenuItem.Parent = menuItem.SubMenu; -- Set the parent of the subMenuItem to the SubMenu table so we can go further up the chain + table.insert(menuItem.SubMenu, subMenuItem); -- Insert the subMenuItem to the menu end, - AddMainMenuItem = function(menuItem) - if TopLevelMenu.SubMenu == nil then -- Create the main menu if necessary - TopLevelMenu.SubMenu = {} - TopLevelMenu.SubMenu.Parent = TopLevelMenu; + if TopLevelMenu.SubMenu == nil then -- Check if we have a SubMenu already or not + TopLevelMenu.SubMenu = {} -- Add the SubMenu table + TopLevelMenu.SubMenu.Parent = TopLevelMenu; -- Set SubMenu parent to TopLevelMenu end - - menuItem.Parent = TopLevelMenu.SubMenu; - table.insert(TopLevelMenu.SubMenu, menuItem); -- Add main menu item + menuItem.Parent = TopLevelMenu.SubMenu; -- Set the parent of the subMenuItem to the SubMenu table so we can go further up the chain + table.insert(TopLevelMenu.SubMenu, menuItem); -- Insert the menuItem to the menu end, - SetTitle = function(title) - TitleText = title; + TitleText = title; -- Set the TitleText end, - SetEmptyText = function(emptyText) - EmptyText = emptyText; + EmptyText = emptyText; -- Set the text to be shown if the menu is empty end, - SetExitOnCancel = function(exitOnCancel) - ExitOnCancel = exitOnCancel == true; + ExitOnCancel = exitOnCancel == true; -- Set the flag that tells the menu system to exit upon being canceled (B being pressed) end, - SetGoBackText = function(goBackText) - GoBackText = goBackText; + GoBackText = goBackText; -- Set the text for the return/back menu item end, - SetSortAlphaBetically = function(sortAlphaBetically) - SortAlphaBetically = sortAlphaBetically == true; + SortAlphaBetically = sortAlphaBetically == true; -- Set the flag that tells us if we should sort alphabetically end, - IsMainMenu = function(menu) return menu == TopLevelMenu.SubMenu; end From ec8e5fdec85ff51eec4f0823f7925796649d0c34 Mon Sep 17 00:00:00 2001 From: EduDicaseGameplay Date: Fri, 7 Aug 2026 20:22:13 -0300 Subject: [PATCH 3/4] Fix redundant restart menu logic --- UtilityScripts/HomebrewStore/Main.lua | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/UtilityScripts/HomebrewStore/Main.lua b/UtilityScripts/HomebrewStore/Main.lua index 3299010..7f7b920 100644 --- a/UtilityScripts/HomebrewStore/Main.lua +++ b/UtilityScripts/HomebrewStore/Main.lua @@ -81,13 +81,9 @@ function main() goto scriptend; end - local g_RestartMenu = true - while g_RestartMenu do - g_RestartMenu = false - Menu.ResetMenu(); - MakeMainMenu(); - DoShowMenu(); - end + Menu.ResetMenu(); + MakeMainMenu(); + DoShowMenu(); if reloadRequired and not gAbortedOperation then local ret = Script.ShowMessageBox( @@ -215,7 +211,7 @@ function MakeMainMenu() end function DoShowMenu(menu) - if gAbortedOperation or g_RestartMenu then + if gAbortedOperation then return; end @@ -294,18 +290,10 @@ function DoShowMenu(menu) end end - if g_RestartMenu then - return; - end - if menuItem.SubMenu ~= nil then -- Open submenu DoShowMenu(menuItem.SubMenu); - if g_RestartMenu then - return; - end - elseif not Menu.IsMainMenu(menu) then -- Content item selected HandleSelection(ret, menu.Parent.Data, menu); From 991d41871d245db465e4a4ad1db729f4ab9ff26b Mon Sep 17 00:00:00 2001 From: EduDicaseGameplay Date: Sun, 16 Aug 2026 19:00:54 -0300 Subject: [PATCH 4/4] Add persistent preferred storage selection --- UtilityScripts/HomebrewStore/Main.lua | 165 +++++++++++++++++++------- 1 file changed, 124 insertions(+), 41 deletions(-) diff --git a/UtilityScripts/HomebrewStore/Main.lua b/UtilityScripts/HomebrewStore/Main.lua index 7f7b920..6cc4708 100644 --- a/UtilityScripts/HomebrewStore/Main.lua +++ b/UtilityScripts/HomebrewStore/Main.lua @@ -12,6 +12,7 @@ local downloadsPath = "Downloads\\"; local gAbortedOperation = false; local absoluteDownloadsPath = ""; local scanPathCache = nil; +local g_RestartMenu = false; -- Unified temporary cleanup function (DRY / Reduced redundancy) local function Cleanup() @@ -81,9 +82,13 @@ function main() goto scriptend; end - Menu.ResetMenu(); - MakeMainMenu(); - DoShowMenu(); + g_RestartMenu = true + while g_RestartMenu do + g_RestartMenu = false + Menu.ResetMenu(); + MakeMainMenu(); + DoShowMenu(); + end if reloadRequired and not gAbortedOperation then local ret = Script.ShowMessageBox( @@ -104,8 +109,58 @@ function main() ::scriptend:: end --- Dynamic device prompt using the native Aurora FileSystem API +-- Preferred storage device helpers for persistent configuration +local function LoadPreferredDrive() + local iniPath = Script.GetBasePath() .. "settings.ini"; + if FileSystem.FileExists(iniPath) then + local ini = IniFile.LoadFile("settings.ini"); + if ini ~= nil then + local val = ini:ReadValue("Storage", "PreferredDrive", ""); + if val ~= "" then + return val; + end + end + end + return nil; +end + +local function SavePreferredDrive(mountPoint) + local cleanDrive = mountPoint or ""; + local iniPath = Script.GetBasePath() .. "settings.ini"; + if io ~= nil and io.open ~= nil then + local f = io.open(iniPath, "w"); + if f ~= nil then + f:write("[Storage]\nPreferredDrive=" .. cleanDrive .. "\n"); + f:close(); + end + end +end + +local function GetPreferredDrive() + local prefMount = LoadPreferredDrive(); + if prefMount ~= nil and prefMount ~= "" then + -- Normalize drive string (e.g. "Hdd1:\", "Hdd1:", "Hdd1" -> "hdd1") + local cleanPref = string.lower(prefMount:gsub("[:\\/]", "")); + local drives = FileSystem.GetDrives(true); + if drives ~= nil then + for _, d in ipairs(drives) do + local cleanD = string.lower(d.MountPoint:gsub("[:\\/]", "")); + if cleanD == cleanPref then + return d; + end + end + end + end + return nil; +end + +-- Dynamic device prompt using the native Aurora FileSystem API (with preferred drive fallback) function PromptContentDrive() + local pref = GetPreferredDrive(); + if pref ~= nil then + return pref; + end + local drives = FileSystem.GetDrives(true); -- Content-capable drives only local names = {}; @@ -208,10 +263,17 @@ function MakeMainMenu() ["name"] = "test", ["iniurl"] = "ENTER_URL", })); + + local pref = GetPreferredDrive(); + local prefLabel = (pref ~= nil and pref.MountPoint:gsub("\\", "") or "Ask every time"); + Menu.AddMainMenuItem(Menu.MakeMenuItem("Set Preferred Storage (" .. prefLabel .. ")", { + ["name"] = "storage_settings", + ["action"] = "set_storage", + })); end function DoShowMenu(menu) - if gAbortedOperation then + if gAbortedOperation or g_RestartMenu then return; end @@ -257,6 +319,36 @@ function DoShowMenu(menu) Script.ShowMessageBox("ERROR", "Failed to download the .ini file:\n\n" .. iniurl, "OK"); end + return + elseif (ret.action == "set_storage") then + -- Select and persist preferred storage drive + local drives = FileSystem.GetDrives(true); + local names = { "Ask every time (Default)" }; + for i, d in ipairs(drives) do + local label = d.MountPoint; + if d.Name ~= nil and d.Name ~= "" then + label = label .. " (" .. d.Name .. ")"; + end + table.insert(names, label); + end + + local pick = Script.ShowPopupList( + "Select Preferred Storage Drive", + "No content drives found", + names + ); + + if not pick.Canceled then + if pick.Selected.Key == 1 then + SavePreferredDrive(""); + Script.ShowNotification("Storage set to: Ask every time"); + else + local chosen = drives[pick.Selected.Key - 1]; + SavePreferredDrive(chosen.MountPoint); + Script.ShowNotification("Preferred drive set to " .. chosen.MountPoint:gsub("\\", "")); + end + end + g_RestartMenu = true; return else -- Load the .ini file from the repository entry @@ -290,10 +382,18 @@ function DoShowMenu(menu) end end + if g_RestartMenu then + return; + end + if menuItem.SubMenu ~= nil then -- Open submenu DoShowMenu(menuItem.SubMenu); + if g_RestartMenu then + return; + end + elseif not Menu.IsMainMenu(menu) then -- Content item selected HandleSelection(ret, menu.Parent.Data, menu); @@ -312,52 +412,39 @@ function HandleSelection(selection, repo, menu) local info = ""; local destinationPath = ""; - if repo.type == "Other" then - destinationPath = GetDestinationPath(selection.path, repo.type); - if destinationPath == nil or destinationPath == "" then - return nil; - end + -- Unified item information formatting (DRY / Deduplication) + info = info .. "Name: " .. selection.itemTitle .. "\n"; - info = info .. "Name: " .. selection.itemTitle .. "\n"; + if selection.itemVersion ~= nil and selection.itemVersion ~= "" then + info = info .. "Version: " .. selection.itemVersion .. "\n"; + end - if selection.itemVersion ~= nil and selection.itemVersion ~= "" then - info = info .. "Version: " .. selection.itemVersion .. "\n"; - end + if selection.itemAuthor ~= nil and selection.itemAuthor ~= "" then + info = info .. "Author: " .. selection.itemAuthor .. "\n"; + end - if selection.itemAuthor ~= nil and selection.itemAuthor ~= "" then - info = info .. "Author: " .. selection.itemAuthor .. "\n"; - end + if selection.itemSize ~= nil and selection.itemSize ~= "" then + info = info .. "Size: " .. selection.itemSize .. "\n"; + end - if selection.itemSize ~= nil and selection.itemSize ~= "" then - info = info .. "Size: " .. selection.itemSize .. "\n"; - end + if selection.itemDescription ~= nil and selection.itemDescription ~= "" then + info = info .. "Description:\n" .. string.gsub(selection.itemDescription, "\\n", "\n") .. "\n\n"; + end - if selection.itemDescription ~= nil and selection.itemDescription ~= "" then - info = info .. "Description:\n" .. string.gsub(selection.itemDescription, "\\n", "\n") .. "\n\n"; + if repo.type == "Other" then + destinationPath = GetDestinationPath(selection.path, repo.type); + if destinationPath == nil or destinationPath == "" then + return nil; end info = info .. "Installation path:\n" .. destinationPath .. "\n\nThis package installs to a predefined location.\n\nDo you want to continue?"; else - -- Prompt the user to select a drive before showing the download confirmation + -- Prompt or fetch preferred drive before showing confirmation local drive = PromptContentDrive(); if drive == nil then return nil; end - info = info .. "Name: " .. selection.itemTitle .. "\n"; - - if selection.itemVersion ~= nil and selection.itemVersion ~= "" then - info = info .. "Version: " .. selection.itemVersion .. "\n"; - end - - if selection.itemAuthor ~= nil and selection.itemAuthor ~= "" then - info = info .. "Author: " .. selection.itemAuthor .. "\n"; - end - - if selection.itemSize ~= nil and selection.itemSize ~= "" then - info = info .. "Size: " .. selection.itemSize .. "\n"; - end - -- Compute the correct destination path using the selected drive mount point destinationPath = GetDestinationPath(selection.path, repo.type, drive.MountPoint); @@ -367,10 +454,6 @@ function HandleSelection(selection, repo, menu) return nil; end - if selection.itemDescription ~= nil and selection.itemDescription ~= "" then - info = info .. "Description:\n" .. string.gsub(selection.itemDescription, "\\n", "\n") .. "\n\n"; - end - info = info .. "\n\n\nDo you want to install this " .. repo.type .. " on " .. drive.MountPoint:gsub("\\", "") .. "?"; end