diff --git a/README.md b/README.md index 7eeca192..a19e3f70 100644 --- a/README.md +++ b/README.md @@ -90,50 +90,56 @@ Checks for missing required assets for each core you have selected (mainly arcad ``` - menu (Default Verb) Interactive Main Menu - -p, --path Absolute path to install location - -s, --skip-update Go straight to the menu, without looking for an update + menu Interactive Main Menu (Default Verb) + -p, --path Absolute path to install location + -s, --skip-update Go straight to the menu, without looking for an update - fund List sponsor links. Lists all if no core is provided - -c, --core The core to check funding links for + fund List sponsor links. Lists all if no core is provided + -c, --core The core to check funding links for - update Run update all. (Can be configured via the settings menu) - -p, --path Absolute path to install location - -c, --core The core you want to update. Runs for all otherwise - -f, --platformsfolder Preserve the Platforms folder, so customizations aren't overwritten by updates. - -r, --clean Clean install. Remove all existing core files, and force a fresh re-install + update Run update all. (Can be configured via the settings menu) + -p, --path Absolute path to install location + -c, --core The core you want to update. Runs for all otherwise + -f, --platformsfolder Preserve the Platforms folder, so customizations aren't overwritten by updates. + -r, --clean Clean install. Remove all existing core files, and force a fresh re-install - uninstall Delete a core - -p, --path Absolute path to install location - -c, --core The core you want to uninstall. Required - -a, --assets Delete the core specific Assets folder. ex: Assets/{platform}/{corename} - - assets Run the asset downloader - -p, --path Absolute path to install location - -c, --core The core you want to download assets for. - - firmware Check for Pocket firmware updates - -p, --path Absolute path to install location - - images Download image packs - -p, --path Absolute path to install location - -o, --owner Image pack repo username - -i, --imagepack Github repo name for image pack - -v, --variant The optional variant - - instancegenerator Run the instance JSON generator - -p, --path Absolute path to install location - - backup-saves Compress and backup Saves & Memories directories - -p, --path Absolute path to install location - -l, --location Absolute path to backup location. Required - -s, --save Save settings to the config file for use during 'Update All' - - update-self Check for updates to pupdate + uninstall Delete a core + -p, --path Absolute path to install location + -c, --core The core you want to uninstall. Required + -a, --assets Delete the core specific Assets folder. ex: Assets/{platform}/{corename} + + assets Run the asset downloader + -p, --path Absolute path to install location + -c, --core The core you want to download assets for. + + firmware Check for Pocket firmware updates + -p, --path Absolute path to install location + + images Download image packs + -p, --path Absolute path to install location + -o, --owner Image pack repo username + -i, --imagepack Github repo name for image pack + -v, --variant The optional variant + + instance-generator Run the instance JSON generator for PC Engine CD + -p, --path Absolute path to install location + + backup-saves Compress and backup Saves & Memories directories + -p, --path Absolute path to install location + -l, --location Absolute path to backup location. Required + -s, --save Save settings to the config file for use during 'Update All' + + gameboy-palettes Run the instance JSON generator + -p, --path Absolute path to install location + + pocket-library-images Run the instance JSON generator + -p, --path Absolute path to install location + + update-self Check for updates to pupdate - help Display more information on a specific command. + help Display more information on a specific command. - version Display version information. + version Display version information. ``` diff --git a/src/Program.cs b/src/Program.cs index abfc30bd..9bb4dccf 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -40,7 +40,8 @@ private static async Task Main(string[] args) parser.ParseArguments(args) + UpdateSelfOptions, UninstallOptions, BackupSavesOptions, GameBoyPalettesOptions, + PocketLibraryImagesOptions>(args) .WithParsed(_ => { selfUpdate = true; }) .WithParsed(o => { @@ -169,6 +170,18 @@ private static async Task Main(string[] args) backupSaves_Path = o.BackupPath; backupSaves_SaveConfig = o.Save; }) + .WithParsed(o => + { + verb = "gameboy-palettes"; + CLI_MODE = true; + path = o.InstallPath; + }) + .WithParsed(o => + { + verb = "pocket-library-images"; + CLI_MODE = true; + path = o.InstallPath; + }) .WithNotParsed(e => { if (e.IsHelp()) @@ -349,35 +362,45 @@ private static async Task Main(string[] args) await pack.Install(path); } - else if (verb == "uninstall") + else switch (verb) { - if (GlobalHelper.GetCore(coreName) == null) - { - Console.WriteLine("Unknown core"); - } - else - { + case "uninstall" when GlobalHelper.GetCore(coreName) == null: + Console.WriteLine($"Unknown core '{coreName}'"); + break; + + case "uninstall": coreUpdater.DeleteCore(GlobalHelper.GetCore(coreName), true, nuke); - } - } - else if (verb == "backup-saves") - { - AssetsService.BackupSaves(path, backupSaves_Path); - AssetsService.BackupMemories(path, backupSaves_Path); + break; - if (backupSaves_SaveConfig) + case "backup-saves": { - var config = GlobalHelper.SettingsManager.GetConfig(); + AssetsService.BackupSaves(path, backupSaves_Path); + AssetsService.BackupMemories(path, backupSaves_Path); + + if (backupSaves_SaveConfig) + { + var config = GlobalHelper.SettingsManager.GetConfig(); - config.backup_saves = true; - config.backup_saves_location = backupSaves_Path; + config.backup_saves = true; + config.backup_saves_location = backupSaves_Path; - GlobalHelper.SettingsManager.SaveSettings(); + GlobalHelper.SettingsManager.SaveSettings(); + } + + break; } - } - else - { - DisplayMenuNew(path, coreUpdater); + + case "gameboy-palettes": + await DownloadGameBoyPalettes(path); + break; + + case "pocket-library-images": + await DownloadPockLibraryImages(path); + break; + + default: + DisplayMenuNew(path, coreUpdater); + break; } } catch (Exception e) diff --git a/src/options/GameBoyPalettesOptions.cs b/src/options/GameBoyPalettesOptions.cs new file mode 100644 index 00000000..bbcd8a1e --- /dev/null +++ b/src/options/GameBoyPalettesOptions.cs @@ -0,0 +1,10 @@ +using CommandLine; + +namespace Pannella.Options; + +[Verb("gameboy-palettes", HelpText = "Downloads and installs the GameBoy Palettes")] +public class GameBoyPalettesOptions +{ + [Option('p', "path", HelpText = "Absolute path to install location", Required = false)] + public string InstallPath { get; set; } +} diff --git a/src/options/InstanceGeneratorOptions.cs b/src/options/InstanceGeneratorOptions.cs index 8f6862c3..49fa6861 100644 --- a/src/options/InstanceGeneratorOptions.cs +++ b/src/options/InstanceGeneratorOptions.cs @@ -2,7 +2,7 @@ namespace Pannella.Options; -[Verb("instancegenerator", HelpText = "Run the instance JSON generator")] +[Verb("instance-generator", aliases: new[] { "instancegenerator" }, HelpText = "Run the instance JSON generator for PC Engine CD" )] public class InstanceGeneratorOptions { [Option('p', "path", HelpText = "Absolute path to install location", Required = false)] diff --git a/src/options/PocketLibraryImagesOptions.cs b/src/options/PocketLibraryImagesOptions.cs new file mode 100644 index 00000000..c37118cd --- /dev/null +++ b/src/options/PocketLibraryImagesOptions.cs @@ -0,0 +1,11 @@ +using CommandLine; + +namespace Pannella.Options +{ + [Verb("pocket-library-images", HelpText = "Downloads and installs the Pocket Library Images")] + public class PocketLibraryImagesOptions + { + [Option('p', "path", HelpText = "Absolute path to install location", Required = false)] + public string InstallPath { get; set; } + } +} diff --git a/src/partials/Program.GameBoyPalettes.cs b/src/partials/Program.GameBoyPalettes.cs new file mode 100644 index 00000000..a58989dd --- /dev/null +++ b/src/partials/Program.GameBoyPalettes.cs @@ -0,0 +1,45 @@ +using System.IO.Compression; +using Pannella.Helpers; +using Pannella.Models.Github; +using Pannella.Services; +using File = System.IO.File; + +namespace Pannella; + +internal partial class Program +{ + private static async Task DownloadGameBoyPalettes(string directory) + { + Release release = await GithubApiService.GetLatestRelease("davewongillies", "openfpga-palettes"); + Asset asset = release.assets.FirstOrDefault(a => a.name.EndsWith(".zip")); + + if (asset != null) + { + string localFile = Path.Combine(directory, asset.name); + string extractPath = Path.Combine(directory, "temp"); + + try + { + Console.WriteLine($"Downloading asset '{asset.name}'..."); + await HttpHelper.Instance.DownloadFileAsync(asset.browser_download_url, localFile); + Console.WriteLine("Download complete."); + Console.WriteLine("Installing..."); + + if (Directory.Exists(extractPath)) + Directory.Delete(extractPath, true); + + ZipFile.ExtractToDirectory(localFile, extractPath); + File.Delete(localFile); + Util.CopyDirectory(extractPath, directory, true, true); + + Directory.Delete(extractPath, true); + Console.WriteLine("Complete."); + } + catch (Exception ex) + { + Console.WriteLine("Something happened while trying to install the asset files..."); + Console.WriteLine(ex); + } + } + } +} diff --git a/src/partials/Program.HelpText.cs b/src/partials/Program.HelpText.cs index 585594d7..f93f3468 100644 --- a/src/partials/Program.HelpText.cs +++ b/src/partials/Program.HelpText.cs @@ -4,49 +4,54 @@ internal partial class Program { private const string HELP_TEXT = @"Usage: - menu Interactive Main Menu (Default Verb) - -p, --path Absolute path to install location - -s, --skip-update Go straight to the menu, without looking for an update + menu Interactive Main Menu (Default Verb) + -p, --path Absolute path to install location + -s, --skip-update Go straight to the menu, without looking for an update - fund List sponsor links. Lists all if no core is provided - -c, --core The core to check funding links for + fund List sponsor links. Lists all if no core is provided + -c, --core The core to check funding links for - update Run update all. (Can be configured via the settings menu) - -p, --path Absolute path to install location - -c, --core The core you want to update. Runs for all otherwise - -f, --platformsfolder Preserve the Platforms folder, so customizations aren't overwritten by updates. - -r, --clean Clean install. Remove all existing core files, and force a fresh re-install + update Run update all. (Can be configured via the settings menu) + -p, --path Absolute path to install location + -c, --core The core you want to update. Runs for all otherwise + -f, --platformsfolder Preserve the Platforms folder, so customizations aren't overwritten by updates. + -r, --clean Clean install. Remove all existing core files, and force a fresh re-install - uninstall Delete a core - -p, --path Absolute path to install location - -c, --core The core you want to uninstall. Required - -a, --assets Delete the core specific Assets folder. ex: Assets/{platform}/{corename} - - assets Run the asset downloader - -p, --path Absolute path to install location - -c, --core The core you want to download assets for. - - firmware Check for Pocket firmware updates - -p, --path Absolute path to install location - - images Download image packs - -p, --path Absolute path to install location - -o, --owner Image pack repo username - -i, --imagepack Github repo name for image pack - -v, --variant The optional variant - - instancegenerator Run the instance JSON generator - -p, --path Absolute path to install location - - backup-saves Compress and backup Saves & Memories directories - -p, --path Absolute path to install location - -l, --location Absolute path to backup location. Required - -s, --save Save settings to the config file for use during 'Update All' - - update-self Check for updates to pupdate + uninstall Delete a core + -p, --path Absolute path to install location + -c, --core The core you want to uninstall. Required + -a, --assets Delete the core specific Assets folder. ex: Assets/{platform}/{corename} + + assets Run the asset downloader + -p, --path Absolute path to install location + -c, --core The core you want to download assets for. + + firmware Check for Pocket firmware updates + -p, --path Absolute path to install location + + images Download image packs + -p, --path Absolute path to install location + -o, --owner Image pack repo username + -i, --imagepack Github repo name for image pack + -v, --variant The optional variant + + instance-generator Run the instance JSON generator for PC Engine CD + -p, --path Absolute path to install location + + backup-saves Compress and backup Saves & Memories directories + -p, --path Absolute path to install location + -l, --location Absolute path to backup location. Required + -s, --save Save settings to the config file for use during 'Update All' + + gameboy-palettes Run the instance JSON generator + -p, --path Absolute path to install location + + pocket-library-images Run the instance JSON generator + -p, --path Absolute path to install location + + update-self Check for updates to pupdate - help Display more information on a specific command. + help Display more information on a specific command. - version Display version information. -"; + version Display version information."; } diff --git a/src/partials/Program.Menus.cs b/src/partials/Program.Menus.cs index b7a7a52d..2429f5ef 100644 --- a/src/partials/Program.Menus.cs +++ b/src/partials/Program.Menus.cs @@ -32,6 +32,16 @@ private static void DisplayMenuNew(string path, PocketCoreUpdater coreUpdater) { await ImagePackSelector(path); }) + .Add("Download Pocket Library Images", async _ => + { + await DownloadPockLibraryImages(path); + Pause(); + }) + .Add("Download GameBoy Palettes", async _ => + { + await DownloadGameBoyPalettes(path); + Pause(); + }) .Add("Generate Instance JSON Files (PC Engine CD)", () => { RunInstanceGenerator(coreUpdater); diff --git a/src/partials/Program.PocketLibraryImages.cs b/src/partials/Program.PocketLibraryImages.cs new file mode 100644 index 00000000..3917a400 --- /dev/null +++ b/src/partials/Program.PocketLibraryImages.cs @@ -0,0 +1,43 @@ +using System.IO.Compression; +using Pannella.Helpers; +using Pannella.Services; +using ArchiveFile = Pannella.Models.Archive.File; + +namespace Pannella; + +internal partial class Program +{ + private static async Task DownloadPockLibraryImages(string directory) + { + const string fileName = "Library_Image_Set_v1.0.zip"; + ArchiveFile archiveFile = GlobalHelper.ArchiveFiles.GetFile(fileName); + + if (archiveFile != null) + { + string localFile = Path.Combine(directory, fileName); + string extractPath = Path.Combine(directory, "temp"); + + try + { + await ArchiveService.DownloadArchiveFile(GlobalHelper.SettingsManager.GetConfig().archive_name, + archiveFile, directory); + Console.WriteLine("Installing..."); + + if (Directory.Exists(extractPath)) + Directory.Delete(extractPath, true); + + ZipFile.ExtractToDirectory(localFile, extractPath); + File.Delete(localFile); + Util.CopyDirectory(extractPath, directory, true, true); + + Directory.Delete(extractPath, true); + Console.WriteLine("Complete."); + } + catch (Exception ex) + { + Console.WriteLine("Something happened while trying to install the asset files..."); + Console.WriteLine(ex); + } + } + } +} diff --git a/src/services/ArchiveService.cs b/src/services/ArchiveService.cs index 4bb2345a..285323cf 100644 --- a/src/services/ArchiveService.cs +++ b/src/services/ArchiveService.cs @@ -1,18 +1,22 @@ using System.Diagnostics.CodeAnalysis; +using System.Net; using System.Text.Json; using Pannella.Helpers; using Pannella.Models.Archive; +using File = Pannella.Models.Archive.File; namespace Pannella.Services; [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] public static class ArchiveService { - private const string END_POINT = "https://archive.org/metadata/{0}"; + private const string METADATA = "https://archive.org/metadata/{0}"; + + private const string DOWNLOAD = "https://archive.org/download/{0}/{1}"; public static async Task GetFiles(string archive) { - string url = string.Format(END_POINT, archive); + string url = string.Format(METADATA, archive); string json = await HttpHelper.Instance.GetHTML(url); Archive result = JsonSerializer.Deserialize(json); @@ -33,4 +37,47 @@ public static async Task GetFilesCustom(string url) return null; } } + + public static async Task DownloadArchiveFile(string archiveName, File archiveFile, string destination) + { + try + { + string url = string.Format(DOWNLOAD, archiveName, archiveFile.name); + string destinationFileName = Path.Combine(destination, archiveFile.name); + int count = 0; + + do + { + Console.WriteLine($"Downloading '{archiveFile.name}'"); + await HttpHelper.Instance.DownloadFileAsync(url, destinationFileName, 600); + Console.WriteLine($"Finished downloading '{archiveFile.name}'"); + count++; + } + while (count < 3 && !ValidateChecksum(destinationFileName, archiveFile)); + } + catch (HttpRequestException e) + { + Console.WriteLine(e.StatusCode switch + { + HttpStatusCode.NotFound => $"Unable to find '{archiveFile.name}' in archive '{archiveName}'", + _ => $"There was a problem downloading '{archiveFile.name}'" + }); + throw; + } + } + + private static bool ValidateChecksum(string filePath, File archiveFile) + { + if (!GlobalHelper.SettingsManager.GetConfig().crc_check) + return true; + + if (archiveFile == null) + return true; + + if (Util.CompareChecksum(filePath, archiveFile.crc32)) + return true; + + Console.WriteLine($"Bad checksum for {Path.GetFileName(filePath)}"); + return false; + } }