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..6a9ac5c
--- /dev/null
+++ b/Games/RomMImport.cs
@@ -0,0 +1,536 @@
+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);
+ }
+
+ // Sort files by how many folders deep the file is and return the file that is at the highest point
+ 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.Id}{ROM.FileNameNoExt}";
+
+ 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 part 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.FileNameNoExt;
+
+ 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 losing 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)
+ {
+ Version versionParsed = new Version(_plugin.Settings.ServerVersion);
+
+ 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.CompareTo(new Version(4,8)) < 0 ?
+ _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.CompareTo(new Version(4, 8)) < 0 ?
+ _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);
+ }
+ }
+ }
+
+ // Apply isSelected to data that is about to be saved
+ if (File.Exists($"{_plugin.ROMDataPath}{ROM.SHA1}.json"))
+ {
+ try
+ {
+ string localROMjson = File.ReadAllText($"{_plugin.ROMDataPath}{ROM.SHA1}.json");
+ var localROM = JsonConvert.DeserializeObject(localROMjson);
+
+ // Check to see if game is installed but no revision is selected!
+ var game = _plugin.PlayniteApi.Database.Games.FirstOrDefault(x => x.GameId == $"{ROM.Id}:{ROM.SHA1}");
+ if(localROM.ROMVersions.All(x => !x.IsSelected) && game != null && game.IsInstalled)
+ {
+ if(localROM.ROMVersions.Count <= 1)
+ {
+ var matchedRevision = toSave.ROMVersions.FirstOrDefault(x => x.Id == localROM.ROMVersions[0].Id);
+
+ if (matchedRevision != null)
+ {
+ matchedRevision.IsSelected = true;
+ matchedRevision.Save = localROM.ROMVersions[0].Save;
+ }
+ }
+ else
+ {
+ foreach (var revision in localROM.ROMVersions)
+ {
+ var dstPath = _mapping.DestinationPathResolved;
+ var installDir = Path.Combine(dstPath, Path.GetFileNameWithoutExtension(revision.FileName));
+
+ if (Directory.Exists(installDir))
+ {
+ var matchedRevision = toSave.ROMVersions.FirstOrDefault(x => x.Id == revision.Id);
+
+ if (matchedRevision != null)
+ {
+ matchedRevision.IsSelected = true;
+ matchedRevision.Save = revision.Save;
+ break;
+ }
+ }
+
+ }
+ }
+ }
+ else // Apply isSelected and Save data to revisions with matching ID
+ {
+ foreach (var revision in localROM.ROMVersions)
+ {
+ var matchedRevision = toSave.ROMVersions.FirstOrDefault(x => x.Id == revision.Id);
+
+ if (matchedRevision != null)
+ {
+ matchedRevision.IsSelected = revision.IsSelected;
+ matchedRevision.Save = revision.Save;
+ }
+
+ }
+ }
+ }
+ catch (Exception)
+ {
+ _plugin.Logger.Error($"{ROM.Name} GameID is malformed or {ROM.SHA1} json file is corrupted!");
+ }
+ }
+
+ // 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..ec2fea8
--- /dev/null
+++ b/Games/RomMImportController.cs
@@ -0,0 +1,232 @@
+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);
+ }
+
+ _plugin.Settings.SaveController.ReloadROMs();
+ 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..e4d34c1
--- /dev/null
+++ b/Models/RomM/Rom/RomMRomLocal.cs
@@ -0,0 +1,43 @@
+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 class 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 bool NeverSave { get; set; } = false;
+ public RomMSave Save { 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/Rom/RomMSave.cs b/Models/RomM/Rom/RomMSave.cs
new file mode 100644
index 0000000..e1b4a89
--- /dev/null
+++ b/Models/RomM/Rom/RomMSave.cs
@@ -0,0 +1,160 @@
+using Newtonsoft.Json;
+using Playnite.SDK.Models;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web;
+
+namespace RomM.Models.RomM.Rom
+{
+ public enum SaveSyncStatus
+ {
+ NotEnabled,
+ RemoteNewer,
+ LocalNewer,
+ InSync,
+ NotUploaded
+ }
+
+ public class RomMSave : ObservableObject
+ {
+ [JsonIgnore] private int _id;
+ [JsonIgnore] private int _romID;
+ [JsonIgnore] private string _fileName;
+ [JsonIgnore] private long _fileSize;
+ [JsonIgnore] private bool _missingFromFS;
+ [JsonIgnore] private DateTime _lastUpdated;
+ [JsonIgnore] private string _saveFolder;
+ [JsonIgnore] private bool _syncEnabled = false;
+ [JsonIgnore] private SaveSyncStatus _isInSync = SaveSyncStatus.NotEnabled;
+ [JsonIgnore] private string _gameName;
+
+
+ [JsonProperty("id")]
+ public int ID
+ {
+ get => _id;
+ set
+ {
+ _id = value;
+ OnPropertyChanged();
+ }
+ }
+
+ [JsonProperty("rom_id")]
+ public int ROMID
+ {
+ get => _romID;
+ set
+ {
+ _romID = value;
+ OnPropertyChanged();
+ }
+ }
+
+ [JsonProperty("file_name")]
+ public string FileName
+ {
+ get => _fileName;
+ set
+ {
+ _fileName = value;
+ OnPropertyChanged();
+ }
+ }
+
+ [JsonProperty("file_size_bytes")]
+ public long FileSize
+ {
+ get => _fileSize;
+ set
+ {
+ _fileSize = value;
+ OnPropertyChanged();
+ }
+ }
+
+ [JsonProperty("missing_from_fs")]
+ public bool MissingFromFS
+ {
+ get => _missingFromFS;
+ set
+ {
+ _missingFromFS = value;
+ OnPropertyChanged();
+ }
+ }
+
+ [JsonProperty("updated_at")]
+ public DateTime LastUpdated
+ {
+ get => _lastUpdated;
+ set
+ {
+ // Round date time to the second
+ _lastUpdated = value.AddTicks(-(value.Ticks % TimeSpan.TicksPerSecond));
+ OnPropertyChanged();
+ }
+ }
+
+ public string SaveFolder
+ {
+ get => _saveFolder;
+ set
+ {
+ if(value == null)
+ _saveFolder = "";
+ else
+ _saveFolder = value.TrimEnd('/');
+
+ OnPropertyChanged();
+ }
+ }
+ public bool SyncEnabled
+ {
+ get => _syncEnabled;
+ set
+ {
+ _syncEnabled = value;
+ OnPropertyChanged();
+ }
+ }
+ public SaveSyncStatus IsInSync
+ {
+ get => _isInSync;
+ set
+ {
+ _isInSync = value;
+ OnPropertyChanged();
+ }
+ }
+
+ [JsonIgnore] public string GameName
+ {
+ get => _gameName;
+ set
+ {
+ _gameName = value;
+ OnPropertyChanged();
+ }
+ }
+ [JsonIgnore] public string LastUpdatedString
+ {
+ get => $"(Last Updated: {LastUpdated})";
+ set
+ {
+ OnPropertyChanged();
+ }
+ }
+ }
+ public class PossibleSave
+ {
+ public FileInfo File { get; set; }
+ public RomMRevision Game { get; set; }
+ public bool IsSelected { 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..c025ab9 100644
--- a/RomM.cs
+++ b/RomM.cs
@@ -1,20 +1,19 @@
using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
+
using Playnite.SDK;
using Playnite.SDK.Events;
using Playnite.SDK.Models;
using Playnite.SDK.Plugins;
-using RomM.Games;
+
using RomM.Downloads;
-using RomM.VersionSelector;
+using RomM.Games;
using RomM.Models.RomM.Collection;
-using RomM.Models.RomM.Platform;
using RomM.Models.RomM.Rom;
using RomM.Settings;
+using RomM.VersionSelector;
+
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 +22,6 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using System.Web;
using System.Windows;
using System.Windows.Controls;
@@ -39,27 +37,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,12 +69,11 @@ 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;
@@ -101,9 +85,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 +103,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()
- {
- 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)
+ #region Helper functions
+ public string CombineUrl(string baseUrl, string relativePath)
{
- 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 +147,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 +177,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 +245,27 @@ public override void OnApplicationStarted(OnApplicationStartedEventArgs args)
{
if (item.PluginId == PluginId)
{
- var version = item.Version;
- if (version == null || !version.StartsWith("RomM:"))
+ if (item.GameId.Contains(':'))
{
- Logger.Warn($"Couldn't find RomMId for {item.Name}.");
- continue;
- }
-
- int romMId;
- if (!int.TryParse(version.Split(':')[1], out romMId))
- {
- 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 +300,284 @@ 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)
+ if (Settings.MergeRevisions && File.Exists($"{ROMDataPath}{args.Games.First().GameId.Split(':')[1]}.json") && args.Games.First().IsInstalled)
{
- Logger.Warn($"Emulator profile {mapping.EmulatorProfileId} for emulator {mapping.EmulatorId} not found, skipping.");
- continue;
- }
-
- if (mapping.Platform == null)
- {
- 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;
+ if (romData.Id == (int)InstallStatus.Cancelled)
+ yield return new RomMInstallController(args.Game, this, romData);
- var completionStatusMap = PlayniteApi.Database.CompletionStatuses.ToDictionary(cs => cs.Name, cs => cs.Id);
-
- 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)
+ RomMVersionSelector VersionSelectorControl = new RomMVersionSelector(gameData.ROMVersions);
+ var window = Playnite.Dialogs.CreateWindow(new WindowCreationOptions
{
- bool foundSibling = false;
+ ShowMinimizeButton = false,
+ ShowMaximizeButton = false,
+ ShowCloseButton = false,
+ });
- foreach (var sibling in item.Siblings)
- {
- if (ImportedROMsWithSiblings.Contains(sibling.Id))
- {
- foundSibling = true;
- break;
- }
- }
+ window.Height = 215;
+ window.Width = 600;
- if (foundSibling)
- continue;
- }
+ 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;
- 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;
+ window.ShowDialog();
- // 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))
+ if (VersionSelectorControl.Cancelled)
{
- Logger.Warn($"Rom {item.Id} returned empty/invalid filename, skipping.");
- continue;
+ romData.Id = (int)InstallStatus.Cancelled;
}
-
- 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)
+ else
{
- 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)
+ // Uninstall old ROM before installing new one
+ if (args.Game.IsInstalled)
{
- var siblingItem = allRoms.Find(x => x.Id == sibling.Id);
-
- if (siblingItem == null)
- {
- Logger.Error($"Unable to find sibling data for id:{sibling.Id}");
- continue;
- }
+ Playnite.UninstallGame(args.Game.Id);
- 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;
- }
-
- sibling.FileName = siblingfileName;
- sibling.DownloadURL = CombineUrl(Settings.RomMHost, $"api/roms/{sibling.Id}/content/{siblingfileName}");
- sibling.HasMultipleFiles = siblingItem.HasMultipleFiles;
-
- gameInfos.Add(sibling);
+ args.Game.IsInstalling = true;
+ Playnite.Database.Games.Update(args.Game);
}
- File.WriteAllText($"{ROMsWithSiblingsPath}{item.Id}.json", JsonConvert.SerializeObject(gameInfos));
- ImportedROMsWithSiblings.Add(item.Id);
- }
-
- string completionStatus;
- // Determine status in Playnite. Backlogged and "now playing" take precedent over the status options
- if (item.RomUser.Backlogged || item.RomUser.NowPlaying)
- {
- completionStatus = item.RomUser.NowPlaying ? RomMRomUser.CompletionStatusMap["now_playing"] : RomMRomUser.CompletionStatusMap["backlogged"];
- }
- 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)
- {
- 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}";
+ 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();
- ignoredGameIds.TryAdd(game.Id, 0);
- Playnite.Database.Games.Update(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)
+ else
{
- if (args.CancelToken.IsCancellationRequested)
- break;
-
- if (responseGameIDs.Contains(game.GameId))
- {
- continue;
- }
-
- Playnite.Database.Games.Remove(game.Id);
+ gameData.ROMVersions[0].IsSelected = true;
}
- Logger.Debug($"Finished removing not found games for {apiPlatform.Name}");
- }
- catch (HttpRequestException e)
- {
- Logger.Error($"Request exception: {e.Message}");
- return games;
+ File.WriteAllText($"{ROMDataPath}{romMSHA1}.json", JsonConvert.SerializeObject(gameData));
}
- }
- 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);
}
- public override IEnumerable GetGameMenuItems(GetGameMenuItemsArgs args)
+ public override void OnGameStarting(OnGameStartingEventArgs args)
{
- List gameMenuItems = new List();
-
- if (args.Games.First().PluginId == PluginId)
+ if(args.Game.PluginId == PluginId)
{
- 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;
- }
-
- 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);
- }
- });
- }
+ Settings.SaveController.GameLaunched(args.Game);
}
- return gameMenuItems;
}
- public override IEnumerable GetInstallActions(GetInstallActionsArgs args)
+ public override void OnGameStopped(OnGameStoppedEventArgs args)
{
- if (args.Game.PluginId == Id)
+ if (args.Game.PluginId == PluginId)
{
- 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);
- }
+ Settings.SaveController.GameStopped();
+ }
+ }
- hasSiblings = true;
- siblingID = VersionSelectorControl.Siblings.Where(x => x.isSelected).First().Id;
+ #endregion
- //Write result back to json file
- siblingInfos = VersionSelectorControl.Siblings.ToList();
- File.WriteAllText($"{ROMsWithSiblingsPath}{romMId}.json", JsonConvert.SerializeObject(siblingInfos));
- }
- }
+ #region RomM Status Syncing
+ public 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();
- yield return args.Game.GetRomMGameInfo().GetInstallController(args.Game, this, hasSiblings, siblingID);
+ // 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();
}
}
-
- public override IEnumerable GetUninstallActions(GetUninstallActionsArgs args)
+ internal RomMCollection CreateFavorites()
{
- if (args.Game.PluginId == Id)
+ 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)
{
- 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 +601,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 +665,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..ec4f28e 100644
--- a/RomM.csproj
+++ b/RomM.csproj
@@ -54,11 +54,6 @@
-
-
- PreserveNewest
-
-
MSBuild:Compile
@@ -77,6 +72,14 @@
MSBuild:Compile
Designer
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
MSBuild:Compile
Designer
@@ -110,6 +113,12 @@
MSBuild:Compile
+
+ PreserveNewest
+
+
+ PreserveNewest
+
SettingsSingleFileGenerator
Settings.Designer.cs
diff --git a/Save/DeleteSave.xaml b/Save/DeleteSave.xaml
new file mode 100644
index 0000000..4848456
--- /dev/null
+++ b/Save/DeleteSave.xaml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Save/DeleteSave.xaml.cs b/Save/DeleteSave.xaml.cs
new file mode 100644
index 0000000..b9d25de
--- /dev/null
+++ b/Save/DeleteSave.xaml.cs
@@ -0,0 +1,62 @@
+using Playnite.SDK.Controls;
+
+using RomM.Models.RomM.Rom;
+using RomM.Settings;
+
+using System.Collections.ObjectModel;
+using System.Windows;
+
+namespace RomM.Save
+{
+ public partial class RomMDeleteSaveView : PluginUserControl
+ {
+ public bool Local = false;
+ public bool Remote = false;
+
+ public RomMDeleteSaveView()
+ {
+ InitializeComponent();
+ }
+
+ private void Click_Cancel(object sender, RoutedEventArgs e)
+ {
+ ((Window)Parent).Close();
+ }
+
+ private void Click_LocalOnly(object sender, RoutedEventArgs e)
+ {
+ var res = SettingsViewModel.Instance.PlayniteAPI.Dialogs.ShowMessage("Delete save locally, Are you sure?", "Confirm delete", MessageBoxButton.YesNo);
+ if (res == MessageBoxResult.Yes)
+ {
+ Local = true;
+ ((Window)Parent).Close();
+ }
+
+
+ }
+
+ private void Click_RemoteOnly(object sender, RoutedEventArgs e)
+ {
+
+ var res = SettingsViewModel.Instance.PlayniteAPI.Dialogs.ShowMessage("Delete save from server, Are you sure?", "Confirm delete", MessageBoxButton.YesNo);
+ if (res == MessageBoxResult.Yes)
+ {
+ Remote = true;
+ ((Window)Parent).Close();
+ }
+ }
+
+ private void Click_Delete(object sender, RoutedEventArgs e)
+ {
+
+ var res = SettingsViewModel.Instance.PlayniteAPI.Dialogs.ShowMessage("Delete save completely, Are you sure?", "Confirm delete", MessageBoxButton.YesNo);
+ if (res == MessageBoxResult.Yes)
+ {
+ Local = true;
+ Remote = true;
+ ((Window)Parent).Close();
+ }
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Save/SaveController.cs b/Save/SaveController.cs
new file mode 100644
index 0000000..af5b5a0
--- /dev/null
+++ b/Save/SaveController.cs
@@ -0,0 +1,836 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Playnite.SDK;
+using Playnite.SDK.Models;
+using Playnite.SDK.Plugins;
+using RomM.Games;
+using RomM.Models.RomM.Rom;
+using RomM.Settings;
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics.Eventing.Reader;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Windows;
+
+namespace RomM.Save
+{
+
+ public class SaveController : ObservableObject
+ {
+ private readonly RomM Plugin;
+ private SettingsViewModel Settings;
+ private RomMRomLocal LaunchedGame;
+ private List PossibleSaveFiles;
+
+ private EmulatorMapping _currentMapping;
+ private ObservableCollection _remoteSaves = new ObservableCollection();
+ private ObservableCollection _localSaves = new ObservableCollection();
+ private ObservableCollection _possibleSaves = new ObservableCollection();
+
+ public List ROMs { get; set; }
+
+ public ObservableCollection Mappings
+ {
+ get => Settings.Mappings;
+ set
+ {
+ OnPropertyChanged();
+ }
+ }
+ public EmulatorMapping CurrentMapping
+ {
+ get => _currentMapping;
+ set
+ {
+ SettingsViewModel.Instance.Notify = false;
+
+ // Save save data to file
+ if (_currentMapping != null)
+ SaveROMRevisions(_currentMapping.MappingId);
+
+ _currentMapping = value;
+ OnPropertyChanged(nameof(FilteredROMs));
+ OnPropertyChanged();
+
+ // Pull mapping saves
+ if (_currentMapping != null)
+ {
+ SyncLocalSaves();
+ SyncPotentialSaves();
+ SyncRemoteSaves();
+ Settings.UpdateNotifcationBar($"Synced saves for {CurrentMapping.RomMPlatform.Name} with {RemoteSaves.Count + LocalSaves.Count} saves found!");
+ }
+
+ }
+ }
+
+ public ObservableCollection RemoteSaves
+ {
+ get => _remoteSaves;
+ set
+ {
+ _remoteSaves = value;
+ OnPropertyChanged();
+ }
+ }
+ public ObservableCollection LocalSaves
+ {
+ get => _localSaves;
+ set
+ {
+ _localSaves = value;
+ OnPropertyChanged();
+ }
+ }
+ public ObservableCollection PossibleSaves
+ {
+ get => _possibleSaves;
+ set
+ {
+ _possibleSaves = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public ObservableCollection FilteredROMs
+ {
+ get
+ {
+ if(ROMs != null && CurrentMapping != null)
+ {
+ return ROMs.Where(x => x.MappingID == CurrentMapping.MappingId).SelectMany(y => y.ROMVersions).OrderBy(z => z.FileName).ToObservable();
+ }
+ return new ObservableCollection();
+ }
+ set
+ {
+ OnPropertyChanged();
+ }
+ }
+
+ public SaveController(RomM plugin)
+ {
+ Plugin = plugin;
+ Settings = SettingsViewModel.Instance;
+ Mappings = Settings.Mappings;
+ ROMs = new List();
+
+ foreach (var game in Directory.EnumerateFiles(Plugin.ROMDataPath, "*.json", SearchOption.TopDirectoryOnly))
+ {
+ try
+ {
+ string json = File.ReadAllText(game);
+ var gamedata = JsonConvert.DeserializeObject(json);
+ ROMs.Add(gamedata);
+ }
+ catch (Exception ex)
+ {
+ Plugin.Logger.Error($"[Save Controller] Failed to read json file! - {game}\n\t{ex}");
+ }
+ }
+
+ }
+
+ public void ReloadROMs()
+ {
+ ROMs = new List();
+
+ foreach (var game in Directory.EnumerateFiles(Plugin.ROMDataPath, "*.json", SearchOption.TopDirectoryOnly))
+ {
+ try
+ {
+ string json = File.ReadAllText(game);
+ ROMs.Add(JsonConvert.DeserializeObject(json));
+ }
+ catch (Exception ex)
+ {
+ Plugin.Logger.Error($"[Save Controller] Failed to read json file! - {game}\n\t{ex}");
+ }
+ }
+ }
+
+ public void SyncRemoteSaves(bool manual = false)
+ {
+ try
+ {
+ if (!Uri.IsWellFormedUriString(Settings.RomMHost, UriKind.RelativeOrAbsolute))
+ throw new ArgumentException("Host is not a valid URL!");
+
+ if(CurrentMapping == null)
+ throw new ArgumentException("No mapping selected cannot sync save for mapping!");
+
+ var response = HttpClientSingleton.Instance.GetAsync($"{Settings.RomMHost}/api/saves?platform_id={CurrentMapping.RomMPlatformId}").GetAwaiter().GetResult();
+ response.EnsureSuccessStatusCode();
+
+ Stream body = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult();
+ using (StreamReader reader = new StreamReader(body))
+ {
+ var data = reader.ReadToEnd();
+ if (data == "[]")
+ {
+ RemoteSaves = new ObservableCollection();
+ return;
+ }
+
+ var jsonResponse = JArray.Parse(data);
+ RemoteSaves = jsonResponse.ToObject>();
+ }
+
+ var toremove = new List();
+
+ foreach (var rs in RemoteSaves)
+ {
+ // Remove remote save if stored locally
+ if (LocalSaves.Any(x => x.ID == rs.ID))
+ {
+ toremove.Add(rs);
+ continue;
+ }
+
+ RomMRevision rom = ROMs.Where(x => x.MappingID == CurrentMapping.MappingId).SelectMany(y => y.ROMVersions).First(z => z.Id == rs.ROMID);
+ if(rom != null)
+ {
+ if(rom.Save != null && rs.FileName == rom.Save.FileName)
+ {
+ rs.SyncEnabled = rom.Save.SyncEnabled;
+ if (rs.SyncEnabled)
+ {
+ rs.IsInSync = rs.LastUpdated > rom.Save.LastUpdated ? SaveSyncStatus.RemoteNewer :
+ (rs.LastUpdated < rom.Save.LastUpdated ? SaveSyncStatus.LocalNewer :
+ (rs.LastUpdated == rom.Save.LastUpdated ? SaveSyncStatus.InSync : SaveSyncStatus.NotEnabled));
+ }
+
+ rs.SaveFolder = rom.Save.SaveFolder;
+ }
+
+ rs.GameName = Path.GetFileNameWithoutExtension(rom.FileName);
+
+ }
+ }
+
+ foreach (var remove in toremove)
+ {
+ RemoteSaves.Remove(remove);
+ }
+
+ if(manual)
+ Settings.UpdateNotifcationBar($"Synced saves for {CurrentMapping.RomMPlatform.Name} with {RemoteSaves.Count} saves found!");
+
+ }
+ catch (Exception ex)
+ {
+ Plugin.Logger.Error($"[SaveController] {ex}");
+ Settings.UpdateNotifcationBar(ex.Message, true);
+ }
+ }
+ public void RemoteSaveEnabled(RomMSave save)
+ {
+ if(save.SyncEnabled)
+ {
+ RomMRevision rom = ROMs.Where(x => x.MappingID == CurrentMapping.MappingId).SelectMany(y => y.ROMVersions).First(z => z.Id == save.ROMID);
+ if (rom != null)
+ {
+ if (rom.Save != null)
+ {
+ RemoteSaves.First(x => x.ID == save.ID).IsInSync =
+ save.LastUpdated > rom.Save.LastUpdated ? SaveSyncStatus.RemoteNewer :
+ (save.LastUpdated < rom.Save.LastUpdated ? SaveSyncStatus.LocalNewer :
+ SaveSyncStatus.InSync);
+ }
+ else
+ {
+
+ RemoteSaves.First(x => x.ID == save.ID).IsInSync = SaveSyncStatus.RemoteNewer;
+ }
+
+ foreach (var saves in RemoteSaves.Where(x => x.ROMID == save.ROMID && x.ID != save.ID))
+ {
+ saves.SyncEnabled = false;
+ }
+ }
+
+ }
+ else
+ {
+ RemoteSaves.First(x => x.ID == save.ID).IsInSync = SaveSyncStatus.NotEnabled;
+ }
+ }
+
+ public void SyncLocalSaves()
+ {
+ ObservableCollection newlocalSaves = new ObservableCollection();
+ foreach (var rom in FilteredROMs)
+ {
+ if (rom.Save != null)
+ {
+ if (rom.Save.ID != -1)
+ {
+ var save = FetchSaveInfo(rom.Save.ID);
+
+ if (save != null)
+ {
+ rom.Save.IsInSync = save.LastUpdated > rom.Save.LastUpdated ? SaveSyncStatus.RemoteNewer :
+ (save.LastUpdated < rom.Save.LastUpdated ? SaveSyncStatus.LocalNewer :
+ SaveSyncStatus.InSync);
+ }
+ else
+ {
+ rom.Save.IsInSync = SaveSyncStatus.NotUploaded;
+ rom.Save.ID = -1;
+ }
+ }
+
+ rom.Save.GameName = Path.GetFileNameWithoutExtension(rom.FileName);
+
+ newlocalSaves.Add(rom.Save);
+ }
+ }
+
+ LocalSaves = newlocalSaves;
+ }
+ public void LocalSaveEnabled(RomMSave save)
+ {
+ if (save.SyncEnabled)
+ {
+ RomMRevision rom = ROMs.Where(x => x.MappingID == CurrentMapping.MappingId).SelectMany(y => y.ROMVersions).First(z => z.Id == save.ROMID);
+ if (rom != null)
+ {
+
+ var remotesave = FetchSaveInfo(rom.Save.ID);
+
+ if(remotesave != null)
+ {
+ rom.Save.IsInSync = remotesave.LastUpdated > rom.Save.LastUpdated ? SaveSyncStatus.RemoteNewer :
+ (remotesave.LastUpdated < rom.Save.LastUpdated ? SaveSyncStatus.LocalNewer :
+ SaveSyncStatus.InSync);
+ }
+ else
+ {
+ rom.Save.IsInSync = SaveSyncStatus.NotUploaded;
+ }
+ }
+
+ foreach (var saves in LocalSaves.Where(x => x.ROMID == save.ROMID && x.ID != save.ID))
+ {
+ saves.SyncEnabled = false;
+ }
+
+ }
+ else
+ {
+ LocalSaves.First(x => x.ID == save.ID).IsInSync = SaveSyncStatus.NotEnabled;
+ }
+ }
+
+ public void SyncPotentialSaves()
+ {
+ if (CurrentMapping != null && !string.IsNullOrEmpty(CurrentMapping.GeneralSavePath) && Directory.Exists(CurrentMapping.GeneralSavePath))
+ {
+ var newpossiblesaves = new ObservableCollection();
+ List fileExtensions = new List();
+
+ if (!string.IsNullOrEmpty(CurrentMapping.SaveFileExtensions))
+ {
+ if(CurrentMapping.SaveFileExtensions.Contains(';'))
+ {
+ fileExtensions = CurrentMapping.SaveFileExtensions.Split(';').ToList();
+ }
+ else
+ {
+ fileExtensions.Add(CurrentMapping.SaveFileExtensions);
+ }
+ }
+
+ foreach (var file in new DirectoryInfo(CurrentMapping.GeneralSavePath).GetFiles("*.*", SearchOption.AllDirectories))
+ {
+ var possiblesave = new PossibleSave();
+
+ if (fileExtensions.Count != 0)
+ {
+ if(fileExtensions.Any(x => file.Extension.TrimStart('.').ToLower() == x.ToLower()))
+ {
+ possiblesave.File = file;
+ }
+ else
+ {
+ continue;
+ }
+ }
+ else
+ {
+ possiblesave.File = file;
+ }
+
+ if(!FilteredROMs.Any(x => x.Save != null && x.Save.FileName == file.Name && x.Save.SaveFolder == file.DirectoryName))
+ {
+ newpossiblesaves.Add(possiblesave);
+ }
+ }
+
+ PossibleSaves = newpossiblesaves;
+
+ }
+ else
+ {
+ Settings.UpdateNotifcationBar("Current mapping has no general save path cannot find possible savefiles", true);
+ PossibleSaves = new ObservableCollection();
+ }
+ }
+ public void UploadNewSave(PossibleSave possiblesave)
+ {
+ if (possiblesave.Game != null)
+ {
+ var save = new RomMSave();
+ save.ROMID = possiblesave.Game.Id;
+ save.SaveFolder = possiblesave.File.DirectoryName;
+ save.FileName = possiblesave.File.Name;
+ save.LastUpdated = possiblesave.File.LastWriteTime;
+ save.IsInSync = SaveSyncStatus.LocalNewer;
+ save.SyncEnabled = true;
+
+ UploadSave(save, CurrentMapping.MappingId, true);
+ PossibleSaves.Remove(possiblesave);
+ SyncLocalSaves();
+ }
+ else
+ {
+ Settings.UpdateNotifcationBar("No game selected cannot upload!", true);
+ }
+ }
+
+ public void SaveROMRevisions(Guid mappingId)
+ {
+ foreach (var rom in ROMs.Where(x => x.MappingID == mappingId))
+ {
+ foreach (var revision in rom.ROMVersions)
+ {
+ if (revision.Save == null)
+ {
+ RomMSave save = RemoteSaves.FirstOrDefault(x => x.SyncEnabled && x.ROMID == revision.Id);
+ if (save == null)
+ continue;
+
+ revision.Save = save;
+ }
+ }
+
+ File.WriteAllText($"{Plugin.ROMDataPath}{rom.SHA1}.json", JsonConvert.SerializeObject(rom));
+ }
+ }
+
+ public void GameLaunched(Game game)
+ {
+ string romMSHA1 = game.GameId.Split(':')[1];
+ if (!File.Exists($"{Plugin.ROMDataPath}{romMSHA1}.json"))
+ {
+ Plugin.Logger.Error($"{game.Name} GameID is malformed!");
+ }
+
+ try
+ {
+ string json = File.ReadAllText($"{Plugin.ROMDataPath}{romMSHA1}.json");
+ LaunchedGame = JsonConvert.DeserializeObject(json);
+ }
+ catch (Exception)
+ {
+ Plugin.Logger.Error($"{game.Name} GameID is malformed or {romMSHA1} json file is corrupted!");
+ }
+
+ // Check to see if save needs syncing
+ var mapping = Settings.Mappings.FirstOrDefault(x => x.MappingId == LaunchedGame.MappingID);
+ if (mapping != null && mapping.DownloadSaveBeforeGame)
+ {
+ var rom = LaunchedGame.ROMVersions.FirstOrDefault(x => x.IsSelected);
+ if(rom != null && rom.Save != null)
+ {
+ var save = FetchSaveInfo(rom.Save.ID);
+ rom.Save.IsInSync = save.LastUpdated > rom.Save.LastUpdated ? SaveSyncStatus.RemoteNewer :
+ (save.LastUpdated < rom.Save.LastUpdated ? SaveSyncStatus.LocalNewer :
+ SaveSyncStatus.InSync);
+
+
+ // Check to see if save has been deleted or a newer save is on the server
+ if (rom.Save.IsInSync == SaveSyncStatus.RemoteNewer || !File.Exists($"{rom.Save.SaveFolder}/{rom.Save.FileName}"))
+ {
+ rom.Save.IsInSync = SaveSyncStatus.RemoteNewer;
+ SyncSave(rom.Save, LaunchedGame.MappingID);
+ }
+ }
+
+ PossibleSaveFiles = new DirectoryInfo(mapping.GeneralSavePath).GetFiles("*.*", SearchOption.AllDirectories).ToList();
+ }
+ }
+ public void GameStopped()
+ {
+ var mapping = Settings.Mappings.FirstOrDefault(x => x.MappingId == LaunchedGame.MappingID);
+ if (mapping != null && mapping.UploadSaveAfterGame)
+ {
+ var rom = LaunchedGame.ROMVersions.FirstOrDefault(x => x.IsSelected);
+
+ if (rom != null && !rom.NeverSave)
+ {
+ if (rom.Save != null)
+ {
+ rom.Save.IsInSync = SaveSyncStatus.LocalNewer;
+ Settings.SaveController.SyncSave(rom.Save, LaunchedGame.MappingID);
+ }
+ else
+ {
+
+ if(PossibleSaveFiles.Count > 0)
+ {
+ var files = new DirectoryInfo(mapping.GeneralSavePath).GetFiles("*.*", SearchOption.AllDirectories);
+ var saveFiles = new ObservableCollection();
+ string[] fileExtensions = new string[0];
+
+ if (!string.IsNullOrEmpty(CurrentMapping.SaveFileExtensions))
+ {
+ if (CurrentMapping.SaveFileExtensions.TrimEnd(';').Contains(';'))
+ {
+ fileExtensions = CurrentMapping.SaveFileExtensions.Split(';');
+ }
+ else
+ {
+ fileExtensions = new string[1];
+ fileExtensions[0] = CurrentMapping.SaveFileExtensions;
+ }
+ }
+
+ foreach (var file in files)
+ {
+ // Check for new or updated possible save files
+ if(!PossibleSaveFiles.Contains(file) || PossibleSaveFiles.Find(x => x.FullName == file.FullName).LastWriteTime != file.LastWriteTime)
+ {
+ // If mapping has set file Extensions filter out files that don't have that extention
+ if(fileExtensions.Length != 0)
+ {
+ if (fileExtensions.Any(x => Path.GetExtension(file.FullName) == x))
+ {
+ var save = new PossibleSave();
+ save.File = file;
+ saveFiles.Add(save);
+ }
+ }
+ else
+ {
+ var save = new PossibleSave();
+ save.File = file;
+ saveFiles.Add(save);
+ }
+
+
+ }
+ }
+
+ if (saveFiles.Count > 0)
+ {
+ RomMSaveSelector saveSelectorControl = new RomMSaveSelector(saveFiles);
+ var window = Plugin.Playnite.Dialogs.CreateWindow(new WindowCreationOptions
+ {
+ ShowMinimizeButton = false,
+ ShowMaximizeButton = false,
+ ShowCloseButton = false,
+ });
+
+ window.Height = 215;
+ window.Width = 600;
+
+ window.Title = "Select save!";
+ window.ShowInTaskbar = false;
+ window.ResizeMode = ResizeMode.NoResize;
+ window.Owner = API.Instance.Dialogs.GetCurrentAppWindow();
+ window.WindowStartupLocation = WindowStartupLocation.CenterOwner;
+ window.Content = saveSelectorControl;
+
+ window.ShowDialog();
+
+ if (saveSelectorControl.Cancelled)
+ {
+ return;
+ }
+ else if (saveSelectorControl.NeverSave)
+ {
+ rom.NeverSave = true;
+ File.WriteAllText($"{Plugin.ROMDataPath}{LaunchedGame.SHA1}.json", JsonConvert.SerializeObject(LaunchedGame));
+ }
+ else
+ {
+ var selectedfile = saveSelectorControl.Saves.FirstOrDefault(x => x.IsSelected);
+
+ var save = new RomMSave();
+ save.FileName = selectedfile.File.Name;
+ save.SaveFolder = selectedfile.File.DirectoryName;
+ save.ROMID = rom.Id;
+ save.IsInSync = SaveSyncStatus.LocalNewer;
+ save.SyncEnabled = true;
+
+ UploadSave(save, mapping.MappingId, true);
+ }
+ }
+
+ }
+
+
+
+
+ }
+ }
+ }
+ }
+
+ public void SyncSave(RomMSave save, Guid mappingID)
+ {
+ switch (save.IsInSync)
+ {
+ case SaveSyncStatus.RemoteNewer:
+ DownloadSave(save, mappingID);
+ break;
+
+ case SaveSyncStatus.LocalNewer:
+ UploadSave(save, mappingID);
+ break;
+
+ case SaveSyncStatus.NotUploaded:
+ UploadSave(save, mappingID, true);
+ break;
+
+ default:
+ break;
+ }
+ }
+ public void RemoveSaveEntry(RomMSave save)
+ {
+ var rom = ROMs.Where(x => x.MappingID == CurrentMapping.MappingId).SelectMany(y => y.ROMVersions).First(z => z.Id == save.ROMID);
+ if (rom != null)
+ {
+ rom.Save = null;
+ }
+
+ SyncLocalSaves();
+ SyncPotentialSaves();
+ SyncRemoteSaves();
+ SaveROMRevisions(CurrentMapping.MappingId);
+ }
+ public void DeleteSave(RomMSave save)
+ {
+ RomMDeleteSaveView savedeleteControl = new RomMDeleteSaveView();
+ var window = Plugin.Playnite.Dialogs.CreateWindow(new WindowCreationOptions
+ {
+ ShowMinimizeButton = false,
+ ShowMaximizeButton = false,
+ ShowCloseButton = false,
+ });
+
+ window.Height = 100;
+ window.Width = 500;
+
+ window.Title = "Delete save!";
+ window.ShowInTaskbar = false;
+ window.ResizeMode = ResizeMode.NoResize;
+ window.Owner = API.Instance.Dialogs.GetCurrentAppWindow();
+ window.WindowStartupLocation = WindowStartupLocation.CenterOwner;
+ window.Content = savedeleteControl;
+
+ window.ShowDialog();
+
+ var rom = ROMs.Where(x => x.MappingID == CurrentMapping.MappingId).SelectMany(y => y.ROMVersions).First(z => z.Id == save.ROMID);
+
+ if(savedeleteControl.Remote || savedeleteControl.Local)
+ {
+ if (savedeleteControl.Remote)
+ {
+ try
+ {
+ if (!Uri.IsWellFormedUriString(Settings.RomMHost, UriKind.RelativeOrAbsolute))
+ throw new ArgumentException("Host is not a valid URL!");
+
+ var saves = new List();
+ saves.Add(rom.Save.ID);
+ var jsonsaves = JsonConvert.SerializeObject(saves);
+
+ var response = HttpClientSingleton.Instance.PostAsync($"{Settings.RomMHost}/api/saves/delete", new StringContent(jsonsaves)).GetAwaiter().GetResult();
+ response.EnsureSuccessStatusCode();
+ }
+ catch (Exception ex)
+ {
+ Settings.UpdateNotifcationBar(ex.Message, true);
+ Plugin.Logger.Error(ex.ToString());
+ }
+
+ rom.Save.IsInSync = SaveSyncStatus.NotUploaded;
+ }
+
+ if (savedeleteControl.Local)
+ {
+ File.Delete($"{rom.Save.SaveFolder}/{rom.Save.FileName}");
+ rom.Save = null;
+ }
+
+ SyncLocalSaves();
+ SyncRemoteSaves();
+ }
+
+ SaveROMRevisions(CurrentMapping.MappingId);
+ }
+ private RomMSave FetchSaveInfo(int id)
+ {
+ try
+ {
+ if (!Uri.IsWellFormedUriString(Settings.RomMHost, UriKind.RelativeOrAbsolute))
+ throw new ArgumentException("Host is not a valid URL!");
+
+ var response = HttpClientSingleton.Instance.GetAsync($"{Settings.RomMHost}/api/saves/{id}").GetAwaiter().GetResult();
+ response.EnsureSuccessStatusCode();
+
+ Stream body = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult();
+ using (StreamReader reader = new StreamReader(body))
+ {
+ var data = reader.ReadToEnd();
+ var jsonResponse = JObject.Parse(data);
+ return jsonResponse.ToObject();
+ }
+ }
+ catch (Exception ex)
+ {
+ Plugin.Logger.Error($"[SaveController] {ex}");
+ Settings.UpdateNotifcationBar(ex.Message, true);
+ return null;
+ }
+ }
+ private void DownloadSave(RomMSave save, Guid mappingID)
+ {
+ SettingsViewModel.Instance.Notify = false;
+
+ try
+ {
+ if (!Uri.IsWellFormedUriString(Settings.RomMHost, UriKind.RelativeOrAbsolute))
+ throw new ArgumentException("Host is not a valid URL!");
+
+ if (string.IsNullOrEmpty(Mappings.First(x => x.MappingId == mappingID).GeneralSavePath))
+ throw new ArgumentException($"{Mappings.First(x => x.MappingId == mappingID).MappingName} has no general save location set, skipping save download!");
+
+ if (string.IsNullOrEmpty(save.SaveFolder))
+ save.SaveFolder = Mappings.First(x => x.MappingId == mappingID).GeneralSavePath;
+
+ var response = HttpClientSingleton.Instance.GetAsync($"{Settings.RomMHost}/api/saves/{save.ID}/content").GetAwaiter().GetResult();
+ response.EnsureSuccessStatusCode();
+
+ var body = response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult();
+ File.WriteAllBytes($"{save.SaveFolder}/{save.FileName}", body);
+ save.IsInSync = SaveSyncStatus.InSync;
+
+ var rom = ROMs.Where(x => x.MappingID == mappingID).SelectMany(y => y.ROMVersions).First(z => z.Id == save.ROMID);
+ rom.Save = save;
+
+ string filesizestring;
+
+ if (rom.Save.FileSize > 1000000)
+ filesizestring = $"({((float)rom.Save.FileSize) / 1000 / 1000}MB)";
+ else if (rom.Save.FileSize > 1000)
+ filesizestring = $"({((float)rom.Save.FileSize) / 1000}KB)";
+ else
+ filesizestring = $"({rom.Save.FileSize}B)";
+
+ Plugin.PlayniteApi.Notifications.Add(new Playnite.SDK.NotificationMessage($"RomM.Save.Downloaded.{rom.Id}", $"Downloaded {save.FileName} from RomM Server! {filesizestring}", Playnite.SDK.NotificationType.Info));
+ Settings.UpdateNotifcationBar($"Downloaded {save.FileName} from RomM Server! {filesizestring}");
+
+ SyncLocalSaves();
+ SyncRemoteSaves();
+ SaveROMRevisions(mappingID);
+ }
+ catch (Exception ex)
+ {
+ Plugin.PlayniteApi.Notifications.Add(new Playnite.SDK.NotificationMessage($"RomM.Save.Downloaded.Failed", $"Save download failed: {ex.Message}", Playnite.SDK.NotificationType.Info));
+ Settings.UpdateNotifcationBar(ex.Message, true);
+ }
+ }
+ private void UploadSave(RomMSave save, Guid mappingID, bool NewSave = false, bool UpdateNotifBar = false)
+ {
+ SettingsViewModel.Instance.Notify = false;
+
+ var rom = ROMs.Where(x => x.MappingID == mappingID).SelectMany(y => y.ROMVersions).First(z => z.Id == save.ROMID);
+ HttpResponseMessage response = null;
+
+ try
+ {
+ if (!Uri.IsWellFormedUriString(Settings.RomMHost, UriKind.RelativeOrAbsolute))
+ throw new ArgumentException("Host is not a valid URL!");
+
+ if (!File.Exists($"{save.SaveFolder}/{save.FileName}"))
+ throw new ArgumentException("Save file does not exist!");
+
+ MultipartFormDataContent request = new MultipartFormDataContent();
+ var savefile = new ByteArrayContent(File.ReadAllBytes($"{save.SaveFolder}/{save.FileName}"));
+ request.Add(savefile, "saveFile", save.FileName);
+
+
+
+ if (NewSave)
+ {
+ response = HttpClientSingleton.Instance.PostAsync($"{Settings.RomMHost}/api/saves?rom_id={save.ROMID.ToString()}", request).GetAwaiter().GetResult();
+ response.EnsureSuccessStatusCode();
+ }
+ else
+ {
+ response = HttpClientSingleton.Instance.PutAsync($"{Settings.RomMHost}/api/saves/{save.ID}", request).GetAwaiter().GetResult();
+ response.EnsureSuccessStatusCode();
+ }
+
+ Stream body = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult();
+ using (StreamReader reader = new StreamReader(body))
+ {
+ var data = reader.ReadToEnd();
+
+ var jsonResponse = JObject.Parse(data);
+ var savedata = jsonResponse.ToObject();
+
+ save.ID = savedata.ID;
+ save.FileSize = savedata.FileSize;
+ save.LastUpdated = savedata.LastUpdated;
+ save.IsInSync = SaveSyncStatus.InSync;
+ rom.Save = save;
+
+ }
+
+ string filesizestring;
+
+ if (save.FileSize > 1000000)
+ filesizestring = $"({((float)save.FileSize) / 1000 / 1000}MB)";
+ else if (save.FileSize > 1000)
+ filesizestring = $"({((float)save.FileSize) / 1000}KB)";
+ else
+ filesizestring = $"({save.FileSize}B)";
+
+ Plugin.PlayniteApi.Notifications.Add(new Playnite.SDK.NotificationMessage($"RomM.Save.Upload.{rom.Id}", $"Backed up {save.FileName} to RomM Server! {filesizestring}", Playnite.SDK.NotificationType.Info));
+ Settings.UpdateNotifcationBar($"Backed up {save.FileName} to RomM Server! {filesizestring}");
+
+ SyncLocalSaves();
+ SyncRemoteSaves();
+ SaveROMRevisions(mappingID);
+
+ }
+ catch (Exception ex)
+ {
+ Plugin.Logger.Error(ex.ToString());
+ Plugin.PlayniteApi.Notifications.Add(new Playnite.SDK.NotificationMessage("RomM.Save.Upload.Failed", $"Save upload failed: {ex.Message}", Playnite.SDK.NotificationType.Info));
+ Settings.UpdateNotifcationBar(ex.Message, true);
+
+ if (response != null && response.StatusCode == System.Net.HttpStatusCode.NotFound)
+ {
+ UploadSave(save, mappingID, true);
+ }
+ else
+ {
+ rom.Save = save;
+ SyncLocalSaves();
+ SyncRemoteSaves();
+ SaveROMRevisions(mappingID);
+ }
+ }
+ }
+ }
+}
diff --git a/Save/SaveSelector.xaml b/Save/SaveSelector.xaml
new file mode 100644
index 0000000..1abc8fd
--- /dev/null
+++ b/Save/SaveSelector.xaml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Save/SaveSelector.xaml.cs b/Save/SaveSelector.xaml.cs
new file mode 100644
index 0000000..850eff3
--- /dev/null
+++ b/Save/SaveSelector.xaml.cs
@@ -0,0 +1,40 @@
+using Playnite.SDK.Controls;
+
+using RomM.Models.RomM.Rom;
+
+using System.Collections.ObjectModel;
+using System.Windows;
+
+namespace RomM.Save
+{
+ public partial class RomMSaveSelector : PluginUserControl
+ {
+ public ObservableCollection Saves { get; set; }
+
+ public bool Cancelled { get; set; } = true;
+ public bool NeverSave { get; set; } = false;
+
+ public RomMSaveSelector(ObservableCollection saves)
+ {
+ Saves = saves;
+ InitializeComponent();
+ }
+
+ private void Click_Never(object sender, RoutedEventArgs e)
+ {
+ NeverSave = true;
+ ((Window)Parent).Close();
+ }
+
+ private void Click_DontSave(object sender, RoutedEventArgs e)
+ {
+ ((Window)Parent).Close();
+ }
+
+ private void Click_Save(object sender, RoutedEventArgs e)
+ {
+ Cancelled = false;
+ ((Window)Parent).Close();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Settings/EmulatorMapping.cs b/Settings/EmulatorMapping.cs
index 620d91a..a2d69f5 100644
--- a/Settings/EmulatorMapping.cs
+++ b/Settings/EmulatorMapping.cs
@@ -6,91 +6,251 @@
using System.ComponentModel;
using System.Linq;
using System.Xml.Serialization;
+using RomM.Models.RomM.Platform;
+using SharpCompress;
+using System.Web;
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 = "";
+ [JsonIgnore] private string _savePath = "";
+ [JsonIgnore] private bool _downloadSaves = false;
+ [JsonIgnore] private bool _uploadSaves = true;
+ [JsonIgnore] private string _savefileExtensions = "";
+
+ 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();
}
}
@@ -125,14 +285,53 @@ public string EmulatorBasePathResolved
}
}
+ public string GeneralSavePath
+ {
+ get => _savePath;
+ set
+ {
+ _savePath = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public bool DownloadSaveBeforeGame
+ {
+ get => _downloadSaves;
+ set
+ {
+ _downloadSaves = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public bool UploadSaveAfterGame
+ {
+ get => _uploadSaves;
+ set
+ {
+ _uploadSaves = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public string SaveFileExtensions
+ {
+ get => _savefileExtensions;
+ set
+ {
+ _savefileExtensions = value.TrimEnd(';');
+ OnPropertyChanged();
+ }
+ }
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..b695a2f 100644
--- a/Settings/Settings.cs
+++ b/Settings/Settings.cs
@@ -1,97 +1,327 @@
using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
using Playnite.SDK;
using Playnite.SDK.Plugins;
+
+using RomM.Models.RomM;
+using RomM.Models.RomM.Platform;
+using RomM.Save;
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 readonly RomM _plugin;
private SettingsViewModel editingClone { get; set; }
+ [JsonIgnore] internal readonly IPlayniteAPI PlayniteAPI;
+ [JsonIgnore] internal readonly IRomM RomM;
+ public static SettingsViewModel Instance { get; private set; }
+ [JsonIgnore] public SaveController SaveController { 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]
- internal readonly IRomM RomM;
+ public string NotifyText
+ {
+ get => _notifyText;
+ set
+ {
+ _notifyText = value;
+ OnPropertyChanged();
+ }
+ }
+ [JsonIgnore]
+ 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);
+ #region Properties
- public static bool IsValidApiToken(string token)
+ public string RomMHost
{
- return !string.IsNullOrEmpty(token) && ApiTokenPattern.IsMatch(token);
+ 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);
- [JsonIgnore]
- public bool HasAnyAuth =>
- IsValidApiToken(RomMApiToken?.Trim()) ||
- (!string.IsNullOrEmpty(RomMUsername) && !string.IsNullOrEmpty(RomMPassword));
+ public bool UseBasicAuth
+ {
+ 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 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;
+ }
+ }
+ }
}
+ #endregion
- internal SettingsViewModel(Plugin plugin, IRomM romM)
+ public SettingsViewModel(){}
+
+ internal SettingsViewModel(RomM plugin, IRomM romM)
{
RomM = romM;
PlayniteAPI = plugin.PlayniteApi;
Instance = this;
_plugin = plugin;
+ SaveController = new SaveController(_plugin);
+
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;
+ SaveController.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 +342,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 = "---";
+ _plugin.Logger.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,11 +463,24 @@ 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)
{
+ if(SaveController.CurrentMapping != null)
+ {
+ SaveController.SaveROMRevisions(SaveController.CurrentMapping.MappingId);
+ }
+
var setDir = _plugin.GetPluginUserDataPath();
var setFile = Path.Combine(setDir, "config.json");
if (!Directory.Exists(setDir))
@@ -150,20 +496,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 +514,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..1d83c2e 100644
--- a/Settings/SettingsView.xaml
+++ b/Settings/SettingsView.xaml
@@ -1,206 +1,652 @@
-
+ xmlns:enum="clr-namespace:RomM.Models.RomM.Rom"
+ 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..02fd7fb 100644
--- a/Settings/SettingsView.xaml.cs
+++ b/Settings/SettingsView.xaml.cs
@@ -1,9 +1,15 @@
-using System;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Playnite.SDK;
+using RomM.Models.RomM;
+using RomM.Models.RomM.Platform;
+using RomM.Models.RomM.Rom;
+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;
@@ -11,15 +17,67 @@ namespace RomM.Settings
{
public partial class SettingsView : UserControl
{
- private bool InManualCellCommit = false;
-
public SettingsView()
{
InitializeComponent();
}
+ private void Click_TestConnection(object sender, RoutedEventArgs e)
+ {
+ SettingsViewModel.Instance.TestConnection(true);
+ e.Handled = true;
+ }
+
+ private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs 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}");
+ }
+ e.Handled = true;
+ }
+
+ private async void Click_PullPlatforms(object sender, RoutedEventArgs e)
+ {
+ SettingsViewModel.Instance.Notify = false;
+
+ try
+ {
+ HttpResponseMessage response = await HttpClientSingleton.Instance.GetAsync($"{SettingsViewModel.Instance.RomMHost}/api/platforms");
+ response.EnsureSuccessStatusCode();
+
+ string body = await response.Content.ReadAsStringAsync();
+ SettingsViewModel.Instance.RomMPlatforms = JsonConvert.DeserializeObject>(body);
+ SettingsViewModel.Instance.UpdateNotifcationBar("Platforms successfully retrieved!");
+ }
+ catch (Exception ex)
+ {
+ LogManager.GetLogger().Error($"RomM - failed to get platforms: {ex}");
+ SettingsViewModel.Instance.UpdateNotifcationBar($"Failed to get platforms: {ex.Message}!", true);
+ }
+ }
+
+ private void Click_AddMapping(object sender, RoutedEventArgs e)
+ {
+ SettingsViewModel.Instance.Notify = false;
+ SettingsViewModel.Instance.Mappings.Add(new EmulatorMapping(SettingsViewModel.Instance.RomMPlatforms));
+ }
+
private void Click_Delete(object sender, RoutedEventArgs e)
{
+ SettingsViewModel.Instance.Notify = false;
if (((FrameworkElement)sender).DataContext is EmulatorMapping mapping)
{
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);
@@ -32,6 +90,7 @@ private void Click_Delete(object sender, RoutedEventArgs e)
private void Click_BrowseDestination(object sender, RoutedEventArgs e)
{
+ SettingsViewModel.Instance.Notify = false;
var mapping = ((FrameworkElement)sender).DataContext as EmulatorMapping;
string path;
if ((path = GetSelectedFolderPath()) == null) return;
@@ -44,126 +103,134 @@ private void Click_BrowseDestination(object sender, RoutedEventArgs e)
mapping.DestinationPath = path;
}
- private async void Click_TestConnection(object sender, RoutedEventArgs e)
+ private static string GetSelectedFolderPath()
{
- var settings = SettingsViewModel.Instance;
- var dialogs = settings.PlayniteAPI.Dialogs;
-
- var host = settings.RomMHost?.Trim().TrimEnd('/');
- if (string.IsNullOrWhiteSpace(host))
- {
- dialogs.ShowMessage("RomM Host is empty.", "RomM");
- return;
- }
-
- if (!settings.HasAnyAuth)
- {
- dialogs.ShowMessage("Provide either a valid API Token or username and password.", "RomM");
- return;
- }
-
- var button = (Button)sender;
- var originalContent = button.Content;
-
- try
- {
- button.IsEnabled = false;
- button.Content = "Testing...";
-
- using (var req = new HttpRequestMessage(HttpMethod.Get, $"{host}/api/users/me"))
- {
- 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");
- }
- }
- }
- }
- 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;
- }
+ SettingsViewModel.Instance.Notify = false;
+ return SettingsViewModel.Instance.PlayniteAPI.Dialogs.SelectFolder();
}
private void Click_Browse7zDestination(object sender, RoutedEventArgs e)
{
+ SettingsViewModel.Instance.Notify = false;
string path;
if ((path = SettingsViewModel.Instance.PlayniteAPI.Dialogs.SelectFile("7Zip Executable|7z.exe")) == null) return;
SettingsViewModel.Instance.PathTo7z = path;
+ e.Handled = true;
}
- private static string GetSelectedFolderPath()
+ private void Click_SyncSaves(object sender, RoutedEventArgs e)
{
- return SettingsViewModel.Instance.PlayniteAPI.Dialogs.SelectFolder();
+ SettingsViewModel.Instance.Notify = false;
+ SettingsViewModel.Instance.SaveController.SyncRemoteSaves(true);
}
- private void DataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
+ private void Click_SaveDirectory(object sender, RoutedEventArgs e)
{
- if (!InManualCellCommit && sender is DataGrid grid)
- {
- InManualCellCommit = true;
+ SettingsViewModel.Instance.Notify = false;
+ var mapping = ((FrameworkElement)sender).DataContext as EmulatorMapping;
- // HACK!!!!
- // Alternate approach 1: try to find new value here and store that somewhere as the currently selected emu
- // Alternate approach 2: the "right" way(?) https://stackoverflow.com/a/34332709
- if (e.Column.Header?.ToString() == "Emulator" || e.Column.Header?.ToString() == "Profile")
- {
- grid.CommitEdit(DataGridEditingUnit.Row, true);
- }
+ if (mapping != null)
+ {
+ string path = SettingsViewModel.Instance.PlayniteAPI.Dialogs.SelectFolder();
+ if (string.IsNullOrEmpty(path))
+ return;
- InManualCellCommit = false;
+ mapping.GeneralSavePath = path;
+ SettingsViewModel.Instance.SaveController.SyncPotentialSaves();
}
+
+ e.Handled = true;
}
- private void DataGrid_CurrentCellChanged(object sender, EventArgs e)
+ private void LostKeyboard_SaveExtensionsBox(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
{
-
+ SettingsViewModel.Instance.Notify = false;
+ SettingsViewModel.Instance.SaveController.CurrentMapping.SaveFileExtensions = SaveExtentionTextBox.Text;
+ SettingsViewModel.Instance.SaveController.SyncPotentialSaves();
+ e.Handled = true;
}
- private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
+ private void LostKeyboard_GeneralSavePath(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
{
- try
+ SettingsViewModel.Instance.Notify = false;
+ var mapping = ((FrameworkElement)sender).DataContext as EmulatorMapping;
+
+ if (mapping != null)
{
- if (e.Uri.Scheme == Uri.UriSchemeHttp || e.Uri.Scheme == Uri.UriSchemeHttps)
- {
- var psi = new ProcessStartInfo
- {
- FileName = e.Uri.AbsoluteUri,
- UseShellExecute = true
- };
- Process.Start(psi);
- }
+ if (string.IsNullOrEmpty(mapping.GeneralSavePath))
+ return;
+
+ SettingsViewModel.Instance.SaveController.SyncPotentialSaves();
}
- catch (Exception ex)
+
+ e.Handled = true;
+ }
+
+ private void Click_BrowseSavefile(object sender, RoutedEventArgs e)
+ {
+ SettingsViewModel.Instance.Notify = false;
+ var save = ((FrameworkElement)sender).DataContext as RomMSave;
+
+ string path = SettingsViewModel.Instance.PlayniteAPI.Dialogs.SelectFolder(SettingsViewModel.Instance.SaveController.CurrentMapping.GeneralSavePath);
+ if (string.IsNullOrEmpty(path))
+ return;
+
+ if(!path.StartsWith(SettingsViewModel.Instance.SaveController.CurrentMapping.GeneralSavePath))
{
- System.Diagnostics.Debug.WriteLine($"Failed to open URL: {ex.Message}");
+ SettingsViewModel.Instance.UpdateNotifcationBar("Selected folder is not in the general save directory!", true);
+ return;
}
+
+ save.SaveFolder = path;
+ e.Handled = true;
+ }
+
+ private void Click_SyncNow(object sender, RoutedEventArgs e)
+ {
+ SettingsViewModel.Instance.Notify = false;
+ var save = ((FrameworkElement)sender).DataContext as RomMSave;
+ SettingsViewModel.Instance.SaveController.SyncSave(save, SettingsViewModel.Instance.SaveController.CurrentMapping.MappingId);
+ e.Handled = true;
+ }
+
+ private void Checked_RemoteEnableSync(object sender, RoutedEventArgs e)
+ {
+ SettingsViewModel.Instance.Notify = false;
+ var save = ((FrameworkElement)sender).DataContext as RomMSave;
+ SettingsViewModel.Instance.SaveController.RemoteSaveEnabled(save);
+ e.Handled = true;
+ }
+
+ private void Checked_LocalEnableSync(object sender, RoutedEventArgs e)
+ {
+ SettingsViewModel.Instance.Notify = false;
+ var save = ((FrameworkElement)sender).DataContext as RomMSave;
+ SettingsViewModel.Instance.SaveController.LocalSaveEnabled(save);
+ e.Handled = true;
+ }
+
+ private void Click_RemoveSave(object sender, RoutedEventArgs e)
+ {
+ SettingsViewModel.Instance.Notify = false;
+ var save = ((FrameworkElement)sender).DataContext as RomMSave;
+ SettingsViewModel.Instance.SaveController.RemoveSaveEntry(save);
+ e.Handled = true;
+ }
+
+ private void Click_DeleteSave(object sender, RoutedEventArgs e)
+ {
+ SettingsViewModel.Instance.Notify = false;
+ var save = ((FrameworkElement)sender).DataContext as RomMSave;
+ SettingsViewModel.Instance.SaveController.DeleteSave(save);
+ e.Handled = true;
+ }
+
+ private void Click_UploadSave(object sender, RoutedEventArgs e)
+ {
+ SettingsViewModel.Instance.Notify = false;
+ var possiblesave = ((FrameworkElement)sender).DataContext as PossibleSave;
+ SettingsViewModel.Instance.SaveController.UploadNewSave(possiblesave);
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