diff --git a/Downloads/VersionSelector.xaml b/Downloads/VersionSelector.xaml
index 7b1651f..1a1f557 100644
--- a/Downloads/VersionSelector.xaml
+++ b/Downloads/VersionSelector.xaml
@@ -11,10 +11,10 @@
-
+
-
+
diff --git a/Downloads/VersionSelector.xaml.cs b/Downloads/VersionSelector.xaml.cs
index 955f4fc..d99d290 100644
--- a/Downloads/VersionSelector.xaml.cs
+++ b/Downloads/VersionSelector.xaml.cs
@@ -10,13 +10,13 @@ namespace RomM.VersionSelector
public partial class RomMVersionSelector : PluginUserControl
{
- public ObservableCollection Siblings { get; set; }
+ public ObservableCollection RomVersions { get; set; }
public bool Cancelled { get; set; } = true;
- public RomMVersionSelector(List siblings)
+ public RomMVersionSelector(List romVersions)
{
- Siblings = new ObservableCollection(siblings);
+ RomVersions = new ObservableCollection(romVersions);
InitializeComponent();
}
diff --git a/Games/RomMGameInfo.cs b/Games/RomMGameInfo.cs
index cf82e2f..9de9beb 100644
--- a/Games/RomMGameInfo.cs
+++ b/Games/RomMGameInfo.cs
@@ -7,7 +7,7 @@
using System.IO;
using System.Linq;
using ProtoBuf;
-using Playnite.SDK;
+using RomM.Models.RomM.Rom;
namespace RomM.Games
{
@@ -68,7 +68,7 @@ private static T FromGameIdString(string gameId) where T : RomMGameInfo
}
}
- public InstallController GetInstallController(Game game, RomM romm, bool HasSiblings, int SelectedSibling) => new RomMInstallController(game, romm, HasSiblings, SelectedSibling);
+ public InstallController GetInstallController(Game game, RomM romm, GameInstallInfo GameData) => new RomMInstallController(game, romm, GameData);
public UninstallController GetUninstallController(Game game, RomM romm) => new RomMUninstallController(game, romm);
diff --git a/Games/RomMImport.cs b/Games/RomMImport.cs
new file mode 100644
index 0000000..7670172
--- /dev/null
+++ b/Games/RomMImport.cs
@@ -0,0 +1,475 @@
+using Newtonsoft.Json;
+using Playnite.SDK;
+using Playnite.SDK.Models;
+using Playnite.SDK.Plugins;
+using RomM.Models.RomM.Rom;
+using RomM.Settings;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace RomM.Games
+{
+ internal class RomMImport
+ {
+ private readonly RomM _plugin;
+ LibraryImportGamesArgs _args;
+ EmulatorMapping _mapping;
+ List _ROMs;
+ Dictionary _completionStatusMap;
+ List _favourites;
+
+ public RomMImport(RomM plugin, LibraryImportGamesArgs args, EmulatorMapping mapping, List roms, List favourites)
+ {
+ _plugin = plugin;
+ _args = args;
+ _mapping = mapping;
+ _ROMs = roms;
+ _completionStatusMap = plugin.Playnite.Database.CompletionStatuses.ToDictionary(cs => cs.Name, cs => cs.Id);
+ _favourites = favourites;
+ }
+
+ private RomMFile DetermineFile(RomMRom ROM)
+ {
+ if(ROM.Files.Count == 0)
+ return null;
+
+ if(ROM.Files.Count > 1)
+ {
+ List fullpaths = new List();
+ foreach (var file in ROM.Files)
+ {
+ fullpaths.Add(file.FullPath);
+ }
+
+ fullpaths = fullpaths.OrderBy(x => x.Count(c => c == '/')).ToList();
+ return ROM.Files.Where(x => x.FullPath == fullpaths[0]).FirstOrDefault();
+ }
+
+ return ROM.Files.FirstOrDefault();
+ }
+
+ // Main library import functions
+ public List ProcessData()
+ {
+ var games = new List();
+ List ImportedGamesIDs = new List();
+ _plugin.PlayniteApi.Database.Platforms.Add(_mapping.RomMPlatform.Name);
+
+ foreach (var ROM in _ROMs)
+ {
+ if (_args.CancelToken.IsCancellationRequested)
+ break;
+
+ // Some newer platforms don't get a hash value so we will compromise with this
+ if (string.IsNullOrEmpty(ROM.SHA1))
+ {
+ var tohash = $"{ROM.Name}{ROM.FileSizeBytes}";
+
+ using (SHA1Managed sha1 = new SHA1Managed())
+ {
+ var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(tohash));
+ var sb = new StringBuilder(hash.Length * 2);
+
+ foreach (byte b in hash)
+ {
+ sb.Append(b.ToString("x2"));
+ }
+
+ ROM.SHA1 = sb.ToString();
+ }
+ }
+
+ // Skip game import if the ROM is apart of the exclusion list
+ if (_plugin.Playnite.Database.ImportExclusions[Playnite.ImportExclusionItem.GetId($"{ROM.Id}:{ROM.SHA1}", _plugin.Id)] != null)
+ {
+ _plugin.Logger.Warn($"[Importer] Excluding {ROM.Name} from import.");
+ continue;
+ }
+
+ // Skip if ROM has no filename
+ if (string.IsNullOrEmpty(ROM.FileName))
+ {
+ _plugin.Playnite.Notifications.Add(new NotificationMessage(_plugin.Id.ToString(), $"Filename for ROM ID: {ROM.Id} doesn't exist!\nDoes ROM exist on the servers filesystem?", NotificationType.Error));
+ continue;
+ }
+
+ // Fail-safe incase none of these are set to true
+ if (!ROM.HasSimpleSingleFile & !ROM.HasNestedSingleFile & !ROM.HasMultipleFiles)
+ ROM.HasMultipleFiles = true;
+
+ // Migrate old RomMGameInfo id to new romMId:SHA1 id
+ string gameID = $"{ROM.Id}:{ROM.SHA1}";
+ UpdatedOldGameID(ROM);
+
+ // Merging revisions
+ if (_plugin.Settings.MergeRevisions && ROM.Siblings?.Count > 0)
+ {
+ if (CheckForMainSibling(ROM) == MainSibling.Other)
+ {
+ var siblinggame = _plugin.Playnite.Database.Games.FirstOrDefault(x => x.GameId == gameID);
+ if(siblinggame != null)
+ {
+ _plugin.Playnite.Database.Games.Remove(siblinggame);
+ }
+ continue;
+ }
+
+ if (ROM.Processed)
+ {
+ var siblinggame = _plugin.Playnite.Database.Games.FirstOrDefault(x => x.GameId == gameID);
+ if (siblinggame != null)
+ {
+ _plugin.Playnite.Database.Games.Remove(siblinggame);
+ }
+ continue;
+ }
+
+ }
+
+ // Save Game ROM data to file
+ SaveGameData(ROM);
+
+ // Skip full import if ROM has already been imported
+ Guid statusID = new Guid();
+ var game = _plugin.Playnite.Database.Games.FirstOrDefault(g => g.GameId == gameID);
+ if (game != null)
+ {
+ // Sync user data
+ if(_plugin.Settings.KeepRomMSynced)
+ {
+ statusID = DetermineCompletionStatus(ROM);
+
+ game.Favorite = _favourites.Exists(f => f == ROM.Id);
+
+ if (statusID != Guid.Empty)
+ {
+ game.CompletionStatusId = statusID;
+ }
+ _plugin.Playnite.Database.Games.Update(game);
+ }
+
+ ImportedGamesIDs.Add(gameID);
+ continue;
+ }
+
+ // If keep deleted games is enabled and a deleted game gets re-added back to the server under a new romMId, Update playnite entry
+ if(_plugin.Settings.KeepDeletedGames)
+ {
+ if(UpdatedDeletedGame(ROM))
+ {
+ ImportedGamesIDs.Add(gameID);
+ continue;
+ }
+ }
+
+ var importedGame = ImportGame(ROM, statusID);
+ if (importedGame != null)
+ {
+ games.Add(importedGame);
+ ImportedGamesIDs.Add(gameID);
+ }
+ else
+ {
+ _plugin.Logger.Error($"[Importer] Failed to import RomM GameID: {ROM.Id}");
+ continue;
+ }
+ }
+ _plugin.Logger.Info($"[Importer] Finished adding new games for {_mapping.RomMPlatform.Name}");
+
+ if (!_plugin.Settings.KeepDeletedGames)
+ {
+ RemoveMissingGames(ImportedGamesIDs);
+ }
+
+ return games;
+ }
+ private Game ImportGame(RomMRom ROM, Guid StatusID)
+ {
+ var rootInstallDir = _plugin.Playnite.Paths.IsPortable
+ ? _mapping.DestinationPathResolved.Replace(_plugin.Playnite.Paths.ApplicationPath, ExpandableVariables.PlayniteDirectory)
+ : _mapping.DestinationPathResolved;
+ var gameInstallDir = Path.Combine(rootInstallDir, Path.GetFileNameWithoutExtension(ROM.Name));
+ var pathToGame = Path.Combine(gameInstallDir, ROM.Name);
+
+ var gameNameWithTags =
+ $"{ROM.Name}" +
+ $"{(ROM.Regions.Count > 0 ? $" ({string.Join(", ", ROM.Regions)})" : "")}" +
+ $"{(!string.IsNullOrEmpty(ROM.Revision) ? $" (Rev {ROM.Revision})" : "")}" +
+ $"{(ROM.Tags.Count > 0 ? $" ({string.Join(", ", ROM.Tags)})" : "")}";
+
+ var preferedRatingsBoard = _plugin.Playnite.ApplicationSettings.AgeRatingOrgPriority;
+ var agerating = ROM.Metadatum.Age_Ratings.Count > 0 ? new HashSet(ROM.Metadatum.Age_Ratings.Where(r => r.Split(':')[0] == preferedRatingsBoard.ToString()).Select(r => new MetadataNameProperty(r.ToString()))) : null;
+
+ var status = _plugin.Playnite.Database.CompletionStatuses.Get(StatusID);
+ var completionStatusProperty = status != null ? new MetadataNameProperty(status.Name) : null;
+
+ List gameLinks = new List();
+ if(ROM.SSId != null)
+ gameLinks.Add(new Link("Screenscraper", $"https://www.screenscraper.fr/gameinfos.php?gameid={ROM.SSId}"));
+ if (ROM.HasheousId != null)
+ gameLinks.Add(new Link("Hasheous", $"https://hasheous.org/index.html?page=dataobjectdetail&type=game&id={ROM.HasheousId}"));
+ if (ROM.RAId != null)
+ gameLinks.Add(new Link("RetroAchievements", $"https://retroachievements.org/game/{ROM.RAId}"));
+ if (ROM.HLTBId != null)
+ gameLinks.Add(new Link("HowLongToBeat", $"https://howlongtobeat.com/game/{ROM.HLTBId}"));
+
+ var metadata = new GameMetadata
+ {
+ Source = _plugin.Source,
+ GameId = $"{ROM.Id}:{ROM.SHA1}",
+
+ Name = ROM.Name,
+ Description = ROM.Summary,
+
+ Platforms = new HashSet { new MetadataNameProperty(_mapping.RomMPlatform.Name ?? "") },
+ Regions = new HashSet(ROM.Regions.Where(r => !string.IsNullOrEmpty(r)).Select(r => new MetadataNameProperty(r.ToString()))),
+ Genres = new HashSet(ROM.Metadatum.Genres.Where(r => !string.IsNullOrEmpty(r)).Select(r => new MetadataNameProperty(r.ToString()))),
+ AgeRatings = agerating,
+ Series = new HashSet(ROM.Metadatum.Franchises.Where(r => !string.IsNullOrEmpty(r)).Select(r => new MetadataNameProperty(r.ToString()))),
+ Features = new HashSet(ROM.Metadatum.Gamemodes.Where(r => !string.IsNullOrEmpty(r)).Select(r => new MetadataNameProperty(r.ToString()))),
+ Categories = new HashSet(ROM.Metadatum.Collections.Where(r => !string.IsNullOrEmpty(r)).Select(r => new MetadataNameProperty(r.ToString()))),
+
+ ReleaseDate = ROM.Metadatum.Release_Date.HasValue ? new ReleaseDate(new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(ROM.Metadatum.Release_Date.Value).ToLocalTime()) : new ReleaseDate(),
+ CommunityScore = (int?)ROM.Metadatum.Average_Rating,
+
+ CoverImage = !string.IsNullOrEmpty(ROM.PathCoverL) ? new MetadataFile($"{_plugin.Settings.RomMHost}{ROM.PathCoverL}") : null,
+
+ Favorite = _favourites.Exists(f => f == ROM.Id),
+ LastActivity = ROM.RomUser.LastPlayed,
+ UserScore = ROM.RomUser.Rating * 10, //RomM-Rating is 1-10, Playnite 1-100, so it can unfortunately only by synced one direction without loosing decimals
+ CompletionStatus = completionStatusProperty,
+ Links = gameLinks,
+ Roms = new List { new GameRom(gameNameWithTags, pathToGame) },
+ InstallDirectory = gameInstallDir,
+ IsInstalled = File.Exists(pathToGame),
+ InstallSize = ROM.FileSizeBytes,
+ GameActions = new List
+ {
+ new GameAction
+ {
+ Name = $"Play in {_mapping.Emulator.Name}",
+ Type = GameActionType.Emulator,
+ EmulatorId = _mapping.EmulatorId,
+ EmulatorProfileId = _mapping.EmulatorProfileId,
+ IsPlayAction = true,
+ },
+ new GameAction
+ {
+ Type = GameActionType.URL,
+ Name = "View in RomM",
+ Path = _plugin.CombineUrl(_plugin.Settings.RomMHost, $"rom/{ROM.Id}"),
+ IsPlayAction = false
+ }
+ }
+ };
+
+ // Import new game
+ Game game = _plugin.Playnite.Database.ImportGame(metadata, _plugin);
+
+ if (ROM.HasManual)
+ {
+ game.Manual = $"{_plugin.Settings.RomMHost}/assets/romm/resources/{ROM.ManualPath}";
+ }
+
+ return game;
+ }
+ private void RemoveMissingGames(List ImportedGames)
+ {
+ var gamesInDatabase = _plugin.Playnite.Database.Games.Where(g =>
+ g.Source != null && g.Source.Name == _plugin.Source.ToString() &&
+ g.Platforms != null && g.Platforms.Any(p => p.Name == _mapping.RomMPlatform.Name)
+ );
+
+ _plugin.Logger.Info($"[Importer] Starting to remove not found games for {_mapping.RomMPlatform.Name}.");
+
+ foreach (var game in gamesInDatabase)
+ {
+ if (_args.CancelToken.IsCancellationRequested)
+ break;
+
+ if (ImportedGames.Contains(game.GameId))
+ {
+ continue;
+ }
+
+ _plugin.Playnite.Database.Games.Remove(game.Id);
+ _plugin.Logger.Info($"[Importer] Removing {game.Name} - {game.Id} for {_mapping.RomMPlatform.Name}");
+ }
+
+ _plugin.Logger.Info($"[Importer] Finished removing not found games for {_mapping.RomMPlatform.Name}");
+ }
+ private bool UpdatedOldGameID(RomMRom ROM)
+ {
+ var filename = ROM.HasMultipleFiles ? Path.GetFileName(ROM.FileName) : Path.GetFileName(ROM.Files.Where(f => f.FullPath.Count(c => c == '/') <= 3).FirstOrDefault().FileName);
+ if (string.IsNullOrWhiteSpace(filename))
+ {
+ _plugin.Logger.Warn($"[Importer] Rom {ROM.Id} returned empty/invalid filename, skipping updating game id.");
+ return false;
+ }
+ var info = new RomMGameInfo
+ {
+ MappingId = _mapping.MappingId,
+ FileName = filename,
+ DownloadUrl = _plugin.CombineUrl(_plugin.Settings.RomMHost, $"api/roms/{ROM.Id}/content/{filename}"),
+ HasMultipleFiles = ROM.HasMultipleFiles
+ };
+
+ // Check to see if a game already exists with
+ var oldgame = _plugin.Playnite.Database.Games.FirstOrDefault(g => g.GameId == info.AsGameId());
+ if (oldgame != null)
+ {
+ oldgame.GameId = $"{ROM.Id}:{ROM.SHA1}";
+ oldgame.PlatformIds = new List { _plugin.Playnite.Database.Platforms.First(x => x.Name == _mapping.RomMPlatform.Name).Id };
+ _plugin.Playnite.Database.Games.Update(oldgame);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ private bool UpdatedDeletedGame(RomMRom ROM)
+ {
+ // Check to see if a game already exists with an old romMId
+ var oldgame = _plugin.Playnite.Database.Games.FirstOrDefault(g => g.PluginId == _plugin.Id && g.GameId.Split(':')[1] == ROM.SHA1);
+ if (oldgame != null)
+ {
+ oldgame.GameId = $"{ROM.Id}:{ROM.SHA1}";
+ _plugin.Playnite.Database.Games.Update(oldgame);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ private MainSibling CheckForMainSibling(RomMRom ROM)
+ {
+ //Check to see if ROM is the main sibling
+ if (ROM.RomUser.IsMainSibling)
+ return MainSibling.Current;
+
+ //Find if there is a main sibling
+ foreach (var sibling in ROM.Siblings)
+ {
+ var siblingROM = _ROMs.Find(x => x.Id == sibling.Id);
+
+ if (siblingROM.RomUser.IsMainSibling)
+ {
+ return MainSibling.Other;
+ }
+ }
+
+ return MainSibling.None;
+ }
+ private void SaveGameData(RomMRom ROM)
+ {
+ string[] versionBreakdown = _plugin.Settings.ServerVersion.Split('.');
+ float versionParsed = float.Parse(versionBreakdown[0]) + (float.Parse(versionBreakdown[1]) / 100);
+
+ RomMRomLocal toSave = new RomMRomLocal();
+ toSave.Name = ROM.Name;
+ toSave.SHA1 = ROM.SHA1;
+ toSave.MappingID = _mapping.MappingId;
+ toSave.ROMVersions = new List();
+
+ RomMRevision baseROM = new RomMRevision();
+
+ // Save base ROM data
+ baseROM.Id = ROM.Id;
+ baseROM.HasMultipleFiles = ROM.HasMultipleFiles;
+ if(!ROM.HasMultipleFiles)
+ {
+ var romfile = DetermineFile(ROM);
+ if (romfile == null)
+ {
+ _plugin.Logger.Error("[Importer] Unable to save ROM data as there is no rom file!");
+ return;
+ }
+
+ baseROM.FileName = romfile.FileName;
+ baseROM.DownloadURL = versionParsed <= 4.7 ?
+ _plugin.CombineUrl(_plugin.Settings.RomMHost, $"api/romsfiles/{romfile.Id}/content/{romfile.FileName}") :
+ _plugin.CombineUrl(_plugin.Settings.RomMHost, $"api/roms/{romfile.Id}/files/content/{romfile.FileName}");
+ }
+ else
+ {
+ baseROM.FileName = ROM.FileName;
+ baseROM.DownloadURL = _plugin.CombineUrl(_plugin.Settings.RomMHost, $"api/roms/{ROM.Id}/content/{ROM.FileName}");
+ }
+ baseROM.IsSelected = false;
+ toSave.ROMVersions.Add(baseROM);
+
+ // Save sibling data
+ if (_plugin.Settings.MergeRevisions && ROM.Siblings?.Count > 0)
+ {
+
+ foreach (var sibling in ROM.Siblings)
+ {
+ var siblingROM = _ROMs.Find(x => x.Id == sibling.Id);
+ if(siblingROM != null)
+ {
+ RomMRevision saveSibling = new RomMRevision();
+
+ saveSibling.Id = siblingROM.Id;
+ saveSibling.HasMultipleFiles = siblingROM.HasMultipleFiles;
+ if (!siblingROM.HasMultipleFiles)
+ {
+ var romfile = DetermineFile(siblingROM);
+ if (romfile == null)
+ {
+ _plugin.Logger.Error("[Importer] Unable to save sibling ROM data as there is no rom file!");
+ continue;
+ }
+
+ saveSibling.FileName = romfile.FileName;
+ saveSibling.DownloadURL = versionParsed <= 4.7 ?
+ _plugin.CombineUrl(_plugin.Settings.RomMHost, $"api/romsfiles/{romfile.Id}/content/{romfile.FileName}") :
+ _plugin.CombineUrl(_plugin.Settings.RomMHost, $"api/roms/{romfile.Id}/files/content/{romfile.FileName}");
+ }
+ else
+ {
+ saveSibling.FileName = siblingROM.FileName;
+ saveSibling.DownloadURL = _plugin.CombineUrl(_plugin.Settings.RomMHost, $"api/roms/{siblingROM.Id}/content/{siblingROM.FileName}");
+ }
+ saveSibling.IsSelected = false;
+ _ROMs.First(x => x.Id == sibling.Id).Processed = true;
+
+ toSave.ROMVersions.Add(saveSibling);
+ }
+ }
+ }
+
+ // Write data to file
+ string json = JsonConvert.SerializeObject(toSave);
+ File.WriteAllText($"{_plugin.ROMDataPath}{ROM.SHA1}.json", json);
+
+ }
+
+ private Guid DetermineCompletionStatus(RomMRom ROM)
+ {
+ string completionStatus;
+ // Determine status in Playnite. Backlogged and "now playing" take precedent over the status options
+ if (ROM.RomUser.Backlogged || ROM.RomUser.NowPlaying)
+ {
+ completionStatus = ROM.RomUser.NowPlaying ? RomMRomUser.CompletionStatusMap["now_playing"] : RomMRomUser.CompletionStatusMap["backlogged"];
+ }
+ else
+ {
+ completionStatus = RomMRomUser.CompletionStatusMap[ROM.RomUser.Status ?? "not_played"];
+ }
+
+ _completionStatusMap.TryGetValue(completionStatus, out var statusId);
+
+ var status = _plugin.Playnite.Database.CompletionStatuses.Get(statusId);
+ var completionStatusProperty = status != null ? new MetadataNameProperty(status.Name) : null;
+
+ return statusId;
+ }
+ }
+}
diff --git a/Games/RomMImportController.cs b/Games/RomMImportController.cs
new file mode 100644
index 0000000..d40bcfe
--- /dev/null
+++ b/Games/RomMImportController.cs
@@ -0,0 +1,231 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Playnite.SDK;
+using Playnite.SDK.Models;
+using Playnite.SDK.Plugins;
+using RomM.Models.RomM.Collection;
+using RomM.Models.RomM.Platform;
+using RomM.Models.RomM.Rom;
+using RomM.Settings;
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+using System.Web;
+
+namespace RomM.Games
+{
+ class RomMImportController
+ {
+ private readonly RomM _plugin;
+ public ILogger Logger => LogManager.GetLogger();
+
+ public RomMImportController(RomM plugin)
+ {
+ _plugin = plugin;
+ }
+
+ public List Import(LibraryImportGamesArgs args)
+ {
+ IList apiPlatforms = FetchPlatforms();
+ List>> tasks = new List>>();
+ List games = new List();
+ IEnumerable enabledMappings = SettingsViewModel.Instance.Mappings?.Where(m => m.Enabled);
+ string url = BuildROMUrl();
+
+ if (enabledMappings == null || !enabledMappings.Any())
+ {
+ _plugin.Playnite.Notifications.Add(_plugin.Id.ToString(), "No emulators are configured or enabled in RomM settings. No games will be fetched.", NotificationType.Error);
+ return games;
+ }
+
+ IList favoritCollections = _plugin.FetchFavorites();
+ var favorites = favoritCollections.FirstOrDefault(c => c.IsFavorite)?.RomIds ?? new List();
+
+ // Pull ROM data for each enabled mapping and add the games to playnite
+ foreach (var mapping in enabledMappings)
+ {
+ if (args.CancelToken.IsCancellationRequested)
+ break;
+
+ // Check mapping has an Emulator, Profile & Platform assigned to it
+ if (mapping.Emulator == null || mapping.EmulatorProfile == null || mapping.RomMPlatform == null || mapping.RomMPlatform.Id == -1)
+ {
+ Logger.Warn($"[Import Controller] Emulator {mapping.MappingId} is misconfigured, skipping.");
+ continue;
+ }
+
+ RomMPlatform apiPlatform = apiPlatforms.FirstOrDefault(p => p.Id == mapping.RomMPlatformId);
+ if (apiPlatform == null)
+ {
+ _plugin.Playnite.Notifications.Add(_plugin.Id.ToString(), $"Platform {mapping.RomMPlatform.Name} with ID {mapping.RomMPlatformId} not found in RomM, skipping.", NotificationType.Error);
+ continue;
+ }
+
+ // Pull data from server
+ // Could be made async, but when testing (4.7.0) found a performance degradation
+ var romMROMs = DownloadROMData(args, url, apiPlatform);
+ if(romMROMs == null)
+ {
+ Logger.Warn($"[Import Controller] Failed to get ROMs for {apiPlatform.Name}.");
+ continue;
+ }
+ Logger.Info($"[Import Controller] Finished parsing response for {apiPlatform.Name}.");
+
+ // Import games for current mapping
+ tasks.Add(Task>.Factory.StartNew(() =>
+ {
+ RomMImport newImport = new RomMImport(_plugin, args, mapping, romMROMs, favorites);
+ return newImport.ProcessData();
+ }));
+
+ }
+
+ Task.WhenAll(tasks).Wait();
+
+ foreach (var task in tasks)
+ {
+ games.AddRange(task.Result);
+ }
+
+ return games;
+ }
+
+ private static async Task GetAsyncWithParams(string baseUrl, NameValueCollection queryParams)
+ {
+ var uriBuilder = new UriBuilder(baseUrl);
+ var query = HttpUtility.ParseQueryString(uriBuilder.Query);
+
+ foreach (string key in queryParams)
+ {
+ query[key] = queryParams[key];
+ }
+
+ uriBuilder.Query = query.ToString();
+
+ return await HttpClientSingleton.Instance.GetAsync(uriBuilder.Uri);
+ }
+
+ private string BuildROMUrl()
+ {
+ string url = _plugin.CombineUrl(_plugin.Settings.RomMHost, "api/roms");
+
+ if (_plugin.Settings.SkipMissingFiles)
+ {
+ url += "?missing=false&";
+ }
+
+ // Exclude genres from import
+ string excludeGenresString = _plugin.Settings.ExcludeGenres.Trim(' ');
+ excludeGenresString = excludeGenresString.Trim(';');
+ List excludeGenres = excludeGenresString.Split(';').ToList();
+ if (!string.IsNullOrEmpty(excludeGenresString))
+ {
+ // Add ? if it hasn't been added already
+ if (!_plugin.Settings.SkipMissingFiles)
+ {
+ url += "?";
+ }
+
+ if (excludeGenres.Count > 1)
+ {
+ foreach (var genre in excludeGenres)
+ {
+ url += $"genres={HttpUtility.UrlEncode(genre)}&";
+ }
+ }
+ else
+ {
+ url += $"genres={HttpUtility.UrlEncode(excludeGenresString)}";
+ }
+ }
+ url.Trim('&');
+
+ return url;
+ }
+ private IList FetchPlatforms()
+ {
+ string apiPlatformsUrl = _plugin.CombineUrl(_plugin.Settings.RomMHost, "api/platforms");
+ try
+ {
+ HttpResponseMessage response = HttpClientSingleton.Instance.GetAsync(apiPlatformsUrl).GetAwaiter().GetResult();
+ response.EnsureSuccessStatusCode();
+
+ string body = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
+ return JsonConvert.DeserializeObject>(body);
+ }
+ catch (HttpRequestException e)
+ {
+ Logger.Error($"[Import Controller] Request exception: {e.Message}");
+ return new List();
+ }
+ }
+
+ private List DownloadROMData(LibraryImportGamesArgs args, string url, RomMPlatform platform)
+ {
+ Logger.Info($"[Import Controller] Starting to fetch games for {platform.Name}.");
+
+ const int pageSize = 50;
+ int offset = 0;
+ bool hasMoreData = true;
+ var romData = new List();
+
+ // Download data from RomM server
+ while (hasMoreData)
+ {
+ if (args.CancelToken.IsCancellationRequested)
+ break;
+
+ NameValueCollection queryParams = new NameValueCollection
+ {
+ { "platform_ids", platform.Id.ToString() },
+ { "genres_logic", "none" },
+ { "order_by", "name" },
+ { "order_dir", "asc" },
+ { "limit", pageSize.ToString() },
+ { "offset", offset.ToString() },
+ };
+
+ try
+ {
+ HttpResponseMessage response = GetAsyncWithParams(url, queryParams).GetAwaiter().GetResult();
+ response.EnsureSuccessStatusCode();
+
+ Logger.Info($"[Import Controller] Parsing response for {platform.Name} batch {offset / pageSize + 1}.");
+
+ Stream body = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult();
+ List roms;
+ using (StreamReader reader = new StreamReader(body))
+ {
+ var jsonResponse = JObject.Parse(reader.ReadToEnd());
+ roms = jsonResponse["items"].ToObject>();
+ }
+
+ Logger.Info($"[Import Controller] Parsed {roms.Count} roms for batch {offset / pageSize + 1}.");
+ romData.AddRange(roms);
+
+ if (roms.Count < pageSize)
+ {
+ Logger.Info($"[Import Controller] Received less than {pageSize} roms for {platform.Name}, assuming no more games.");
+ hasMoreData = false;
+ break;
+ }
+
+ offset += pageSize;
+ }
+ catch (HttpRequestException e)
+ {
+ Logger.Error($"[Import Controller] Request exception: {e.Message}");
+ hasMoreData = false;
+ }
+ }
+
+ return romData;
+ }
+ }
+
+}
diff --git a/Games/RomMInstallController.cs b/Games/RomMInstallController.cs
index 58c89eb..8cc37ba 100644
--- a/Games/RomMInstallController.cs
+++ b/Games/RomMInstallController.cs
@@ -12,107 +12,57 @@
namespace RomM.Games
{
+ enum InstallStatus
+ {
+ Cancelled = -1
+ }
+
internal class RomMInstallController : InstallController
{
protected readonly IRomM _romM;
public ILogger Logger => LogManager.GetLogger();
- public bool HasSiblings = false;
- public int SelectedSibling = -1;
+ public GameInstallInfo _gameData;
- internal RomMInstallController(Game game, IRomM romM, bool hasSiblings, int selectedSibling) : base(game)
+ internal RomMInstallController(Game game, IRomM romM, GameInstallInfo GameData) : base(game)
{
Name = "Download";
_romM = romM;
- HasSiblings = hasSiblings;
- SelectedSibling = selectedSibling;
+ _gameData = GameData;
}
public override void Install(InstallActionArgs args)
{
- var info = Game.GetRomMGameInfo();
-
- if (SelectedSibling == -2)
+ if (_gameData.Id == (int)InstallStatus.Cancelled)
{
CancelInstall();
return;
- }
-
- // Replace info if a different version of the game is selected
- if (HasSiblings && SelectedSibling != -1)
- {
- List siblingInfos = new List();
-
- var version = Game.Version;
- if (version == null || !version.StartsWith("RomM:"))
- {
- _romM.Playnite.Notifications.Add(
- Game.GameId,
- $"Failed to download {Game.Name}.{Environment.NewLine}{Environment.NewLine}{$"Couldn't find RomMId for {Game.Name}."}",
- NotificationType.Error);
-
- CancelInstall();
- return;
- }
-
- int romMId;
- if (!int.TryParse(version.Split(':')[1], out romMId))
- {
- _romM.Playnite.Notifications.Add(
- Game.GameId,
- $"Failed to download {Game.Name}.{Environment.NewLine}{Environment.NewLine}{$"Malformed version string? {version} > {romMId}"}",
- NotificationType.Error);
-
- CancelInstall();
- return;
- }
-
- siblingInfos = JsonConvert.DeserializeObject>(File.ReadAllText($"{_romM.ROMsWithSiblingsPath}{romMId}.json"));
-
- var selectedSibling = siblingInfos.Find(x => x.Id == SelectedSibling);
- if (selectedSibling != null)
- {
- info.FileName = selectedSibling.FileName;
- info.DownloadUrl = selectedSibling.DownloadURL;
- // This has to be changed as systems can have single ROM and Multi ROM files. E.g. .chd vs .bin/.cue
- info.HasMultipleFiles = selectedSibling.HasMultipleFiles;
- }
- else
- {
- _romM.Playnite.Notifications.Add(
- Game.GameId,
- $"Failed to find selected version of {Game.Name}.{Environment.NewLine}Selected sibling ID {SelectedSibling} was not found. Reimport libary!",
- NotificationType.Error);
- CancelInstall();
- return;
- }
-
- }
+ }
- var dstPath = info.Mapping?.DestinationPathResolved
+ var dstPath = _gameData.Mapping?.DestinationPathResolved
?? throw new Exception("Mapped emulator data cannot be found, try removing and re-adding.");
// Paths (same as before)
- var installDir = Path.Combine(dstPath, Path.GetFileNameWithoutExtension(info.FileName));
+ var installDir = Path.Combine(dstPath, Path.GetFileNameWithoutExtension(_gameData.FileName));
// If RomM indicates multiple files, we download as an archive name (zip) into the install folder.
// Otherwise we download the single ROM file.
- var downloadFilePath = info.HasMultipleFiles
- ? Path.Combine(installDir, info.FileName + ".zip")
- : Path.Combine(installDir, info.FileName);
+ var downloadFilePath = _gameData.HasMultipleFiles
+ ? Path.Combine(installDir, _gameData.FileName + ".zip")
+ : Path.Combine(installDir, _gameData.FileName);
var req = new DownloadRequest
{
GameId = Game.Id,
GameName = Game.Name,
- DownloadUrl = info.DownloadUrl,
+ DownloadUrl = _gameData.DownloadURL,
InstallDir = installDir,
GamePath = downloadFilePath,
Use7z = _romM.Settings.Use7z,
PathTo7Z = _romM.Settings.PathTo7z,
- HasMultipleFiles = info.HasMultipleFiles,
- AutoExtract = info.Mapping != null && info.Mapping.AutoExtract,
+ HasMultipleFiles = _gameData.HasMultipleFiles,
+ AutoExtract = _gameData.Mapping != null && _gameData.Mapping.AutoExtract,
// Called by queue AFTER download/extract is done
BuildRoms = () =>
@@ -127,11 +77,11 @@ public override void Install(InstallActionArgs args)
}
// Otherwise, we assume extracted files are in installDir
- var supported = GetEmulatorSupportedFileTypes(info);
+ var supported = GetEmulatorSupportedFileTypes(_gameData);
var actualRomFiles = GetRomFiles(installDir, supported);
// Prefer .m3u if requested
- var useM3u = info.Mapping != null && info.Mapping.UseM3u;
+ var useM3u = _gameData.Mapping != null && _gameData.Mapping.UseM3U && supported.Any(x => x.ToLower() == "m3u");
if (useM3u)
{
var m3uFile = actualRomFiles.FirstOrDefault(m =>
@@ -177,7 +127,7 @@ public override void Install(InstallActionArgs args)
{
_romM.Playnite.Notifications.Add(
Game.GameId,
- $"Failed to download {Game.Name}.{Environment.NewLine}{Environment.NewLine}{ex}",
+ $"Failed to download {Game.Name}.{Environment.NewLine}{Environment.NewLine}{ex.Message}",
NotificationType.Error);
Game.IsInstalling = false;
@@ -223,7 +173,7 @@ private static string[] GetRomFiles(string installDir, List supportedFil
}).ToArray();
}
- private static List GetEmulatorSupportedFileTypes(RomMGameInfo info)
+ private static List GetEmulatorSupportedFileTypes(GameInstallInfo info)
{
if (info.Mapping.EmulatorProfile is CustomEmulatorProfile)
{
diff --git a/Games/RomMMetadataProvider.cs b/Games/RomMMetadataProvider.cs
new file mode 100644
index 0000000..8c608f5
--- /dev/null
+++ b/Games/RomMMetadataProvider.cs
@@ -0,0 +1,73 @@
+using Playnite.SDK;
+using Playnite.SDK.Models;
+using RomM.Models.RomM.Rom;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace RomM.Games
+{
+ public class RomMMetadataProvider : LibraryMetadataProvider
+ {
+ private readonly IRomM _romM;
+ public RomMMetadataProvider(RomM romM)
+ {
+ _romM = romM;
+ }
+
+ public override GameMetadata GetMetadata(Game game)
+ {
+ int romMId;
+ if (!int.TryParse(game.GameId.Split(':')[0], out romMId))
+ {
+ _romM.Logger.Error($"[Metadata] {game.Name} GameID is malformed!");
+ return null;
+ }
+
+ RomMRom romMGame = _romM.FetchRom(romMId.ToString());
+ if(romMGame == null)
+ {
+ _romM.Logger.Error($"[Metadata] {game.Name} failed to get game!");
+ return null;
+ }
+
+ var preferedRatingsBoard = _romM.Playnite.ApplicationSettings.AgeRatingOrgPriority;
+ var agerating = romMGame.Metadatum.Age_Ratings.Count > 0 ? new HashSet(romMGame.Metadatum.Age_Ratings.Where(r => r.Split(':')[0] == preferedRatingsBoard.ToString()).Select(r => new MetadataNameProperty(r.ToString()))) : null;
+
+ List gameLinks = new List();
+ if (romMGame.SSId != null)
+ gameLinks.Add(new Link("Screenscraper", $"https://www.screenscraper.fr/gameinfos.php?gameid={romMGame.SSId}"));
+ if (romMGame.HasheousId != null)
+ gameLinks.Add(new Link("Hasheous", $"https://hasheous.org/index.html?page=dataobjectdetail&type=game&id={romMGame.HasheousId}"));
+ if (romMGame.RAId != null)
+ gameLinks.Add(new Link("RetroAchievements", $"https://retroachievements.org/game/{romMGame.RAId}"));
+ if (romMGame.HLTBId != null)
+ gameLinks.Add(new Link("HowLongToBeat", $"https://howlongtobeat.com/game/{romMGame.HLTBId}"));
+
+ var metadata = new GameMetadata
+ {
+ Name = romMGame.Name,
+ Description = romMGame.Summary,
+
+ Regions = new HashSet(romMGame.Regions.Where(r => !string.IsNullOrEmpty(r)).Select(r => new MetadataNameProperty(r.ToString()))),
+ Genres = new HashSet(romMGame.Metadatum.Genres.Where(r => !string.IsNullOrEmpty(r)).Select(r => new MetadataNameProperty(r.ToString()))),
+ AgeRatings = agerating,
+ Series = new HashSet(romMGame.Metadatum.Franchises.Where(r => !string.IsNullOrEmpty(r)).Select(r => new MetadataNameProperty(r.ToString()))),
+ Features = new HashSet(romMGame.Metadatum.Gamemodes.Where(r => !string.IsNullOrEmpty(r)).Select(r => new MetadataNameProperty(r.ToString()))),
+ Categories = new HashSet(romMGame.Metadatum.Collections.Where(r => !string.IsNullOrEmpty(r)).Select(r => new MetadataNameProperty(r.ToString()))),
+
+ ReleaseDate = romMGame.Metadatum.Release_Date.HasValue ? new ReleaseDate(new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(romMGame.Metadatum.Release_Date.Value).ToLocalTime()) : new ReleaseDate(),
+ CommunityScore = (int?)romMGame.Metadatum.Average_Rating,
+
+ CoverImage = !string.IsNullOrEmpty(romMGame.PathCoverL) ? new MetadataFile($"{_romM.Settings.RomMHost}{romMGame.PathCoverL}") : null,
+
+ LastActivity = romMGame.RomUser.LastPlayed,
+ UserScore = romMGame.RomUser.Rating * 10, //RomM-Rating is 1-10, Playnite 1-100, so it can unfortunately only by synced one direction without loosing decimals
+ Links = gameLinks,
+
+ };
+
+ return metadata;
+ }
+ }
+}
diff --git a/IRomm.cs b/IRomm.cs
index 2017abb..4b4f4af 100644
--- a/IRomm.cs
+++ b/IRomm.cs
@@ -1,14 +1,21 @@
using Playnite.SDK;
+using Playnite.SDK.Models;
+using RomM.Models.RomM.Rom;
+using System;
namespace RomM
{
internal interface IRomM
{
- ILogger Logger { get; }
+ ILogger Logger { get; }
IPlayniteAPI Playnite { get; }
- Settings.SettingsViewModel Settings { get; }
- string ROMsWithSiblingsPath { get; }
+ Guid Id { get; }
+
+ Settings.SettingsViewModel Settings { get; }
+ MetadataProperty Source { get; }
Downloads.DownloadQueueController DownloadQueueController { get; }
string GetPluginUserDataPath();
- }
+ RomMRom FetchRom(string romId);
+
+ }
}
\ No newline at end of file
diff --git a/Models/RomM/Platform/RomMPlatform.cs b/Models/RomM/Platform/RomMPlatform.cs
index e340380..3e2adcd 100644
--- a/Models/RomM/Platform/RomMPlatform.cs
+++ b/Models/RomM/Platform/RomMPlatform.cs
@@ -4,8 +4,36 @@
namespace RomM.Models.RomM.Platform
{
-public class RomMPlatform
+public class RomMPlatform : IEquatable
{
+ public bool Equals(RomMPlatform other)
+ {
+ if (Object.ReferenceEquals(other, null)) return false;
+ if (Object.ReferenceEquals(other, this)) return true;
+ return this.Name == other.Name;
+ }
+
+ public sealed override bool Equals(object obj)
+ {
+ var otherMyItem = obj as RomMPlatform;
+ if (Object.ReferenceEquals(otherMyItem, null)) return false;
+ return otherMyItem.Equals(this);
+ }
+
+ public static bool operator ==(RomMPlatform myItem1, RomMPlatform myItem2)
+ {
+ return Object.Equals(myItem1, myItem2);
+ }
+
+ public static bool operator !=(RomMPlatform myItem1, RomMPlatform myItem2)
+ {
+ return !(myItem1 == myItem2);
+ }
+ public override int GetHashCode()
+ {
+ return this.Id.GetHashCode();
+ }
+
[JsonProperty("id")]
public int Id { get; set; }
diff --git a/Models/RomM/Rom/RomMRom.cs b/Models/RomM/Rom/RomMRom.cs
index 1800401..eb310f2 100644
--- a/Models/RomM/Rom/RomMRom.cs
+++ b/Models/RomM/Rom/RomMRom.cs
@@ -37,6 +37,9 @@ public class metadatum
public class RomMFile
{
+ [JsonProperty("id")]
+ public int? Id { get; set; }
+
[JsonProperty("file_name")]
public string FileName { get; set; }
@@ -47,7 +50,7 @@ public class RomMFile
public string FullPath { get; set; }
}
- public class RomMSibling : ObservableObject
+ public class RomMSibling
{
[JsonProperty("id")]
public int Id { get; set; }
@@ -60,12 +63,6 @@ public class RomMSibling : ObservableObject
[JsonProperty("fs_name_no_ext")]
public string FileNameNoExt { get; set; }
-
- // Don't add JsonProperty data not from server
- public string FileName { get; set; }
- public string DownloadURL { get; set; }
- public bool HasMultipleFiles { get; set; }
- public bool isSelected { get; set; } = false;
}
public class RomMRom
@@ -82,6 +79,18 @@ public class RomMRom
[JsonProperty("moby_id")]
public object MobyId { get; set; }
+ [JsonProperty("ss_id")]
+ public int? SSId { get; set; }
+
+ [JsonProperty("ra_id")]
+ public int? RAId { get; set; }
+
+ [JsonProperty("hasheous_id")]
+ public int? HasheousId { get; set; }
+
+ [JsonProperty("hltb_id")]
+ public int? HLTBId { get; set; }
+
[JsonProperty("platform_id")]
public int PlatformId { get; set; }
@@ -184,6 +193,15 @@ public class RomMRom
[JsonProperty("siblings")]
public List Siblings { get; set; }
+ [JsonProperty("sha1_hash")]
+ public string SHA1 { get; set; }
+
+ [JsonProperty("has_manual")]
+ public bool HasManual { get; set; }
+
+ [JsonProperty("path_manual")]
+ public string ManualPath { get; set; }
+
[JsonProperty("full_path")]
public string FullPath { get; set; }
@@ -198,5 +216,7 @@ public class RomMRom
[JsonProperty("sort_comparator")]
public string SortComparator { get; set; }
- }
+
+ public bool Processed { get; set; } = false;
+}
}
diff --git a/Models/RomM/Rom/RomMRomLocal.cs b/Models/RomM/Rom/RomMRomLocal.cs
new file mode 100644
index 0000000..12ceb07
--- /dev/null
+++ b/Models/RomM/Rom/RomMRomLocal.cs
@@ -0,0 +1,41 @@
+using RomM.Settings;
+using System;
+using System.Collections.Generic;
+
+namespace RomM.Models.RomM.Rom
+{
+ enum MainSibling
+ {
+ None = -1,
+ Current = 0,
+ Other = 1
+ }
+
+ public struct GameInstallInfo
+ {
+ public int Id { get; set; }
+ public string FileName { get; set; }
+ public bool HasMultipleFiles { get; set; }
+ public string DownloadURL { get; set; }
+ public EmulatorMapping Mapping { get; set; }
+ }
+
+ public struct RomMRevision
+ {
+ public int Id { get; set; }
+ public string FileName { get; set; }
+ public bool HasMultipleFiles { get; set; }
+ public string DownloadURL { get; set; }
+ public bool IsSelected { get; set; }
+ }
+
+ public class RomMRomLocal
+ {
+ public string Name { get; set; }
+ public string SHA1 { get; set; }
+ public Guid MappingID { get; set; }
+
+ public List ROMVersions { get; set; }
+
+ }
+}
diff --git a/Models/RomM/Rom/RomMRomUser.cs b/Models/RomM/Rom/RomMRomUser.cs
index d7786cf..19254a6 100644
--- a/Models/RomM/Rom/RomMRomUser.cs
+++ b/Models/RomM/Rom/RomMRomUser.cs
@@ -12,6 +12,9 @@ public class RomMRomUser
[JsonProperty("user_id")]
public int UserId { get; set; }
+ [JsonProperty("is_main_sibling")]
+ public bool IsMainSibling { get; set; }
+
[JsonProperty("last_played")]
public DateTime? LastPlayed { get; set; }
diff --git a/Models/RomM/RomMHeartbeat.cs b/Models/RomM/RomMHeartbeat.cs
new file mode 100644
index 0000000..ba6b2bd
--- /dev/null
+++ b/Models/RomM/RomMHeartbeat.cs
@@ -0,0 +1,23 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace RomM.Models.RomM
+{
+ struct ServerInfo
+ {
+ [JsonProperty("VERSION")]
+ public string Version { get; set; }
+ [JsonProperty("SHOW_SETUP_WIZARD")]
+ public bool ShowSetupWizard { get; set; }
+ }
+
+ class RomMHeartbeat
+ {
+ [JsonProperty("SYSTEM")]
+ public ServerInfo SystemInfo { get; set; }
+ }
+}
diff --git a/Models/RomM/RomMUser.cs b/Models/RomM/RomMUser.cs
new file mode 100644
index 0000000..57dd55d
--- /dev/null
+++ b/Models/RomM/RomMUser.cs
@@ -0,0 +1,26 @@
+using Newtonsoft.Json;
+
+namespace RomM.Models.RomM
+{
+ public class RomMUser
+ {
+ [JsonProperty("id")]
+ public int Id { get; set; }
+ [JsonProperty("username")]
+ public string Username { get; set; }
+ [JsonProperty("email")]
+ public string Email { get; set; }
+ [JsonProperty("enabled")]
+ public bool Enabled { get; set; }
+ [JsonProperty("role")]
+ public string Role { get; set; }
+ [JsonProperty("avatar_path")]
+ public string IconPath { get; set; }
+ [JsonProperty("last_login")]
+ public string LastLogin { get; set; }
+ [JsonProperty("last_active")]
+ public string LastActive { get; set; }
+ [JsonProperty("ra_username")]
+ public string RAUsername { get; set; }
+ }
+}
diff --git a/RomM.cs b/RomM.cs
index 17d5d93..aa6e3f2 100644
--- a/RomM.cs
+++ b/RomM.cs
@@ -1,5 +1,4 @@
using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
using Playnite.SDK;
using Playnite.SDK.Events;
using Playnite.SDK.Models;
@@ -8,13 +7,10 @@
using RomM.Downloads;
using RomM.VersionSelector;
using RomM.Models.RomM.Collection;
-using RomM.Models.RomM.Platform;
using RomM.Models.RomM.Rom;
using RomM.Settings;
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
-using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net.Http;
@@ -23,7 +19,6 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using System.Web;
using System.Windows;
using System.Windows.Controls;
@@ -39,27 +34,14 @@ static HttpClientSingleton()
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
- public static void ConfigureAuth(SettingsViewModel settings)
+ public static void ConfigureBasicAuth(string username, string password)
{
- httpClient.DefaultRequestHeaders.Authorization = BuildAuthHeader(settings);
+ var base64Credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{username}:{password}"));
+ httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64Credentials);
}
-
- public static AuthenticationHeaderValue BuildAuthHeader(SettingsViewModel settings)
+ public static void ConfigureAPIAuth(string apiToken)
{
- var token = settings.RomMApiToken?.Trim();
- if (SettingsViewModel.IsValidApiToken(token))
- {
- return new AuthenticationHeaderValue("Bearer", token);
- }
-
- if (!string.IsNullOrEmpty(settings.RomMUsername) && !string.IsNullOrEmpty(settings.RomMPassword))
- {
- var creds = Convert.ToBase64String(
- Encoding.ASCII.GetBytes($"{settings.RomMUsername}:{settings.RomMPassword}"));
- return new AuthenticationHeaderValue("Basic", creds);
- }
-
- return null;
+ httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiToken);
}
public static HttpClient Instance => httpClient;
@@ -84,15 +66,14 @@ public class RomM : LibraryPlugin, IRomM
public ILogger Logger => LogManager.GetLogger();
public IPlayniteAPI Playnite { get; private set; }
- public SettingsViewModel Settings { get; private set; }
-
- public string ROMsWithSiblingsPath { get; private set; }
+ public SettingsViewModel Settings { get; private set; }
+ public string ROMDataPath { get; private set; }
+ public MetadataProperty Source { get; private set; }
public DownloadQueueController DownloadQueueController { get; private set; }
-
internal RomMDownloadsSidebarItem DownloadsSidebar { get; private set; }
private readonly DownloadQueueViewModel downloadsVm;
-
+
// Implementing Client adds ability to open it via special menu in playnite
public override LibraryClient Client { get; } = new RomMClient();
@@ -101,9 +82,10 @@ public RomM(IPlayniteAPI api) : base(api)
Playnite = api;
Properties = new LibraryPluginProperties
{
- HasSettings = true
+ HasSettings = true,
+ HasCustomizedGameImport = true,
};
- ROMsWithSiblingsPath = $"{Playnite.Paths.ExtensionsDataPath}\\{Id}\\ROMsWithSiblings\\";
+ ROMDataPath = $"{Playnite.Paths.ExtensionsDataPath}\\{Id}\\Games\\";
// Initialise the download queue
downloadsVm = new DownloadQueueViewModel();
@@ -118,93 +100,14 @@ public RomM(IPlayniteAPI api) : base(api)
}
}
- private string CombineUrl(string baseUrl, string relativePath)
- {
- return $"{baseUrl?.TrimEnd('/')}/{relativePath?.TrimStart('/') ?? ""}";
- }
-
- internal IList FetchPlatforms()
- {
- string apiPlatformsUrl = CombineUrl(Settings.RomMHost, "api/platforms");
- try
- {
- HttpResponseMessage response = HttpClientSingleton.Instance.GetAsync(apiPlatformsUrl).GetAwaiter().GetResult();
- response.EnsureSuccessStatusCode();
-
- string body = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
- return JsonConvert.DeserializeObject>(body);
- }
- catch (HttpRequestException e)
- {
- Logger.Error($"Request exception: {e.Message}");
- return new List();
- }
- }
-
- internal IList FetchFavorites()
- {
- string apiFavoriteUrl = CombineUrl(Settings.RomMHost, "api/collections");
- try
- {
- // Make the request and get the response
- HttpResponseMessage response = HttpClientSingleton.Instance.GetAsync(apiFavoriteUrl).GetAwaiter().GetResult();
- response.EnsureSuccessStatusCode();
-
- // Assuming the response is in JSON format
- string body = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
- return JsonConvert.DeserializeObject>(body);
- }
- catch (HttpRequestException e)
- {
- Logger.Error($"Request exception: {e.Message}");
- return new List();
- }
- }
- internal RomMCollection CreateFavorites()
+ #region Helper functions
+ public string CombineUrl(string baseUrl, string relativePath)
{
- string apiCollectionUrl = CombineUrl(Settings.RomMHost, "api/collections?is_favorite=true&is_public=false");
- try
- {
- var formData = new MultipartFormDataContent();
- formData.Add(new StringContent("Favorites"), "name");
-
- HttpResponseMessage postResponse = HttpClientSingleton.Instance.PostAsync(apiCollectionUrl, formData).GetAwaiter().GetResult();
- postResponse.EnsureSuccessStatusCode();
-
- string body = postResponse.Content.ReadAsStringAsync().GetAwaiter().GetResult();
- return JsonConvert.DeserializeObject(body);
- }
- catch (HttpRequestException e)
- {
- Logger.Error($"Request exception: {e.Message}");
- return null;
- }
- }
-
- internal void UpdateFavorites(RomMCollection favoriteCollection, List romIds)
- {
- if (favoriteCollection == null)
- {
- Logger.Error($"Can't update favorites, collection is null");
- return;
- }
-
- string apiCollectionUrl = CombineUrl(Settings.RomMHost, "api/collections");
- try
- {
- var formData = new MultipartFormDataContent();
- formData.Add(new StringContent(JsonConvert.SerializeObject(romIds)), "rom_ids");
- HttpResponseMessage putResponse = HttpClientSingleton.Instance.PutAsync($"{apiCollectionUrl}/{favoriteCollection.Id}", formData).GetAwaiter().GetResult();
- putResponse.EnsureSuccessStatusCode();
- }
- catch (HttpRequestException e)
- {
- Logger.Error($"Request exception: {e.Message}");
- }
+ return $"{baseUrl?.TrimEnd('/')}/{relativePath?.TrimStart('/') ?? ""}";
}
- internal RomMRom FetchRom(string romId)
+ public RomMRom FetchRom(string romId)
{
string romUrl = CombineUrl(Settings.RomMHost, $"api/roms/{romId}");
try
@@ -241,12 +144,12 @@ internal void HandleRommUri(PlayniteUriEventArgs args)
foreach (var mapping in SettingsViewModel.Instance.Mappings?.Where(m => m.Enabled))
{
- if (mapping.Platform.IgdbId.ToString() == platformIgdbId)
+ if (mapping.RomMPlatform.IgdbId.ToString() == platformIgdbId)
{
var gameName = rom.Name;
var game = Playnite.Database.Games.FirstOrDefault(g => g.Source.Name == SourceName.ToString() &&
- g.Platforms.Any(p => p.Name == mapping.Platform.Name) &&
+ g.Platforms.Any(p => p.Name == mapping.RomMPlatform.Name) &&
g.Name == gameName);
if (game == null)
@@ -271,13 +174,34 @@ internal void HandleRommUri(PlayniteUriEventArgs args)
}
}
+ // New-style overload (used by DownloadQueueController)
+ public static Task GetAsync(string url, HttpCompletionOption completionOption, CancellationToken ct)
+ {
+ return HttpClientSingleton.Instance.GetAsync(url, completionOption, ct);
+ }
+ #endregion
+
+ #region Playnite functions
public override void OnApplicationStarted(OnApplicationStartedEventArgs args)
{
base.OnApplicationStarted(args);
+ if (!Directory.Exists($"{ROMDataPath}"))
+ Directory.CreateDirectory($"{ROMDataPath}");
+
Settings = new SettingsViewModel(this, this);
- HttpClientSingleton.ConfigureAuth(Settings);
+
+ if (Settings.UseBasicAuth && !string.IsNullOrEmpty(Settings.RomMUsername) && !string.IsNullOrEmpty(Settings.RomMPassword))
+ {
+ HttpClientSingleton.ConfigureBasicAuth(Settings.RomMUsername, Settings.RomMPassword);
+ }
+ else if(SettingsViewModel.ApiTokenPattern.IsMatch(Settings.RomMClientToken))
+ {
+ HttpClientSingleton.ConfigureAPIAuth(Settings.RomMClientToken);
+ }
+
Playnite.UriHandler.RegisterSource("romm", HandleRommUri);
+ Source = SourceName;
// Portable path fix: expand "{PlayniteDir}" to absolute paths in DB on startup
if (Playnite.Paths.IsPortable)
@@ -318,36 +242,27 @@ public override void OnApplicationStarted(OnApplicationStartedEventArgs args)
{
if (item.PluginId == PluginId)
{
- var version = item.Version;
- if (version == null || !version.StartsWith("RomM:"))
- {
- Logger.Warn($"Couldn't find RomMId for {item.Name}.");
- continue;
- }
-
- int romMId;
- if (!int.TryParse(version.Split(':')[1], out romMId))
+ if (item.GameId.Contains(':'))
{
- Logger.Error($"Malformed version string? {version} > {romMId}");
- continue;
+ if (File.Exists($"{ROMDataPath}{item.GameId.Split(':')[1]}.json"))
+ {
+ File.Delete($"{ROMDataPath}{item.GameId.Split(':')[1]}.json");
+ }
}
-
- if (File.Exists($"{ROMsWithSiblingsPath}{romMId}.json"))
+ else
{
- File.Delete($"{ROMsWithSiblingsPath}{romMId}.json");
+ Logger.Error($"Game {item.Name} id is malformed!");
}
-
}
}
}
};
}
-
public override void OnApplicationStopped(OnApplicationStoppedEventArgs args)
{
base.OnApplicationStopped(args);
-
+
Playnite.Database.Games.ItemUpdated -= OnItemUpdated;
// Portable path fix: restore "{PlayniteDir}" tokens before exiting
@@ -382,573 +297,262 @@ public override void OnApplicationStopped(OnApplicationStoppedEventArgs args)
}
}
- // Old-style overload (keeps older call sites working)
- public static Task GetAsync(
- string url,
- HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead)
- {
- return HttpClientSingleton.Instance.GetAsync(url, completionOption);
- }
-
- // New-style overload (used by DownloadQueueController)
- public static Task GetAsync(
- string url,
- HttpCompletionOption completionOption,
- CancellationToken ct)
- {
- return HttpClientSingleton.Instance.GetAsync(url, completionOption, ct);
- }
-
- public static async Task GetAsyncWithParams(string baseUrl, NameValueCollection queryParams)
- {
- var uriBuilder = new UriBuilder(baseUrl);
- var query = HttpUtility.ParseQueryString(uriBuilder.Query);
-
- foreach (string key in queryParams)
- {
- query[key] = queryParams[key];
- }
-
- uriBuilder.Query = query.ToString();
-
- return await HttpClientSingleton.Instance.GetAsync(uriBuilder.Uri);
- }
-
- public override IEnumerable GetGames(LibraryGetGamesArgs args)
+ public override IEnumerable ImportGames(LibraryImportGamesArgs args)
{
if (Playnite.ApplicationInfo.Mode == ApplicationMode.Fullscreen && !Settings.ScanGamesInFullScreen)
{
- return new List();
+ return new List();
}
- if (string.IsNullOrEmpty(Settings.RomMHost))
+ if(!Settings.TestConnection())
{
- Logger.Warn("RomM host is not set.");
- return new List();
+ return new List();
}
- if (!Settings.HasAnyAuth)
- {
- Logger.Warn("RomM API token (rmm_ + 64 hex chars) or username/password must be set.");
- return new List();
- }
+ return new RomMImportController(this).Import(args);
+ }
- IList apiPlatforms = FetchPlatforms();
- List games = new List();
- IEnumerable enabledMappings = SettingsViewModel.Instance.Mappings?.Where(m => m.Enabled);
+ public override ISettings GetSettings(bool firstRunSettings)
+ {
+ return Settings;
+ }
+ public override UserControl GetSettingsView(bool firstRunSettings)
+ {
+ return new SettingsView();
+ }
- if (enabledMappings == null || !enabledMappings.Any())
+ public override IEnumerable GetSidebarItems()
+ {
+ if (DownloadsSidebar != null)
{
- Logger.Warn("No emulators are configured or enabled in RomM settings. No games will be fetched.");
- return games;
+ yield return DownloadsSidebar;
}
+ }
+ public override IEnumerable GetGameMenuItems(GetGameMenuItemsArgs args)
+ {
+ List gameMenuItems = new List();
- IList favoritCollections = FetchFavorites();
- var favorites = favoritCollections.FirstOrDefault(c => c.IsFavorite)?.RomIds ?? new List();
-
- foreach (var mapping in enabledMappings)
+ if (args.Games.First().PluginId == PluginId)
{
- if (args.CancelToken.IsCancellationRequested)
- break;
-
- if (mapping.Emulator == null)
- {
- Logger.Warn($"Emulator {mapping.EmulatorId} not found, skipping.");
- continue;
- }
-
- if (mapping.EmulatorProfile == null)
- {
- Logger.Warn($"Emulator profile {mapping.EmulatorProfileId} for emulator {mapping.EmulatorId} not found, skipping.");
- continue;
- }
- if (mapping.Platform == null)
+ if (Settings.MergeRevisions && File.Exists($"{ROMDataPath}{args.Games.First().GameId.Split(':')[1]}.json") && args.Games.First().IsInstalled)
{
- Logger.Warn($"Platform {mapping.PlatformId} not found, skipping.");
- continue;
+ try
+ {
+ string json = File.ReadAllText($"{ROMDataPath}{args.Games.First().GameId.Split(':')[1]}.json");
+ var gameData = JsonConvert.DeserializeObject(json);
+ if(gameData.ROMVersions.Count > 1)
+ {
+ gameMenuItems.Add(new GameMenuItem
+ {
+ //MenuSection = "@",
+ Description = "Switch ROM Version!",
+ Action = (gameMenuItem) =>
+ {
+ Playnite.InstallGame(args.Games.First().Id);
+ }
+ });
+ }
+ }
+ catch (Exception)
+ {
+ Logger.Error($"{args.Games.First().Name} GameID is malformed or json file is corrupted!");
+ }
}
+ }
+ return gameMenuItems;
+ }
- string url = CombineUrl(Settings.RomMHost, "api/roms");
- RomMPlatform apiPlatform = apiPlatforms.FirstOrDefault(p => p.IgdbId == mapping.Platform.IgdbId);
+ public override IEnumerable GetInstallActions(GetInstallActionsArgs args)
+ {
+ if (args.Game.PluginId == Id)
+ {
+ string gameID = args.Game.GameId;
+ GameInstallInfo romData = new GameInstallInfo();
+ RomMRomLocal gameData = new RomMRomLocal();
- if (apiPlatform == null)
+ if (args.Game.GameId.StartsWith("!0"))
{
- Logger.Warn($"Platform {mapping.Platform.Name} with IGDB ID {mapping.Platform.IgdbId} not found in RomM, skipping.");
- continue;
+ PlayniteApi.Notifications.Add(new NotificationMessage(PluginId.ToString(), "Old ID detected run update game library before installing!", NotificationType.Error));
+ romData.Id = (int)InstallStatus.Cancelled;
}
-
- Logger.Debug($"Starting to fetch games for {apiPlatform.Name}.");
-
- const int pageSize = 72;
- int offset = 0;
- bool hasMoreData = true;
- var allRoms = new List();
- var responseGameIDs = new HashSet();
-
- while (hasMoreData)
+ else
{
- if (args.CancelToken.IsCancellationRequested)
- break;
-
- NameValueCollection queryParams = new NameValueCollection
+ // Pull game file from RomM data directory
+ int romMId;
+ string romMSHA1 = gameID.Split(':')[1];
+ if (!int.TryParse(args.Game.GameId.Split(':')[0], out romMId) || !File.Exists($"{ROMDataPath}{romMSHA1}.json"))
{
- { "limit", pageSize.ToString() },
- { "offset", offset.ToString() },
- { "platform_ids", apiPlatform.Id.ToString() },
- { "order_by", "name" },
- { "order_dir", "asc" },
- };
+ Logger.Error($"{args.Game.Name} GameID is malformed!");
+ romData.Id = (int)InstallStatus.Cancelled;
+ yield return new RomMInstallController(args.Game, this, romData);
+ }
try
{
- HttpResponseMessage response = GetAsyncWithParams(url, queryParams).GetAwaiter().GetResult();
- response.EnsureSuccessStatusCode();
-
- Logger.Debug($"Parsing response for {apiPlatform.Name} batch {offset / pageSize + 1}.");
-
- Stream body = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult();
- List roms;
- using (StreamReader reader = new StreamReader(body))
- {
- var jsonResponse = JObject.Parse(reader.ReadToEnd());
- roms = jsonResponse["items"].ToObject>();
- }
-
- Logger.Debug($"Parsed {roms.Count} roms for batch {offset / pageSize + 1}.");
- allRoms.AddRange(roms);
-
- if (roms.Count < pageSize)
- {
- Logger.Debug($"Received less than {pageSize} roms for {apiPlatform.Name}, assuming no more games.");
- hasMoreData = false;
- break;
- }
-
- offset += pageSize;
+ string json = File.ReadAllText($"{ROMDataPath}{romMSHA1}.json");
+ gameData = JsonConvert.DeserializeObject(json);
}
- catch (HttpRequestException e)
+ catch (Exception)
{
- Logger.Error($"Request exception: {e.Message}");
- hasMoreData = false;
+ Logger.Error($"{args.Game.Name} GameID is malformed or {romMSHA1} json file is corrupted!");
+ romData.Id = (int)InstallStatus.Cancelled;
}
- }
- try
- {
- Logger.Debug($"Finished parsing response for {apiPlatform.Name}.");
-
- var rootInstallDir = PlayniteApi.Paths.IsPortable
- ? mapping.DestinationPathResolved.Replace(PlayniteApi.Paths.ApplicationPath, ExpandableVariables.PlayniteDirectory)
- : mapping.DestinationPathResolved;
-
- var completionStatusMap = PlayniteApi.Database.CompletionStatuses.ToDictionary(cs => cs.Name, cs => cs.Id);
+ if (romData.Id == (int)InstallStatus.Cancelled)
+ yield return new RomMInstallController(args.Game, this, romData);
- if (!Directory.Exists(ROMsWithSiblingsPath))
- Directory.CreateDirectory(ROMsWithSiblingsPath);
-
- List ImportedROMsWithSiblings = new List();
- if (Settings.MergeRevisions)
+ // Set ROM data to base ROM
+ romData = new GameInstallInfo
{
- foreach (string filename in Directory.EnumerateFiles(ROMsWithSiblingsPath, "*.json", SearchOption.TopDirectoryOnly))
- {
- int FileId;
- if (!int.TryParse(Path.GetFileNameWithoutExtension(filename), out FileId))
- {
- continue;
- }
-
- ImportedROMsWithSiblings.Add(FileId);
- }
- }
+ Id = gameData.ROMVersions[0].Id,
+ FileName = gameData.ROMVersions[0].FileName,
+ HasMultipleFiles = gameData.ROMVersions[0].HasMultipleFiles,
+ DownloadURL = gameData.ROMVersions[0].DownloadURL,
+ Mapping = Settings.Mappings.FirstOrDefault(x => x.MappingId == gameData.MappingID)
+ };
- foreach (var item in allRoms)
+ // If Siblings are avaiable prompt user with version selection
+ if (Settings.MergeRevisions && gameData.ROMVersions?.Count > 1)
{
- if (args.CancelToken.IsCancellationRequested)
- break;
-
- // Check for siblings and if one has already been imported skip
- if (Settings.MergeRevisions && item.Siblings.Count > 0)
- {
- bool foundSibling = false;
-
- foreach (var sibling in item.Siblings)
- {
- if (ImportedROMsWithSiblings.Contains(sibling.Id))
- {
- foundSibling = true;
- break;
- }
- }
-
- if (foundSibling)
- continue;
- }
-
- var gameName = item.Name;
- // Not sure if this a server bug or if my RomM server is borked but some games like Wii U dont have any of these enabled
- if (!item.HasSimpleSingleFile & !item.HasNestedSingleFile & !item.HasMultipleFiles)
- item.HasMultipleFiles = true;
-
- // Defensive: never allow path segments from server-provided filename & make sure single ROM files have an extention
- var fileName = item.HasMultipleFiles ? Path.GetFileName(item.FileName) : Path.GetFileName(item.Files.Where(f => f.FullPath.Count(c => c == '/') <= 3).FirstOrDefault().FileName);
- if (string.IsNullOrWhiteSpace(fileName))
- {
- Logger.Warn($"Rom {item.Id} returned empty/invalid filename, skipping.");
- continue;
- }
-
- var urlCover = item.UrlCover;
- var gameInstallDir = Path.Combine(rootInstallDir, Path.GetFileNameWithoutExtension(fileName));
- var pathToGame = Path.Combine(gameInstallDir, fileName);
-
- var info = new RomMGameInfo
- {
- MappingId = mapping.MappingId,
- FileName = fileName,
- DownloadUrl = CombineUrl(Settings.RomMHost, $"api/roms/{item.Id}/content/{fileName}"),
- HasMultipleFiles = item.HasMultipleFiles
- };
-
- var gameId = info.AsGameId();
- responseGameIDs.Add(gameId);
- // Save sibling data so user can select the version they want installed
- if (Settings.MergeRevisions && item.Siblings.Count > 0)
+ RomMVersionSelector VersionSelectorControl = new RomMVersionSelector(gameData.ROMVersions);
+ var window = Playnite.Dialogs.CreateWindow(new WindowCreationOptions
{
- List gameInfos = new List();
-
- var baseSibling = new RomMSibling
- {
- Id = item.Id,
- Name = item.Name,
- FileNameNoTags = item.FileNameNoTags,
- FileNameNoExt = item.FileNameNoExt,
- FileName = fileName,
- HasMultipleFiles = item.HasMultipleFiles,
- DownloadURL = CombineUrl(Settings.RomMHost, $"api/roms/{item.Id}/content/{fileName}"),
- isSelected = true
- };
- gameInfos.Add(baseSibling);
-
- foreach (var sibling in item.Siblings)
- {
- var siblingItem = allRoms.Find(x => x.Id == sibling.Id);
-
- if (siblingItem == null)
- {
- Logger.Error($"Unable to find sibling data for id:{sibling.Id}");
- continue;
- }
-
- var siblingfileName = "";
- try
- {
- siblingfileName = siblingItem.HasMultipleFiles ? Path.GetFileName(siblingItem.FileName) : Path.GetFileName(siblingItem.Files.Where(f => f.FullPath.Count(c => c == '/') <= 3).FirstOrDefault().FileName);
- }
- catch (Exception ex)
- {
- Logger.Error($"ROM: {item.Id} Error:{ex.ToString()}! Skipping ROM!");
- continue;
- }
-
- if (string.IsNullOrWhiteSpace(siblingfileName))
- {
- Logger.Warn($"Rom {siblingItem.Id} returned empty/invalid filename, skipping.");
- continue;
- }
+ ShowMinimizeButton = false,
+ ShowMaximizeButton = false,
+ ShowCloseButton = false,
+ });
- sibling.FileName = siblingfileName;
- sibling.DownloadURL = CombineUrl(Settings.RomMHost, $"api/roms/{sibling.Id}/content/{siblingfileName}");
- sibling.HasMultipleFiles = siblingItem.HasMultipleFiles;
+ window.Height = 215;
+ window.Width = 600;
- gameInfos.Add(sibling);
- }
+ window.Title = "Select Version to install!";
+ window.ShowInTaskbar = false;
+ window.ResizeMode = ResizeMode.NoResize;
+ window.Owner = API.Instance.Dialogs.GetCurrentAppWindow();
+ window.WindowStartupLocation = WindowStartupLocation.CenterOwner;
+ window.Content = VersionSelectorControl;
- File.WriteAllText($"{ROMsWithSiblingsPath}{item.Id}.json", JsonConvert.SerializeObject(gameInfos));
- ImportedROMsWithSiblings.Add(item.Id);
- }
+ window.ShowDialog();
- string completionStatus;
- // Determine status in Playnite. Backlogged and "now playing" take precedent over the status options
- if (item.RomUser.Backlogged || item.RomUser.NowPlaying)
+ if (VersionSelectorControl.Cancelled)
{
- completionStatus = item.RomUser.NowPlaying ? RomMRomUser.CompletionStatusMap["now_playing"] : RomMRomUser.CompletionStatusMap["backlogged"];
+ romData.Id = (int)InstallStatus.Cancelled;
}
else
{
- completionStatus = RomMRomUser.CompletionStatusMap[item.RomUser.Status ?? "not_played"];
- }
-
- completionStatusMap.TryGetValue(completionStatus, out var statusId);
-
- var status = PlayniteApi.Database.CompletionStatuses.Get(statusId);
- var completionStatusProperty = status != null ? new MetadataNameProperty(status.Name) : null;
-
- // Check if the game is already installed
- var game = Playnite.Database.Games.FirstOrDefault(g => g.GameId == gameId);
- if (game != null)
- {
- // If it is already installed, we sync over metadata like favorite and status
- if (Settings.KeepRomMSynced == true)
+ // Uninstall old ROM before installing new one
+ if (args.Game.IsInstalled)
{
- game.Favorite = favorites.Exists(f => f == item.Id);
-
- if (statusId != Guid.Empty)
- {
- game.CompletionStatusId = statusId;
- }
-
- // Using the Version-Field for storing the ID instead of "RomMGameInfo"
- // Could be useful in the future: https://github.com/JosefNemec/Playnite/issues/801
- game.Version = $"RomM:{item.Id}";
+ Playnite.UninstallGame(args.Game.Id);
- ignoredGameIds.TryAdd(game.Id, 0);
- Playnite.Database.Games.Update(game);
+ args.Game.IsInstalling = true;
+ Playnite.Database.Games.Update(args.Game);
}
- continue;
- }
-
- var gameNameWithTags =
- $"{gameName}" +
- $"{(item.Regions.Count > 0 ? $" ({string.Join(", ", item.Regions)})" : "")}" +
- $"{(!string.IsNullOrEmpty(item.Revision) ? $" (Rev {item.Revision})" : "")}" +
- $"{(item.Tags.Count > 0 ? $" ({string.Join(", ", item.Tags)})" : "")}";
-
- // Add newly found game
- games.Add(new GameMetadata
- {
- Source = SourceName,
- Name = gameName,
- Roms = new List { new GameRom(gameNameWithTags, pathToGame) },
- InstallDirectory = gameInstallDir,
- IsInstalled = File.Exists(pathToGame),
- GameId = gameId,
- Platforms = new HashSet { new MetadataNameProperty(mapping.Platform.Name ?? "") },
- Regions = new HashSet(item.Regions.Where(r => !string.IsNullOrEmpty(r)).Select(r => new MetadataNameProperty(r.ToString()))),
- Genres = new HashSet(item.Metadatum.Genres.Where(r => !string.IsNullOrEmpty(r)).Select(r => new MetadataNameProperty(r.ToString()))),
- ReleaseDate = item.Metadatum.Release_Date.HasValue ? new ReleaseDate(new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(item.Metadatum.Release_Date.Value).ToLocalTime()) : new ReleaseDate(),
- Series = new HashSet(item.Metadatum.Franchises.Where(r => !string.IsNullOrEmpty(r)).Select(r => new MetadataNameProperty(r.ToString()))),
- CommunityScore = (int?)item.Metadatum.Average_Rating,
- Features = new HashSet(item.Metadatum.Gamemodes.Where(r => !string.IsNullOrEmpty(r)).Select(r => new MetadataNameProperty(r.ToString()))),
- Categories = new HashSet(item.Metadatum.Collections.Where(r => !string.IsNullOrEmpty(r)).Select(r => new MetadataNameProperty(r.ToString()))),
- InstallSize = item.FileSizeBytes,
- Description = item.Summary,
- CoverImage = !string.IsNullOrEmpty(urlCover) ? new MetadataFile(urlCover) : null,
- Favorite = favorites.Exists(f => f == item.Id),
- LastActivity = item.RomUser.LastPlayed,
- UserScore = item.RomUser.Rating * 10, //RomM-Rating is 1-10, Playnite 1-100, so it can unfortunately only by synced one direction without loosing decimals
- CompletionStatus = completionStatusProperty,
- GameActions = new List
- {
- new GameAction
- {
- Name = $"Play in {mapping.Emulator.Name}",
- Type = GameActionType.Emulator,
- EmulatorId = mapping.EmulatorId,
- EmulatorProfileId = mapping.EmulatorProfileId,
- IsPlayAction = true,
- },
- new GameAction
- {
- Type = GameActionType.URL,
- Name = "View in RomM",
- Path = CombineUrl(Settings.RomMHost, $"rom/{item.Id}"),
- IsPlayAction = false
- }
- },
- Version = $"RomM:{item.Id}"
- });
- }
-
- Logger.Debug($"Finished adding new games for {apiPlatform.Name}");
-
- var gamesInDatabase = Playnite.Database.Games.Where(g =>
- g.Source != null && g.Source.Name == SourceName.ToString() &&
- g.Platforms != null && g.Platforms.Any(p => p.Name == mapping.Platform.Name)
- );
- Logger.Debug($"Starting to remove not found games for {apiPlatform.Name}.");
- foreach (var game in gamesInDatabase)
- {
- if (args.CancelToken.IsCancellationRequested)
- break;
+ var selectedrevision = VersionSelectorControl.RomVersions.First(x => x.IsSelected);
+ romData.Id = selectedrevision.Id;
+ romData.FileName = selectedrevision.FileName;
+ romData.HasMultipleFiles = selectedrevision.HasMultipleFiles;
+ romData.DownloadURL = selectedrevision.DownloadURL;
+
+ gameData.ROMVersions = VersionSelectorControl.RomVersions.ToList();
- if (responseGameIDs.Contains(game.GameId))
- {
- continue;
+ File.WriteAllText($"{ROMDataPath}{romMSHA1}.json", JsonConvert.SerializeObject(gameData));
}
-
- Playnite.Database.Games.Remove(game.Id);
}
-
- Logger.Debug($"Finished removing not found games for {apiPlatform.Name}");
}
- catch (HttpRequestException e)
- {
- Logger.Error($"Request exception: {e.Message}");
- return games;
- }
- }
- return games;
+ yield return new RomMInstallController(args.Game, this, romData);
+ }
}
-
- public override IEnumerable GetSidebarItems()
+ public override IEnumerable GetUninstallActions(GetUninstallActionsArgs args)
{
- if (DownloadsSidebar != null)
+ if (args.Game.PluginId == Id)
{
- yield return DownloadsSidebar;
+ yield return new RomMUninstallController(args.Game, this);
}
}
-
- public override ISettings GetSettings(bool firstRunSettings)
+ public override void OnGameInstalled(OnGameInstalledEventArgs args)
{
- return Settings;
+ base.OnGameInstalled(args);
+
+ if (args.Game.PluginId == PluginId && Settings.NotifyOnInstallComplete)
+ {
+ Playnite.Notifications.Add(args.Game.GameId, $"Download of \"{args.Game.Name}\" is complete", NotificationType.Info);
+ }
}
- public override UserControl GetSettingsView(bool firstRunSettings)
+ public override LibraryMetadataProvider GetMetadataDownloader()
{
- return new SettingsView();
+ return new RomMMetadataProvider(this);
}
+ #endregion
- public override IEnumerable GetGameMenuItems(GetGameMenuItemsArgs args)
+ #region RomM Status Syncing
+ public IList FetchFavorites()
{
- List gameMenuItems = new List();
-
- if (args.Games.First().PluginId == PluginId)
+ string apiFavoriteUrl = CombineUrl(Settings.RomMHost, "api/collections");
+ try
{
- var version = args.Games.First().Version;
- if (version == null || !version.StartsWith("RomM:"))
- {
- Logger.Warn($"Couldn't find RomMId for {args.Games.First().Name}.");
- return gameMenuItems;
- }
-
- int romMId;
- if (!int.TryParse(version.Split(':')[1], out romMId))
- {
- Logger.Error($"Malformed version string? {version} > {romMId}");
- return gameMenuItems;
- }
+ // Make the request and get the response
+ HttpResponseMessage response = HttpClientSingleton.Instance.GetAsync(apiFavoriteUrl).GetAwaiter().GetResult();
+ response.EnsureSuccessStatusCode();
- if (Settings.MergeRevisions && File.Exists($"{ROMsWithSiblingsPath}{romMId}.json") && args.Games.First().IsInstalled)
- {
- gameMenuItems.Add(new GameMenuItem
- {
- //MenuSection = "@",
- Description = "Switch ROM Version!",
- Action = (gameMenuItem) =>
- {
- Playnite.InstallGame(args.Games.First().Id);
- }
- });
- }
+ // Assuming the response is in JSON format
+ string body = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
+ return JsonConvert.DeserializeObject>(body);
+ }
+ catch (HttpRequestException e)
+ {
+ Logger.Error($"Request exception: {e.Message}");
+ return new List();
}
- return gameMenuItems;
}
-
- public override IEnumerable GetInstallActions(GetInstallActionsArgs args)
+ internal RomMCollection CreateFavorites()
{
- if (args.Game.PluginId == Id)
+ string apiCollectionUrl = CombineUrl(Settings.RomMHost, "api/collections?is_favorite=true&is_public=false");
+ try
{
- bool hasSiblings = false;
- int siblingID = -1;
-
- var version = args.Game.Version;
- if (version == null || !version.StartsWith("RomM:"))
- {
- Logger.Warn($"Couldn't find RomMId for {args.Game.Name}.");
- //Set SiblingId to -2 to cancel request
- siblingID = -2;
- }
-
- int romMId = -1;
- if (siblingID != -2)
- {
- if (!int.TryParse(version.Split(':')[1], out romMId))
- {
- Logger.Error($"Malformed version string? {version} > {romMId}");
- siblingID = -2;
- }
- }
-
- // If Siblings are avaiable prompt user with version selection
- if (Settings.MergeRevisions && File.Exists($"{ROMsWithSiblingsPath}{romMId}.json") && siblingID != -2)
- {
- List siblingInfos = new List();
- string json = File.ReadAllText($"{ROMsWithSiblingsPath}{romMId}.json");
- siblingInfos = JsonConvert.DeserializeObject>(json);
-
- RomMVersionSelector VersionSelectorControl = new RomMVersionSelector(siblingInfos);
-
- var window = Playnite.Dialogs.CreateWindow(new WindowCreationOptions
- {
- ShowMinimizeButton = false,
- ShowMaximizeButton = false,
- ShowCloseButton = false,
- });
-
- window.Height = 215;
- window.Width = 600;
-
- window.Title = "Select Version to install!";
- window.ShowInTaskbar = false;
- window.ResizeMode = ResizeMode.NoResize;
- window.Owner = API.Instance.Dialogs.GetCurrentAppWindow();
- window.WindowStartupLocation = WindowStartupLocation.CenterOwner;
- window.Content = VersionSelectorControl;
-
- window.ShowDialog();
-
- if (VersionSelectorControl.Cancelled)
- {
- siblingID = -2;
- }
- else
- {
- //Uninstall old ROM before installing new one
- if (args.Game.IsInstalled)
- {
- Playnite.UninstallGame(args.Game.Id);
-
- args.Game.IsInstalling = true;
- Playnite.Database.Games.Update(args.Game);
- }
-
- hasSiblings = true;
- siblingID = VersionSelectorControl.Siblings.Where(x => x.isSelected).First().Id;
+ var formData = new MultipartFormDataContent();
+ formData.Add(new StringContent("Favorites"), "name");
- //Write result back to json file
- siblingInfos = VersionSelectorControl.Siblings.ToList();
- File.WriteAllText($"{ROMsWithSiblingsPath}{romMId}.json", JsonConvert.SerializeObject(siblingInfos));
- }
- }
+ HttpResponseMessage postResponse = HttpClientSingleton.Instance.PostAsync(apiCollectionUrl, formData).GetAwaiter().GetResult();
+ postResponse.EnsureSuccessStatusCode();
- yield return args.Game.GetRomMGameInfo().GetInstallController(args.Game, this, hasSiblings, siblingID);
+ string body = postResponse.Content.ReadAsStringAsync().GetAwaiter().GetResult();
+ return JsonConvert.DeserializeObject(body);
}
- }
-
- public override IEnumerable GetUninstallActions(GetUninstallActionsArgs args)
- {
- if (args.Game.PluginId == Id)
+ catch (HttpRequestException e)
{
- yield return args.Game.GetRomMGameInfo().GetUninstallController(args.Game, this);
+ Logger.Error($"Request exception: {e.Message}");
+ return null;
}
}
-
- public override void OnGameInstalled(OnGameInstalledEventArgs args)
+ internal void UpdateFavorites(RomMCollection favoriteCollection, List romIds)
{
- base.OnGameInstalled(args);
+ if (favoriteCollection == null)
+ {
+ Logger.Error($"Can't update favorites, collection is null");
+ return;
+ }
- if (args.Game.PluginId == PluginId && Settings.NotifyOnInstallComplete)
+ string apiCollectionUrl = CombineUrl(Settings.RomMHost, "api/collections");
+ try
{
- Playnite.Notifications.Add(args.Game.GameId, $"Download of \"{args.Game.Name}\" is complete", NotificationType.Info);
+ var formData = new MultipartFormDataContent();
+ formData.Add(new StringContent(JsonConvert.SerializeObject(romIds)), "rom_ids");
+ HttpResponseMessage putResponse = HttpClientSingleton.Instance.PutAsync($"{apiCollectionUrl}/{favoriteCollection.Id}", formData).GetAwaiter().GetResult();
+ putResponse.EnsureSuccessStatusCode();
+ }
+ catch (HttpRequestException e)
+ {
+ Logger.Error($"Request exception: {e.Message}");
}
}
- private readonly ConcurrentDictionary ignoredGameIds = new ConcurrentDictionary();
private void OnItemUpdated(object sender, ItemUpdatedEventArgs e)
{
Task.Run(async () =>
@@ -972,27 +576,12 @@ private void OnItemUpdated(object sender, ItemUpdatedEventArgs e)
if (Settings.KeepRomMSynced == true)
{
- if (ignoredGameIds.ContainsKey(newGame.Id))
+ int romMId;
+ if(!int.TryParse(newGame.GameId.Split(':')[0], out romMId))
{
- // This GameId is marked as an internal update, should be ignored this time
- ignoredGameIds.TryRemove(newGame.Id, out _);
- continue;
- }
-
- var version = newGame.Version;
- if (version == null || !version.StartsWith("RomM:"))
- {
- Logger.Warn($"Couldn't find RomMId for {update.NewData.Name}.");
- continue;
+ Logger.Error($"{newGame.Name} GameID is malformed!");
}
- int romMId;
- if (!int.TryParse(version.Split(':')[1], out romMId))
- {
- Logger.Error($"Malformed version string? {version} > {romMId}");
- continue;
- }
-
if (oldGame.Favorite != newGame.Favorite)
{
Logger.Info($"Favorites changed for {romMId}.");
@@ -1051,5 +640,6 @@ private void OnItemUpdated(object sender, ItemUpdatedEventArgs e)
}
});
}
+ #endregion
}
-}
+}
\ No newline at end of file
diff --git a/RomM.csproj b/RomM.csproj
index 39297f9..160dd3b 100644
--- a/RomM.csproj
+++ b/RomM.csproj
@@ -54,11 +54,6 @@
-
-
- PreserveNewest
-
-
MSBuild:Compile
@@ -110,6 +105,12 @@
MSBuild:Compile
+
+ PreserveNewest
+
+
+ PreserveNewest
+
SettingsSingleFileGenerator
Settings.Designer.cs
diff --git a/Settings/EmulatorMapping.cs b/Settings/EmulatorMapping.cs
index 620d91a..12fa907 100644
--- a/Settings/EmulatorMapping.cs
+++ b/Settings/EmulatorMapping.cs
@@ -6,91 +6,260 @@
using System.ComponentModel;
using System.Linq;
using System.Xml.Serialization;
+using RomM.Models.RomM.Platform;
+using SharpCompress;
namespace RomM.Settings
{
public class EmulatorMapping : ObservableObject
{
- public EmulatorMapping()
+ [JsonIgnore]
+ private Guid _mappingId;
+ [JsonIgnore]
+ private string _mappingName = "";
+ [JsonIgnore]
+ private bool _enabled = true;
+ [JsonIgnore]
+ private bool _autoExtract = false;
+ [JsonIgnore]
+ private bool _useM3U = false;
+ [JsonIgnore]
+ private Emulator _emulator;
+ [JsonIgnore]
+ private Guid _emulatorId;
+ [JsonIgnore]
+ private EmulatorProfile _emulatorProfile;
+ [JsonIgnore]
+ private IEnumerable _availableProfiles;
+ [JsonIgnore]
+ public string _emulatorProfileId;
+ [JsonIgnore]
+ private RomMPlatform _emulatedPlatform = new RomMPlatform();
+ [JsonIgnore]
+ private IEnumerable _availablePlatforms;
+ [JsonIgnore]
+ public int _romMPlatformId = -1;
+ [JsonIgnore]
+ private string _destinationPath = "";
+
+ public EmulatorMapping(List romMPlatforms)
{
MappingId = Guid.NewGuid();
+ AvailablePlatforms = romMPlatforms;
}
- public Guid MappingId { get; set; }
+ public Guid MappingId
+ {
+ get => _mappingId;
+ set
+ {
+ _mappingId = value;
+ OnPropertyChanged();
+ }
+ }
[DefaultValue(true)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
- public bool Enabled { get; set; }
+ public bool Enabled
+ {
+ get => _enabled;
+ set
+ {
+ _enabled = value;
+ OnPropertyChanged();
+ }
+ }
[DefaultValue(false)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
- public bool AutoExtract { get; set; }
+ public bool AutoExtract
+ {
+ get => _autoExtract;
+ set
+ {
+ _autoExtract = value;
+ OnPropertyChanged();
+ }
+ }
[DefaultValue(false)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
- public bool UseM3u { get; set; }
+ public bool UseM3U
+ {
+ get => _useM3U;
+ set
+ {
+ _useM3U = value;
+ OnPropertyChanged();
+ }
+ }
[JsonIgnore]
public Emulator Emulator
{
- get => AvailableEmulators.FirstOrDefault(e => e.Id == EmulatorId);
- set { EmulatorId = value.Id; }
+ get => _emulator;
+ set
+ {
+ if (value != null)
+ {
+ _emulator = value;
+ _emulatorId = value.Id;
+ AvailableProfiles = Emulator?.SelectableProfiles;
+ RomMPlatform = new RomMPlatform();
+ MappingName = value.Name;
+ OnPropertyChanged();
+ }
+ }
+ }
+ public Guid EmulatorId
+ {
+ get => _emulatorId;
+ set
+ {
+ _emulatorId = value;
+ Emulator = SettingsViewModel.Instance.PlayniteAPI.Database.Emulators.FirstOrDefault(x => x.Id == _emulatorId);
+ OnPropertyChanged();
+ }
}
- public Guid EmulatorId { get; set; }
[JsonIgnore]
public EmulatorProfile EmulatorProfile
{
- get => Emulator?.SelectableProfiles.FirstOrDefault(p => p.Id == EmulatorProfileId);
- set { EmulatorProfileId = value.Id; }
- }
+ get => _emulatorProfile;
+ set
+ {
+ if (value != null)
+ {
+ _emulatorProfile = value;
+ _emulatorProfileId = value.Id;
- public string EmulatorProfileId { get; set; }
+ if (Emulator != null)
+ {
+ var name = Emulator.Name;
+ if (EmulatorProfile != null && EmulatorProfile.Name != "")
+ name += " - " + EmulatorProfile.Name;
+ if (RomMPlatform != null && !string.IsNullOrEmpty(RomMPlatform.Name))
+ name += " - " + RomMPlatform.Name;
+ MappingName = name;
+ }
+ }
+ OnPropertyChanged();
+ }
+ }
+ public string EmulatorProfileId
+ {
+ get => _emulatorProfileId;
+ set
+ {
+ _emulatorProfileId = value;
+ EmulatorProfile = Emulator?.SelectableProfiles.FirstOrDefault(x => x.Id == _emulatorProfileId);
+ OnPropertyChanged();
+ }
+ }
+
+ // (Deprecated) DON'T USE
+ [JsonIgnore]
+ public Platform Platform
+ {
+ get => null;
+ set
+ {
+ }
+ }
+ // (Deprecated) DON'T USE
[JsonIgnore]
- public EmulatedPlatform Platform
+ public string PlatformId
{
- get => AvailablePlatforms.FirstOrDefault(p => p.Id == PlatformId);
- set { PlatformId = value.Id; }
+ get => "";
+ set
+ {
+ }
}
- public string PlatformId { get; set; }
- public string DestinationPath { get; set; }
- public static IEnumerable AvailableEmulators => SettingsViewModel.Instance.PlayniteAPI.Database.Emulators?.OrderBy(x => x.Name) ?? Enumerable.Empty();
+ [JsonIgnore]
+ public RomMPlatform RomMPlatform
+ {
+ get => _emulatedPlatform;
+ set
+ {
+ _emulatedPlatform = value;
+ _romMPlatformId = -1;
+ if(value != null)
+ {
+ _romMPlatformId = value.Id;
+
+ if(Emulator != null)
+ {
+ var name = Emulator.Name;
+ if (EmulatorProfile != null && EmulatorProfile.Name != "")
+ name += " - " + EmulatorProfile.Name;
+ if (RomMPlatform != null && !string.IsNullOrEmpty(RomMPlatform.Name))
+ name += " - " + RomMPlatform.Name;
+
+ MappingName = name;
+ }
+
+ }
+ OnPropertyChanged();
+ }
+ }
+ public int RomMPlatformId
+ {
+ get => _romMPlatformId;
+ set
+ {
+ _romMPlatformId = value;
+ OnPropertyChanged();
+ }
+ }
[JsonIgnore]
- public IEnumerable AvailableProfiles => Emulator?.SelectableProfiles;
+ public string MappingName
+ {
+ get => _mappingName;
+ set
+ {
+ _mappingName = value;
+ OnPropertyChanged();
+ }
+ }
+ public string DestinationPath
+ {
+ get => _destinationPath;
+ set
+ {
+ _destinationPath = value;
+ OnPropertyChanged();
+ }
+}
+
+[JsonIgnore]
+ public static IEnumerable AvailableEmulators => SettingsViewModel.Instance.PlayniteAPI.Database.Emulators?.OrderBy(x => x.Name) ?? Enumerable.Empty();
[JsonIgnore]
- public IEnumerable AvailablePlatforms
+ public IEnumerable AvailableProfiles
{
- get
+ get => _availableProfiles;
+ set
{
- var playnite = SettingsViewModel.Instance.PlayniteAPI;
- HashSet validPlatforms;
+ _availableProfiles = value;
+ OnPropertyChanged();
+ }
+ }
+ [JsonIgnore]
+ public IEnumerable AvailablePlatforms
+ {
+ get => _availablePlatforms;
+ set
+ {
+ _availablePlatforms = value;
+ OnPropertyChanged();
- if (EmulatorProfile is CustomEmulatorProfile)
+ if (_availablePlatforms != null && RomMPlatformId != -1)
{
- var customProfile = EmulatorProfile as CustomEmulatorProfile;
- validPlatforms = new HashSet(playnite.Database.Platforms.Where(p => customProfile.Platforms.Contains(p.Id)).Select(p => p.SpecificationId));
+ RomMPlatform = AvailablePlatforms.FirstOrDefault (x => x.Id == RomMPlatformId);
}
- else if (EmulatorProfile is BuiltInEmulatorProfile)
- {
- var builtInProfile = (EmulatorProfile as BuiltInEmulatorProfile);
- validPlatforms = new HashSet(
- playnite.Emulation.Emulators
- .FirstOrDefault(e => e.Id == Emulator.BuiltInConfigId)?
- .Profiles
- .FirstOrDefault(p => p.Name == builtInProfile.Name)?
- .Platforms
- );
- }
- else
- {
- validPlatforms = new HashSet();
- }
-
- return playnite.Emulation.Platforms?.Where(p => validPlatforms.Contains(p.Id)) ?? Enumerable.Empty();
}
}
@@ -128,11 +297,11 @@ public string EmulatorBasePathResolved
public IEnumerable GetDescriptionLines()
{
- yield return $"{nameof(EmulatorId)}: {EmulatorId}";
+ yield return $"{nameof(_emulatorId)}: {_emulatorId}";
yield return $"{nameof(Emulator)}*: {Emulator?.Name ?? ""}";
yield return $"{nameof(EmulatorProfileId)}: {EmulatorProfileId ?? ""}";
yield return $"{nameof(EmulatorProfile)}*: {EmulatorProfile?.Name ?? ""}";
- yield return $"{nameof(PlatformId)}: {PlatformId ?? ""}";
+ yield return $"{nameof(PlatformId)}: {PlatformId}";
yield return $"{nameof(Platform)}*: {Platform?.Name ?? ""}";
yield return $"{nameof(DestinationPath)}: {DestinationPath ?? ""}";
yield return $"{nameof(DestinationPathResolved)}*: {DestinationPathResolved ?? ""}";
diff --git a/Settings/Settings.cs b/Settings/Settings.cs
index cc6ecaf..da93ea4 100644
--- a/Settings/Settings.cs
+++ b/Settings/Settings.cs
@@ -1,73 +1,280 @@
using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
using Playnite.SDK;
using Playnite.SDK.Plugins;
+
+using RomM.Models.RomM;
+using RomM.Models.RomM.Platform;
+
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
+using System.Net.Http;
+using System.Reflection;
using System.Text.RegularExpressions;
+using System.Windows.Data;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
namespace RomM.Settings
{
public class SettingsViewModel : ObservableObject, ISettings
{
private readonly Plugin _plugin;
-
private SettingsViewModel editingClone { get; set; }
+ [JsonIgnore] internal readonly IPlayniteAPI PlayniteAPI;
+ [JsonIgnore] internal readonly IRomM RomM;
+ public static SettingsViewModel Instance { get; private set; }
- [JsonIgnore]
- internal readonly IPlayniteAPI PlayniteAPI;
+ #region Backing Variables
+
+ [JsonIgnore] private string _romMHost = "";
+ [JsonIgnore] private string _romMServerVersion = "---";
+ [JsonIgnore] private string _romMClientToken = "";
+ [JsonIgnore] private bool _useBasicAuth = true;
+ [JsonIgnore] private string _romMUsername = "";
+ [JsonIgnore] private string _romMPassword = "";
+ [JsonIgnore] private string _defaultprofilepath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @"profile.png");
+ [JsonIgnore] private string _profilepath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @"profile.png");
+ [JsonIgnore] private string _romMUser = "----";
+ [JsonIgnore] private string _profileType = "----";
+
+ [JsonIgnore] private string _excludeGenres = "";
+ [JsonIgnore] private string _7zPath = "";
+
+ [JsonIgnore] private List _romMPlatforms = new List();
+
+ [JsonIgnore] private bool _notify = false;
+ [JsonIgnore] private string _notifyText = "";
+ [JsonIgnore] private string _notifyIcon = "";
+ [JsonIgnore] private Color _notfiyColour = Colors.DarkSlateGray;
+ [JsonIgnore] private Brush _notfiyTextColour = new SolidColorBrush(Colors.LightGray);
+
+ #endregion
+
+ #region Notifcation Bar
+ [JsonIgnore]
+ public bool Notify
+ {
+ get => _notify;
+ set
+ {
+ _notify = value;
+ OnPropertyChanged();
+ }
+ }
+ [JsonIgnore]
+ public string NotifyText
+ {
+ get => _notifyText;
+ set
+ {
+ _notifyText = value;
+ OnPropertyChanged();
+ }
+ }
[JsonIgnore]
- internal readonly IRomM RomM;
+ public string NotifyIcon
+ {
+ get => _notifyIcon;
+ set
+ {
+ _notifyIcon = value;
+ OnPropertyChanged();
+ }
+ }
+ [JsonIgnore]
+ public Color NotfiyColour
+ {
+ get => _notfiyColour;
+ set
+ {
+ _notfiyColour = value;
+ OnPropertyChanged();
+ }
+ }
+ [JsonIgnore]
+ public Brush NotfiyTextColour
+ {
+ get => _notfiyTextColour;
+ set
+ {
+ _notfiyTextColour = value;
+ OnPropertyChanged();
+ }
+ }
+ public void UpdateNotifcationBar(string Message, bool IsError = false)
+ {
+ if (IsError)
+ {
+ NotfiyColour = (Color)ColorConverter.ConvertFromString("#730000");
+ NotfiyTextColour = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#ff6b6b"));
+ NotifyIcon = $" \uE730";
+ NotifyText = $" {Message}";
+ Notify = true;
+ }
+ else
+ {
+ NotfiyColour = (Color)ColorConverter.ConvertFromString("#035900");
+ NotfiyTextColour = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#91ff8e"));
+ NotifyIcon = $" \uE73E";
+ NotifyText = $" {Message}";
+ Notify = true;
+ }
+ }
- public static SettingsViewModel Instance { get; private set; }
+ #endregion
- // RomM client API tokens are "rmm_" + 64 lowercase hex chars (secrets.token_hex(32) on the server).
- private static readonly Regex ApiTokenPattern = new Regex(@"^rmm_[0-9a-f]{64}$", RegexOptions.Compiled);
+ public string RomMHost
+ {
+ get => _romMHost;
+ set
+ {
+ if(value.Length == 0)
+ {
+ _romMHost = "";
+ }
+ else
+ {
+ _romMHost = value.TrimEnd('/');
+ }
+ OnPropertyChanged();
+ }
+ }
+ public string RomMClientToken
+ {
+ get => _romMClientToken;
+ set
+ {
+ _romMClientToken = value;
+ OnPropertyChanged();
+ }
+ }
+ public static readonly Regex ApiTokenPattern = new Regex(@"^rmm_[0-9a-f]{64}$", RegexOptions.Compiled);
- public static bool IsValidApiToken(string token)
+ public bool UseBasicAuth
{
- return !string.IsNullOrEmpty(token) && ApiTokenPattern.IsMatch(token);
+ get => _useBasicAuth;
+ set
+ {
+ _useBasicAuth = value;
+ OnPropertyChanged();
+ }
+ }
+ public string RomMUsername
+ {
+ get => _romMUsername;
+ set
+ {
+ _romMUsername = value;
+ OnPropertyChanged();
+ }
+ }
+ public string RomMPassword
+ {
+ get => _romMPassword;
+ set
+ {
+ _romMPassword = value;
+ OnPropertyChanged();
+ }
+ }
+ public string RomMUser
+ {
+ get => _romMUser;
+ set
+ {
+ _romMUser = value;
+ OnPropertyChanged();
+ }
}
- [JsonIgnore]
- public bool HasAnyAuth =>
- IsValidApiToken(RomMApiToken?.Trim()) ||
- (!string.IsNullOrEmpty(RomMUsername) && !string.IsNullOrEmpty(RomMPassword));
+ [JsonIgnore] public string ClientTokenURL
+ {
+ get => $"{RomMHost}/client-api-tokens";
+ set { }
+ }
+ public string ServerVersion
+ {
+ get => _romMServerVersion;
+ set
+ {
+ _romMServerVersion = value;
+ OnPropertyChanged();
+ }
+ }
+ public string ProfilePath
+ {
+ get => _profilepath;
+ set
+ {
+ _profilepath = value;
+ OnPropertyChanged();
+ }
+ }
+ public string RomMProfileType
+ {
+ get => _profileType;
+ set
+ {
+ _profileType = value;
+ OnPropertyChanged();
+ }
+ }
+
public bool ScanGamesInFullScreen { get; set; } = false;
public bool NotifyOnInstallComplete { get; set; } = false;
public bool KeepRomMSynced { get; set; } = false;
- public string RomMHost { get; set; } = "";
- public string RomMUsername { get; set; } = "";
- public string RomMPassword { get; set; } = "";
- private string _romMApiToken = "";
- public string RomMApiToken
+ public bool Use7z { get; set; } = false;
+ public string PathTo7z
{
- get => _romMApiToken;
+ get => _7zPath;
set
{
- if (_romMApiToken == value) return;
- _romMApiToken = value;
+ _7zPath = value;
+ OnPropertyChanged();
+ }
+ }
+ public bool MergeRevisions { get; set; } = false;
+ public bool KeepDeletedGames { get; set; } = false;
+ public string ExcludeGenres
+ {
+ get => _excludeGenres;
+ set
+ {
+ _excludeGenres = value;
OnPropertyChanged();
- OnPropertyChanged(nameof(HasValidApiToken));
}
}
+ public bool SkipMissingFiles { get; set; } = false;
- [JsonIgnore]
- public bool HasValidApiToken => IsValidApiToken(RomMApiToken?.Trim());
public ObservableCollection Mappings { get; set; }
- public bool Use7z { get; set; } = false;
- public string PathTo7z { get; set; } = "";
- public bool MergeRevisions { get; set; } = false;
-
- public SettingsViewModel()
+ public List RomMPlatforms
{
+ get => _romMPlatforms;
+ set
+ {
+ if(value != null)
+ {
+ _romMPlatforms = value;
+ OnPropertyChanged();
+
+ foreach (var mapping in Mappings)
+ {
+ mapping.AvailablePlatforms = value;
+ }
+ }
+ }
}
+ public SettingsViewModel(){}
+
internal SettingsViewModel(Plugin plugin, IRomM romM)
{
RomM = romM;
@@ -78,20 +285,36 @@ internal SettingsViewModel(Plugin plugin, IRomM romM)
bool forceSave = false;
var savedSettings = plugin.LoadPluginSettings();
- if (savedSettings == null) {
+ if (savedSettings == null)
+ {
forceSave = true;
- } else {
- ScanGamesInFullScreen = savedSettings.ScanGamesInFullScreen;
- NotifyOnInstallComplete = savedSettings.NotifyOnInstallComplete;
+ }
+ else
+ {
RomMHost = savedSettings.RomMHost;
+ RomMClientToken = savedSettings.RomMClientToken;
RomMUsername = savedSettings.RomMUsername;
RomMPassword = savedSettings.RomMPassword;
- RomMApiToken = savedSettings.RomMApiToken ?? "";
+ UseBasicAuth = savedSettings.UseBasicAuth;
+
+ RomMUser = savedSettings.RomMUser;
+ RomMProfileType = savedSettings.RomMProfileType;
+ ProfilePath = savedSettings.ProfilePath;
+ ServerVersion = savedSettings.ServerVersion;
+
+ // ----- These need to stay in this order -----
Mappings = savedSettings.Mappings;
+ RomMPlatforms = savedSettings.RomMPlatforms;
+ // --------------------------------------------
+
KeepRomMSynced = savedSettings.KeepRomMSynced;
+ ScanGamesInFullScreen = savedSettings.ScanGamesInFullScreen;
+ NotifyOnInstallComplete = savedSettings.NotifyOnInstallComplete;
Use7z = savedSettings.Use7z;
PathTo7z = savedSettings.PathTo7z;
MergeRevisions = savedSettings.MergeRevisions;
+ KeepDeletedGames = savedSettings.KeepDeletedGames;
+ ExcludeGenres = savedSettings.ExcludeGenres;
}
if (Mappings == null)
@@ -112,6 +335,109 @@ internal SettingsViewModel(Plugin plugin, IRomM romM)
}
}
+ public bool TestConnection(bool UpdateNotificationBar = false)
+ {
+ Notify = false;
+
+ try
+ {
+ if(string.IsNullOrEmpty(RomMHost))
+ {
+ throw new ArgumentException("Host not set!");
+ }
+ if(!Uri.IsWellFormedUriString(RomMHost, UriKind.RelativeOrAbsolute))
+ {
+ throw new ArgumentException("Host is not a valid URL!");
+ }
+
+ if(UseBasicAuth)
+ {
+ if(string.IsNullOrEmpty(RomMUsername) || string.IsNullOrEmpty(RomMPassword))
+ {
+ throw new ArgumentException("Username/Password not set!");
+ }
+
+ HttpClientSingleton.ConfigureBasicAuth(RomMUsername, RomMPassword);
+ }
+ else
+ {
+ if (string.IsNullOrEmpty(RomMClientToken))
+ {
+ throw new ArgumentException("Client token not set!");
+ }
+
+ if(!ApiTokenPattern.IsMatch(RomMClientToken))
+ {
+ throw new ArgumentException("Client token format invaild!");
+ }
+
+ HttpClientSingleton.ConfigureAPIAuth(RomMClientToken);
+ }
+
+ // Check server is present
+ HttpResponseMessage response = HttpClientSingleton.Instance.GetAsync($"{RomMHost}/api/heartbeat", HttpCompletionOption.ResponseContentRead, new System.Threading.CancellationToken()).GetAwaiter().GetResult();
+ response.EnsureSuccessStatusCode();
+
+ Stream body = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult();
+
+ using (StreamReader reader = new StreamReader(body))
+ {
+ var jsonResponse = JObject.Parse(reader.ReadToEnd());
+ ServerInfo info = jsonResponse["SYSTEM"].ToObject();
+
+ ServerVersion = info.Version;
+ }
+
+ // Get user info
+ response = HttpClientSingleton.Instance.GetAsync($"{RomMHost}/api/users/me", System.Net.Http.HttpCompletionOption.ResponseContentRead, new System.Threading.CancellationToken()).GetAwaiter().GetResult();
+ response.EnsureSuccessStatusCode();
+
+ body = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult();
+ RomMUser userinfo;
+
+ using (StreamReader reader = new StreamReader(body))
+ {
+ var jsonResponse = JObject.Parse(reader.ReadToEnd());
+ userinfo = jsonResponse.ToObject();
+ }
+
+ if (!string.IsNullOrEmpty(userinfo.IconPath))
+ {
+ response = HttpClientSingleton.Instance.GetAsync($"{RomMHost}/api/raw/assets/{userinfo.IconPath}", System.Net.Http.HttpCompletionOption.ResponseContentRead, new System.Threading.CancellationToken()).GetAwaiter().GetResult();
+ response.EnsureSuccessStatusCode();
+ var imagebytes = response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult();
+ File.WriteAllBytes($"{PlayniteAPI.Paths.ExtensionsDataPath}\\{RomM.Id.ToString()}\\avatar.png", imagebytes);
+ ProfilePath = $"{PlayniteAPI.Paths.ExtensionsDataPath}\\{RomM.Id.ToString()}\\avatar.png";
+ }
+ else
+ {
+ ProfilePath = _defaultprofilepath;
+ }
+
+ RomMProfileType = userinfo.Role;
+ RomMUser = userinfo.Username;
+ if(UpdateNotificationBar)
+ UpdateNotifcationBar("Authenticated!");
+ }
+ catch (Exception ex)
+ {
+ Notify = true;
+ ProfilePath = _defaultprofilepath;
+ RomMUser = "----";
+ RomMProfileType = "----";
+ ServerVersion = "---";
+ LogManager.GetLogger().Error($"Failed to read response! {ex}");
+
+ if (UpdateNotificationBar)
+ UpdateNotifcationBar($"Authentication failed: {ex.Message}", true);
+
+ PlayniteAPI.Notifications.Add(new NotificationMessage($"RomMPlugin.Authentication.Failed.{ex.Message}", $"RomM - Authentication failed: {ex.Message}", NotificationType.Error));
+ return false;
+ }
+
+ return true;
+ }
+
public void BeginEdit()
{
// Code executed when settings view is opened and user starts editing values.
@@ -130,7 +456,15 @@ public void EndEdit()
// Code executed when user decides to confirm changes made since BeginEdit was called.
// This method should save settings made to Option1 and Option2.
SavePluginSettings(this);
- HttpClientSingleton.ConfigureAuth(this);
+ if (UseBasicAuth)
+ {
+ HttpClientSingleton.ConfigureBasicAuth(RomMUsername, RomMPassword);
+ }
+ else
+ {
+ HttpClientSingleton.ConfigureAPIAuth(RomMClientToken);
+ }
+
}
private void SavePluginSettings(SettingsViewModel settings)
@@ -150,20 +484,17 @@ public bool VerifySettings(out List errors)
{
var mappingErrors = new List();
- if (!string.IsNullOrWhiteSpace(RomMApiToken) && !IsValidApiToken(RomMApiToken.Trim()))
- {
- mappingErrors.Add("API Token must start with 'rmm_' followed by 64 lowercase hex characters.");
- }
-
Mappings.Where(m => m.Enabled)?.ForEach(m =>
{
if (string.IsNullOrEmpty(m.DestinationPathResolved))
{
mappingErrors.Add($"{m.MappingId}: No destination path specified.");
+ UpdateNotifcationBar($"{m.MappingId}: No destination path specified.", true);
}
else if (!Directory.Exists(m.DestinationPathResolved))
{
mappingErrors.Add($"{m.MappingId}: Destination path doesn't exist ({m.DestinationPathResolved}).");
+ UpdateNotifcationBar($"{m.MappingId}: Destination path doesn't exist ({m.DestinationPathResolved}).", true);
}
});
@@ -171,4 +502,30 @@ public bool VerifySettings(out List errors)
return errors.Count == 0;
}
}
+
+
+ // Used to load profile image into cache so it can be changed while the application is running
+ public class ImageCacheConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType,
+ object parameter, System.Globalization.CultureInfo culture)
+ {
+
+ var path = (string)value;
+ var image = new BitmapImage();
+ image.BeginInit();
+ image.CacheOption = BitmapCacheOption.OnLoad;
+ image.UriSource = new Uri(path);
+ image.EndInit();
+
+ return image;
+
+ }
+
+ public object ConvertBack(object value, Type targetType,
+ object parameter, System.Globalization.CultureInfo culture)
+ {
+ throw new NotImplementedException("Not implemented.");
+ }
+ }
}
diff --git a/Settings/SettingsView.xaml b/Settings/SettingsView.xaml
index bdcd95b..56200fa 100644
--- a/Settings/SettingsView.xaml
+++ b/Settings/SettingsView.xaml
@@ -1,206 +1,280 @@
-
+ xmlns:draw="clr-namespace:System.Drawing;assembly=System.Drawing"
+ xmlns:sys="clr-namespace:System;assembly=mscorlib" mc:Ignorable="d" d:DesignHeight="1000" d:DesignWidth="800" Padding="2,0,2,4">
+
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Help
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Get Token
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Help
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Settings/SettingsView.xaml.cs b/Settings/SettingsView.xaml.cs
index e81c877..2f6d364 100644
--- a/Settings/SettingsView.xaml.cs
+++ b/Settings/SettingsView.xaml.cs
@@ -1,9 +1,14 @@
-using System;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Playnite.SDK;
+using RomM.Models.RomM;
+using RomM.Models.RomM.Platform;
+using System;
+using System.Collections.Generic;
using System.Diagnostics;
+using System.IO;
using System.Linq;
-using System.Net;
using System.Net.Http;
-using System.Threading;
using System.Windows;
using System.Windows.Controls;
@@ -18,104 +23,82 @@ public SettingsView()
InitializeComponent();
}
- private void Click_Delete(object sender, RoutedEventArgs e)
+ private void Click_TestConnection(object sender, RoutedEventArgs e)
{
- if (((FrameworkElement)sender).DataContext is EmulatorMapping mapping)
+ SettingsViewModel.Instance.TestConnection(true);
+ e.Handled = true;
+ }
+
+ private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
+ {
+ try
{
- var res = SettingsViewModel.Instance.PlayniteAPI.Dialogs.ShowMessage(string.Format("Delete this mapping?\r\n\r\n{0}", mapping.GetDescriptionLines().Aggregate((a, b) => $"{a}{Environment.NewLine}{b}")), "Confirm delete", MessageBoxButton.YesNo);
- if (res == MessageBoxResult.Yes)
+ if (e.Uri.Scheme == Uri.UriSchemeHttp || e.Uri.Scheme == Uri.UriSchemeHttps)
{
- SettingsViewModel.Instance.Mappings.Remove(mapping);
+ var psi = new ProcessStartInfo
+ {
+ FileName = e.Uri.AbsoluteUri,
+ UseShellExecute = true
+ };
+ Process.Start(psi);
}
}
- }
-
- private void Click_BrowseDestination(object sender, RoutedEventArgs e)
- {
- var mapping = ((FrameworkElement)sender).DataContext as EmulatorMapping;
- string path;
- if ((path = GetSelectedFolderPath()) == null) return;
- var playnite = SettingsViewModel.Instance.PlayniteAPI;
- if (playnite.Paths.IsPortable)
+ catch (Exception ex)
{
- path = path.Replace(playnite.Paths.ApplicationPath, Playnite.SDK.ExpandableVariables.PlayniteDirectory);
+ System.Diagnostics.Debug.WriteLine($"Failed to open URL: {ex.Message}");
}
-
- mapping.DestinationPath = path;
+ e.Handled = true;
}
- private async void Click_TestConnection(object sender, RoutedEventArgs e)
+ private async void Click_PullPlatforms(object sender, RoutedEventArgs e)
{
- var settings = SettingsViewModel.Instance;
- var dialogs = settings.PlayniteAPI.Dialogs;
+ SettingsViewModel.Instance.Notify = false;
- var host = settings.RomMHost?.Trim().TrimEnd('/');
- if (string.IsNullOrWhiteSpace(host))
+ try
{
- dialogs.ShowMessage("RomM Host is empty.", "RomM");
- return;
- }
+ HttpResponseMessage response = await HttpClientSingleton.Instance.GetAsync($"{SettingsViewModel.Instance.RomMHost}/api/platforms");
+ response.EnsureSuccessStatusCode();
- if (!settings.HasAnyAuth)
+ string body = await response.Content.ReadAsStringAsync();
+ SettingsViewModel.Instance.RomMPlatforms = JsonConvert.DeserializeObject>(body);
+ SettingsViewModel.Instance.UpdateNotifcationBar("Platforms successfully retrieved!");
+ }
+ catch (Exception ex)
{
- dialogs.ShowMessage("Provide either a valid API Token or username and password.", "RomM");
- return;
+ LogManager.GetLogger().Error($"RomM - failed to get platforms: {ex}");
+ SettingsViewModel.Instance.UpdateNotifcationBar($"Failed to get platforms: {ex.Message}!", true);
}
+ }
- var button = (Button)sender;
- var originalContent = button.Content;
+ private void Click_AddMapping(object sender, RoutedEventArgs e)
+ {
+ SettingsViewModel.Instance.Mappings.Add(new EmulatorMapping(SettingsViewModel.Instance.RomMPlatforms));
+ }
- try
+ private void Click_Delete(object sender, RoutedEventArgs e)
+ {
+ if (((FrameworkElement)sender).DataContext is EmulatorMapping mapping)
{
- button.IsEnabled = false;
- button.Content = "Testing...";
-
- using (var req = new HttpRequestMessage(HttpMethod.Get, $"{host}/api/users/me"))
+ var res = SettingsViewModel.Instance.PlayniteAPI.Dialogs.ShowMessage(string.Format("Delete this mapping?\r\n\r\n{0}", mapping.GetDescriptionLines().Aggregate((a, b) => $"{a}{Environment.NewLine}{b}")), "Confirm delete", MessageBoxButton.YesNo);
+ if (res == MessageBoxResult.Yes)
{
- req.Headers.Authorization = HttpClientSingleton.BuildAuthHeader(settings);
- using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)))
- using (var resp = await HttpClientSingleton.Instance.SendAsync(req, cts.Token))
- {
- if (resp.IsSuccessStatusCode)
- {
- dialogs.ShowMessage($"Connection successful ({(int)resp.StatusCode}).", "RomM");
- }
- else if (resp.StatusCode == HttpStatusCode.Unauthorized || resp.StatusCode == HttpStatusCode.Forbidden)
- {
- dialogs.ShowMessage($"Authentication rejected (HTTP {(int)resp.StatusCode}). Check your API token or username/password.", "RomM");
- }
- else
- {
- dialogs.ShowMessage($"Connection failed: HTTP {(int)resp.StatusCode} {resp.ReasonPhrase}", "RomM");
- }
- }
+ SettingsViewModel.Instance.Mappings.Remove(mapping);
}
}
- catch (OperationCanceledException)
- {
- dialogs.ShowMessage("Connection timed out after 10 seconds.", "RomM");
- }
- catch (HttpRequestException ex)
- {
- dialogs.ShowMessage($"Connection failed: {ex.Message}", "RomM");
- }
- catch (Exception ex)
- {
- dialogs.ShowMessage($"Unexpected error: {ex.Message}", "RomM");
- }
- finally
- {
- button.IsEnabled = true;
- button.Content = originalContent;
- }
}
- private void Click_Browse7zDestination(object sender, RoutedEventArgs e)
+ private void Click_BrowseDestination(object sender, RoutedEventArgs e)
{
+ var mapping = ((FrameworkElement)sender).DataContext as EmulatorMapping;
string path;
- if ((path = SettingsViewModel.Instance.PlayniteAPI.Dialogs.SelectFile("7Zip Executable|7z.exe")) == null) return;
+ if ((path = GetSelectedFolderPath()) == null) return;
+ var playnite = SettingsViewModel.Instance.PlayniteAPI;
+ if (playnite.Paths.IsPortable)
+ {
+ path = path.Replace(playnite.Paths.ApplicationPath, Playnite.SDK.ExpandableVariables.PlayniteDirectory);
+ }
- SettingsViewModel.Instance.PathTo7z = path;
+ mapping.DestinationPath = path;
}
private static string GetSelectedFolderPath()
@@ -143,27 +126,15 @@ private void DataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventA
private void DataGrid_CurrentCellChanged(object sender, EventArgs e)
{
-
+
}
- private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
+ private void Click_Browse7zDestination(object sender, RoutedEventArgs e)
{
- try
- {
- if (e.Uri.Scheme == Uri.UriSchemeHttp || e.Uri.Scheme == Uri.UriSchemeHttps)
- {
- var psi = new ProcessStartInfo
- {
- FileName = e.Uri.AbsoluteUri,
- UseShellExecute = true
- };
- Process.Start(psi);
- }
- }
- catch (Exception ex)
- {
- System.Diagnostics.Debug.WriteLine($"Failed to open URL: {ex.Message}");
- }
+ string path;
+ if ((path = SettingsViewModel.Instance.PlayniteAPI.Dialogs.SelectFile("7Zip Executable|7z.exe")) == null) return;
+
+ SettingsViewModel.Instance.PathTo7z = path;
e.Handled = true;
}
}
diff --git a/profile.png b/profile.png
new file mode 100644
index 0000000..883f997
Binary files /dev/null and b/profile.png differ