Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
cc04bc6
Initial rewrite of import process
matthew-pye Mar 11, 2026
1b9385b
Add metadata provider
matthew-pye Mar 12, 2026
9611479
Various additions & fixes (Read Desc.)
matthew-pye Mar 12, 2026
3c8f570
Added SHA1 hashing for games that don't have one
matthew-pye Mar 12, 2026
a3514ba
Replace finding filename with helper function
matthew-pye Mar 12, 2026
f9dd8a7
Finish import refactor/cleanup
matthew-pye Mar 13, 2026
8c86e70
Cleanup ROM data saving and changed download URL endpoint
matthew-pye Mar 13, 2026
4770888
Changed failed download message to just uses the message
matthew-pye Mar 13, 2026
a5ea5dd
4.8 support and adding user data to settings
matthew-pye Mar 16, 2026
879c9c4
Fix settings not loading when RomMHost is blank
matthew-pye Mar 16, 2026
00b7b0f
Replaced using igdb platforms for RomM platforms
matthew-pye Mar 17, 2026
6a7c649
Add OnPropertyChange to DestinationPath
matthew-pye Mar 17, 2026
286abe8
Fix platform name not being restored in UI
matthew-pye Mar 22, 2026
3f7e60b
Fix object ref error
matthew-pye Mar 22, 2026
71aafd6
Fix issues & add platform to migration
matthew-pye Mar 23, 2026
43d0c75
Guard Emulator being set to null
matthew-pye Mar 23, 2026
423f5bc
Move settings into correct categories & Added some tooltips
matthew-pye Mar 26, 2026
8a09e0e
Merge upsteam client token code with my already existing client token…
matthew-pye Apr 27, 2026
92de6b9
Replace failed text with a notification bar
matthew-pye Apr 27, 2026
7bea6e4
Hopefully mitigate random DependencySource errors
matthew-pye Apr 28, 2026
abdc078
Only update notification bar from settings
matthew-pye Apr 28, 2026
b42af10
Replace RomMSavedSibling to RomMRevision
matthew-pye May 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Downloads/VersionSelector.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
<Separator Margin="0,5,0,0" />
<ScrollViewer DockPanel.Dock="Top" Width="auto" Height="120" Margin="5,5,5,5" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled">
<StackPanel x:Name="tStack">
<ItemsControl ItemsSource="{Binding Siblings}">
<ItemsControl ItemsSource="{Binding RomVersions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Margin="5,5,5,5" GroupName="Options" IsChecked="{Binding isSelected, Mode=TwoWay}" Content="{Binding FileName, Mode=OneWay}"/>
<RadioButton Margin="5,5,5,5" GroupName="Options" IsChecked="{Binding IsSelected, Mode=TwoWay}" Content="{Binding FileName, Mode=OneWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Expand Down
6 changes: 3 additions & 3 deletions Downloads/VersionSelector.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ namespace RomM.VersionSelector
public partial class RomMVersionSelector : PluginUserControl
{

public ObservableCollection<RomMSibling> Siblings { get; set; }
public ObservableCollection<RomMRevision> RomVersions { get; set; }
public bool Cancelled { get; set; } = true;

public RomMVersionSelector(List<RomMSibling> siblings)
public RomMVersionSelector(List<RomMRevision> romVersions)
{

Siblings = new ObservableCollection<RomMSibling>(siblings);
RomVersions = new ObservableCollection<RomMRevision>(romVersions);

InitializeComponent();
}
Expand Down
4 changes: 2 additions & 2 deletions Games/RomMGameInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
using System.IO;
using System.Linq;
using ProtoBuf;
using Playnite.SDK;
using RomM.Models.RomM.Rom;

namespace RomM.Games
{
Expand Down Expand Up @@ -68,7 +68,7 @@ private static T FromGameIdString<T>(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);

Expand Down
475 changes: 475 additions & 0 deletions Games/RomMImport.cs

Large diffs are not rendered by default.

231 changes: 231 additions & 0 deletions Games/RomMImportController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Playnite.SDK;
using Playnite.SDK.Models;
using Playnite.SDK.Plugins;
using RomM.Models.RomM.Collection;
using RomM.Models.RomM.Platform;
using RomM.Models.RomM.Rom;
using RomM.Settings;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;

namespace RomM.Games
{
class RomMImportController
{
private readonly RomM _plugin;
public ILogger Logger => LogManager.GetLogger();

public RomMImportController(RomM plugin)
{
_plugin = plugin;
}

public List<Game> Import(LibraryImportGamesArgs args)
{
IList<RomMPlatform> apiPlatforms = FetchPlatforms();
List<Task<List<Game>>> tasks = new List<Task<List<Game>>>();
List<Game> games = new List<Game>();
IEnumerable<EmulatorMapping> 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<RomMCollection> favoritCollections = _plugin.FetchFavorites();
var favorites = favoritCollections.FirstOrDefault(c => c.IsFavorite)?.RomIds ?? new List<int>();

// 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<List<Game>>.Factory.StartNew(() =>
{
RomMImport newImport = new RomMImport(_plugin, args, mapping, romMROMs, favorites);
return newImport.ProcessData();
}));

}

Task.WhenAll(tasks).Wait();

foreach (var task in tasks)
{
games.AddRange(task.Result);
}

return games;
}

private static async Task<HttpResponseMessage> 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<string> 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<RomMPlatform> 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<List<RomMPlatform>>(body);
}
catch (HttpRequestException e)
{
Logger.Error($"[Import Controller] Request exception: {e.Message}");
return new List<RomMPlatform>();
}
}

private List<RomMRom> 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<RomMRom>();

// 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<RomMRom> roms;
using (StreamReader reader = new StreamReader(body))
{
var jsonResponse = JObject.Parse(reader.ReadToEnd());
roms = jsonResponse["items"].ToObject<List<RomMRom>>();
}

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;
}
}

}
Loading