From de441ffe70bce094704269d2f4d20ad25366978b Mon Sep 17 00:00:00 2001 From: Michael Hallett Date: Thu, 19 Sep 2024 15:13:02 -0400 Subject: [PATCH] Merges PRs for Coin-Op Beta Support and Display Modes Support (#336) * wip * testing * Added dynamic support for display modes - Added support for a curated list of display modes that is based off of a json file - Added support for a display mode selector that applies the selection to all cores Note: The video.json file is reset after a core update happens. * Added a maximum per the specs and a current count * model update * Display Modes Menu paging support - Added paging to the display mode selector with next, prev, and quit - Added prev and quit to core selector as well - Condensed the Pocket Setup menu into sub menus since we were full * wip * Updated the readme and some extras names * Added support for applying selected display modes to selected installed cores * Added support for updating a installing select cores * Made two hidden settings visible - use local pocket extras - use local display modes * more progress * remove jt stuff * whatever * refactoring * more cleanup * more * sigh * moved menu item * Little bit of cleanup * Display modes persistence - Display modes will get saved in the core settings - Display modes will be restored after a core gets updated - Display modes will be reset on core reinstall * readme * Updated the readme files and switch the CLI for display modes to use curated * Added missing using statement * Little bit of clean up * Fixed merge anomylies * 3am code is perfect code * fixed asset download * local file api * fix * oops * Fixed a logic issue on uninstall & moved logic for ignoring core instance jsons to a json file rather than code * List beta cores as new when they leave beta Closes #317 * Reconfigured the menus * need a user agent for the api * uri encode the get string paramater --------- Co-authored-by: Matt Pannella --- CLI.md | 2 +- MENU.md | 70 ++-- README.md | 68 +++- display_modes.json | 44 +++ pocket_extras.json | 6 +- pupdate.csproj | 12 + src/Program.cs | 10 +- src/helpers/ConsoleHelper.cs | 42 +- src/helpers/ServiceHelper.cs | 8 +- src/helpers/Util.cs | 49 +-- src/helpers/ZipHelper.cs | 176 ++++----- src/models/Base.cs | 2 + src/models/BaseProcess.cs | 2 + src/models/DisplayModes/DisplayMode.cs | 12 + src/models/DisplayModes/DisplayModes.cs | 6 + src/models/Events/StatusUpdatedEventArgs.cs | 2 +- .../Events/UpdateProcessCompleteEventArgs.cs | 4 +- src/models/Extras/PocketExtra.cs | 35 +- src/models/IngoreInstanceJson.cs | 7 + src/models/OpenFPGA_Cores_Inventory/Core.cs | 5 +- src/models/Settings/Archive.cs | 57 ++- src/models/Settings/Config.cs | 22 +- src/models/Settings/CoreSettings.cs | 12 + src/models/Updater/License.cs | 8 + src/models/Updater/Updaters.cs | 3 + src/partials/Program.DisplayModes.cs | 23 +- src/partials/Program.GameAndWatch.cs | 1 - src/partials/Program.Menus.Cores.cs | 148 +++++++ src/partials/Program.Menus.DisplayModes.cs | 133 +++++++ .../Program.Menus.PlatformImagePacks.cs | 63 +++ src/partials/Program.Menus.Questions.cs | 45 +++ src/partials/Program.Menus.Settings.cs | 51 +++ src/partials/Program.Menus.cs | 364 +++++------------- src/partials/Program.PocketLibraryImages.cs | 1 - src/services/AnalogizerSettingsService.cs | 21 +- src/services/AssetsService.cs | 42 +- src/services/CoinOpService.cs | 40 ++ src/services/CoreUpdaterService.cs | 62 +-- src/services/CoresService.DisplayModes.cs | 44 +++ src/services/CoresService.Download.cs | 57 +-- src/services/CoresService.Extras.cs | 13 +- src/services/CoresService.Helpers.cs | 43 ++- src/services/CoresService.Jotego.cs | 63 +-- src/services/CoresService.Json.cs | 16 + src/services/CoresService.License.cs | 94 +++++ src/services/CoresService.Video.cs | 144 +++---- src/services/CoresService.cs | 94 ++++- src/services/PlatformImagePacksService.cs | 3 +- src/services/SettingsService.cs | 22 +- 49 files changed, 1447 insertions(+), 804 deletions(-) create mode 100644 display_modes.json create mode 100644 src/models/DisplayModes/DisplayMode.cs create mode 100644 src/models/DisplayModes/DisplayModes.cs create mode 100644 src/models/IngoreInstanceJson.cs create mode 100644 src/models/Updater/License.cs create mode 100644 src/partials/Program.Menus.Cores.cs create mode 100644 src/partials/Program.Menus.DisplayModes.cs create mode 100644 src/partials/Program.Menus.PlatformImagePacks.cs create mode 100644 src/partials/Program.Menus.Questions.cs create mode 100644 src/partials/Program.Menus.Settings.cs create mode 100644 src/services/CoinOpService.cs create mode 100644 src/services/CoresService.DisplayModes.cs create mode 100644 src/services/CoresService.License.cs diff --git a/CLI.md b/CLI.md index f298c019..37335235 100644 --- a/CLI.md +++ b/CLI.md @@ -52,7 +52,7 @@ pocket-extras Download Pocket Extras -i, --info Shows the details for the specified 'name' -l, --list Lists out all of the values for 'name' and their details -display-modes Enable all Display Modes +display-modes Enable the curated list of Display Modes for all cores -p, --path Absolute path to install location update-self Check for updates to pupdate diff --git a/MENU.md b/MENU.md index 45c78d4a..9ae725f8 100644 --- a/MENU.md +++ b/MENU.md @@ -2,44 +2,58 @@ 2. Update Firmware 3. Select Cores 4. Download Assets -5. Backup Saves +5. Backup Saves & Memories 6. Pocket Setup - 1. Jotego Analogizer Config - 2. Download Platform Image Packs - 3. Download Pocket Library Images - 4. Download GameBoy Palettes - 5. Generate Instance JSON Files (PC Engine CD) - 6. Generate Game & Watch ROMs - 7. Enable All Display Modes - 8. Apply 8:7 Aspect Ratio to Super GameBoy cores - 9. Restore 4:3 Aspect Ratio to Super GameBoy cores - 10. Go Back -7. Pocket Maintenance - 1. Reinstall All Cores - 2. Reinstall Select Cores - 3. Uninstall Select Cores - 4. Remove JT Beta Keys - 5. Prune Save States + 1. Add Display Modes + 1. Enable Recommended Display Modes + 2. Enable Selected Display Modes for All Cores + 3. Enable Selected Display Modes for Select Cores + 2. Download Images and Palettes + 1. Download Platform Image Packs + 2. Download Pocket Library Images + 3. Download GameBoy Palettes + 3. Generate ROMs & JSON Files + 1. Generate Instance JSON Files (PC Engine CD) + 2. Generate Game & Watch ROMs + 4. Super GameBoy Aspect Ratio + 1. Apply 8:7 Aspect Ratio to Super GameBoy cores + 2. Restore 4:3 Aspect Ratio to Super GameBoy cores + 5. Jotego Analogizer Config 6. Go Back +7. Pocket Maintenance + 1. Update Selected + 2. Install Selected + 3. Reinstall All Cores + 4. Reinstall Select Cores + 5. Uninstall Select Cores + 6. Prune Save States + 7. Go Back 8. Pocket Extras 1. Additional Assets - 1. Download for Eric Lewis's Donkey Kong + 1. Download extras for Eric Lewis's Donkey Kong 2. Download extras for Eric Lewis's Radar Scope - 3. Download for Jotego's Bubble Bobble (jtbubl) - 4. Download for Jotego's Capcom CPS 1 (jtcps1) - 5. Download for Jotego's Capcom CPS 1.5 (jtcps15) - 6. Download for Jotego's Capcom CPS 2 (jtcps2) - 7. Download for Jotego's Pang / Super Pang (jtpang) + 3. Download extras for Jotego's Bubble Bobble (jtbubl) + 4. Download extras for Jotego's Capcom CPS 1 (jtcps1) + 5. Download extras for Jotego's Capcom CPS 1.5 (jtcps15) + 6. Download extras for Jotego's Capcom CPS 2 (jtcps2) + 7. Download extras for Jotego's Pang / Super Pang (jtpang) 8. Go Back 2. Combination Platforms 1. Download Toaplan Version 2 Hardware Combination Core - 2. Download Sega System 16 Combination Core - 3. Go Back + 2. Download Capcom Z80 Hardware Combination Core + 3. Download Williams 6809 Combination Core + 4. Download Sega System 16 Combination Core + 5. Go Back 3. Variant Cores 1. Download Vectrex Extras - 2. Download Super GameBoy 2 - 3. Download Super GameBoy 2: Vaperwave Edition - 4. Go Back + 2. Download Dig Dug II + 3. Download Joust + 4. Download Super GameBoy 2 + 5. Download Super GameBoy 2: Vaperwave Edition + 6. Download Dual Aspect Ratio: Super GameBoy + 7. Download Dual Aspect Ratio: Super GameBoy 2 + 8. Download Dual Aspect Ratio: Super GameBoy 2: Vaperwave Edition + 9. Go Back 4. Go Back 9. Settings 10. Exit diff --git a/README.md b/README.md index 1bda626e..0dbf537f 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ For a full view of the interactive console menu, see [here](MENU.md). [CLI Commands and Parameters](#cli-commands-and-parameters) | [Jotego Beta Cores](#jotego-beta-cores) | [Jotego Cores Analogizer Setup](#jotego-cores-analogizer-setup) | +[Coin-Op Collection Beta Cores](#coin-op-collection-beta-cores) | ### Update All Install/Update all of your cores, plus a bunch of other stuff. It can basically be used as the "do everything I want" option. Everything marked with a * can be turned on/off via settings. @@ -49,8 +50,14 @@ Install/Update all of your cores, plus a bunch of other stuff. It can basically 6. Runs the instance JSON generator for each core you have selected (currently, only PC Engine CD) * 7. Rename every Jotego core you have selected * +### Update Selected +Presents you with a list of your installed cores and lets you choose which ones you want to update. + +### Install Selected +Presents you with a list of cores you don't have installed and lets you choose which ones you want to install and then immediately installs them with having to run Update All. + ### Update Firmware -Self explanatory. Just checks for firmware updates and exits. +Self-explanatory. Just checks for firmware updates and exits. ### Select Cores @@ -72,6 +79,19 @@ _Note: You are responsible for finding and adding your own ROMs for non-arcade c This will compress the Saves and Memories directories from your Pocket to the location specified in the config settings. +### Pocket Setup - Display Modes + +- Enable Recommended Display Modes + +This enables a curates set of display modes and applies them to specific cores that you have installed. This list can be found in the [`display_modes.json`](display_modes.json) file. If you wish to make changes to this file, download it from GitHub and place it in the same directory as the pupdate executable. Then set `use_local_display_modes` to `true` in your `pupdate_settings.json` file. + +- Enable Selected Display Modes for All Cores + +This presents you with a list of all of the supported display modes and lets you select which ones you want to apply. Then it applies those display modes to all of the cores you have installed. + +- Enable Selected Display Modes for Select Cores + +This presents you with a list of all of the supported display modes and lets you select which ones you want to apply. Next, you'll be asked to select which of your installed cores you want to apply the display modes to. Then it applies those display modes to the cores you have selected. ### Pocket Setup - Download Platform Image Packs @@ -177,21 +197,24 @@ This contains a list of alternate core setups. These take existing cores and mak The following settings are all available via the `Settings` menu item. -| Name | Description | -|----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Download Firmware Updates | Check for firmware updates when running "Update All" | -| Download Missing Assets | Check for missing assets (ROMs, BIOS files, etc) when running "Update All" | -| Download Game & Watch ROMS | Download Game & Watch ROMS when running "Update All" | -| Build game JSON Files | Run the Instance JSON builder during "Update All" | -| Delete untracked cores | Any core that is available, but you have not chosen in the "Core Selector" will be uninstalled, if found on your SD card when running "Update All" | -| Automatically rename Jotego cores | Jotego's cores will be renamed to the correct titles of the platforms they are emulating, when running "Update All". example: jtcontra is Contra | -| Use CRC check | Use CRC file hashes to verify Asset files, and re-download if needed. When running "Update All" or "Download Required Assets" | -| Preserve Platforms folder | Don't overwrite changes made to files in the Platforms folder when running "Update All" | -| Skip alternative ROMs | Ignore files if they are in a folder named "_alternatives" when checking for Assets (Note: this is on by default) | -| Compress and backup Saves and Memories | This will compress and backup the Saves and Memories directory to the specified location. By default, a Backups directory will be created off the root. The location can be changed manually by setting the "backup_saves_location" with the absolute path in the settings file. | -| Show Menu Descriptions | This will show descriptions for some of the advanced menu items after they are selected, including a prompt asking if you want to proceed. This is enabled by default. | -| Use custom archive | Allows you to use a custom site for Asset file checking (there is a pre-configured site available). The actual URL of the custom site can be set manually by editing the settings file in an editor. | -| Automatically install updates to Pupdate | Turn this on and the application will automatically update itself without asking for input, when you start it | +| Name | Description | +|------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Download Firmware Updates | Check for firmware updates when running "Update All" | +| Download Missing Assets | Check for missing assets (ROMs, BIOS files, etc) when running "Update All" | +| Download Game & Watch ROMS | Download Game & Watch ROMS when running "Update All" | +| Build game JSON Files | Run the Instance JSON builder during "Update All" | +| Delete untracked cores | Any core that is available, but you have not chosen in the "Core Selector" will be uninstalled, if found on your SD card when running "Update All" | +| Automatically rename Jotego cores | Jotego's cores will be renamed to the correct titles of the platforms they are emulating, when running "Update All". example: jtcontra is Contra | +| Use CRC check | Use CRC file hashes to verify Asset files, and re-download if needed. When running "Update All" or "Download Required Assets" | +| Preserve Platforms folder | Don't overwrite changes made to files in the Platforms folder when running "Update All" | +| Skip alternative ROMs | Ignore files if they are in a folder named "_alternatives" when checking for Assets (Note: this is on by default) | +| Compress and backup Saves and Memories | This will compress and backup the Saves and Memories directory to the specified location. By default, a Backups directory will be created off the root. The location can be changed manually by setting the "backup_saves_location" with the absolute path in the settings file. | +| Show Menu Descriptions | This will show descriptions for some of the advanced menu items after they are selected, including a prompt asking if you want to proceed. This is enabled by default. | +| Use custom archive | Allows you to use a custom site for Asset file checking (there is a pre-configured site available). The actual URL of the custom site can be set manually by editing the settings file in an editor. | +| Automatically install updates to Pupdate | Turn this on and the application will automatically update itself without asking for input, when you start it. | +| Use Local Pocket Extras | Turn this on and place the `pocket_extras.json` file in the same directory as `pupdate` and it will use the local file instead of getting it from GitHub. | +| Use Local Display Modes | Turn this on and place the `display_modes.json` file in the same directory as `pupdate` and it will use the local file instead of getting it from GitHub. | + ### Additional Settings @@ -205,8 +228,8 @@ This contains a list of alternate core setups. These take existing cores and mak | config.custom_archive | You can set a custom URL here, if you don't want to use the default. `index` is a relative path to the index of your custom site's files. This is not required, but it's needed for CRC checking. If you have CRC checking enabled, the setting will be ignored unless this provides the necessary format. It must match the output of archive.org's json endpoint. https://archive.org/developers/md-read.html | | config.backup_saves | Set this to `true` if you want your Saves directory to be backed up automatically during `Update All` | | config.backup_saves_location | Put the absolute path to the backup location here to backup your `Saves` directory to. This defaults to `Backups` in the current directory when not set. | -| config.temp_directory | When left `null` all zip files are downloaded and extracted using a temp directory in your pocket install location. If you supply a path, that will be used, instead | -| core_settings | This allows you to set a subset of settings on a per core basis. It contains a list of every core, with 3 options. `skip`, `download_assets`, and `platform_rename`. You can use these to override your global defaults | +| config.temp_directory | When left `null` all zip files are downloaded and extracted using a temp directory in your pocket install location. If you supply a path, that will be used, instead | +| core_settings | This allows you to set a subset of settings on a per core basis. It contains a list of every core, with 3 options. `skip`, `download_assets`, and `platform_rename`. You can use these to override your global defaults | ## CLI Commands and Parameters @@ -262,7 +285,7 @@ This contains a list of alternate core setups. These take existing cores and mak -i, --info Shows the details for the specified 'name' -l, --list Lists out all of the values for 'name' and their details - display-modes Enable all Display Modes + display-modes Enable the curated list of Display Modes for all cores -p, --path Absolute path to install location prune-memories Delete all but the latest save states for each game (defaults to all cores) @@ -294,6 +317,13 @@ Now that Jotego is releasing his beta cores publicly (and requiring a beta key t To set your global configuration for the Analogizer in Jotego's Cores just go to `Pocket Setup` > `Jotego Analogizer Config` and it will walk you through the available settings. The settings will be saved to the correct location, so you don't need to do anything else. For more help please refer to his github repo. +## Coin-Op Collection Beta Cores + +- Go into Settings and turn on the Coin-Op Beta setting +- Next time you run Update All it will prompt you to enter your email address that you use for patreon. +- From now on each time you run Update All it will pull your beta license automatically, so that when you install Coin-Op beta cores, they will function correctly +- If you ever want to change the email address, go into the Pocket Setup menu + ## Troubleshooting - Slow asset downloads? Try toggling `use_custom_archive` to true, in your settings. diff --git a/display_modes.json b/display_modes.json new file mode 100644 index 00000000..690a3a69 --- /dev/null +++ b/display_modes.json @@ -0,0 +1,44 @@ +{ + "display_modes": { + "all": [ + { "value": "0x10", "description": "CRT Trinitron" }, + { "value": "0x20", "description": "Greyscale LCD" }, + { "value": "0x30", "description": "Reflective Color LCD" }, + { "value": "0x40", "description": "Backlit Color LCD" }, + { "value": "0xE0", "description": "Pinball Neon Matrix" }, + { "value": "0xE1", "description": "Vacuum Fluorescent" } + ], + "gb": [ + { "value": "0x21", "description": "Original GB DMG" }, + { "value": "0x22", "description": "Original GBP" }, + { "value": "0x23", "description": "Original GBP Light" } + ], + "gbc": [ + { "value": "0x31", "description": "Original GBC LCD" }, + { "value": "0x32", "description": "Original GBC LCD+" } + ], + "gba": [ + { "value": "0x41", "description": "Original GBA LCD" }, + { "value": "0x42", "description": "Original GBA SP 101" } + ], + "gg": [ + { "value": "0x51", "description": "Original GG" }, + { "value": "0x52", "description": "Original GG+" } + ], + "ngp": [ + { "value": "0x61", "description": "Original NGP" } + ], + "ngpc": [ + { "value": "0x62", "description": "Original NGPC" }, + { "value": "0x63", "description": "Original NGPC+" } + ], + "pce": [ + { "value": "0x71", "description": "TurboExpress" }, + { "value": "0x72", "description": "PC Engine LT" } + ], + "lynx": [ + { "value": "0x81", "description": "Original Lynx" }, + { "value": "0x82", "description": "Original Lynx+" } + ] + } +} diff --git a/pocket_extras.json b/pocket_extras.json index ce9246e5..cb15be49 100644 --- a/pocket_extras.json +++ b/pocket_extras.json @@ -197,7 +197,7 @@ }, { "id": "sgb-dual", - "name": "Super GameBoy - Dual Aspect Ratio", + "name": "Dual Aspect Ratio: Super GameBoy", "description": "Installs two cores based of off the Spiritiualized.SuperGB core, one for each aspect ratio (4:3, 8:7). You do not need to have the Super GameBoy core installed prior to using this.", "type": "variant_core", "core_identifiers": [ @@ -210,7 +210,7 @@ }, { "id": "sgb2-dual", - "name": "Super GameBoy 2 - Dual Aspect Ratio", + "name": "Dual Aspect Ratio: Super GameBoy 2", "description": "Installs two cores based of off the Spiritiualized.SuperGB core for the Super GameBoy 2 rom, one for each aspect ratio (4:3, 8:7). You do not need to have the Super GameBoy core installed prior to using this.", "type": "variant_core", "core_identifiers": [ @@ -223,7 +223,7 @@ }, { "id": "sgb2vw-dual", - "name": "Super GameBoy 2: Vaporwave Edition - Dual Aspect Ratio", + "name": "Dual Aspect Ratio: Super GameBoy 2: Vaporwave Edition", "description": "Installs two cores based of off the Spiritualized.SuperGB core for the Super GameBoy 2: Vaporwave Edition rom hack, one for each aspect ratio (4:3, 8:7). You do not need to have the Super GameBoy or Super GameBoy 2 core installed prior to using this.", "type": "variant_core", "core_identifiers": [ diff --git a/pupdate.csproj b/pupdate.csproj index 91d5de12..7599ac50 100644 --- a/pupdate.csproj +++ b/pupdate.csproj @@ -35,8 +35,20 @@ Always + + Always + + + Always + + + + + + + diff --git a/src/Program.cs b/src/Program.cs index 4cd8aac4..0575c671 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,6 +1,7 @@ using CommandLine; using Pannella.Helpers; using Pannella.Models; +using Pannella.Models.Events; using Pannella.Options; using Pannella.Services; @@ -205,7 +206,7 @@ private static void Main(string[] args) break; case DisplayModesOptions: - EnableDisplayModes(); + EnableDisplayModes(isCurated: true); break; case PruneMemoriesOptions options: @@ -281,12 +282,11 @@ private static void coreUpdater_UpdateProcessComplete(object sender, UpdateProce Console.WriteLine(); } - if (e.MissingBetaKeys.Count > 0) + if (e.MissingLicenses.Count > 0) { - Console.WriteLine("Missing or incorrect JT Beta Key for the following cores:"); - Console.WriteLine($"The {CoresService.BETA_KEY_FILENAME} or {CoresService.BETA_KEY_ALT_FILENAME} must be placed on the root of the SD card."); + Console.WriteLine("Missing or incorrect License file for the following cores:"); - foreach (string core in e.MissingBetaKeys) + foreach (string core in e.MissingLicenses) { Console.WriteLine(core); } diff --git a/src/helpers/ConsoleHelper.cs b/src/helpers/ConsoleHelper.cs index e46af4fe..4743a14c 100644 --- a/src/helpers/ConsoleHelper.cs +++ b/src/helpers/ConsoleHelper.cs @@ -1,33 +1,29 @@ -using System.IO.Compression; - namespace Pannella.Helpers; public class ConsoleHelper { - public static void ShowProgressBar(int current, int total) - { - var progress = (double)current / (double)total; - - var progressWidth = Console.WindowWidth - 14; - var progressBarWidth = (int)(progress * progressWidth); - var progressBar = new string('=', progressBarWidth); - var emptyProgressBar = new string(' ', progressWidth - progressBarWidth); - - Console.Write($"\r{progressBar}{emptyProgressBar}] {(progress * 100):0.00}%"); - - if (current == total) - { - Console.CursorLeft = 0; - Console.Write(new string(' ', Console.WindowWidth)); - Console.CursorLeft = 0; - Console.Write("\r"); - } - } + // public static void ShowProgressBar(int current, int total) + // { + // var progress = (double)current / total; + // var progressWidth = Console.WindowWidth - 14; + // var progressBarWidth = (int)(progress * progressWidth); + // var progressBar = new string('=', progressBarWidth); + // var emptyProgressBar = new string(' ', progressWidth - progressBarWidth); + // + // Console.Write($"\r{progressBar}{emptyProgressBar}] {(progress * 100):0.00}%"); + // + // if (current == total) + // { + // Console.CursorLeft = 0; + // Console.Write(new string(' ', Console.WindowWidth)); + // Console.CursorLeft = 0; + // Console.Write("\r"); + // } + // } public static void ShowProgressBar(long current, long total) { var progress = (double)current / total; - var progressWidth = Console.WindowWidth - 14; var progressBarWidth = (int)(progress * progressWidth); var progressBar = new string('=', progressBarWidth); @@ -43,4 +39,4 @@ public static void ShowProgressBar(long current, long total) Console.Write("\r"); } } -} \ No newline at end of file +} diff --git a/src/helpers/ServiceHelper.cs b/src/helpers/ServiceHelper.cs index c857e3e5..a1a6a44f 100644 --- a/src/helpers/ServiceHelper.cs +++ b/src/helpers/ServiceHelper.cs @@ -1,4 +1,5 @@ using Pannella.Models; +using Pannella.Models.Events; using Pannella.Services; namespace Pannella.Helpers; @@ -6,7 +7,7 @@ namespace Pannella.Helpers; public static class ServiceHelper { public static string UpdateDirectory { get; private set; } // move off this - public static string SettingsDirectory { get; private set; } //for retrodriven's app + public static string SettingsDirectory { get; private set; } // for retrodriven's app public static string TempDirectory { get; private set; } public static CoresService CoresService { get; private set; } public static SettingsService SettingsService { get; private set ;} @@ -34,6 +35,7 @@ public static void Initialize(string path, string settingsPath, EventHandler allfiles = Directory.GetFiles(sourceDir, "*",SearchOption.AllDirectories).ToList(); + List allFiles = Directory.GetFiles(sourceDir, "*",SearchOption.AllDirectories).ToList(); - int total = (totalFiles == null) ? allfiles.Count() : (int)totalFiles; + int total = totalFiles ?? allFiles.Count; int count = currentFileCount; + // Get the files in the source directory and copy to the destination directory foreach (FileInfo file in dir.GetFiles()) { string targetFilePath = Path.Combine(destinationDir, file.Name); file.CopyTo(targetFilePath, overwrite); - + count++; - if(console) ConsoleHelper.ShowProgressBar(count, total); + + if (console) + ConsoleHelper.ShowProgressBar(count, total); } // If recursive and copying subdirectories, recursively call this method @@ -71,14 +71,14 @@ private static int _copyDirectory(string sourceDir, string destinationDir, bool { string newDestinationDir = Path.Combine(destinationDir, subDir.Name); - count = _copyDirectory(subDir.FullName, newDestinationDir, true, overwrite, count, total); + count = CopyDirectory(subDir.FullName, newDestinationDir, true, overwrite, count, total); } } return count; } - public static void CleanDir(string source, string path = "", bool preservePlatformsFolder = false, bool preserveSaveFile = false, string platform = "") + public static void CleanDir(string source, string path = "", bool preservePlatformsFolder = false, string platform = "") { // Clean up any bad directories (like Mac OS directories). foreach (var dir in BAD_DIRS) @@ -112,31 +112,6 @@ public static void CleanDir(string source, string path = "", bool preservePlatfo } } - // if (preserveSaveFile) - // { - // string existing = Path.Combine(path, SAVES_DIRECTORY, platform, "common"); - // if (Directory.Exists(existing)) - // { - // var saves = Directory.GetFiles(existing, "*.sav", SearchOption.TopDirectoryOnly); - // foreach (var file in saves) - // { - // var filename = Path.GetFileName(file); - // var sourceFile = Path.Combine(source, SAVES_DIRECTORY, platform, "common", file); - // if (File.Exists(sourceFile)) - // { - // try - // { - // File.Delete(file); - // } - // catch - // { - // // Ignore - // } - // } - // } - // } - // } - // Clean files. var files = Directory.EnumerateFiles(source).Where(file => IsBadFile(Path.GetFileName(file))); diff --git a/src/helpers/ZipHelper.cs b/src/helpers/ZipHelper.cs index bd17d458..ecaa1c32 100644 --- a/src/helpers/ZipHelper.cs +++ b/src/helpers/ZipHelper.cs @@ -1,86 +1,90 @@ -using System.IO.Compression; - -namespace Pannella.Helpers; - -public class ZipHelper -{ - private static void UpdateProgress(object sender, ZipProgress zipProgress) - { - try - { - _ = Console.WindowWidth; - ConsoleHelper.ShowProgressBar(zipProgress.Processed, zipProgress.Total); - } - catch - { - // Ignore - } - } - - public static void ExtractToDirectory(string zipFile, string destination, bool overwrite = false) - { - Progress _progress = new Progress(); - _progress.ProgressChanged += UpdateProgress; - var stream = new FileStream(zipFile, FileMode.Open); - var zip = new ZipArchive(stream); - zip.ExtractToDirectory(destination, _progress, overwrite); - stream.Close(); - } -} - -public class ZipProgress -{ - public ZipProgress(int total, int processed, string currentItem) - { - Total = total; - Processed = processed; - CurrentItem = currentItem; - } - public int Total { get; } - public int Processed { get; } - public string CurrentItem { get; } -} - -public static class ZipExtension -{ - public static void ExtractToDirectory(this ZipArchive zipFile, string target, IProgress progress) - { - ExtractToDirectory(zipFile, target, progress, overwrite: false); - } - - public static void ExtractToDirectory(this ZipArchive zipFile, string target, IProgress progress, bool overwrite) - { - DirectoryInfo info = Directory.CreateDirectory(target); - string targetPath = info.FullName; - - int count = 0; - foreach (ZipArchiveEntry entry in zipFile.Entries) - { - count++; - string fileDestinationPath = Path.GetFullPath(Path.Combine(targetPath, entry.FullName)); - - if (!fileDestinationPath.StartsWith(targetPath, StringComparison.OrdinalIgnoreCase)) - { - throw new IOException("File is extracting to outside of the folder specified."); - } - - var zipProgress = new ZipProgress(zipFile.Entries.Count, count, entry.FullName); - progress.Report(zipProgress); - - if (Path.GetFileName(fileDestinationPath).Length == 0) - { - if (entry.Length != 0) - { - throw new IOException("Directory entry with data."); - } - - Directory.CreateDirectory(fileDestinationPath); - } - else - { - Directory.CreateDirectory(Path.GetDirectoryName(fileDestinationPath)); - entry.ExtractToFile(fileDestinationPath, overwrite: overwrite); - } - } - } -} \ No newline at end of file +using System.IO.Compression; + +namespace Pannella.Helpers; + +public class ZipHelper +{ + private static void UpdateProgress(object sender, ZipProgress zipProgress) + { + try + { + _ = Console.WindowWidth; + ConsoleHelper.ShowProgressBar(zipProgress.Processed, zipProgress.Total); + } + catch + { + // Ignore + } + } + + public static void ExtractToDirectory(string zipFile, string destination, bool overwrite = false, bool useProgress = true) + { + Progress progress = new Progress(); + + if (useProgress) + { + progress.ProgressChanged += UpdateProgress; + } + + var stream = new FileStream(zipFile, FileMode.Open); + var zip = new ZipArchive(stream); + + zip.ExtractToDirectory(destination, progress, overwrite); + stream.Close(); + } +} + +public class ZipProgress +{ + public ZipProgress(int total, int processed, string currentItem) + { + Total = total; + Processed = processed; + CurrentItem = currentItem; + } + + public int Total { get; } + public int Processed { get; } + public string CurrentItem { get; } +} + +public static class ZipExtension +{ + public static void ExtractToDirectory(this ZipArchive zipFile, string target, IProgress progress, bool overwrite = false) + { + DirectoryInfo info = Directory.CreateDirectory(target); + string targetPath = info.FullName; + int count = 0; + + foreach (ZipArchiveEntry entry in zipFile.Entries) + { + count++; + + string fileDestinationPath = Path.GetFullPath(Path.Combine(targetPath, entry.FullName)); + + if (!fileDestinationPath.StartsWith(targetPath, StringComparison.OrdinalIgnoreCase)) + { + throw new IOException("File is extracting to outside of the folder specified."); + } + + var zipProgress = new ZipProgress(zipFile.Entries.Count, count, entry.FullName); + + progress.Report(zipProgress); + + if (Path.GetFileName(fileDestinationPath).Length == 0) + { + if (entry.Length != 0) + { + throw new IOException("Directory entry with data."); + } + + Directory.CreateDirectory(fileDestinationPath); + } + else + { + Directory.CreateDirectory(Path.GetDirectoryName(fileDestinationPath)!); + entry.ExtractToFile(fileDestinationPath, overwrite: overwrite); + } + } + } +} diff --git a/src/models/Base.cs b/src/models/Base.cs index d5f87adb..3ff38990 100644 --- a/src/models/Base.cs +++ b/src/models/Base.cs @@ -1,3 +1,5 @@ +using Pannella.Models.Events; + namespace Pannella.Models; public class Base diff --git a/src/models/BaseProcess.cs b/src/models/BaseProcess.cs index a66538f5..220cc919 100644 --- a/src/models/BaseProcess.cs +++ b/src/models/BaseProcess.cs @@ -1,3 +1,5 @@ +using Pannella.Models.Events; + namespace Pannella.Models; public abstract class BaseProcess : Base diff --git a/src/models/DisplayModes/DisplayMode.cs b/src/models/DisplayModes/DisplayMode.cs new file mode 100644 index 00000000..e07fcd8f --- /dev/null +++ b/src/models/DisplayModes/DisplayMode.cs @@ -0,0 +1,12 @@ +namespace Pannella.Models.DisplayModes; + +public class DisplayMode +{ + public string value { get; set; } + public string description { get; set; } + + public override string ToString() + { + return value; + } +} diff --git a/src/models/DisplayModes/DisplayModes.cs b/src/models/DisplayModes/DisplayModes.cs new file mode 100644 index 00000000..dedbe768 --- /dev/null +++ b/src/models/DisplayModes/DisplayModes.cs @@ -0,0 +1,6 @@ +namespace Pannella.Models.DisplayModes; + +public class DisplayModes +{ + public Dictionary> display_modes { get; set; } +} diff --git a/src/models/Events/StatusUpdatedEventArgs.cs b/src/models/Events/StatusUpdatedEventArgs.cs index 51ebf498..ce7dfee6 100644 --- a/src/models/Events/StatusUpdatedEventArgs.cs +++ b/src/models/Events/StatusUpdatedEventArgs.cs @@ -1,4 +1,4 @@ -namespace Pannella.Models; +namespace Pannella.Models.Events; public class StatusUpdatedEventArgs : EventArgs { diff --git a/src/models/Events/UpdateProcessCompleteEventArgs.cs b/src/models/Events/UpdateProcessCompleteEventArgs.cs index cc8dfd4f..b81c55a9 100644 --- a/src/models/Events/UpdateProcessCompleteEventArgs.cs +++ b/src/models/Events/UpdateProcessCompleteEventArgs.cs @@ -1,4 +1,4 @@ -namespace Pannella.Models; +namespace Pannella.Models.Events; public class UpdateProcessCompleteEventArgs : EventArgs { @@ -7,6 +7,6 @@ public class UpdateProcessCompleteEventArgs : EventArgs public List InstalledAssets { get; set; } public List SkippedAssets { get; set; } public string FirmwareUpdated { get; set; } = string.Empty; - public List MissingBetaKeys { get; set; } + public List MissingLicenses { get; set; } public bool SkipOutro; } diff --git a/src/models/Extras/PocketExtra.cs b/src/models/Extras/PocketExtra.cs index 6351f8cc..d0f968cd 100644 --- a/src/models/Extras/PocketExtra.cs +++ b/src/models/Extras/PocketExtra.cs @@ -1,33 +1,32 @@ using System.Text.Json.Serialization; -namespace Pannella.Models.Extras +namespace Pannella.Models.Extras; + +public class PocketExtra { - public class PocketExtra - { - public string id { get; set; } + public string id { get; set; } - public string name { get; set; } + public string name { get; set; } - public string description { get; set; } + public string description { get; set; } - [JsonConverter(typeof(JsonStringEnumConverter))] - public PocketExtraType type { get; set; } + [JsonConverter(typeof(JsonStringEnumConverter))] + public PocketExtraType type { get; set; } - public List core_identifiers { get; set; } + public List core_identifiers { get; set; } - public bool has_placeholders { get; set; } + public bool has_placeholders { get; set; } - public string github_user { get; set; } + public string github_user { get; set; } - public string github_repository { get; set; } + public string github_repository { get; set; } - public string github_asset_prefix { get; set; } + public string github_asset_prefix { get; set; } - public List additional_links { get; set; } = new(); + public List additional_links { get; set; } = new(); - public override string ToString() - { - return this.id; - } + public override string ToString() + { + return this.id; } } diff --git a/src/models/IngoreInstanceJson.cs b/src/models/IngoreInstanceJson.cs new file mode 100644 index 00000000..2cec974c --- /dev/null +++ b/src/models/IngoreInstanceJson.cs @@ -0,0 +1,7 @@ +namespace Pannella.Models +{ + public class IgnoreInstanceJson + { + public List core_identifiers { get; set; } + } +} diff --git a/src/models/OpenFPGA_Cores_Inventory/Core.cs b/src/models/OpenFPGA_Cores_Inventory/Core.cs index 114b02dd..c52679b4 100644 --- a/src/models/OpenFPGA_Cores_Inventory/Core.cs +++ b/src/models/OpenFPGA_Cores_Inventory/Core.cs @@ -10,8 +10,9 @@ public class Core public string download_url { get; set; } public string release_date { get; set; } public string version { get; set; } - public string beta_slot_id; - public int beta_slot_platform_id_index; + public string license_slot_id; + public int license_slot_platform_id_index; + public string license_slot_filename; public bool requires_license { get; set; } = false; public override string ToString() diff --git a/src/models/Settings/Archive.cs b/src/models/Settings/Archive.cs index 5acd5c8d..f7b8dc4b 100644 --- a/src/models/Settings/Archive.cs +++ b/src/models/Settings/Archive.cs @@ -1,43 +1,42 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace Pannella.Models.Settings +namespace Pannella.Models.Settings; + +[JsonConverter(typeof(StringEnumConverter))] +public enum ArchiveType { - [JsonConverter(typeof(StringEnumConverter))] - public enum ArchiveType - { - internet_archive, - custom_archive, - core_specific_archive, - } + internet_archive, + custom_archive, + core_specific_archive, +} - public class Archive - { - public string name { get; set; } +public class Archive +{ + public string name { get; set; } - public ArchiveType type { get; set; } + public ArchiveType type { get; set; } - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public string archive_name { get; set; } + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string archive_name { get; set; } - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public string archive_folder { get; set; } + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string archive_folder { get; set; } - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public string url { get; set; } + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string url { get; set; } - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public string index { get; set; } + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string index { get; set; } - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public List file_extensions { get; set; } + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public List file_extensions { get; set; } - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public bool has_instance_jsons { get; set; } + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public bool has_instance_jsons { get; set; } - /// - /// This setting only applies to Core Specific Archives - /// - public bool enabled; - } + /// + /// This setting only applies to Core Specific Archives + /// + public bool enabled; } diff --git a/src/models/Settings/Config.cs b/src/models/Settings/Config.cs index 865472b3..544274c6 100644 --- a/src/models/Settings/Config.cs +++ b/src/models/Settings/Config.cs @@ -16,9 +16,6 @@ public class Config [Description("Preserve 'Platforms' folder during 'Update All'")] public bool preserve_platforms_folder { get; set; } = false; - // [Description("Preserve Save Files during 'Update All'. If a core developer packs a pre-configured save file with their update, this file will be installed on your SD card unless you turn this setting on")] - //public bool preserve_save_files { get; set; } = false; - [Description("Delete untracked cores during 'Update All'")] public bool delete_skipped_cores { get; set; } = true; @@ -50,18 +47,35 @@ public class Config [Description("Automatically install updates to Pupdate")] public bool auto_install_updates { get; set; } = false; + [Description("Coin-Op Collection Beta Access")] + public bool coin_op_beta { get; set; } = false; + public string temp_directory { get; set; } = null; + public string patreon_email_address { get; set; } = null; + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + //[Description("Use a local blacklist.json file")] public bool use_local_blacklist { get; set; } = false; [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + //[Description("Use a local image_packs.json file")] public bool use_local_image_packs { get; set; } = false; - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + //[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + [Description("Use a local pocket_extras.json file")] public bool use_local_pocket_extras { get; set; } = false; + //[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + [Description("Use a local display_modes.json file")] + public bool use_local_display_modes { get; set; } = false; + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + //[Description("Use a local ignore_instance.json file")] + public bool use_local_ignore_instance_json { get; set; } = false; + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + //[Description("Suppress the 'Already Installed' messages for core files and assets")] public bool suppress_already_installed { get; set; } [JsonProperty(ObjectCreationHandling = ObjectCreationHandling.Replace)] diff --git a/src/models/Settings/CoreSettings.cs b/src/models/Settings/CoreSettings.cs index 834b3f06..075263d1 100644 --- a/src/models/Settings/CoreSettings.cs +++ b/src/models/Settings/CoreSettings.cs @@ -13,4 +13,16 @@ public class CoreSettings [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public string pocket_extras_version { get; set; } = null; + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public bool display_modes { get; set; } + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string original_display_modes { get; set; } = null; + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string selected_display_modes { get; set; } = null; + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public bool requires_license { get; set; } = false; } diff --git a/src/models/Updater/License.cs b/src/models/Updater/License.cs new file mode 100644 index 00000000..d2bfa55f --- /dev/null +++ b/src/models/Updater/License.cs @@ -0,0 +1,8 @@ +using System.ComponentModel; + +namespace Pannella.Models.Updater; + +public class License +{ + public string filename { get; set; } +} diff --git a/src/models/Updater/Updaters.cs b/src/models/Updater/Updaters.cs index c30c891a..e4e4244d 100644 --- a/src/models/Updater/Updaters.cs +++ b/src/models/Updater/Updaters.cs @@ -1,6 +1,9 @@ +using System.ComponentModel; + namespace Pannella.Models.Updater; public class Updaters { public Substitute[] previous { get; set; } + public License license { get; set; } } diff --git a/src/partials/Program.DisplayModes.cs b/src/partials/Program.DisplayModes.cs index 30e238e7..3668fc5a 100644 --- a/src/partials/Program.DisplayModes.cs +++ b/src/partials/Program.DisplayModes.cs @@ -4,30 +4,27 @@ namespace Pannella; internal partial class Program { - private static void EnableDisplayModes() + private static void EnableDisplayModes(List coreIdentifiers = null, string[] displayModes = null, bool isCurated = false) { - var cores = ServiceHelper.CoresService.Cores.Where(core => - !ServiceHelper.SettingsService.GetCoreSettings(core.identifier).skip).ToList(); + coreIdentifiers ??= ServiceHelper.CoresService.Cores + .Where(core => !ServiceHelper.SettingsService.GetCoreSettings(core.identifier).skip) + .Select(core => core.identifier) + .ToList(); - foreach (var core in cores) + foreach (var core in coreIdentifiers) { - if (core == null) - { - Console.WriteLine("Core name is required. Skipping"); - return; - } - try { // not sure if this check is still needed - if (core.identifier == null) + if (core == null) { Console.WriteLine("Core Name is required. Skipping."); continue; } - Console.WriteLine("Updating " + core.identifier); - ServiceHelper.CoresService.AddDisplayModes(core.identifier); + Console.WriteLine("Updating " + core); + ServiceHelper.CoresService.AddDisplayModes(core, displayModes, isCurated); + ServiceHelper.SettingsService.Save(); } catch (Exception e) { diff --git a/src/partials/Program.GameAndWatch.cs b/src/partials/Program.GameAndWatch.cs index f94a6abd..ae1e2d1e 100644 --- a/src/partials/Program.GameAndWatch.cs +++ b/src/partials/Program.GameAndWatch.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using System.IO.Compression; using Pannella.Helpers; using Pannella.Models.Github; using Pannella.Services; diff --git a/src/partials/Program.Menus.Cores.cs b/src/partials/Program.Menus.Cores.cs new file mode 100644 index 00000000..bd4fd624 --- /dev/null +++ b/src/partials/Program.Menus.Cores.cs @@ -0,0 +1,148 @@ +using ConsoleTools; +using Pannella.Helpers; +using Pannella.Models.OpenFPGA_Cores_Inventory; + +namespace Pannella; + +internal partial class Program +{ + private static Dictionary ShowCoresMenu(List cores, string message, bool isCoreSelection) + { + const int pageSize = 12; + var offset = 0; + bool more = true; + var results = new Dictionary(); + string save, + quit; + + if (isCoreSelection) + { + save = "Save Choices"; + quit = "Quit without saving"; + } + else + { + save = "Apply Choices"; + quit = "Quit without applying"; + } + + while (more) + { + var menu = new ConsoleMenu() + .Configure(config => + { + config.Selector = "=>"; + config.EnableWriteTitle = false; + config.WriteHeaderAction = () => Console.WriteLine($"{message} Use enter to check/uncheck your choices."); + config.SelectedItemBackgroundColor = Console.ForegroundColor; + config.SelectedItemForegroundColor = Console.BackgroundColor; + config.WriteItemAction = item => Console.Write("{0}", item.Name); + }); + var current = -1; + + if ((offset + pageSize) <= cores.Count) + { + menu.Add("Next Page", thisMenu => + { + offset += pageSize; + thisMenu.CloseMenu(); + }); + } + + foreach (Core core in cores) + { + current++; + + if ((current <= (offset + pageSize)) && (current >= offset)) + { + var coreSettings = ServiceHelper.SettingsService.GetCoreSettings(core.identifier); + var selected = + (isCoreSelection && !coreSettings.skip) || + (results.TryGetValue(core.identifier, out var result) && result); + var name = core.identifier; + var title = MenuItemName(name, selected, core.requires_license); + + menu.Add(title, thisMenu => + { + selected = !selected; + + if (results.ContainsKey(core.identifier)) + { + results[core.identifier] = selected; + } + else + { + results.Add(core.identifier, selected); + } + + thisMenu.CurrentItem.Name = MenuItemName(core.identifier, selected, core.requires_license); + }); + } + } + + if ((offset + pageSize) <= cores.Count) + { + menu.Add("Next Page", thisMenu => + { + offset += pageSize; + thisMenu.CloseMenu(); + }); + } + + if (offset != 0) + { + menu.Add("Prev Page", thisMenu => + { + offset -= pageSize; + thisMenu.CloseMenu(); + }); + } + + menu.Add(save, thisMenu => + { + thisMenu.CloseMenu(); + more = false; + }); + + menu.Add(quit, thisMenu => + { + thisMenu.CloseMenu(); + results.Clear(); + more = false; + }); + + menu.Show(); + } + + return results; + } + + private static Dictionary RunCoreSelector(List cores, string message = "Select your cores.") + { + Dictionary results = null; + + if (ServiceHelper.SettingsService.GetConfig().download_new_cores?.ToLowerInvariant() == "yes") + { + foreach (Core core in cores) + { + ServiceHelper.SettingsService.EnableCore(core.identifier); + } + } + else + { + results = ShowCoresMenu(cores, message, true); + + foreach (var item in results) + { + if (item.Value) + ServiceHelper.SettingsService.EnableCore(item.Key); + else + ServiceHelper.SettingsService.DisableCore(item.Key); + } + } + + ServiceHelper.SettingsService.Save(); + + return results; + } +} diff --git a/src/partials/Program.Menus.DisplayModes.cs b/src/partials/Program.Menus.DisplayModes.cs new file mode 100644 index 00000000..1396c2c8 --- /dev/null +++ b/src/partials/Program.Menus.DisplayModes.cs @@ -0,0 +1,133 @@ +using ConsoleTools; +using Pannella.Helpers; +using Pannella.Models.DisplayModes; + +namespace Pannella; + +internal partial class Program +{ + private static void DisplayModeSelector(bool showCoreSelector = false) + { + Console.Clear(); + + const int pageSize = 12; + var offset = 0; + var more = true; + var count = 0; + var results = new List(); + var allDisplayModes = ServiceHelper.CoresService.GetAllDisplayModes(); + + while (more) + { + var menu = new ConsoleMenu() + .Configure(config => + { + config.Selector = "=>"; + config.EnableWriteTitle = false; + config.WriteHeaderAction = () => + { + Console.WriteLine("Which display modes would you like to enable?"); + Console.WriteLine($"Note: There is a maximum of 16. You have {16 - count} remaining."); + }; + config.SelectedItemBackgroundColor = Console.ForegroundColor; + config.SelectedItemForegroundColor = Console.BackgroundColor; + config.WriteItemAction = item => Console.Write("{0}", item.Name); + }); + var current = -1; + + if ((offset + pageSize) <= allDisplayModes.Count) + { + menu.Add("Next Page", thisMenu => + { + offset += pageSize; + thisMenu.CloseMenu(); + }); + } + + foreach (DisplayMode displayMode in allDisplayModes) + { + current++; + + if ((current <= (offset + pageSize)) && (current >= offset)) + { + var selected = results.Contains(displayMode.value); + var title = MenuItemName(displayMode.description, selected); + + menu.Add(title, thisMenu => + { + if (count >= 16 && !selected) + return; + + selected = !selected; + + if (selected) + { + results.Add(displayMode.value); + count++; + } + else + { + results.Remove(displayMode.value); + count--; + } + + thisMenu.CurrentItem.Name = MenuItemName(displayMode.description, selected); + }); + } + } + + if ((offset + pageSize) <= allDisplayModes.Count) + { + menu.Add("Next Page", thisMenu => + { + offset += pageSize; + thisMenu.CloseMenu(); + }); + } + + if (offset != 0) + { + menu.Add("Prev Page", thisMenu => + { + offset -= pageSize; + thisMenu.CloseMenu(); + }); + } + + if (!showCoreSelector) + { + menu.Add("Apply Choices", thisMenu => + { + EnableDisplayModes(displayModes: results.ToArray()); + thisMenu.CloseMenu(); + more = false; + }); + } + else + { + menu.Add("Select Cores", thisMenu => + { + thisMenu.CloseMenu(); + more = false; + + var coreResults = ShowCoresMenu( + ServiceHelper.CoresService.InstalledCores, + "Which cores would you like to apply the selected display modes to?", + false); + + EnableDisplayModes( + coreResults.Where(kvp => kvp.Value).Select(kvp => kvp.Key).ToList(), + results.ToArray()); + }); + } + + menu.Add("Quit without applying", thisMenu => + { + thisMenu.CloseMenu(); + more = false; + }); + + menu.Show(); + } + } +} diff --git a/src/partials/Program.Menus.PlatformImagePacks.cs b/src/partials/Program.Menus.PlatformImagePacks.cs new file mode 100644 index 00000000..c3e14776 --- /dev/null +++ b/src/partials/Program.Menus.PlatformImagePacks.cs @@ -0,0 +1,63 @@ +using ConsoleTools; +using Pannella.Helpers; + +namespace Pannella; + +internal partial class Program +{ + private static void PlatformImagePackSelector() + { + Console.Clear(); + + if (ServiceHelper.PlatformImagePacksService.List.Count > 0) + { + int choice = 0; + var menu = new ConsoleMenu() + .Configure(config => + { + config.Selector = "=>"; + config.EnableWriteTitle = false; + config.WriteHeaderAction = () => Console.WriteLine("So, what'll it be?:"); + config.SelectedItemBackgroundColor = Console.ForegroundColor; + config.SelectedItemForegroundColor = Console.BackgroundColor; + }); + + foreach (var pack in ServiceHelper.PlatformImagePacksService.List) + { + menu.Add($"{pack.owner}: {pack.repository} {pack.variant ?? string.Empty}", thisMenu => + { + choice = thisMenu.CurrentItem.Index; + thisMenu.CloseMenu(); + }); + } + + menu.Add("Go Back", thisMenu => + { + choice = ServiceHelper.PlatformImagePacksService.List.Count; + thisMenu.CloseMenu(); + }); + + menu.Show(); + + if (choice < ServiceHelper.PlatformImagePacksService.List.Count && choice >= 0) + { + ServiceHelper.PlatformImagePacksService.Install( + ServiceHelper.PlatformImagePacksService.List[choice].owner, + ServiceHelper.PlatformImagePacksService.List[choice].repository, + ServiceHelper.PlatformImagePacksService.List[choice].variant); + } + else if (choice == ServiceHelper.PlatformImagePacksService.List.Count) + { + // What causes this? + } + else + { + Console.WriteLine("You fucked up."); + } + } + else + { + Console.WriteLine("None found. Have a nice day."); + } + } +} diff --git a/src/partials/Program.Menus.Questions.cs b/src/partials/Program.Menus.Questions.cs new file mode 100644 index 00000000..abaf914e --- /dev/null +++ b/src/partials/Program.Menus.Questions.cs @@ -0,0 +1,45 @@ +using Pannella.Helpers; + +namespace Pannella; + +internal partial class Program +{ + private static void AskAboutNewCores(bool force = false) + { + while (ServiceHelper.SettingsService.GetConfig().download_new_cores == null || force) + { + force = false; + + Console.WriteLine("Would you like to, by default, install new cores? [Y]es, [N]o, [A]sk for each:"); + + ConsoleKey response = Console.ReadKey(true).Key; + + ServiceHelper.SettingsService.GetConfig().download_new_cores = response switch + { + ConsoleKey.Y => "yes", + ConsoleKey.N => "no", + ConsoleKey.A => "ask", + _ => null + }; + } + } + + private static bool AskYesNoQuestion(string question) + { + Console.WriteLine($"{question} [Y]es, [N]o"); + + bool? result = null; + + while (result == null) + { + result = Console.ReadKey(true).Key switch + { + ConsoleKey.Y => true, + ConsoleKey.N => false, + _ => null + }; + } + + return result.Value; + } +} diff --git a/src/partials/Program.Menus.Settings.cs b/src/partials/Program.Menus.Settings.cs new file mode 100644 index 00000000..14490c4b --- /dev/null +++ b/src/partials/Program.Menus.Settings.cs @@ -0,0 +1,51 @@ +using System.ComponentModel; +using ConsoleTools; +using Pannella.Helpers; +using Pannella.Models.Settings; + +namespace Pannella; + +internal partial class Program +{ + private static void SettingsMenu() + { + Console.Clear(); + + var type = typeof(Config); + var menuItems = + from property in type.GetProperties() + let attribute = property.GetCustomAttributes(typeof(DescriptionAttribute), true) + where attribute.Length == 1 + select (property.Name, ((DescriptionAttribute)attribute[0]).Description); + var menu = new ConsoleMenu() + .Configure(config => + { + config.Selector = "=>"; + config.EnableWriteTitle = false; + config.WriteHeaderAction = () => Console.WriteLine("Settings. Use enter to check/uncheck your choices."); + config.SelectedItemBackgroundColor = Console.ForegroundColor; + config.SelectedItemForegroundColor = Console.BackgroundColor; + config.WriteItemAction = item => Console.Write("{0}", item.Name); + }); + + foreach (var (name, text) in menuItems) + { + var property = type.GetProperty(name); + var value = (bool)property!.GetValue(ServiceHelper.SettingsService.GetConfig())!; + var title = MenuItemName(text, value); + + menu.Add(title, thisMenu => + { + value = !value; + property.SetValue(ServiceHelper.SettingsService.GetConfig(), value); + thisMenu.CurrentItem.Name = MenuItemName(text, value); + }); + } + + menu.Add("Save", thisMenu => { thisMenu.CloseMenu(); }); + + menu.Show(); + + ServiceHelper.SettingsService.Save(); + } +} diff --git a/src/partials/Program.Menus.cs b/src/partials/Program.Menus.cs index 619ebfbe..6f6b0b25 100644 --- a/src/partials/Program.Menus.cs +++ b/src/partials/Program.Menus.cs @@ -1,9 +1,6 @@ -using System.ComponentModel; using ConsoleTools; using Pannella.Helpers; using Pannella.Models.Extras; -using Pannella.Models.OpenFPGA_Cores_Inventory; -using Pannella.Models.Settings; using Pannella.Services; namespace Pannella; @@ -42,16 +39,27 @@ private static void DisplayMenu(CoreUpdaterService coreUpdaterService) SelectedItemForegroundColor = Console.BackgroundColor, }; - var pocketSetupMenu = new ConsoleMenu() + var displayModesMenu = new ConsoleMenu() .Configure(menuConfig) - .Add("Jotego Analogizer Config", _=> + .Add("Enable Recommended Display Modes", () => { - AnalogizerSettingsService settings = new AnalogizerSettingsService(); - settings.RunAnalogizerSettings(); - - Console.WriteLine("Analogizer configuration updated."); + EnableDisplayModes(isCurated: true); + Pause(); + }) + .Add("Enable Selected Display Modes", () => + { + DisplayModeSelector(); + Pause(); + }) + .Add("Enable Selected Display Modes for Selected Cores", () => + { + DisplayModeSelector(true); Pause(); }) + .Add("Go Back", ConsoleMenu.Close); + + var downloadFilesMenu = new ConsoleMenu() + .Configure(menuConfig) .Add("Download Platform Image Packs", _ => { PlatformImagePackSelector(); @@ -67,6 +75,10 @@ private static void DisplayMenu(CoreUpdaterService coreUpdaterService) DownloadGameBoyPalettes(); Pause(); }) + .Add("Go Back", ConsoleMenu.Close); + + var generateFilesMenu = new ConsoleMenu() + .Configure(menuConfig) .Add("Generate Instance JSON Files (PC Engine CD)", () => { RunInstanceGenerator(coreUpdaterService); @@ -77,15 +89,15 @@ private static void DisplayMenu(CoreUpdaterService coreUpdaterService) BuildGameAndWatchRoms(); Pause(); }) - .Add("Enable All Display Modes", () => - { - EnableDisplayModes(); - Pause(); - }) + .Add("Go Back", ConsoleMenu.Close); + + var sgbAspectRatioMenu = new ConsoleMenu() + .Configure(menuConfig) .Add("Apply 8:7 Aspect Ratio to Super GameBoy cores", () => { var results = ShowCoresMenu( - ServiceHelper.CoresService.InstalledCores.Where(c => c.identifier.StartsWith("Spiritualized.SuperGB")).ToList(), + ServiceHelper.CoresService.InstalledCores + .Where(c => c.identifier.StartsWith("Spiritualized.SuperGB")).ToList(), "Which Super GameBoy cores would you like to change to the 8:7 aspect ratio?\n", false); @@ -104,7 +116,8 @@ private static void DisplayMenu(CoreUpdaterService coreUpdaterService) .Add("Restore 4:3 Aspect Ratio to Super GameBoy cores", () => { var results = ShowCoresMenu( - ServiceHelper.CoresService.InstalledCores.Where(c => c.identifier.StartsWith("Spiritualized.SuperGB")).ToList(), + ServiceHelper.CoresService.InstalledCores + .Where(c => c.identifier.StartsWith("Spiritualized.SuperGB")).ToList(), "Which Super GameBoy cores would you like to change to the 8:7 aspect ratio?\n", false); @@ -122,8 +135,57 @@ private static void DisplayMenu(CoreUpdaterService coreUpdaterService) }) .Add("Go Back", ConsoleMenu.Close); + var pocketSetupMenu = new ConsoleMenu() + .Configure(menuConfig) + .Add("Apply Display Modes >", displayModesMenu.Show) + .Add("Download Images and Palettes >", downloadFilesMenu.Show) + .Add("Generate ROMs & JSON Files >", generateFilesMenu.Show) + .Add("Super GameBoy Aspect Ratio >", sgbAspectRatioMenu.Show) + .Add("Jotego Analogizer Config", _=> + { + AnalogizerSettingsService settings = new AnalogizerSettingsService(); + settings.RunAnalogizerSettings(); + + Console.WriteLine("Analogizer configuration updated."); + Pause(); + }) + .Add("Set Patreon Email Address", () => + { + Console.WriteLine($"Current email address: {ServiceHelper.SettingsService.GetConfig().patreon_email_address}"); + var result = AskYesNoQuestion("Would you like to change your address?"); + + if (!result) + return; + + string input = PromptForInput(); + ServiceHelper.SettingsService.GetConfig().patreon_email_address = input; + ServiceHelper.SettingsService.Save(); + + Pause(); + }) + .Add("Go Back", ConsoleMenu.Close); + var pocketMaintenanceMenu = new ConsoleMenu() .Configure(menuConfig) + .Add("Update Selected", _ => + { + var selectedCores = ShowCoresMenu( + ServiceHelper.CoresService.InstalledCores, + "Which cores would you like to update?", + false); + + coreUpdaterService.RunUpdates(selectedCores.Where(kvp => kvp.Value).Select(kvp => kvp.Key).ToArray()); + Pause(); + }) + .Add("Install Selected", _ => + { + var selectedCores = RunCoreSelector( + ServiceHelper.CoresService.CoresNotInstalled, + "Which cores would you like to install?"); + + coreUpdaterService.RunUpdates(selectedCores.Where(kvp => kvp.Value).Select(kvp => kvp.Key).ToArray()); + Pause(); + }) .Add("Reinstall All Cores", _ => { coreUpdaterService.RunUpdates(null, true); @@ -135,9 +197,10 @@ private static void DisplayMenu(CoreUpdaterService coreUpdaterService) ServiceHelper.CoresService.InstalledCores, "Which cores would you like to reinstall?", false); - var identifiers = results.Where(x => x.Value) - .Select(x => x.Key) - .ToArray(); + var identifiers = results + .Where(x => x.Value) + .Select(x => x.Key) + .ToArray(); if (identifiers.Length > 0) { @@ -166,28 +229,6 @@ private static void DisplayMenu(CoreUpdaterService coreUpdaterService) Pause(); } }) - .Add("Remove JT Beta Keys", () => - { - Console.WriteLine($"WARNING: This will delete all instances of {CoresService.BETA_KEY_FILENAME} and {CoresService.BETA_KEY_ALT_FILENAME}"); - var result = AskYesNoQuestion("Would you like to continue?"); - - if (!result) - return; - - var binFiles = Directory.EnumerateFiles(ServiceHelper.UpdateDirectory, CoresService.BETA_KEY_ALT_FILENAME, - SearchOption.AllDirectories); - var zipFiles = Directory.EnumerateFiles(ServiceHelper.UpdateDirectory, CoresService.BETA_KEY_FILENAME, - SearchOption.AllDirectories); - var allFiles = zipFiles.Union(binFiles); - - foreach (var file in allFiles) - { - Console.WriteLine($"Deleting '{file}'..."); - File.Delete(file); - } - - Pause(); - }) .Add("Prune Save States", _ => { AssetsService.PruneSaveStates(ServiceHelper.UpdateDirectory); @@ -306,246 +347,13 @@ private static void DisplayMenu(CoreUpdaterService coreUpdaterService) menu.Show(); } - private static void AskAboutNewCores(bool force = false) + private static string PromptForInput() { - while (ServiceHelper.SettingsService.GetConfig().download_new_cores == null || force) - { - force = false; - - Console.WriteLine("Would you like to, by default, install new cores? [Y]es, [N]o, [A]sk for each:"); - - ConsoleKey response = Console.ReadKey(true).Key; + Console.Write("Enter value: "); - ServiceHelper.SettingsService.GetConfig().download_new_cores = response switch - { - ConsoleKey.Y => "yes", - ConsoleKey.N => "no", - ConsoleKey.A => "ask", - _ => null - }; - } - } - - private static bool AskYesNoQuestion(string question) - { - Console.WriteLine($"{question} [Y]es, [N]o"); - - bool? result = null; - - while (result == null) - { - result = Console.ReadKey(true).Key switch - { - ConsoleKey.Y => true, - ConsoleKey.N => false, - _ => null - }; - } - - return result.Value; - } - - private static Dictionary ShowCoresMenu(List cores, string message, bool isCoreSelection) - { - const int pageSize = 15; - var offset = 0; - bool more = true; - var results = new Dictionary(); - - while (more) - { - var menu = new ConsoleMenu() - .Configure(config => - { - config.Selector = "=>"; - config.EnableWriteTitle = false; - config.WriteHeaderAction = () => Console.WriteLine($"{message} Use enter to check/uncheck your choices."); - config.SelectedItemBackgroundColor = Console.ForegroundColor; - config.SelectedItemForegroundColor = Console.BackgroundColor; - config.WriteItemAction = item => Console.Write("{0}", item.Name); - }); - var current = -1; - - if ((offset + pageSize) <= cores.Count) - { - menu.Add("Next Page", thisMenu => - { - offset += pageSize; - thisMenu.CloseMenu(); - }); - } - - foreach (Core core in cores) - { - current++; - - if ((current <= (offset + pageSize)) && (current >= offset)) - { - var coreSettings = ServiceHelper.SettingsService.GetCoreSettings(core.identifier); - var selected = isCoreSelection && !coreSettings.skip; - var name = core.identifier; - var title = MenuItemName(name, selected, core.requires_license); - - menu.Add(title, thisMenu => - { - selected = !selected; - - if (results.ContainsKey(core.identifier)) - { - results[core.identifier] = selected; - } - else - { - results.Add(core.identifier, selected); - } - - thisMenu.CurrentItem.Name = MenuItemName(core.identifier, selected, core.requires_license); - }); - } - } - - if ((offset + pageSize) <= cores.Count) - { - menu.Add("Next Page", thisMenu => - { - offset += pageSize; - thisMenu.CloseMenu(); - }); - } - - menu.Add("Save Choices", thisMenu => - { - thisMenu.CloseMenu(); - more = false; - }); - - menu.Show(); - } - - return results; - } - - private static void RunCoreSelector(List cores, string message = "Select your cores.") - { - if (ServiceHelper.SettingsService.GetConfig().download_new_cores?.ToLowerInvariant() == "yes") - { - foreach (Core core in cores) - { - ServiceHelper.SettingsService.EnableCore(core.identifier); - } - } - else - { - var results = ShowCoresMenu(cores, message, true); - - foreach (var item in results) - { - if (item.Value) - ServiceHelper.SettingsService.EnableCore(item.Key); - else - ServiceHelper.SettingsService.DisableCore(item.Key); - } - } - - ServiceHelper.SettingsService.Save(); - } - - private static void PlatformImagePackSelector() - { - Console.Clear(); - - if (ServiceHelper.PlatformImagePacksService.List.Count > 0) - { - int choice = 0; - var menu = new ConsoleMenu() - .Configure(config => - { - config.Selector = "=>"; - config.EnableWriteTitle = false; - config.WriteHeaderAction = () => Console.WriteLine("So, what'll it be?:"); - config.SelectedItemBackgroundColor = Console.ForegroundColor; - config.SelectedItemForegroundColor = Console.BackgroundColor; - }); - - foreach (var pack in ServiceHelper.PlatformImagePacksService.List) - { - menu.Add($"{pack.owner}: {pack.repository} {pack.variant ?? string.Empty}", thisMenu => - { - choice = thisMenu.CurrentItem.Index; - thisMenu.CloseMenu(); - }); - } - - menu.Add("Go Back", thisMenu => - { - choice = ServiceHelper.PlatformImagePacksService.List.Count; - thisMenu.CloseMenu(); - }); - - menu.Show(); - - if (choice < ServiceHelper.PlatformImagePacksService.List.Count && choice >= 0) - { - ServiceHelper.PlatformImagePacksService.Install( - ServiceHelper.PlatformImagePacksService.List[choice].owner, - ServiceHelper.PlatformImagePacksService.List[choice].repository, - ServiceHelper.PlatformImagePacksService.List[choice].variant); - } - else if (choice == ServiceHelper.PlatformImagePacksService.List.Count) - { - // What causes this? - } - else - { - Console.WriteLine("You fucked up."); - } - } - else - { - Console.WriteLine("None found. Have a nice day."); - } - } - - private static void SettingsMenu() - { - Console.Clear(); - - var type = typeof(Config); - var menuItems = - from property in type.GetProperties() - let attribute = property.GetCustomAttributes(typeof(DescriptionAttribute), true) - where attribute.Length == 1 - select (property.Name, ((DescriptionAttribute)attribute[0]).Description); - var menu = new ConsoleMenu() - .Configure(config => - { - config.Selector = "=>"; - config.EnableWriteTitle = false; - config.WriteHeaderAction = () => Console.WriteLine("Settings. Use enter to check/uncheck your choices."); - config.SelectedItemBackgroundColor = Console.ForegroundColor; - config.SelectedItemForegroundColor = Console.BackgroundColor; - config.WriteItemAction = item => Console.Write("{0}", item.Name); - }); - - foreach (var (name, text) in menuItems) - { - var property = type.GetProperty(name); - var value = (bool)property!.GetValue(ServiceHelper.SettingsService.GetConfig())!; - var title = MenuItemName(text, value); - - menu.Add(title, thisMenu => - { - value = !value; - property.SetValue(ServiceHelper.SettingsService.GetConfig(), value); - thisMenu.CurrentItem.Name = MenuItemName(text, value); - }); - } - - menu.Add("Save", thisMenu => { thisMenu.CloseMenu(); }); - - menu.Show(); + string value = Console.ReadLine(); - ServiceHelper.SettingsService.Save(); + return value; } private static string MenuItemName(string title, bool value, bool requiresLicense = false) diff --git a/src/partials/Program.PocketLibraryImages.cs b/src/partials/Program.PocketLibraryImages.cs index 249fa281..ec09f173 100644 --- a/src/partials/Program.PocketLibraryImages.cs +++ b/src/partials/Program.PocketLibraryImages.cs @@ -1,4 +1,3 @@ -using System.IO.Compression; using Pannella.Helpers; using Pannella.Models.Settings; using ArchiveFile = Pannella.Models.Archive.File; diff --git a/src/services/AnalogizerSettingsService.cs b/src/services/AnalogizerSettingsService.cs index 02454a84..b514c09c 100644 --- a/src/services/AnalogizerSettingsService.cs +++ b/src/services/AnalogizerSettingsService.cs @@ -22,7 +22,7 @@ public AnalogizerOptionType(bool exp1 = true, string[] skp = null) public string GetInput() { Console.WriteLine(Options); - string input = Console.ReadLine().ToLower(); + string input = Console.ReadLine()!.ToLower(); string notFound = ""; if (Expect1 && input.Length > 1) @@ -91,9 +91,9 @@ public static void GenerateUserOptions(AnalogizerOptionType[] records, int wLen { if (file == null) continue; File.WriteAllBytes(file, Enumerable.Range(0, hexStr.Length) - .Where(x => x % 2 == 0) - .Select(x => Convert.ToByte(hexStr.Substring(x, 2), 16)) - .ToArray()); + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(hexStr.Substring(x, 2), 16)) + .ToArray()); } } } @@ -103,6 +103,7 @@ public class AnalogizerSettingsService public void RunAnalogizerSettings() { var crt = new AnalogizerOptionType(); + crt.Options = @" Please, select your preferred Video Option. For example: A for RGB video @@ -153,6 +154,7 @@ public void RunAnalogizerSettings() }; var snac = new AnalogizerOptionType(); + snac.Options = @" Please, select the option corresponding to your controller. @@ -183,21 +185,26 @@ public void RunAnalogizerSettings() { "a", "" }, { "f", "ec" }, { "d", "cb" }, - {"e", "eb"} + { "e", "eb" } }; - string filename = "crtcfg.bin"; + const string filename = "crtcfg.bin"; string filepath = Path.Combine(ServiceHelper.UpdateDirectory, filename); + UserOptions.GenerateUserOptions(new AnalogizerOptionType[] { crt, snac }, filename: filepath, filename2: filepath); + if (File.Exists(filepath)) { string jtpatreon = Path.Combine(ServiceHelper.UpdateDirectory, "Assets", "jtpatreon", "common"); + if (!Directory.Exists(jtpatreon)) { Directory.CreateDirectory(jtpatreon); } + string destPath = Path.Combine(jtpatreon, filename); + File.Move(filepath, destPath, true); } } -} \ No newline at end of file +} diff --git a/src/services/AssetsService.cs b/src/services/AssetsService.cs index f8e27855..b4b946aa 100644 --- a/src/services/AssetsService.cs +++ b/src/services/AssetsService.cs @@ -9,8 +9,7 @@ namespace Pannella.Services; public class AssetsService { - private const string BLACKLIST = - "https://raw.githubusercontent.com/mattpannella/pupdate/main/blacklist.json"; + private const string BLACKLIST = "https://raw.githubusercontent.com/mattpannella/pupdate/main/blacklist.json"; private readonly bool useLocalBlacklist; private List blacklist; @@ -104,35 +103,31 @@ string backupLocation private static string ComputeDirectoryHash(string directoryPath) { - using (var sha256 = SHA256.Create()) - { - var allFiles = Directory - .GetFiles(directoryPath, "*.*", SearchOption.AllDirectories) - .OrderBy(p => p) - .ToList(); + using var sha256 = SHA256.Create(); - var hashBuilder = new StringBuilder(); + var allFiles = Directory + .GetFiles(directoryPath, "*.*", SearchOption.AllDirectories) + .OrderBy(p => p) + .ToList(); - foreach (var filePath in allFiles) - { - byte[] fileBytes = File.ReadAllBytes(filePath); - byte[] hashBytes = sha256.ComputeHash(fileBytes); + var hashBuilder = new StringBuilder(); - hashBuilder.Append( - BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant() - ); - } + foreach (var filePath in allFiles) + { + byte[] fileBytes = File.ReadAllBytes(filePath); + byte[] hashBytes = sha256.ComputeHash(fileBytes); - byte[] finalHashBytes = sha256.ComputeHash( - Encoding.UTF8.GetBytes(hashBuilder.ToString()) - ); - return BitConverter.ToString(finalHashBytes).Replace("-", "").ToLowerInvariant(); + hashBuilder.Append(BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant()); } + + byte[] finalHashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(hashBuilder.ToString())); + + return BitConverter.ToString(finalHashBytes).Replace("-", "").ToLowerInvariant(); } public static void PruneSaveStates(string rootDirectory, string coreName = null) { - AssetsService.BackupMemories(ServiceHelper.UpdateDirectory, ServiceHelper.SettingsService.GetConfig().backup_saves_location); + BackupMemories(ServiceHelper.UpdateDirectory, ServiceHelper.SettingsService.GetConfig().backup_saves_location); string savesPath = Path.Combine(rootDirectory, "Memories", "Save States"); //YYYYMMDD_HHMMSS_SOMETHING_SOMETHING_GAMETITLE.STA @@ -182,7 +177,7 @@ public static void PruneSaveStates(string rootDirectory, string coreName = null) // Match the filename with the regex Match match = regex.Match(fileName); - + if (match.Success) { // Extract the game name (Group 2) @@ -198,6 +193,7 @@ public static void PruneSaveStates(string rootDirectory, string coreName = null) } } } + Console.WriteLine("Pruning completed. Most recent save state for each game retained."); } } diff --git a/src/services/CoinOpService.cs b/src/services/CoinOpService.cs new file mode 100644 index 00000000..9e4e90af --- /dev/null +++ b/src/services/CoinOpService.cs @@ -0,0 +1,40 @@ +using System.Net; +using System.Net.Http.Headers; +using Newtonsoft.Json; +using Pannella.Helpers; + +namespace Pannella.Services; + +public static class CoinOpService +{ + private const string LICENSE_ENDPOINT = "https://key.coinopcollection.org/?username={0}"; + + public static byte[] FetchLicense(string email) + { + var client = new HttpClient(); + + string url = String.Format(LICENSE_ENDPOINT, System.Web.HttpUtility.UrlEncode(email)); + var request = new HttpRequestMessage + { + Method = HttpMethod.Get, + RequestUri = new Uri(url) + }; + + var agent = new ProductInfoHeaderValue("Pupdate", "1.0"); + + request.Headers.UserAgent.Add(agent); + + var response = client.Send(request); + + if (response.StatusCode == HttpStatusCode.NotFound) { + var responseBody = response.Content.ReadAsStringAsync().Result; + throw new Exception(responseBody); + } else if (response.StatusCode != HttpStatusCode.OK) { + throw new Exception("Didn't work"); + } + + var bytes = response.Content.ReadAsByteArrayAsync().Result; + + return bytes; + } +} diff --git a/src/services/CoreUpdaterService.cs b/src/services/CoreUpdaterService.cs index c9aab760..235b8823 100644 --- a/src/services/CoreUpdaterService.cs +++ b/src/services/CoreUpdaterService.cs @@ -1,6 +1,7 @@ using Newtonsoft.Json; using Pannella.Helpers; using Pannella.Models; +using Pannella.Models.Events; using Pannella.Models.Extras; using Pannella.Models.OpenFPGA_Cores_Inventory; using File = System.IO.File; @@ -53,7 +54,7 @@ public void RunUpdates(string[] ids = null, bool clean = false) List> installed = new List>(); List installedAssets = new List(); List skippedAssets = new List(); - List missingBetaKeys = new List(); + List missingLicenses = new List(); string firmwareDownloaded = null; if (this.settingsService.GetConfig().backup_saves) @@ -76,7 +77,7 @@ public void RunUpdates(string[] ids = null, bool clean = false) Divide(); } - bool jtBetaKeyExists = this.coresService.ExtractBetaKey(); + this.coresService.RetrieveKeys(); foreach (var core in this.cores.Where(core => ids == null || ids.Any(id => id == core.identifier))) { @@ -90,9 +91,14 @@ public void RunUpdates(string[] ids = null, bool clean = false) continue; } - if (core.requires_license && !jtBetaKeyExists) + var requiresLicense = this.coresService.RequiresLicense(core.identifier); + core.license_slot_filename = requiresLicense.Item4; + core.license_slot_id = requiresLicense.Item2; + core.license_slot_platform_id_index = requiresLicense.Item3; + + if (core.requires_license && !this.coresService.GrossCheck(core)) { - missingBetaKeys.Add(core.identifier); + missingLicenses.Add(core.identifier); continue; // skip if you don't have the key } @@ -125,22 +131,18 @@ public void RunUpdates(string[] ids = null, bool clean = false) { WriteMessage("No releases found. Skipping."); - var isBetaCore = this.coresService.IsBetaCore(core.identifier); - - if (isBetaCore.Item1) + if (requiresLicense.Item1) { - core.beta_slot_id = isBetaCore.Item2; - core.beta_slot_platform_id_index = isBetaCore.Item3; - this.coresService.CopyBetaKey(core); + this.coresService.CopyLicense(core); } results = this.coresService.DownloadAssets(core); installedAssets.AddRange(results["installed"] as List); skippedAssets.AddRange(results["skipped"] as List); - if ((bool)results["missingBetaKey"]) + if ((bool)results["missingLicense"]) { - missingBetaKeys.Add(core.identifier); + missingLicenses.Add(core.identifier); } JotegoRename(core); @@ -168,13 +170,9 @@ public void RunUpdates(string[] ids = null, bool clean = false) } else { - var isBetaCore = this.coresService.IsBetaCore(core.identifier); - - if (isBetaCore.Item1) + if (requiresLicense.Item1) { - core.beta_slot_id = isBetaCore.Item2; - core.beta_slot_platform_id_index = isBetaCore.Item3; - this.coresService.CopyBetaKey(core); + this.coresService.CopyLicense(core); } if (coreSettings.pocket_extras && @@ -206,9 +204,9 @@ public void RunUpdates(string[] ids = null, bool clean = false) installedAssets.AddRange(results["installed"] as List); skippedAssets.AddRange(results["skipped"] as List); - if ((bool)results["missingBetaKey"]) + if ((bool)results["missingLicense"]) { - missingBetaKeys.Add(core.identifier); + missingLicenses.Add(core.identifier); } WriteMessage("Up to date. Skipping core."); @@ -271,22 +269,23 @@ public void RunUpdates(string[] ids = null, bool clean = false) JotegoRename(core); - var isJtBetaCore = this.coresService.IsBetaCore(core.identifier); + var isJtBetaCore = this.coresService.RequiresLicense(core.identifier); if (isJtBetaCore.Item1) { - core.beta_slot_id = isJtBetaCore.Item2; - core.beta_slot_platform_id_index = isJtBetaCore.Item3; - this.coresService.CopyBetaKey(core); + core.license_slot_id = isJtBetaCore.Item2; + core.license_slot_platform_id_index = isJtBetaCore.Item3; + core.license_slot_filename = isJtBetaCore.Item4; + this.coresService.CopyLicense(core); } results = this.coresService.DownloadAssets(core); installedAssets.AddRange(results["installed"] as List); skippedAssets.AddRange(results["skipped"] as List); - if ((bool)results["missingBetaKey"]) + if ((bool)results["missingLicense"]) { - missingBetaKeys.Add(core.identifier); + missingLicenses.Add(core.identifier); } WriteMessage("Installation complete."); @@ -303,7 +302,6 @@ public void RunUpdates(string[] ids = null, bool clean = false) } } - this.coresService.DeleteBetaKey(); this.coresService.RefreshLocalCores(); this.coresService.RefreshInstalledCores(); @@ -313,7 +311,7 @@ public void RunUpdates(string[] ids = null, bool clean = false) InstalledCores = installed, InstalledAssets = installedAssets, SkippedAssets = skippedAssets, - MissingBetaKeys = missingBetaKeys, + MissingLicenses = missingLicenses, FirmwareUpdated = firmwareDownloaded, SkipOutro = false, }; @@ -352,10 +350,12 @@ public void DeleteCore(Core core, bool force = false, bool nuke = false) { var analogueCore = this.coresService.ReadCoreJson(core.identifier); - core.platform_id = analogueCore.metadata.platform_ids[0]; + core.platform_id = analogueCore?.metadata.platform_ids[0]; } - if (this.settingsService.GetConfig().delete_skipped_cores || force) + // If the platform id is still missing, it's a pocket extra that was already deleted, so skip it. + if (!string.IsNullOrEmpty(core.platform_id) && + (this.settingsService.GetConfig().delete_skipped_cores || force)) { this.coresService.Uninstall(core.identifier, core.platform_id, nuke); } @@ -363,7 +363,7 @@ public void DeleteCore(Core core, bool force = false, bool nuke = false) public void ReloadSettings() { - this.settingsService = ServiceHelper.SettingsService;; + this.settingsService = ServiceHelper.SettingsService; this.coresService = ServiceHelper.CoresService; } } diff --git a/src/services/CoresService.DisplayModes.cs b/src/services/CoresService.DisplayModes.cs new file mode 100644 index 00000000..10740c87 --- /dev/null +++ b/src/services/CoresService.DisplayModes.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json; +using Pannella.Helpers; +using Pannella.Models.DisplayModes; + +namespace Pannella.Services; + +public partial class CoresService +{ + private const string DISPLAY_MODES_END_POINT = "https://raw.githubusercontent.com/mattpannella/pupdate/main/display_modes.json"; + + private Dictionary> displayModesList; + + public Dictionary> DisplayModes + { + get { return displayModesList ??= GetDisplayModesList(); } + } + + private Dictionary> GetDisplayModesList() + { +#if DEBUG + string json = File.ReadAllText("display_modes.json"); +#else + string json = this.settingsService.GetConfig().use_local_display_modes + ? File.ReadAllText("display_modes.json") + : HttpHelper.Instance.GetHTML(DISPLAY_MODES_END_POINT); +#endif + + DisplayModes localDisplayModes = JsonConvert.DeserializeObject(json); + + return localDisplayModes.display_modes; + } + + public List GetAllDisplayModes() + { + List displayModes = new List(); + + foreach (string key in this.DisplayModes.Keys) + { + displayModes.AddRange(this.DisplayModes[key]); + } + + return displayModes; + } +} diff --git a/src/services/CoresService.Download.cs b/src/services/CoresService.Download.cs index 7e79ef80..9d324d5c 100644 --- a/src/services/CoresService.Download.cs +++ b/src/services/CoresService.Download.cs @@ -6,6 +6,7 @@ using Pannella.Models.Analogue.Data; using Pannella.Models.Analogue.Instance; using Pannella.Models.Analogue.Instance.Simple; +using Pannella.Models.Events; using Pannella.Models.InstancePackager; using Pannella.Models.OpenFPGA_Cores_Inventory; using Pannella.Models.Settings; @@ -23,7 +24,7 @@ public void DownloadCoreAssets(List cores) { List installedAssets = new List(); List skippedAssets = new List(); - List missingBetaKeys = new List(); + List missingLicenses = new List(); if (cores == null) { @@ -50,9 +51,9 @@ public void DownloadCoreAssets(List cores) installedAssets.AddRange((List)results["installed"]); skippedAssets.AddRange((List)results["skipped"]); - if ((bool)results["missingBetaKey"]) + if ((bool)results["missingLicense"]) { - missingBetaKeys.Add(core.identifier); + missingLicenses.Add(core.identifier); } Divide(); @@ -73,7 +74,7 @@ public void DownloadCoreAssets(List cores) Message = "All Done", InstalledAssets = installedAssets, SkippedAssets = skippedAssets, - MissingBetaKeys = missingBetaKeys, + MissingLicenses = missingLicenses, SkipOutro = false, }; @@ -84,28 +85,32 @@ public Dictionary DownloadAssets(Core core, bool ignoreGlobalSet { List installed = new List(); List skipped = new List(); - bool missingBetaKey = false; - bool run = false; - - //run if: - //globaloverride is on and core specific is on - //or - //globaloverride is off, global setting is on, and core specific is on - if ((ignoreGlobalSetting && this.settingsService.GetCoreSettings(core.identifier).download_assets) - || (!ignoreGlobalSetting && this.settingsService.GetConfig().download_assets && this.settingsService.GetCoreSettings(core.identifier).download_assets)) { - run = true; + bool missingLicense = false; + + //dynamically add the license file to the blacklist so we dont try to download it + if (core.license_slot_filename != null) + { + this.assetsService.Blacklist.Add(core.license_slot_filename); } + // run if: + // global override is on and core specific is on + // or + // global override is off, global setting is on, and core specific is on + bool run = (ignoreGlobalSetting && this.settingsService.GetCoreSettings(core.identifier).download_assets) || + (!ignoreGlobalSetting && this.settingsService.GetConfig().download_assets && + this.settingsService.GetCoreSettings(core.identifier).download_assets); + if (!run) { return new Dictionary { { "installed", installed }, { "skipped", skipped }, - { "missingBetaKey", false } + { "missingLicense", false } }; } - + WriteMessage("Looking for Assets..."); Archive archive = this.archiveService.GetArchive(core.identifier); AnalogueCore info = this.ReadCoreJson(core.identifier); @@ -118,7 +123,7 @@ public Dictionary DownloadAssets(Core core, bool ignoreGlobalSet { foreach (DataSlot slot in dataJson.data.data_slots) { - if (slot.filename != null && slot.filename != string.Empty && + if (!string.IsNullOrEmpty(slot.filename) && !slot.filename.EndsWith(".sav") && !this.assetsService.Blacklist.Contains(slot.filename)) { @@ -212,15 +217,13 @@ public Dictionary DownloadAssets(Core core, bool ignoreGlobalSet // These cores have instance json files and the roms are not in the default archive. // Check to see if they have a core specific archive defined, skip otherwise. - // this is getting gross - if (core.identifier is "Mazamars312.NeoGeo" or "Mazamars312.NeoGeo_Overdrive" or "Mazamars312.NeoGeo_Analogizer" or "obsidian.Vectrex" - && archive.type != ArchiveType.core_specific_archive) + if (this.IgnoreInstanceJson.Contains(core.identifier) && archive.type != ArchiveType.core_specific_archive) { return new Dictionary { { "installed", installed }, { "skipped", skipped }, - { "missingBetaKey", false } + { "missingLicense", false } }; } @@ -232,7 +235,7 @@ public Dictionary DownloadAssets(Core core, bool ignoreGlobalSet { { "installed", installed }, { "skipped", skipped }, - { "missingBetaKey", false } + { "missingLicense", false } }; } @@ -266,12 +269,12 @@ public Dictionary DownloadAssets(Core core, bool ignoreGlobalSet foreach (DataSlot slot in instanceJson.instance.data_slots) { - var platformId = info.metadata.platform_ids[core.beta_slot_platform_id_index]; + var platformId = info.metadata.platform_ids[core.license_slot_platform_id_index]; - if (!CheckBetaMd5(slot, core.beta_slot_id, platformId)) + if (!CheckLicenseMd5(slot, core.license_slot_id, platformId)) { // Moved message to the CheckBetaMd5 method - missingBetaKey = true; + missingLicense = true; } if (!this.assetsService.Blacklist.Contains(slot.filename) && @@ -327,7 +330,7 @@ public Dictionary DownloadAssets(Core core, bool ignoreGlobalSet { { "installed", installed }, { "skipped", skipped }, - { "missingBetaKey", missingBetaKey } + { "missingLicense", missingLicense } }; return results; @@ -423,7 +426,7 @@ public void BuildInstanceJson(string identifier, bool overwrite = true) simpleInstanceJson.instance = instance; string[] parts = dir.Split(commonPath); - //split on dir separator and remove last one? + // split on dir separator and remove last one? parts = parts[1].Split(dirName); string subDirectory = string.Empty; diff --git a/src/services/CoresService.Extras.cs b/src/services/CoresService.Extras.cs index 81444b72..cd10b332 100644 --- a/src/services/CoresService.Extras.cs +++ b/src/services/CoresService.Extras.cs @@ -1,7 +1,7 @@ -using System.IO.Compression; using Newtonsoft.Json; using Pannella.Helpers; using Pannella.Models; +using Pannella.Models.Events; using Pannella.Models.Extras; using Pannella.Models.Github; using Pannella.Models.OpenFPGA_Cores_Inventory; @@ -129,7 +129,6 @@ private void DownloadPocketExtrasPlatform(PocketExtra pocketExtra, string path, string contents = File.ReadAllText(renameFile); Uri uri = new Uri(contents); - string urlFileName = Path.GetFileName(uri.LocalPath); string localRenameFileName = Path.Combine(Path.GetDirectoryName(renameFile)!, renamedFileName); WriteMessage($"Downloading '{renamedFileName}'"); @@ -189,7 +188,7 @@ private void DownloadPocketExtrasPlatform(PocketExtra pocketExtra, string path, Message = "Complete.", InstalledAssets = (List)results["installed"], SkippedAssets = (List)results["skipped"], - MissingBetaKeys = (bool)results["missingBetaKey"] + MissingLicenses = (bool)results["missingLicense"] ? new List { core.identifier } : new List(), SkipOutro = true, @@ -209,7 +208,7 @@ private void DownloadPocketExtras(PocketExtra pocketExtra, string path, bool dow if (!this.IsInstalled(core.identifier)) { - bool jtBetaKeyExists = this.ExtractBetaKey(); + bool jtBetaKeyExists = this.ExtractJTBetaKey(); WriteMessage($"The '{pocketExtra.core_identifiers[0]}' core is not currently installed."); @@ -238,9 +237,9 @@ private void DownloadPocketExtras(PocketExtra pocketExtra, string path, bool dow this.Install(core); - if (core.requires_license && jtBetaKeyExists) + if (core.requires_license) { - this.CopyBetaKey(core); + this.CopyLicense(core); } if (!this.IsInstalled(core.identifier)) @@ -329,7 +328,7 @@ private void DownloadPocketExtras(PocketExtra pocketExtra, string path, bool dow Message = "Complete.", InstalledAssets = (List)results["installed"], SkippedAssets = (List)results["skipped"], - MissingBetaKeys = (bool)results["missingBetaKey"] + MissingLicenses = (bool)results["missingLicense"] ? new List { core.identifier } : new List(), SkipOutro = true, diff --git a/src/services/CoresService.Helpers.cs b/src/services/CoresService.Helpers.cs index 93976df6..581b110b 100644 --- a/src/services/CoresService.Helpers.cs +++ b/src/services/CoresService.Helpers.cs @@ -1,4 +1,3 @@ -using System.IO.Compression; using Pannella.Helpers; using Pannella.Models.Analogue.Shared; using Pannella.Models.Extras; @@ -64,7 +63,7 @@ private bool InstallGithubAsset(string identifier, string platformId, string dow ZipHelper.ExtractToDirectory(zipPath, tempDir, true); // Clean problematic directories and files. - Util.CleanDir(tempDir, this.installPath, this.settingsService.GetConfig().preserve_platforms_folder, false, platformId); + Util.CleanDir(tempDir, this.installPath, this.settingsService.GetConfig().preserve_platforms_folder, platformId); // Move the files into place and delete our core's temp directory. WriteMessage("Installing..."); @@ -99,6 +98,19 @@ private void CheckForPocketExtras(string identifier) } } + private void CheckForDisplayModes(string identifier) + { + var coreSettings = this.settingsService.GetCoreSettings(identifier); + + if (coreSettings.display_modes) + { + var displayModes = coreSettings.selected_display_modes.Split(','); + + WriteMessage("Reapplying Display Modes..."); + this.AddDisplayModes(identifier, displayModes, forceOriginal: true); + } + } + private bool CheckCrc(string filePath, ArchiveFile archiveFile) { if (!this.settingsService.GetConfig().crc_check) @@ -120,9 +132,9 @@ private bool CheckCrc(string filePath, ArchiveFile archiveFile) return false; } - private bool CheckBetaMd5(DataSlot slot, string betaSlotId, string platform) + private bool CheckLicenseMd5(DataSlot slot, string licenseSlotId, string platform) { - if (slot.md5 != null && (betaSlotId != null && slot.id == betaSlotId)) + if (slot.md5 != null && (licenseSlotId != null && slot.id == licenseSlotId)) { string path = Path.Combine(this.installPath, "Assets", platform); string filePath = Path.Combine(path, "common", slot.filename); @@ -131,11 +143,11 @@ private bool CheckBetaMd5(DataSlot slot, string betaSlotId, string platform) if (!(exists = File.Exists(filePath))) { - WriteMessage($"JT beta key not found at '{filePath}'"); + WriteMessage($"License not found at '{filePath}'"); } else if (!(checksum = Util.CompareChecksum(filePath, slot.md5, Util.HashTypes.MD5))) { - WriteMessage("JT beta key checksum validation failed."); + WriteMessage("License checksum validation failed."); WriteMessage($"Location: '{filePath}'"); } @@ -144,4 +156,23 @@ private bool CheckBetaMd5(DataSlot slot, string betaSlotId, string platform) return true; } + + public bool GrossCheck(Core core) + { + //if author starts with jt + //look for licenses/beta.bin + + //if author is pram0d or atrac17 + //look for coinop.key + if (core.identifier.StartsWith("jotego")) + { + return File.Exists(Path.Combine(this.installPath, "licenses", "beta.bin")); + } + else if (core.identifier.StartsWith("pram0d") || core.identifier.StartsWith("atrac17")) + { + return File.Exists(Path.Combine(this.installPath, "licenses", "coinop.key")); + } + + return true; + } } diff --git a/src/services/CoresService.Jotego.cs b/src/services/CoresService.Jotego.cs index e9f98e34..a649acc2 100644 --- a/src/services/CoresService.Jotego.cs +++ b/src/services/CoresService.Jotego.cs @@ -1,4 +1,3 @@ -using System.IO.Compression; using Pannella.Helpers; using Pannella.Models.OpenFPGA_Cores_Inventory; using AnalogueCore = Pannella.Models.Analogue.Core.Core; @@ -8,9 +7,8 @@ namespace Pannella.Services; public partial class CoresService { - public const string BETA_KEY_FILENAME = "jtbeta.zip"; - public const string BETA_KEY_ALT_FILENAME = "beta.bin"; - private const string EXTRACT_LOCATION = "betakeys"; + public const string JTBETA_KEY_FILENAME = "jtbeta.zip"; + public const string JTBETA_KEY_ALT_FILENAME = "beta.bin"; private Dictionary renamedPlatformFiles; @@ -57,53 +55,20 @@ private Dictionary LoadRenamedPlatformFiles() return platformFiles; } - public (bool, string, int) IsBetaCore(string identifier) + public bool ExtractJTBetaKey() { - var data = this.ReadDataJson(identifier); - var slot = data.data.data_slots.FirstOrDefault(x => x.name == "JTBETA"); - - return slot != null - ? (true, slot.id, slot.GetPlatformIdIndex()) - : (false, null, 0); - } - - public void CopyBetaKey(Core core) - { - AnalogueCore info = this.ReadCoreJson(core.identifier); - string path = Path.Combine( - this.installPath, - "Assets", - info.metadata.platform_ids[core.beta_slot_platform_id_index], - "common"); - - if (!Directory.Exists(path)) - { - Directory.CreateDirectory(path); - } - - string keyPath = Path.Combine(this.installPath, EXTRACT_LOCATION); - - if (Directory.Exists(keyPath) && Directory.Exists(path)) - { - Util.CopyDirectory(keyPath, path, false, true); - WriteMessage($"JT beta key copied to '{path}'."); - } - } - - public bool ExtractBetaKey() - { - string keyPath = Path.Combine(this.installPath, EXTRACT_LOCATION); - string zipFile = Path.Combine(this.installPath, BETA_KEY_FILENAME); + string keyPath = Path.Combine(this.installPath, LICENSE_EXTRACT_LOCATION); + string zipFile = Path.Combine(this.installPath, JTBETA_KEY_FILENAME); if (File.Exists(zipFile)) { WriteMessage("JT beta key detected. Extracting..."); - ZipHelper.ExtractToDirectory(zipFile, keyPath, true); + ZipHelper.ExtractToDirectory(zipFile, keyPath, true, false); return true; } - string binFile = Path.Combine(this.installPath, BETA_KEY_ALT_FILENAME); + string binFile = Path.Combine(this.installPath, JTBETA_KEY_ALT_FILENAME); if (File.Exists(binFile)) { @@ -114,23 +79,11 @@ public bool ExtractBetaKey() Directory.CreateDirectory(keyPath); } - File.Copy(binFile, Path.Combine(keyPath, BETA_KEY_ALT_FILENAME), true); + File.Copy(binFile, Path.Combine(keyPath, JTBETA_KEY_ALT_FILENAME), true); return true; } - WriteMessage("JT beta key not found at either location:"); - WriteMessage($" {zipFile}"); - WriteMessage($" {binFile}"); - return false; } - - public void DeleteBetaKey() - { - string keyPath = Path.Combine(this.installPath, EXTRACT_LOCATION); - - if (Directory.Exists(keyPath)) - Directory.Delete(keyPath, true); - } } diff --git a/src/services/CoresService.Json.cs b/src/services/CoresService.Json.cs index 2b77799e..82176785 100644 --- a/src/services/CoresService.Json.cs +++ b/src/services/CoresService.Json.cs @@ -2,6 +2,7 @@ using Pannella.Models.Analogue.Data; using Pannella.Models.Analogue.Video; using Pannella.Models.OpenFPGA_Cores_Inventory; +using Pannella.Models.Updater; using AnalogueCore = Pannella.Models.Analogue.Core.Core; namespace Pannella.Services; @@ -69,4 +70,19 @@ public Video ReadVideoJson(string identifier) return config; } + + public Updaters ReadUpdatersJson(string identifier) + { + string file = Path.Combine(this.installPath, "Cores", identifier, "updaters.json"); + + if (!File.Exists(file)) + { + return null; + } + + string json = File.ReadAllText(file); + Updaters data = JsonConvert.DeserializeObject(json); + + return data; + } } diff --git a/src/services/CoresService.License.cs b/src/services/CoresService.License.cs new file mode 100644 index 00000000..4e000e9b --- /dev/null +++ b/src/services/CoresService.License.cs @@ -0,0 +1,94 @@ +using System.IO.Compression; +using Pannella.Helpers; +using Pannella.Models.OpenFPGA_Cores_Inventory; +using AnalogueCore = Pannella.Models.Analogue.Core.Core; +using GithubFile = Pannella.Models.Github.File; + +namespace Pannella.Services; + +public partial class CoresService +{ + private const string LICENSE_EXTRACT_LOCATION = "licenses"; + + public (bool, string, int, string) RequiresLicense(string identifier) + { + var updater = this.ReadUpdatersJson(identifier); + if (updater == null || updater.license == null) { + return (false, null, 0, null); + } + + var data = this.ReadDataJson(identifier); + var slot = data.data.data_slots.FirstOrDefault(x => x.filename == updater.license.filename); + + return slot != null + ? (true, slot.id, slot.GetPlatformIdIndex(), updater.license.filename) + : (false, null, 0, null); + } + + public void CopyLicense(Core core) + { + AnalogueCore info = this.ReadCoreJson(core.identifier); + string path = Path.Combine( + this.installPath, + "Assets", + info.metadata.platform_ids[core.license_slot_platform_id_index], + "common"); + string licensePath = Path.Combine(this.installPath, LICENSE_EXTRACT_LOCATION); + + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + string keyFile = Path.Combine(licensePath, core.license_slot_filename); + + if (File.Exists(keyFile) && Directory.Exists(path)) + { + File.Copy(keyFile, Path.Combine(path, core.license_slot_filename), true); + WriteMessage($"License copied to '{path}'."); + } + } + + public bool RetrieveKeys() + { + string keyPath = Path.Combine(this.installPath, LICENSE_EXTRACT_LOCATION); + this.ExtractJTBetaKey(); + + string email = ServiceHelper.SettingsService.GetConfig().patreon_email_address; + if (email == null && ServiceHelper.SettingsService.GetConfig().coin_op_beta) + { + Console.WriteLine("Unable to retrieve Coin-Op Collection Beta license. Please set your patreon email address."); + Console.Write("Enter value: "); + email = Console.ReadLine(); + ServiceHelper.SettingsService.GetConfig().patreon_email_address = email; + ServiceHelper.SettingsService.Save(); + } + if (email != null && ServiceHelper.SettingsService.GetConfig().coin_op_beta) + { + if (!Directory.Exists(keyPath)) + { + Directory.CreateDirectory(keyPath); + } + try { + Console.WriteLine("Retrieving Coin-Op Collection license..."); + var license = CoinOpService.FetchLicense(email); + File.WriteAllBytes(Path.Combine(keyPath, "coinop.key"), license); + Console.WriteLine("License successfully downloaded."); + } catch (Exception e) { + Console.WriteLine(e.Message); + } finally { + Divide(); + } + } + + return true; + } + + public void DeleteBetaKeys() + { + string keyPath = Path.Combine(this.installPath, LICENSE_EXTRACT_LOCATION); + + if (Directory.Exists(keyPath)) + Directory.Delete(keyPath, true); + } +} diff --git a/src/services/CoresService.Video.cs b/src/services/CoresService.Video.cs index 4b15a614..c35e669a 100644 --- a/src/services/CoresService.Video.cs +++ b/src/services/CoresService.Video.cs @@ -5,64 +5,6 @@ namespace Pannella.Services; public partial class CoresService { - private static readonly string[] ALL_MODES = - { - "0x10", // CRT Trinitron - "0x20", // Grayscale LCD - "0x30", // Reflective Color LCD - "0x40", // Backlit Color LCD - "0xE0", // Pinball Neon Matrix - "0xE1", // Vacuum Fluorescent - }; - - private static readonly string[] GB_MODES = - { - "0x21", // Original GB DMG - "0x22", // Original GBP - "0x23", // Original GBP Light - }; - - private static readonly string[] GBC_MODES = - { - "0x31", // Original GBC LCD - "0x32", // Original GBC LCD+ - }; - - private static readonly string[] GBA_MODES = - { - "0x41", // Original GBA LCD - "0x42", // Original GBA SP 101 - }; - - private static readonly string[] GG_MODES = - { - "0x51", // Original GG - "0x52", // Original GG+ - }; - - private static readonly string[] NGP_MODES = - { - "0x61", // Original NGP - }; - - private static readonly string[] NGPC_MODES = - { - "0x62", // Original NGPC - "0x63", // Original NGPC+ - }; - - private static readonly string[] PCE_MODES = - { - "0x71", // TurboExpress - "0x72", // PC Engine LT - }; - - private static readonly string[] LYNX_MODES = - { - "0x81", // Original Lynx - "0x82", // Original Lynx+ - }; - public void ChangeAspectRatio(string identifier, int fromWidth, int fromHeight, int toWidth, int toHeight) { var video = this.ReadVideoJson(identifier); @@ -82,44 +24,72 @@ public void ChangeAspectRatio(string identifier, int fromWidth, int fromHeight, File.WriteAllText(Path.Combine(this.installPath, "Cores", identifier, "video.json"), json); } - public void AddDisplayModes(string identifier) + public void AddDisplayModes(string identifier, string[] displayModes = null, bool isCurated = false, bool forceOriginal = false) { var info = this.ReadCoreJson(identifier); var video = this.ReadVideoJson(identifier); - List all = new List(); + List toAdd = new List(); - switch (info.metadata.platform_ids) + if (isCurated) + { + if (this.DisplayModes.TryGetValue("all", out var all)) + { + toAdd.AddRange(all.Select(displayMode => new DisplayMode { id = displayMode.value })); + } + if (info.metadata.platform_ids.Contains("gb") && this.DisplayModes.TryGetValue("gb", out var gb)) + { + toAdd.AddRange(gb.Select(displayMode => new DisplayMode { id = displayMode.value })); + } + else if (info.metadata.platform_ids.Contains("gbc") && this.DisplayModes.TryGetValue("gbc", out var gbc)) + { + toAdd.AddRange(gbc.Select(displayMode => new DisplayMode { id = displayMode.value })); + } + else if (info.metadata.platform_ids.Contains("gba") && this.DisplayModes.TryGetValue("gba", out var gba)) + { + toAdd.AddRange(gba.Select(displayMode => new DisplayMode { id = displayMode.value })); + } + else if (info.metadata.platform_ids.Contains("gg") && this.DisplayModes.TryGetValue("gg", out var gg)) + { + toAdd.AddRange(gg.Select(displayMode => new DisplayMode { id = displayMode.value })); + } + else if (info.metadata.platform_ids.Contains("lynx") && this.DisplayModes.TryGetValue("lynx", out var lynx)) + { + toAdd.AddRange(lynx.Select(displayMode => new DisplayMode { id = displayMode.value })); + } + else if (info.metadata.platform_ids.Contains("jtngpc") && this.DisplayModes.TryGetValue("jtngpc", out var ngpc)) + { + toAdd.AddRange(ngpc.Select(displayMode => new DisplayMode { id = displayMode.value })); + } + else if (info.metadata.platform_ids.Contains("jtngp") && this.DisplayModes.TryGetValue("jtngp", out var ngp)) + { + toAdd.AddRange(ngp.Select(displayMode => new DisplayMode { id = displayMode.value })); + } + else if (info.metadata.platform_ids.Contains("pce") && this.DisplayModes.TryGetValue("pce", out var pce)) + { + toAdd.AddRange(pce.Select(displayMode => new DisplayMode { id = displayMode.value })); + } + } + else { - case string[] p when p.Contains("gb"): - all.AddRange(GB_MODES.Select(id => new DisplayMode { id = id })); - break; - case string[] p when p.Contains("gbc"): - all.AddRange(GBC_MODES.Select(id => new DisplayMode { id = id })); - break; - case string[] p when p.Contains("gba"): - all.AddRange(GBA_MODES.Select(id => new DisplayMode { id = id })); - break; - case string[] p when p.Contains("gg"): - all.AddRange(GG_MODES.Select(id => new DisplayMode { id = id })); - break; - case string[] p when p.Contains("lynx"): - all.AddRange(LYNX_MODES.Select(id => new DisplayMode { id = id })); - break; - case string[] p when p.Any(s => s.EndsWith("ngpc")): // ngpc & jtngpc - all.AddRange(NGPC_MODES.Select(id => new DisplayMode { id = id })); - break; - case string[] p when p.Any(s => s.EndsWith("ngp")): // ngp & jtngp - all.AddRange(NGP_MODES.Select(id => new DisplayMode { id = id })); - break; - case string[] p when p.Any(s => s.StartsWith("pce")): // pce & pcecd - all.AddRange(PCE_MODES.Select(id => new DisplayMode { id = id })); - break; + displayModes ??= this.GetAllDisplayModes().Select(m => m.value).ToArray(); + toAdd = displayModes.Select(id => new DisplayMode { id = id }).ToList(); + } + + var settings = this.settingsService.GetCoreSettings(identifier); + if (!settings.display_modes || forceOriginal) + { + // if this is the first time custom display modes are being applied, save the original ones + settings.original_display_modes = video.display_modes is { Count: > 0 } + ? string.Join(',', video.display_modes.Select(d => d.id)) + : string.Empty; } - all.AddRange(ALL_MODES.Select(id => new DisplayMode { id = id })); - video.display_modes = all; + settings.display_modes = true; + settings.selected_display_modes = string.Join(',', toAdd.Select(d => d.id)); + + video.display_modes = toAdd; Dictionary output = new Dictionary { { "video", video } }; string json = JsonConvert.SerializeObject(output, Formatting.Indented); diff --git a/src/services/CoresService.cs b/src/services/CoresService.cs index e99bf173..3cb69b85 100644 --- a/src/services/CoresService.cs +++ b/src/services/CoresService.cs @@ -8,6 +8,8 @@ namespace Pannella.Services; public partial class CoresService : BaseProcess { private const string CORES_END_POINT = "https://openfpga-cores-inventory.github.io/analogue-pocket/api/v2/cores.json"; + private const string IGNORE_INSTANCE_JSON = "https://raw.githubusercontent.com/mattpannella/pupdate/main/ignore_instance.json"; + private const string CORES_LOCAL_END_POINT = "api_override.json"; private const string ZIP_FILE_NAME = "core.zip"; private readonly string installPath; @@ -15,6 +17,30 @@ public partial class CoresService : BaseProcess private readonly ArchiveService archiveService; private readonly AssetsService assetsService; private static List cores; + private static List ignoreInstanceJson; + + private List IgnoreInstanceJson + { + get + { + if (ignoreInstanceJson == null) + { +#if DEBUG + string json = File.ReadAllText("ignore_instance.json"); +#else + string json = this.settingsService.GetConfig().use_local_ignore_instance_json + ? File.ReadAllText("ignore_instance.json") + : HttpHelper.Instance.GetHTML(IGNORE_INSTANCE_JSON); +#endif + + var coreIdentifiers = JsonConvert.DeserializeObject(json); + + ignoreInstanceJson = coreIdentifiers.core_identifiers; + } + + return ignoreInstanceJson; + } + } public List Cores { @@ -22,7 +48,18 @@ public List Cores { if (cores == null) { - string json = HttpHelper.Instance.GetHTML(CORES_END_POINT); + var localPayload = Path.Combine(ServiceHelper.UpdateDirectory, CORES_LOCAL_END_POINT); + string json; + + if (File.Exists(localPayload)) + { + json = File.ReadAllText(localPayload); + } + else + { + json = HttpHelper.Instance.GetHTML(CORES_END_POINT); + } + Dictionary> parsed = JsonConvert.DeserializeObject>>(json); if (parsed.TryGetValue("data", out var coresList)) @@ -66,6 +103,21 @@ public List InstalledCoresWithSponsors } } + private static List coresNotInstalled; + + public List CoresNotInstalled + { + get + { + if (coresNotInstalled == null) + { + RefreshInstalledCores(); + } + + return coresNotInstalled; + } + } + public CoresService(string path, SettingsService settingsService, ArchiveService archiveService, AssetsService assetsService) { @@ -95,8 +147,26 @@ public Core GetInstalledCore(string identifier) public void RefreshInstalledCores() { - installedCores = cores.Where(c => this.IsInstalled(c.identifier)).ToList(); - installedCoresWithSponsors = installedCores.Where(c => c.sponsor != null).ToList(); + installedCores = new List(); + coresNotInstalled = new List(); + installedCoresWithSponsors = new List(); + + foreach (var core in cores) + { + if (this.IsInstalled(core.identifier)) + { + installedCores.Add(core); + + if (core.sponsor != null) + { + installedCoresWithSponsors.Add(core); + } + } + else + { + coresNotInstalled.Add(core); + } + } } public bool Install(Core core, bool clean = false) @@ -117,8 +187,23 @@ public bool Install(Core core, bool clean = false) if (this.InstallGithubAsset(core.identifier, core.platform_id, core.download_url)) { this.ReplaceCheck(core.identifier); + + // not resetting the pocket extras on a clean install (a.k.a reinstall) + // the combination cores and variant cores aren't affected + // the additional assets extras just add roms so they're not affected either this.CheckForPocketExtras(core.identifier); + // reset the display modes customizations on a clean install (a.k.a reinstall) + if (clean) + { + this.settingsService.DisableDisplayModes(core.identifier); + this.settingsService.Save(); + } + else + { + this.CheckForDisplayModes(core.identifier); + } + return true; } @@ -129,10 +214,11 @@ public void Uninstall(string identifier, string platformId, bool nuke = false) { WriteMessage($"Uninstalling {identifier}..."); - Delete(identifier, platformId, nuke); + this.Delete(identifier, platformId, nuke); this.settingsService.DisableCore(identifier); this.settingsService.DisablePocketExtras(identifier); + this.settingsService.DisableDisplayModes(identifier); this.settingsService.Save(); this.RefreshInstalledCores(); diff --git a/src/services/PlatformImagePacksService.cs b/src/services/PlatformImagePacksService.cs index 14822d9a..2f24316b 100644 --- a/src/services/PlatformImagePacksService.cs +++ b/src/services/PlatformImagePacksService.cs @@ -1,4 +1,3 @@ -using System.IO.Compression; using Newtonsoft.Json; using Pannella.Helpers; using Pannella.Models; @@ -103,4 +102,4 @@ private static string FindPlatformImagePack(string temp) throw new Exception("Can't find image pack"); } -} \ No newline at end of file +} diff --git a/src/services/SettingsService.cs b/src/services/SettingsService.cs index 85902c7b..fc3798f7 100644 --- a/src/services/SettingsService.cs +++ b/src/services/SettingsService.cs @@ -68,10 +68,20 @@ public void InitializeCoreSettings(List cores) foreach (Core core in cores) { - if (!settings.core_settings.ContainsKey(core.identifier)) + // if (!settings.core_settings.ContainsKey(core.identifier)) + if (!settings.core_settings.TryGetValue(core.identifier, out var coreSettings)) { this.missingCores.Add(core); } + else if (coreSettings.requires_license && !core.requires_license) + { + this.missingCores.Add(core); + coreSettings.requires_license = false; + } + else if (core.requires_license) + { + coreSettings.requires_license = true; + } } } @@ -116,6 +126,16 @@ public void DisablePocketExtras(string name) } } + public void DisableDisplayModes(string name) + { + if (settings.core_settings.TryGetValue(name, out CoreSettings value)) + { + value.display_modes = false; + value.original_display_modes = null; + value.selected_display_modes = null; + } + } + public List GetMissingCores() => this.missingCores; public void EnableMissingCores()