diff --git a/.vscode/launch.json b/.vscode/launch.json index e29b25a7..bf70844b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,7 @@ "request": "launch", "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/bin/Debug/net6.0/pupdate.dll", + "program": "${workspaceFolder}/bin/Debug/net7.0/pupdate.dll", "args": ["-p", "/Users/mattpannella/pocket-test"], "cwd": "${workspaceFolder}", // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console diff --git a/pupdate.csproj b/pupdate.csproj index ffda5684..d82cfc10 100644 --- a/pupdate.csproj +++ b/pupdate.csproj @@ -3,15 +3,16 @@ true Exe true - net6.0 + net7.0 enable - enable + disable 3.2.1 Keep your Analogue Pocket up to date 2024 Matt Pannella Matt Pannella Pupdate https://github.com/mattpannella/pocket-updater-utility + Pannella diff --git a/src/Factory.cs b/src/Factory.cs deleted file mode 100644 index ddcba8c7..00000000 --- a/src/Factory.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace pannella.analoguepocket; - -public class Factory -{ - public static HttpHelper GetHttpHelper() - { - return HttpHelper.Instance; - } - - public static GlobalHelper GetGlobals() - { - return GlobalHelper.Instance; - } -} \ No newline at end of file diff --git a/src/PocketCoreUpdater.cs b/src/PocketCoreUpdater.cs new file mode 100644 index 00000000..b8d7a92a --- /dev/null +++ b/src/PocketCoreUpdater.cs @@ -0,0 +1,546 @@ +using System.Diagnostics.CodeAnalysis; +using System.IO.Compression; +using System.Text.Json; +using Pannella.Helpers; +using Pannella.Models; +using Pannella.Services; +using File = System.IO.File; +using AnalogueCore = Pannella.Models.Analogue.Core.Core; +using GithubFile = Pannella.Models.Github.File; + +namespace Pannella; + +[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] +public class PocketCoreUpdater : Base +{ + private const string FIRMWARE_FILENAME_PATTERN = "pocket_firmware_*.bin"; + + private bool _downloadAssets; + private bool _preservePlatformsFolder; + private bool _downloadFirmware = true; + private bool _deleteSkippedCores = true; + private bool _renameJotegoCores = true; + private bool _jtBeta; + private bool _backupSaves; + private string _backupSavesLocation; + private Dictionary _platformFiles = new(); + + public PocketCoreUpdater() + { + Directory.CreateDirectory(Path.Combine(GlobalHelper.UpdateDirectory, "Cores")); + + foreach (Core core in GlobalHelper.Cores) + { + core.StatusUpdated += updater_StatusUpdated; // attach handler to bubble event up + } + } + + #region Settings + + /// + /// Turn on/off renaming the Jotego cores + /// + /// Set to true to rename the Jotego cores + public void RenameJotegoCores(bool set) + { + _renameJotegoCores = set; + } + + /// + /// Turn on/off the automatic BIOS downloader + /// + /// Set to true to enable automatic BIOS downloading + public void DownloadAssets(bool set) + { + _downloadAssets = set; + } + + /// + /// Turn on/off preserving customizations to /Platforms + /// + /// Set to true to enable preserving custom /Platforms changes + public void PreservePlatformsFolder(bool set) + { + _preservePlatformsFolder = set; + } + + /// + /// Turn on/off downloading the Analogue Pocket firmware + /// + /// Set to true to download the latest Analogue Pocket firmware + public void DownloadFirmware(bool set) + { + _downloadFirmware = set; + } + + /// + /// Turn on/off compressing and backing up the /Saves directory + /// + /// Set to true to compress and backup the /Saves directory + /// The absolute path to the backup location + public void BackupSaves(bool set, string location) + { + _backupSaves = set; + _backupSavesLocation = location; + } + + /// + /// Turn on/off the deletion of skipped cores + /// + /// Set to true to delete the skipped cores + public void DeleteSkippedCores(bool set) + { + _deleteSkippedCores = set; + } + + #endregion + + public void BuildInstanceJson(bool overwrite = false, string coreName = null) + { + foreach (Core core in GlobalHelper.Cores) + { + if (core.CheckInstancePackager() && (coreName == null || coreName == core.identifier)) + { + WriteMessage(core.identifier); + core.BuildInstanceJSONs(overwrite); + Divide(); + } + } + } + + /// + /// Run the full openFPGA core download and update process + /// + public async Task RunUpdates(string id = null, bool clean = false) + { + List> installed = new List>(); + List installedAssets = new List(); + List skippedAssets = new List(); + List missingBetaKeys = new List(); + string firmwareDownloaded = string.Empty; + + if (GlobalHelper.Cores == null) + { + throw new Exception("Must initialize updater before running update process"); + } + + if (_backupSaves) + { + AssetsService.BackupSaves(GlobalHelper.UpdateDirectory, _backupSavesLocation); + } + + if (_downloadFirmware && id == null) + { + firmwareDownloaded = await UpdateFirmware(); + } + + ExtractBetaKey(); + + foreach (var core in GlobalHelper.Cores.Where(core => id == null || core.identifier == id)) + { + core.download_assets = _downloadAssets && id == null; + core.build_instances = GlobalHelper.SettingsManager.GetConfig().build_instance_jsons && id == null; + + try + { + if (GlobalHelper.SettingsManager.GetCoreSettings(core.identifier).skip) + { + DeleteCore(core); + continue; + } + + if (core.requires_license && !_jtBeta) + { + continue; //skip if you don't have the key + } + + string name = core.identifier; + + if (name == null) + { + WriteMessage("Core Name is required. Skipping."); + continue; + } + + WriteMessage("Checking Core: " + name); + var mostRecentRelease = core.version; + + Dictionary results; + + if (mostRecentRelease == null) + { + WriteMessage("No releases found. Skipping"); + + CopyBetaKey(core); + + results = await core.DownloadAssets(); + installedAssets.AddRange(results["installed"] as List); + skippedAssets.AddRange(results["skipped"] as List); + + if ((bool)results["missingBetaKey"]) + { + missingBetaKeys.Add(core.identifier); + } + + await JotegoRename(core); + Divide(); + continue; + } + + WriteMessage(mostRecentRelease + " is the most recent release, checking local core..."); + + if (core.IsInstalled()) + { + AnalogueCore localCore = core.GetConfig(); + string localVersion = localCore.metadata.version; + + if (localVersion != null) + { + WriteMessage("local core found: " + localVersion); + } + + if (mostRecentRelease != localVersion || clean) + { + WriteMessage("Updating core"); + } + else + { + CopyBetaKey(core); + results = await core.DownloadAssets(); + await JotegoRename(core); + + installedAssets.AddRange(results["installed"] as List); + skippedAssets.AddRange(results["skipped"] as List); + + if ((bool)results["missingBetaKey"]) + { + missingBetaKeys.Add(core.identifier); + } + + WriteMessage("Up to date. Skipping core"); + Divide(); + continue; + } + } + else + { + WriteMessage("Downloading core"); + } + + if (await core.Install(_preservePlatformsFolder, clean)) + { + Dictionary summary = new Dictionary + { + { "version", mostRecentRelease }, + { "core", core.identifier }, + { "platform", core.platform.name } + }; + + installed.Add(summary); + } + + await JotegoRename(core); + CopyBetaKey(core); + + results = await core.DownloadAssets(); + installedAssets.AddRange(results["installed"] as List); + skippedAssets.AddRange(results["skipped"] as List); + + if ((bool)results["missingBetaKey"]) + { + missingBetaKeys.Add(core.identifier); + } + + WriteMessage("Installation complete."); + Divide(); + + } + catch (Exception e) + { + WriteMessage("Uh oh something went wrong."); + WriteMessage(e.Message); + } + } + + UpdateProcessCompleteEventArgs args = new UpdateProcessCompleteEventArgs + { + Message = "Update Process Complete", + InstalledCores = installed, + InstalledAssets = installedAssets, + SkippedAssets = skippedAssets, + MissingBetaKeys = missingBetaKeys, + FirmwareUpdated = firmwareDownloaded + }; + + GlobalHelper.RefreshInstalledCores(); + OnUpdateProcessComplete(args); + } + + private async Task LoadPlatformFiles() + { + try + { + List files = await GithubApiService.GetFiles( + "dyreschlock", + "pocket-platform-images", + "arcade/Platforms"); + Dictionary platformFiles = new(); + + foreach (GithubFile file in files) + { + string url = file.download_url; + string filename = file.name; + + if (filename.EndsWith(".json")) + { + string platform = Path.GetFileNameWithoutExtension(filename); + + platformFiles.Add(platform, url); + } + } + + _platformFiles = platformFiles; + } + catch (Exception) + { + WriteMessage("Unable to retrieve archive contents. Asset download may not work."); + _platformFiles = new Dictionary(); + } + } + + private async Task JotegoRename(Core core) + { + if (_renameJotegoCores && + GlobalHelper.SettingsManager.GetCoreSettings(core.identifier).platform_rename && + core.identifier.Contains("jotego")) + { + await LoadPlatformFiles(); + + core.platform_id = core.identifier.Split('.')[1]; //whatever + + string path = Path.Combine(GlobalHelper.UpdateDirectory, "Platforms", core.platform_id + ".json"); + string json = await File.ReadAllTextAsync(path); + Dictionary data = JsonSerializer.Deserialize>(json); + Platform platform = data["platform"]; + + if (_platformFiles.TryGetValue(core.platform_id, out string value) && platform.name == core.platform_id) + { + WriteMessage("Updating JT Platform Name..."); + await HttpHelper.Instance.DownloadFileAsync(value, path); + WriteMessage("Complete"); + } + } + } + + private void CopyBetaKey(Core core) + { + if (core.JTBetaCheck()) + { + AnalogueCore info = core.GetConfig(); + string path = Path.Combine( + GlobalHelper.UpdateDirectory, + "Assets", + info.metadata.platform_ids[core.beta_slot_platform_id_index], + "common"); + + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + string keyPath = Path.Combine(GlobalHelper.UpdateDirectory, "betakeys"); + + if (Directory.Exists(keyPath) && Directory.Exists(path)) + { + Util.CopyDirectory(keyPath, path, false, true); + WriteMessage("Beta key copied to common directory."); + } + } + } + + private void ExtractBetaKey() + { + string keyPath = Path.Combine(GlobalHelper.UpdateDirectory, "betakeys"); + string file = Path.Combine(GlobalHelper.UpdateDirectory, "jtbeta.zip"); + + if (File.Exists(file)) + { + _jtBeta = true; + WriteMessage("Extracting JT beta key..."); + ZipFile.ExtractToDirectory(file, keyPath, true); + } + } + + public async Task RunAssetDownloader(string id = null) + { + List installedAssets = new List(); + List skippedAssets = new List(); + List missingBetaKeys = new List(); + + if (GlobalHelper.Cores == null) + { + throw new Exception("Must initialize updater before running update process"); + } + + foreach (var core in GlobalHelper.Cores.Where(core => id == null || core.identifier == id) + .Where(core => !GlobalHelper.SettingsManager.GetCoreSettings(core.identifier).skip)) + { + core.download_assets = true; + + try + { + string name = core.identifier; + + if (name == null) + { + WriteMessage("Core Name is required. Skipping."); + continue; + } + + WriteMessage(core.identifier); + + var results = await core.DownloadAssets(); + + installedAssets.AddRange(results["installed"] as List); + skippedAssets.AddRange(results["skipped"] as List); + + if ((bool)results["missingBetaKey"]) + { + missingBetaKeys.Add(core.identifier); + } + + Divide(); + } + catch (Exception e) + { + WriteMessage("Uh oh something went wrong."); + WriteMessage(e.Message); + } + } + + UpdateProcessCompleteEventArgs args = new UpdateProcessCompleteEventArgs + { + Message = "All Done", + InstalledAssets = installedAssets, + SkippedAssets = skippedAssets, + MissingBetaKeys = missingBetaKeys + }; + + OnUpdateProcessComplete(args); + } + + public void ForceDisplayModes(string id = null) + { + if (GlobalHelper.Cores == null) + { + throw new Exception("Must initialize updater before running update process"); + } + + foreach (var core in GlobalHelper.Cores.Where(core => id == null || core.identifier == id) + .Where(core => !GlobalHelper.SettingsManager.GetCoreSettings(core.identifier).skip)) + { + core.download_assets = true; + + try + { + string name = core.identifier; + + if (name == null) + { + WriteMessage("Core Name is required. Skipping."); + continue; + } + + WriteMessage("Updating " + core.identifier); + core.AddDisplayModes(); + Divide(); + } + catch (Exception e) + { + WriteMessage("Uh oh something went wrong."); + WriteMessage(e.Message); + } + } + + WriteMessage("Finished."); + } + + private void OnUpdateProcessComplete(UpdateProcessCompleteEventArgs e) + { + EventHandler handler = UpdateProcessComplete; + + handler?.Invoke(this, e); + + GlobalHelper.RefreshInstalledCores(); + } + + public async Task UpdateFirmware() + { + string version = string.Empty; + + WriteMessage("Checking for firmware updates..."); + + var details = await AnalogueFirmwareService.GetDetails(); + string[] parts = details.download_url.Split("/"); + string filename = parts[parts.Length - 1]; + string filepath = Path.Combine(GlobalHelper.UpdateDirectory, filename); + + if (!File.Exists(filepath) || !Util.CompareChecksum(filepath, details.md5, Util.HashTypes.MD5)) + { + version = filename; + + var oldFiles = Directory.GetFiles(GlobalHelper.UpdateDirectory, FIRMWARE_FILENAME_PATTERN); + + WriteMessage("Firmware update found. Downloading..."); + + await HttpHelper.Instance.DownloadFileAsync(details.download_url, Path.Combine(GlobalHelper.UpdateDirectory, filename)); + + WriteMessage("Download Complete"); + WriteMessage(Path.Combine(GlobalHelper.UpdateDirectory, filename)); + + foreach (string oldFile in oldFiles) + { + if (File.Exists(oldFile) && Path.GetFileName(oldFile) != filename) + { + WriteMessage("Deleting old firmware file..."); + File.Delete(oldFile); + } + } + + WriteMessage("To install firmware, restart your Pocket."); + } + else + { + WriteMessage("Firmware up to date."); + } + + Divide(); + + return version; + } + + public void DeleteCore(Core core, bool force = false, bool nuke = false) + { + if (_deleteSkippedCores || force) + { + core.Uninstall(nuke); + } + } + + private void updater_StatusUpdated(object sender, StatusUpdatedEventArgs e) + { + this.OnStatusUpdated(e); + } + + public event EventHandler UpdateProcessComplete; +} + +public class UpdateProcessCompleteEventArgs : EventArgs +{ + public string Message { get; set; } + public List> InstalledCores { get; set; } + public List InstalledAssets { get; set; } + public List SkippedAssets { get; set; } + public string FirmwareUpdated { get; set; } = string.Empty; + public List MissingBetaKeys { get; set; } +} diff --git a/src/Program.cs b/src/Program.cs index 3569c3e5..5e453d73 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,189 +1,218 @@ using System.Diagnostics; -using System.IO.Compression; -using System.Reflection; -using System.Runtime.InteropServices; using CommandLine; -using pannella.analoguepocket; -using ConsoleTools; +using Pannella.Helpers; +using Pannella.Models; +using Pannella.Options; +using Pannella.Services; +namespace Pannella; -internal class Program +internal partial class Program { - private static string version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(3); - private const string USER = "mattpannella"; - private const string REPOSITORY = "pocket-updater-utility"; - private const string RELEASE_URL = "https://github.com/mattpannella/pocket-updater-utility/releases/download/{0}/pupdate_{1}.zip"; - private static SettingsManager settings; + private static bool CLI_MODE; - private static PocketCoreUpdater updater; - - private static bool cliMode = false; private static async Task Main(string[] args) { - try { - string location = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName; - string? path = Path.GetDirectoryName(location); + try + { + string location = Process.GetCurrentProcess().MainModule.FileName; + string path = Path.GetDirectoryName(location); bool preservePlatformsFolder = false; bool forceUpdate = false; bool forceInstanceGenerator = false; - string? downloadAssets = null; - string? coreName = null; - string? imagePackOwner = null; - string? imagePackRepo = null; - string? imagePackVariant = null; + string downloadAssets = null; + string coreName = null; + string imagePackOwner = null; + string imagePackRepo = null; + string imagePackVariant = null; bool downloadFirmware = false; bool selfUpdate = false; bool nuke = false; bool cleanInstall = false; - string backupSaves_Path = null!; + string backupSaves_Path = null; bool backupSaves_SaveConfig = false; - ConsoleKey response; - string verb = "menu"; - Dictionary data = new Dictionary(); + Dictionary data = new Dictionary(); + + #region Command Line Arguments + Parser.Default.ParseArguments(args) - .WithParsed(o => { - selfUpdate = true; - }) - .WithParsed(async o => - { - verb = "fund"; - data.Add("core", null); - if(o.Core != null && o.Core != "") { - data["core"] = o.Core; - } - } - ) - .WithParsed(async o => - { - verb = "update"; - cliMode = true; - forceUpdate = true; - if(o.InstallPath != null && o.InstallPath != "") { - path = o.InstallPath; - } - if(o.PreservePlatformsFolder) { - preservePlatformsFolder = true; - } - if(o.CleanInstall) { - cleanInstall = true; - } - if(o.CoreName != null && o.CoreName != "") { + AssetsOptions, FirmwareOptions, ImagesOptions, InstanceGeneratorOptions, + UpdateSelfOptions, UninstallOptions, BackupSavesOptions>(args) + .WithParsed(_ => { selfUpdate = true; }) + .WithParsed(o => + { + verb = "fund"; + data.Add("core", null); + + if (!string.IsNullOrEmpty(o.Core)) + { + data["core"] = o.Core; + } + }) + .WithParsed(o => + { + verb = "update"; + CLI_MODE = true; + forceUpdate = true; + + if (!string.IsNullOrEmpty(o.InstallPath)) + { + path = o.InstallPath; + } + + if (o.PreservePlatformsFolder) + { + preservePlatformsFolder = true; + } + + if (o.CleanInstall) + { + cleanInstall = true; + } + + if (!string.IsNullOrEmpty(o.CoreName)) + { + coreName = o.CoreName; + } + }) + .WithParsed(o => + { + verb = "uninstall"; + CLI_MODE = true; coreName = o.CoreName; - } - } - ) - .WithParsed(async o => - { - verb = "uninstall"; - cliMode = true; - coreName = o.CoreName; - if(o.InstallPath != null && o.InstallPath != "") { - path = o.InstallPath; - } - if(o.DeleteAssets) { - nuke = true; - } - } - ) - .WithParsed(async o => - { - verb = "assets"; - cliMode = true; - downloadAssets = "all"; - if(o.InstallPath != null && o.InstallPath != "") { - path = o.InstallPath; - } - if(o.CoreName != null) { - downloadAssets = o.CoreName; - } - } - ) - .WithParsed(async o => - { - verb = "firmware"; - cliMode = true; - downloadFirmware = true; - if(o.InstallPath != null && o.InstallPath != "") { - path = o.InstallPath; - } - } - ) - .WithParsed(async o => - { - verb = "images"; - cliMode = true; - if(o.InstallPath != null && o.InstallPath != "") { - path = o.InstallPath; - } - if(o.ImagePackOwner != null) { - imagePackOwner = o.ImagePackOwner; - imagePackRepo = o.ImagePackRepo; - imagePackVariant = o.ImagePackVariant; - } - } - ) - .WithParsed(async o => - { - verb = "instancegenerator"; - forceInstanceGenerator = true; - cliMode = true; - if(o.InstallPath != null && o.InstallPath != "") { - path = o.InstallPath; - } - } - ) + + if (!string.IsNullOrEmpty(o.InstallPath)) + { + path = o.InstallPath; + } + + if (o.DeleteAssets) + { + nuke = true; + } + }) + .WithParsed(o => + { + verb = "assets"; + CLI_MODE = true; + downloadAssets = "all"; + + if (!string.IsNullOrEmpty(o.InstallPath)) + { + path = o.InstallPath; + } + + if (o.CoreName != null) + { + downloadAssets = o.CoreName; + } + }) + .WithParsed(o => + { + verb = "firmware"; + CLI_MODE = true; + downloadFirmware = true; + + if (!string.IsNullOrEmpty(o.InstallPath)) + { + path = o.InstallPath; + } + }) + .WithParsed(o => + { + verb = "images"; + CLI_MODE = true; + + if (!string.IsNullOrEmpty(o.InstallPath)) + { + path = o.InstallPath; + } + + if (o.ImagePackOwner != null) + { + imagePackOwner = o.ImagePackOwner; + imagePackRepo = o.ImagePackRepo; + imagePackVariant = o.ImagePackVariant; + } + }) + .WithParsed(o => + { + verb = "instancegenerator"; + CLI_MODE = true; + forceInstanceGenerator = true; + + if (!string.IsNullOrEmpty(o.InstallPath)) + { + path = o.InstallPath; + } + }) .WithParsed(o => - { - if(o.InstallPath != null && o.InstallPath != "") { - path = o.InstallPath; - } - if(o.SkipUpdate) { - cliMode = true; - } - } - ) + { + if (!string.IsNullOrEmpty(o.InstallPath)) + { + path = o.InstallPath; + } + + if (o.SkipUpdate) + { + CLI_MODE = true; + } + }) .WithParsed(o => - { - verb = "backup-saves"; - cliMode = true; - path = o.InstallPath; - backupSaves_Path = o.BackupPath; - backupSaves_SaveConfig = o.Save; - } - ) + { + verb = "backup-saves"; + CLI_MODE = true; + path = o.InstallPath; + backupSaves_Path = o.BackupPath; + backupSaves_SaveConfig = o.Save; + }) .WithNotParsed(o => - { - if(o.IsHelp()) { - Environment.Exit(1); - } - if(o.IsVersion()) { - Environment.Exit(1); - } - } - ); + { + if (o.IsHelp()) + { + Environment.Exit(1); + } + + if (o.IsVersion()) + { + Environment.Exit(1); + } + }); + + #endregion - if (!cliMode) { - Console.WriteLine("Pupdate v" + version); + GlobalHelper.Initialize(path); + + if (!CLI_MODE) + { + Console.WriteLine("Pupdate v" + VERSION); Console.WriteLine("Checking for updates..."); - if(await CheckVersion(path) && !selfUpdate) { - string platform = GetPlatform(); - ConsoleKey[] acceptedInputs = new[] { ConsoleKey.I, ConsoleKey.C, ConsoleKey.Q }; - do { - if (platform == "win" || platform == "linux" || platform == "mac") { + if (await CheckVersion(path) && !selfUpdate) + { + ConsoleKey[] acceptedInputs = { ConsoleKey.I, ConsoleKey.C, ConsoleKey.Q }; + ConsoleKey response; + + do + { + if (SYSTEM_OS_PLATFORM is "win" or "linux" or "mac") + { Console.Write("Would you like to [i]nstall the update, [c]ontinue with the current version, or [q]uit? [i/c/q]: "); - } else { + } + else + { Console.Write("Update downloaded. Would you like to [c]ontinue with the current version, or [q]uit? [c/q]: "); } + response = Console.ReadKey(false).Key; Console.WriteLine(); - } while(!acceptedInputs.Contains(response)); + } + while (!acceptedInputs.Contains(response)); - switch(response) { + switch (response) + { case ConsoleKey.I: int result = UpdateSelfAndRun(path, args); Environment.Exit(result); @@ -198,971 +227,306 @@ private static async Task Main(string[] args) break; } } - if(selfUpdate) { + + if (selfUpdate) + { Environment.Exit(0); } } - updater = new PocketCoreUpdater(path); - settings = new SettingsManager(path); + PocketCoreUpdater coreUpdater = new PocketCoreUpdater(); - switch(verb) { + switch (verb) + { case "fund": - await Funding((string)data["core"]); + Funding((string)data["core"]); Environment.Exit(1); break; - default: - break; } - if(preservePlatformsFolder || settings.GetConfig().preserve_platforms_folder) { - updater.PreservePlatformsFolder(true); + // how should the logic work here? what takes priority, the command line parameter or the config setting? + // currently this well preserve the platforms folder if either is set to true + if (preservePlatformsFolder || GlobalHelper.SettingsManager.GetConfig().preserve_platforms_folder) + { + coreUpdater.PreservePlatformsFolder(true); } - updater.DeleteSkippedCores(settings.GetConfig().delete_skipped_cores); - updater.SetGithubApiKey(settings.GetConfig().github_token); - updater.DownloadFirmware(settings.GetConfig().download_firmware); - updater.RenameJotegoCores(settings.GetConfig().fix_jt_names); - updater.StatusUpdated += updater_StatusUpdated; - updater.UpdateProcessComplete += updater_UpdateProcessComplete; - updater.DownloadAssets(settings.GetConfig().download_assets); - updater.BackupSaves(settings.GetConfig().backup_saves, settings.GetConfig().backup_saves_location); - await updater.Initialize(); - settings = GlobalHelper.Instance.SettingsManager; + coreUpdater.DeleteSkippedCores(GlobalHelper.SettingsManager.GetConfig().delete_skipped_cores); + coreUpdater.DownloadFirmware(GlobalHelper.SettingsManager.GetConfig().download_firmware); + coreUpdater.RenameJotegoCores(GlobalHelper.SettingsManager.GetConfig().fix_jt_names); + coreUpdater.StatusUpdated += coreUpdater_StatusUpdated; + coreUpdater.UpdateProcessComplete += coreUpdater_UpdateProcessComplete; + coreUpdater.DownloadAssets(GlobalHelper.SettingsManager.GetConfig().download_assets); + coreUpdater.BackupSaves(GlobalHelper.SettingsManager.GetConfig().backup_saves, GlobalHelper.SettingsManager.GetConfig().backup_saves_location); + + //await coreUpdater.Initialize(); // If we have any missing cores, handle them. - if(updater.GetMissingCores().Any()) { + if (GlobalHelper.SettingsManager.GetMissingCores().Any()) + { Console.WriteLine("\nNew cores found since the last run."); AskAboutNewCores(); - string? download_new_cores = settings.GetConfig().download_new_cores?.ToLowerInvariant(); - switch(download_new_cores) { + string downloadNewCores = GlobalHelper.SettingsManager.GetConfig().download_new_cores?.ToLowerInvariant(); + + switch (downloadNewCores) + { case "yes": Console.WriteLine("The following cores have been enabled:"); - foreach(Core core in updater.GetMissingCores()) + + foreach (Core core in GlobalHelper.SettingsManager.GetMissingCores()) + { Console.WriteLine($"- {core.identifier}"); + } - settings.EnableMissingCores(updater.GetMissingCores()); - settings.SaveSettings(); + GlobalHelper.SettingsManager.EnableMissingCores(GlobalHelper.SettingsManager.GetMissingCores()); + GlobalHelper.SettingsManager.SaveSettings(); break; + case "no": Console.WriteLine("The following cores have been disabled:"); - foreach(Core core in updater.GetMissingCores()) + + foreach (Core core in GlobalHelper.SettingsManager.GetMissingCores()) + { Console.WriteLine($"- {core.identifier}"); + } - settings.DisableMissingCores(updater.GetMissingCores()); - settings.SaveSettings(); + GlobalHelper.SettingsManager.DisableMissingCores(GlobalHelper.SettingsManager.GetMissingCores()); + GlobalHelper.SettingsManager.SaveSettings(); break; + default: - case "ask": - var newones = updater.GetMissingCores(); - settings.EnableMissingCores(newones); - if (cliMode) { - settings.SaveSettings(); - } else { - await RunCoreSelector(newones, "New cores are available!"); + var newOnes = GlobalHelper.SettingsManager.GetMissingCores(); + + GlobalHelper.SettingsManager.EnableMissingCores(newOnes); + + if (CLI_MODE) + { + GlobalHelper.SettingsManager.SaveSettings(); + } + else + { + RunCoreSelector(newOnes, "New cores are available!"); } + break; } - updater.LoadSettings(); + // Is reloading the settings file necessary? + GlobalHelper.ReloadSettings(); } - if(forceUpdate) { + + if (forceUpdate) + { Console.WriteLine("Starting update process..."); - await updater.RunUpdates(coreName, cleanInstall); + await coreUpdater.RunUpdates(coreName, cleanInstall); Pause(); - } else if(downloadFirmware) { - await updater.UpdateFirmware(); - } else if(forceInstanceGenerator) { - await RunInstanceGenerator(updater, true); - } else if(downloadAssets != null) { - if (downloadAssets == "all") { - await updater.RunAssetDownloader(); - } else { - await updater.RunAssetDownloader(downloadAssets); + } + else if (downloadFirmware) + { + await coreUpdater.UpdateFirmware(); + } + else if (forceInstanceGenerator) + { + RunInstanceGenerator(coreUpdater, true); + } + else if (downloadAssets != null) + { + if (downloadAssets == "all") + { + await coreUpdater.RunAssetDownloader(); + } + else + { + await coreUpdater.RunAssetDownloader(downloadAssets); } - } else if (imagePackOwner != null) { - ImagePack pack = new ImagePack() { + } + else if (imagePackOwner != null) + { + ImagePack pack = new ImagePack + { owner = imagePackOwner, repository = imagePackRepo, variant = imagePackVariant }; - await InstallImagePack(path, pack); - } else if (verb == "uninstall") { - if (GlobalHelper.Instance.GetCore(coreName) == null) { + + await pack.Install(path); + } + else if (verb == "uninstall") + { + if (GlobalHelper.GetCore(coreName) == null) + { Console.WriteLine("Unknown core"); - } else { - await updater.DeleteCore(GlobalHelper.Instance.GetCore(coreName), true, nuke); } - } else if (verb == "backup-saves") { + else + { + coreUpdater.DeleteCore(GlobalHelper.GetCore(coreName), true, nuke); + } + } + else if (verb == "backup-saves") + { AssetsService.BackupSaves(path, backupSaves_Path); - + if (backupSaves_SaveConfig) { - var config = settings.GetConfig(); - + var config = GlobalHelper.SettingsManager.GetConfig(); + config.backup_saves = true; config.backup_saves_location = backupSaves_Path; - - settings.SaveSettings(); + + GlobalHelper.SettingsManager.SaveSettings(); } - } else { + } + else + { bool flag = true; - while(flag) { + + while (flag) + { int choice = DisplayMenuNew(); - switch(choice) { + switch (choice) + { case 1: - await updater.UpdateFirmware(); + await coreUpdater.UpdateFirmware(); Pause(); break; + case 2: Console.WriteLine("Checking for required files..."); - await updater.RunAssetDownloader(); + await coreUpdater.RunAssetDownloader(); Pause(); break; + case 3: List cores = await CoresService.GetCores(); AskAboutNewCores(true); - await RunCoreSelector(cores); - updater.LoadSettings(); + RunCoreSelector(cores); + // Is reloading the settings file necessary? + GlobalHelper.ReloadSettings(); break; + case 4: await ImagePackSelector(path); break; + case 5: - await RunInstanceGenerator(updater); + RunInstanceGenerator(coreUpdater); Pause(); break; + case 6: - await BuildGameandWatchROMS(path); + await BuildGameAndWatchRoms(path); Pause(); break; + case 7: - await updater.ForceDisplayModes(); + coreUpdater.ForceDisplayModes(); Pause(); break; + case 8: - AssetsService.BackupSaves(path, settings.GetConfig().backup_saves_location); + AssetsService.BackupSaves(path, GlobalHelper.SettingsManager.GetConfig().backup_saves_location); Pause(); break; + case 9: SettingsMenu(); - SetUpdaterFlags(); + + coreUpdater.DeleteSkippedCores(GlobalHelper.SettingsManager.GetConfig().delete_skipped_cores); + coreUpdater.DownloadFirmware(GlobalHelper.SettingsManager.GetConfig().download_firmware); + coreUpdater.DownloadAssets(GlobalHelper.SettingsManager.GetConfig().download_assets); + coreUpdater.RenameJotegoCores(GlobalHelper.SettingsManager.GetConfig().fix_jt_names); + coreUpdater.BackupSaves(GlobalHelper.SettingsManager.GetConfig().backup_saves, GlobalHelper.SettingsManager.GetConfig().backup_saves_location); + // Is reloading the settings file necessary? + GlobalHelper.ReloadSettings(); break; + case 10: flag = false; break; - case 0: + default: Console.WriteLine("Starting update process..."); - await updater.RunUpdates(); + await coreUpdater.RunUpdates(); Pause(); break; } } } - } catch(Exception e) { - Console.WriteLine("Well, something went wrong. Sorry about that."); - Console.WriteLine(e.Message); - Pause(); } - } - - private static int UpdateSelfAndRun(string directory, string[] updaterArgs) - { - string execName = "pupdate"; - if(GetPlatform() == "win") { - execName += ".exe"; - } - string execLocation = Path.Combine(directory, execName); - string backupName = $"{execName}.backup"; - string backupLocation = Path.Combine(directory, backupName); - string updateName = "pupdate.zip"; - string updateLocation = Path.Combine(directory, updateName); - - int exitcode = int.MinValue; - - try { - // Load System.IO.Compression now - Assembly.Load("System.IO.Compression"); - if(GetPlatform() != "win") { - Assembly.Load("System.IO.Pipes"); - } - - // Move current process file - Console.WriteLine($"Renaming {execLocation} to {backupLocation}"); - File.Move(execLocation, backupLocation, true); - - // Extract update - Console.WriteLine($"Extracting {updateLocation} to {directory}"); - ZipFile.ExtractToDirectory(updateLocation, directory, true); - - // Execute - Console.WriteLine($"Executing {execLocation}"); - ProcessStartInfo pInfo = new ProcessStartInfo(execLocation) { - Arguments = string.Join(' ', updaterArgs), - UseShellExecute = false - }; - - Process p = Process.Start(pInfo); - p.WaitForExit(); - exitcode = p.ExitCode; - } catch(Exception e) { - Console.Error.WriteLine($"An error occurred: {e.GetType().Name}:{e.ToString()}"); - } - - return exitcode; - } - - private async static Task BuildGameandWatchROMS(string directory) - { - Github.Release release = await GithubApi.GetLatestRelease("agg23", "fpga-gameandwatch"); - foreach(Github.Asset asset in release.assets) { - if (asset.name.EndsWith("Tools.zip")) { - string downloadPath = Path.Combine(directory, "tools", "gameandwatch"); - string filename = Path.Combine(downloadPath, asset.name); - if(!File.Exists(filename)) { - Directory.CreateDirectory(downloadPath); - await HttpHelper.Instance.DownloadFileAsync(asset.browser_download_url, filename); - ZipFile.ExtractToDirectory(filename, downloadPath, true); - } - break; - } - } - string execName = "fpga-gnw-romgenerator"; - string execLocation = Path.Combine(directory, "tools", "gameandwatch"); - string manifestPath = Path.Combine(directory, "tools", "gameandwatch"); - switch(GetPlatform()) { - case "win": - execName += ".exe"; - execLocation = Path.Combine(execLocation, "windows", execName); - manifestPath = Path.Combine(manifestPath, "windows", "manifest.json"); - break; - case "mac": - execLocation = Path.Combine(execLocation, "mac", execName); - manifestPath = Path.Combine(manifestPath, "mac", "manifest.json"); - Exec($"chmod +x {execLocation}"); - break; - default: - execLocation = Path.Combine(execLocation, "linux", execName); - manifestPath = Path.Combine(manifestPath, "linux", "manifest.json"); - Exec($"chmod +x {execLocation}"); - break; - } - - string romLocation = Path.Combine(directory, "Assets", "gameandwatch", "agg23.GameAndWatch"); - string outputLocation = Path.Combine(directory, "Assets", "gameandwatch", "common"); - - try { - // Execute - Console.WriteLine($"Executing {execLocation}"); - ProcessStartInfo pInfo = new ProcessStartInfo(execLocation) { - Arguments = $"--mame-path \"{romLocation}\" --output-path \"{outputLocation}\" --manifest-path \"{manifestPath}\" supported", - UseShellExecute = false - }; - - Process p = Process.Start(pInfo); - p.WaitForExit(); - } catch(Exception e) { - Console.Error.WriteLine($"An error occurred: {e.GetType().Name}:{e.ToString()}"); - } - } - - static void SetUpdaterFlags() - { - updater.DeleteSkippedCores(settings.GetConfig().delete_skipped_cores); - updater.SetGithubApiKey(settings.GetConfig().github_token); - updater.DownloadFirmware(settings.GetConfig().download_firmware); - updater.DownloadAssets(settings.GetConfig().download_assets); - updater.RenameJotegoCores(settings.GetConfig().fix_jt_names); - updater.BackupSaves(settings.GetConfig().backup_saves, settings.GetConfig().backup_saves_location); - updater.LoadSettings(); - } - - static async Task RunInstanceGenerator(PocketCoreUpdater updater, bool force = false) - { - if(!force) { - ConsoleKey response; - Console.Write("Do you want to overwrite existing json files? [y/N] "); - Console.WriteLine(""); - response = Console.ReadKey(false).Key; - if(response == ConsoleKey.Y) { - force = true; - } - } - await updater.BuildInstanceJSON(force); - } - - public static void Exec(string cmd) - { - var escapedArgs = cmd.Replace("\"", "\\\""); - - using var process = new Process + catch (Exception e) { - StartInfo = new ProcessStartInfo - { - RedirectStandardOutput = true, - UseShellExecute = false, - CreateNoWindow = true, - WindowStyle = ProcessWindowStyle.Hidden, - FileName = "/bin/bash", - Arguments = $"-c \"{escapedArgs}\"" - } - }; - - process.Start(); - process.WaitForExit(); - } - - static async Task RunCoreSelector(List cores, string message = "Select your cores.") - { - if(settings.GetConfig().download_new_cores?.ToLowerInvariant() == "yes") { - foreach(Core core in cores) - settings.EnableCore(core.identifier); - } else { - var pageSize = 15; - var offset = 0; - bool next = false; - bool previous = false; - bool more = true; - 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("{1}", item.Index, 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 = settings.GetCoreSettings(core.identifier); - var selected = !coreSettings.skip; - var name = core.identifier; - if (core.requires_license) { - name += " (Requires beta access)"; - } - var title = settingsMenuItem(name, selected); - menu.Add(title, (thisMenu) => { - selected = !selected; - if (!selected) { - settings.DisableCore(core.identifier); - } else { - settings.EnableCore(core.identifier); - } - - thisMenu.CurrentItem.Name = settingsMenuItem(core.identifier, selected); - }); - } - } - 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(); - } + Console.WriteLine("Well, something went wrong. Sorry about that."); + Console.WriteLine(e); + Pause(); } - settings.GetConfig().core_selector = false; - settings.SaveSettings(); } - static void updater_StatusUpdated(object sender, StatusUpdatedEventArgs e) + private static void coreUpdater_StatusUpdated(object sender, StatusUpdatedEventArgs e) { Console.WriteLine(e.Message); } - static void updater_UpdateProcessComplete(object sender, UpdateProcessCompleteEventArgs e) + static void coreUpdater_UpdateProcessComplete(object sender, UpdateProcessCompleteEventArgs e) { Console.WriteLine("-------------"); Console.WriteLine(e.Message); - if(e.InstalledCores != null && e.InstalledCores.Count > 0) { + + if (e.InstalledCores != null && e.InstalledCores.Count > 0) + { Console.WriteLine("Cores Updated:"); - foreach(Dictionary core in e.InstalledCores) { + + foreach (Dictionary core in e.InstalledCores) + { Console.WriteLine(core["core"] + " " + core["version"]); } + Console.WriteLine(""); } - if(e.InstalledAssets.Count > 0) { + + if (e.InstalledAssets.Count > 0) + { Console.WriteLine("Assets Installed:"); - foreach(string asset in e.InstalledAssets) { + + foreach (string asset in e.InstalledAssets) + { Console.WriteLine(asset); } + Console.WriteLine(""); } - if(e.SkippedAssets.Count > 0) { + + if (e.SkippedAssets.Count > 0) + { Console.WriteLine("Assets Not Found:"); - foreach(string asset in e.SkippedAssets) { + foreach (string asset in e.SkippedAssets) + { Console.WriteLine(asset); } + Console.WriteLine(""); } - if(e.FirmwareUpdated != "") { + + if (e.FirmwareUpdated != "") + { Console.WriteLine("New Firmware was downloaded. Restart your Pocket to install"); Console.WriteLine(e.FirmwareUpdated); Console.WriteLine(""); } - if(e.MissingBetaKeys.Count > 0) { + + if (e.MissingBetaKeys.Count > 0) + { Console.WriteLine("Missing or incorrect Beta Key for the following cores:"); - foreach(string core in e.MissingBetaKeys) { + foreach (string core in e.MissingBetaKeys) + { Console.WriteLine(core); } - Console.WriteLine(""); - } - ShowSponsorLinks(); - FunFacts(); - } - private static void ShowSponsorLinks() - { - if (GlobalHelper.Instance.InstalledCores.Count == 0) return; - var random = new Random(); - var index = random.Next(GlobalHelper.Instance.InstalledCores.Count); - var randomItem = GlobalHelper.Instance.InstalledCores[index]; - if(randomItem.sponsor != null) { - var links = ""; - if (randomItem.sponsor.custom != null) { - links += "\r\n" + String.Join("\r\n", randomItem.sponsor.custom); - } - if (randomItem.sponsor.github != null) { - links += "\r\n" + String.Join("\r\n", randomItem.sponsor.github); - } - if (randomItem.sponsor.patreon != null) { - links += "\r\n" + randomItem.sponsor.patreon; - } Console.WriteLine(""); - Console.WriteLine($"Please consider supporting {randomItem.getConfig().metadata.author} for their work on the {randomItem} core:"); - Console.WriteLine(links.Trim()); - } - } - - private static string? GetSponsorLinks() - { - if (GlobalHelper.Instance.InstalledCores.Count == 0) return null; - var random = new Random(); - var index = random.Next(GlobalHelper.Instance.InstalledCores.Count); - var randomItem = GlobalHelper.Instance.InstalledCores[index]; - string output = ""; - if(randomItem.sponsor != null) { - var links = ""; - if (randomItem.sponsor.custom != null) { - links += "\r\n" + String.Join("\r\n", randomItem.sponsor.custom); - } - if (randomItem.sponsor.github != null) { - links += "\r\n" + String.Join("\r\n", randomItem.sponsor.github); - } - if (randomItem.sponsor.patreon != null) { - links += "\r\n" + randomItem.sponsor.patreon; - } - output += "\r\n"; - output += $"Please consider supporting {randomItem.getConfig().metadata.author} for their work on the {randomItem} core:"; - output += $"\r\n{links.Trim()}"; - } - - return output; - } - - private static async Task Funding(string? identifier) - { - await updater.Initialize(); - if (GlobalHelper.Instance.InstalledCores.Count == 0) return; - - List cores = new List(); - if (identifier == null) { - cores = GlobalHelper.Instance.InstalledCores; - } else { - var c = GlobalHelper.Instance.GetCore(identifier); - if (c != null && c.isInstalled()) { - cores.Add(c); - } - } - - foreach(Core core in cores) { - if(core.sponsor != null) { - var links = ""; - if (core.sponsor.custom != null) { - links += "\r\n" + String.Join("\r\n", core.sponsor.custom); - } - if (core.sponsor.github != null) { - links += "\r\n" + String.Join("\r\n", core.sponsor.github); - } - if (core.sponsor.patreon != null) { - links += "\r\n" + core.sponsor.patreon; - } - Console.WriteLine(""); - Console.WriteLine($"{core.identifier}:"); - Console.WriteLine(links.Trim()); - } } - } - private static void FunFacts() - { - if (GlobalHelper.Instance.InstalledCores.Count == 0) return; - List cores = new List(); - - foreach(Core c in GlobalHelper.Instance.InstalledCores) { - if (c.getConfig().framework.sleep_supported) { - cores.Add(c.identifier); - } - } - Console.WriteLine(""); - string list = String.Join(", ", cores.ToArray()); - Console.WriteLine("Fun fact! The ONLY cores that support save states and sleep are the following:"); - Console.WriteLine(list); - Console.WriteLine("Please don't bother the developers of the other cores about this feature. It's a lot of work and most likely will not be coming."); - - } - - //return true if newer version is available - async static Task CheckVersion(string path) - { - try { - List releases = await GithubApi.GetReleases(USER, REPOSITORY); - - string tag_name = releases[0].tag_name; - string? v = SemverUtil.FindSemver(tag_name); - if(v != null) { - bool check = SemverUtil.SemverCompare(v, version); - if(check) { - Console.WriteLine("A new version is available. Downloading now..."); - string platform = GetPlatform(); - string url = String.Format(RELEASE_URL, tag_name, platform); - string saveLocation = Path.Combine(path, "pupdate.zip"); - await Factory.GetHttpHelper().DownloadFileAsync(url, saveLocation); - Console.WriteLine("Download complete."); - Console.WriteLine(saveLocation); - Console.WriteLine("Go to " + releases[0].html_url + " for a change log"); - } else { - Console.WriteLine("Up to date."); - } - return check; - } - - return false; - } catch(HttpRequestException e) { - return false; - } - } - - private static string GetPlatform() - { - if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - return "win"; - } - if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - return "mac"; - } - if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - Architecture arch = RuntimeInformation.ProcessArchitecture; - if(arch == Architecture.Arm64) { - return "linux_arm64"; - } else if(arch == Architecture.Arm) { - return "linux_arm32"; - } - return "linux"; - } - - return ""; - } - - private static int DisplayMenuNew() - { - Console.Clear(); - Random random = new Random(); - int i = random.Next(0, welcomeMessages.Length); - string welcome = welcomeMessages[i]; - - int choice = 0; - - var menu = new ConsoleMenu() - .Configure(config => - { - config.Selector = "=>"; - //config.EnableFilter = true; - config.Title = $"{welcome}\r\n{GetSponsorLinks()}\r\n"; - config.EnableWriteTitle = true; - //config.EnableBreadcrumb = true; - config.WriteHeaderAction = () => Console.WriteLine("Choose your destiny:"); - config.SelectedItemBackgroundColor = Console.ForegroundColor; - config.SelectedItemForegroundColor = Console.BackgroundColor; - }); - - foreach(var (item, index) in menuItems.WithIndex()) { - menu.Add(item, (thisMenu) => - { - choice = thisMenu.CurrentItem.Index; - thisMenu.CloseMenu(); - }); - } - - menu.Show(); - - return choice; - } - - private static int DisplayMenu() - { - Console.Clear(); - Random random = new Random(); - int i = random.Next(0, welcomeMessages.Length); - string welcome = welcomeMessages[i]; - - Console.WriteLine(welcome); - - foreach(var (item, index) in menuItems.WithIndex()) { - Console.WriteLine($"{index}) {item}"); - } - ShowSponsorLinks(); - Console.Write("\nChoose your destiny: "); - int choice; - bool result = int.TryParse(Console.ReadLine(), out choice); - if(result) { - return choice; - } - return 0; - } - - private static async Task SettingsMenu() - { - Console.Clear(); - - var menuItems = new Dictionary(){ - {"download_firmware", "Download Firmware Updates during 'Update All'"}, - {"download_assets", "Download Missing Assets (ROMs and BIOS Files) during 'Update All'"}, - {"build_instance_jsons", "Build game JSON files for supported cores during 'Update All'"}, - {"delete_skipped_cores", "Delete untracked cores during 'Update All'"}, - {"fix_jt_names", "Automatically rename Jotego cores during 'Update All"}, - {"crc_check", "Use CRC check when checking ROMs and BIOS files"}, - {"preserve_platforms_folder", "Preserve 'Platforms' folder during 'Update All'"}, - {"skip_alternative_assets", "Skip alternative roms when downloading assets"}, - {"backup_saves", "Compress and backup Saves directory during 'Update All'"}, - {"use_custom_archive", "Use custom asset archive"} - }; - - var type = typeof(Config); - - 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("{1}", item.Index, item.Name); - }); - - foreach(var (name, text) in menuItems) { - var property = type.GetProperty(name); - var value = (bool) property.GetValue(settings.GetConfig()); - var title = settingsMenuItem(text, value); - - menu.Add(title, (thisMenu) => - { - value = !value; - property.SetValue(settings.GetConfig(), value); - thisMenu.CurrentItem.Name = settingsMenuItem(text, value); - }); - } - - menu.Add("Save", (thisMenu) => {thisMenu.CloseMenu();}); - - menu.Show(); - - settings.SaveSettings(); - } - - private static string settingsMenuItem(string title, bool value) - { - var x = " "; - if (value) { - x = "X"; - } - - return $"[{x}] {title}"; - } - - private static async Task ImagePackSelector(string path) - { - Console.Clear(); - Console.WriteLine("Checking for image packs...\n"); - ImagePack[] packs = await ImagePacksService.GetImagePacks(); - if(packs.Length > 0) { - int choice = 0; - var menu = new ConsoleMenu() - .Configure(config => - { - config.Selector = "=>"; - config.EnableWriteTitle = false; - //config.EnableBreadcrumb = true; - config.WriteHeaderAction = () => Console.WriteLine("So, what'll it be?:"); - config.SelectedItemBackgroundColor = Console.ForegroundColor; - config.SelectedItemForegroundColor = Console.BackgroundColor; - }); - - foreach(var (pack, index) in packs.WithIndex()) { - menu.Add($"{pack.owner}: {pack.repository} {pack.variant}", (thisMenu) => { choice = thisMenu.CurrentItem.Index; thisMenu.CloseMenu(); }); - } - menu.Add("Go Back", (thisMenu) => {choice = packs.Length; thisMenu.CloseMenu();}); - - menu.Show(); - - if(choice < packs.Length && choice >= 0) { - await InstallImagePack(path, packs[choice]); - Pause(); - } else if(choice == packs.Length) { - return; - } else { - Console.WriteLine("you fucked up"); - Pause(); - } - } else { - Console.WriteLine("None found. Have a nice day"); - Pause(); - } - } - - private static async Task InstallImagePack(string path, ImagePack pack) - { - await pack.Install(path); - } - - private static void PauseExit(int exitcode = 0) - { - Console.WriteLine("Press any key to exit."); - Console.ReadLine(); //wait for input so the console doesn't auto close in windows - Environment.Exit(exitcode); - } - - private static void Pause() - { - if(cliMode) return; - Console.WriteLine("Press any key to continue."); - Console.ReadKey(true); - } - - private static void AskAboutNewCores(bool force = false) - { - while(settings.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(false).Key; - settings.GetConfig().download_new_cores = response switch { - ConsoleKey.Y => "yes", - ConsoleKey.N => "no", - ConsoleKey.A => "ask", - _ => null - }; - } + Console.WriteLine(GetRandomSponsorLinks()); + FunFacts(); } - - private static string[] menuItems = { - "Update All", - "Update Firmware", - "Download Required Assets", - "Select Cores", - "Download Platform Image Packs", - "Generate Instance JSON Files", - "Generate Game and Watch ROMS", - "Enable All Display Modes", - "Backup Saves Directory", - "Settings", - "Exit" - }; - - private static string[] welcomeMessages = { - @" - _____ _ _ ___ _____ _ -| __ | |___ _____ ___ _ _ ___ _ _ ___ ___ ___| | _| ___ ___ | __|___ _| | -| __ -| | .'| | -_| | | | . | | | _|_ -| -_| | _| | . | _| | | | . | . | -|_____|_|__,|_|_|_|___| |_ |___|___|_| |___|___|_|_| |___|_| |_____|___|___| - |___| ", - @" - _ _ _ _ _ _____ _ _ -| | | |___| |___ ___ _____ ___ | |_ ___ | __| |___ _ _ ___ ___| |_ ___ _ _ _ ___ -| | | | -_| | _| . | | -_| | _| . | | __| | .'| | | . | _| _| . | | | | | -|_____|___|_|___|___|_|_|_|___| |_| |___| |__| |_|__,|\_/|___|_| |_| |___|_____|_|_| - ", - @" - _ ___ _ _ _ _ - ___| |_ ___ | _|_|___ _| |___ _ _ ___ _ _ ___ ___ _ _ ___| |_ _ _ _| |___ _ _ ___ -|_ -| | -_| | _| | | . |_ -| | | | . | | | | _| _| | |_ -| _| | | | . | .'| | | -_| -|___|_|_|___| |_| |_|_|_|___|___| |_ |___|___| |___|_| |___|___|_| |_ | |___|__,|\_/|___| - |___| |___| ", - @" - _____ _ _ _ ___ _ _ -|_ _| |_|_|___ |_|___ ___ | _|___ ___ ___ _ _ ___ ___ ___| |_ ___ _ _ ___ ___ ___| |_ - | | | | |_ -| | |_ -| | .'| | _| .'| | _| | | | _| -_|_ -| _| .'| | | _| .'| | _| - |_| |_|_|_|___| |_|___| |__,| |_| |__,|_|_|___|_ | |_| |___|___|_| |__,|___|_| |__,|_|_|_| - |___| ", - @" __ - _ _ _ _ _ _ _ _____ _ _ _____ _ _ | | -| | | |___| |___ ___ _____ ___ | |_ ___ | |_| |_ ___ | __ | |___ ___| |_ | |___ ___| |_ ___| |_| | -| | | | -_| | _| . | | -_| | _| . | | _| | -_| | __ -| | .'| _| '_| | | | | .'| _| '_| -_| _|__| -|_____|___|_|___|___|_|_|_|___| |_| |___| |_| |_|_|___| |_____|_|__,|___|_,_| |_|_|_|__,|_| |_,_|___|_| |__| - ", - @" - _____ _ _ _____ _ -| | |_ ___| |_ _ _ |_ _|___ ___| |_ _ _ -|- -| _| _| | | |_ | | | .'|_ -| _| | |_ -|_____|_| |___|_|_|_ |_| |_| |__,|___|_| |_ |_| - |___| |___| ", - @" _ _____ - _ _ _ _ _ | | _ _ |___ | -| | | | |_ ___| |_|_|___ ___ _ _ ___ | |_ _ _ _ _|_|___| _| -| | | | | .'| _| | _| -_| | | | .'| | . | | | | | | |_| -|_____|_|_|__,|_| |_| |___| |_ |__,| |___|___|_ |_|_|_|_| - |___| |___| ", - @" _____ - _ _ _ _ _ _ |___ | -| | | | |_ ___| |_ |_|___ ___ _____ ___ ___| _| -| | | | | .'| _| | |_ -| | .'| | | .'| |_| -|_____|_|_|__,|_| |_|___| |__,| |_|_|_|__,|_|_|_| - ", - @" - _____ _ _ _____ _ _ _ -| __|_|___ ___|_|___ ___ | |___|_| |___ _| | -| __| |_ -|_ -| | . | | | | | | .'| | | -_| . | -|__| |_|___|___|_|___|_|_| |_|_|_|__,|_|_|___|___| - ", - @" - _____ _ -| | |_____| |_ ___ ___ ___ -| | | | . | .'|_ -| .'| -|_____|_|_|_|___|__,|___|__,| - ", - @" _ - __ _ | | -| | ___| |_|_|___ _____ ___ ___ ___ _ _ -| |__| -_| _| |_ -| | | . |_ -| -_| | | -|_____|___|_| |___| |_|_|_|___|___|___|_ | - |___|", -@" _=,_ - o_/6 /#\ - \__ |##/ - ='|--\ - / #'-. - \#|_ _'-. / - |/ \_( # |'' - C/ ,--___/" - }; -} -[Verb("menu", isDefault: true, HelpText = "Interactive Main Menu")] -public class MenuOptions -{ - [Option('p', "path", HelpText = "Absolute path to install location", Required = false)] - public string? InstallPath { get; set; } - - [Option('s', "skip-update", HelpText = "Skip the self update check", Required = false)] - public bool SkipUpdate { get; set; } -} - -[Verb("update", HelpText = "Run update all. (You can configure via the settings menu)")] -public class UpdateOptions -{ - [Option('p', "path", HelpText = "Absolute path to install location", Required = false)] - public string? InstallPath { get; set; } - - [Option ('c', "core", Required = false, HelpText = "The core you want to update.")] - public string CoreName { get; set; } - - [Option('f', "platformsfolder", Required = false, HelpText = "Preserve the Platforms folder, so customizations aren't overwritten by updates.")] - public bool PreservePlatformsFolder { get; set; } - - [Option('r', "clean", Required = false, HelpText = "Clean install. Remove all existing core files, before updating")] - public bool CleanInstall { get; set; } - -} - -[Verb("uninstall", HelpText = "Delete a core")] -public class UninstallOptions -{ - [Option('p', "path", HelpText = "Absolute path to install location", Required = false)] - public string? InstallPath { get; set; } - - [Option ('c', "core", Required = true, HelpText = "The core you want to delete.")] - public string CoreName { get; set; } - - [Option('a', "assets", Required = false, HelpText = "Delete the core specific Assets folder")] - public bool DeleteAssets { get; set; } -} - -[Verb("assets", HelpText = "Run the asset downloader")] -public class AssetsOptions -{ - [Option('p', "path", HelpText = "Absolute path to install location", Required = false)] - public string? InstallPath { get; set; } - - [Option ('c', "core", Required = false, HelpText = "The core you want to download assets for.")] - public string CoreName { get; set; } -} - -[Verb("instancegenerator", HelpText = "Run the instance JSON generator")] -public class InstancegeneratorOptions -{ - [Option('p', "path", HelpText = "Absolute path to install location", Required = false)] - public string? InstallPath { get; set; } -} - -[Verb("images", HelpText = "Download image packs")] -public class ImagesOptions -{ - [Option('p', "path", HelpText = "Absolute path to install location", Required = false)] - public string? InstallPath { get; set; } - - [Option('o', "owner", Required = true, HelpText = "Image pack repo username")] - public string ImagePackOwner { get; set; } - - [Option('i', "imagepack", Required = true, HelpText = "Github repo name for image pack")] - public string ImagePackRepo { get; set; } - - [Option('v', "variant", Required = false, HelpText = "The optional variant")] - public string? ImagePackVariant { get; set; } -} - -[Verb("firmware", HelpText = "Check for Pocket firmware updates")] -public class FirmwareOptions -{ - [Option('p', "path", HelpText = "Absolute path to install location", Required = false)] - public string? InstallPath { get; set; } -} - -[Verb("fund", HelpText = "List sponsor links")] -public class FundOptions -{ - [Option('c', "core", HelpText = "The core to check funding links for", Required = false)] - public string? Core { get; set; } -} - -[Verb("update-self", HelpText = "Update this utility")] -public class UpdateSelfOptions -{ -} - -[Verb("backup-saves", HelpText = "Create a compressed zip file of the Saves directory.")] -public class BackupSavesOptions -{ - [Option('p', "path", HelpText = "Absolute path to install location", Required = false)] - public string? InstallPath { get; set; } - - [Option('l', "location", HelpText = "Absolute path to backup location", Required = true)] - public string BackupPath { get; set; } = null!; - - [Option('s', "save", HelpText = "Save settings to the config file", Required = false)] - public bool Save { get; set; } -} - -public static class EnumExtension -{ - public static IEnumerable<(T item, int index)> WithIndex(this IEnumerable self) - => self.Select((item, index) => (item, index)); } diff --git a/src/SettingsManager.cs b/src/SettingsManager.cs index 869fed05..88cc7cfb 100644 --- a/src/SettingsManager.cs +++ b/src/SettingsManager.cs @@ -1,14 +1,16 @@ -using System; +using System.Diagnostics.CodeAnalysis; using System.Text.Json; -using System.IO; +using Pannella.Models; +using Pannella.Models.Settings; -namespace pannella.analoguepocket; +namespace Pannella; +[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] public class SettingsManager { - private Settings _settings; - private string _settingsFile; - private List _newCores = new(); + private readonly Settings _settings; + private readonly string _settingsFile; + private readonly List _newCores = new(); private const string OLD_DEFAULT = "pocket-roms"; private const string NEW_DEFAULT = "openFPGA-Files"; @@ -16,9 +18,10 @@ public class SettingsManager private const string OLD_SETTINGS_FILENAME = "pocket_updater_settings.json"; private const string SETTINGS_FILENAME = "pupdate_settings.json"; - public SettingsManager(string settingsPath, List? cores = null) + public SettingsManager(string settingsPath, List cores = null) { _settings = new Settings(); + string file = Path.Combine(settingsPath, SETTINGS_FILENAME); string oldFile = Path.Combine(settingsPath, OLD_SETTINGS_FILENAME); string json = null!; @@ -37,120 +40,96 @@ public SettingsManager(string settingsPath, List? cores = null) { _settings = JsonSerializer.Deserialize(json); - //hack to force people over to new default :) - if(_settings.config.archive_name == OLD_DEFAULT) { + // hack to force people over to new default :) + if (_settings.config.archive_name == OLD_DEFAULT) + { _settings.config.archive_name = NEW_DEFAULT; } } - //bandaid to fix old settings files - if(_settings.config == null) { - _settings.config = new Config(); - } + // bandaid to fix old settings files + _settings.config ??= new Config(); _settingsFile = file; - if(cores != null) { + if (cores != null) + { InitializeCoreSettings(cores); } SaveSettings(); } - //loop through every core, and add any missing ones to the settings file + // loop through every core, and add any missing ones to the settings file public void InitializeCoreSettings(List cores) { - if(_settings.coreSettings == null) { - _settings.coreSettings = new Dictionary(); - } - foreach(Core core in cores) + _settings.coreSettings ??= new Dictionary(); + + foreach (Core core in cores) { - if(!_settings.coreSettings.ContainsKey(core.identifier)) { + if (!_settings.coreSettings.ContainsKey(core.identifier)) + { _newCores.Add(core); } } } public List GetMissingCores() => _newCores; + public void EnableMissingCores(List cores) { foreach (var core in cores) + { EnableCore(core.identifier); + } } + public void DisableMissingCores(List cores) { foreach (var core in cores) + { DisableCore(core.identifier); + } } - public bool SaveSettings() + public void SaveSettings() { var options = new JsonSerializerOptions { WriteIndented = true }; File.WriteAllText(_settingsFile, JsonSerializer.Serialize(_settings, options)); - - return true; } public void DisableCore(string name) { - if(_settings.coreSettings.ContainsKey(name)) + if (_settings.coreSettings.TryGetValue(name, out CoreSettings value)) { - _settings.coreSettings[name].skip = true; + value.skip = true; } else { - CoreSettings core = new CoreSettings(); - core.skip = true; + CoreSettings core = new CoreSettings { skip = true }; + _settings.coreSettings.Add(name, core); } } public void EnableCore(string name) { - if (_settings.coreSettings.ContainsKey(name)) + if (_settings.coreSettings.TryGetValue(name, out CoreSettings value)) { - _settings.coreSettings[name].skip = false; + value.skip = false; } else { - CoreSettings core = new CoreSettings(); - core.skip = false; - _settings.coreSettings.Add(name, core); - } - } + CoreSettings core = new CoreSettings { skip = false }; - public void UpdateCore(CoreSettings core, string name) - { - if (_settings.coreSettings.ContainsKey(name)) - { - _settings.coreSettings[name] = core; - } - else - { _settings.coreSettings.Add(name, core); } } public CoreSettings GetCoreSettings(string name) { - if (_settings.coreSettings.ContainsKey(name)) - { - return _settings.coreSettings[name]; - } - else - { - return new CoreSettings(); - } - } - - public Firmware GetCurrentFirmware() - { - return _settings.firmware; - } - - public void SetFirmwareVersion(string version) - { - _settings.firmware.version = version; - SaveSettings(); + return _settings.coreSettings.TryGetValue(name, out CoreSettings value) + ? value + : new CoreSettings(); } public Config GetConfig() @@ -158,9 +137,10 @@ public Config GetConfig() return _settings.config; } + // This is used by the RetroDriven Pocket Updater Windows Application + // ReSharper disable once UnusedMember.Global public void UpdateConfig(Config config) { _settings.config = config; } - } diff --git a/src/Updater.cs b/src/Updater.cs deleted file mode 100644 index 4467d50d..00000000 --- a/src/Updater.cs +++ /dev/null @@ -1,564 +0,0 @@ -using System; -using System.IO; -using System.IO.Compression; -using System.Text.Json; -using System.Text.RegularExpressions; - -namespace pannella.analoguepocket; - -public class PocketCoreUpdater : Base -{ - private const string FIRMWARE_FILENAME_PATTERN = "pocket_firmware_*.bin"; - private const string FIRMWARE_URL = "https://www.analogue.co/support/pocket/firmware/latest"; - private static readonly Regex BIN_REGEX = new Regex(@"(?inx) - ]* - href \s* = \s* - (? ['""] ) - (? [^'""]*\.bin ) - \k - [^>]* >"); - private bool _downloadAssets = false; - private bool _preservePlatformsFolder = false; - - private string _githubApiKey = ""; - - private bool _downloadFirmware = true; - private bool _deleteSkippedCores = true; - private bool _useConsole = false; - private bool _renameJotegoCores = true; - private bool _jtBeta = false; - private bool _backupSaves = false; - private string _backupSavesLocation; - - private Dictionary _platformFiles = new Dictionary(); - - /// - /// Constructor - /// - /// The directory to install/update openFPGA cores in. - /// Path to settings json file - public PocketCoreUpdater(string updateDirectory, string? settingsPath = null) - { - Factory.GetGlobals().UpdateDirectory = updateDirectory; - Directory.CreateDirectory(Path.Combine(Factory.GetGlobals().UpdateDirectory, "Cores")); - - if(settingsPath != null) { - Factory.GetGlobals().SettingsPath = settingsPath; - } else { - Factory.GetGlobals().SettingsPath = updateDirectory; - } - } - - public async Task Initialize() - { - LoadSettings(); - await LoadPlatformFiles(); - await LoadCores(); - await RefreshInstalledCores(); - await LoadArchive(); - await LoadBlacklist(); - } - - private async Task RefreshInstalledCores() - { - var installedCores = new List(); - foreach(Core c in GlobalHelper.Instance.Cores) { - if(c.isInstalled()) { - installedCores.Add(c); - } - } - GlobalHelper.Instance.InstalledCores = installedCores; - } - - private async Task LoadPlatformFiles() - { - try { - List files = await GithubApi.GetFiles("dyreschlock", "pocket-platform-images", "arcade/Platforms", - GlobalHelper.Instance.SettingsManager.GetConfig().github_token); - Dictionary platformFiles = new Dictionary(); - foreach(Github.File file in files) { - string url = file.download_url; - string filename = file.name; - if (filename.EndsWith(".json")) { - string platform = Path.GetFileNameWithoutExtension(filename); - platformFiles.Add(platform, url); - } - } - _platformFiles = platformFiles; - } catch (Exception e) { - _writeMessage("Unable to retrieve archive contents. Asset download may not work."); - _platformFiles = new Dictionary(); - } - } - - private async Task LoadArchive() - { - _writeMessage("Loading Assets Index..."); - if(Factory.GetGlobals().SettingsManager.GetConfig().use_custom_archive) { - var custom = Factory.GetGlobals().SettingsManager.GetConfig().custom_archive; - Uri baseUrl = new Uri(custom["url"]); - Uri url = new Uri(baseUrl, custom["index"]); - - Factory.GetGlobals().ArchiveFiles = await ArchiveService.GetFilesCustom(url.ToString()); - } else { - Factory.GetGlobals().ArchiveFiles = await ArchiveService.GetFiles(Factory.GetGlobals().SettingsManager.GetConfig().archive_name); - } - } - - private async Task LoadBlacklist() - { - Factory.GetGlobals().Blacklist = await AssetsService.GetBlacklist(); - } - - public async Task LoadCores() - { - Factory.GetGlobals().Cores = await CoresService.GetCores(); - Factory.GetGlobals().SettingsManager.InitializeCoreSettings(Factory.GetGlobals().Cores); - foreach(Core core in Factory.GetGlobals().Cores) { - core.StatusUpdated += updater_StatusUpdated; //attach handler to bubble event up - } - } - - public List GetCores() - { - return Factory.GetGlobals().Cores; - } - - public void LoadSettings() - { - Factory.GetGlobals().SettingsManager = new SettingsManager(Factory.GetGlobals().SettingsPath, Factory.GetGlobals().Cores); - } - - public List GetMissingCores() => Factory.GetGlobals().SettingsManager?.GetMissingCores() ?? new List(); - - /// - /// Turn on/off printing progress messages to the console - /// - /// Set to true to turn on console messages - public void PrintToConsole(bool set) - { - _useConsole = set; - } - - public void RenameJotegoCores(bool set) - { - _renameJotegoCores = set; - } - - /// - /// Turn on/off the automatic BIOS downloader - /// - /// Set to true to enable automatic BIOS downloading - public void DownloadAssets(bool set) - { - _downloadAssets = set; - } - - /// - /// Turn on/off preserving customizations to /Platforms - /// - /// Set to true to enable preserving custom /Platforms changes - public void PreservePlatformsFolder(bool set) - { - _preservePlatformsFolder = set; - } - - public void DownloadFirmware(bool set) - { - _downloadFirmware = set; - } - - public void BackupSaves(bool set, string location) - { - _backupSaves = set; - _backupSavesLocation = location; - } - - //get api and local cores - private async Task> getAllCores() - { - List cores = Factory.GetGlobals().Cores; - List local = await GetLocalCores(); - foreach(Core core in local) { - core.StatusUpdated += updater_StatusUpdated; //attach handler to bubble event up - } - cores.AddRange(local); - - return cores; - } - - public async Task BuildInstanceJSON(bool overwrite = false, string? corename = null) - { - List cores = await getAllCores(); - foreach(Core core in Factory.GetGlobals().Cores) { - if(core.CheckInstancePackager() && (corename == null || corename == core.identifier)) { - _writeMessage(core.identifier); - core.BuildInstanceJSONs(overwrite); - Divide(); - } - } - } - - /// - /// Run the full openFPGA core download and update process - /// - public async Task RunUpdates(string? id = null, bool clean = false) - { - List> installed = new List>(); - List installedAssets = new List(); - List skippedAssets = new List(); - List missingBetaKeys = new List(); - Dictionary results = new Dictionary(); - string firmwareDownloaded = ""; - if(Factory.GetGlobals().Cores == null) { - throw new Exception("Must initialize updater before running update process"); - } - - if (_backupSaves) - { - AssetsService.BackupSaves(Factory.GetGlobals().UpdateDirectory, _backupSavesLocation); - } - - if(_downloadFirmware && id == null) { - firmwareDownloaded = await UpdateFirmware(); - } - - await ExtractBetaKey(); - - List cores = await getAllCores(); - string json; - foreach(Core core in Factory.GetGlobals().Cores) { - if(id != null && core.identifier != id) { - continue; - } - - core.downloadAssets = (_downloadAssets && (id==null)); - core.buildInstances = (Factory.GetGlobals().SettingsManager.GetConfig().build_instance_jsons && (id==null)); - try { - if(Factory.GetGlobals().SettingsManager.GetCoreSettings(core.identifier).skip) { - await DeleteCore(core); - continue; - } - - if (core.requires_license && !_jtBeta) { - continue; //skip if you don't have the key - } - - string name = core.identifier; - if(name == null) { - _writeMessage("Core Name is required. Skipping."); - continue; - } - - _writeMessage("Checking Core: " + name); - var mostRecentRelease = core.version; - - if(mostRecentRelease == null) { - _writeMessage("No releases found. Skipping"); - await CopyBetaKey(core); - results = await core.DownloadAssets(); - installedAssets.AddRange(results["installed"] as List); - skippedAssets.AddRange(results["skipped"] as List); - if((bool)results["missingBetaKey"]) { - missingBetaKeys.Add(core.identifier); - } - await JotegoRename(core); - Divide(); - continue; - } - - _writeMessage(mostRecentRelease + " is the most recent release, checking local core..."); - if (core.isInstalled()) { - Analogue.Cores.Core.Core localCore = core.getConfig(); - string localVersion = localCore.metadata.version; - - if(localVersion != null) { - _writeMessage("local core found: " + localVersion); - } - - if (mostRecentRelease != localVersion || clean){ - _writeMessage("Updating core"); - } else { - await CopyBetaKey(core); - results = await core.DownloadAssets(); - await JotegoRename(core); - installedAssets.AddRange(results["installed"] as List); - skippedAssets.AddRange(results["skipped"] as List); - if((bool)results["missingBetaKey"]) { - missingBetaKeys.Add(core.identifier); - } - _writeMessage("Up to date. Skipping core"); - Divide(); - continue; - } - } else { - _writeMessage("Downloading core"); - } - - if(await core.Install(clean)) { - Dictionary summary = new Dictionary(); - summary.Add("version", mostRecentRelease); - summary.Add("core", core.identifier); - summary.Add("platform", core.platform.name); - installed.Add(summary); - } - await JotegoRename(core); - await CopyBetaKey(core); - results = await core.DownloadAssets(); - installedAssets.AddRange(results["installed"] as List); - skippedAssets.AddRange(results["skipped"] as List); - if((bool)results["missingBetaKey"]) { - missingBetaKeys.Add(core.identifier); - } - _writeMessage("Installation complete."); - Divide(); - - } catch(Exception e) { - _writeMessage("Uh oh something went wrong."); - _writeMessage(e.Message); - } - } - - UpdateProcessCompleteEventArgs args = new UpdateProcessCompleteEventArgs(); - args.Message = "Update Process Complete"; - args.InstalledCores = installed; - args.InstalledAssets = installedAssets; - args.SkippedAssets = skippedAssets; - args.MissingBetaKeys = missingBetaKeys; - args.FirmwareUpdated = firmwareDownloaded; - OnUpdateProcessComplete(args); - } - - private async Task JotegoRename(Core core) - { - if(_renameJotegoCores && Factory.GetGlobals().SettingsManager.GetCoreSettings(core.identifier).platform_rename - && core.identifier.Contains("jotego")) { - core.platform_id = core.identifier.Split('.')[1]; //whatever - string path = Path.Combine(Factory.GetGlobals().UpdateDirectory, "Platforms", core.platform_id + ".json"); - string json = File.ReadAllText(path); - Dictionary data = JsonSerializer.Deserialize>(json); - Platform platform = data["platform"]; - if(_platformFiles.ContainsKey(core.platform_id) && platform.name == core.platform_id) { - _writeMessage("Updating JT Platform Name..."); - await Factory.GetHttpHelper().DownloadFileAsync(_platformFiles[core.platform_id], path); - _writeMessage("Complete"); - } - } - } - - private async Task CopyBetaKey(Core core) - { - if(core.JTBetaCheck()) { - Analogue.Cores.Core.Core info = core.getConfig(); - string path = Path.Combine(Factory.GetGlobals().UpdateDirectory, "Assets", info.metadata.platform_ids[core.betaSlotPlatformIdIndex], "common"); - if(!Directory.Exists(path)) { - Directory.CreateDirectory(path); - } - string keyPath = Path.Combine(Factory.GetGlobals().UpdateDirectory, "betakeys"); - if(Directory.Exists(keyPath) && Directory.Exists(path)) { - Util.CopyDirectory(keyPath, path, false, true); - _writeMessage("Beta key copied to common directory."); - } - } - } - - private async Task ExtractBetaKey() - { - string keyPath = Path.Combine(Factory.GetGlobals().UpdateDirectory, "betakeys"); - string file = Path.Combine(Factory.GetGlobals().UpdateDirectory, "jtbeta.zip"); - if(File.Exists(file)) { - _jtBeta = true; - _writeMessage("Extracting JT beta key..."); - ZipFile.ExtractToDirectory(file, keyPath, true); - } - } - - public async Task RunAssetDownloader(string? id = null) - { - List installedAssets = new List(); - List skippedAssets = new List(); - List missingBetaKeys = new List(); - Dictionary results = new Dictionary(); - if(Factory.GetGlobals().Cores == null) { - throw new Exception("Must initialize updater before running update process"); - } - List cores = await getAllCores(); - foreach(Core core in Factory.GetGlobals().Cores) { - if(id != null && core.identifier != id) { - continue; - } - - if(Factory.GetGlobals().SettingsManager.GetCoreSettings(core.identifier).skip) { - continue; - } - - core.downloadAssets = true; - try { - string name = core.identifier; - if(name == null) { - _writeMessage("Core Name is required. Skipping."); - continue; - } - _writeMessage(core.identifier); - results = await core.DownloadAssets(); - installedAssets.AddRange(results["installed"] as List); - skippedAssets.AddRange(results["skipped"] as List); - if((bool)results["missingBetaKey"]) { - missingBetaKeys.Add(core.identifier); - } - Divide(); - } catch(Exception e) { - _writeMessage("Uh oh something went wrong."); - _writeMessage(e.Message); - } - } - - UpdateProcessCompleteEventArgs args = new UpdateProcessCompleteEventArgs(); - args.Message = "All Done"; - args.InstalledAssets = installedAssets; - args.SkippedAssets = skippedAssets; - args.MissingBetaKeys = missingBetaKeys; - OnUpdateProcessComplete(args); - } - - public async Task ForceDisplayModes(string? id = null) - { - if(Factory.GetGlobals().Cores == null) { - throw new Exception("Must initialize updater before running update process"); - } - List cores = await getAllCores(); - foreach(Core core in Factory.GetGlobals().Cores) { - if(id != null && core.identifier != id) { - continue; - } - - if(Factory.GetGlobals().SettingsManager.GetCoreSettings(core.identifier).skip) { - continue; - } - - core.downloadAssets = true; - try { - string name = core.identifier; - if(name == null) { - _writeMessage("Core Name is required. Skipping."); - continue; - } - _writeMessage("Updating " + core.identifier); - await core.AddDisplayModes(); - Divide(); - } catch(Exception e) { - _writeMessage("Uh oh something went wrong."); - _writeMessage(e.Message); - } - } - _writeMessage("Finished."); - } - - private void Divide() - { - _writeMessage("-------------"); - } - - public async Task> GetLocalCores() - { - string coresDirectory = Path.Combine(Factory.GetGlobals().UpdateDirectory, "Cores"); - string[] directories = Directory.GetDirectories(coresDirectory,"*", SearchOption.TopDirectoryOnly); - List all = new List(); - foreach(string name in directories) { - string n = Path.GetFileName(name); - var matches = Factory.GetGlobals().Cores.Where(i=>i.identifier == n); - if(matches.Count() == 0) { - Core c = new Core { - identifier = n - }; - c.platform = c.ReadPlatformFile(); - all.Add(c); - } - } - - return all; - } - - public void SetGithubApiKey(string key) - { - _githubApiKey = key; - } - - protected virtual void OnUpdateProcessComplete(UpdateProcessCompleteEventArgs e) - { - EventHandler handler = UpdateProcessComplete; - if(handler != null) - { - handler(this, e); - } - RefreshInstalledCores(); - } - - public async Task UpdateFirmware() - { - string version = ""; - _writeMessage("Checking for firmware updates..."); - var details = await AnalogueFirmware.GetDetails(); - - string[] parts = details.download_url.Split("/"); - string filename = parts[parts.Length-1]; - string filepath = Path.Combine(Factory.GetGlobals().UpdateDirectory, filename); - if(!File.Exists(filepath) || !Util.CompareChecksum(filepath, details.md5, Util.HashTypes.MD5)) { - version = filename; - var oldfiles = Directory.GetFiles(Factory.GetGlobals().UpdateDirectory, FIRMWARE_FILENAME_PATTERN); - _writeMessage("Firmware update found. Downloading..."); - await Factory.GetHttpHelper().DownloadFileAsync(details.download_url, Path.Combine(Factory.GetGlobals().UpdateDirectory, filename)); - _writeMessage("Download Complete"); - _writeMessage(Path.Combine(Factory.GetGlobals().UpdateDirectory, filename)); - foreach (string oldfile in oldfiles) { - if (File.Exists(oldfile) && Path.GetFileName(oldfile) != filename) { - _writeMessage("Deleting old firmware file..."); - File.Delete(oldfile); - } - } - _writeMessage("To install firmware, restart your Pocket."); - } else { - _writeMessage("Firmware up to date."); - } - Divide(); - return version; - } - - public void DeleteSkippedCores(bool value) - { - _deleteSkippedCores = value; - } - - public async Task DeleteCore(Core core, bool force = false, bool nuke = false) - { - if(!_deleteSkippedCores || !force) { - return; - } - - core.Uninstall(nuke); - } - - private void updater_StatusUpdated(object sender, StatusUpdatedEventArgs e) - { - this.OnStatusUpdated(e); - } - public event EventHandler? UpdateProcessComplete; - - public void SetDownloadProgressHandler(EventHandler handler) - { - Factory.GetHttpHelper().DownloadProgressUpdate += handler; - } -} - -public class UpdateProcessCompleteEventArgs : EventArgs -{ - /// - /// Some kind of results - /// - public string Message { get; set; } - public List> InstalledCores { get; set; } - public List InstalledAssets { get; set; } - public List SkippedAssets { get; set; } - public string FirmwareUpdated { get; set; } = ""; - public List MissingBetaKeys { get; set; } -} diff --git a/src/exceptions/MissingRequiredInstanceFiles.cs b/src/exceptions/MissingRequiredInstanceFiles.cs index 8ed62583..fd4dcb60 100644 --- a/src/exceptions/MissingRequiredInstanceFiles.cs +++ b/src/exceptions/MissingRequiredInstanceFiles.cs @@ -1,20 +1,12 @@ -namespace pannella.analoguepocket; - -using System; +namespace Pannella; public class MissingRequiredInstanceFiles : Exception { - public MissingRequiredInstanceFiles() - { - } + public MissingRequiredInstanceFiles() { } public MissingRequiredInstanceFiles(string message) - : base(message) - { - } + : base(message) { } public MissingRequiredInstanceFiles(string message, Exception inner) - : base(message, inner) - { - } -} \ No newline at end of file + : base(message, inner) { } +} diff --git a/src/helpers/GlobalHelper.cs b/src/helpers/GlobalHelper.cs index 516ab7c2..a2e18370 100644 --- a/src/helpers/GlobalHelper.cs +++ b/src/helpers/GlobalHelper.cs @@ -1,42 +1,63 @@ -using System.IO; -using System.Net.Http; +using Pannella.Models; +using Pannella.Models.Archive; +using Pannella.Services; -namespace pannella.analoguepocket; +namespace Pannella.Helpers; -public class GlobalHelper +public static class GlobalHelper { - private static GlobalHelper instance = null; - private static object syncLock = new object(); - public archiveorg.Archive ArchiveFiles { get; set; } - public SettingsManager? SettingsManager { get; set ;} - public string UpdateDirectory { get; set; } - public string SettingsPath { get; set; } - public string[] Blacklist { get; set; } - public List? Cores { get; set; } - public List? InstalledCores { get; set; } - - private GlobalHelper() - { - - } + public static Archive ArchiveFiles { get; private set; } + public static SettingsManager SettingsManager { get; private set ;} + public static string UpdateDirectory { get; private set; } + public static string[] Blacklist { get; private set; } + public static List Cores { get; private set; } + public static List InstalledCores { get; private set; } - public static GlobalHelper Instance + private static bool isInitialized; + + public static async void Initialize(string path) { - get + if (!isInitialized) { - lock (syncLock) + isInitialized = true; + UpdateDirectory = path; + SettingsManager = new SettingsManager(path); + Cores = await CoresService.GetCores(); + SettingsManager.InitializeCoreSettings(Cores); + RefreshInstalledCores(); + Blacklist = await AssetsService.GetBlacklist(); + + Console.WriteLine("Loading Assets Index..."); + + if (SettingsManager.GetConfig().use_custom_archive) { - if (GlobalHelper.instance == null) { - GlobalHelper.instance = new GlobalHelper(); - } + var custom = SettingsManager.GetConfig().custom_archive; + Uri baseUrl = new Uri(custom["url"]); + Uri url = new Uri(baseUrl, custom["index"]); - return GlobalHelper.instance; + ArchiveFiles = await ArchiveService.GetFilesCustom(url.ToString()); + } + else + { + ArchiveFiles = await ArchiveService.GetFiles(SettingsManager.GetConfig().archive_name); } + + RefreshInstalledCores(); } - } + } + + public static void ReloadSettings() + { + SettingsManager = new SettingsManager(UpdateDirectory, Cores); + } + + public static void RefreshInstalledCores() + { + InstalledCores = Cores.Where(c => c.IsInstalled()).ToList(); + } - public Core? GetCore(string identifier) + public static Core GetCore(string identifier) { - return instance.Cores.Find(i => i.identifier == identifier); + return Cores.Find(i => i.identifier == identifier); } } diff --git a/src/helpers/Hacks.cs b/src/helpers/Hacks.cs deleted file mode 100644 index 746df410..00000000 --- a/src/helpers/Hacks.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.IO; - -namespace pannella.analoguepocket; - -public class Hacks -{ - private static string gamegearCore = @"{ - ""core"": { - ""magic"": ""APF_VER_1"", - ""metadata"": { - ""platform_ids"": [ - ""gg"" - ], - ""shortname"": ""GG"", - ""description"": ""GG Core"", - ""author"": ""Spiritualized"", - ""url"": """", - ""version"": ""1.3.0"", - ""date_release"": ""2022-08-25"" - }, - ""framework"": { - ""target_product"": ""Analogue Pocket"", - ""version_required"": ""1.1"", - ""sleep_supported"": true, - ""dock"": { - ""supported"": true, - ""analog_output"": false - }, - ""hardware"": { - ""link_port"": false, - ""cartridge_adapter"": -1 - } - }, - ""cores"": [ - { - ""name"": ""default"", - ""id"": 0, - ""filename"": ""gg.rev"" - } - ] - } -}"; - - public static void GamegearFix(string path) - { - string corefile = Path.Combine(path, "Cores", "Spiritualized.GG", "core.json"); - File.WriteAllText(corefile, gamegearCore, System.Text.Encoding.Default); - } - -} diff --git a/src/helpers/HttpHelper.cs b/src/helpers/HttpHelper.cs index f862f62b..4f6068b3 100644 --- a/src/helpers/HttpHelper.cs +++ b/src/helpers/HttpHelper.cs @@ -1,18 +1,16 @@ -using System.IO; -using System.Net.Http; - -namespace pannella.analoguepocket; +namespace Pannella.Helpers; public class HttpHelper { - private static HttpHelper instance = null; - private static object syncLock = new object(); - private HttpClient client = null; + private static HttpHelper instance; + private static readonly object syncLock = new(); + private HttpClient client; + public event EventHandler DownloadProgressUpdate; private HttpHelper() { - createClient(); + this.CreateClient(); } public static HttpHelper Instance @@ -21,30 +19,33 @@ public static HttpHelper Instance { lock (syncLock) { - if (HttpHelper.instance == null) { - HttpHelper.instance = new HttpHelper(); - } - - return HttpHelper.instance; + return instance ??= new HttpHelper(); } } } - - public async Task DownloadFileAsync(string uri, string outputPath, int timeout = 100) - { + public async Task DownloadFileAsync(string uri, string outputPath, int timeout = 100) + { bool console = false; - try { + + try + { var test = Console.WindowWidth; + console = true; - } catch (Exception) { } + } + catch (Exception) + { + // Do Nothing. + } using var cts = new CancellationTokenSource(); cts.CancelAfter(TimeSpan.FromSeconds(timeout)); - Uri? uriResult; - if (!Uri.TryCreate(uri, UriKind.Absolute, out uriResult)) + if (!Uri.TryCreate(uri, UriKind.Absolute, out _)) + { throw new InvalidOperationException("URI is invalid."); + } using HttpResponseMessage r = await this.client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, cts.Token); @@ -59,23 +60,31 @@ public async Task DownloadFileAsync(string uri, string outputPath, int timeout = while (isMoreToRead) { var read = await stream.ReadAsync(buffer, 0, buffer.Length); + if (read == 0) { isMoreToRead = false; - if (console) { + + if (console) + { Console.Write("\r"); } } else { readSoFar += read; + var progress = (double)readSoFar / totalSize; - if (console) { + + if (console) + { 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 (readSoFar == totalSize) { Console.CursorLeft = 0; @@ -83,52 +92,57 @@ public async Task DownloadFileAsync(string uri, string outputPath, int timeout = Console.CursorLeft = 0; } } - DownloadProgressEventArgs args = new DownloadProgressEventArgs(); - args.progress = progress; + + DownloadProgressEventArgs args = new() + { + Progress = progress + }; + OnDownloadProgressUpdate(args); + await fileStream.WriteAsync(buffer, 0, read); } } } - public async Task GetHTML(string uri, bool allowRedirect = true) - { - Uri? uriResult; - - if (!Uri.TryCreate(uri, UriKind.Absolute, out uriResult)) + public async Task GetHTML(string uri, bool allowRedirect = true) + { + if (!Uri.TryCreate(uri, UriKind.Absolute, out _)) + { throw new InvalidOperationException("URI is invalid."); + } - if(!allowRedirect) { - createClient(false); + if (!allowRedirect) + { + this.CreateClient(false); } var response = await this.client.GetAsync(uri); string html = await response.Content.ReadAsStringAsync(); - if(!allowRedirect) { - createClient(); + if (!allowRedirect) + { + this.CreateClient(); } - + return html; - } + } - private void createClient(bool allowRedirect = true) - { - this.client = new HttpClient(new HttpClientHandler() { AllowAutoRedirect = allowRedirect }); - this.client.Timeout = TimeSpan.FromMinutes(10); //10min - } + private void CreateClient(bool allowRedirect = true) + { + this.client = new HttpClient(new HttpClientHandler { AllowAutoRedirect = allowRedirect }); + this.client.Timeout = TimeSpan.FromMinutes(10); // 10min + } - protected virtual void OnDownloadProgressUpdate(DownloadProgressEventArgs e) + private void OnDownloadProgressUpdate(DownloadProgressEventArgs e) { EventHandler handler = DownloadProgressUpdate; - if(handler != null) - { - handler(this, e); - } + + handler?.Invoke(this, e); } } public class DownloadProgressEventArgs : EventArgs { - public double progress = 0; + public double Progress; } diff --git a/src/helpers/SemverUtil.cs b/src/helpers/SemverUtil.cs index abb8481a..13c7adbe 100644 --- a/src/helpers/SemverUtil.cs +++ b/src/helpers/SemverUtil.cs @@ -1,36 +1,41 @@ -namespace pannella.analoguepocket; - using System.Text.RegularExpressions; +namespace Pannella.Helpers; + public class SemverUtil { private const string SEMVER_FINDER = @"\D*(\d+(\.\d+)*\.\d+)\D*"; - public static string? FindSemver(string input) + public static string FindSemver(string input) { Regex r = new Regex(SEMVER_FINDER); Match matches = r.Match(input); - if(matches == null || matches.Groups.Count <= 1) { + + if (matches.Groups.Count <= 1) + { return null; } + var semver = matches.Groups[1].Value; - //TODO throw some error if it doesn't find a semver in the tag + + // TODO: throw some error if it doesn't find a semver in the tag + semver = CompleteSemver(semver); return semver; } - /// - /////even though its technically not a valid semver, allow use of 2 part versions, and just add a .0 to complete the 3rd part - /// - public static string CompleteSemver(string version) + // Even though its technically not a valid semver, allow use of 2 part versions, + // and just add a .0 to complete the 3rd part + private static string CompleteSemver(string version) { string[] parts = version.Split("."); - if(parts.Length == 2) { + if (parts.Length == 2) + { version += ".0"; } - + return version; } @@ -38,22 +43,18 @@ public static bool SemverCompare(string semverA, string semverB) { Version verA = Version.Parse(semverA); Version verB = Version.Parse(semverB); - - switch(verA.CompareTo(verB)) + + switch (verA.CompareTo(verB)) { case 0: case -1: return false; + case 1: return true; + default: return true; } } - - public static bool IsActuallySemver(string potentiallySemver) - { - Version? ver = null; - return Version.TryParse(potentiallySemver, out ver); - } -} \ No newline at end of file +} diff --git a/src/helpers/StringConverter.cs b/src/helpers/StringConverter.cs index 63aef1a9..df5bc635 100644 --- a/src/helpers/StringConverter.cs +++ b/src/helpers/StringConverter.cs @@ -1,25 +1,22 @@ -namespace pannella; using System.Text.Json; +using System.Text.Json.Serialization; -public class StringConverter : System.Text.Json.Serialization.JsonConverter +namespace Pannella.Helpers; + +public class StringConverter : JsonConverter { public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (reader.TokenType == JsonTokenType.Number) - { - var stringValue = reader.GetInt32(); - return stringValue.ToString(); - } - else if (reader.TokenType == JsonTokenType.String) + return reader.TokenType switch { - return reader.GetString(); - } - - throw new System.Text.Json.JsonException(); + JsonTokenType.Number => reader.GetInt32().ToString(), + JsonTokenType.String => reader.GetString(), + _ => throw new JsonException() + }; } public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) { writer.WriteStringValue(value); } -} \ No newline at end of file +} diff --git a/src/helpers/Util.cs b/src/helpers/Util.cs index f900cedc..c271effa 100644 --- a/src/helpers/Util.cs +++ b/src/helpers/Util.cs @@ -1,14 +1,16 @@ -using System.IO; -using Force.Crc32; using System.Security.Cryptography; -namespace pannella.analoguepocket; +using Force.Crc32; + +namespace Pannella.Helpers; public class Util { - private static string _platformsDirectory = "Platforms"; - private static string _temp = "imagesbackup"; + private const string PLATFORMS_DIRECTORY = "Platforms"; + private static readonly string[] BAD_DIRS = { "__MACOSX" }; - public enum HashTypes { + + public enum HashTypes + { CRC32, MD5 } @@ -20,7 +22,9 @@ public static void CopyDirectory(string sourceDir, string destinationDir, bool r // Check if the source directory exists if (!dir.Exists) + { throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}"); + } // Cache directories before we start copying DirectoryInfo[] dirs = dir.GetDirectories(); @@ -32,6 +36,7 @@ public static void CopyDirectory(string sourceDir, string destinationDir, bool r foreach (FileInfo file in dir.GetFiles()) { string targetFilePath = Path.Combine(destinationDir, file.Name); + file.CopyTo(targetFilePath, overwrite); } @@ -41,6 +46,7 @@ public static void CopyDirectory(string sourceDir, string destinationDir, bool r foreach (DirectoryInfo subDir in dirs) { string newDestinationDir = Path.Combine(destinationDir, subDir.Name); + CopyDirectory(subDir.FullName, newDestinationDir, true, overwrite); } } @@ -49,82 +55,95 @@ public static void CopyDirectory(string sourceDir, string destinationDir, bool r public static void CleanDir(string source, bool preservePlatformsFolder = false, string platform = "") { // Clean up any bad directories (like Mac OS directories). - foreach(var dir in BAD_DIRS) { - try { + foreach (var dir in BAD_DIRS) + { + try + { Directory.Delete(Path.Combine(source, dir), true); } - catch { } + catch + { + // Ignore + } } - if(preservePlatformsFolder) { - string existing = Path.Combine(Factory.GetGlobals().UpdateDirectory, _platformsDirectory, platform + ".json"); - if(File.Exists(existing)) { - try { - string dir = Path.Combine(source, _platformsDirectory); + if (preservePlatformsFolder) + { + string existing = Path.Combine(GlobalHelper.UpdateDirectory, PLATFORMS_DIRECTORY, platform + ".json"); + + if (File.Exists(existing)) + { + try + { + string dir = Path.Combine(source, PLATFORMS_DIRECTORY); + Directory.Delete(dir, true); } - catch { } + catch + { + // Ignore + } } } // Clean files. - var files = Directory.EnumerateFiles(source).Where(file => isBadFile(Path.GetFileName(file))); - foreach(var file in files) { - try { + var files = Directory.EnumerateFiles(source).Where(file => IsBadFile(Path.GetFileName(file))); + + foreach (var file in files) + { + try + { File.Delete(file); } - catch { } + catch + { + // Ignore + } } // Recurse through subdirectories. var dirs = Directory.GetDirectories(source); - foreach(var dir in dirs) { - CleanDir(Path.Combine(source, Path.GetFileName(dir))); - } - static bool isBadFile(string name) + foreach (var dir in dirs) { - if (name.StartsWith('.')) return true; - if (name.EndsWith(".mra")) return true; - if (name.EndsWith(".txt")) return true; - return false; + CleanDir(Path.Combine(source, Path.GetFileName(dir))); } } - public static string GetCRC32(string filepath) + private static bool IsBadFile(string name) { - if(File.Exists(filepath)) { - var checksum = Crc32Algorithm.Compute(File.ReadAllBytes(filepath)); - return checksum.ToString("x8"); - } else { - throw new Exception("File doesn't exist. Cannot compute checksum"); - } + return name.StartsWith('.') || name.EndsWith(".mra") || name.EndsWith(".txt"); } - public static string GetMD5(string filepath) + public static bool CompareChecksum(string filepath, string checksum, HashTypes type = HashTypes.CRC32) { - if(File.Exists(filepath)) { - var checksum = MD5.HashData(File.ReadAllBytes(filepath)); - return Convert.ToHexString(checksum); - } else { - throw new Exception("File doesn't exist. Cannot compute checksum"); + if (!File.Exists(filepath)) + { + throw new Exception("File doesn't exist. Cannot compute checksum."); } - } - public static bool CompareChecksum(string filepath, string checksum, HashTypes type = HashTypes.CRC32) - { string hash; - switch(type) { + + switch (type) + { case HashTypes.MD5: - hash = GetMD5(filepath); + { + var newChecksum = MD5.HashData(File.ReadAllBytes(filepath)); + + hash = Convert.ToHexString(newChecksum); break; + } + case HashTypes.CRC32: default: - hash = GetCRC32(filepath); + { + var newChecksum = Crc32Algorithm.Compute(File.ReadAllBytes(filepath)); + + hash = newChecksum.ToString("x8"); break; + } } - + return hash.Equals(checksum, StringComparison.CurrentCultureIgnoreCase); } - } diff --git a/src/models/Analogue/AnalogueData.cs b/src/models/Analogue/AnalogueData.cs deleted file mode 100644 index 035fbfa7..00000000 --- a/src/models/Analogue/AnalogueData.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace pannella.analoguepocket.Analogue; - -public class Data -{ - public DataSlot[] data_slots { get; set; } -} \ No newline at end of file diff --git a/src/models/Analogue/AnalogueDataJSON.cs b/src/models/Analogue/AnalogueDataJSON.cs deleted file mode 100644 index a4c27df4..00000000 --- a/src/models/Analogue/AnalogueDataJSON.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace pannella.analoguepocket.Analogue; - -public class DataJSON -{ - public Data? data { get; set; } -} \ No newline at end of file diff --git a/src/models/Analogue/AnalogueDataSlot.cs b/src/models/Analogue/AnalogueDataSlot.cs deleted file mode 100644 index 776a0fc6..00000000 --- a/src/models/Analogue/AnalogueDataSlot.cs +++ /dev/null @@ -1,64 +0,0 @@ -namespace pannella.analoguepocket.Analogue; - -using System.Collections; - -public class DataSlot -{ - public string id { get; set; } - public string name { get; set; } - public bool required { get; set; } - public string parameters { get; set; } - - public string? filename { get; set; } - - public string[]? alternate_filenames{ get; set; } - - public string? md5 { get; set; } - - private BitArray? getBits() - { - if(parameters == null) { - return null; - } - int p = 0; - if(parameters.StartsWith("0x")) { - p = Convert.ToInt32(parameters, 16); - } else { - p = Int32.Parse(parameters); - } - - byte[] bytes = System.BitConverter.GetBytes(p); - BitArray bits = new BitArray(bytes); - - return bits; - } - - public bool isCoreSpecific() - { - var bits = getBits(); - - if(bits == null) { - return false; - } - - return bits[1]; - } - - public int getPlatformIdIndex() - { - var bits = getBits(); - - if(bits == null) { - return 0; - } - - var temp = new BitArray(2); - temp[1] = bits[25]; - temp[0] = bits[24]; - - int[] index = new int[1]; - temp.CopyTo(index, 0); - - return index[0]; - } -} \ No newline at end of file diff --git a/src/models/Analogue/AnalogueInstance.cs b/src/models/Analogue/AnalogueInstance.cs deleted file mode 100644 index c2d153d9..00000000 --- a/src/models/Analogue/AnalogueInstance.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace pannella.analoguepocket.Analogue; - -public class Instance -{ - public DataSlot[] data_slots { get; set; } - public string data_path { get; set; } = ""; - public string magic { get; set; } = "APF_VER_1"; -} \ No newline at end of file diff --git a/src/models/Analogue/AnalogueInstanceDataSlot.cs b/src/models/Analogue/AnalogueInstanceDataSlot.cs deleted file mode 100644 index bfe67615..00000000 --- a/src/models/Analogue/AnalogueInstanceDataSlot.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace pannella.analoguepocket.Analogue; - -using System.Collections; - -public class InstanceDataSlot -{ - public string id { get; set; } - public string? filename { get; set; } -} \ No newline at end of file diff --git a/src/models/Analogue/AnalogueMetadata.cs b/src/models/Analogue/AnalogueMetadata.cs deleted file mode 100644 index c23380a2..00000000 --- a/src/models/Analogue/AnalogueMetadata.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace pannella.analoguepocket.Analogue; - -public class Metadata -{ - public string[]? platform_ids { get; set; } - public string? shortname { get; set; } - public string? description { get; set; } - public string? author { get; set; } - public string? url { get; set; } - public string? version { get; set; } - public string? date_release { get; set; } -} \ No newline at end of file diff --git a/src/models/Analogue/AnalogueSimpleInstance.cs b/src/models/Analogue/AnalogueSimpleInstance.cs deleted file mode 100644 index db2184e8..00000000 --- a/src/models/Analogue/AnalogueSimpleInstance.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace pannella.analoguepocket.Analogue; - -public class SimpleInstance -{ - public InstanceDataSlot[] data_slots { get; set; } - public string data_path { get; set; } = ""; - public string magic { get; set; } = "APF_VER_1"; -} \ No newline at end of file diff --git a/src/models/Analogue/Core/AnalogueCore.cs b/src/models/Analogue/Core/AnalogueCore.cs new file mode 100644 index 00000000..5710b4b3 --- /dev/null +++ b/src/models/Analogue/Core/AnalogueCore.cs @@ -0,0 +1,10 @@ +namespace Pannella.Models.Analogue.Core; + +public class Core +{ + public Metadata metadata { get; set; } + + public string magic { get; set; } + + public Framework framework { get; set; } +} diff --git a/src/models/Analogue/AnalogueFramework.cs b/src/models/Analogue/Core/AnalogueFramework.cs similarity index 78% rename from src/models/Analogue/AnalogueFramework.cs rename to src/models/Analogue/Core/AnalogueFramework.cs index 8a2637cf..8a28eef5 100644 --- a/src/models/Analogue/AnalogueFramework.cs +++ b/src/models/Analogue/Core/AnalogueFramework.cs @@ -1,8 +1,8 @@ -namespace pannella.analoguepocket.Analogue; +namespace Pannella.Models.Analogue.Core; public class Framework { public string target_product { get; set; } public string version_required { get; set; } public bool sleep_supported { get; set; } -} \ No newline at end of file +} diff --git a/src/models/Analogue/Core/AnalogueMetadata.cs b/src/models/Analogue/Core/AnalogueMetadata.cs new file mode 100644 index 00000000..635bb773 --- /dev/null +++ b/src/models/Analogue/Core/AnalogueMetadata.cs @@ -0,0 +1,12 @@ +namespace Pannella.Models.Analogue.Core; + +public class Metadata +{ + public string[] platform_ids { get; set; } + public string shortname { get; set; } + public string description { get; set; } + public string author { get; set; } + public string url { get; set; } + public string version { get; set; } + public string date_release { get; set; } +} diff --git a/src/models/Analogue/Cores/Core/AnalogueCore.cs b/src/models/Analogue/Cores/Core/AnalogueCore.cs deleted file mode 100644 index 9e2b0578..00000000 --- a/src/models/Analogue/Cores/Core/AnalogueCore.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace pannella.analoguepocket.Analogue.Cores.Core; - -public class Core -{ - public Metadata? metadata { get; set; } - public string? magic { get; set; } - - public Framework? framework { get; set; } - -} \ No newline at end of file diff --git a/src/models/Analogue/Cores/Video/Video.cs b/src/models/Analogue/Cores/Video/Video.cs deleted file mode 100644 index 12acd054..00000000 --- a/src/models/Analogue/Cores/Video/Video.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace pannella.analoguepocket.Analogue.Cores.Video; - -public class Video -{ - public string? magic { get; set; } - - public List? scaler_modes { get; set; } - - public List? display_modes { get; set; } - -} \ No newline at end of file diff --git a/src/models/Analogue/Data/AnalogueData.cs b/src/models/Analogue/Data/AnalogueData.cs new file mode 100644 index 00000000..814143ab --- /dev/null +++ b/src/models/Analogue/Data/AnalogueData.cs @@ -0,0 +1,8 @@ +using Pannella.Models.Analogue.Shared; + +namespace Pannella.Models.Analogue.Data; + +public class Data +{ + public DataSlot[] data_slots { get; set; } +} diff --git a/src/models/Analogue/Data/AnalogueDataJSON.cs b/src/models/Analogue/Data/AnalogueDataJSON.cs new file mode 100644 index 00000000..830bbf09 --- /dev/null +++ b/src/models/Analogue/Data/AnalogueDataJSON.cs @@ -0,0 +1,6 @@ +namespace Pannella.Models.Analogue.Data; + +public class DataJSON +{ + public Data data { get; set; } +} diff --git a/src/models/Analogue/Instance/AnalogueInstance.cs b/src/models/Analogue/Instance/AnalogueInstance.cs new file mode 100644 index 00000000..2c981b1b --- /dev/null +++ b/src/models/Analogue/Instance/AnalogueInstance.cs @@ -0,0 +1,10 @@ +using Pannella.Models.Analogue.Shared; + +namespace Pannella.Models.Analogue.Instance; + +public class Instance +{ + public DataSlot[] data_slots { get; set; } + public string data_path { get; set; } = string.Empty; + public string magic { get; set; } = "APF_VER_1"; +} diff --git a/src/models/Analogue/AnalogueInstanceJSON.cs b/src/models/Analogue/Instance/AnalogueInstanceJSON.cs similarity index 60% rename from src/models/Analogue/AnalogueInstanceJSON.cs rename to src/models/Analogue/Instance/AnalogueInstanceJSON.cs index 97df9e68..b8a43eb8 100644 --- a/src/models/Analogue/AnalogueInstanceJSON.cs +++ b/src/models/Analogue/Instance/AnalogueInstanceJSON.cs @@ -1,6 +1,6 @@ -namespace pannella.analoguepocket.Analogue; +namespace Pannella.Models.Analogue.Instance; public class InstanceJSON { public Instance instance { get; set; } -} \ No newline at end of file +} diff --git a/src/models/Analogue/Instance/Simple/AnalogueSimpleInstance.cs b/src/models/Analogue/Instance/Simple/AnalogueSimpleInstance.cs new file mode 100644 index 00000000..ec00500c --- /dev/null +++ b/src/models/Analogue/Instance/Simple/AnalogueSimpleInstance.cs @@ -0,0 +1,8 @@ +namespace Pannella.Models.Analogue.Instance.Simple; + +public class SimpleInstance +{ + public SimpleDataSlot[] data_slots { get; set; } + public string data_path { get; set; } = string.Empty; + public string magic { get; set; } = "APF_VER_1"; +} diff --git a/src/models/Analogue/Instance/Simple/AnalogueSimpleInstanceDataSlot.cs b/src/models/Analogue/Instance/Simple/AnalogueSimpleInstanceDataSlot.cs new file mode 100644 index 00000000..953525e2 --- /dev/null +++ b/src/models/Analogue/Instance/Simple/AnalogueSimpleInstanceDataSlot.cs @@ -0,0 +1,7 @@ +namespace Pannella.Models.Analogue.Instance.Simple; + +public class SimpleDataSlot +{ + public string id { get; set; } + public string filename { get; set; } +} diff --git a/src/models/Analogue/AnalogueSimpleInstanceJSON.cs b/src/models/Analogue/Instance/Simple/AnalogueSimpleInstanceJSON.cs similarity index 60% rename from src/models/Analogue/AnalogueSimpleInstanceJSON.cs rename to src/models/Analogue/Instance/Simple/AnalogueSimpleInstanceJSON.cs index be49cfb6..7c1b7326 100644 --- a/src/models/Analogue/AnalogueSimpleInstanceJSON.cs +++ b/src/models/Analogue/Instance/Simple/AnalogueSimpleInstanceJSON.cs @@ -1,6 +1,6 @@ -namespace pannella.analoguepocket.Analogue; +namespace Pannella.Models.Analogue.Instance.Simple; public class SimpleInstanceJSON { public SimpleInstance instance { get; set; } -} \ No newline at end of file +} diff --git a/src/models/Analogue/Release.cs b/src/models/Analogue/Release.cs index f8b433a6..f8040589 100644 --- a/src/models/Analogue/Release.cs +++ b/src/models/Analogue/Release.cs @@ -1,12 +1,12 @@ -namespace pannella.analoguepocket.Analogue; +namespace Pannella.Models.Analogue; public class ReleaseDetails { - public string? url { get; set; } - public string? version { get; set; } - public string? published_at { get; set; } - public string? download_url { get; set; } - public string? md5 { get; set; } - public string? file_size { get; set; } - public string? release_notes_html { get; set; } -} \ No newline at end of file + public string url { get; set; } + public string version { get; set; } + public string published_at { get; set; } + public string download_url { get; set; } + public string md5 { get; set; } + public string file_size { get; set; } + public string release_notes_html { get; set; } +} diff --git a/src/models/Analogue/Shared/AnalogueDataSlot.cs b/src/models/Analogue/Shared/AnalogueDataSlot.cs new file mode 100644 index 00000000..c80147df --- /dev/null +++ b/src/models/Analogue/Shared/AnalogueDataSlot.cs @@ -0,0 +1,67 @@ +using System.Collections; + +namespace Pannella.Models.Analogue.Shared; + +public class DataSlot +{ + public string id { get; set; } + public string name { get; set; } + public bool required { get; set; } + public string parameters { get; set; } + public string filename { get; set; } + public string[] alternate_filenames { get; set; } + public string md5 { get; set; } + + private BitArray GetBits() + { + if (parameters == null) + { + return null; + } + + int p; + + if (parameters.StartsWith("0x")) + { + p = Convert.ToInt32(parameters, 16); + } + else + { + p = int.Parse(parameters); + } + + byte[] bytes = BitConverter.GetBytes(p); + BitArray bits = new BitArray(bytes); + + return bits; + } + + public bool IsCoreSpecific() + { + var bits = this.GetBits(); + + return bits != null && bits[1]; + } + + public int GetPlatformIdIndex() + { + var bits = this.GetBits(); + + if (bits == null) + { + return 0; + } + + var temp = new BitArray(2) + { + [1] = bits[25], + [0] = bits[24] + }; + + int[] index = new int[1]; + + temp.CopyTo(index, 0); + + return index[0]; + } +} diff --git a/src/models/Analogue/Cores/Video/DisplayMode.cs b/src/models/Analogue/Video/AnalogueDisplayMode.cs similarity index 50% rename from src/models/Analogue/Cores/Video/DisplayMode.cs rename to src/models/Analogue/Video/AnalogueDisplayMode.cs index 2a87a29e..f76b0b91 100644 --- a/src/models/Analogue/Cores/Video/DisplayMode.cs +++ b/src/models/Analogue/Video/AnalogueDisplayMode.cs @@ -1,7 +1,6 @@ -namespace pannella.analoguepocket.Analogue.Cores.Video; +namespace Pannella.Models.Analogue.Video; public class DisplayMode { public string id { get; set; } - -} \ No newline at end of file +} diff --git a/src/models/Analogue/Cores/Video/ScalerMode.cs b/src/models/Analogue/Video/AnalogueScalerMode.cs similarity index 81% rename from src/models/Analogue/Cores/Video/ScalerMode.cs rename to src/models/Analogue/Video/AnalogueScalerMode.cs index dc77ab22..b8468fdf 100644 --- a/src/models/Analogue/Cores/Video/ScalerMode.cs +++ b/src/models/Analogue/Video/AnalogueScalerMode.cs @@ -1,4 +1,4 @@ -namespace pannella.analoguepocket.Analogue.Cores.Video; +namespace Pannella.Models.Analogue.Video; public class ScalerMode { @@ -8,4 +8,4 @@ public class ScalerMode public int aspect_h { get; set; } public int rotation { get; set; } public int mirror { get; set; } -} \ No newline at end of file +} diff --git a/src/models/Analogue/Video/AnalogueVideo.cs b/src/models/Analogue/Video/AnalogueVideo.cs new file mode 100644 index 00000000..2d4483fd --- /dev/null +++ b/src/models/Analogue/Video/AnalogueVideo.cs @@ -0,0 +1,10 @@ +namespace Pannella.Models.Analogue.Video; + +public class Video +{ + public string magic { get; set; } + + public List scaler_modes { get; set; } + + public List display_modes { get; set; } +} diff --git a/src/models/Archive/Archive.cs b/src/models/Archive/Archive.cs index 51eac6f3..ad3a1673 100644 --- a/src/models/Archive/Archive.cs +++ b/src/models/Archive/Archive.cs @@ -1,16 +1,15 @@ -namespace archiveorg; - -using System.Linq; +namespace Pannella.Models.Archive; public class Archive { public int files_count { get; set; } public int item_last_updated { get; set; } - public archiveorg.File[] files { get; set; } + public File[] files { get; set; } - public archiveorg.File? GetFile(string filename) + public File GetFile(string filename) { - archiveorg.File? file = files.Where(file => file.name == filename).FirstOrDefault() as archiveorg.File; + File file = this.files.FirstOrDefault(file => file.name == filename); + return file; } -} \ No newline at end of file +} diff --git a/src/models/Archive/File.cs b/src/models/Archive/File.cs index c98992d5..0346d945 100644 --- a/src/models/Archive/File.cs +++ b/src/models/Archive/File.cs @@ -1,9 +1,9 @@ -namespace archiveorg; +namespace Pannella.Models.Archive; public class File { - public string? name { get; set; } - public string? md5 { get; set; } + public string name { get; set; } + public string md5 { get; set; } public string crc32 { get; set; } - public string? source { get; set; } -} \ No newline at end of file + public string source { get; set; } +} diff --git a/src/models/Asset.cs b/src/models/Asset.cs deleted file mode 100644 index 249e264d..00000000 --- a/src/models/Asset.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace pannella.analoguepocket; - -public class Asset -{ - public string platform{ get; set; } = ""; - public string? filename { get; set; } - public bool core_specific { get; set; } - public List? extensions { get; set; } -} \ No newline at end of file diff --git a/src/Base.cs b/src/models/Base.cs similarity index 58% rename from src/Base.cs rename to src/models/Base.cs index e033dc13..ed9e9426 100644 --- a/src/Base.cs +++ b/src/models/Base.cs @@ -1,28 +1,31 @@ -namespace pannella.analoguepocket; +namespace Pannella.Models; public class Base { protected const string ARCHIVE_BASE_URL = "https://archive.org/download"; - public event EventHandler? StatusUpdated; + + public event EventHandler StatusUpdated; + protected void Divide() { - _writeMessage("-------------"); + WriteMessage("-------------"); } - protected void _writeMessage(string message) + protected void WriteMessage(string message) { - StatusUpdatedEventArgs args = new StatusUpdatedEventArgs(); - args.Message = message; + StatusUpdatedEventArgs args = new StatusUpdatedEventArgs + { + Message = message + }; + OnStatusUpdated(args); } - protected virtual void OnStatusUpdated(StatusUpdatedEventArgs e) + protected void OnStatusUpdated(StatusUpdatedEventArgs e) { EventHandler handler = StatusUpdated; - if(handler != null) - { - handler(this, e); - } + + handler?.Invoke(this, e); } } diff --git a/src/models/Core.cs b/src/models/Core.cs index cfb52e11..3554662d 100644 --- a/src/models/Core.cs +++ b/src/models/Core.cs @@ -1,121 +1,149 @@ -namespace pannella.analoguepocket; - -using System.IO; +using System.Collections; +using System.Diagnostics.CodeAnalysis; using System.IO.Compression; -using System.Net.Http; +using System.Net; using System.Text.Json; -using System.Collections; - +using Pannella.Helpers; +using Pannella.Models.Analogue.Data; +using Pannella.Models.Analogue.Instance; +using Pannella.Models.Analogue.Instance.Simple; +using Pannella.Models.Analogue.Video; +using Pannella.Models.InstancePackager; +using Pannella.Models.Updater; +using AnalogueCore = Pannella.Models.Analogue.Core.Core; +using ArchiveFile = Pannella.Models.Archive.File; +using DataSlot = Pannella.Models.Analogue.Shared.DataSlot; +using InstancePackagerDataSlot = Pannella.Models.InstancePackager.DataSlot; + +namespace Pannella.Models; + +[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] public class Core : Base { public string identifier { get; set; } - public Repo? repository { get; set; } - public Platform? platform { get; set; } + public Repo repository { get; set; } + public Platform platform { get; set; } public string platform_id { get; set; } - public Sponsor? sponsor { get; set; } - public string? download_url { get; set; } - public string? release_date { get; set; } - public string? version { get; set; } - public string? betaSlotId = null; - public int betaSlotPlatformIdIndex = 0; - + public Sponsor sponsor { get; set; } + 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 bool requires_license { get; set; } = false; - private const string ZIP_FILE_NAME = "core.zip"; - public bool downloadAssets { get; set; } = Factory.GetGlobals().SettingsManager.GetConfig().download_assets; - public bool buildInstances { get; set; } = Factory.GetGlobals().SettingsManager.GetConfig().build_instance_jsons; + public bool download_assets { get; set; } = GlobalHelper.SettingsManager.GetConfig().download_assets; + + public bool build_instances { get; set; } = GlobalHelper.SettingsManager.GetConfig().build_instance_jsons; - private string[] allModes = { - "0x10", "0x20", "0x30", "0x31", "0x32", "0x40", "0x41", "0x42", "0x51", "0x52", "0xE0" - }; + private const string ZIP_FILE_NAME = "core.zip"; - private string[] gbModes = { - "0x21", "0x22", "0x23" - }; + private static string[] ALL_MODES = { "0x10", "0x20", "0x30", "0x31", "0x32", "0x40", "0x41", "0x42", "0x51", "0x52", "0xE0" }; + private static string[] GB_MODES = { "0x21", "0x22", "0x23" }; public override string ToString() { return platform.name; } - public async Task Install(bool clean = false) + public async Task Install(bool preservePlatformsFolder, bool clean = false) { - if(this.repository == null) { - _writeMessage("Core installed manually. Skipping."); + if (this.repository == null) + { + WriteMessage("Core installed manually. Skipping."); + return false; } - if (clean && this.isInstalled()) { + + if (clean && this.IsInstalled()) + { Delete(); } - //iterate through assets to find the zip release - if(await _installGithubAsset()) { - await this.ReplaceCheck(); + + // iterate through assets to find the zip release + if (await InstallGithubAsset(preservePlatformsFolder)) + { + this.ReplaceCheck(); + return true; - } else { - return false; } + + return false; } - private async Task _installGithubAsset() + private async Task InstallGithubAsset(bool preservePlatformsFolder) { - bool updated = false; - if (this.download_url == null) { - _writeMessage("No release URL found..."); - return updated; + if (this.download_url == null) + { + WriteMessage("No release URL found..."); + + return false; } - _writeMessage("Downloading file " + this.download_url + "..."); - string zipPath = Path.Combine(Factory.GetGlobals().UpdateDirectory, ZIP_FILE_NAME); - string extractPath = Factory.GetGlobals().UpdateDirectory; - await Factory.GetHttpHelper().DownloadFileAsync(this.download_url, zipPath); - _writeMessage("Extracting..."); + WriteMessage("Downloading file " + this.download_url + "..."); + + string zipPath = Path.Combine(GlobalHelper.UpdateDirectory, ZIP_FILE_NAME); + string extractPath = GlobalHelper.UpdateDirectory; + + await HttpHelper.Instance.DownloadFileAsync(this.download_url, zipPath); + + WriteMessage("Extracting..."); + string tempDir = Path.Combine(extractPath, "temp", this.identifier); + ZipFile.ExtractToDirectory(zipPath, tempDir, true); // Clean problematic directories and files. - Util.CleanDir(tempDir, Factory.GetGlobals().SettingsManager.GetConfig().preserve_platforms_folder, this.platform_id); + Util.CleanDir(tempDir, preservePlatformsFolder, this.platform_id); // Move the files into place and delete our core's temp directory. - _writeMessage("Installing..."); + WriteMessage("Installing..."); Util.CopyDirectory(tempDir, extractPath, true, true); Directory.Delete(tempDir, true); // See if the temp directory itself can be removed. - // Probably not needed if we aren't going to multithread this, but this is an async function so let's future proof. + // Probably not needed if we aren't going to multi-thread this, but this is an async function so let's future proof. if (!Directory.GetFiles(Path.Combine(extractPath, "temp")).Any()) + { Directory.Delete(Path.Combine(extractPath, "temp")); + } - updated = true; - File.Delete(zipPath); - return updated; + return true; } - private bool checkUpdateDirectory() + private static void CheckUpdateDirectory() { - if(!Directory.Exists(Factory.GetGlobals().UpdateDirectory)) { + if (!Directory.Exists(GlobalHelper.UpdateDirectory)) + { throw new Exception("Unable to access update directory"); } - - return true; } - public void Delete(bool nuke = false) + private void Delete(bool nuke = false) { - List folders = new List{"Cores", "Presets", "Settings"}; - foreach(string folder in folders) { - string path = Path.Combine(Factory.GetGlobals().UpdateDirectory, folder, this.identifier); - if(Directory.Exists(path)) { - _writeMessage("Deleting " + path); + List folders = new List { "Cores", "Presets", "Settings" }; + + foreach (string folder in folders) + { + string path = Path.Combine(GlobalHelper.UpdateDirectory, folder, this.identifier); + + if (Directory.Exists(path)) + { + WriteMessage("Deleting " + path); Directory.Delete(path, true); } } - if(nuke) { - string path = Path.Combine(Factory.GetGlobals().UpdateDirectory, "Assets", this.platform_id, this.identifier); - if (Directory.Exists(path)) { - _writeMessage("Deleting " + path); + + if (nuke) + { + string path = Path.Combine(GlobalHelper.UpdateDirectory, "Assets", this.platform_id, this.identifier); + + if (Directory.Exists(path)) + { + WriteMessage("Deleting " + path); Directory.Delete(path, true); } } @@ -123,123 +151,108 @@ public void Delete(bool nuke = false) public void Uninstall(bool nuke = false) { - _writeMessage("Uninstalling " + this.identifier); - + WriteMessage("Uninstalling " + this.identifier); + Delete(nuke); - Factory.GetGlobals().SettingsManager.DisableCore(this.identifier); - Factory.GetGlobals().SettingsManager.SaveSettings(); + GlobalHelper.SettingsManager.DisableCore(this.identifier); + GlobalHelper.SettingsManager.SaveSettings(); - _writeMessage("Finished"); + WriteMessage("Finished"); Divide(); } - public Platform? ReadPlatformFile() + public Platform ReadPlatformFile() { - var info = this.getConfig(); + var info = this.GetConfig(); + if (info == null) { return this.platform; } - - string UpdateDirectory = Factory.GetGlobals().UpdateDirectory; - //cores with multiple platforms won't work...not sure any exist right now? - string platformsFolder = Path.Combine(UpdateDirectory, "Platforms"); + string updateDirectory = GlobalHelper.UpdateDirectory; + // cores with multiple platforms won't work...not sure any exist right now? + string platformsFolder = Path.Combine(updateDirectory, "Platforms"); string dataFile = Path.Combine(platformsFolder, info.metadata.platform_ids[0] + ".json"); - var p = JsonSerializer.Deserialize>(File.ReadAllText(dataFile)); - + var p = JsonSerializer.Deserialize>(File.ReadAllText(dataFile)); + return p["platform"]; } - public bool UpdatePlatform(string title, string category = null) + public async Task> DownloadAssets() { - var info = this.getConfig(); - if (info == null) + List installed = new List(); + List skipped = new List(); + bool missingBetaKey = false; + + if (!this.download_assets || !GlobalHelper.SettingsManager.GetCoreSettings(this.identifier).download_assets) { - return false; + return new Dictionary + { + { "installed", installed }, + { "skipped", skipped }, + { "missingBetaKey", false } + }; } - - string UpdateDirectory = Factory.GetGlobals().UpdateDirectory; - //cores with multiple platforms won't work...not sure any exist right now? - string platformsFolder = Path.Combine(UpdateDirectory, "Platforms"); - string dataFile = Path.Combine(platformsFolder, info.metadata.platform_ids[0] + ".json"); - if (!File.Exists(dataFile)) + CheckUpdateDirectory(); + WriteMessage("Looking for Assets"); + AnalogueCore info = this.GetConfig(); + string updateDirectory = GlobalHelper.UpdateDirectory; + // cores with multiple platforms won't work...not sure any exist right now? + string instancesDirectory = Path.Combine(updateDirectory, "Assets", info.metadata.platform_ids[0], this.identifier); + var options = new JsonSerializerOptions { Converters = { new StringConverter() } }; + + DataJSON dataJson = ReadDataJSON(); + + if (this.beta_slot_id != null) { - return false; + // what to do? } - if (platform.name != title || platform.category != category) + if (dataJson.data.data_slots.Length > 0) { - - Dictionary platform = new Dictionary(); - this.platform.name = title; - if (category != null) + foreach (DataSlot slot in dataJson.data.data_slots) { - this.platform.category = category; - } - platform.Add("platform", this.platform); - string json = JsonSerializer.Serialize(platform); - - File.WriteAllText(dataFile, json); - Factory.GetGlobals().SettingsManager.GetConfig().preserve_platforms_folder = true; - Factory.GetGlobals().SettingsManager.SaveSettings(); - } - - return true; - } - - public async Task> DownloadAssets() - { - List installed = new List(); - List skipped = new List(); - bool missingBetaKey = false; - if(!downloadAssets || !Factory.GetGlobals().SettingsManager.GetCoreSettings(this.identifier).download_assets) { - return new Dictionary{ - {"installed", installed }, - {"skipped", skipped }, - {"missingBetaKey", missingBetaKey } - }; - } - checkUpdateDirectory(); - _writeMessage("Looking for Assets"); - Analogue.Cores.Core.Core info = this.getConfig(); - string UpdateDirectory = Factory.GetGlobals().UpdateDirectory; - //cores with multiple platforms won't work...not sure any exist right now? - string instancesDirectory = Path.Combine(UpdateDirectory, "Assets", info.metadata.platform_ids[0], this.identifier); - var options = new JsonSerializerOptions - { - Converters = { new StringConverter() } - }; - - Analogue.DataJSON data = ReadDataJSON(); - if (betaSlotId != null) { + if (slot.filename != null && !slot.filename.EndsWith(".sav") && + !GlobalHelper.Blacklist.Contains(slot.filename)) + { + string path = Path.Combine(updateDirectory, "Assets", info.metadata.platform_ids[0]); - } - if(data.data.data_slots.Length > 0) { - foreach(Analogue.DataSlot slot in data.data.data_slots) { - if(slot.filename != null && !slot.filename.EndsWith(".sav") && !Factory.GetGlobals().Blacklist.Contains(slot.filename)) { - string path = Path.Combine(UpdateDirectory, "Assets", info.metadata.platform_ids[0]); - if(slot.isCoreSpecific()) { + if (slot.IsCoreSpecific()) + { path = Path.Combine(path, this.identifier); - } else { + } + else + { path = Path.Combine(path, "common"); } - List files = new List(); - files.Add(slot.filename); - if (slot.alternate_filenames != null) { + + List files = new List { slot.filename }; + + if (slot.alternate_filenames != null) + { files.AddRange(slot.alternate_filenames); } - foreach (string f in files) { + + foreach (string f in files) + { string filepath = Path.Combine(path, f); - if(File.Exists(filepath) && CheckCRC(filepath)) { - _writeMessage("Already installed: " + f); - } else { - if(await DownloadAsset(f, filepath)) { - installed.Add(filepath.Replace(UpdateDirectory, "")); - } else { - skipped.Add(filepath.Replace(UpdateDirectory, "")); + + if (File.Exists(filepath) && CheckCRC(filepath)) + { + WriteMessage("Already installed: " + f); + } + else + { + if (await DownloadAsset(f, filepath)) + { + installed.Add(filepath.Replace(updateDirectory, "")); + } + else + { + skipped.Add(filepath.Replace(updateDirectory, "")); } } } @@ -247,138 +260,186 @@ public async Task> DownloadAssets() } } - if(this.identifier == "Mazamars312.NeoGeo" || this.identifier == "Mazamars312.NeoGeo_Overdrive") { - return new Dictionary{ - {"installed", installed }, - {"skipped", skipped }, - {"missingBetaKey", false } - }; //nah + if (this.identifier is "Mazamars312.NeoGeo" or "Mazamars312.NeoGeo_Overdrive") + { + return new Dictionary + { + { "installed", installed }, + { "skipped", skipped }, + { "missingBetaKey", false } + }; } - if(CheckInstancePackager()) { + if (CheckInstancePackager()) + { BuildInstanceJSONs(); - return new Dictionary{ - {"installed", installed }, - {"skipped", skipped }, - {"missingBetaKey", missingBetaKey } + + return new Dictionary + { + { "installed", installed }, + { "skipped", skipped }, + { "missingBetaKey", false } }; } - - if(Directory.Exists(instancesDirectory)) { - string[] files = Directory.GetFiles(instancesDirectory,"*.json", SearchOption.AllDirectories); - foreach(string file in files) { - try { - //skip mac ._ files - if(File.GetAttributes(file).HasFlag(FileAttributes.Hidden)) { + + if (Directory.Exists(instancesDirectory)) + { + string[] files = Directory.GetFiles(instancesDirectory, "*.json", SearchOption.AllDirectories); + + foreach (string file in files) + { + try + { + // skip mac ._ files + if (File.GetAttributes(file).HasFlag(FileAttributes.Hidden)) + { continue; } - if(Factory.GetGlobals().SettingsManager.GetConfig().skip_alternative_assets && file.Contains(Path.Combine(instancesDirectory, "_alternatives"))) { + + if (GlobalHelper.SettingsManager.GetConfig().skip_alternative_assets && + file.Contains(Path.Combine(instancesDirectory, "_alternatives"))) + { continue; } - Analogue.InstanceJSON instance = JsonSerializer.Deserialize(File.ReadAllText(file), options); - if(instance.instance.data_slots.Length > 0) { - string data_path = instance.instance.data_path; - foreach(Analogue.DataSlot slot in instance.instance.data_slots) { - var plat = info.metadata.platform_ids[betaSlotPlatformIdIndex]; - if(!CheckBetaMD5(slot, plat)) { - _writeMessage("Invalid or missing beta key."); + + InstanceJSON instanceJson = JsonSerializer.Deserialize(File.ReadAllText(file), options); + + if (instanceJson.instance.data_slots.Length > 0) + { + string dataPath = instanceJson.instance.data_path; + + foreach (DataSlot slot in instanceJson.instance.data_slots) + { + var plat = info.metadata.platform_ids[this.beta_slot_platform_id_index]; + + if (!CheckBetaMD5(slot, plat)) + { + WriteMessage("Invalid or missing beta key."); missingBetaKey = true; } - if(!Factory.GetGlobals().Blacklist.Contains(slot.filename) && !slot.filename.EndsWith(".sav")) { - string path = Path.Combine(UpdateDirectory, "Assets", info.metadata.platform_ids[0], "common", data_path, slot.filename); - if(File.Exists(path) && CheckCRC(path)) { - _writeMessage("Already installed: " + slot.filename); - } else { - if(await DownloadAsset(slot.filename, path)) { - installed.Add(path.Replace(UpdateDirectory, "")); - } else { - skipped.Add(path.Replace(UpdateDirectory, "")); + + if (!GlobalHelper.Blacklist.Contains(slot.filename) && + !slot.filename.EndsWith(".sav")) + { + string path = Path.Combine(updateDirectory, "Assets", info.metadata.platform_ids[0], + "common", dataPath, slot.filename); + + if (File.Exists(path) && CheckCRC(path)) + { + WriteMessage("Already installed: " + slot.filename); + } + else + { + if (await DownloadAsset(slot.filename, path)) + { + installed.Add(path.Replace(updateDirectory, "")); + } + else + { + skipped.Add(path.Replace(updateDirectory, "")); } } } } } - } catch (Exception e) { - _writeMessage("Error while processing " + file); - _writeMessage(e.Message); + } + catch (Exception e) + { + WriteMessage("Error while processing " + file); + WriteMessage(e.Message); } } } - Dictionary results = new Dictionary{ - {"installed", installed }, - {"skipped", skipped }, - {"missingBetaKey", missingBetaKey } + + Dictionary results = new Dictionary + { + { "installed", installed }, + { "skipped", skipped }, + { "missingBetaKey", missingBetaKey } }; return results; } - public Analogue.Cores.Core.Core? getConfig() + public AnalogueCore GetConfig() { - checkUpdateDirectory(); - string file = Path.Combine(Factory.GetGlobals().UpdateDirectory, "Cores", this.identifier, "core.json"); + CheckUpdateDirectory(); + + string file = Path.Combine(GlobalHelper.UpdateDirectory, "Cores", this.identifier, "core.json"); + if (!File.Exists(file)) { return null; } + string json = File.ReadAllText(file); - var options = new JsonSerializerOptions() - { - AllowTrailingCommas = true - }; - Analogue.Cores.Core.Core? config = JsonSerializer.Deserialize>(json, options)["core"]; + var options = new JsonSerializerOptions { AllowTrailingCommas = true }; + AnalogueCore config = JsonSerializer.Deserialize>(json, options)["core"]; return config; } - public Updater.Substitute[]? getSubstitutes() + public Substitute[] GetSubstitutes() { - checkUpdateDirectory(); - string file = Path.Combine(Factory.GetGlobals().UpdateDirectory, "Cores", this.identifier, "updaters.json"); + CheckUpdateDirectory(); + + string file = Path.Combine(GlobalHelper.UpdateDirectory, "Cores", this.identifier, "updaters.json"); + if (!File.Exists(file)) { return null; } - string json = File.ReadAllText(file); - Updater.Updaters? config = JsonSerializer.Deserialize(json); - if (config == null) { - return null; - } + string json = File.ReadAllText(file); + Updaters config = JsonSerializer.Deserialize(json); - return config.previous; + return config?.previous; } - public bool isInstalled() + public bool IsInstalled() { - checkUpdateDirectory(); - string localCoreFile = Path.Combine(Factory.GetGlobals().UpdateDirectory, "Cores", this.identifier, "core.json"); + CheckUpdateDirectory(); + + string localCoreFile = Path.Combine(GlobalHelper.UpdateDirectory, "Cores", this.identifier, "core.json"); + return File.Exists(localCoreFile); } private async Task DownloadAsset(string filename, string destination) { - if(Factory.GetGlobals().ArchiveFiles != null) { - archiveorg.File? file = Factory.GetGlobals().ArchiveFiles.GetFile(filename); - if(file == null) { - _writeMessage("Unable to find " + filename + " in archive"); + if (GlobalHelper.ArchiveFiles != null) + { + ArchiveFile file = GlobalHelper.ArchiveFiles.GetFile(filename); + + if (file == null) + { + WriteMessage("Unable to find " + filename + " in archive"); return false; } } - try { + try + { string url = BuildAssetUrl(filename); int count = 0; - do { - _writeMessage("Downloading " + filename); - await Factory.GetHttpHelper().DownloadFileAsync(url, destination, 600); - _writeMessage("Finished downloading " + filename); + + do + { + WriteMessage("Downloading " + filename); + await HttpHelper.Instance.DownloadFileAsync(url, destination, 600); + WriteMessage("Finished downloading " + filename); count++; - } while(count < 3 && !CheckCRC(destination)); - } catch(HttpRequestException e) { - if(e.StatusCode == System.Net.HttpStatusCode.NotFound) { - _writeMessage("Unable to find " + filename + " in archive"); - } else { - _writeMessage("There was a problem downloading " + filename); + } + while (count < 3 && !CheckCRC(destination)); + } + catch (HttpRequestException e) + { + if (e.StatusCode == HttpStatusCode.NotFound) + { + WriteMessage("Unable to find " + filename + " in archive"); + } + else + { + WriteMessage("There was a problem downloading " + filename); } return false; @@ -387,48 +448,53 @@ private async Task DownloadAsset(string filename, string destination) return true; } - private string BuildAssetUrl(string filename) + private static string BuildAssetUrl(string filename) { - if(Factory.GetGlobals().SettingsManager.GetConfig().use_custom_archive) { - var custom = Factory.GetGlobals().SettingsManager.GetConfig().custom_archive; + if (GlobalHelper.SettingsManager.GetConfig().use_custom_archive) + { + var custom = GlobalHelper.SettingsManager.GetConfig().custom_archive; Uri baseUrl = new Uri(custom["url"]); Uri url = new Uri(baseUrl, filename); return url.ToString(); - } else { - return ARCHIVE_BASE_URL + "/" + Factory.GetGlobals().SettingsManager.GetConfig().archive_name + "/" + filename; } + + return ARCHIVE_BASE_URL + "/" + GlobalHelper.SettingsManager.GetConfig().archive_name + "/" + filename; } private bool CheckCRC(string filepath) { - if(Factory.GetGlobals().ArchiveFiles == null || !Factory.GetGlobals().SettingsManager.GetConfig().crc_check) { + if (GlobalHelper.ArchiveFiles == null || !GlobalHelper.SettingsManager.GetConfig().crc_check) + { return true; } + string filename = Path.GetFileName(filepath); - archiveorg.File? file = Factory.GetGlobals().ArchiveFiles.GetFile(filename); - if(file == null) { + ArchiveFile file = GlobalHelper.ArchiveFiles.GetFile(filename); + + if (file == null) + { return true; //no checksum to compare to } - if(Util.CompareChecksum(filepath, file.crc32)) { + if (Util.CompareChecksum(filepath, file.crc32)) + { return true; } - _writeMessage(filename + ": Bad checksum!"); + WriteMessage(filename + ": Bad checksum!"); return false; } - //return false if a beta ley is required and missing or wrong - private bool CheckBetaMD5(Analogue.DataSlot slot, string platform) + // return false if a beta ley is required and missing or wrong + private bool CheckBetaMD5(DataSlot slot, string platform) { - if(slot.md5 != null && (betaSlotId != null && slot.id == betaSlotId)) { - string UpdateDirectory = Factory.GetGlobals().UpdateDirectory; - string path = Path.Combine(UpdateDirectory, "Assets", platform); + if (slot.md5 != null && (this.beta_slot_id != null && slot.id == this.beta_slot_id)) + { + string updateDirectory = GlobalHelper.UpdateDirectory; + string path = Path.Combine(updateDirectory, "Assets", platform); string filepath = Path.Combine(path, "common", slot.filename); - if(!File.Exists(filepath)) { - return false; - } - return Util.CompareChecksum(filepath, slot.md5, Util.HashTypes.MD5); + + return File.Exists(filepath) && Util.CompareChecksum(filepath, slot.md5, Util.HashTypes.MD5); } return true; @@ -436,115 +502,157 @@ private bool CheckBetaMD5(Analogue.DataSlot slot, string platform) public void BuildInstanceJSONs(bool overwrite = true) { - if(!buildInstances) { + if (!this.build_instances) + { return; } - string instancePackagerFile = Path.Combine(Factory.GetGlobals().UpdateDirectory, "Cores", this.identifier, "instance-packager.json"); - if(!File.Exists(instancePackagerFile)) { + + string instancePackagerFile = Path.Combine(GlobalHelper.UpdateDirectory, "Cores", this.identifier, "instance-packager.json"); + + if (!File.Exists(instancePackagerFile)) + { return; } - _writeMessage("Building instance json files."); - InstancePackager packager = JsonSerializer.Deserialize(File.ReadAllText(instancePackagerFile)); - string commonPath = Path.Combine(Factory.GetGlobals().UpdateDirectory, "Assets", packager.platform_id, "common"); - string outputDir = Path.Combine(Factory.GetGlobals().UpdateDirectory, packager.output); + + WriteMessage("Building instance json files."); + InstanceJsonPackager jsonPackager = JsonSerializer.Deserialize(File.ReadAllText(instancePackagerFile)); + string commonPath = Path.Combine(GlobalHelper.UpdateDirectory, "Assets", jsonPackager.platform_id, "common"); bool warning = false; - foreach(string dir in Directory.GetDirectories(commonPath, "*", SearchOption.AllDirectories)) { - Analogue.SimpleInstanceJSON instancejson = new Analogue.SimpleInstanceJSON(); - Analogue.SimpleInstance instance = new Analogue.SimpleInstance(); + + foreach (string dir in Directory.GetDirectories(commonPath, "*", SearchOption.AllDirectories)) + { + SimpleInstanceJSON simpleInstanceJson = new SimpleInstanceJSON(); + SimpleInstance instance = new SimpleInstance(); string dirName = Path.GetFileName(dir); - try { + + try + { instance.data_path = dir.Replace(commonPath + Path.DirectorySeparatorChar, "") + "/"; - List slots = new List(); + + List slots = new(); string jsonFileName = dirName + ".json"; - foreach(DataSlot slot in packager.data_slots) { + + foreach (InstancePackagerDataSlot slot in jsonPackager.data_slots) + { string[] files = Directory.GetFiles(dir, slot.filename); int index = slot.id; - switch(slot.sort) { + + switch (slot.sort) + { case "single": case "ascending": Array.Sort(files); break; + case "descending": - IComparer myComparer = new myReverserClass(); + IComparer myComparer = new ReverseComparer(); Array.Sort(files, myComparer); break; } - if(slot.required && files.Count() == 0) { + + if (slot.required && !files.Any()) + { throw new MissingRequiredInstanceFiles("Missing required files."); } - foreach(string file in files) { - if(File.GetAttributes(file).HasFlag(FileAttributes.Hidden)) { + + foreach (string file in files) + { + if (File.GetAttributes(file).HasFlag(FileAttributes.Hidden)) + { continue; } - Analogue.InstanceDataSlot current = new Analogue.InstanceDataSlot(); + + SimpleDataSlot current = new(); string filename = Path.GetFileName(file); - if(slot.as_filename) { + + if (slot.as_filename) + { jsonFileName = Path.GetFileNameWithoutExtension(file) + ".json"; } + current.id = index.ToString(); current.filename = filename; index++; slots.Add(current); } } - var limit = (JsonElement)packager.slot_limit["count"]; - if (slots.Count == 0 || (packager.slot_limit != null && slots.Count > limit.GetInt32())) { - _writeMessage("Unable to build " + jsonFileName); + + var limit = (JsonElement)jsonPackager.slot_limit["count"]; + + if (slots.Count == 0 || (jsonPackager.slot_limit != null && slots.Count > limit.GetInt32())) + { + WriteMessage("Unable to build " + jsonFileName); warning = true; continue; } + instance.data_slots = slots.ToArray(); - instancejson.instance = instance; - var options = new JsonSerializerOptions() - { - WriteIndented = true - }; + simpleInstanceJson.instance = instance; + + var options = new JsonSerializerOptions { WriteIndented = true }; string[] parts = dir.Split(commonPath); parts = parts[1].Split(jsonFileName.Remove(jsonFileName.Length - 5)); - string subdir = ""; - if(parts[0].Length > 1) { - subdir = parts[0].Trim(Path.DirectorySeparatorChar); + string subDirectory = string.Empty; + + if (parts[0].Length > 1) + { + subDirectory = parts[0].Trim(Path.DirectorySeparatorChar); } - string outputfile = Path.Combine(Factory.GetGlobals().UpdateDirectory, packager.output, subdir, jsonFileName); - if(!overwrite && File.Exists(outputfile)) { - _writeMessage(jsonFileName + " already exists."); - } else { - string json = JsonSerializer.Serialize(instancejson, options); - _writeMessage("Saving " + jsonFileName); - FileInfo file = new System.IO.FileInfo(outputfile); + + string outputFile = Path.Combine(GlobalHelper.UpdateDirectory, jsonPackager.output, subDirectory, jsonFileName); + + if (!overwrite && File.Exists(outputFile)) + { + WriteMessage(jsonFileName + " already exists."); + } + else + { + string json = JsonSerializer.Serialize(simpleInstanceJson, options); + + WriteMessage("Saving " + jsonFileName); + + FileInfo file = new FileInfo(outputFile); + file.Directory.Create(); // If the directory already exists, this method does nothing. - File.WriteAllText(outputfile, json); + + File.WriteAllText(outputFile, json); } - } catch(MissingRequiredInstanceFiles) { - //do nothin - } catch(Exception e) { - _writeMessage("Unable to build " + dirName); + } + catch (MissingRequiredInstanceFiles) + { + // Do nothing. + } + catch (Exception) + { + WriteMessage("Unable to build " + dirName); } } - if (warning) { - var message = (JsonElement)packager.slot_limit["message"]; - _writeMessage(message.GetString()); + + if (warning) + { + var message = (JsonElement)jsonPackager.slot_limit["message"]; + + WriteMessage(message.GetString()); } - _writeMessage("Finished"); + + WriteMessage("Finished"); } public bool CheckInstancePackager() { - string instancePackagerFile = Path.Combine(Factory.GetGlobals().UpdateDirectory, "Cores", this.identifier, "instance-packager.json"); + string instancePackagerFile = Path.Combine(GlobalHelper.UpdateDirectory, "Cores", this.identifier, "instance-packager.json"); + return File.Exists(instancePackagerFile); } - public Analogue.DataJSON ReadDataJSON() + private DataJSON ReadDataJSON() { - string UpdateDirectory = Factory.GetGlobals().UpdateDirectory; - string coreDirectory = Path.Combine(UpdateDirectory, "Cores", this.identifier); + string updateDirectory = GlobalHelper.UpdateDirectory; + string coreDirectory = Path.Combine(updateDirectory, "Cores", this.identifier); string dataFile = Path.Combine(coreDirectory, "data.json"); - var options = new JsonSerializerOptions - { - Converters = { new StringConverter() } - }; + var options = new JsonSerializerOptions { Converters = { new StringConverter() } }; - Analogue.DataJSON data = JsonSerializer.Deserialize(File.ReadAllText(dataFile), options); + DataJSON data = JsonSerializer.Deserialize(File.ReadAllText(dataFile), options); return data; } @@ -552,28 +660,35 @@ public Analogue.DataJSON ReadDataJSON() public bool JTBetaCheck() { var data = ReadDataJSON(); - bool check = data.data.data_slots.Any(x=>x.name=="JTBETA"); + bool check = data.data.data_slots.Any(x => x.name == "JTBETA"); + + if (check) + { + var slot = data.data.data_slots.First(x => x.name == "JTBETA"); - if (check) { - var slot = data.data.data_slots.Where(x=>x.name=="JTBETA").First(); - betaSlotId = slot.id; - betaSlotPlatformIdIndex = slot.getPlatformIdIndex(); + this.beta_slot_id = slot.id; + this.beta_slot_platform_id_index = slot.GetPlatformIdIndex(); } return check; } - public async Task ReplaceCheck() + public void ReplaceCheck() { - var replaces = this.getSubstitutes(); - if (replaces != null) { - foreach(var replacement in replaces) { + var replaces = this.GetSubstitutes(); + + if (replaces != null) + { + foreach (var replacement in replaces) + { string identifier = $"{replacement.author}.{replacement.shortname}"; - Core c = new Core(){identifier = identifier, platform_id = replacement.platform_id}; - if (c.isInstalled()) { + Core c = new Core { identifier = identifier, platform_id = replacement.platform_id }; + + if (c.IsInstalled()) + { Replace(c); c.Uninstall(); - _writeMessage($"Uninstalled {identifier}. It was replaced by this core."); + WriteMessage($"Uninstalled {identifier}. It was replaced by this core."); } } } @@ -581,72 +696,79 @@ public async Task ReplaceCheck() private void Replace(Core core) { - string root = Factory.GetGlobals().UpdateDirectory; + string root = GlobalHelper.UpdateDirectory; string path = Path.Combine(root, "Assets", core.platform_id, core.identifier); - if(Directory.Exists(path)) { + + if (Directory.Exists(path)) + { Directory.Move(path, Path.Combine(root, "Assets", core.platform_id, this.identifier)); } - + path = Path.Combine(root, "Saves", core.platform_id, core.identifier); - if(Directory.Exists(path)) { + if (Directory.Exists(path)) + { Directory.Move(path, Path.Combine(root, "Saves", core.platform_id, this.identifier)); } path = Path.Combine(root, "Settings", core.identifier); - if(Directory.Exists(path)) { + if (Directory.Exists(path)) + { Directory.Move(path, Path.Combine(root, "Settings", this.identifier)); } } - public async Task GetVideoConfig() + public Video GetVideoConfig() { - checkUpdateDirectory(); - string file = Path.Combine(Factory.GetGlobals().UpdateDirectory, "Cores", this.identifier, "video.json"); + CheckUpdateDirectory(); + + string file = Path.Combine(GlobalHelper.UpdateDirectory, "Cores", this.identifier, "video.json"); + if (!File.Exists(file)) { return null; } + string json = File.ReadAllText(file); - var options = new JsonSerializerOptions() - { - AllowTrailingCommas = true - }; - Analogue.Cores.Video.Video? config = JsonSerializer.Deserialize>(json, options)["video"]; + var options = new JsonSerializerOptions { AllowTrailingCommas = true }; + Video config = JsonSerializer.Deserialize>(json, options)["video"]; return config; } - public async Task AddDisplayModes() + public void AddDisplayModes() { - var info = this.getConfig(); - var video = await GetVideoConfig(); - List all = new List(); - foreach(string id in allModes) { - all.Add(new Analogue.Cores.Video.DisplayMode{id = id}); - } - if(info.metadata.platform_ids.Contains("gb")) { - foreach(string id in gbModes) { - all.Add(new Analogue.Cores.Video.DisplayMode{id = id}); + var info = this.GetConfig(); + var video = GetVideoConfig(); + List all = new List(); + + foreach (string id in ALL_MODES) + { + all.Add(new DisplayMode { id = id }); + } + + if (info.metadata.platform_ids.Contains("gb")) + { + foreach (string id in GB_MODES) + { + all.Add(new DisplayMode { id = id }); } } + video.display_modes = all; - Dictionary output = new Dictionary(); - output.Add("video", video); - var options = new JsonSerializerOptions() - { - WriteIndented = true - }; + Dictionary output = new Dictionary { { "video", video } }; + var options = new JsonSerializerOptions { WriteIndented = true }; string json = JsonSerializer.Serialize(output, options); - - File.WriteAllText(Path.Combine(Factory.GetGlobals().UpdateDirectory, "Cores", this.identifier, "video.json"), json); + + File.WriteAllText(Path.Combine(GlobalHelper.UpdateDirectory, "Cores", this.identifier, "video.json"), json); } } -public class myReverserClass : IComparer { - - // Calls CaseInsensitiveComparer.Compare with the parameters reversed. - int IComparer.Compare( Object x, Object y ) { - return( (new CaseInsensitiveComparer()).Compare( y, x ) ); - } - } +public class ReverseComparer : IComparer +{ + // Calls CaseInsensitiveComparer.Compare with the parameters reversed. + int IComparer.Compare(object x, object y) + { + return new CaseInsensitiveComparer().Compare(y, x); + } +} diff --git a/src/models/Github/GithubAsset.cs b/src/models/Github/GithubAsset.cs index bc882c3f..2c2deb9f 100644 --- a/src/models/Github/GithubAsset.cs +++ b/src/models/Github/GithubAsset.cs @@ -1,9 +1,9 @@ -namespace Github; +namespace Pannella.Models.Github; public class Asset { - public string? url {get; set;} - public string? name {get; set;} - public string? content_type {get; set;} - public string? browser_download_url {get; set;} -} \ No newline at end of file + public string url {get; set;} + public string name {get; set;} + public string content_type {get; set;} + public string browser_download_url {get; set;} +} diff --git a/src/models/Github/GithubFile.cs b/src/models/Github/GithubFile.cs index 7dde4b7c..a225ac54 100644 --- a/src/models/Github/GithubFile.cs +++ b/src/models/Github/GithubFile.cs @@ -1,12 +1,12 @@ -namespace Github; +namespace Pannella.Models.Github; public class File { - public string? name {get; set;} - public string? path {get; set;} - public string? sha {get; set;} + public string name {get; set;} + public string path {get; set;} + public string sha {get; set;} public int? size {get; set;} - public string? url {get; set;} - public string? html_url {get; set;} - public string? download_url {get; set;} -} \ No newline at end of file + public string url {get; set;} + public string html_url {get; set;} + public string download_url {get; set;} +} diff --git a/src/models/Github/GithubRelease.cs b/src/models/Github/GithubRelease.cs index c21e1d79..8bf4be1c 100644 --- a/src/models/Github/GithubRelease.cs +++ b/src/models/Github/GithubRelease.cs @@ -1,12 +1,12 @@ -namespace Github; +namespace Pannella.Models.Github; public class Release { - public string? tag_name { get; set; } - public string? name { get; set; } + public string tag_name { get; set; } + public string name { get; set; } public bool prerelease { get; set; } - public string? url { get; set; } - public List? assets { get; set; } + public string url { get; set; } + public List assets { get; set; } public bool draft { get; set; } - public string? html_url { get; set; } -} \ No newline at end of file + public string html_url { get; set; } +} diff --git a/src/models/ImagePack.cs b/src/models/ImagePack.cs index 7197d157..6c7e0032 100644 --- a/src/models/ImagePack.cs +++ b/src/models/ImagePack.cs @@ -1,74 +1,98 @@ -namespace pannella.analoguepocket; -using System.IO; using System.IO.Compression; +using Pannella.Helpers; +using Pannella.Models.Github; +using Pannella.Services; +using File = System.IO.File; + +namespace Pannella.Models; public class ImagePack { public string owner { get; set; } public string repository { get; set; } - public string? variant { get; set; } - + public string variant { get; set; } - public async Task Install(string path) + public async Task Install(string path) { - string filepath = await fetchImagePack(path); - return await installImagePack(path, filepath); + string filepath = await this.FetchImagePack(path); + + InstallImagePack(path, filepath); } - private async Task fetchImagePack(string path) + private async Task FetchImagePack(string path) { - Github.Release release = await GithubApi.GetLatestRelease(this.owner, this.repository); + Release release = await GithubApiService.GetLatestRelease(this.owner, this.repository); string localFile = Path.Combine(path, "imagepack.zip"); - string downloadUrl = ""; - if(release.assets == null) { + string downloadUrl = string.Empty; + + if (release.assets == null) + { throw new Exception("Github Release contains no assets"); } - if(this.variant == null) { + + if (this.variant == null) + { downloadUrl = release.assets[0].browser_download_url; - } else { - foreach(Github.Asset asset in release.assets) { - if(asset.name.Contains(this.variant)) { - downloadUrl = asset.browser_download_url; - } + } + else + { + foreach (var asset in release.assets.Where(asset => asset.name.Contains(this.variant))) + { + downloadUrl = asset.browser_download_url; } } - if(downloadUrl != "") { + + if (downloadUrl != string.Empty) + { Console.WriteLine("Downloading image pack..."); - await Factory.GetHttpHelper().DownloadFileAsync(downloadUrl, localFile); + + await HttpHelper.Instance.DownloadFileAsync(downloadUrl, localFile); + Console.WriteLine("Download complete."); + return localFile; } - return ""; + + return string.Empty; } - private async Task installImagePack(string path, string filepath) + private static void InstallImagePack(string path, string filepath) { Console.WriteLine("Installing..."); + string extractPath = Path.Combine(path, "temp"); + ZipFile.ExtractToDirectory(filepath, extractPath, true); + string imagePack = FindImagePack(extractPath); string target = Path.Combine(path, "Platforms", "_images"); + Util.CopyDirectory(imagePack, target, false, true); Directory.Delete(extractPath, true); File.Delete(filepath); - Console.WriteLine("All Done"); - return true; + Console.WriteLine("All Done"); } - private string FindImagePack(string temp) + private static string FindImagePack(string temp) { string path = Path.Combine(temp, "Platforms", "_images"); - if(Directory.Exists(path)) { + + if (Directory.Exists(path)) + { return path; } - foreach(string d in Directory.EnumerateDirectories(temp)) { + foreach (string d in Directory.EnumerateDirectories(temp)) + { path = Path.Combine(d, "Platforms", "_images"); - if(Directory.Exists(path)) { + + if (Directory.Exists(path)) + { return path; } } + throw new Exception("Can't find image pack"); } } diff --git a/src/models/InstancePackager/DataSlot.cs b/src/models/InstancePackager/DataSlot.cs index 5bf0da94..f427cde9 100644 --- a/src/models/InstancePackager/DataSlot.cs +++ b/src/models/InstancePackager/DataSlot.cs @@ -1,4 +1,4 @@ -namespace pannella.analoguepocket; +namespace Pannella.Models.InstancePackager; public class DataSlot { @@ -7,4 +7,4 @@ public class DataSlot public bool required { get; set; } public string sort { get; set; } = "ascending"; public bool as_filename { get; set; } -} \ No newline at end of file +} diff --git a/src/models/InstancePackager/InstancePackager.cs b/src/models/InstancePackager/InstanceJsonPackager.cs similarity index 71% rename from src/models/InstancePackager/InstancePackager.cs rename to src/models/InstancePackager/InstanceJsonPackager.cs index 2c30fd28..97b6cc27 100644 --- a/src/models/InstancePackager/InstancePackager.cs +++ b/src/models/InstancePackager/InstanceJsonPackager.cs @@ -1,9 +1,9 @@ -namespace pannella.analoguepocket; +namespace Pannella.Models.InstancePackager; -public class InstancePackager +public class InstanceJsonPackager { public List data_slots { get; set; } public string output { get; set; } public string platform_id { get; set; } public Dictionary slot_limit { get; set; } -} \ No newline at end of file +} diff --git a/src/models/Platform.cs b/src/models/Platform.cs index 6e237c59..df94935f 100644 --- a/src/models/Platform.cs +++ b/src/models/Platform.cs @@ -1,4 +1,4 @@ -namespace pannella.analoguepocket; +namespace Pannella.Models; public class Platform { @@ -6,4 +6,4 @@ public class Platform public string name { get; set; } public int year { get; set; } public string manufacturer { get; set; } -} \ No newline at end of file +} diff --git a/src/models/Repo.cs b/src/models/Repo.cs index aee7ab37..75240eb0 100644 --- a/src/models/Repo.cs +++ b/src/models/Repo.cs @@ -1,8 +1,8 @@ -namespace pannella.analoguepocket; +namespace Pannella.Models; public class Repo { - public string? name { get; set; } - public string? owner { get; set; } - public string? platform { get; set; } -} \ No newline at end of file + public string name { get; set; } + public string owner { get; set; } + public string platform { get; set; } +} diff --git a/src/models/Settings/Config.cs b/src/models/Settings/Config.cs index 74787984..0772406c 100644 --- a/src/models/Settings/Config.cs +++ b/src/models/Settings/Config.cs @@ -1,43 +1,26 @@ -namespace pannella.analoguepocket; +namespace Pannella.Models.Settings; public class Config { - public bool download_assets { get; set; } - public string archive_name { get; set; } - public string? github_token { get; set; } - public bool download_firmware { get; set; } - public bool core_selector { get; set; } - public bool preserve_platforms_folder { get; set; } - public bool delete_skipped_cores { get; set; } - public string? download_new_cores { get; set; } - public bool build_instance_jsons { get; set; } - public bool crc_check { get; set; } - public bool fix_jt_names { get; set; } - public bool skip_alternative_assets { get; set; } + public bool download_assets { get; set; } = true; + public string archive_name { get; set; } = "openFPGA-Files"; + public string github_token { get; set; } = string.Empty; + public bool download_firmware { get; set; } = true; + public bool core_selector { get; set; } = true; + public bool preserve_platforms_folder { get; set; } = false; + public bool delete_skipped_cores { get; set; } = true; + public string download_new_cores { get; set; } + public bool build_instance_jsons { get; set; } = true; + public bool crc_check { get; set; } = true; + public bool fix_jt_names { get; set; } = true; + public bool skip_alternative_assets { get; set; } = true; public bool backup_saves { get; set; } - public string? backup_saves_location { get; set; } - public bool use_custom_archive { get; set; } - public Dictionary custom_archive { get; set; } + public string backup_saves_location { get; set; } = "Backups"; + public bool use_custom_archive { get; set; } = false; - public Config() + public Dictionary custom_archive { get; set; } = new() { - download_assets = true; - download_firmware = true; - archive_name = "openFPGA-Files"; - core_selector = true; - preserve_platforms_folder = false; - delete_skipped_cores = true; - download_new_cores = null; - build_instance_jsons = true; - crc_check = true; - fix_jt_names = true; - skip_alternative_assets = true; - backup_saves = false; - backup_saves_location = "Backups"; - use_custom_archive = false; - custom_archive = new Dictionary() { - {"url", "https://updater.retrodriven.com"}, - {"index", "updater.php"} - }; - } -} \ No newline at end of file + { "url", "https://updater.retrodriven.com" }, + { "index", "updater.php" } + }; +} diff --git a/src/models/Settings/CoreSettings.cs b/src/models/Settings/CoreSettings.cs index 493e717c..fefb23c9 100644 --- a/src/models/Settings/CoreSettings.cs +++ b/src/models/Settings/CoreSettings.cs @@ -1,15 +1,8 @@ -namespace pannella.analoguepocket; +namespace Pannella.Models.Settings; public class CoreSettings { public bool skip { get; set; } - public bool download_assets { get; set; } - public bool platform_rename { get; set; } - - public CoreSettings() - { - skip = false; - download_assets = true; - platform_rename = true; - } -} \ No newline at end of file + public bool download_assets { get; set; } = true; + public bool platform_rename { get; set; } = true; +} diff --git a/src/models/Settings/Firmware.cs b/src/models/Settings/Firmware.cs index 3e8c37ec..971529fa 100644 --- a/src/models/Settings/Firmware.cs +++ b/src/models/Settings/Firmware.cs @@ -1,11 +1,6 @@ -namespace pannella.analoguepocket; +namespace Pannella.Models.Settings; public class Firmware { - public string version { get; set; } - - public Firmware() - { - version = ""; - } -} \ No newline at end of file + public string version { get; set; } = string.Empty; +} diff --git a/src/models/Settings/Settings.cs b/src/models/Settings/Settings.cs index 9bdf5f34..6367f472 100644 --- a/src/models/Settings/Settings.cs +++ b/src/models/Settings/Settings.cs @@ -1,15 +1,8 @@ -namespace pannella.analoguepocket; +namespace Pannella.Models.Settings; public class Settings { - public Firmware firmware { get; set; } - public Config config { get; set; } - public Dictionary coreSettings { get; set; } - - public Settings() - { - firmware = new Firmware(); - config = new Config(); - coreSettings = new Dictionary(); - } -} \ No newline at end of file + public Firmware firmware { get; set; } = new(); + public Config config { get; set; } = new(); + public Dictionary coreSettings { get; set; } = new(); +} diff --git a/src/models/Sponsor.cs b/src/models/Sponsor.cs index ccff8403..215d4d15 100644 --- a/src/models/Sponsor.cs +++ b/src/models/Sponsor.cs @@ -1,15 +1,45 @@ -namespace pannella.analoguepocket; +using System.Text; + +namespace Pannella.Models; public class Sponsor { - public string? community_bridge { get; set; } - public List? github { get; set; } - public string? issuehunt { get; set; } - public string? ko_fi { get; set; } - public string? liberapay { get; set; } - public string? open_collective { get; set; } - public string? otechie { get; set; } - public string? patreon { get; set; } - public string? tidelift { get; set; } - public List? custom { get; set; } -} \ No newline at end of file + public string community_bridge { get; set; } + public List github { get; set; } + public string issuehunt { get; set; } + public string ko_fi { get; set; } + public string liberapay { get; set; } + public string open_collective { get; set; } + public string otechie { get; set; } + public string patreon { get; set; } + public string tidelift { get; set; } + public List custom { get; set; } + + public override string ToString() + { + var links = new StringBuilder(); + var properties = typeof(Sponsor).GetProperties(); + + foreach (var prop in properties) + { + object value = prop.GetValue(this, null); + + if (value == null) continue; + + links.AppendLine(); + + if (value.GetType() == typeof(List)) + { + var stringArray = (List)value; + + links.Append(string.Join(Environment.NewLine, stringArray)); + } + else if (value is string) + { + links.Append(value); + } + } + + return links.ToString(); + } +} diff --git a/src/models/Updater/Substitute.cs b/src/models/Updater/Substitute.cs index ddea0457..01c94d24 100644 --- a/src/models/Updater/Substitute.cs +++ b/src/models/Updater/Substitute.cs @@ -1,8 +1,8 @@ -namespace pannella.analoguepocket.Updater; +namespace Pannella.Models.Updater; public class Substitute { public string shortname { get; set; } public string author { get; set; } public string platform_id { get; set; } -} \ No newline at end of file +} diff --git a/src/models/Updater/Updaters.cs b/src/models/Updater/Updaters.cs index 98447eeb..c30c891a 100644 --- a/src/models/Updater/Updaters.cs +++ b/src/models/Updater/Updaters.cs @@ -1,6 +1,6 @@ -namespace pannella.analoguepocket.Updater; +namespace Pannella.Models.Updater; public class Updaters { public Substitute[] previous { get; set; } -} \ No newline at end of file +} diff --git a/src/options/AssetsOptions.cs b/src/options/AssetsOptions.cs new file mode 100644 index 00000000..57700bea --- /dev/null +++ b/src/options/AssetsOptions.cs @@ -0,0 +1,13 @@ +using CommandLine; + +namespace Pannella.Options; + +[Verb("assets", HelpText = "Run the asset downloader")] +public class AssetsOptions +{ + [Option('p', "path", HelpText = "Absolute path to install location", Required = false)] + public string InstallPath { get; set; } + + [Option ('c', "core", Required = false, HelpText = "The core you want to download assets for.")] + public string CoreName { get; set; } +} diff --git a/src/options/BackupSavesOptions.cs b/src/options/BackupSavesOptions.cs new file mode 100644 index 00000000..2510ebfc --- /dev/null +++ b/src/options/BackupSavesOptions.cs @@ -0,0 +1,16 @@ +using CommandLine; + +namespace Pannella.Options; + +[Verb("backup-saves", HelpText = "Create a compressed zip file of the Saves directory.")] +public class BackupSavesOptions +{ + [Option('p', "path", HelpText = "Absolute path to install location", Required = false)] + public string InstallPath { get; set; } + + [Option('l', "location", HelpText = "Absolute path to backup location", Required = true)] + public string BackupPath { get; set; } = null!; + + [Option('s', "save", HelpText = "Save settings to the config file", Required = false)] + public bool Save { get; set; } +} diff --git a/src/options/FirmwareOptions.cs b/src/options/FirmwareOptions.cs new file mode 100644 index 00000000..8c1ce1c1 --- /dev/null +++ b/src/options/FirmwareOptions.cs @@ -0,0 +1,10 @@ +using CommandLine; + +namespace Pannella.Options; + +[Verb("firmware", HelpText = "Check for Pocket firmware updates")] +public class FirmwareOptions +{ + [Option('p', "path", HelpText = "Absolute path to install location", Required = false)] + public string InstallPath { get; set; } +} diff --git a/src/options/FundOptions.cs b/src/options/FundOptions.cs new file mode 100644 index 00000000..606e260a --- /dev/null +++ b/src/options/FundOptions.cs @@ -0,0 +1,10 @@ +using CommandLine; + +namespace Pannella.Options; + +[Verb("fund", HelpText = "List sponsor links")] +public class FundOptions +{ + [Option('c', "core", HelpText = "The core to check funding links for", Required = false)] + public string Core { get; set; } +} diff --git a/src/options/ImagesOptions.cs b/src/options/ImagesOptions.cs new file mode 100644 index 00000000..5b3e1d1c --- /dev/null +++ b/src/options/ImagesOptions.cs @@ -0,0 +1,19 @@ +using CommandLine; + +namespace Pannella.Options; + +[Verb("images", HelpText = "Download image packs")] +public class ImagesOptions +{ + [Option('p', "path", HelpText = "Absolute path to install location", Required = false)] + public string InstallPath { get; set; } + + [Option('o', "owner", Required = true, HelpText = "Image pack repo username")] + public string ImagePackOwner { get; set; } + + [Option('i', "imagepack", Required = true, HelpText = "Github repo name for image pack")] + public string ImagePackRepo { get; set; } + + [Option('v', "variant", Required = false, HelpText = "The optional variant")] + public string ImagePackVariant { get; set; } +} diff --git a/src/options/InstanceGeneratorOptions.cs b/src/options/InstanceGeneratorOptions.cs new file mode 100644 index 00000000..8f6862c3 --- /dev/null +++ b/src/options/InstanceGeneratorOptions.cs @@ -0,0 +1,10 @@ +using CommandLine; + +namespace Pannella.Options; + +[Verb("instancegenerator", HelpText = "Run the instance JSON generator")] +public class InstanceGeneratorOptions +{ + [Option('p', "path", HelpText = "Absolute path to install location", Required = false)] + public string InstallPath { get; set; } +} diff --git a/src/options/MenuOptions.cs b/src/options/MenuOptions.cs new file mode 100644 index 00000000..899d23f6 --- /dev/null +++ b/src/options/MenuOptions.cs @@ -0,0 +1,13 @@ +using CommandLine; + +namespace Pannella.Options; + +[Verb("menu", isDefault: true, HelpText = "Interactive Main Menu")] +public class MenuOptions +{ + [Option('p', "path", HelpText = "Absolute path to install location", Required = false)] + public string InstallPath { get; set; } + + [Option('s', "skip-update", HelpText = "Skip the self update check", Required = false)] + public bool SkipUpdate { get; set; } +} diff --git a/src/options/UninstallOptions.cs b/src/options/UninstallOptions.cs new file mode 100644 index 00000000..86357ba3 --- /dev/null +++ b/src/options/UninstallOptions.cs @@ -0,0 +1,16 @@ +using CommandLine; + +namespace Pannella.Options; + +[Verb("uninstall", HelpText = "Delete a core")] +public class UninstallOptions +{ + [Option('p', "path", HelpText = "Absolute path to install location", Required = false)] + public string InstallPath { get; set; } + + [Option ('c', "core", Required = true, HelpText = "The core you want to delete.")] + public string CoreName { get; set; } + + [Option('a', "assets", Required = false, HelpText = "Delete the core specific Assets folder")] + public bool DeleteAssets { get; set; } +} diff --git a/src/options/UpdateOptions.cs b/src/options/UpdateOptions.cs new file mode 100644 index 00000000..fd198618 --- /dev/null +++ b/src/options/UpdateOptions.cs @@ -0,0 +1,20 @@ +using CommandLine; + +namespace Pannella.Options; + +[Verb("update", HelpText = "Run update all. (You can configure via the settings menu)")] +public class UpdateOptions +{ + [Option('p', "path", HelpText = "Absolute path to install location", Required = false)] + public string InstallPath { get; set; } + + [Option ('c', "core", Required = false, HelpText = "The core you want to update.")] + public string CoreName { get; set; } + + [Option('f', "platformsfolder", Required = false, HelpText = "Preserve the Platforms folder, so customizations aren't overwritten by updates.")] + public bool PreservePlatformsFolder { get; set; } + + [Option('r', "clean", Required = false, HelpText = "Clean install. Remove all existing core files, before updating")] + public bool CleanInstall { get; set; } + +} diff --git a/src/options/UpdateSelfOptions.cs b/src/options/UpdateSelfOptions.cs new file mode 100644 index 00000000..dc2e7b31 --- /dev/null +++ b/src/options/UpdateSelfOptions.cs @@ -0,0 +1,6 @@ +using CommandLine; + +namespace Pannella.Options; + +[Verb("update-self", HelpText = "Update this utility")] +public class UpdateSelfOptions { } diff --git a/src/partials/Program.Constants.cs b/src/partials/Program.Constants.cs new file mode 100644 index 00000000..bc082179 --- /dev/null +++ b/src/partials/Program.Constants.cs @@ -0,0 +1,94 @@ +using System.Reflection; + +namespace Pannella; + +internal partial class Program +{ + private static readonly string VERSION = Assembly.GetExecutingAssembly().GetName().Version.ToString(3); + + private static readonly string SYSTEM_OS_PLATFORM = GetSystemPlatform(); + + private const string USER = "mattpannella"; + + private const string REPOSITORY = "pocket-updater-utility"; + + private const string RELEASE_URL = "https://github.com/mattpannella/pocket-updater-utility/releases/download/{0}/pupdate_{1}.zip"; + + private static readonly string[] WELCOME_MESSAGES = + { + @" + _____ _ _ ___ _____ _ +| __ | |___ _____ ___ _ _ ___ _ _ ___ ___ ___| | _| ___ ___ | __|___ _| | +| __ -| | .'| | -_| | | | . | | | _|_ -| -_| | _| | . | _| | | | . | . | +|_____|_|__,|_|_|_|___| |_ |___|___|_| |___|___|_|_| |___|_| |_____|___|___| + |___| ", + @" + _ _ _ _ _ _____ _ _ +| | | |___| |___ ___ _____ ___ | |_ ___ | __| |___ _ _ ___ ___| |_ ___ _ _ _ ___ +| | | | -_| | _| . | | -_| | _| . | | __| | .'| | | . | _| _| . | | | | | +|_____|___|_|___|___|_|_|_|___| |_| |___| |__| |_|__,|\_/|___|_| |_| |___|_____|_|_| + ", + @" + _ ___ _ _ _ _ + ___| |_ ___ | _|_|___ _| |___ _ _ ___ _ _ ___ ___ _ _ ___| |_ _ _ _| |___ _ _ ___ +|_ -| | -_| | _| | | . |_ -| | | | . | | | | _| _| | |_ -| _| | | | . | .'| | | -_| +|___|_|_|___| |_| |_|_|_|___|___| |_ |___|___| |___|_| |___|___|_| |_ | |___|__,|\_/|___| + |___| |___| ", + @" + _____ _ _ _ ___ _ _ +|_ _| |_|_|___ |_|___ ___ | _|___ ___ ___ _ _ ___ ___ ___| |_ ___ _ _ ___ ___ ___| |_ + | | | | |_ -| | |_ -| | .'| | _| .'| | _| | | | _| -_|_ -| _| .'| | | _| .'| | _| + |_| |_|_|_|___| |_|___| |__,| |_| |__,|_|_|___|_ | |_| |___|___|_| |__,|___|_| |__,|_|_|_| + |___| ", + @" __ + _ _ _ _ _ _ _ _____ _ _ _____ _ _ | | +| | | |___| |___ ___ _____ ___ | |_ ___ | |_| |_ ___ | __ | |___ ___| |_ | |___ ___| |_ ___| |_| | +| | | | -_| | _| . | | -_| | _| . | | _| | -_| | __ -| | .'| _| '_| | | | | .'| _| '_| -_| _|__| +|_____|___|_|___|___|_|_|_|___| |_| |___| |_| |_|_|___| |_____|_|__,|___|_,_| |_|_|_|__,|_| |_,_|___|_| |__| + ", + @" + _____ _ _ _____ _ +| | |_ ___| |_ _ _ |_ _|___ ___| |_ _ _ +|- -| _| _| | | |_ | | | .'|_ -| _| | |_ +|_____|_| |___|_|_|_ |_| |_| |__,|___|_| |_ |_| + |___| |___| ", + @" _ _____ + _ _ _ _ _ | | _ _ |___ | +| | | | |_ ___| |_|_|___ ___ _ _ ___ | |_ _ _ _ _|_|___| _| +| | | | | .'| _| | _| -_| | | | .'| | . | | | | | | |_| +|_____|_|_|__,|_| |_| |___| |_ |__,| |___|___|_ |_|_|_|_| + |___| |___| ", + @" _____ + _ _ _ _ _ _ |___ | +| | | | |_ ___| |_ |_|___ ___ _____ ___ ___| _| +| | | | | .'| _| | |_ -| | .'| | | .'| |_| +|_____|_|_|__,|_| |_|___| |__,| |_|_|_|__,|_|_|_| + ", + @" + _____ _ _ _____ _ _ _ +| __|_|___ ___|_|___ ___ | |___|_| |___ _| | +| __| |_ -|_ -| | . | | | | | | .'| | | -_| . | +|__| |_|___|___|_|___|_|_| |_|_|_|__,|_|_|___|___| + ", + @" + _____ _ +| | |_____| |_ ___ ___ ___ +| | | | . | .'|_ -| .'| +|_____|_|_|_|___|__,|___|__,| + ", + @" _ + __ _ | | +| | ___| |_|_|___ _____ ___ ___ ___ _ _ +| |__| -_| _| |_ -| | | . |_ -| -_| | | +|_____|___|_| |___| |_|_|_|___|___|___|_ | + |___|", + @" _=,_ + o_/6 /#\ + \__ |##/ + ='|--\ + / #'-. + \#|_ _'-. / + |/ \_( # |'' + C/ ,--___/" + }; +} diff --git a/src/partials/Program.GameAndWatch.cs b/src/partials/Program.GameAndWatch.cs new file mode 100644 index 00000000..c8ae0c97 --- /dev/null +++ b/src/partials/Program.GameAndWatch.cs @@ -0,0 +1,103 @@ +using System.Diagnostics; +using System.IO.Compression; +using Pannella.Helpers; +using Pannella.Models.Github; +using Pannella.Services; +using File = System.IO.File; +using GithubAsset = Pannella.Models.Github.Asset; + +namespace Pannella; + +internal partial class Program +{ + private static async Task BuildGameAndWatchRoms(string directory) + { + Release release = await GithubApiService.GetLatestRelease("agg23", "fpga-gameandwatch"); + + foreach (GithubAsset asset in release.assets) + { + if (asset.name.EndsWith("Tools.zip")) + { + string downloadPath = Path.Combine(directory, "tools", "gameandwatch"); + string filename = Path.Combine(downloadPath, asset.name); + + if (!File.Exists(filename)) + { + Directory.CreateDirectory(downloadPath); + await HttpHelper.Instance.DownloadFileAsync(asset.browser_download_url, filename); + ZipFile.ExtractToDirectory(filename, downloadPath, true); + } + + break; + } + } + + string execName = "fpga-gnw-romgenerator"; + string execLocation = Path.Combine(directory, "tools", "gameandwatch"); + string manifestPath = Path.Combine(directory, "tools", "gameandwatch"); + + switch (SYSTEM_OS_PLATFORM) + { + case "win": + execName += ".exe"; + execLocation = Path.Combine(execLocation, "windows", execName); + manifestPath = Path.Combine(manifestPath, "windows", "manifest.json"); + break; + + case "mac": + execLocation = Path.Combine(execLocation, "mac", execName); + manifestPath = Path.Combine(manifestPath, "mac", "manifest.json"); + Exec($"chmod +x {execLocation}"); + break; + + default: + execLocation = Path.Combine(execLocation, "linux", execName); + manifestPath = Path.Combine(manifestPath, "linux", "manifest.json"); + Exec($"chmod +x {execLocation}"); + break; + } + + string romLocation = Path.Combine(directory, "Assets", "gameandwatch", "agg23.GameAndWatch"); + string outputLocation = Path.Combine(directory, "Assets", "gameandwatch", "common"); + + try + { + // Execute + Console.WriteLine($"Executing {execLocation}"); + + ProcessStartInfo pInfo = new ProcessStartInfo(execLocation) + { + Arguments = $"--mame-path \"{romLocation}\" --output-path \"{outputLocation}\" --manifest-path \"{manifestPath}\" supported", + UseShellExecute = false + }; + + Process p = Process.Start(pInfo); + + p.WaitForExit(); + } + catch (Exception e) + { + Console.Error.WriteLine($"An error occurred: {e.GetType().Name} : {e}"); + } + } + + private static void Exec(string cmd) + { + var escapedArgs = cmd.Replace("\"", "\\\""); + + using var process = new Process(); + + process.StartInfo = new ProcessStartInfo + { + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + FileName = "/bin/bash", + Arguments = $"-c \"{escapedArgs}\"" + }; + + process.Start(); + process.WaitForExit(); + } +} diff --git a/src/partials/Program.Helpers.cs b/src/partials/Program.Helpers.cs new file mode 100644 index 00000000..b9567623 --- /dev/null +++ b/src/partials/Program.Helpers.cs @@ -0,0 +1,115 @@ +using System.Runtime.InteropServices; +using Pannella.Helpers; +using Pannella.Services; +using GithubRelease = Pannella.Models.Github.Release; + +namespace Pannella; + +internal partial class Program +{ + // return true if newer version is available + private static async Task CheckVersion(string path) + { + try + { + List releases = await GithubApiService.GetReleases(USER, REPOSITORY); + + string tag_name = releases[0].tag_name; + string v = SemverUtil.FindSemver(tag_name); + + if (v != null) + { + bool check = SemverUtil.SemverCompare(v, VERSION); + + if (check) + { + Console.WriteLine("A new version is available. Downloading now..."); + + string url = string.Format(RELEASE_URL, tag_name, SYSTEM_OS_PLATFORM); + string saveLocation = Path.Combine(path, "pupdate.zip"); + + await HttpHelper.Instance.DownloadFileAsync(url, saveLocation); + + Console.WriteLine("Download complete."); + Console.WriteLine(saveLocation); + Console.WriteLine("Go to " + releases[0].html_url + " for a change log"); + } + else + { + Console.WriteLine("Up to date."); + } + + return check; + } + + return false; + } + catch (HttpRequestException e) + { + Console.WriteLine(e); + return false; + } + } + + private static void FunFacts() + { + if (GlobalHelper.InstalledCores.Count == 0) + { + return; + } + + string[] sleepSupported = GlobalHelper.InstalledCores + .Where(c => c.GetConfig().framework.sleep_supported) + .Select(c => c.identifier) + .ToArray(); + string list = string.Join(", ", sleepSupported); + + Console.WriteLine(); + Console.WriteLine("Fun fact! The ONLY cores that support save states and sleep are the following:"); + Console.WriteLine(list); + Console.WriteLine("Please don't bother the developers of the other cores about this feature. It's a lot of work and most likely will not be coming."); + } + + private static string GetSystemPlatform() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return "win"; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return "mac"; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + Architecture arch = RuntimeInformation.ProcessArchitecture; + + return arch switch + { + Architecture.Arm64 => "linux_arm64", + Architecture.Arm => "linux_arm32", + _ => "linux" + }; + } + + return string.Empty; + } + + private static void Pause() + { + if (!CLI_MODE) + { + Console.WriteLine("Press any key to continue."); + Console.ReadKey(true); + } + } + + private static void PauseExit(int exitCode = 0) + { + Console.WriteLine("Press any key to exit."); + Console.ReadKey(true); // wait for input so the console doesn't auto close in windows + Environment.Exit(exitCode); + } +} diff --git a/src/partials/Program.ImagePack.cs b/src/partials/Program.ImagePack.cs new file mode 100644 index 00000000..ecd6877d --- /dev/null +++ b/src/partials/Program.ImagePack.cs @@ -0,0 +1,67 @@ +using ConsoleTools; +using Pannella.Models; +using Pannella.Services; + +namespace Pannella; + +internal partial class Program +{ + private static async Task ImagePackSelector(string path) + { + Console.Clear(); + Console.WriteLine("Checking for image packs...\n"); + + ImagePack[] packs = await ImagePacksService.GetImagePacks(); + + if (packs.Length > 0) + { + int choice = 0; + var menu = new ConsoleMenu() + .Configure(config => + { + config.Selector = "=>"; + config.EnableWriteTitle = false; + //config.EnableBreadcrumb = true; + config.WriteHeaderAction = () => Console.WriteLine("So, what'll it be?:"); + config.SelectedItemBackgroundColor = Console.ForegroundColor; + config.SelectedItemForegroundColor = Console.BackgroundColor; + }); + + foreach (var pack in packs) + { + menu.Add($"{pack.owner}: {pack.repository} {pack.variant}", thisMenu => + { + choice = thisMenu.CurrentItem.Index; + thisMenu.CloseMenu(); + }); + } + + menu.Add("Go Back", thisMenu => + { + choice = packs.Length; + thisMenu.CloseMenu(); + }); + + menu.Show(); + + if (choice < packs.Length && choice >= 0) + { + await packs[choice].Install(path); + Pause(); + } + else if (choice == packs.Length) + { + } + else + { + Console.WriteLine("You fucked up."); + Pause(); + } + } + else + { + Console.WriteLine("None found. Have a nice day."); + Pause(); + } + } +} diff --git a/src/partials/Program.InstanceGenerator.cs b/src/partials/Program.InstanceGenerator.cs new file mode 100644 index 00000000..b702d6a9 --- /dev/null +++ b/src/partials/Program.InstanceGenerator.cs @@ -0,0 +1,22 @@ +namespace Pannella; + +internal partial class Program +{ + private static void RunInstanceGenerator(PocketCoreUpdater pocketCoreUpdater, bool force = false) + { + if (!force) + { + Console.Write("Do you want to overwrite existing json files? [Y/N] "); + Console.WriteLine(""); + + var response = Console.ReadKey(false).Key; + + if (response == ConsoleKey.Y) + { + force = true; + } + } + + pocketCoreUpdater.BuildInstanceJson(force); + } +} diff --git a/src/partials/Program.Menus.cs b/src/partials/Program.Menus.cs new file mode 100644 index 00000000..12d3b07c --- /dev/null +++ b/src/partials/Program.Menus.cs @@ -0,0 +1,234 @@ +using ConsoleTools; +using Pannella.Helpers; +using Pannella.Models; +using Pannella.Models.Settings; + +namespace Pannella; + +internal partial class Program +{ + private static int DisplayMenuNew() + { + Console.Clear(); + + string[] menuItems = + { + "Update All", + "Update Firmware", + "Download Required Assets", + "Select Cores", + "Download Platform Image Packs", + "Generate Instance JSON Files", + "Generate Game and Watch ROMS", + "Enable All Display Modes", + "Backup Saves Directory", + "Settings", + "Exit" + }; + + Random random = new Random(); + int i = random.Next(0, WELCOME_MESSAGES.Length); + string welcome = WELCOME_MESSAGES[i]; + int choice = 0; + + var menu = new ConsoleMenu() + .Configure(config => + { + config.Selector = "=>"; + //config.EnableFilter = true; + config.Title = $"{welcome}\r\n{GetRandomSponsorLinks()}\r\n"; + config.EnableWriteTitle = true; + //config.EnableBreadcrumb = true; + config.WriteHeaderAction = () => Console.WriteLine("Choose your destiny:"); + config.SelectedItemBackgroundColor = Console.ForegroundColor; + config.SelectedItemForegroundColor = Console.BackgroundColor; + }); + + foreach (var item in menuItems) + { + menu.Add(item, thisMenu => + { + choice = thisMenu.CurrentItem.Index; + thisMenu.CloseMenu(); + }); + } + + menu.Show(); + + return choice; + } + + private static void AskAboutNewCores(bool force = false) + { + while (GlobalHelper.SettingsManager.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(false).Key; + + GlobalHelper.SettingsManager.GetConfig().download_new_cores = response switch + { + ConsoleKey.Y => "yes", + ConsoleKey.N => "no", + ConsoleKey.A => "ask", + _ => null + }; + } + } + + private static void RunCoreSelector(List cores, string message = "Select your cores.") + { + if (GlobalHelper.SettingsManager.GetConfig().download_new_cores?.ToLowerInvariant() == "yes") + { + foreach (Core core in cores) + { + GlobalHelper.SettingsManager.EnableCore(core.identifier); + } + } + else + { + const int pageSize = 15; + var offset = 0; + bool more = true; + + 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("{1}", item.Index, item.Name); + 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 = GlobalHelper.SettingsManager.GetCoreSettings(core.identifier); + var selected = !coreSettings.skip; + var name = core.identifier; + + if (core.requires_license) + { + name += " (Requires beta access)"; + } + + var title = MenuItemName(name, selected); + + menu.Add(title, thisMenu => + { + selected = !selected; + + if (!selected) + { + GlobalHelper.SettingsManager.DisableCore(core.identifier); + } + else + { + GlobalHelper.SettingsManager.EnableCore(core.identifier); + } + + thisMenu.CurrentItem.Name = MenuItemName(core.identifier, selected); + }); + } + } + + 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(); + } + } + + GlobalHelper.SettingsManager.GetConfig().core_selector = false; + GlobalHelper.SettingsManager.SaveSettings(); + } + + private static void SettingsMenu() + { + Console.Clear(); + + var menuItems = new Dictionary + { + { "download_firmware", "Download Firmware Updates during 'Update All'" }, + { "download_assets", "Download Missing Assets (ROMs and BIOS Files) during 'Update All'" }, + { "build_instance_jsons", "Build game JSON files for supported cores during 'Update All'" }, + { "delete_skipped_cores", "Delete untracked cores during 'Update All'" }, + { "fix_jt_names", "Automatically rename Jotego cores during 'Update All" }, + { "crc_check", "Use CRC check when checking ROMs and BIOS files" }, + { "preserve_platforms_folder", "Preserve 'Platforms' folder during 'Update All'" }, + { "skip_alternative_assets", "Skip alternative roms when downloading assets" }, + { "backup_saves", "Compress and backup Saves directory during 'Update All'" }, + { "use_custom_archive", "Use custom asset archive" } + }; + + var type = typeof(Config); + 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("{1}", item.Index, item.Name); + config.WriteItemAction = item => Console.Write("{0}", item.Name); + }); + + foreach (var (name, text) in menuItems) + { + var property = type.GetProperty(name); + var value = (bool)property.GetValue(GlobalHelper.SettingsManager.GetConfig()); + var title = MenuItemName(text, value); + + menu.Add(title, thisMenu => + { + value = !value; + property.SetValue(GlobalHelper.SettingsManager.GetConfig(), value); + thisMenu.CurrentItem.Name = MenuItemName(text, value); + }); + } + + menu.Add("Save", thisMenu => { thisMenu.CloseMenu(); }); + + menu.Show(); + + GlobalHelper.SettingsManager.SaveSettings(); + } + + private static string MenuItemName(string title, bool value) + { + return $"[{ (value ? "x" : " ") }] {title}"; + } +} diff --git a/src/partials/Program.Sponsors.cs b/src/partials/Program.Sponsors.cs new file mode 100644 index 00000000..8e7c9344 --- /dev/null +++ b/src/partials/Program.Sponsors.cs @@ -0,0 +1,65 @@ +using System.Text; +using Pannella.Helpers; +using Pannella.Models; + +namespace Pannella; + +internal partial class Program +{ + private static string GetRandomSponsorLinks() + { + var output = new StringBuilder(); + + if (GlobalHelper.InstalledCores.Count > 0) + { + var random = new Random(); + var index = random.Next(GlobalHelper.InstalledCores.Count); + var randomItem = GlobalHelper.InstalledCores[index]; + + if (randomItem.sponsor != null) + { + var author = randomItem.GetConfig().metadata.author; + + output.AppendLine(); + output.AppendLine($"Please consider supporting {author} for their work on the {randomItem} core:"); + output.Append(randomItem.sponsor); + } + } + + return output.ToString(); + } + + private static void Funding(string identifier) + { + if (GlobalHelper.InstalledCores.Count == 0) + { + return; + } + + List cores = new List(); + + if (identifier == null) + { + cores = GlobalHelper.InstalledCores; + } + else + { + var c = GlobalHelper.GetCore(identifier); + + if (c != null && c.IsInstalled()) + { + cores.Add(c); + } + } + + foreach (Core core in cores) + { + if (core.sponsor != null) + { + Console.WriteLine(); + Console.WriteLine($"{core.identifier}:"); + Console.WriteLine(core.sponsor); + } + } + } +} diff --git a/src/partials/Program.UpdateSelfAndRun.cs b/src/partials/Program.UpdateSelfAndRun.cs new file mode 100644 index 00000000..fd912fb2 --- /dev/null +++ b/src/partials/Program.UpdateSelfAndRun.cs @@ -0,0 +1,66 @@ +using System.Diagnostics; +using System.IO.Compression; +using System.Reflection; + +namespace Pannella; + +internal partial class Program +{ + private static int UpdateSelfAndRun(string directory, string[] updaterArgs) + { + string execName = "pupdate"; + + if (SYSTEM_OS_PLATFORM == "win") + { + execName += ".exe"; + } + + string execLocation = Path.Combine(directory, execName); + string backupName = $"{execName}.backup"; + string backupLocation = Path.Combine(directory, backupName); + const string updateName = "pupdate.zip"; + string updateLocation = Path.Combine(directory, updateName); + + int exitCode = int.MinValue; + + try + { + // Load System.IO.Compression now + Assembly.Load("System.IO.Compression"); + + if (SYSTEM_OS_PLATFORM != "win") + { + Assembly.Load("System.IO.Pipes"); + } + + // Move current process file + Console.WriteLine($"Renaming {execLocation} to {backupLocation}"); + File.Move(execLocation, backupLocation, true); + + // Extract update + Console.WriteLine($"Extracting {updateLocation} to {directory}"); + ZipFile.ExtractToDirectory(updateLocation, directory, true); + + // Execute + Console.WriteLine($"Executing {execLocation}"); + + ProcessStartInfo pInfo = new ProcessStartInfo(execLocation) + { + Arguments = string.Join(' ', updaterArgs), + UseShellExecute = false + }; + + Process p = Process.Start(pInfo); + + p.WaitForExit(); + + exitCode = p.ExitCode; + } + catch (Exception e) + { + Console.Error.WriteLine($"An error occurred: {e.GetType().Name}:{e}"); + } + + return exitCode; + } +} diff --git a/src/services/AnalogueFirmware.cs b/src/services/AnalogueFirmware.cs deleted file mode 100644 index 3fdab280..00000000 --- a/src/services/AnalogueFirmware.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Text.Json; - -namespace pannella.analoguepocket; -using Analogue; - -public static class AnalogueFirmware -{ - private const string BASE_URL = "https://www.analogue.co/"; - private const string VERSION = "support/pocket/firmware/latest"; - private const string DOWNLOAD = "support/pocket/firmware/{0}/download"; - private const string DETAILS = "support/pocket/firmware/{0}/details"; - - private static ReleaseDetails? latest = null; - - public static async Task GetLatestVersion() - { - ReleaseDetails? details = await GetDetails(); - return details.version; - } - - public static async Task GetDetails(string version = "latest") - { - if(latest != null) { - return latest; - } - string url = String.Format(BASE_URL + DETAILS, version); - string response = await Factory.GetHttpHelper().GetHTML(url); - ReleaseDetails? details = JsonSerializer.Deserialize(response); - - if (version == "latest") { - latest = details; - } - - return details; - } - - public static async Task GetFirmwareUrl(string version = "latest") - { - ReleaseDetails? details = await GetDetails(version); - return details.download_url; - } -} \ No newline at end of file diff --git a/src/services/AnalogueFirmwareService.cs b/src/services/AnalogueFirmwareService.cs new file mode 100644 index 00000000..16475867 --- /dev/null +++ b/src/services/AnalogueFirmwareService.cs @@ -0,0 +1,34 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using Pannella.Helpers; +using Pannella.Models.Analogue; + +namespace Pannella.Services; + +[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] +public static class AnalogueFirmwareService +{ + private const string BASE_URL = "https://www.analogue.co/"; + private const string DETAILS = "support/pocket/firmware/{0}/details"; + + private static ReleaseDetails latest; + + public static async Task GetDetails(string version = "latest") + { + if (latest != null) + { + return latest; + } + + string url = string.Format(BASE_URL + DETAILS, version); + string response = await HttpHelper.Instance.GetHTML(url); + ReleaseDetails details = JsonSerializer.Deserialize(response); + + if (version == "latest") + { + latest = details; + } + + return details; + } +} diff --git a/src/services/ArchiveService.cs b/src/services/ArchiveService.cs index 3822988c..4bb2345a 100644 --- a/src/services/ArchiveService.cs +++ b/src/services/ArchiveService.cs @@ -1,31 +1,36 @@ +using System.Diagnostics.CodeAnalysis; using System.Text.Json; +using Pannella.Helpers; +using Pannella.Models.Archive; -namespace pannella.analoguepocket; -using archiveorg; +namespace Pannella.Services; +[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] public static class ArchiveService { private const string END_POINT = "https://archive.org/metadata/{0}"; - public static async Task GetFiles(string archive) { - string url = String.Format(END_POINT, archive); - string json = await Factory.GetHttpHelper().GetHTML(url); + string url = string.Format(END_POINT, archive); + string json = await HttpHelper.Instance.GetHTML(url); Archive result = JsonSerializer.Deserialize(json); return result; } - public static async Task GetFilesCustom(string url) + public static async Task GetFilesCustom(string url) { - try { - string json = await Factory.GetHttpHelper().GetHTML(url); + try + { + string json = await HttpHelper.Instance.GetHTML(url); Archive result = JsonSerializer.Deserialize(json); return result; - } catch(Exception e) { + } + catch (Exception) + { return null; } } -} \ No newline at end of file +} diff --git a/src/services/AssetsService.cs b/src/services/AssetsService.cs index a80f3608..05ff4270 100644 --- a/src/services/AssetsService.cs +++ b/src/services/AssetsService.cs @@ -1,21 +1,27 @@ +using System.Diagnostics.CodeAnalysis; using System.IO.Compression; using System.Text.Json; +using Pannella.Helpers; -namespace pannella.analoguepocket; +namespace Pannella.Services; +[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] public static class AssetsService { - private const string IMAGE_PACKS = "https://raw.githubusercontent.com/mattpannella/pupdate/main/image_packs.json"; private const string BLACKLIST = "https://raw.githubusercontent.com/mattpannella/pupdate/main/blacklist.json"; public static void BackupSaves(string directory, string backupLocation) { if (string.IsNullOrEmpty(directory)) + { throw new ArgumentNullException(nameof(directory)); + } if (string.IsNullOrEmpty(backupLocation)) + { throw new ArgumentNullException(nameof(backupLocation)); - + } + Console.WriteLine("Compressing and backing up Saves directory..."); string savesPath = Path.Combine(directory, "Saves"); string fileName = $"Saves_Backup_{DateTime.Now:yyyy-MM-dd_HH.mm.ss}.zip"; @@ -24,8 +30,10 @@ public static void BackupSaves(string directory, string backupLocation) if (Directory.Exists(savesPath)) { if (!Directory.Exists(backupLocation)) + { Directory.CreateDirectory(backupLocation); - + } + ZipFile.CreateFromDirectory(savesPath, archiveName); Console.WriteLine("Complete."); } @@ -34,28 +42,12 @@ public static void BackupSaves(string directory, string backupLocation) Console.WriteLine("No Saves directory found, skipping backup..."); } } - - public static async Task GetImagePacks() - { - string json = await Factory.GetHttpHelper().GetHTML(IMAGE_PACKS); - ImagePack[] packs = JsonSerializer.Deserialize(json); - - if(packs != null) { - return packs; - } - - return new ImagePack[0]; - } public static async Task GetBlacklist() { - string json = await Factory.GetHttpHelper().GetHTML(BLACKLIST); - string[] files = JsonSerializer.Deserialize(json); - - if(files != null) { - return files; - } + string json = await HttpHelper.Instance.GetHTML(BLACKLIST); + string[] files = JsonSerializer.Deserialize(json); - return new string[0]; + return files ?? Array.Empty(); } -} \ No newline at end of file +} diff --git a/src/services/CoresService.cs b/src/services/CoresService.cs index 548d21ab..10d96317 100644 --- a/src/services/CoresService.cs +++ b/src/services/CoresService.cs @@ -1,21 +1,33 @@ +using System.Diagnostics.CodeAnalysis; using System.Text.Json; +using Pannella.Helpers; +using Pannella.Models; -namespace pannella.analoguepocket; +namespace Pannella.Services; +[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] public static class CoresService { private const string END_POINT = "https://openfpga-cores-inventory.github.io/analogue-pocket/api/v2/cores.json"; public static async Task> GetCores() { - string json = await Factory.GetHttpHelper().GetHTML(END_POINT); - Dictionary> parsed = JsonSerializer.Deserialize>>(json); + string json = await HttpHelper.Instance.GetHTML(END_POINT); - if(parsed.ContainsKey("data")) { - var cores = parsed["data"]; - return cores; - } else { - throw new Exception("Error communicating with openFPGA Cores API"); + try + { + Dictionary> parsed = JsonSerializer.Deserialize>>(json); + + if (parsed.TryGetValue("data", out var cores)) + { + return cores; + } + } + catch (Exception e) + { + Console.WriteLine(e); } + + throw new Exception("Error communicating with openFPGA Cores API"); } -} \ No newline at end of file +} diff --git a/src/services/GithubApiService.cs b/src/services/GithubApiService.cs index b479acdf..6d397bb4 100644 --- a/src/services/GithubApiService.cs +++ b/src/services/GithubApiService.cs @@ -1,83 +1,86 @@ -using System.Text.Json; +using System.Diagnostics.CodeAnalysis; using System.Net.Http.Headers; -using System.Net.Http; +using System.Text.Json; +using Pannella.Helpers; +using Pannella.Models.Github; +using GithubFile = Pannella.Models.Github.File; -namespace pannella.analoguepocket; +namespace Pannella.Services; -public static class GithubApi +[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] +public static class GithubApiService { private const string RELEASES = "https://api.github.com/repos/{0}/{1}/releases"; private const string CONTENTS = "https://api.github.com/repos/{0}/{1}/contents/{2}"; - public static async Task> GetReleases(string user, string repository, string? token = "") + public static async Task> GetReleases(string user, string repository) { - string url = String.Format(RELEASES, user, repository); - var responseBody = await CallAPI(url, token); - - List? releases = JsonSerializer.Deserialize>(responseBody); - - if(releases == null) { - releases = new List(); - } + string url = string.Format(RELEASES, user, repository); + var responseBody = await CallAPI(url, GlobalHelper.SettingsManager.GetConfig().github_token); + List releases = JsonSerializer.Deserialize>(responseBody) ?? new List(); return releases; } - public static async Task GetRelease(string user, string repository, string tag_name, string? token = "") + public static async Task GetRelease(string user, string repository, string tagName) { - string url = String.Format(RELEASES, user, repository) + "/tags/" + tag_name; - - var responseBody = await CallAPI(url, token); - Github.Release? release = JsonSerializer.Deserialize(responseBody); - + string url = string.Format(RELEASES, user, repository) + "/tags/" + tagName; + var responseBody = await CallAPI(url, GlobalHelper.SettingsManager.GetConfig().github_token); + Release release = JsonSerializer.Deserialize(responseBody); + return release; } - public static async Task GetLatestRelease(string user, string repository, string? token = "") + public static async Task GetLatestRelease(string user, string repository) { - string url = String.Format(RELEASES, user, repository) + "/latest"; - - var responseBody = await CallAPI(url, token); - Github.Release? release = JsonSerializer.Deserialize(responseBody); - + string url = string.Format(RELEASES, user, repository) + "/latest"; + var responseBody = await CallAPI(url, GlobalHelper.SettingsManager.GetConfig().github_token); + Release release = JsonSerializer.Deserialize(responseBody); + return release; } - public static async Task GetFile(string user, string repository, string path, string? token = "") + public static async Task GetFile(string user, string repository, string path) { - string url = String.Format(CONTENTS, user, repository, path); - - var responseBody = await CallAPI(url, token); - Github.File? file = JsonSerializer.Deserialize(responseBody); - + string url = string.Format(CONTENTS, user, repository, path); + var responseBody = await CallAPI(url, GlobalHelper.SettingsManager.GetConfig().github_token); + GithubFile file = JsonSerializer.Deserialize(responseBody); + return file; } - public static async Task?> GetFiles(string user, string repository, string path, string? token = "") + public static async Task> GetFiles(string user, string repository, string path) { - string url = String.Format(CONTENTS, user, repository, path); - - var responseBody = await CallAPI(url, token); - List? files = JsonSerializer.Deserialize>(responseBody); - + string url = string.Format(CONTENTS, user, repository, path); + var responseBody = await CallAPI(url, GlobalHelper.SettingsManager.GetConfig().github_token); + List files = JsonSerializer.Deserialize>(responseBody); + return files; } - private static async Task CallAPI(string url, string? token = "") + private static async Task CallAPI(string url, string token) { var client = new HttpClient(); + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var request = new HttpRequestMessage { Method = HttpMethod.Get, RequestUri = new Uri(url) }; + var agent = new ProductInfoHeaderValue("Analogue-Pocket-Updater-Utility", "1.0"); + request.Headers.UserAgent.Add(agent); - if(token != null && token != "") { + + if (!string.IsNullOrEmpty(token)) + { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("token", token); } + var response = await client.SendAsync(request).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); var responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false); diff --git a/src/services/ImagePacksService.cs b/src/services/ImagePacksService.cs index 07b96962..327b8387 100644 --- a/src/services/ImagePacksService.cs +++ b/src/services/ImagePacksService.cs @@ -1,20 +1,20 @@ +using System.Diagnostics.CodeAnalysis; using System.Text.Json; +using Pannella.Helpers; +using Pannella.Models; -namespace pannella.analoguepocket; +namespace Pannella.Services; +[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] public static class ImagePacksService { private const string END_POINT = "https://raw.githubusercontent.com/mattpannella/pupdate/main/image_packs.json"; public static async Task GetImagePacks() { - string json = await Factory.GetHttpHelper().GetHTML(END_POINT); - ImagePack[] packs = JsonSerializer.Deserialize(json); + string json = await HttpHelper.Instance.GetHTML(END_POINT); + ImagePack[] packs = JsonSerializer.Deserialize(json); - if(packs != null) { - return packs; - } - - return new ImagePack[0]; + return packs ?? Array.Empty(); } -} \ No newline at end of file +}