diff --git a/media/icon.xcf b/media/icon.xcf index fbb59c9cd..3102a11e8 100644 Binary files a/media/icon.xcf and b/media/icon.xcf differ diff --git a/references/LiteDB.dll b/references/LiteDB.dll new file mode 100644 index 000000000..8b82b1e1a Binary files /dev/null and b/references/LiteDB.dll differ diff --git a/scripts/GenerateRetroArchProfile.ps1 b/scripts/GenerateRetroArchProfile.ps1 index 834b27ded..75f36668f 100644 --- a/scripts/GenerateRetroArchProfile.ps1 +++ b/scripts/GenerateRetroArchProfile.ps1 @@ -72,7 +72,11 @@ $ignoreList = @( "cruzes_libretro.info", "chaigame_libretro.info", "chailove_libretro.info", - "freej2me_libretro.info" + "freej2me_libretro.info", + "thepowdertoy_libretro.info", + "reminiscence_libretro.info", + "mpv_libretro.info", + "cannonball_libretro.info" ) $platformsTranslate = @{ @@ -163,6 +167,15 @@ foreach ($infoFile in $infoFiles) if ($profile.supported_extensions) { $extensions = [System.String]::Join(", ", $profile.supported_extensions.Split("|", [System.StringSplitOptions]::RemoveEmptyEntries)) + if ($extensions -notmatch "zip") + { + $extensions += ", zip" + } + + if ($extensions -notmatch "7z") + { + $extensions += ", 7z" + } } $profileString = $profileTemplate -f $name, $infoFile.BaseName, $platforms, $extensions, $infoFile.BaseName diff --git a/source/Playnite.sln b/source/Playnite.sln index fba5bf1f0..187c9986f 100644 --- a/source/Playnite.sln +++ b/source/Playnite.sln @@ -15,10 +15,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlayniteUITests", "Playnite EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlayniteSDK", "PlayniteSDK\PlayniteSDK.csproj", "{19BC9097-5705-4352-90E2-99F0C63230D0}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{214A3A6E-C961-405E-8F5E-96314B672CF2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbTools", "Tools\DbTools\DbTools.csproj", "{D5DFD7C9-E747-45F8-BE7D-E9BE3D463248}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlayniteInstaller", "Tools\PlayniteInstaller\PlayniteInstaller.csproj", "{62DDB6BD-36D1-42C1-BA38-6C7550EA366E}" +EndProject Global - GlobalSection(Performance) = preSolution - HasPerformanceSessions = true - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 @@ -84,6 +87,30 @@ Global {19BC9097-5705-4352-90E2-99F0C63230D0}.Release|x64.Build.0 = Release|Any CPU {19BC9097-5705-4352-90E2-99F0C63230D0}.Release|x86.ActiveCfg = Release|Any CPU {19BC9097-5705-4352-90E2-99F0C63230D0}.Release|x86.Build.0 = Release|Any CPU + {D5DFD7C9-E747-45F8-BE7D-E9BE3D463248}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5DFD7C9-E747-45F8-BE7D-E9BE3D463248}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5DFD7C9-E747-45F8-BE7D-E9BE3D463248}.Debug|x64.ActiveCfg = Debug|x64 + {D5DFD7C9-E747-45F8-BE7D-E9BE3D463248}.Debug|x64.Build.0 = Debug|x64 + {D5DFD7C9-E747-45F8-BE7D-E9BE3D463248}.Debug|x86.ActiveCfg = Debug|x86 + {D5DFD7C9-E747-45F8-BE7D-E9BE3D463248}.Debug|x86.Build.0 = Debug|x86 + {D5DFD7C9-E747-45F8-BE7D-E9BE3D463248}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5DFD7C9-E747-45F8-BE7D-E9BE3D463248}.Release|Any CPU.Build.0 = Release|Any CPU + {D5DFD7C9-E747-45F8-BE7D-E9BE3D463248}.Release|x64.ActiveCfg = Release|Any CPU + {D5DFD7C9-E747-45F8-BE7D-E9BE3D463248}.Release|x64.Build.0 = Release|Any CPU + {D5DFD7C9-E747-45F8-BE7D-E9BE3D463248}.Release|x86.ActiveCfg = Release|Any CPU + {D5DFD7C9-E747-45F8-BE7D-E9BE3D463248}.Release|x86.Build.0 = Release|Any CPU + {62DDB6BD-36D1-42C1-BA38-6C7550EA366E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {62DDB6BD-36D1-42C1-BA38-6C7550EA366E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {62DDB6BD-36D1-42C1-BA38-6C7550EA366E}.Debug|x64.ActiveCfg = Debug|Any CPU + {62DDB6BD-36D1-42C1-BA38-6C7550EA366E}.Debug|x64.Build.0 = Debug|Any CPU + {62DDB6BD-36D1-42C1-BA38-6C7550EA366E}.Debug|x86.ActiveCfg = Debug|Any CPU + {62DDB6BD-36D1-42C1-BA38-6C7550EA366E}.Debug|x86.Build.0 = Debug|Any CPU + {62DDB6BD-36D1-42C1-BA38-6C7550EA366E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {62DDB6BD-36D1-42C1-BA38-6C7550EA366E}.Release|Any CPU.Build.0 = Release|Any CPU + {62DDB6BD-36D1-42C1-BA38-6C7550EA366E}.Release|x64.ActiveCfg = Release|Any CPU + {62DDB6BD-36D1-42C1-BA38-6C7550EA366E}.Release|x64.Build.0 = Release|Any CPU + {62DDB6BD-36D1-42C1-BA38-6C7550EA366E}.Release|x86.ActiveCfg = Release|Any CPU + {62DDB6BD-36D1-42C1-BA38-6C7550EA366E}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -91,6 +118,8 @@ Global GlobalSection(NestedProjects) = preSolution {3BB5A3D4-B998-4D0F-9B7A-B133905AA5C9} = {FBE3E4C8-611E-435D-B47C-FE006AAA8C4A} {59D0C141-64A8-4D86-8156-4BEF8472CB8E} = {FBE3E4C8-611E-435D-B47C-FE006AAA8C4A} + {D5DFD7C9-E747-45F8-BE7D-E9BE3D463248} = {214A3A6E-C961-405E-8F5E-96314B672CF2} + {62DDB6BD-36D1-42C1-BA38-6C7550EA366E} = {214A3A6E-C961-405E-8F5E-96314B672CF2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3300FF38-9F6F-4C0F-87F3-776D6C64846D} @@ -101,4 +130,10 @@ Global GlobalSection(Performance) = preSolution HasPerformanceSessions = true EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection EndGlobal diff --git a/source/Playnite/API/PlayniteAPI.cs b/source/Playnite/API/PlayniteAPI.cs index 2b30aab09..ba4764a84 100644 --- a/source/Playnite/API/PlayniteAPI.cs +++ b/source/Playnite/API/PlayniteAPI.cs @@ -173,6 +173,7 @@ public bool LoadScripts() continue; } + logger.Info($"Loaded script extension {path}"); script.SetVariable("PlayniteApi", this); scripts.Add(script); } @@ -207,6 +208,7 @@ public void LoadPlugins() continue; } + logger.Info($"Loaded plugin extension {path}"); plugins.AddRange(plugin); } @@ -217,6 +219,7 @@ public void InvokeExtension(ExtensionFunction function) { try { + logger.Debug($"Invoking extension function {function}"); function.Invoke(); } catch (Exception e) diff --git a/source/Playnite/App/Update.cs b/source/Playnite/App/Updater.cs similarity index 95% rename from source/Playnite/App/Update.cs rename to source/Playnite/App/Updater.cs index a72b7a0c4..73fb1e2b4 100644 --- a/source/Playnite/App/Update.cs +++ b/source/Playnite/App/Updater.cs @@ -131,6 +131,7 @@ public async Task DownloadUpdate(UpdateManifest.Package package, Action addedGames, List removed } } - public class FileDefinition - { - public string Path - { - get; set; - } - - public string Name - { - get; set; - } - - public byte[] Data - { - get; set; - } - - public FileDefinition() - { - } - - public FileDefinition(string path, string name, byte[] data) - { - Path = path; - Name = name; - Data = data; - } - } - public class GameDatabase { private static NLog.Logger logger = LogManager.GetCurrentClassLogger(); @@ -342,6 +314,50 @@ public void UpdateDatabaseSettings(DatabaseSettings settings) } } + public static void CloneLibrary(string dbPath, string targetPath) + { + using (var sourceDb = new LiteDatabase($"Filename={dbPath};Mode=Exclusive")) + { + using (var targetDb = new LiteDatabase($"Filename={targetPath};Mode=Exclusive")) + { + var games = sourceDb.GetCollection("games").FindAll(); + var targetGames = targetDb.GetCollection("games"); + foreach (var game in games) + { + targetGames.Insert(game); + } + + var targetPlatforms = targetDb.GetCollection("platforms"); + foreach (var platform in sourceDb.GetCollection("platforms").FindAll()) + { + targetPlatforms.Insert(platform); + } + + var targetEmulators = targetDb.GetCollection("emulators"); + foreach (var emulator in sourceDb.GetCollection("emulators").FindAll()) + { + targetEmulators.Insert(emulator); + } + + var targetSettings = targetDb.GetCollection("settings"); + foreach (var setting in sourceDb.GetCollection("settings").FindAll()) + { + targetSettings.Insert(setting); + } + + foreach (var file in sourceDb.FileStorage.FindAll()) + { + using (var fileStream = file.OpenRead()) + { + targetDb.FileStorage.Upload(file.Id, file.Filename, fileStream); + } + } + + targetDb.Engine.UserVersion = sourceDb.Engine.UserVersion; + } + } + } + public static void MigrateDatabase(string path) { using (var db = new LiteDatabase(path)) @@ -613,6 +629,17 @@ public LiteDatabase OpenDatabase(string path) return OpenDatabase(); } + public LiteDatabase OpenDatabase(MemoryStream stream) + { + Database = new LiteDatabase(stream); + GamesCollection = Database.GetCollection("games"); + PlatformsCollection = Database.GetCollection("platforms"); + EmulatorsCollection = Database.GetCollection("emulators"); + ActiveControllersCollection = Database.GetCollection("controllers"); + IsOpen = true; + return Database; + } + public LiteDatabase OpenDatabase() { if (string.IsNullOrEmpty(Path)) @@ -1083,9 +1110,9 @@ public List GetEmulators() return EmulatorsCollection.FindAll().ToList(); } - public string AddFileNoDuplicate(FileDefinition file) + public string AddFileNoDuplicate(MetadataFile file) { - return AddFileNoDuplicate(file.Path, file.Name, file.Data); + return AddFileNoDuplicate(file.FileId, file.FileName, file.Content); } public string AddFileNoDuplicate(string id, string name, byte[] data) diff --git a/source/Playnite/Emulators/Definitions.yaml b/source/Playnite/Emulators/Definitions.yaml index 610748a68..fddfb2a9c 100644 --- a/source/Playnite/Emulators/Definitions.yaml +++ b/source/Playnite/Emulators/Definitions.yaml @@ -595,210 +595,217 @@ - Name: 4DO DefaultArguments: '-L ".\cores\4do_libretro.dll" "{ImagePath}"' Platforms: [3DO Interactive Multiplayer] - ImageExtensions: [iso, bin, chd, cue] + ImageExtensions: [iso, bin, chd, cue, zip, 7z] RequiredFiles: ['cores\4do_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: 81 DefaultArguments: '-L ".\cores\81_libretro.dll" "{ImagePath}"' Platforms: [Sinclair ZX 81] - ImageExtensions: [p, tzx, t81] + ImageExtensions: [p, tzx, t81, zip, 7z] RequiredFiles: ['cores\81_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Atari800 DefaultArguments: '-L ".\cores\atari800_libretro.dll" "{ImagePath}"' Platforms: [Atari 5200] - ImageExtensions: [xfd, atr, atx, cdm, cas, bin, a52, xex, zip] + ImageExtensions: [xfd, atr, atx, cdm, cas, bin, a52, xex, zip, 7z] RequiredFiles: ['cores\atari800_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: BlastEm DefaultArguments: '-L ".\cores\blastem_libretro.dll" "{ImagePath}"' Platforms: [Sega Genesis] - ImageExtensions: [md, bin, smd] + ImageExtensions: [md, bin, smd, zip, 7z] RequiredFiles: ['cores\blastem_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: blueMSX DefaultArguments: '-L ".\cores\bluemsx_libretro.dll" "{ImagePath}"' Platforms: [Microsoft MSX, Microsoft MSX2, Coleco ColecoVision, Sega SG 1000] - ImageExtensions: [rom, ri, mx1, mx2, col, dsk, cas, sg, sc, m3u] + ImageExtensions: [rom, ri, mx1, mx2, col, dsk, cas, sg, sc, m3u, zip, 7z] RequiredFiles: ['cores\bluemsx_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: bnes/higan DefaultArguments: '-L ".\cores\bnes_libretro.dll" "{ImagePath}"' Platforms: [Nintendo Entertainment System] - ImageExtensions: [nes] + ImageExtensions: [nes, zip, 7z] RequiredFiles: ['cores\bnes_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: bsnes/higan Accuracy DefaultArguments: '-L ".\cores\bsnes_accuracy_libretro.dll" "{ImagePath}"' Platforms: [Super Nintendo Entertainment System, Nintendo Sufami Turbo] - ImageExtensions: [sfc, smc, bml] + ImageExtensions: [sfc, smc, bml, zip, 7z] RequiredFiles: ['cores\bsnes_accuracy_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: bsnes/higan Balanced DefaultArguments: '-L ".\cores\bsnes_balanced_libretro.dll" "{ImagePath}"' Platforms: [Super Nintendo Entertainment System, Nintendo Sufami Turbo] - ImageExtensions: [sfc, smc, bml] + ImageExtensions: [sfc, smc, bml, zip, 7z] RequiredFiles: ['cores\bsnes_balanced_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: bsnes C++98 (v085) DefaultArguments: '-L ".\cores\bsnes_cplusplus98_libretro.dll" "{ImagePath}"' Platforms: [Super Nintendo Entertainment System, Nintendo Sufami Turbo] - ImageExtensions: [sfc, smc] + ImageExtensions: [sfc, smc, zip, 7z] RequiredFiles: ['cores\bsnes_cplusplus98_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: bsnes-mercury Accuracy DefaultArguments: '-L ".\cores\bsnes_mercury_accuracy_libretro.dll" "{ImagePath}"' Platforms: [Super Nintendo Entertainment System, Nintendo Sufami Turbo] - ImageExtensions: [sfc, smc, bml] + ImageExtensions: [sfc, smc, bml, zip, 7z] RequiredFiles: ['cores\bsnes_mercury_accuracy_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: bsnes-mercury Balanced DefaultArguments: '-L ".\cores\bsnes_mercury_balanced_libretro.dll" "{ImagePath}"' Platforms: [Super Nintendo Entertainment System, Nintendo Sufami Turbo] - ImageExtensions: [sfc, smc, bml] + ImageExtensions: [sfc, smc, bml, zip, 7z] RequiredFiles: ['cores\bsnes_mercury_balanced_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: bsnes-mercury Performance DefaultArguments: '-L ".\cores\bsnes_mercury_performance_libretro.dll" "{ImagePath}"' Platforms: [Super Nintendo Entertainment System, Nintendo Sufami Turbo] - ImageExtensions: [sfc, smc, bml] + ImageExtensions: [sfc, smc, bml, zip, 7z] RequiredFiles: ['cores\bsnes_mercury_performance_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: bsnes/higan Performance DefaultArguments: '-L ".\cores\bsnes_performance_libretro.dll" "{ImagePath}"' Platforms: [Super Nintendo Entertainment System, Nintendo Sufami Turbo] - ImageExtensions: [sfc, smc, bml] + ImageExtensions: [sfc, smc, bml, zip, 7z] RequiredFiles: ['cores\bsnes_performance_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Caprice32 DefaultArguments: '-L ".\cores\cap32_libretro.dll" "{ImagePath}"' Platforms: [Amstrad CPC] - ImageExtensions: [dsk, sna, zip, tap, cdt, voc] + ImageExtensions: [dsk, sna, zip, tap, cdt, voc, 7z] RequiredFiles: ['cores\cap32_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Citra Canary/Experimental DefaultArguments: '-L ".\cores\citra_canary_libretro.dll" "{ImagePath}"' Platforms: [Nintendo 3DS] - ImageExtensions: [3ds, 3dsx, elf, axf, cci, cxi, app] + ImageExtensions: [3ds, 3dsx, elf, axf, cci, cxi, app, zip, 7z] RequiredFiles: ['cores\citra_canary_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Citra DefaultArguments: '-L ".\cores\citra_libretro.dll" "{ImagePath}"' Platforms: [Nintendo 3DS] - ImageExtensions: [3ds, 3dsx, elf, axf, cci, cxi, app] + ImageExtensions: [3ds, 3dsx, elf, axf, cci, cxi, app, zip, 7z] RequiredFiles: ['cores\citra_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: CrocoDS DefaultArguments: '-L ".\cores\crocods_libretro.dll" "{ImagePath}"' Platforms: [Amstrad CPC] - ImageExtensions: [dsk, sna, kcr] + ImageExtensions: [dsk, sna, kcr, zip, 7z] RequiredFiles: ['cores\crocods_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: DeSmuME 2015 DefaultArguments: '-L ".\cores\desmume2015_libretro.dll" "{ImagePath}"' Platforms: [Nintendo DS] - ImageExtensions: [nds, bin] + ImageExtensions: [nds, bin, zip, 7z] RequiredFiles: ['cores\desmume2015_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: DeSmuME DefaultArguments: '-L ".\cores\desmume_libretro.dll" "{ImagePath}"' Platforms: [Nintendo DS] - ImageExtensions: [nds, bin] + ImageExtensions: [nds, bin, zip, 7z] RequiredFiles: ['cores\desmume_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Dolphin Launcher DefaultArguments: '-L ".\cores\dolphin_launcher_libretro.dll" "{ImagePath}"' Platforms: [Nintendo Wii, Nintendo GameCube] - ImageExtensions: [elf, dol, gcm, iso, wbfs, ciso, gcz, wad] + ImageExtensions: [elf, dol, gcm, iso, wbfs, ciso, gcz, wad, zip, 7z] RequiredFiles: ['cores\dolphin_launcher_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Dolphin DefaultArguments: '-L ".\cores\dolphin_libretro.dll" "{ImagePath}"' Platforms: [Nintendo GameCube, Nintendo Wii] - ImageExtensions: [gcm, iso, wbfs, ciso, gcz, elf, dol, dff, tgc, wad] + ImageExtensions: [gcm, iso, wbfs, ciso, gcz, elf, dol, dff, tgc, wad, zip, 7z] RequiredFiles: ['cores\dolphin_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: DOSBox DefaultArguments: '-L ".\cores\dosbox_libretro.dll" "{ImagePath}"' Platforms: [DOS] - ImageExtensions: [exe, com, bat, conf] + ImageExtensions: [exe, com, bat, conf, zip, 7z] RequiredFiles: ['cores\dosbox_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ + - Name: DOSBox-SVN + DefaultArguments: '-L ".\cores\dosbox_svn_libretro.dll" "{ImagePath}"' + Platforms: [DOS] + ImageExtensions: [exe, com, bat, conf, zip, 7z] + RequiredFiles: ['cores\dosbox_svn_libretro.dll'] + ExecutableLookup: ^retroarch\.exe$ + - Name: EasyRPG DefaultArguments: '-L ".\cores\easyrpg_libretro.dll" "{ImagePath}"' Platforms: [RPG Maker] - ImageExtensions: [ini] + ImageExtensions: [ini, zip, 7z] RequiredFiles: ['cores\easyrpg_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Emux CHIP-8 DefaultArguments: '-L ".\cores\emux_chip8_libretro.dll" "{ImagePath}"' Platforms: [Joseph Weisbecker CHIP-8] - ImageExtensions: [ch8, bin, rom] + ImageExtensions: [ch8, bin, rom, zip, 7z] RequiredFiles: ['cores\emux_chip8_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Emux GB DefaultArguments: '-L ".\cores\emux_gb_libretro.dll" "{ImagePath}"' Platforms: [Nintendo Game Boy, Nintendo Game Boy Color] - ImageExtensions: [gb, bin, rom] + ImageExtensions: [gb, bin, rom, zip, 7z] RequiredFiles: ['cores\emux_gb_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Emux NES DefaultArguments: '-L ".\cores\emux_nes_libretro.dll" "{ImagePath}"' Platforms: [Nintendo Entertainment System] - ImageExtensions: [nes, bin, rom] + ImageExtensions: [nes, bin, rom, zip, 7z] RequiredFiles: ['cores\emux_nes_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Emux SMS DefaultArguments: '-L ".\cores\emux_sms_libretro.dll" "{ImagePath}"' Platforms: [Sega Master System] - ImageExtensions: [sms, bin, rom] + ImageExtensions: [sms, bin, rom, zip, 7z] RequiredFiles: ['cores\emux_sms_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: FB Alpha 2012 CPS-1 DefaultArguments: '-L ".\cores\fbalpha2012_cps1_libretro.dll" "{ImagePath}"' Platforms: [Capcom CP System I] - ImageExtensions: [zip] + ImageExtensions: [zip, 7z] RequiredFiles: ['cores\fbalpha2012_cps1_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: FB Alpha 2012 CPS-2 DefaultArguments: '-L ".\cores\fbalpha2012_cps2_libretro.dll" "{ImagePath}"' Platforms: [Capcom CP System II] - ImageExtensions: [zip] + ImageExtensions: [zip, 7z] RequiredFiles: ['cores\fbalpha2012_cps2_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: FB Alpha 2012 CPS-3 DefaultArguments: '-L ".\cores\fbalpha2012_cps3_libretro.dll" "{ImagePath}"' Platforms: [Capcom CP System III] - ImageExtensions: [zip] + ImageExtensions: [zip, 7z] RequiredFiles: ['cores\fbalpha2012_cps3_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ @@ -812,7 +819,7 @@ - Name: FB Alpha 2012 Neo Geo DefaultArguments: '-L ".\cores\fbalpha2012_neogeo_libretro.dll" "{ImagePath}"' Platforms: [SNK Neo Geo] - ImageExtensions: [zip] + ImageExtensions: [zip, 7z] RequiredFiles: ['cores\fbalpha2012_neogeo_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ @@ -826,112 +833,126 @@ - Name: FCEUmm DefaultArguments: '-L ".\cores\fceumm_libretro.dll" "{ImagePath}"' Platforms: [Nintendo Entertainment System, Nintendo Family Computer Disk System] - ImageExtensions: [fds, nes, unif, unf] + ImageExtensions: [fds, nes, unif, unf, zip, 7z] RequiredFiles: ['cores\fceumm_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ + - Name: fixGB + DefaultArguments: '-L ".\cores\fixgb_libretro.dll" "{ImagePath}"' + Platforms: [Nintendo Game Boy, Nintendo Game Boy Color] + ImageExtensions: [gb, gbc, gbs, zip, 7z] + RequiredFiles: ['cores\fixgb_libretro.dll'] + ExecutableLookup: ^retroarch\.exe$ + + - Name: fixNES + DefaultArguments: '-L ".\cores\fixnes_libretro.dll" "{ImagePath}"' + Platforms: [Nintendo Entertainment System, Nintendo Family Computer Disk System] + ImageExtensions: [nes, fds, qd, nsf, zip, 7z] + RequiredFiles: ['cores\fixnes_libretro.dll'] + ExecutableLookup: ^retroarch\.exe$ + - Name: fMSX DefaultArguments: '-L ".\cores\fmsx_libretro.dll" "{ImagePath}"' Platforms: [Microsoft MSX, Microsoft MSX2] - ImageExtensions: [rom, mx1, mx2, dsk, cas] + ImageExtensions: [rom, mx1, mx2, dsk, cas, zip, 7z] RequiredFiles: ['cores\fmsx_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: FreeIntv DefaultArguments: '-L ".\cores\freeintv_libretro.dll" "{ImagePath}"' Platforms: [Mattel Intellivision] - ImageExtensions: [int, bin, rom] + ImageExtensions: [int, bin, rom, zip, 7z] RequiredFiles: ['cores\freeintv_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Frodo DefaultArguments: '-L ".\cores\frodo_libretro.dll" "{ImagePath}"' Platforms: [Commodore 64] - ImageExtensions: [d64, t64, x64, p00, lnx, zip] + ImageExtensions: [d64, t64, x64, p00, lnx, zip, 7z] RequiredFiles: ['cores\frodo_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: FS-UAE DefaultArguments: '-L ".\cores\fsuae_libretro.dll" "{ImagePath}"' Platforms: [Commodore Amiga] - ImageExtensions: [adf, ipf, fs-uae] + ImageExtensions: [adf, ipf, fs-uae, zip, 7z] RequiredFiles: ['cores\fsuae_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Fuse DefaultArguments: '-L ".\cores\fuse_libretro.dll" "{ImagePath}"' Platforms: [Sinclair ZX Spectrum +3, Sinclair ZX Spectrum] - ImageExtensions: [tzx, tap, z80, rzx, scl, trd] + ImageExtensions: [tzx, tap, z80, rzx, scl, trd, zip, 7z] RequiredFiles: ['cores\fuse_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Gambatte DefaultArguments: '-L ".\cores\gambatte_libretro.dll" "{ImagePath}"' Platforms: [Nintendo Game Boy, Nintendo Game Boy Color] - ImageExtensions: [gb, gbc, dmg] + ImageExtensions: [gb, gbc, dmg, zip, 7z] RequiredFiles: ['cores\gambatte_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Gearboy DefaultArguments: '-L ".\cores\gearboy_libretro.dll" "{ImagePath}"' Platforms: [Nintendo Game Boy, Nintendo Game Boy Color] - ImageExtensions: [gb, dmg, gbc, cgb, sgb] + ImageExtensions: [gb, dmg, gbc, cgb, sgb, zip, 7z] RequiredFiles: ['cores\gearboy_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Gearsystem DefaultArguments: '-L ".\cores\gearsystem_libretro.dll" "{ImagePath}"' Platforms: [Sega Game Gear, Sega Master System] - ImageExtensions: [sms, gg, bin, rom] + ImageExtensions: [sms, gg, bin, rom, zip, 7z] RequiredFiles: ['cores\gearsystem_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Genesis Plus GX DefaultArguments: '-L ".\cores\genesis_plus_gx_libretro.dll" "{ImagePath}"' Platforms: [Sega Game Gear, Sega Master System, Sega CD, Sega Genesis, Sega PICO, Sega SG 1000] - ImageExtensions: [mdx, md, smd, gen, bin, cue, iso, sms, gg, sg, 68k, chd] + ImageExtensions: [mdx, md, smd, gen, bin, cue, iso, sms, gg, sg, 68k, chd, zip, 7z] RequiredFiles: ['cores\genesis_plus_gx_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: gpSP DefaultArguments: '-L ".\cores\gpsp_libretro.dll" "{ImagePath}"' Platforms: [Nintendo Game Boy Advance] - ImageExtensions: [gba, bin] + ImageExtensions: [gba, bin, zip, 7z] RequiredFiles: ['cores\gpsp_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: GW DefaultArguments: '-L ".\cores\gw_libretro.dll" "{ImagePath}"' Platforms: [Handheld Electronic Game] - ImageExtensions: [mgw] + ImageExtensions: [mgw, zip, 7z] RequiredFiles: ['cores\gw_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Handy DefaultArguments: '-L ".\cores\handy_libretro.dll" "{ImagePath}"' Platforms: [Atari Lynx] - ImageExtensions: [lnx] + ImageExtensions: [lnx, zip, 7z] RequiredFiles: ['cores\handy_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Hatari DefaultArguments: '-L ".\cores\hatari_libretro.dll" "{ImagePath}"' Platforms: [Atari ST/STE/TT/Falcon] - ImageExtensions: [st, msa, zip, stx, dim, ipf] + ImageExtensions: [st, msa, zip, stx, dim, ipf, 7z] RequiredFiles: ['cores\hatari_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: nSide (Super Famicom Balanced) DefaultArguments: '-L ".\cores\higan_sfc_balanced_libretro.dll" "{ImagePath}"' Platforms: [Super Nintendo Entertainment System, Nintendo Game Boy, Nintendo Game Boy Color] - ImageExtensions: [sfc, smc, gb, gbc, bml, rom] + ImageExtensions: [sfc, smc, gb, gbc, bml, rom, zip, 7z] RequiredFiles: ['cores\higan_sfc_balanced_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: higan (Super Famicom Accuracy) DefaultArguments: '-L ".\cores\higan_sfc_libretro.dll" "{ImagePath}"' Platforms: [Super Nintendo Entertainment System, Nintendo Game Boy, Nintendo Game Boy Color] - ImageExtensions: [sfc, smc, gb, gbc, bml, rom] + ImageExtensions: [sfc, smc, gb, gbc, bml, rom, zip, 7z] RequiredFiles: ['cores\higan_sfc_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ @@ -945,21 +966,21 @@ - Name: MAME 2003 (0.78) DefaultArguments: '-L ".\cores\mame2003_libretro.dll" "{ImagePath}"' Platforms: [Various] - ImageExtensions: [zip] + ImageExtensions: [zip, 7z] RequiredFiles: ['cores\mame2003_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: MAME 2003 Midway (0.78) DefaultArguments: '-L ".\cores\mame2003_midway_libretro.dll" "{ImagePath}"' Platforms: [Various] - ImageExtensions: [zip] + ImageExtensions: [zip, 7z] RequiredFiles: ['cores\mame2003_midway_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: MAME 2003 Plus DefaultArguments: '-L ".\cores\mame2003_plus_libretro.dll" "{ImagePath}"' Platforms: [MAME 2003 Plus] - ImageExtensions: [zip] + ImageExtensions: [zip, 7z] RequiredFiles: ['cores\mame2003_plus_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ @@ -1001,98 +1022,98 @@ - Name: Beetle GBA DefaultArguments: '-L ".\cores\mednafen_gba_libretro.dll" "{ImagePath}"' Platforms: [Nintendo Game Boy Advance] - ImageExtensions: [gba, agb, bin] + ImageExtensions: [gba, agb, bin, zip, 7z] RequiredFiles: ['cores\mednafen_gba_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Beetle Lynx DefaultArguments: '-L ".\cores\mednafen_lynx_libretro.dll" "{ImagePath}"' Platforms: [Atari Lynx] - ImageExtensions: [lnx] + ImageExtensions: [lnx, zip, 7z] RequiredFiles: ['cores\mednafen_lynx_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Beetle NeoPop DefaultArguments: '-L ".\cores\mednafen_ngp_libretro.dll" "{ImagePath}"' Platforms: [SNK Neo Geo Pocket, SNK Neo Geo Pocket Color] - ImageExtensions: [ngp, ngc, ngpc] + ImageExtensions: [ngp, ngc, ngpc, zip, 7z] RequiredFiles: ['cores\mednafen_ngp_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Beetle PCE Fast DefaultArguments: '-L ".\cores\mednafen_pce_fast_libretro.dll" "{ImagePath}"' Platforms: [NEC TurboGrafx 16, NEC TurboGrafx-CD] - ImageExtensions: [pce, cue, ccd, iso, img, bin, chd] + ImageExtensions: [pce, cue, ccd, iso, img, bin, chd, zip, 7z] RequiredFiles: ['cores\mednafen_pce_fast_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Beetle PC-FX DefaultArguments: '-L ".\cores\mednafen_pcfx_libretro.dll" "{ImagePath}"' Platforms: [NEC PC-FX] - ImageExtensions: [cue, ccd, toc, chd] + ImageExtensions: [cue, ccd, toc, chd, zip, 7z] RequiredFiles: ['cores\mednafen_pcfx_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Beetle PSX HW DefaultArguments: '-L ".\cores\mednafen_psx_hw_libretro.dll" "{ImagePath}"' Platforms: [Sony PlayStation] - ImageExtensions: [cue, toc, m3u, ccd, exe, pbp, chd] + ImageExtensions: [cue, toc, m3u, ccd, exe, pbp, chd, zip, 7z] RequiredFiles: ['cores\mednafen_psx_hw_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Beetle PSX DefaultArguments: '-L ".\cores\mednafen_psx_libretro.dll" "{ImagePath}"' Platforms: [Sony PlayStation] - ImageExtensions: [cue, toc, m3u, ccd, exe, pbp, chd] + ImageExtensions: [cue, toc, m3u, ccd, exe, pbp, chd, zip, 7z] RequiredFiles: ['cores\mednafen_psx_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Beetle Saturn DefaultArguments: '-L ".\cores\mednafen_saturn_libretro.dll" "{ImagePath}"' Platforms: [Sega Saturn] - ImageExtensions: [cue, toc, m3u, ccd, chd] + ImageExtensions: [cue, toc, m3u, ccd, chd, zip, 7z] RequiredFiles: ['cores\mednafen_saturn_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Beetle bsnes DefaultArguments: '-L ".\cores\mednafen_snes_libretro.dll" "{ImagePath}"' Platforms: [Super Nintendo Entertainment System, Nintendo Sufami Turbo] - ImageExtensions: [smc, fig, bs, st, sfc] + ImageExtensions: [smc, fig, bs, st, sfc, zip, 7z] RequiredFiles: ['cores\mednafen_snes_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Beetle SGX DefaultArguments: '-L ".\cores\mednafen_supergrafx_libretro.dll" "{ImagePath}"' Platforms: [NEC PC Engine SuperGrafx, NEC TurboGrafx 16, NEC TurboGrafx-CD] - ImageExtensions: [pce, sgx, cue, ccd, chd] + ImageExtensions: [pce, sgx, cue, ccd, chd, zip, 7z] RequiredFiles: ['cores\mednafen_supergrafx_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Beetle VB DefaultArguments: '-L ".\cores\mednafen_vb_libretro.dll" "{ImagePath}"' Platforms: [Nintendo Virtual Boy] - ImageExtensions: [vb, vboy, bin] + ImageExtensions: [vb, vboy, bin, zip, 7z] RequiredFiles: ['cores\mednafen_vb_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Beetle WonderSwan DefaultArguments: '-L ".\cores\mednafen_wswan_libretro.dll" "{ImagePath}"' Platforms: [Bandai WonderSwan, Bandai WonderSwan Color] - ImageExtensions: [ws, wsc, pc2] + ImageExtensions: [ws, wsc, pc2, zip, 7z] RequiredFiles: ['cores\mednafen_wswan_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: melonDS DefaultArguments: '-L ".\cores\melonds_libretro.dll" "{ImagePath}"' Platforms: [Nintendo DS] - ImageExtensions: [nds] + ImageExtensions: [nds, zip, 7z] RequiredFiles: ['cores\melonds_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Mesen DefaultArguments: '-L ".\cores\mesen_libretro.dll" "{ImagePath}"' Platforms: [Nintendo Entertainment System, Nintendo Family Computer Disk System] - ImageExtensions: [nes, fds, unf, unif] + ImageExtensions: [nes, fds, unf, unif, zip, 7z] RequiredFiles: ['cores\mesen_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ @@ -1106,273 +1127,259 @@ - Name: Meteor DefaultArguments: '-L ".\cores\meteor_libretro.dll" "{ImagePath}"' Platforms: [Nintendo Game Boy Advance] - ImageExtensions: [gba] + ImageExtensions: [gba, zip, 7z] RequiredFiles: ['cores\meteor_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: mGBA DefaultArguments: '-L ".\cores\mgba_libretro.dll" "{ImagePath}"' Platforms: [Nintendo Game Boy, Nintendo Game Boy Color, Nintendo Game Boy Advance] - ImageExtensions: [gb, gbc, gba] + ImageExtensions: [gb, gbc, gba, zip, 7z] RequiredFiles: ['cores\mgba_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Mupen64Plus DefaultArguments: '-L ".\cores\mupen64plus_gles3_libretro.dll" "{ImagePath}"' Platforms: [Nintendo 64, Nintendo 64DD] - ImageExtensions: [n64, v64, z64, bin, u1, ndd] + ImageExtensions: [n64, v64, z64, bin, u1, ndd, zip, 7z] RequiredFiles: ['cores\mupen64plus_gles3_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Mupen64Plus OpenGL DefaultArguments: '-L ".\cores\mupen64plus_libretro.dll" "{ImagePath}"' Platforms: [Nintendo 64, Nintendo 64DD] - ImageExtensions: [n64, v64, z64, bin, u1, ndd] + ImageExtensions: [n64, v64, z64, bin, u1, ndd, zip, 7z] RequiredFiles: ['cores\mupen64plus_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Neko Project II DefaultArguments: '-L ".\cores\nekop2_libretro.dll" "{ImagePath}"' Platforms: [NEC PC-9801] - ImageExtensions: [d98, zip, 98d, fdi, fdd, 2hd, tfd, d88, 88d, hdm, xdf, dup, cmd, hdi, thd, nhd, hdd] + ImageExtensions: [d98, zip, 98d, fdi, fdd, 2hd, tfd, d88, 88d, hdm, xdf, dup, cmd, hdi, thd, nhd, hdd, 7z] RequiredFiles: ['cores\nekop2_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Nestopia DefaultArguments: '-L ".\cores\nestopia_libretro.dll" "{ImagePath}"' Platforms: [Nintendo Entertainment System, Nintendo Family Computer Disk System] - ImageExtensions: [nes, fds, unf, unif] + ImageExtensions: [nes, fds, unf, unif, zip, 7z] RequiredFiles: ['cores\nestopia_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Neko Project II DefaultArguments: '-L ".\cores\np2kai_libretro.dll" "{ImagePath}"' Platforms: [NEC PC-9801] - ImageExtensions: [d98, zip, 98d, fdi, fdd, 2hd, tfd, d88, 88d, hdm, xdf, dup, cmd, hdi, thd, nhd, hdd, hdn] + ImageExtensions: [d98, zip, 98d, fdi, fdd, 2hd, tfd, d88, 88d, hdm, xdf, dup, cmd, hdi, thd, nhd, hdd, hdn, 7z] RequiredFiles: ['cores\np2kai_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: O2EM DefaultArguments: '-L ".\cores\o2em_libretro.dll" "{ImagePath}"' Platforms: [Magnavox Odyssey2, Phillips Videopac+] - ImageExtensions: [bin] + ImageExtensions: [bin, zip, 7z] RequiredFiles: ['cores\o2em_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: ParaLLEl (Debug) DefaultArguments: '-L ".\cores\parallel_n64_debug_libretro.dll" "{ImagePath}"' Platforms: [Nintendo 64, Nintendo 64DD] - ImageExtensions: [n64, v64, z64, bin, u1, ndd] + ImageExtensions: [n64, v64, z64, bin, u1, ndd, zip, 7z] RequiredFiles: ['cores\parallel_n64_debug_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: ParaLLEl N64 DefaultArguments: '-L ".\cores\parallel_n64_libretro.dll" "{ImagePath}"' Platforms: [Nintendo 64, Nintendo 64DD] - ImageExtensions: [n64, v64, z64, bin, u1, ndd] + ImageExtensions: [n64, v64, z64, bin, u1, ndd, zip, 7z] RequiredFiles: ['cores\parallel_n64_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: PCem DefaultArguments: '-L ".\cores\pcem_libretro.dll" "{ImagePath}"' Platforms: [IBM PC] - ImageExtensions: [exe, com, bat, conf] + ImageExtensions: [exe, com, bat, conf, zip, 7z] RequiredFiles: ['cores\pcem_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: PCSX1 DefaultArguments: '-L ".\cores\pcsx1_libretro.dll" "{ImagePath}"' Platforms: [Sony PlayStation] - ImageExtensions: [bin, cue, img, mdf, pbp, toc, cbn, m3u] + ImageExtensions: [bin, cue, img, mdf, pbp, toc, cbn, m3u, zip, 7z] RequiredFiles: ['cores\pcsx1_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: PCSX ReARMed [Interpreter] DefaultArguments: '-L ".\cores\pcsx_rearmed_interpreter_libretro.dll" "{ImagePath}"' Platforms: [Sony PlayStation] - ImageExtensions: [bin, cue, img, mdf, pbp, cbn, toc] + ImageExtensions: [bin, cue, img, mdf, pbp, cbn, toc, zip, 7z] RequiredFiles: ['cores\pcsx_rearmed_interpreter_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: PCSX ReARMed DefaultArguments: '-L ".\cores\pcsx_rearmed_libretro.dll" "{ImagePath}"' Platforms: [Sony PlayStation] - ImageExtensions: [bin, cue, img, mdf, pbp, toc, cbn, m3u] + ImageExtensions: [bin, cue, img, mdf, pbp, toc, cbn, m3u, zip, 7z] RequiredFiles: ['cores\pcsx_rearmed_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: PCSX ReARMed [NEON] DefaultArguments: '-L ".\cores\pcsx_rearmed_libretro_neon.dll" "{ImagePath}"' Platforms: [Sony PlayStation] - ImageExtensions: [bin, cue, img, mdf, pbp, toc, cbn, m3u] + ImageExtensions: [bin, cue, img, mdf, pbp, toc, cbn, m3u, zip, 7z] RequiredFiles: ['cores\pcsx_rearmed_libretro_neon.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: PicoDrive DefaultArguments: '-L ".\cores\picodrive_libretro.dll" "{ImagePath}"' Platforms: [Sega Master System, Sega CD, Sega Genesis, Sega PICO, Sega 32X] - ImageExtensions: [bin, gen, smd, md, 32x, cue, iso, sms, 68k] + ImageExtensions: [bin, gen, smd, md, 32x, cue, iso, sms, 68k, zip, 7z] RequiredFiles: ['cores\picodrive_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: PPSSPP DefaultArguments: '-L ".\cores\ppsspp_libretro.dll" "{ImagePath}"' Platforms: [Sony PSP] - ImageExtensions: [elf, iso, cso, prx, pbp] + ImageExtensions: [elf, iso, cso, prx, pbp, zip, 7z] RequiredFiles: ['cores\ppsspp_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: ProSystem DefaultArguments: '-L ".\cores\prosystem_libretro.dll" "{ImagePath}"' Platforms: [Atari 7800] - ImageExtensions: [a78, bin] + ImageExtensions: [a78, bin, zip, 7z] RequiredFiles: ['cores\prosystem_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - - Name: PSP1 - DefaultArguments: '-L ".\cores\psp1_libretro.dll" "{ImagePath}"' - Platforms: [Sony PSP] - ImageExtensions: [elf, iso, cso, prx, pbp] - RequiredFiles: ['cores\psp1_libretro.dll'] - ExecutableLookup: ^retroarch\.exe$ - - Name: PUAE DefaultArguments: '-L ".\cores\puae_libretro.dll" "{ImagePath}"' Platforms: [Commodore Amiga] - ImageExtensions: [adf, dms, fdi, ipf, zip, uae] + ImageExtensions: [adf, dms, fdi, ipf, zip, uae, 7z] RequiredFiles: ['cores\puae_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: PX68k DefaultArguments: '-L ".\cores\px68k_libretro.dll" "{ImagePath}"' Platforms: [Sharp X68000] - ImageExtensions: [dim, zip, img, d88, 88d, hdm, dup, 2hd, xdf, hdf, cmd, m3u] + ImageExtensions: [dim, zip, img, d88, 88d, hdm, dup, 2hd, xdf, hdf, cmd, m3u, 7z] RequiredFiles: ['cores\px68k_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: QuickNES DefaultArguments: '-L ".\cores\quicknes_libretro.dll" "{ImagePath}"' Platforms: [Nintendo Entertainment System] - ImageExtensions: [nes] + ImageExtensions: [nes, zip, 7z] RequiredFiles: ['cores\quicknes_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Redream DefaultArguments: '-L ".\cores\redream_libretro.dll" "{ImagePath}"' Platforms: [Sega Dreamcast] - ImageExtensions: [gdi, chd, cdi] + ImageExtensions: [gdi, chd, cdi, zip, 7z] RequiredFiles: ['cores\redream_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Reicast DefaultArguments: '-L ".\cores\reicast_libretro.dll" "{ImagePath}"' - Platforms: [Sega Dreamcast] - ImageExtensions: [cdi, gdi, chd, cue] + Platforms: [Sega Dreamcast, Sega NAOMI] + ImageExtensions: [chd, cdi, iso, elf, bin, cue, gdi, lst, zip, 7z] RequiredFiles: ['cores\reicast_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Reicast NAOMI DefaultArguments: '-L ".\cores\reicast_naomi_libretro.dll" "{ImagePath}"' Platforms: [Sega NAOMI] - ImageExtensions: [chd, cdi, iso, elf, bin, cue, gdi] + ImageExtensions: [chd, cdi, iso, elf, bin, cue, gdi, zip, 7z] RequiredFiles: ['cores\reicast_naomi_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - - Name: REminiscence - DefaultArguments: '-L ".\cores\reminiscence_libretro.dll" "{ImagePath}"' - Platforms: [Flashback] - ImageExtensions: [map] - RequiredFiles: ['cores\reminiscence_libretro.dll'] + - Name: Reicast OIT + DefaultArguments: '-L ".\cores\reicast_oit_libretro.dll" "{ImagePath}"' + Platforms: [Sega Dreamcast, Sega NAOMI] + ImageExtensions: [chd, cdi, iso, elf, bin, cue, gdi, lst, zip, 7z] + RequiredFiles: ['cores\reicast_oit_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Rustation DefaultArguments: '-L ".\cores\rustation_libretro.dll" "{ImagePath}"' Platforms: [Sony PlayStation] - ImageExtensions: [cue, toc, m3u, ccd, exe] + ImageExtensions: [cue, toc, m3u, ccd, exe, zip, 7z] RequiredFiles: ['cores\rustation_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: SameBoy DefaultArguments: '-L ".\cores\sameboy_libretro.dll" "{ImagePath}"' Platforms: [Nintendo Game Boy, Nintendo Game Boy Color] - ImageExtensions: [gb, gbc] + ImageExtensions: [gb, gbc, zip, 7z] RequiredFiles: ['cores\sameboy_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Snes9x 2002 DefaultArguments: '-L ".\cores\snes9x2002_libretro.dll" "{ImagePath}"' Platforms: [Super Nintendo Entertainment System, Nintendo Sufami Turbo] - ImageExtensions: [smc, fig, sfc, gd3, gd7, dx2, bsx, swc] + ImageExtensions: [smc, fig, sfc, gd3, gd7, dx2, bsx, swc, zip, 7z] RequiredFiles: ['cores\snes9x2002_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Snes9x 2005 DefaultArguments: '-L ".\cores\snes9x2005_libretro.dll" "{ImagePath}"' Platforms: [Super Nintendo Entertainment System, Nintendo Sufami Turbo] - ImageExtensions: [smc, fig, sfc, gd3, gd7, dx2, bsx, swc] + ImageExtensions: [smc, fig, sfc, gd3, gd7, dx2, bsx, swc, zip, 7z] RequiredFiles: ['cores\snes9x2005_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Snes9x 2005 Plus DefaultArguments: '-L ".\cores\snes9x2005_plus_libretro.dll" "{ImagePath}"' Platforms: [Super Nintendo Entertainment System, Nintendo Sufami Turbo] - ImageExtensions: [smc, fig, sfc, gd3, gd7, dx2, bsx, swc] + ImageExtensions: [smc, fig, sfc, gd3, gd7, dx2, bsx, swc, zip, 7z] RequiredFiles: ['cores\snes9x2005_plus_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Snes9x 2010 DefaultArguments: '-L ".\cores\snes9x2010_libretro.dll" "{ImagePath}"' Platforms: [Super Nintendo Entertainment System, Nintendo Sufami Turbo] - ImageExtensions: [smc, fig, sfc, gd3, gd7, dx2, bsx, swc] + ImageExtensions: [smc, fig, sfc, gd3, gd7, dx2, bsx, swc, zip, 7z] RequiredFiles: ['cores\snes9x2010_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Snes9x DefaultArguments: '-L ".\cores\snes9x_libretro.dll" "{ImagePath}"' Platforms: [Super Nintendo Entertainment System, Nintendo Sufami Turbo, Nintendo Satellaview] - ImageExtensions: [smc, sfc, swc, fig, bs] + ImageExtensions: [smc, sfc, swc, fig, bs, zip, 7z] RequiredFiles: ['cores\snes9x_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Stella DefaultArguments: '-L ".\cores\stella_libretro.dll" "{ImagePath}"' Platforms: [Atari 2600] - ImageExtensions: [a26, bin] + ImageExtensions: [a26, bin, zip, 7z] RequiredFiles: ['cores\stella_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: TempGBA DefaultArguments: '-L ".\cores\tempgba_libretro.dll" "{ImagePath}"' Platforms: [Nintendo Game Boy Advance] - ImageExtensions: [gba, bin, agb, gbz] + ImageExtensions: [gba, bin, agb, gbz, zip, 7z] RequiredFiles: ['cores\tempgba_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: TGB Dual DefaultArguments: '-L ".\cores\tgbdual_libretro.dll" "{ImagePath}"' Platforms: [Nintendo Game Boy, Nintendo Game Boy Color] - ImageExtensions: [gb, gbc, sgb] + ImageExtensions: [gb, gbc, sgb, zip, 7z] RequiredFiles: ['cores\tgbdual_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: Theodore DefaultArguments: '-L ".\cores\theodore_libretro.dll" "{ImagePath}"' Platforms: [Thomson TO8D] - ImageExtensions: [fd, sap, k7, m7, rom] + ImageExtensions: [fd, sap, k7, m7, rom, zip, 7z] RequiredFiles: ['cores\theodore_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - - Name: ThePowderToy - DefaultArguments: '-L ".\cores\thepowdertoy_libretro.dll" "{ImagePath}"' - Platforms: [ Physics Toy] - ImageExtensions: [cps] - RequiredFiles: ['cores\thepowdertoy_libretro.dll'] - ExecutableLookup: ^retroarch\.exe$ - - Name: UAE4ARM DefaultArguments: '-L ".\cores\uae4arm_libretro.dll" "{ImagePath}"' Platforms: [Commodore Amiga] - ImageExtensions: [adf, dms, zip, ipf, adz, uae] + ImageExtensions: [adf, dms, zip, ipf, adz, uae, 7z] RequiredFiles: ['cores\uae4arm_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ @@ -1386,35 +1393,35 @@ - Name: uzem DefaultArguments: '-L ".\cores\uzem_libretro.dll" "{ImagePath}"' Platforms: [Uzebox] - ImageExtensions: [uze] + ImageExtensions: [uze, zip, 7z] RequiredFiles: ['cores\uzem_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: VBA-M DefaultArguments: '-L ".\cores\vbam_libretro.dll" "{ImagePath}"' Platforms: [Nintendo Game Boy Advance] - ImageExtensions: [gba] + ImageExtensions: [gba, zip, 7z] RequiredFiles: ['cores\vbam_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: VBA Next DefaultArguments: '-L ".\cores\vba_next_libretro.dll" "{ImagePath}"' Platforms: [Nintendo Game Boy Advance] - ImageExtensions: [gba] + ImageExtensions: [gba, zip, 7z] RequiredFiles: ['cores\vba_next_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: vecx DefaultArguments: '-L ".\cores\vecx_libretro.dll" "{ImagePath}"' Platforms: [GCE Vectrex] - ImageExtensions: [bin, vec] + ImageExtensions: [bin, vec, zip, 7z] RequiredFiles: ['cores\vecx_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ - Name: VeMUlator DefaultArguments: '-L ".\cores\vemulator_libretro.dll" "{ImagePath}"' Platforms: [ SEGA Visual Memory Unit] - ImageExtensions: [vms, dci, bin] + ImageExtensions: [vms, dci, bin, zip, 7z] RequiredFiles: ['cores\vemulator_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ @@ -1449,13 +1456,20 @@ - Name: Virtual Jaguar DefaultArguments: '-L ".\cores\virtualjaguar_libretro.dll" "{ImagePath}"' Platforms: [Atari Jaguar] - ImageExtensions: [j64, jag, rom, abs, cof, bin, prg] + ImageExtensions: [j64, jag, rom, abs, cof, bin, prg, zip, 7z] RequiredFiles: ['cores\virtualjaguar_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ + - Name: VICE SDL + DefaultArguments: '-L ".\cores\x64sdl_libretro.dll" "{ImagePath}"' + Platforms: [Commodore 64] + ImageExtensions: [d64, d71, d80, d81, d82, g64, g41, x64, t64, tap, prg, p00, crt, bin, zip, gz, d6z, d7z, d8z, g6z, g4z, x6z] + RequiredFiles: ['cores\x64sdl_libretro.dll'] + ExecutableLookup: ^retroarch\.exe$ + - Name: Yabause DefaultArguments: '-L ".\cores\yabause_libretro.dll" "{ImagePath}"' Platforms: [Sega Saturn] - ImageExtensions: [bin, cue, iso] + ImageExtensions: [bin, cue, iso, zip, 7z] RequiredFiles: ['cores\yabause_libretro.dll'] ExecutableLookup: ^retroarch\.exe$ \ No newline at end of file diff --git a/source/Playnite/Emulators/EmulatorFinder.cs b/source/Playnite/Emulators/EmulatorFinder.cs index 69e4a463e..8a79db037 100644 --- a/source/Playnite/Emulators/EmulatorFinder.cs +++ b/source/Playnite/Emulators/EmulatorFinder.cs @@ -158,7 +158,7 @@ public static async Task> SearchForGames(DirectoryInfoBase path, Emul { var newGame = new Game() { - Name = StringExtensions.NormalizeGameName(Path.GetFileNameWithoutExtension(file.Name)), + Name = StringExtensions.NormalizeGameName(StringExtensions.GetPathWithoutAllExtensions(Path.GetFileName(file.Name))), IsoPath = file.FullName, InstallDirectory = Path.GetDirectoryName(file.FullName) }; diff --git a/source/Playnite/Extensions/ListExtensions.cs b/source/Playnite/Extensions/ListExtensions.cs index 924497371..644d180d3 100644 --- a/source/Playnite/Extensions/ListExtensions.cs +++ b/source/Playnite/Extensions/ListExtensions.cs @@ -57,5 +57,10 @@ public static bool IntersectsExactlyWith(this List source, List return intersects; } + + public static bool ContainsInsensitive(this List source, string value) + { + return source.Any(a => a.Equals(value, StringComparison.InvariantCultureIgnoreCase)) == true; + } } } diff --git a/source/Playnite/Extensions/StringExtensions.cs b/source/Playnite/Extensions/StringExtensions.cs index 108b36b12..b91c942a0 100644 --- a/source/Playnite/Extensions/StringExtensions.cs +++ b/source/Playnite/Extensions/StringExtensions.cs @@ -75,5 +75,15 @@ public static string GetSHA256Hash(this string input) return BitConverter.ToString(hash).Replace("-", ""); } } + + public static string GetPathWithoutAllExtensions(string path) + { + if (string.IsNullOrEmpty(path)) + { + return string.Empty; + } + + return Regex.Replace(path, @"(\.[a-z0-9]+)+$", ""); + } } } diff --git a/source/Playnite/GameHandler.cs b/source/Playnite/GameHandler.cs index f23bf858d..805f7da8a 100644 --- a/source/Playnite/GameHandler.cs +++ b/source/Playnite/GameHandler.cs @@ -16,13 +16,12 @@ public class GameHandler public static Process ActivateTask(GameTask task) { + logger.Info($"Activating game task {task}"); switch (task.Type) { case GameTaskType.File: - logger.Info($"Starting process: {task.Path}, {task.Arguments}, {task.WorkingDir}"); return ProcessStarter.StartProcess(task.Path, task.Arguments, task.WorkingDir); case GameTaskType.URL: - logger.Info($"Opening URL {task.Path}"); return ProcessStarter.StartUrl(task.Path); case GameTaskType.Emulator: throw new Exception("Cannot start emulated game without emulator."); @@ -33,17 +32,16 @@ public static Process ActivateTask(GameTask task) public static Process ActivateTask(GameTask task, Game gameData) { + logger.Info($"Activating game task {task}"); switch (task.Type) { case GameTaskType.File: var path = gameData.ResolveVariables(task.Path); var arguments = gameData.ResolveVariables(task.Arguments); var workdir = gameData.ResolveVariables(task.WorkingDir); - logger.Info($"Starting process: {path}, {arguments}, {workdir}"); return ProcessStarter.StartProcess(path, arguments, workdir); case GameTaskType.URL: var url = gameData.ResolveVariables(task.Path); - logger.Info($"Opening URL {url}"); return ProcessStarter.StartUrl(url); case GameTaskType.Emulator: throw new Exception("Cannot start emulated game without emulator."); @@ -54,6 +52,7 @@ public static Process ActivateTask(GameTask task, Game gameData) public static Process ActivateTask(GameTask task, Game gameData, EmulatorProfile config) { + logger.Info($"Activating game task {task}"); switch (task.Type) { case GameTaskType.File: @@ -78,7 +77,6 @@ public static Process ActivateTask(GameTask task, Game gameData, EmulatorProfile } var workdir = gameData.ResolveVariables(config.WorkingDirectory); - logger.Info($"Starting emulator: {path}, {arguments}, {workdir}"); return ProcessStarter.StartProcess(path, arguments, workdir); } diff --git a/source/Playnite/MetaProviders/IGDBMetadataDownloader.cs b/source/Playnite/MetaProviders/IGDBMetadataDownloader.cs deleted file mode 100644 index 2e29e9bbe..000000000 --- a/source/Playnite/MetaProviders/IGDBMetadataDownloader.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Playnite.Services; -using System.Collections.Concurrent; -using System.Collections.ObjectModel; -using Playnite.Models; -using System.Globalization; -using Playnite.Database; -using System.IO; -using Playnite.SDK.Models; -using Playnite.SDK; -using Playnite.Web; - -namespace Playnite.MetaProviders -{ - public class IGDBMetadataProvider : IMetadataProvider - { - private ServicesClient client; - private string apiKey; - - public IGDBMetadataProvider() - { - client = new ServicesClient(); - } - - public IGDBMetadataProvider(string apiKey) - { - client = new ServicesClient(); - this.apiKey = apiKey; - } - - public IGDBMetadataProvider(ServicesClient client) - { - this.client = client; - } - - public IGDBMetadataProvider(ServicesClient client, string apiKey) - { - this.client = client; - this.apiKey = apiKey; - } - - public List Search(string game) - { - return client.GetIGDBGames(game, apiKey); - } - - public Game GetParsedGame(ulong id) - { - var dbGame = client.GetIGDBGameParsed(id, apiKey); - var game = new Game() - { - Name = dbGame.name, - Description = dbGame.summary?.Replace("\n", "\n
") - }; - - if (dbGame.cover != null) - { - game.Image = dbGame.cover.Replace("t_thumb", "t_cover_big"); - if (!game.Image.StartsWith("https:", StringComparison.InvariantCultureIgnoreCase)) - { - game.Image = "https:" + game.Image; - } - } - - if (dbGame.first_release_date != 0) - { - game.ReleaseDate = DateTimeOffset.FromUnixTimeMilliseconds(dbGame.first_release_date).DateTime; - } - - if (dbGame.developers?.Any() == true) - { - game.Developers = new ComparableList(dbGame.developers); - } - - if (dbGame.publishers?.Any() == true) - { - game.Publishers = new ComparableList(dbGame.publishers); - } - - if (dbGame.genres?.Any() == true) - { - game.Genres = new ComparableList(dbGame.genres); - } - - if (dbGame.websites?.Any() == true) - { - game.Links = new ObservableCollection(dbGame.websites.Select(a => new Link(a.category.ToString(), a.url))); - } - - if (dbGame.game_modes?.Any() == true) - { - var cultInfo = new CultureInfo("en-US", false).TextInfo; - game.Tags = new ComparableList(dbGame.game_modes.Select(a => cultInfo.ToTitleCase(a))); - } - - if (dbGame.aggregated_rating != 0) - { - game.CriticScore = Convert.ToInt32(dbGame.aggregated_rating); - } - - if (dbGame.rating != 0) - { - game.CommunityScore = Convert.ToInt32(dbGame.rating); - } - - return game; - } - - public bool GetSupportsIdSearch() - { - return false; - } - - public List SearchGames(string gameName) - { - return Search(gameName)?.Select(a => new MetadataSearchResult() - { - Id = a.id.ToString(), - Name = a.name, - ReleaseDate = a.first_release_date == 0 ? (DateTime?)null : DateTimeOffset.FromUnixTimeMilliseconds(a.first_release_date).DateTime - }).ToList(); - } - - public GameMetadata GetGameData(string gameId) - { - var game = GetParsedGame(ulong.Parse(gameId)); - FileDefinition image = null; - if (!string.IsNullOrEmpty(game.Image)) - { - var name = Path.GetFileName(game.Image); - image = new FileDefinition($"images/custom/{name}", name, HttpDownloader.DownloadData(game.Image)); - } - - return new GameMetadata(game, null, image, string.Empty); - } - } -} diff --git a/source/Playnite/Models/GameMetadata.cs b/source/Playnite/Metadata/GameMetadata.cs similarity index 59% rename from source/Playnite/Models/GameMetadata.cs rename to source/Playnite/Metadata/GameMetadata.cs index 27c9a3567..7cbc137fd 100644 --- a/source/Playnite/Models/GameMetadata.cs +++ b/source/Playnite/Metadata/GameMetadata.cs @@ -3,24 +3,33 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using Playnite.Database; using Playnite.SDK.Models; -namespace Playnite.Models +namespace Playnite.Metadata { public class GameMetadata { + public static GameMetadata Empty + { + get => new GameMetadata(); + } + + public bool IsEmpty + { + get => GameData == null; + } + public Game GameData { get; set; } - public FileDefinition Icon + public MetadataFile Icon { get; set; } - public FileDefinition Image + public MetadataFile Image { get; set; } @@ -34,7 +43,7 @@ public GameMetadata() { } - public GameMetadata(Game gameData, FileDefinition icon, FileDefinition image, string background) + public GameMetadata(Game gameData, MetadataFile icon, MetadataFile image, string background) { GameData = gameData; Icon = icon; diff --git a/source/Playnite/Metadata/IMetadataProvider.cs b/source/Playnite/Metadata/IMetadataProvider.cs new file mode 100644 index 000000000..51440ca31 --- /dev/null +++ b/source/Playnite/Metadata/IMetadataProvider.cs @@ -0,0 +1,16 @@ +using Playnite.SDK.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Playnite.Metadata +{ + public interface IMetadataProvider + { + ICollection SearchMetadata(Game game); + GameMetadata GetMetadata(string metadataId); + GameMetadata GetMetadata(Game game); + } +} diff --git a/source/Playnite/MetaProviders/MetadataDownloader.cs b/source/Playnite/Metadata/MetadataDownloader.cs similarity index 57% rename from source/Playnite/MetaProviders/MetadataDownloader.cs rename to source/Playnite/Metadata/MetadataDownloader.cs index b66533970..73957e792 100644 --- a/source/Playnite/MetaProviders/MetadataDownloader.cs +++ b/source/Playnite/Metadata/MetadataDownloader.cs @@ -5,6 +5,7 @@ using Playnite.Providers.GOG; using Playnite.Providers.Origin; using Playnite.Providers.Steam; +using Playnite.Metadata.Providers; using System; using System.Collections.Generic; using System.Linq; @@ -15,303 +16,8 @@ using Playnite.SDK.Models; using Playnite.SDK; -namespace Playnite.MetaProviders +namespace Playnite.Metadata { - public interface IMetadataProvider - { - bool GetSupportsIdSearch(); - List SearchGames(string gameName); - GameMetadata GetGameData(string gameId); - } - - public enum MetadataGamesSource - { - AllFromDB, - Selected, - Filtered - } - - public enum MetadataSource - { - Store, - IGDB, - IGDBOverStore, - StoreOverIGDB - } - - public class MetadataFieldSettings : ObservableObject - { - private bool import = true; - public bool Import - { - get => import; - set - { - import = value; - OnPropertyChanged("Import"); - } - } - - private MetadataSource source = MetadataSource.StoreOverIGDB; - public MetadataSource Source - { - get => source; - set - { - source = value; - OnPropertyChanged("Source"); - } - } - - public MetadataFieldSettings() - { - } - - public MetadataFieldSettings(bool import, MetadataSource source) - { - Import = import; - Source = source; - } - } - - public class MetadataDownloaderSettings : ObservableObject - { - private MetadataGamesSource gamesSource = MetadataGamesSource.AllFromDB; - public MetadataGamesSource GamesSource - { - get - { - return gamesSource; - } - - set - { - gamesSource = value; - OnPropertyChanged("GamesSource"); - } - } - - private bool skipExistingValues = true; - public bool SkipExistingValues - { - get - { - return skipExistingValues; - } - - set - { - skipExistingValues = value; - OnPropertyChanged("SkipExistingValues"); - } - } - - private MetadataFieldSettings name = new MetadataFieldSettings(false, MetadataSource.Store); - public MetadataFieldSettings Name - { - get => name; - set - { - name = value; - OnPropertyChanged("Name"); - } - } - - private MetadataFieldSettings genre = new MetadataFieldSettings(); - public MetadataFieldSettings Genre - { - get => genre; - set - { - genre = value; - OnPropertyChanged("Genre"); - } - } - - private MetadataFieldSettings releaseDate = new MetadataFieldSettings(); - public MetadataFieldSettings ReleaseDate - { - get => releaseDate; - set - { - releaseDate = value; - OnPropertyChanged("ReleaseDate"); - } - } - - private MetadataFieldSettings developer = new MetadataFieldSettings(); - public MetadataFieldSettings Developer - { - get => developer; - set - { - developer = value; - OnPropertyChanged("Developer"); - } - } - - private MetadataFieldSettings publisher = new MetadataFieldSettings(); - public MetadataFieldSettings Publisher - { - get => publisher; - set - { - publisher = value; - OnPropertyChanged("Publisher"); - } - } - - private MetadataFieldSettings tag = new MetadataFieldSettings(); - public MetadataFieldSettings Tag - { - get => tag; - set - { - tag = value; - OnPropertyChanged("Tag"); - } - } - - private MetadataFieldSettings description = new MetadataFieldSettings(); - public MetadataFieldSettings Description - { - get => description; - set - { - description = value; - OnPropertyChanged("Description"); - } - } - - private MetadataFieldSettings coverImage = new MetadataFieldSettings() { Source = MetadataSource.IGDBOverStore }; - public MetadataFieldSettings CoverImage - { - get => coverImage; - set - { - coverImage = value; - OnPropertyChanged("CoverImage"); - } - } - - private MetadataFieldSettings backgroundImage = new MetadataFieldSettings() { Source = MetadataSource.Store }; - public MetadataFieldSettings BackgroundImage - { - get => backgroundImage; - set - { - backgroundImage = value; - OnPropertyChanged("BackgroundImage"); - } - } - - private MetadataFieldSettings icon = new MetadataFieldSettings() { Source = MetadataSource.Store }; - public MetadataFieldSettings Icon - { - get => icon; - set - { - icon = value; - OnPropertyChanged("Icon"); - } - } - - private MetadataFieldSettings links = new MetadataFieldSettings(); - public MetadataFieldSettings Links - { - get => links; - set - { - links = value; - OnPropertyChanged("Links"); - } - } - - private MetadataFieldSettings criticScore = new MetadataFieldSettings(); - public MetadataFieldSettings CriticScore - { - get => criticScore; - set - { - criticScore = value; - OnPropertyChanged("CriticScore"); - } - } - - private MetadataFieldSettings communityScore = new MetadataFieldSettings(); - public MetadataFieldSettings CommunityScore - { - get => communityScore; - set - { - communityScore = value; - OnPropertyChanged("CommunityScore"); - } - } - - public void ConfigureFields(MetadataSource source, bool import) - { - Genre.Import = import; - Genre.Source = source; - Description.Import = import; - Description.Source = source; - Developer.Import = import; - Developer.Source = source; - Publisher.Import = import; - Publisher.Source = source; - Tag.Import = import; - Tag.Source = source; - Links.Import = import; - Links.Source = source; - CoverImage.Import = import; - CoverImage.Source = source; - BackgroundImage.Import = import; - BackgroundImage.Source = source; - Icon.Import = import; - Icon.Source = source; - ReleaseDate.Import = import; - ReleaseDate.Source = source; - CommunityScore.Import = import; - CommunityScore.Source = source; - CriticScore.Import = import; - CriticScore.Source = source; - } - } - - public class MetadataSearchResult - { - public string Id - { - get; set; - } - - public string Name - { - get; set; - } - - public DateTime? ReleaseDate - { - get; set; - } - - public MetadataSearchResult() - { - } - - public MetadataSearchResult(string id, string name, DateTime? releaseDate) - { - Id = id; - Name = name; - ReleaseDate = releaseDate; - } - - public override string ToString() - { - return Name; - } - } - public class MetadataDownloader { private static Logger logger = LogManager.GetCurrentClassLogger(); @@ -347,7 +53,7 @@ public MetadataDownloader( this.igdbProvider = igdbProvider; } - private IMetadataProvider GetMetaProviderByProvider(Provider provider) + internal IMetadataProvider GetMetaProviderByProvider(Provider provider) { switch (provider) { @@ -375,7 +81,7 @@ private GameMetadata ProcessDownload(Game game, ref GameMetadata data) { if (data == null) { - data = DownloadGameData(game.Name, game.ProviderId, GetMetaProviderByProvider(game.Provider)); + data = GetMetaProviderByProvider(game.Provider).GetMetadata(game); } return data; @@ -399,7 +105,7 @@ private GameMetadata ProcessField( { if (igdbData == null) { - igdbData = DownloadGameData(game.Name, "", igdbProvider); + igdbData = igdbProvider.GetMetadata(game); } return igdbData; @@ -408,7 +114,7 @@ private GameMetadata ProcessField( { if (igdbData == null) { - igdbData = DownloadGameData(game.Name, "", igdbProvider); + igdbData = igdbProvider.GetMetadata(game); } if (igdbData.GameData == null && game.Provider != Provider.Custom && game.Provider != Provider.Uplay) @@ -458,7 +164,7 @@ private GameMetadata ProcessField( { if (igdbData == null) { - igdbData = DownloadGameData(game.Name, "", igdbProvider); + igdbData = igdbProvider.GetMetadata(game); } return igdbData; @@ -474,7 +180,7 @@ private GameMetadata ProcessField( { if (igdbData == null) { - igdbData = DownloadGameData(game.Name, "", igdbProvider); + igdbData = igdbProvider.GetMetadata(game); } return igdbData; @@ -485,7 +191,7 @@ private GameMetadata ProcessField( { if (igdbData == null) { - igdbData = DownloadGameData(game.Name, "", igdbProvider); + igdbData = igdbProvider.GetMetadata(game); } return igdbData; @@ -496,7 +202,7 @@ private GameMetadata ProcessField( return null; } - public async Task DownloadMetadataThreaded( + public async Task DownloadMetadataGroupedAsync( List games, GameDatabase database, MetadataDownloaderSettings settings, @@ -510,12 +216,13 @@ public async Task DownloadMetadataThreaded( await Task.Run(() => { var grouped = games.GroupBy(a => a.Provider); + logger.Info($"Downloading metadata using {grouped.Count()} threads."); foreach (IGrouping group in grouped) { tasks.Add(Task.Run(() => { var gms = group.ToList(); - DownloadMetadata(gms, database, settings, (g, i, t) => + DownloadMetadataAsync(gms, database, settings, (g, i, t) => { index++; processCallback?.Invoke(g, index, total); @@ -527,7 +234,7 @@ await Task.Run(() => }); } - public async Task DownloadMetadata( + public async Task DownloadMetadataAsync( List games, GameDatabase database, MetadataDownloaderSettings settings, @@ -536,33 +243,36 @@ public async Task DownloadMetadata( { await Task.Run(() => { - if (games == null || games.Count == 0) - { - return; - } - - for (int i = 0; i < games.Count; i++) - { - if (cancelToken?.IsCancellationRequested == true) + if (games == null || games.Count == 0) { return; } - GameMetadata storeData = null; - GameMetadata igdbData = null; - GameMetadata gameData = null; - - // We need to get new instance from DB in case game got edited or deleted. - // We don't want to block game editing while metadata is downloading for other games. - var game = database.GamesCollection.FindOne(a => a.ProviderId == games[i].ProviderId); - if (game == null) + for (int i = 0; i < games.Count; i++) { - processCallback?.Invoke(null, i, games.Count); - continue; - } + if (cancelToken?.IsCancellationRequested == true) + { + return; + } + + GameMetadata storeData = null; + GameMetadata igdbData = null; + GameMetadata gameData = null; + + // We need to get new instance from DB in case game got edited or deleted. + // We don't want to block game editing while metadata is downloading for other games. + var game = database.GamesCollection.FindOne(a => a.ProviderId == games[i].ProviderId); + if (game == null) + { + logger.Warn($"Game {game.ProviderId} no longer in DB, skipping metadata download."); + processCallback?.Invoke(null, i, games.Count); + continue; + } try { + logger.Debug($"Downloading metadata for {game.Provider} game {game.Name}, {game.ProviderId}"); + // Name if (game.Provider != Provider.Custom && settings.Name.Import) { @@ -728,7 +438,7 @@ await Task.Run(() => { if (storeData == null) { - storeData = steamProvider.GetGameData(game.ProviderId); + storeData = steamProvider.GetMetadata(game.ProviderId); } if (storeData?.GameData?.OtherTasks != null) @@ -751,6 +461,10 @@ await Task.Run(() => { database.UpdateGameInDatabase(game); } + else + { + logger.Warn($"Game {game.ProviderId} no longer in DB, skipping metadata update in DB."); + } } catch (Exception e) when (!PlayniteEnvironment.ThrowAllErrors) { @@ -763,116 +477,5 @@ await Task.Run(() => } }); } - - public async Task DownloadMetadata( - List games, - GameDatabase database, - MetadataDownloaderSettings settings, - Action processCallback) - { - await DownloadMetadata(games, database, settings, processCallback, null); - } - - public async Task DownloadMetadata( - List games, - GameDatabase database, - MetadataDownloaderSettings settings) - { - await DownloadMetadata(games, database, settings, null, null); - } - - public virtual GameMetadata DownloadGameData(string gameName, string id, IMetadataProvider provider) - { - if (provider.GetSupportsIdSearch()) - { - return provider.GetGameData(id); - } - else - { - var name = StringExtensions.NormalizeGameName(gameName); - var results = provider.SearchGames(name); - results.ForEach(a => a.Name = StringExtensions.NormalizeGameName(a.Name)); - - GameMetadata matchFun(string matchName, List list) - { - var res = list.FirstOrDefault(a => string.Equals(matchName, a.Name, StringComparison.InvariantCultureIgnoreCase)); - if (res != null) - { - return provider.GetGameData(res.Id); - } - else - { - return null; - } - } - - GameMetadata data = null; - string testName = string.Empty; - - // Direct comparison - data = matchFun(name, results); - if (data != null) - { - return data; - } - - // Try replacing roman numerals: 3 => III - testName = Regex.Replace(name, @"\d+", ReplaceNumsForRomans); - data = matchFun(testName, results); - if (data != null) - { - return data; - } - - // Try adding The - testName = "The " + name; - data = matchFun(testName, results); - if (data != null) - { - return data; - } - - // Try chaning & / and - testName = Regex.Replace(name, @"\s+and\s+", " & ", RegexOptions.IgnoreCase); - data = matchFun(testName, results); - if (data != null) - { - return data; - } - - // Try removing all ":" - testName = Regex.Replace(testName, @"\s*:\s*", " "); - var resCopy = results.CloneJson(); - resCopy.ForEach(a => a.Name = Regex.Replace(a.Name, @"\s*:\s*", " ")); - data = matchFun(testName, resCopy); - if (data != null) - { - return data; - } - - // Try without subtitle - var testResult = results.OrderBy(a => a.ReleaseDate).FirstOrDefault(a => - { - if (a.ReleaseDate == null) - { - return false; - } - - if (!string.IsNullOrEmpty(a.Name) && a.Name.Contains(":")) - { - return string.Equals(name, a.Name.Split(':')[0], StringComparison.InvariantCultureIgnoreCase); - } - - return false; - }); - - if (testResult != null) - { - return provider.GetGameData(testResult.Id); - } - - return data ?? new GameMetadata(); - } - } } } diff --git a/source/Playnite/Metadata/MetadataDownloaderSettings.cs b/source/Playnite/Metadata/MetadataDownloaderSettings.cs new file mode 100644 index 000000000..3da00f5cd --- /dev/null +++ b/source/Playnite/Metadata/MetadataDownloaderSettings.cs @@ -0,0 +1,262 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Playnite.Metadata +{ + public enum MetadataGamesSource + { + AllFromDB, + Selected, + Filtered + } + + public enum MetadataSource + { + Store, + IGDB, + IGDBOverStore, + StoreOverIGDB + } + + public class MetadataFieldSettings : ObservableObject + { + private bool import = true; + public bool Import + { + get => import; + set + { + import = value; + OnPropertyChanged("Import"); + } + } + + private MetadataSource source = MetadataSource.StoreOverIGDB; + public MetadataSource Source + { + get => source; + set + { + source = value; + OnPropertyChanged("Source"); + } + } + + public MetadataFieldSettings() + { + } + + public MetadataFieldSettings(bool import, MetadataSource source) + { + Import = import; + Source = source; + } + } + + public class MetadataDownloaderSettings : ObservableObject + { + private MetadataGamesSource gamesSource = MetadataGamesSource.AllFromDB; + public MetadataGamesSource GamesSource + { + get + { + return gamesSource; + } + + set + { + gamesSource = value; + OnPropertyChanged("GamesSource"); + } + } + + private bool skipExistingValues = true; + public bool SkipExistingValues + { + get + { + return skipExistingValues; + } + + set + { + skipExistingValues = value; + OnPropertyChanged("SkipExistingValues"); + } + } + + private MetadataFieldSettings name = new MetadataFieldSettings(false, MetadataSource.Store); + public MetadataFieldSettings Name + { + get => name; + set + { + name = value; + OnPropertyChanged("Name"); + } + } + + private MetadataFieldSettings genre = new MetadataFieldSettings(); + public MetadataFieldSettings Genre + { + get => genre; + set + { + genre = value; + OnPropertyChanged("Genre"); + } + } + + private MetadataFieldSettings releaseDate = new MetadataFieldSettings(); + public MetadataFieldSettings ReleaseDate + { + get => releaseDate; + set + { + releaseDate = value; + OnPropertyChanged("ReleaseDate"); + } + } + + private MetadataFieldSettings developer = new MetadataFieldSettings(); + public MetadataFieldSettings Developer + { + get => developer; + set + { + developer = value; + OnPropertyChanged("Developer"); + } + } + + private MetadataFieldSettings publisher = new MetadataFieldSettings(); + public MetadataFieldSettings Publisher + { + get => publisher; + set + { + publisher = value; + OnPropertyChanged("Publisher"); + } + } + + private MetadataFieldSettings tag = new MetadataFieldSettings(); + public MetadataFieldSettings Tag + { + get => tag; + set + { + tag = value; + OnPropertyChanged("Tag"); + } + } + + private MetadataFieldSettings description = new MetadataFieldSettings(); + public MetadataFieldSettings Description + { + get => description; + set + { + description = value; + OnPropertyChanged("Description"); + } + } + + private MetadataFieldSettings coverImage = new MetadataFieldSettings() { Source = MetadataSource.IGDBOverStore }; + public MetadataFieldSettings CoverImage + { + get => coverImage; + set + { + coverImage = value; + OnPropertyChanged("CoverImage"); + } + } + + private MetadataFieldSettings backgroundImage = new MetadataFieldSettings() { Source = MetadataSource.Store }; + public MetadataFieldSettings BackgroundImage + { + get => backgroundImage; + set + { + backgroundImage = value; + OnPropertyChanged("BackgroundImage"); + } + } + + private MetadataFieldSettings icon = new MetadataFieldSettings() { Source = MetadataSource.Store }; + public MetadataFieldSettings Icon + { + get => icon; + set + { + icon = value; + OnPropertyChanged("Icon"); + } + } + + private MetadataFieldSettings links = new MetadataFieldSettings(); + public MetadataFieldSettings Links + { + get => links; + set + { + links = value; + OnPropertyChanged("Links"); + } + } + + private MetadataFieldSettings criticScore = new MetadataFieldSettings(); + public MetadataFieldSettings CriticScore + { + get => criticScore; + set + { + criticScore = value; + OnPropertyChanged("CriticScore"); + } + } + + private MetadataFieldSettings communityScore = new MetadataFieldSettings(); + public MetadataFieldSettings CommunityScore + { + get => communityScore; + set + { + communityScore = value; + OnPropertyChanged("CommunityScore"); + } + } + + public void ConfigureFields(MetadataSource source, bool import) + { + Genre.Import = import; + Genre.Source = source; + Description.Import = import; + Description.Source = source; + Developer.Import = import; + Developer.Source = source; + Publisher.Import = import; + Publisher.Source = source; + Tag.Import = import; + Tag.Source = source; + Links.Import = import; + Links.Source = source; + CoverImage.Import = import; + CoverImage.Source = source; + BackgroundImage.Import = import; + BackgroundImage.Source = source; + Icon.Import = import; + Icon.Source = source; + ReleaseDate.Import = import; + ReleaseDate.Source = source; + CommunityScore.Import = import; + CommunityScore.Source = source; + CriticScore.Import = import; + CriticScore.Source = source; + } + } +} diff --git a/source/Playnite/Metadata/MetadataFile.cs b/source/Playnite/Metadata/MetadataFile.cs new file mode 100644 index 000000000..6cab52587 --- /dev/null +++ b/source/Playnite/Metadata/MetadataFile.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Playnite.Metadata +{ + public class MetadataFile + { + public string FileId + { + get; set; + } + + public string FileName + { + get; set; + } + + public byte[] Content + { + get; set; + } + + public string OriginalUrl + { + get; set; + } + + public MetadataFile() + { + } + + public MetadataFile(string url, string fileId) + { + FileId = fileId; + FileName = Path.GetFileName(url); + } + + public MetadataFile(string fileId, string name, byte[] data) : this(fileId, name, data, null) + { + } + + public MetadataFile(string fileId, string name, byte[] data, string originalUrl) + { + FileId = fileId; + FileName = name; + Content = data; + OriginalUrl = originalUrl; + } + } +} diff --git a/source/Playnite/Metadata/MetadataSearchResult.cs b/source/Playnite/Metadata/MetadataSearchResult.cs new file mode 100644 index 000000000..5f68a5eaa --- /dev/null +++ b/source/Playnite/Metadata/MetadataSearchResult.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Playnite.Metadata +{ + public class MetadataSearchResult + { + public string Id + { + get; set; + } + + public string Name + { + get; set; + } + + public DateTime? ReleaseDate + { + get; set; + } + + public List AlternativeNames + { + get; set; + } + + public MetadataSearchResult() + { + } + + public MetadataSearchResult(string id, string name, DateTime? releaseDate, List alternativeNames) + { + Id = id; + Name = name; + ReleaseDate = releaseDate; + AlternativeNames = alternativeNames; + } + + public override string ToString() + { + return Name; + } + } +} diff --git a/source/Playnite/Metadata/Providers/IGDBMetadataProvider.cs b/source/Playnite/Metadata/Providers/IGDBMetadataProvider.cs new file mode 100644 index 000000000..e4a8f62d9 --- /dev/null +++ b/source/Playnite/Metadata/Providers/IGDBMetadataProvider.cs @@ -0,0 +1,273 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Playnite.Services; +using System.Collections.Concurrent; +using System.Collections.ObjectModel; +using Playnite.Models; +using System.Globalization; +using Playnite.Database; +using System.IO; +using Playnite.SDK.Models; +using Playnite.SDK; +using Playnite.Web; +using System.Text.RegularExpressions; + +namespace Playnite.Metadata.Providers +{ + public class IGDBMetadataProvider : IMetadataProvider + { + private ServicesClient client; + + public IGDBMetadataProvider() : this(new ServicesClient()) + { + } + + public IGDBMetadataProvider(ServicesClient client) + { + this.client = client; + } + + private Game GetParsedGame(ulong id) + { + var dbGame = client.GetIGDBGameParsed(id); + var game = new Game() + { + Name = dbGame.name, + Description = dbGame.summary?.Replace("\n", "\n
") + }; + + if (dbGame.cover != null) + { + game.Image = dbGame.cover.Replace("t_thumb", "t_cover_big"); + if (!game.Image.StartsWith("https:", StringComparison.InvariantCultureIgnoreCase)) + { + game.Image = "https:" + game.Image; + } + } + + if (dbGame.first_release_date != 0) + { + game.ReleaseDate = DateTimeOffset.FromUnixTimeMilliseconds(dbGame.first_release_date).DateTime; + } + + if (dbGame.developers?.Any() == true) + { + game.Developers = new ComparableList(dbGame.developers); + } + + if (dbGame.publishers?.Any() == true) + { + game.Publishers = new ComparableList(dbGame.publishers); + } + + if (dbGame.genres?.Any() == true) + { + game.Genres = new ComparableList(dbGame.genres); + } + + if (dbGame.websites?.Any() == true) + { + game.Links = new ObservableCollection(dbGame.websites.Select(a => new Link(a.category.ToString(), a.url))); + } + + if (dbGame.game_modes?.Any() == true) + { + var cultInfo = new CultureInfo("en-US", false).TextInfo; + game.Tags = new ComparableList(dbGame.game_modes.Select(a => cultInfo.ToTitleCase(a))); + } + + if (dbGame.aggregated_rating != 0) + { + game.CriticScore = Convert.ToInt32(dbGame.aggregated_rating); + } + + if (dbGame.rating != 0) + { + game.CommunityScore = Convert.ToInt32(dbGame.rating); + } + + return game; + } + + private string ReplaceNumsForRomans(Match m) + { + return Roman.To(int.Parse(m.Value)); + } + + public ICollection SearchMetadata(Game game) + { + return client.GetIGDBGames(game.Name)?.Select(a => new MetadataSearchResult() + { + Id = a.id.ToString(), + Name = a.name, + ReleaseDate = a.first_release_date == 0 ? (DateTime?)null : DateTimeOffset.FromUnixTimeMilliseconds(a.first_release_date).DateTime, + AlternativeNames = a.alternative_names?.Any() == true ? a.alternative_names.Select(name => name.name).ToList() : null + }).ToList(); + } + + public GameMetadata GetMetadata(string gameId) + { + var game = GetParsedGame(ulong.Parse(gameId)); + MetadataFile image = null; + if (!string.IsNullOrEmpty(game.Image)) + { + var name = Path.GetFileName(game.Image); + image = new MetadataFile($"images/custom/{name}", name, HttpDownloader.DownloadData(game.Image)); + } + + return new GameMetadata(game, null, image, string.Empty); + } + + public GameMetadata GetMetadata(Game game) + { + if (game.Provider == Provider.Steam) + { + var igdbId = client.GetIGDBGameBySteamId(game.ProviderId); + if (igdbId != 0) + { + return GetMetadata(igdbId.ToString()); + } + } + + if (string.IsNullOrEmpty(game.Name)) + { + return GameMetadata.Empty; + } + + var copyGame = game.CloneJson(); + copyGame.Name = StringExtensions.NormalizeGameName(game.Name); + var name = copyGame.Name; + var results = SearchMetadata(copyGame).ToList(); + results.ForEach(a => a.Name = StringExtensions.NormalizeGameName(a.Name)); + + GameMetadata data = null; + string testName = string.Empty; + + // Direct comparison + data = matchFun(game, name, results); + if (data != null) + { + return data; + } + + // Try replacing roman numerals: 3 => III + testName = Regex.Replace(name, @"\d+", ReplaceNumsForRomans); + data = matchFun(game, testName, results); + if (data != null) + { + return data; + } + + // Try adding The + testName = "The " + name; + data = matchFun(game, testName, results); + if (data != null) + { + return data; + } + + // Try chaning & / and + testName = Regex.Replace(name, @"\s+and\s+", " & ", RegexOptions.IgnoreCase); + data = matchFun(game, testName, results); + if (data != null) + { + return data; + } + + // Try removing apostrophes + var resCopy = results.CloneJson(); + resCopy.ForEach(a => a.Name = a.Name.Replace("'", "")); + data = matchFun(game, name, resCopy); + if (data != null) + { + return data; + } + + // Try removing all ":" + testName = Regex.Replace(testName, @"\s*:\s*", " "); + resCopy = results.CloneJson(); + resCopy.ForEach(a => a.Name = Regex.Replace(a.Name, @"\s*:\s*", " ")); + data = matchFun(game, testName, resCopy); + if (data != null) + { + return data; + } + + // Try without subtitle + var testResult = results.OrderBy(a => a.ReleaseDate).FirstOrDefault(a => + { + if (a.ReleaseDate == null) + { + return false; + } + + if (!string.IsNullOrEmpty(a.Name) && a.Name.Contains(":")) + { + return string.Equals(name, a.Name.Split(':')[0], StringComparison.InvariantCultureIgnoreCase); + } + + return false; + }); + + if (testResult != null) + { + return GetMetadata(testResult.Id); + } + + if (data != null) + { + return data; + } + else + { + return GameMetadata.Empty; + } + } + + private GameMetadata matchFun(Game game, string matchName, IEnumerable list) + { + var res = list.Where(a => string.Equals(matchName, a.Name, StringComparison.InvariantCultureIgnoreCase)); + if (!res.Any()) + { + res = list.Where(a => a.AlternativeNames?.ContainsInsensitive(matchName) == true); + } + + if (res.Any()) + { + if (res.Count() == 1) + { + return GetMetadata(res.First().Id); + } + else + { + if (game.ReleaseDate != null) + { + var igdbGame = res.FirstOrDefault(a => a.ReleaseDate?.Year == game.ReleaseDate.Value.Year); + if (igdbGame != null) + { + return GetMetadata(igdbGame.Id); + } + } + else + { + // If multiple matches are found and we don't have release date then prioritize older game + if (res.All(a => a.ReleaseDate == null)) + { + return GetMetadata(res.First().Id); + } + else + { + var igdbGame = res.OrderBy(a => a.ReleaseDate?.Year).First(a => a.ReleaseDate != null); + return GetMetadata(igdbGame.Id); + } + } + } + } + + return null; + } + } +} diff --git a/source/Playnite/MetaProviders/Wikipedia.cs b/source/Playnite/Metadata/Providers/WikipediaMetadataProvider.cs similarity index 94% rename from source/Playnite/MetaProviders/Wikipedia.cs rename to source/Playnite/Metadata/Providers/WikipediaMetadataProvider.cs index c9a39d432..5d98afdf4 100644 --- a/source/Playnite/MetaProviders/Wikipedia.cs +++ b/source/Playnite/Metadata/Providers/WikipediaMetadataProvider.cs @@ -6,20 +6,16 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Web; -using AngleSharp; using AngleSharp.Dom; using AngleSharp.Parser.Html; using Newtonsoft.Json; -using Playnite.Models; -using Playnite.Providers; using NLog; using Playnite.SDK.Models; -using Playnite.SDK; using Playnite.Web; -namespace Playnite.MetaProviders +namespace Playnite.Metadata.Providers { - public class Wikipedia + public class WikipediaMetadataProvider { private static Logger logger = LogManager.GetCurrentClassLogger(); @@ -91,7 +87,7 @@ public Dictionary text } } - public Wikipedia() + public WikipediaMetadataProvider() { } @@ -251,7 +247,8 @@ public Game ParseGamePage(WikiPage page, string gameName = "") if (rowName.IndexOf("release", StringComparison.OrdinalIgnoreCase) >= 0) { - List dates= rowValue.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).ToList(); + rowValue = Regex.Replace(rowValue, "[A-Z]+:", "\n"); + var dates = rowValue.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).ToList(); // Take first valid date we find foreach (var stringDate in dates) diff --git a/source/Playnite/Playnite.csproj b/source/Playnite/Playnite.csproj index 31aa72a18..74a3f806c 100644 --- a/source/Playnite/Playnite.csproj +++ b/source/Playnite/Playnite.csproj @@ -84,7 +84,8 @@ ..\packages\IronPython.2.7.8.1\lib\net45\IronPython.Wpf.dll - ..\packages\LiteDB.3.1.4\lib\net462\LiteDB.dll + False + ..\..\references\LiteDB.dll ..\packages\DynamicLanguageRuntime.1.2.1\lib\net45\Microsoft.Dynamic.dll @@ -175,6 +176,10 @@ + + + + @@ -197,8 +202,8 @@ - - + + @@ -215,11 +220,11 @@ - + - + @@ -266,7 +271,7 @@ - + diff --git a/source/Playnite/Providers/BattleNet/BattleNetLibrary.cs b/source/Playnite/Providers/BattleNet/BattleNetLibrary.cs index daac9977e..b16288025 100644 --- a/source/Playnite/Providers/BattleNet/BattleNetLibrary.cs +++ b/source/Playnite/Providers/BattleNet/BattleNetLibrary.cs @@ -1,4 +1,5 @@ using AngleSharp.Parser.Html; +using Playnite.Metadata; using Playnite.Models; using Playnite.SDK.Models; using Playnite.Web; @@ -334,6 +335,11 @@ public List GetInstalledGames() continue; } + if (prog.DisplayName.EndsWith("Test")) + { + continue; + } + var iId = match.Groups[1].Value; var product = BattleNetProducts.FirstOrDefault(a => a.Type == BNetAppType.Default && iId.StartsWith(a.InternalId)); if (product == null) @@ -375,10 +381,10 @@ public GameMetadata UpdateGameWithMetadata(Game game) game.Name = product.Name; var icon = HttpDownloader.DownloadData(product.IconUrl); var iconFile = Path.GetFileName(product.IconUrl); - metadata.Icon = new Database.FileDefinition($"images/battlenet/{game.ProviderId}/{iconFile}", iconFile, icon); + metadata.Icon = new MetadataFile($"images/battlenet/{game.ProviderId}/{iconFile}", iconFile, icon); var cover = HttpDownloader.DownloadData(product.CoverUrl); var coverFile = Path.GetFileName(product.CoverUrl); - metadata.Image = new Database.FileDefinition($"images/battlenet/{game.ProviderId}/{coverFile}", coverFile, cover); + metadata.Image = new MetadataFile($"images/battlenet/{game.ProviderId}/{coverFile}", coverFile, cover); game.BackgroundImage = product.BackgroundUrl; metadata.BackgroundImage = product.BackgroundUrl; game.Links = new ObservableCollection(product.Links); diff --git a/source/Playnite/Providers/BattleNet/BattleNetMetadataProvider.cs b/source/Playnite/Providers/BattleNet/BattleNetMetadataProvider.cs index fdcc5e7c9..6423b74a7 100644 --- a/source/Playnite/Providers/BattleNet/BattleNetMetadataProvider.cs +++ b/source/Playnite/Providers/BattleNet/BattleNetMetadataProvider.cs @@ -1,37 +1,42 @@ -using Playnite.MetaProviders; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Playnite.Models; using Playnite.SDK.Models; +using Playnite.Metadata; namespace Playnite.Providers.BattleNet { public class BattleNetMetadataProvider : IMetadataProvider { - public GameMetadata GetGameData(string gameId) + public GameMetadata GetMetadata(string metadataId) { var gameData = new Game("BattleNetGame") { - Provider = Provider.Steam, - ProviderId = gameId + Provider = Provider.BattleNet, + ProviderId = metadataId }; - var steamLib = new BattleNetLibrary(); - var data = steamLib.UpdateGameWithMetadata(gameData); + var bnetLib = new BattleNetLibrary(); + var data = bnetLib.UpdateGameWithMetadata(gameData); return new GameMetadata(gameData, data.Icon, data.Image, data.BackgroundImage); } - public bool GetSupportsIdSearch() + public GameMetadata GetMetadata(Game game) { - return true; + if (game.Provider == Provider.BattleNet) + { + return GetMetadata(game.ProviderId); + } + + throw new NotImplementedException(); } - public List SearchGames(string gameName) + public ICollection SearchMetadata(Game game) { - throw new NotSupportedException(); + throw new NotImplementedException(); } } } diff --git a/source/Playnite/Providers/BattleNet/IBattleNetLibrary.cs b/source/Playnite/Providers/BattleNet/IBattleNetLibrary.cs index 0a0faad1f..8a3de9474 100644 --- a/source/Playnite/Providers/BattleNet/IBattleNetLibrary.cs +++ b/source/Playnite/Providers/BattleNet/IBattleNetLibrary.cs @@ -1,5 +1,5 @@ using Playnite.SDK.Models; -using Playnite.Models; +using Playnite.Metadata; using System; using System.Collections.Generic; using System.Linq; diff --git a/source/Playnite/Providers/GOG/GOGMetadataProvider.cs b/source/Playnite/Providers/GOG/GOGMetadataProvider.cs index eeab15cad..b438b1792 100644 --- a/source/Playnite/Providers/GOG/GOGMetadataProvider.cs +++ b/source/Playnite/Providers/GOG/GOGMetadataProvider.cs @@ -1,4 +1,4 @@ -using Playnite.MetaProviders; +using Playnite.Metadata; using Playnite.SDK.Models; using System; using System.Collections.Generic; @@ -11,27 +11,32 @@ namespace Playnite.Providers.GOG { public class GogMetadataProvider : IMetadataProvider { - public GameMetadata GetGameData(string gameId) + public GameMetadata GetMetadata(string metadataId) { var gameData = new Game("GOGGame") { - Provider = Provider.Steam, - ProviderId = gameId + Provider = Provider.GOG, + ProviderId = metadataId }; - var steamLib = new GogLibrary(); - var data = steamLib.UpdateGameWithMetadata(gameData); + var gogLib = new GogLibrary(); + var data = gogLib.UpdateGameWithMetadata(gameData); return new GameMetadata(gameData, data.Icon, data.Image, data.BackgroundImage); } - public bool GetSupportsIdSearch() + public GameMetadata GetMetadata(Game game) { - return true; + if (game.Provider == Provider.GOG) + { + return GetMetadata(game.ProviderId); + } + + throw new NotImplementedException(); } - public List SearchGames(string gameName) + public ICollection SearchMetadata(Game game) { - throw new NotSupportedException(); + throw new NotImplementedException(); } } } diff --git a/source/Playnite/Providers/GOG/GogGameController.cs b/source/Playnite/Providers/GOG/GogGameController.cs index 73819ea8b..c46893b1d 100644 --- a/source/Playnite/Providers/GOG/GogGameController.cs +++ b/source/Playnite/Providers/GOG/GogGameController.cs @@ -41,6 +41,7 @@ public override void Play(List emulators) ReleaseResources(); if (settings?.GOGSettings.RunViaGalaxy == true) { + logger.Info($"Starting game {Game.ProviderId} via GOG Galaxy from {Game.InstallDirectory}"); OnStarting(this, new GameControllerEventArgs(this, 0)); stopWatch = Stopwatch.StartNew(); procMon = new ProcessMonitor(); @@ -59,7 +60,7 @@ public override void Play(List emulators) public override void Install() { ReleaseResources(); - Process.Start(@"goggalaxy://openGameView/" + Game.ProviderId); + ProcessStarter.StartUrl(@"goggalaxy://openGameView/" + Game.ProviderId); StartInstallWatcher(); } diff --git a/source/Playnite/Providers/GOG/GogLibrary.cs b/source/Playnite/Providers/GOG/GogLibrary.cs index 339d4af19..745f874ab 100644 --- a/source/Playnite/Providers/GOG/GogLibrary.cs +++ b/source/Playnite/Providers/GOG/GogLibrary.cs @@ -1,8 +1,6 @@ using Newtonsoft.Json; using NLog; -using Playnite.Database; -using Playnite.Models; -using Playnite.SDK; +using Playnite.Metadata; using Playnite.SDK.Models; using Playnite.Web; using System; @@ -172,13 +170,13 @@ public GogGameMetadata DownloadGameMetadata(string id, string storeUrl = null) var image = HttpDownloader.DownloadData("http:" + gameDetail.images.logo2x); var imageName = Path.GetFileName(new Uri(gameDetail.images.logo2x).AbsolutePath); - metadata.Icon = new FileDefinition( + metadata.Icon = new MetadataFile( string.Format("images/gog/{0}/{1}", id, iconName), iconName, icon ); - metadata.Image = new FileDefinition( + metadata.Image = new MetadataFile( string.Format("images/gog/{0}/{1}", id, imageName), imageName, image diff --git a/source/Playnite/Providers/Origin/OriginLibrary.cs b/source/Playnite/Providers/Origin/OriginLibrary.cs index 860c4db09..f734bf6b3 100644 --- a/source/Playnite/Providers/Origin/OriginLibrary.cs +++ b/source/Playnite/Providers/Origin/OriginLibrary.cs @@ -7,8 +7,7 @@ using System.Threading.Tasks; using System.Web; using NLog; -using Playnite.Models; -using Playnite.SDK; +using Playnite.Metadata; using Playnite.SDK.Models; using Playnite.Providers.Steam; using Microsoft.Win32; @@ -326,7 +325,7 @@ public OriginGameMetadata DownloadGameMetadata(string id) var imageData = HttpDownloader.DownloadData(imageUrl); var imageName = Guid.NewGuid() + Path.GetExtension(new Uri(imageUrl).AbsolutePath); - data.Image = new FileDefinition( + data.Image = new MetadataFile( string.Format("images/origin/{0}/{1}", id.Replace(":", ""), imageName), imageName, imageData @@ -378,7 +377,7 @@ public OriginGameMetadata UpdateGameWithMetadata(Game game) { var iconName = Guid.NewGuid() + ".png"; - metadata.Icon = new FileDefinition( + metadata.Icon = new MetadataFile( string.Format("images/origin/{0}/{1}", game.ProviderId.Replace(":", ""), iconName), iconName, exeIcon.ToByteArray(System.Drawing.Imaging.ImageFormat.Png) diff --git a/source/Playnite/Providers/Origin/OriginMetadataProvider.cs b/source/Playnite/Providers/Origin/OriginMetadataProvider.cs index 2bc1d1d24..960f246ff 100644 --- a/source/Playnite/Providers/Origin/OriginMetadataProvider.cs +++ b/source/Playnite/Providers/Origin/OriginMetadataProvider.cs @@ -1,4 +1,4 @@ -using Playnite.MetaProviders; +using Playnite.Metadata; using Playnite.SDK.Models; using System; using System.Collections.Generic; @@ -11,27 +11,32 @@ namespace Playnite.Providers.Origin { public class OriginMetadataProvider : IMetadataProvider { - public GameMetadata GetGameData(string gameId) + public GameMetadata GetMetadata(string metadataId) { var gameData = new Game("OriginGame") { - Provider = Provider.Steam, - ProviderId = gameId + Provider = Provider.Origin, + ProviderId = metadataId }; - var steamLib = new OriginLibrary(); - var data = steamLib.UpdateGameWithMetadata(gameData); + var originLib = new OriginLibrary(); + var data = originLib.UpdateGameWithMetadata(gameData); return new GameMetadata(gameData, data.Icon, data.Image, data.BackgroundImage); } - public bool GetSupportsIdSearch() + public GameMetadata GetMetadata(Game game) { - return true; + if (game.Provider == Provider.Origin) + { + return GetMetadata(game.ProviderId); + } + + throw new NotImplementedException(); } - public List SearchGames(string gameName) + public ICollection SearchMetadata(Game game) { - throw new NotSupportedException(); + throw new NotImplementedException(); } } } diff --git a/source/Playnite/Providers/Steam/SteamGameController.cs b/source/Playnite/Providers/Steam/SteamGameController.cs index 3f039d2b4..ab019691d 100644 --- a/source/Playnite/Providers/Steam/SteamGameController.cs +++ b/source/Playnite/Providers/Steam/SteamGameController.cs @@ -44,14 +44,14 @@ public override void Play(List emulators) public override void Install() { ReleaseResources(); - Process.Start(@"steam://install/" + Game.ProviderId); + ProcessStarter.StartUrl(@"steam://install/" + Game.ProviderId); StartInstallWatcher(); } public override void Uninstall() { ReleaseResources(); - Process.Start(@"steam://uninstall/" + Game.ProviderId); + ProcessStarter.StartUrl(@"steam://uninstall/" + Game.ProviderId); StartUninstallWatcher(); } diff --git a/source/Playnite/Providers/Steam/SteamLibrary.cs b/source/Playnite/Providers/Steam/SteamLibrary.cs index cf451ae7c..29a187bb4 100644 --- a/source/Playnite/Providers/Steam/SteamLibrary.cs +++ b/source/Playnite/Providers/Steam/SteamLibrary.cs @@ -12,16 +12,14 @@ using Microsoft.Win32; using Newtonsoft.Json; using NLog; -using Playnite.Models; -using Playnite.Providers.Steam; using SteamKit2; using Playnite.Services; -using Playnite.Database; using System.Windows; using System.Globalization; using Playnite.SDK; using Playnite.SDK.Models; using Playnite.Web; +using Playnite.Metadata; namespace Playnite.Providers.Steam { @@ -391,37 +389,39 @@ public SteamGameMetadata DownloadGameMetadata(int id, bool screenAsBackground) metadata.StoreDetails = GetStoreData(id); // Icon - var iconRoot = @"https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/{0}/{1}.ico"; - var icon = productInfo["common"]["clienticon"]; - var iconUrl = string.Empty; - if (icon.Name != null) + if (productInfo != null) { - iconUrl = string.Format(iconRoot, id, icon.Value); - } - else - { - var newIcon = productInfo["common"]["icon"]; - if (newIcon.Name != null) + var iconRoot = @"https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/{0}/{1}.ico"; + var icon = productInfo["common"]["clienticon"]; + var iconUrl = string.Empty; + if (icon.Name != null) { - iconRoot = @"https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/{0}/{1}.jpg"; - iconUrl = string.Format(iconRoot, id, newIcon.Value); + iconUrl = string.Format(iconRoot, id, icon.Value); + } + else + { + var newIcon = productInfo["common"]["icon"]; + if (newIcon.Name != null) + { + iconRoot = @"https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/{0}/{1}.jpg"; + iconUrl = string.Format(iconRoot, id, newIcon.Value); + } } - } - - // There might be no icon assigned to game - if (!string.IsNullOrEmpty(iconUrl)) - { - var iconName = Path.GetFileName(new Uri(iconUrl).AbsolutePath); - var iconData = HttpDownloader.DownloadData(iconUrl); - metadata.Icon = new FileDefinition( - string.Format("images/steam/{0}/{1}", id.ToString(), iconName), - iconName, - iconData - ); + // There might be no icon assigned to game + if (!string.IsNullOrEmpty(iconUrl)) + { + var iconName = Path.GetFileName(new Uri(iconUrl).AbsolutePath); + var iconData = HttpDownloader.DownloadData(iconUrl); + metadata.Icon = new MetadataFile( + + string.Format("images/steam/{0}/{1}", id.ToString(), iconName), + iconName, + iconData + ); + } } - // Image var imageRoot = @"http://cdn.akamai.steamstatic.com/steam/apps/{0}/header.jpg"; var imageUrl = string.Format(imageRoot, id); @@ -436,12 +436,15 @@ public SteamGameMetadata DownloadGameMetadata(int id, bool screenAsBackground) var response = (HttpWebResponse)e.Response; if (response.StatusCode == HttpStatusCode.NotFound) { - imageRoot = @"https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/{0}/{1}.jpg"; - var image = productInfo["common"]["logo"]; - if (image.Name != null) + if (productInfo != null) { - imageUrl = string.Format(imageRoot, id, image.Value); - imageData = HttpDownloader.DownloadData(imageUrl); + imageRoot = @"https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/{0}/{1}.jpg"; + var image = productInfo["common"]["logo"]; + if (image.Name != null) + { + imageUrl = string.Format(imageRoot, id, image.Value); + imageData = HttpDownloader.DownloadData(imageUrl); + } } } else @@ -453,7 +456,7 @@ public SteamGameMetadata DownloadGameMetadata(int id, bool screenAsBackground) if (imageData != null) { var imageName = Path.GetFileName(new Uri(imageUrl).AbsolutePath); - metadata.Image = new FileDefinition( + metadata.Image = new MetadataFile( string.Format("images/steam/{0}/{1}", id.ToString(), imageName), imageName, imageData @@ -479,7 +482,7 @@ public SteamGameMetadata DownloadGameMetadata(int id, bool screenAsBackground) public SteamGameMetadata UpdateGameWithMetadata(Game game, SteamSettings settings) { var metadata = DownloadGameMetadata(int.Parse(game.ProviderId), settings.PreferScreenshotForBackground); - game.Name = metadata.ProductDetails["common"]["name"].Value ?? game.Name; + game.Name = metadata.ProductDetails?["common"]["name"]?.Value ?? game.Name; game.Links = new ObservableCollection() { new Link("Forum", @"https://steamcommunity.com/app/" + game.ProviderId), @@ -505,52 +508,55 @@ public SteamGameMetadata UpdateGameWithMetadata(Game game, SteamSettings setting game.CriticScore = metadata.StoreDetails.metacritic?.score; } - var tasks = new ObservableCollection(); - var launchList = metadata.ProductDetails["config"]["launch"].Children; - foreach (var task in launchList.Skip(1)) + if (metadata.ProductDetails != null) { - var properties = task["config"]; - if (properties.Name != null) + var tasks = new ObservableCollection(); + var launchList = metadata.ProductDetails["config"]["launch"].Children; + foreach (var task in launchList.Skip(1)) { - if (properties["oslist"].Name != null) + var properties = task["config"]; + if (properties.Name != null) { - if (properties["oslist"].Value != "windows") + if (properties["oslist"].Name != null) { - continue; + if (properties["oslist"].Value != "windows") + { + continue; + } } } + + // Ignore action without name - shoudn't be visible to end user + if (task["description"].Name != null) + { + var newTask = new GameTask() + { + Name = task["description"].Value, + Arguments = task["arguments"].Value ?? string.Empty, + Path = task["executable"].Value, + IsBuiltIn = true, + WorkingDir = "{InstallDir}" + }; + + tasks.Add(newTask); + } } - // Ignore action without name - shoudn't be visible to end user - if (task["description"].Name != null) + var manual = metadata.ProductDetails["extended"]["gamemanualurl"]; + if (manual.Name != null) { - var newTask = new GameTask() + tasks.Add((new GameTask() { - Name = task["description"].Value, - Arguments = task["arguments"].Value ?? string.Empty, - Path = task["executable"].Value, - IsBuiltIn = true, - WorkingDir = "{InstallDir}" - }; - - tasks.Add(newTask); + Name = "Manual", + Type = GameTaskType.URL, + Path = manual.Value, + IsBuiltIn = true + })); } - } - var manual = metadata.ProductDetails["extended"]["gamemanualurl"]; - if (manual.Name != null) - { - tasks.Add((new GameTask() - { - Name = "Manual", - Type = GameTaskType.URL, - Path = manual.Value, - IsBuiltIn = true - })); + game.OtherTasks = tasks; } - game.OtherTasks = tasks; - if (!string.IsNullOrEmpty(metadata.BackgroundImage)) { game.BackgroundImage = metadata.BackgroundImage; diff --git a/source/Playnite/Providers/Steam/SteamMetadataProvider.cs b/source/Playnite/Providers/Steam/SteamMetadataProvider.cs index 3103827bb..40c5b851f 100644 --- a/source/Playnite/Providers/Steam/SteamMetadataProvider.cs +++ b/source/Playnite/Providers/Steam/SteamMetadataProvider.cs @@ -1,4 +1,4 @@ -using Playnite.MetaProviders; +using Playnite.Metadata; using System; using System.Collections.Generic; using System.Linq; @@ -27,12 +27,12 @@ public SteamMetadataProvider(ServicesClient playniteServices, SteamSettings sett this.playniteServices = playniteServices; } - public GameMetadata GetGameData(string gameId) + public GameMetadata GetMetadata(string metadataId) { var gameData = new Game("SteamGame") { Provider = Provider.Steam, - ProviderId = gameId + ProviderId = metadataId }; var steamLib = new SteamLibrary(playniteServices); @@ -40,14 +40,19 @@ public GameMetadata GetGameData(string gameId) return new GameMetadata(gameData, data.Icon, data.Image, data.BackgroundImage); } - public bool GetSupportsIdSearch() + public GameMetadata GetMetadata(Game game) { - return true; + if (game.Provider == Provider.Steam) + { + return GetMetadata(game.ProviderId); + } + + throw new NotImplementedException(); } - public List SearchGames(string gameName) + public ICollection SearchMetadata(Game game) { - throw new NotSupportedException(); + throw new NotImplementedException(); } } } diff --git a/source/Playnite/Providers/Uplay/IUplayLibrary.cs b/source/Playnite/Providers/Uplay/IUplayLibrary.cs index 55bc5b27f..77dc589a9 100644 --- a/source/Playnite/Providers/Uplay/IUplayLibrary.cs +++ b/source/Playnite/Providers/Uplay/IUplayLibrary.cs @@ -1,4 +1,4 @@ -using Playnite.Models; +using Playnite.Metadata; using Playnite.SDK.Models; using System; using System.Collections.Generic; diff --git a/source/Playnite/Providers/Uplay/UplayLibrary.cs b/source/Playnite/Providers/Uplay/UplayLibrary.cs index 4944f88a0..ae2706c42 100644 --- a/source/Playnite/Providers/Uplay/UplayLibrary.cs +++ b/source/Playnite/Providers/Uplay/UplayLibrary.cs @@ -6,7 +6,7 @@ using Playnite.SDK.Models; using Microsoft.Win32; using System.IO; -using Playnite.Models; +using Playnite.Metadata; namespace Playnite.Providers.Uplay { @@ -75,7 +75,7 @@ public GameMetadata UpdateGameWithMetadata(Game game) var iconPath = program.DisplayIcon; var iconFile = Path.GetFileName(iconPath); var data = File.ReadAllBytes(iconPath); - metadata.Icon = new Database.FileDefinition($"images/uplay/{game.ProviderId}/{iconFile}", iconFile, data); + metadata.Icon = new MetadataFile($"images/uplay/{game.ProviderId}/{iconFile}", iconFile, data); } game.Name = StringExtensions.NormalizeGameName(program.DisplayName); diff --git a/source/Playnite/Services/ServicesClient.cs b/source/Playnite/Services/ServicesClient.cs index d3341ddca..18c81e13d 100644 --- a/source/Playnite/Services/ServicesClient.cs +++ b/source/Playnite/Services/ServicesClient.cs @@ -85,6 +85,12 @@ public string GetSteamStoreData(int appId) return ExecuteGetRequest>(url); } + public ulong GetIGDBGameBySteamId(string id, string apiKey = null) + { + var url = $"/api/igdb/gamesBySteamId/{id}"; + return ExecuteGetRequest(url); + } + public PlayniteServices.Models.IGDB.Game GetIGDBGame(UInt64 id, string apiKey = null) { var url = string.IsNullOrEmpty(apiKey) ? $"/api/igdb/game/{id}" : $"/api/igdb/game/{id}?apikey={apiKey}"; diff --git a/source/Playnite/Settings/FilterSettings.cs b/source/Playnite/Settings/FilterSettings.cs index a92a66530..4eda61a5a 100644 --- a/source/Playnite/Settings/FilterSettings.cs +++ b/source/Playnite/Settings/FilterSettings.cs @@ -29,6 +29,12 @@ public FilterChangedEventArgs(List fields) public class FilterSettings : INotifyPropertyChanged { + [JsonIgnore] + public bool SearchActive + { + get => !string.IsNullOrEmpty(Name); + } + [JsonIgnore] public bool Active { @@ -74,6 +80,7 @@ public string Name OnPropertyChanged("Name"); OnFilterChanged("Name"); OnPropertyChanged("Active"); + OnPropertyChanged("SearchActive"); } } diff --git a/source/Playnite/Settings/Settings.cs b/source/Playnite/Settings/Settings.cs index 79320957a..7bdd92844 100644 --- a/source/Playnite/Settings/Settings.cs +++ b/source/Playnite/Settings/Settings.cs @@ -197,7 +197,7 @@ public bool MigrationV2PcPlatformAdded } } - private bool showIconsOnList = false; + private bool showIconsOnList = true; public bool ShowIconsOnList { get diff --git a/source/Playnite/System/ProcessMonitor.cs b/source/Playnite/System/ProcessMonitor.cs index 2e5144a16..72a675a00 100644 --- a/source/Playnite/System/ProcessMonitor.cs +++ b/source/Playnite/System/ProcessMonitor.cs @@ -41,7 +41,7 @@ public void WatchDirectoryProcesses(string directory, bool alreadyRunning) public void StopWatching() { - watcherToken.Cancel(); + watcherToken?.Cancel(); } private async void WatchDirectory(string directory, bool alreadyRunning) diff --git a/source/Playnite/System/ProcessStarter.cs b/source/Playnite/System/ProcessStarter.cs index a882c1001..111a9eda1 100644 --- a/source/Playnite/System/ProcessStarter.cs +++ b/source/Playnite/System/ProcessStarter.cs @@ -1,4 +1,5 @@ -using System; +using NLog; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -9,8 +10,11 @@ namespace Playnite { public static class ProcessStarter { + private static Logger logger = LogManager.GetCurrentClassLogger(); + public static Process StartUrl(string url) { + logger.Debug($"Opening URL: {url}"); return Process.Start(url); } @@ -21,6 +25,7 @@ public static Process StartProcess(string path, string arguments) public static Process StartProcess(string path, string arguments, string workDir) { + logger.Debug($"Starting process: {path}, {arguments}, {workDir}"); var info = new ProcessStartInfo(path) { Arguments = arguments, diff --git a/source/Playnite/Web/Downloader.cs b/source/Playnite/Web/Downloader.cs index 4ef4a9202..22ae06046 100644 --- a/source/Playnite/Web/Downloader.cs +++ b/source/Playnite/Web/Downloader.cs @@ -46,6 +46,7 @@ public Downloader() public string DownloadString(IEnumerable mirrors) { + logger.Debug($"Downloading string content from multiple mirrors."); foreach (var mirror in mirrors) { try @@ -68,6 +69,7 @@ public string DownloadString(string url) public string DownloadString(string url, Encoding encoding) { + logger.Debug($"Downloading string content from {url} using {encoding} encoding."); var webClient = new WebClient { Encoding = encoding }; return webClient.DownloadString(url); } @@ -79,6 +81,7 @@ public string DownloadString(string url, List cookies) public string DownloadString(string url, List cookies, Encoding encoding) { + logger.Debug($"Downloading string content from {url} using cookies and {encoding} encoding."); var webClient = new WebClient { Encoding = encoding }; if (cookies?.Any() == true) { @@ -96,19 +99,22 @@ public void DownloadString(string url, string path) public void DownloadString(string url, string path, Encoding encoding) { - var webClient = new WebClient { Encoding = Encoding.UTF8 }; + logger.Debug($"Downloading string content from {url} to {path} using {encoding} encoding."); + var webClient = new WebClient { Encoding = encoding }; var data = webClient.DownloadString(url); File.WriteAllText(path, data); } public byte[] DownloadData(string url) { + logger.Debug($"Downloading data from {url}."); var webClient = new WebClient(); return webClient.DownloadData(url); } public void DownloadFile(string url, string path) { + logger.Debug($"Downloading data from {url} to {path}."); FileSystem.CreateDirectory(Path.GetDirectoryName(path)); var webClient = new WebClient(); webClient.DownloadFile(url, path); @@ -116,6 +122,7 @@ public void DownloadFile(string url, string path) public async Task DownloadFileAsync(string url, string path, Action progressHandler) { + logger.Debug($"Downloading data async from {url} to {path}."); FileSystem.CreateDirectory(Path.GetDirectoryName(path)); var webClient = new WebClient(); webClient.DownloadProgressChanged += (s, e) => progressHandler(e); @@ -125,6 +132,7 @@ public async Task DownloadFileAsync(string url, string path, Action mirrors, string path, Action progressHandler) { + logger.Debug($"Downloading data async from multiple mirrors."); foreach (var mirror in mirrors) { try @@ -143,6 +151,7 @@ public async Task DownloadFileAsync(IEnumerable mirrors, string path, Ac public void DownloadFile(IEnumerable mirrors, string path) { + logger.Debug($"Downloading data from multiple mirrors."); foreach (var mirror in mirrors) { try @@ -161,6 +170,7 @@ public void DownloadFile(IEnumerable mirrors, string path) public string GetCachedWebFile(string url) { + logger.Debug($"Getting cached web file from {url}."); if (string.IsNullOrEmpty(url)) { return string.Empty; @@ -170,13 +180,19 @@ public string GetCachedWebFile(string url) var md5 = url.MD5(); var cacheFile = Path.Combine(Paths.ImagesCachePath, md5 + extension); - if (!File.Exists(cacheFile)) + if (File.Exists(cacheFile)) + { + logger.Debug($"Returning {url} from file cache {cacheFile}."); + return cacheFile; + } + else { FileSystem.CreateDirectory(Paths.ImagesCachePath); try { DownloadFile(url, cacheFile); + return cacheFile; } catch (WebException e) { @@ -195,9 +211,7 @@ public string GetCachedWebFile(string url) return string.Empty; } } - } - - return cacheFile; + } } } } diff --git a/source/Playnite/packages.config b/source/Playnite/packages.config index 26a874d27..f2a00f763 100644 --- a/source/Playnite/packages.config +++ b/source/Playnite/packages.config @@ -10,7 +10,6 @@ - diff --git a/source/PlayniteSDK/ExtensionFunction.cs b/source/PlayniteSDK/ExtensionFunction.cs index a1204c593..f919ace71 100644 --- a/source/PlayniteSDK/ExtensionFunction.cs +++ b/source/PlayniteSDK/ExtensionFunction.cs @@ -47,5 +47,11 @@ public virtual void Invoke() { func?.Invoke(); } + + /// + public override string ToString() + { + return Name; + } } } diff --git a/source/PlayniteSDK/Models/GameTask.cs b/source/PlayniteSDK/Models/GameTask.cs index 70d85ea15..ea58cb7ea 100644 --- a/source/PlayniteSDK/Models/GameTask.cs +++ b/source/PlayniteSDK/Models/GameTask.cs @@ -193,5 +193,21 @@ public ObjectId EmulatorProfileId OnPropertyChanged("EmulatorProfileId"); } } + + /// + public override string ToString() + { + switch (Type) + { + case GameTaskType.File: + return $"File: {Path}, {Arguments}, {WorkingDir}"; + case GameTaskType.URL: + return $"Url: {Path}"; + case GameTaskType.Emulator: + return $"Emulator: {EmulatorId}, {EmulatorProfileId}, {OverrideDefaultArgs}, {AdditionalArguments}"; + default: + return Path; + } + } } } diff --git a/source/PlayniteSDK/PlayniteSDK.csproj b/source/PlayniteSDK/PlayniteSDK.csproj index 7c66c69ba..275970aea 100644 --- a/source/PlayniteSDK/PlayniteSDK.csproj +++ b/source/PlayniteSDK/PlayniteSDK.csproj @@ -33,7 +33,8 @@ - ..\packages\LiteDB.3.1.4\lib\net462\LiteDB.dll + False + ..\..\references\LiteDB.dll ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll diff --git a/source/PlayniteSDK/packages.config b/source/PlayniteSDK/packages.config index d809ff734..6cae1e63d 100644 --- a/source/PlayniteSDK/packages.config +++ b/source/PlayniteSDK/packages.config @@ -1,5 +1,4 @@  - \ No newline at end of file diff --git a/source/PlayniteServices/Controllers/IGDB/GameController.cs b/source/PlayniteServices/Controllers/IGDB/GameController.cs index 7711fa51b..cd347dbf0 100644 --- a/source/PlayniteServices/Controllers/IGDB/GameController.cs +++ b/source/PlayniteServices/Controllers/IGDB/GameController.cs @@ -57,7 +57,9 @@ public async Task> Get(ulong gameId, [FromQuery]str alternative_names = game.alternative_names, external = game.external, screenshots = game.screenshots, - videos = game.videos + videos = game.videos, + artworks = game.artworks, + release_dates = game.release_dates }; if (game.developers?.Any() == true) @@ -103,13 +105,23 @@ public async Task> Get(ulong gameId, [FromQuery]str if (game.themes?.Any() == true) { parsedGame.themes = new List(); - foreach (var genre in game.themes) + foreach (var theme in game.themes) { - var dbTheme = (await (new ThemeController()).Get(genre, apiKey)).Data; + var dbTheme = (await (new ThemeController()).Get(theme, apiKey)).Data; parsedGame.themes.Add(dbTheme.name); } } + if (game.platforms?.Any() == true) + { + parsedGame.platforms = new List(); + foreach (var platform in game.platforms) + { + var dbPlatform = (await (new PlatformController()).Get(platform, apiKey)).Data; + parsedGame.platforms.Add(dbPlatform.name); + } + } + cacheCollection.Upsert(parsedGame); return new ServicesResponse(parsedGame, string.Empty); } diff --git a/source/PlayniteServices/Controllers/IGDB/GamesBySteamIdController.cs b/source/PlayniteServices/Controllers/IGDB/GamesBySteamIdController.cs new file mode 100644 index 000000000..a8818e3a0 --- /dev/null +++ b/source/PlayniteServices/Controllers/IGDB/GamesBySteamIdController.cs @@ -0,0 +1,51 @@ +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; +using PlayniteServices.Models.IGDB; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using LiteDB; + +namespace PlayniteServices.Controllers.IGDB +{ + [Route("api/igdb/gamesBySteamId")] + public class GamesBySteamIdController : Controller + { + [HttpGet("{gameId}")] + public async Task> Get(string gameId, [FromQuery]string apiKey) + { + var ulongId = ulong.Parse(gameId); + var cacheCollection = Program.DatabaseCache.GetCollection("IGBDSteamIdCache"); + var cache = cacheCollection.FindById(ulongId); + if (cache != null) + { + var dateDiff = DateTime.Now - cache.creation_time; + if (dateDiff.TotalHours <= (IGDB.CacheTimeout + 100)) + { + return new ServicesResponse(cache.igdbId, string.Empty); + } + } + + var url = string.Format(@"/games/?fields=name,id&filter[external.steam][eq]={0}&limit=1", gameId); + var libraryStringResult = await IGDB.SendStringRequest(url, apiKey); + var games = JsonConvert.DeserializeObject>(libraryStringResult); + if (games.Any()) + { + cacheCollection.Upsert(new SteamIdGame() + { + steamId = ulongId, + igdbId = games.First().id, + creation_time = DateTime.Now + }); + + return new ServicesResponse(games.First().id, string.Empty); + } + else + { + return new ServicesResponse(0, string.Empty); + } + } + } +} diff --git a/source/PlayniteServices/Controllers/IGDB/GamesController.cs b/source/PlayniteServices/Controllers/IGDB/GamesController.cs index 28ea3acf4..aa63a8b1c 100644 --- a/source/PlayniteServices/Controllers/IGDB/GamesController.cs +++ b/source/PlayniteServices/Controllers/IGDB/GamesController.cs @@ -28,7 +28,7 @@ public async Task>> Get(string gameName, [FromQuery] } } - var url = string.Format(@"games/?fields=name,first_release_date&limit=40&offset=0&search={0}", gameName); + var url = string.Format(@"games/?fields=*&limit=40&offset=0&search={0}", gameName); var libraryStringResult = await IGDB.SendStringRequest(url, apiKey); var games = JsonConvert.DeserializeObject>(libraryStringResult); cacheCollection.Upsert(new GamesSearch() diff --git a/source/PlayniteServices/Controllers/IGDB/IGDB.cs b/source/PlayniteServices/Controllers/IGDB/IGDB.cs index 4ab17d0e0..06df255c8 100644 --- a/source/PlayniteServices/Controllers/IGDB/IGDB.cs +++ b/source/PlayniteServices/Controllers/IGDB/IGDB.cs @@ -32,20 +32,10 @@ public static int CacheTimeout } } - private static HttpClient httpClient; public static HttpClient HttpClient { - get - { - if (httpClient == null) - { - httpClient = new HttpClient(); - - } - - return httpClient; - } - } + get; + } = new HttpClient(); private static HttpRequestMessage CreateRequest(string url, string apiKey) { @@ -76,5 +66,17 @@ public static async Task SendStringRequest(string url, string key) var sharedResponse = await HttpClient.SendAsync(sharedRequest); return await sharedResponse.Content.ReadAsStringAsync(); } + + public static async Task SendDirectRequest(string url) + { + var request = new HttpRequestMessage() + { + RequestUri = new Uri(url), + Method = HttpMethod.Get + }; + + var response = await HttpClient.SendAsync(request); + return await response.Content.ReadAsStringAsync(); + } } } diff --git a/source/PlayniteServices/Controllers/IGDB/NameRecognitionController.cs b/source/PlayniteServices/Controllers/IGDB/NameRecognitionController.cs new file mode 100644 index 000000000..9e6958d82 --- /dev/null +++ b/source/PlayniteServices/Controllers/IGDB/NameRecognitionController.cs @@ -0,0 +1,50 @@ +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; +using PlayniteServices.Models.IGDB; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using LiteDB; + +namespace PlayniteServices.Controllers.IGDB +{ + [Route("api/igdb/nameRecognition")] + public class NameRecognitionController : Controller + { + [HttpGet("{gameName}")] + public async Task> Get(string gameName, [FromQuery]string apiKey) + { + //var cacheCollection = Program.DatabaseCache.GetCollection("IGBDSteamIdCache"); + //var cache = cacheCollection.FindById(ulongId); + //if (cache != null) + //{ + // var dateDiff = DateTime.Now - cache.creation_time; + // if (dateDiff.TotalHours <= (IGDB.CacheTimeout + 100)) + // { + // return new ServicesResponse(cache.igdbId, string.Empty); + // } + //} + + var url = string.Format(@"https://namematcher.igdb.com/entityrecognition?subject={0}&limit=5", gameName); + var libraryStringResult = await IGDB.SendDirectRequest(url); + var games = JsonConvert.DeserializeObject>(libraryStringResult); + if (games.Any()) + { + //cacheCollection.Upsert(new SteamIdGame() + //{ + // steamId = ulongId, + // igdbId = games.First().id, + // creation_time = DateTime.Now + //}); + + return new ServicesResponse(games.First().id, string.Empty); + } + else + { + return new ServicesResponse(0, string.Empty); + } + } + } +} diff --git a/source/PlayniteServices/Controllers/IGDB/PlatformController.cs b/source/PlayniteServices/Controllers/IGDB/PlatformController.cs new file mode 100644 index 000000000..59be02ba7 --- /dev/null +++ b/source/PlayniteServices/Controllers/IGDB/PlatformController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; +using PlayniteServices.Models.IGDB; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace PlayniteServices.Controllers.IGDB +{ + [Route("api/igdb/platforms")] + public class PlatformController : Controller + { + [HttpGet("{platformId}")] + public async Task> Get(UInt64 platformId, [FromQuery]string apiKey) + { + var cacheCollection = Program.DatabaseCache.GetCollection("IGBDPlatformsCache"); + var cache = cacheCollection.FindById(platformId); + if (cache != null) + { + return new ServicesResponse(cache, string.Empty); + } + + var url = string.Format(@"platforms/{0}?fields=name,url", platformId); + var stringResult = await IGDB.SendStringRequest(url, apiKey); + var theme = JsonConvert.DeserializeObject>(stringResult)[0]; + cacheCollection.Insert(theme); + return new ServicesResponse(theme, string.Empty); + } + } +} diff --git a/source/PlayniteServices/Models/IGDB/IGDB.cs b/source/PlayniteServices/Models/IGDB/IGDB.cs index 5fde9f30e..b053d0fa2 100644 --- a/source/PlayniteServices/Models/IGDB/IGDB.cs +++ b/source/PlayniteServices/Models/IGDB/IGDB.cs @@ -23,6 +23,26 @@ public enum WebSiteCategory : ulong Steam = 13 } + public class SteamIdGame + { + [BsonId(false)] + [BsonIndex(true)] + public ulong steamId + { + get; set; + } + + public ulong igdbId + { + get; set; + } + + public DateTime creation_time + { + get; set; + } + } + public class GamesSearch { [BsonId(false)] @@ -105,110 +125,60 @@ public string video_id } } - public class ParsedGame + public class ReleaseDate { - public ulong id - { - get; set; - } - - public string name - { - get; set; - } - - public string summary - { - get; set; - } - - public List developers - { - get; set; - } - - public List publishers - { - get; set; - } - - public List genres - { - get; set; - } - - public List themes - { - get; set; - } - - public List game_modes - { - get; set; - } - - public long first_release_date - { - get; set; - } - - public string cover - { - get; set; - } - - public List websites - { - get; set; - } + public ulong id { get; set; } + public ulong game { get; set; } + public ulong category { get; set; } + public ulong platform { get; set; } + public string human { get; set; } + public long updated_at { get; set; } + public long created_at { get; set; } + public long date { get; set; } + public uint region { get; set; } + public uint y { get; set; } + public uint m { get; set; } + } + public class ParsedGame : Game + { [JsonIgnore] public DateTime creation_time { get; set; } - public double rating - { - get; set; - } - - public double aggregated_rating - { - get; set; - } - - public double total_rating + public new List developers { get; set; } - public ulong collection + public new List publishers { get; set; } - public ulong franchise + public new List genres { get; set; } - public List alternative_names + public new List themes { get; set; } - public Dictionary external + public new List game_modes { get; set; } - public List screenshots + public new string cover { get; set; } - public List - ..\packages\LiteDB.3.1.4\lib\net462\LiteDB.dll + False + ..\..\references\LiteDB.dll @@ -150,11 +151,9 @@ - - - - - + + + diff --git a/source/PlayniteTests/Providers/BattleNet/BattleNetMetadataProviderTests.cs b/source/PlayniteTests/Providers/BattleNet/BattleNetMetadataProviderTests.cs index 6f819e1cb..83e4c8096 100644 --- a/source/PlayniteTests/Providers/BattleNet/BattleNetMetadataProviderTests.cs +++ b/source/PlayniteTests/Providers/BattleNet/BattleNetMetadataProviderTests.cs @@ -15,8 +15,7 @@ public class BattleNetMetadataProviderTests public void StandardDownloadTest() { var provider = new BattleNetMetadataProvider(); - Assert.IsTrue(provider.GetSupportsIdSearch()); - var data = provider.GetGameData("D3"); + var data = provider.GetMetadata("D3"); Assert.IsNotNull(data.GameData); Assert.IsNotNull(data.Icon); Assert.IsNotNull(data.Image); diff --git a/source/PlayniteTests/Providers/GOG/GogLibraryTests.cs b/source/PlayniteTests/Providers/GOG/GogLibraryTests.cs index c41384885..8b54b5f14 100644 --- a/source/PlayniteTests/Providers/GOG/GogLibraryTests.cs +++ b/source/PlayniteTests/Providers/GOG/GogLibraryTests.cs @@ -42,24 +42,24 @@ public void DownloadGameMetadataTest() var existingStore = gogLib.DownloadGameMetadata("1207658645"); Assert.IsNotNull(existingStore.GameDetails); Assert.IsNotNull(existingStore.StoreDetails); - Assert.IsNotNull(existingStore.Icon.Data); - Assert.IsNotNull(existingStore.Image.Data); + Assert.IsNotNull(existingStore.Icon.Content); + Assert.IsNotNull(existingStore.Image.Content); Assert.IsNotNull(existingStore.BackgroundImage); // Game with missing store link in api data var customStore = gogLib.DownloadGameMetadata("1207662223", "https://www.gog.com/game/commandos_2_3"); Assert.IsNotNull(customStore.GameDetails); Assert.IsNotNull(customStore.StoreDetails); - Assert.IsNotNull(customStore.Icon.Data); - Assert.IsNotNull(customStore.Image.Data); + Assert.IsNotNull(customStore.Icon.Content); + Assert.IsNotNull(customStore.Image.Content); Assert.IsNotNull(customStore.BackgroundImage); // Existing game not present on store var nonStore = gogLib.DownloadGameMetadata("2"); Assert.IsNotNull(nonStore.GameDetails); Assert.IsNull(nonStore.StoreDetails); - Assert.IsNotNull(nonStore.Icon.Data); - Assert.IsNotNull(nonStore.Image.Data); + Assert.IsNotNull(nonStore.Icon.Content); + Assert.IsNotNull(nonStore.Image.Content); Assert.IsNotNull(nonStore.BackgroundImage); } diff --git a/source/PlayniteTests/Providers/GOG/GogMetadataProviderTests.cs b/source/PlayniteTests/Providers/GOG/GogMetadataProviderTests.cs index 3d1b2b89a..72393e9e6 100644 --- a/source/PlayniteTests/Providers/GOG/GogMetadataProviderTests.cs +++ b/source/PlayniteTests/Providers/GOG/GogMetadataProviderTests.cs @@ -15,8 +15,7 @@ public class GogMetadataProviderTests public void StandardDownloadTest() { var provider = new GogMetadataProvider(); - Assert.IsTrue(provider.GetSupportsIdSearch()); - var data = provider.GetGameData("1207659012"); + var data = provider.GetMetadata("1207659012"); Assert.IsNotNull(data.GameData); Assert.IsNotNull(data.Icon); Assert.IsNotNull(data.Image); diff --git a/source/PlayniteTests/Providers/Origin/OriginLibraryTests.cs b/source/PlayniteTests/Providers/Origin/OriginLibraryTests.cs index 347432cb6..5fdf9fa20 100644 --- a/source/PlayniteTests/Providers/Origin/OriginLibraryTests.cs +++ b/source/PlayniteTests/Providers/Origin/OriginLibraryTests.cs @@ -55,7 +55,7 @@ public void DownloadGameMetadataTest() var originLib = new OriginLibrary(); var existingStore = originLib.DownloadGameMetadata("OFB-EAST:60108"); Assert.IsNotNull(existingStore.StoreDetails); - Assert.IsNotNull(existingStore.Image.Data); + Assert.IsNotNull(existingStore.Image.Content); } [Test] diff --git a/source/PlayniteTests/Providers/Origin/OriginMetadataProviderTests.cs b/source/PlayniteTests/Providers/Origin/OriginMetadataProviderTests.cs index ae875956c..569f9048f 100644 --- a/source/PlayniteTests/Providers/Origin/OriginMetadataProviderTests.cs +++ b/source/PlayniteTests/Providers/Origin/OriginMetadataProviderTests.cs @@ -15,8 +15,7 @@ public class OriginMetadataProviderTests public void StandardDownloadTest() { var provider = new OriginMetadataProvider(); - Assert.IsTrue(provider.GetSupportsIdSearch()); - var data = provider.GetGameData("Origin.OFR.50.0000557"); + var data = provider.GetMetadata("Origin.OFR.50.0000557"); Assert.IsNotNull(data.GameData); Assert.IsNotNull(data.Image); Assert.IsNotNull(data.GameData.ReleaseDate); diff --git a/source/PlayniteTests/Providers/Steam/SteamLibraryTests.cs b/source/PlayniteTests/Providers/Steam/SteamLibraryTests.cs index f4d2fed2e..5a913e976 100644 --- a/source/PlayniteTests/Providers/Steam/SteamLibraryTests.cs +++ b/source/PlayniteTests/Providers/Steam/SteamLibraryTests.cs @@ -51,16 +51,16 @@ public void DownloadGameMetadataTest() var existing = steamLib.DownloadGameMetadata(107410, false); Assert.IsNotNull(existing.ProductDetails); Assert.IsNotNull(existing.StoreDetails); - Assert.IsNotNull(existing.Icon.Data); - Assert.IsNotNull(existing.Image.Data); + Assert.IsNotNull(existing.Icon.Content); + Assert.IsNotNull(existing.Image.Content); Assert.IsNotNull(existing.BackgroundImage); // NonExisting store var nonExisting = steamLib.DownloadGameMetadata(201280, true); Assert.IsNotNull(nonExisting.ProductDetails); Assert.IsNull(nonExisting.StoreDetails); - Assert.IsNotNull(nonExisting.Icon.Data); - Assert.IsNotNull(nonExisting.Image.Data); + Assert.IsNotNull(nonExisting.Icon.Content); + Assert.IsNotNull(nonExisting.Image.Content); Assert.IsNull(nonExisting.BackgroundImage); } diff --git a/source/PlayniteTests/Providers/Steam/SteamMetadataProviderTests.cs b/source/PlayniteTests/Providers/Steam/SteamMetadataProviderTests.cs index 7887d70d1..6e4961ce9 100644 --- a/source/PlayniteTests/Providers/Steam/SteamMetadataProviderTests.cs +++ b/source/PlayniteTests/Providers/Steam/SteamMetadataProviderTests.cs @@ -15,8 +15,7 @@ public class SteamMetadataProviderTests public void StandardDownloadTest() { var provider = new SteamMetadataProvider(); - Assert.IsTrue(provider.GetSupportsIdSearch()); - var data = provider.GetGameData("578080"); + var data = provider.GetMetadata("578080"); Assert.IsNotNull(data.GameData); Assert.IsNotNull(data.Icon); Assert.IsNotNull(data.Image); diff --git a/source/PlayniteTests/StringExtensionsTests.cs b/source/PlayniteTests/StringExtensionsTests.cs index f98323903..b418d0f1e 100644 --- a/source/PlayniteTests/StringExtensionsTests.cs +++ b/source/PlayniteTests/StringExtensionsTests.cs @@ -21,10 +21,10 @@ public void NormalizeGameNameTest() StringExtensions.NormalizeGameName("Witcher 3, The")); Assert.AreEqual("Pokemon Red Test", - StringExtensions.NormalizeGameName("Pokemon.Red.[US].[l33th4xor].Test.[22]")); + StringExtensions.NormalizeGameName("Pokemon.Red.[US].[l33th4xor].Test.[22]")); Assert.AreEqual("Pokemon Red Test", - StringExtensions.NormalizeGameName("Pokemon.Red.[US].(l33th 4xor).Test.(22)")); + StringExtensions.NormalizeGameName("Pokemon.Red.[US].(l33th 4xor).Test.(22)")); } [Test] @@ -36,5 +36,16 @@ public void ConvertToSortableName() Assert.AreEqual("Usual Game", StringExtensions.ConvertToSortableName("An Usual Game")); Assert.AreEqual("AnUsual Game", StringExtensions.ConvertToSortableName("AnUsual Game")); } + + [Test] + public void GetPathWithoutAllExtensionsTest() + { + Assert.AreEqual(@"c:\test\SomeFile", StringExtensions.GetPathWithoutAllExtensions(@"c:\test\SomeFile.zip")); + Assert.AreEqual(@"SomeFile", StringExtensions.GetPathWithoutAllExtensions(@"SomeFile.r888s.42rar.zip1")); + Assert.AreEqual(@"SomeFile", StringExtensions.GetPathWithoutAllExtensions(@"SomeFile")); + Assert.AreEqual(@"SomeFile.Test", StringExtensions.GetPathWithoutAllExtensions(@"SomeFile.Test.42rar.zip1")); + Assert.AreEqual(@"SomeFile.zip1test.aa_aa", StringExtensions.GetPathWithoutAllExtensions(@"SomeFile.zip1test.aa_aa.zip")); + Assert.AreEqual(@"SomeFile.zip1_test", StringExtensions.GetPathWithoutAllExtensions(@"SomeFile.zip1_test")); + } } } diff --git a/source/PlayniteTests/packages.config b/source/PlayniteTests/packages.config index 1da2afb27..33f65c079 100644 --- a/source/PlayniteTests/packages.config +++ b/source/PlayniteTests/packages.config @@ -4,7 +4,6 @@ - diff --git a/source/PlayniteUI/App.xaml.cs b/source/PlayniteUI/App.xaml.cs index 71624b8f1..30bc96b95 100644 --- a/source/PlayniteUI/App.xaml.cs +++ b/source/PlayniteUI/App.xaml.cs @@ -19,7 +19,7 @@ using System.IO; using System.Windows.Input; using System.ComponentModel; -using Playnite.MetaProviders; +using Playnite.Metadata; using Playnite.API; using PlayniteUI.API; using Playnite.Plugins; @@ -34,7 +34,7 @@ namespace PlayniteUI public partial class App : Application, INotifyPropertyChanged, IPlayniteApplication { private static NLog.Logger logger = LogManager.GetCurrentClassLogger(); - private string instanceMuxet = "PlayniteInstaceMutex"; + private const string instanceMuxet = "PlayniteInstaceMutex"; private Mutex appMutex; private bool resourcesReleased = false; private PipeService pipeService; @@ -42,6 +42,11 @@ public partial class App : Application, INotifyPropertyChanged, IPlayniteApplica private XInputDevice xdevice; private DialogsFactory dialogs; + public Version CurrentVersion + { + get => Updater.GetCurrentVersion(); + } + public PlayniteAPI Api { get; set; @@ -49,7 +54,6 @@ public PlayniteAPI Api public event PropertyChangedEventHandler PropertyChanged; - public static MainViewModel MainModel { get; @@ -103,6 +107,7 @@ public App() private void Application_SessionEnding(object sender, SessionEndingCancelEventArgs e) { + logger.Info("Shutting down application because of session ending."); Quit(); } @@ -195,13 +200,11 @@ private void Application_Startup(object sender, StartupEventArgs e) // First run wizard ulong steamCatImportId = 0; bool isFirstStart = !AppSettings.FirstTimeWizardComplete; + bool existingDb = false; if (!AppSettings.FirstTimeWizardComplete) { var wizardWindow = FirstTimeStartupWindowFactory.Instance; - var wizardModel = new FirstTimeStartupViewModel( - wizardWindow, - dialogs, - new ResourceProvider()); + var wizardModel = new FirstTimeStartupViewModel(wizardWindow, dialogs, new ResourceProvider()); if (wizardModel.OpenView() == true) { var settings = wizardModel.Settings; @@ -221,23 +224,13 @@ private void Application_Startup(object sender, StartupEventArgs e) AppSettings.BattleNetSettings = settings.BattleNetSettings; AppSettings.UplaySettings = settings.UplaySettings; AppSettings.SaveSettings(); - + existingDb = File.Exists(AppSettings.DatabasePath); Database = new GameDatabase(AppSettings, AppSettings.DatabasePath); Database.OpenDatabase(); - if (wizardModel.ImportedGames.Count > 0) + if (wizardModel.ImportedGames?.Any() == true) { - foreach (var game in wizardModel.ImportedGames) - { - if (game.Icon != null) - { - var iconId = "images/custom/" + game.Icon.Name; - game.Game.Icon = Database.AddFileNoDuplicate(iconId, game.Icon.Name, game.Icon.Data); ; - } - - Database.AssignPcPlatform(game.Game); - Database.AddGame(game.Game); - } + InstalledGamesViewModel.AddImportableGamesToDb(wizardModel.ImportedGames, Database); } if (wizardModel.SteamImportCategories) @@ -249,6 +242,7 @@ private void Application_Startup(object sender, StartupEventArgs e) { AppSettings.DatabasePath = Path.Combine(Paths.UserProgramDataPath, "games.db"); AppSettings.SaveSettings(); + existingDb = File.Exists(AppSettings.DatabasePath); Database = new GameDatabase(AppSettings, AppSettings.DatabasePath); } } @@ -282,7 +276,7 @@ private void Application_Startup(object sender, StartupEventArgs e) } else { - OpenNormalView(steamCatImportId, isFirstStart); + OpenNormalView(steamCatImportId, isFirstStart, existingDb); } // Update and stats @@ -310,7 +304,7 @@ private void Application_Startup(object sender, StartupEventArgs e) SimulateNavigationKeys = true }; - logger.Info("Application started"); + logger.Info($"Application {CurrentVersion} started"); } private void PipeService_CommandExecuted(object sender, CommandExecutedEventArgs args) @@ -406,12 +400,14 @@ public void Restart() public void Quit() { + logger.Info("Shutting down Playnite"); ReleaseResources(); Shutdown(0); } private void ReleaseResources() { + logger.Debug("Releasing Playnite resources..."); if (resourcesReleased) { return; @@ -445,8 +441,9 @@ private void ReleaseResources() resourcesReleased = true; } - public async void OpenNormalView(ulong steamCatImportId, bool isFirstStart) + public async void OpenNormalView(ulong steamCatImportId, bool isFirstStart, bool existingDb) { + logger.Debug("Opening Desktop view"); if (Database.IsOpen) { FullscreenModel = null; @@ -472,7 +469,7 @@ public async void OpenNormalView(ulong steamCatImportId, bool isFirstStart) await MainModel.UpdateDatabase(AppSettings.UpdateLibStartup, steamCatImportId, !isFirstStart); } - if (isFirstStart) + if (isFirstStart && !existingDb) { var metaSettings = new MetadataDownloaderSettings(); metaSettings.ConfigureFields(MetadataSource.StoreOverIGDB, true); @@ -484,6 +481,7 @@ public async void OpenNormalView(ulong steamCatImportId, bool isFirstStart) public async void OpenFullscreenView(bool updateDb) { + logger.Debug("Opening Fullscreen view"); if (Database.IsOpen) { MainModel = null; @@ -556,6 +554,8 @@ private void ApplyTheme(string name, string profile, bool fullscreen) themeProfile = profile; } + logger.Debug($"Applying theme {themeName}, {themeProfile}, {fullscreen}"); + if (fullscreen) { Themes.ApplyFullscreenTheme(themeName, themeProfile, true); diff --git a/source/PlayniteUI/Converters/AfterGameLaunchOptionToStringConverter.cs b/source/PlayniteUI/Converters/AfterGameLaunchOptionToStringConverter.cs index f077da9a8..5ff161d81 100644 --- a/source/PlayniteUI/Converters/AfterGameLaunchOptionToStringConverter.cs +++ b/source/PlayniteUI/Converters/AfterGameLaunchOptionToStringConverter.cs @@ -1,5 +1,4 @@ using Playnite; -using Playnite.MetaProviders; using System; using System.Collections.Generic; using System.Linq; diff --git a/source/PlayniteUI/Converters/CompletionStatusToStringConverter.cs b/source/PlayniteUI/Converters/CompletionStatusToStringConverter.cs index 4d4214839..5c8d868cd 100644 --- a/source/PlayniteUI/Converters/CompletionStatusToStringConverter.cs +++ b/source/PlayniteUI/Converters/CompletionStatusToStringConverter.cs @@ -1,5 +1,4 @@ using Playnite; -using Playnite.MetaProviders; using Playnite.SDK.Models; using System; using System.Collections.Generic; diff --git a/source/PlayniteUI/Converters/FilterToStringConverter.cs b/source/PlayniteUI/Converters/FilterToStringConverter.cs new file mode 100644 index 000000000..824024a31 --- /dev/null +++ b/source/PlayniteUI/Converters/FilterToStringConverter.cs @@ -0,0 +1,44 @@ +using Playnite; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Data; +using System.Windows.Markup; + +namespace PlayniteUI +{ + public class FilterToStringConverter : MarkupExtension, IValueConverter + { + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + // TODO: this is just for fullscreen mode right now where only three filter options are available. + // This should be extended in future. + var settings = (FilterSettings)value; + if (settings.IsInstalled) + { + return ResourceProvider.Instance.FindString("LOCGameIsInstalledTitle"); + } + else if (settings.IsUnInstalled) + { + return ResourceProvider.Instance.FindString("LOCGameIsUnInstalledTitle"); + } + else + { + return ResourceProvider.Instance.FindString("LOCAllGames"); + } + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } + } +} diff --git a/source/PlayniteUI/Converters/MetadataSourceToStringConverter.cs b/source/PlayniteUI/Converters/MetadataSourceToStringConverter.cs index ef0f4aad1..48032a053 100644 --- a/source/PlayniteUI/Converters/MetadataSourceToStringConverter.cs +++ b/source/PlayniteUI/Converters/MetadataSourceToStringConverter.cs @@ -1,5 +1,5 @@ using Playnite; -using Playnite.MetaProviders; +using Playnite.Metadata; using System; using System.Collections.Generic; using System.Linq; diff --git a/source/PlayniteUI/Localization/english.xaml b/source/PlayniteUI/Localization/english.xaml index 13542f884..afd5b4cbf 100644 --- a/source/PlayniteUI/Localization/english.xaml +++ b/source/PlayniteUI/Localization/english.xaml @@ -131,6 +131,7 @@ Automatically imported games may reappear in Playnite after next import procedur Saturday Sunday Import completed successfully. + All Games Special variables can be used to add dynamic string: {InstallDir} - Game installation directory @@ -485,7 +486,8 @@ It's currently being used by {1} game(s) and {2} emulator(s). Failed to check for new version. No new version found, you are up to date. Failed to download and install update. - Background task is currently in progress, do you want to cancel it and processed with update installation? + Background task is currently running, do you want to cancel it and processed with update installation? + Background task is currently running, do you want to cancel it and close Playnite? Reload theme list Apply selected theme diff --git a/source/PlayniteUI/Localization/italian.xaml b/source/PlayniteUI/Localization/italian.xaml new file mode 100644 index 000000000..72fea746b --- /dev/null +++ b/source/PlayniteUI/Localization/italian.xaml @@ -0,0 +1,524 @@ + + + Italiano + Italian + + Lingua + + Esci + Filtro Attivato (clicca per abilitare il pannello filtro) + Filtro Disattivato (clicca per abilitare il pannello filtro) + + Dati non validi + Salvare Modifiche? + + Pagina principale su www.playnite.link + Codice Sorgente su GitHub + Crea pacchetto diag. + Informazioni su Playnite + Creato da Josef Němec + + Assegna una categoria + Imposta categorie + Aggiungi una categoria + Selezionato - Assegna categoria +Non Selezionato - Rimuovi categoria +Indeterminato - Nessuna Modifica (durante la modifica di più giochi) + + Oh no, è successo qualcosa di brutto... + Playnite ha riscontrato un problema e non ha potuto risolverlo. Se ci vuoi aiutare, la pregriamo di creare un pacchetto diagnostica e segnalare il problema. Grazie. + Segnala un problema + Riavvia Playnite + Chiudi Playnite + + Rimuovi Gioco/chi? + Impossibile rimuovere il gioco mentre è avviato o durante la installazione/disinstallazione. + Impossibile disinstallare il gioco mentre è avviato. + Sicuro di voler rimuovere questo gioco? + +I giochi automaticamente importati potrebbero ricomparire in Playnite dopo la prossima procedura d'importazione (secondo le sue impostazioni). + Sicuro di voler rimuovere {0} giochi? + +I giochi automaticamente importati potrebbero ricomparire in Playnite dopo la prossima procedura d'importazione (secondo le sue impostazioni). + + Lista amici di Steam + La sezione contiene delle modifiche da salvare + Aggiornamento del database a versione... + Aggiornamento del database fallito, impossibile aprire il file del database. + + Errore di gioco + Impossibile avviare il gioco. '{0}' non è stato trovato nel database. + Impossibile avviare gioco: {0} + Impossibile avviare l'azione: {0} + Impossibile aprire il percorso del gioco: {0} + La creazione del collegamento è fallita: {0} + Imposssibile installare il gioco: {0} + Impossibile disinstallare il gioco: {0} + + + Rimuovi + Copia + Aggiungi + Icona di base + Copertina di base + Finito + Avanti + Indietro + Importa + Sorgente + Nome + Serie + Versione + Classificazione + Regione + Giocato l'ultima volta + Numero di partite + Aggiunto + Modificato + Sito + Percorso + OK + Salva + Chiudi + Cancella + Si + No + Benvenuto + Utente locale + Autentifica + Generale + Media + Collegamenti + Installazione + Azioni + Scaricando... + Tipo + Profilo + Profili + Rimuovi + Scaricamento + Cerca + Genere + Sviluppatore + Editore + Categoria + Etichetta + Etichette + Zoom + Lista + Cover + Dettagli + Personalizza + URL + Patrons + Licenza + Contributori + Chiusura Playnite... + Oggi + Ieri + Lunedi + Martedi + Mercoledi + Giovedi + Venerdi + Sabato + Domenica + Importazione completata + Tutti i giochi + + Delle variabili speciali possono essere usate per aggiungere stringhe dinamiche: +{InstallDir} - Cartella d'installazione del gioco +{ImagePath} - Percorso del gioco ISO/ROM se impostato +{ImageName} - Nome del file ISO/ROM del gioco +{ImageNameNoExt} - Nome del file ISO/ROM del gioco senza estensione +{PlayniteDir} - Cartella di installazione di Playnite + +Esempio: -image "{ImagePath}" -fullscreen +Risultati in: -image "c:\folder\image.iso" -fullscreen + + Impossibile ottenere l'icona se l'azione di avvio risulta mancante o impostata su URL. + + Scarica il metadata soltanto per i giochi sprovvisti + Abilitare questa opzione significa saltare lo scaricamento del gioco se alcuni dati (di uno specifico campo) sono gia presenti. + Selezione Giochi + Seleziona quali giochi dorebbero essere aggiornati con un nuovo metadata: + Tutti i giochi dalla banca dati + Tutti i giochi al momento filtrati + Solo i giochi selezionati + Negozio ufficiale + IGDB + IGDB > Negozio ufficiale + Negozio ufficiale > IGDB + Selezionare quali campi dovrebbero essere automaticamente popolati da Playnite e da quali fonti dovrebbero essere usati per ricavare i dati. + Negozio: +Usare soltanto i dati del negozio ufficiale. + +IGDB: +Usare soltanto i dati dalla bancadati di IDDB.com. + +IGDB > Negozio ufficiale: +Preferire i dati dalla banca dati di IGDB.com e se non sono disponibili usare i dati del negozio ufficiale. + +Negozio ufficiale > IGDB: +Preferire i dati del negozio ufficiale e se non sono disponibili usare la banca dati di IGDB.com. + + + Scaricamento aggiornamenti libreria di Steam... + Scaricamento aggiornamenti libreria di GOG... + Scaricamento aggiornamenti libreria di Origin... + Scaricamento aggiornamenti libreria di Battle.net... + Scaricamento metadata... + Importazione dei giochi installati... + Scaricamento degli aggiornamenti della libreria... + Scaricamento degli aggiornamenti della libreria + Aggiornamento libreria completato + Rilascio delle risorse... + + + Configurazione + Impostazioni + Piattaforme ed emulatori + Strumenti + Scaricamento metadata + Aprire Client di terze parti + Aggiorna la lista dei giochi + Aggiungere gioco + Manualmente + Importa installati o dalla cartella + Importa gioco/chi emulati + Informazioni su Playnite + Invia commento + Avvia a Schermo Intero + Documentazione + + + Generale + Aspetto + Avanzate + Schermo intero + Distributore + Importa automaticamente le modifiche alla libreria + Importa i giochi installati + Importa tutti i giochi + Importa tutti i giochi (Le impostazioni di privacy dell'account devono essere impostate su Pubblico o dev'essere fornita la chiave API) + Il percorso del file della banca dati non valido, dev'essere impostato un percorso file corretto. + Nome dell'account di Steam non puù essere vuoto. + Nessun account di Steam selezionato per l'importazione della libreria. + Nessun account di Steam selezionato per l'importazione delle categorie. + Scaricare gli aggiornamenti della libreria di terze parti all'avvio + Avvia a Schermo Intero + Includi Steam, Origin e altri. + Caricamento asincrono dell'immagine (riavvio richiesto) + Migliora la fluidità dello scorrimento delle liste giochi in cambio di tempi di caricamento dell'immagine lenti. + Mostra il nome del gioco quando la copertina risulta mancante + Mostra le icone del gioco nella visualizzazione a Lista + Disattiva l'accelerazione hardware (riavvio richiesto) + Utilizzare quando si presentano scatti o problemi simili nell'interfaccia grafica + Tema + Profilo del tema + Tema Schermo Intero + Colore del tema Schermo Intero + Percorso della banca dati + Qual è il nome del mio account? + Stato del login: + Specifica la libreria utente per: + Impostazioni di Playnite + Importa le categorie di Steam: + da account: + Ripulire la web cache + Potrebbe risolvere i problemi riscontrati durante il collegamento degli account. + Mostra icona + Riduci a icona + Chiudi a icona + Quando si avvia il gioco: + Integra utilizzando il client Galaxy + Attiverà funzioni come salvataggi cloud, conteggio del tempo di gioco ecc. + Questo sovvrascriverà le attuali categorie su tutti i giochi di Steam. Desidera continuare? + Importa le categorie? + Impossibile importare le categorie, l'account per l'importazione non è selezionato. + Impossibile importare le categorie, la banca dati non è aperta. + Importazione delle categorie di Steam fallita: {0} + Questo ti farà uscire da tutti i servizi collegati. E' richiesto il riavvio dell'applicazione, desidera procedere? + Ripulire la cache? + La chiave API è richiesta per gli account privati di Steam + Ottieni la chiave API + E' richiesto il riavvio di Playnite per applicare il nuovo tema + Ottieni più temi + Creare un nuovo tema + Contribuisci a una nuova localizzazione + Alcune delle impostazioni cambiate richiedono il riavvio di Playnite. Desidera riavviare Playnite adesso? + + Ogni processo al momento attivo (scaricamento di metadata) sarà annullato. + Riavviare Playnite? + Il nuovo percorso file della banca dati sarà usato soltanto dopo che l'applicazione verrà riavviata. + Il tempo di gioco non verrà registrato se è impostata l'azione "Chiudi". + Numero di righe + Numero di colonne + Numero di dettagli delle righe + Preferire le immagini di gioco piuttosto che l'immagine di sfondo del negozio + Playnite userà le schermate disponibili come sfondo al posto dello sfondo del negozio. +Non si applica retroattivamente ai giochi esistenti. + + Errore di importazione + Controllo... + Autentificazione richiesta + Autentificazione fallita + + + Importazione del metadata + Scaricamento del metadata + + + Wizard dell'importazione dell'emulazione + Questo wizard la guiderà attraverso un processo di scaricamento e importazione di un'emulatore di console, cosi come l'importazione di giochi emulati. + Si ricordi che può sempre aggiungere altri emulatori e/o giochi in seguito attraverso il menù principale (nel menù "Strumenti" per le impostazioni dell'emulatore e nel menù "Aggiungi giochi" per i giochi emulati). + Di seguito la lista di emulatori che Playnite può riconoscere e configurare automaticamente. Può scaricarli e installarli visitando i loro siti. Una volta che ha installato gli emulatori, proceda nella schermata successiva per importarli in Playnite. + +Può anche configurare e importare ogni emulatore personalizzato attraverso il menù di configurazione. + Può importare ogni emulatore installato sul PC premendo il tasto di "Scansione automatica dalla cartella..." . Playnite cercerà la cartella selezionata per ogni emulatore conosciuto e provvederà l'opzione di importarli. Può importare da più cartelle usando tale tasto più volte, gli emulatori saranno aggiunti sul fondo della lista attuale. + Può importare i giochi premendo il tasto "Scansiona la cartella usando l'emulatore" . Selezionando l'emulatore appropriato, sarà comunicato a Playnite quali tipi di tile dovrebbero essere scansionati e importati. Può anche importare da più cartelle usando tale tasto più volte, i giochi saranno aggiunti sul fondo della lista attuale. + Non ci sono emulatori selezionati per l'importazione. Non sarà in grado di importare automaticamente ogni gioco emulato senza configurare per prima gli emulatori. Sicuro di voler continuare e uscire dal processo di importazione? + Non ci sono emulatori configurati in Playnite. Non può importare i giochi senza configurare per prima l'emulatore e selezionando i tipi di file appropriati. Desidera aggiungere ora qualche emulatore? + Scansiona la cartella usando l'emulatore + Seleziona i file + Scansione automatica dalla cartella... + Configura gli emulatori... + Scansione... + + + Configurazione iniziale + Questo Wizard la guiderà attraverso il processo di importazione e di configurazione automatica delle librerie di gioco esterne. Playnite può importare automaticamente i giochi da più servizi di gioco come Steam o Gog, e può inoltre mantenere aggiornata la libreria aggiornandola automaticamente durante l'avvio dell'applicazione. + +Si ricordi che può sempre aggiungere manualmente qualunque gioco personalizzato da qualsiasi piattaforma cliccando sul tasto "Playnite". + + Scelga il percorso per il file della banca dati della libreria. Il percorso può essere cambiato in seguito attraverso il menù delle impostazioni. + +Consiglio: se desidera di sincronizzare la sua libreria tra più computer, allora imposti il percorso della banca dati su una cartella cloud (Google Drive, Dropbox, ecc.) + Percorso di base dei dati del programma per tutti gli utenti + Percorso personalizzato + Integrazione della libreria + Importa automaticamente i giochi dai seguenti servizi. Ogni modifica successiva del gioco (stato di installazione) sarà automaticamente aggiornata all'avvio di Playnite o se attivato manualmente. Le impostazioni iniziali hanno effetto sulle impostazioni iniziali e su tutte le importazioni successive. + Integrazione di Steam + Integrazione di Origin + Integrazione di GOG + Integrazione di Battle.net + Integrazione di Uplay + Importa le categorie dall'account: + Altri giochi + Clicca sul tasto per importare altri giochi installati o naviga manualmente per essi (inclusi i giochi di Windows Store). + Importa giochi + Configurazione completata + Il setup iniziale è stato completato. Ricorda che può cambiare tutte le impostazioni in seguito attraverso il menù "Impostazioni". + +Può anche aggiungere qualunque gioco/chi in seguito dal menù principale cliccando sul logo dell'applicazione di Playnite. + + + Configurazione delle piattaforme e degli emulatori + Piattaforme + Piattaforma + Emulatori + Emulatore + Aggiungere piattaforma + Seleziona icona + Seleziona copertina + Seleziona file + Seleziona URL + Aggiungi emulatore + Piattaforma/e supportata/e + Desidera salvare le modifiche della piattaforma? + Desidera salvare le modifiche dell'emulatore? + Eseguibili + Parametri + Cartella funzionante + Tipologie di file supportati + Importa emulatori... + Scarica emulatori... + Caricare parametri di base da profilo emulatore conosciuto + Sicuro di voler rimuovere l'emulatore {0} ? +E'al momento utilizzato da {1} gioco/chi. + Sicuro di voler rimuovere la piattaforma {0} ? +E' al momento utilizzata da {1} gioco/chi e {2} emulatore/i. + Aiuto impostazioni + + Ordina per + Crescente + Decrescente + Non raggruppare + Raggruppa per distributore + Raggruppa per categoria + Raggruppa per piattaforma + + + Icona + Immagine di copertina + Immagine dello sfondo + Sorting Name + Distributore + Nome + Piattaforma + Categoria/e + Genere/i + Data di uscita + Sviluppatore/i + Etichetta/e + Editore/i + Installati + Disinstallati + Nascosti + Preferiti + Ultima attività + Categoria + Descrizione + Cartella d'installazione + Immagine della copertina + Collegamenti + Percorso Immagine/ISO + + + Errore della banca dati + Impossibile aprire la banca dati della libreria + Impossibile accedere la banca dati della libreria. Il file "{0}" è stato utilizzato da un'altro processo o è in un percorso inaccessibile. + Creazione del pacchetto diagnostica fallito. + Pacchetto diagnostica creato con successo. + + Importazione delle categorie di Steam fallita. + Importazione dei giochi di Battle.net installati fallita. + Importazione dei giochi di Steam installati fallita. + Importazione dei giochi di GOG installati fallita. + Importazione dei giochi di Origin installati fallita. + Importazione dei giochi di Uplay installati fallita. + + Scaricamento della librearia di Battle.net fallita. + Scaricamento della librearia di Steam fallita. + Scaricamento della librearia di GOG fallita. + Scaricamento della librearia di Origin fallita. + Scaricamento della librearia di Uplay fallita. + + Impossibile cercare dei giochi selezionando il profilo emulatore. Il profilo non coniente nessuna esensione file o piattaforma. + Avvio di Playnite fallito. Chiudere tutti i processi attivi e riprovare. + Applicazione del tema "{0}", profilo colore "{1}" {2} falliti + Impossibile aprire il collegamento, l'URL non è in un formato valido. + + + Modifica i dettagli del gioco + Utilizza l'icona dell'exe + URL immagine + AggiungiLink + Salva modifiche + Azione di avvio + Aggiungi Azione di avvio + Aggiungi Azione + Cancella Azione + Rimuovi Azione di avvio + Altre Azioni + Aggiungi giochi + Scansione cartella... + Rilevazione automatica + Sfoglia... + Apri Playnite + Impostazioni del profilo + Il nome del gioco non puo essere vuoto. + Il nome del gioco non può essere vuoto prima della ricerca del metadata. + Dati di gioco invalidi + Inserire un URL web valido che inizia con http:// o https:// + Seleziona URL + Scaricamento del metadata fallito: {0} + Errore di scaricamento + Riupulire i filtri + Account privato + Chiave API + Errore all'avvio + Errore tema + Ripulire tutto + Impostazione in corso... + Avvio + Esecuzione + URL non corretto + Non fare niente + Minimizza + Chiudi + Cambia + Avanzate + Mai + Non giocato + Giocato + Finito + Completato + Stato di completamento + Voto dell'utenza + Voto della critica + Voto della comunità + Script + Plugin + Estenzioni + Ricarica gli script + Tutti gli script sono stati ricaricati con successo. + Nessun gioco trovato + Ritorna alla Modalità desktop + Esci da Playnite + + Argomenti dell'emulatore + Ulteriori argomenti dell'emulatore + Sovvrascrivi gli argomenti dell'emulatore + + Seleziona un gioco da importare. + Ricerca del metadata + Aggiornamento disponibile + Modifiche dall'ultimo aggiornamento: + Installa l'aggiornamento + Controlla aggiornamenti + Errore aggiornamento + Controllo della nuova versione fallito. + Nessuna versione trovata, sei alla versione recente. + Scaricamento e installazione aggiornamento fallita. + Processo di backgorund in corso, desidera annullarlo per procedere all'installazione dell'aggiornamento? + Processo di backgorund in corso, desidera uscire da Playnite? + + Ricarica la lista dei temi + Applica il tema selezionato + Osserva la modifica del file + Applicare automaticamente il tema quando il file di sorgente viene cambiato + + + Aggiungi ai preferiti + Rimuovi dai preferiti + Nascondi + Mostra in libreria + Modifica... + Imposta la categoria... + Rimuovi + Gioca + Installa + Disinstalla + Apri percorso gioco + Crea collegamento nel desktop + Altro + + Non è stata trovata alcuna informazione riguardo il gioco '{0}' nella pagina specificata. + Consiglio: puoi utilizzare un processo di scaricamento del metadata più avanzato durante la modifica del gioco attraverso l'opzione del menu "Modifica". + Non disponibile mentre alcune azioni sono in corso. + La descrizione del testo è sensibile alla sintassi HTML + Il tempo di gioco è tracciato e conservato in secondi. + I valori da 0 a 100 o nulli per nessun punteggio. + Lo sviluppo di Playnite è sostenuto da questi patrons: + Codice, traduzione e altri contributori in nessun ordine particolare: + + Annullare il monitoraggio del gioco? + Il monitoraggio dell'installazione è tutt'ora in corso, desidera cancellare il processo e di far tornare il gioco a uno stadio precedente? + Il monitoraggio del gioco in esecuzione è tutt'ora in corso, desidera cancellare il processo e di far tornare il gioco a uno stadio precedente? + + Tempo di gioco + Giocato l'ultima volta + {0}h {1}m + {0} minuti + {0} secondi + Non giocato + + Apertura della Modalità Desktop... + Apertura della Modalità schermo intero... + Installazione del file script fallita. + Script installato con successo. + Installazione script + diff --git "a/source/PlayniteUI/Localization/\344\270\255\346\226\207.xaml" "b/source/PlayniteUI/Localization/\344\270\255\346\226\207.xaml" index aed03095d..a49a30240 100644 --- "a/source/PlayniteUI/Localization/\344\270\255\346\226\207.xaml" +++ "b/source/PlayniteUI/Localization/\344\270\255\346\226\207.xaml" @@ -131,8 +131,9 @@ 星期六 星期日 导入已成功完成。 - - 特殊变量可以用来添加动态字符串: + 所有游戏 + + 特殊变量可以用来添加动态字符串: {InstallDir} - 游戏安装目录 {InstallDirName} - 安装文件夹的名称 {ImagePath} - 游戏ISO / ROM路径(如果设置) @@ -485,9 +486,10 @@ IGDB > 官方商店: 无法检查新版本。 找不到新版本,您已是最新版本。 无法下载并安装更新。 - 后台任务目前正在进行中,您是否要取消它并安装更新? - - 重新加载主题列表 + 后台任务当前正在运行,是否要取消它并使用更新安装进行处理? + 后台任务当前正在运行,您要取消它并关闭Playnite吗? + + 重新加载主题列表 应用选定的主题 观看文件更改 源文件更改时自动应用主题 @@ -506,7 +508,7 @@ IGDB > 官方商店: 打开安装位置 >创建桌面快捷方式 更多 - + 在指定页面上没有找到关于游戏“{0}”的任何相关信息。 提示:您可以通过“编辑”菜单选项编辑单个游戏时使用更高级的数据下载过程 某些操作正在进行时不可用 diff --git a/source/PlayniteUI/PlayniteUI.csproj b/source/PlayniteUI/PlayniteUI.csproj index cbe4f4340..81e29dcc6 100644 --- a/source/PlayniteUI/PlayniteUI.csproj +++ b/source/PlayniteUI/PlayniteUI.csproj @@ -102,7 +102,8 @@ ..\..\references\HtmlRenderer.WPF.dll - ..\packages\LiteDB.3.1.4\lib\net462\LiteDB.dll + False + ..\..\references\LiteDB.dll ..\packages\CommonServiceLocator.1.3\lib\portable-net4+sl5+netcore45+wpa81+wp8\Microsoft.Practices.ServiceLocation.dll @@ -212,6 +213,7 @@ + @@ -481,6 +483,11 @@ Designer Always + + MSBuild:Compile + Designer + Always + MSBuild:Compile Designer diff --git a/source/PlayniteUI/Properties/AssemblyInfo.cs b/source/PlayniteUI/Properties/AssemblyInfo.cs index bdb75fdb6..9dd286cd0 100644 --- a/source/PlayniteUI/Properties/AssemblyInfo.cs +++ b/source/PlayniteUI/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -51,4 +51,4 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.31.0.*")] +[assembly: AssemblyVersion("4.40.0.*")] diff --git a/source/PlayniteUI/SkinsFullscreen/Playnite/Playnite.PlayStation.xaml b/source/PlayniteUI/SkinsFullscreen/Playnite/Playnite.PlayStation.xaml index 02b51e014..1759306dd 100644 --- a/source/PlayniteUI/SkinsFullscreen/Playnite/Playnite.PlayStation.xaml +++ b/source/PlayniteUI/SkinsFullscreen/Playnite/Playnite.PlayStation.xaml @@ -28,6 +28,8 @@ RenderOptions.BitmapScalingMode="Fant" /> + diff --git a/source/PlayniteUI/SkinsFullscreen/Playnite/Playnite.xaml b/source/PlayniteUI/SkinsFullscreen/Playnite/Playnite.xaml index 150cc0842..d1f0c6585 100644 --- a/source/PlayniteUI/SkinsFullscreen/Playnite/Playnite.xaml +++ b/source/PlayniteUI/SkinsFullscreen/Playnite/Playnite.xaml @@ -59,6 +59,8 @@ RenderOptions.BitmapScalingMode="Fant" /> + @@ -707,8 +709,8 @@ - + + + + + diff --git a/source/PlayniteUI/ViewModels/EmulatorImportViewModel.cs b/source/PlayniteUI/ViewModels/EmulatorImportViewModel.cs index 32fd20b20..b9888c97a 100644 --- a/source/PlayniteUI/ViewModels/EmulatorImportViewModel.cs +++ b/source/PlayniteUI/ViewModels/EmulatorImportViewModel.cs @@ -67,7 +67,7 @@ public bool Import } = true; public ImportableEmulator(ScannedEmulator emulator) : base(emulator.Name, emulator.Profiles) - { + { } } @@ -182,6 +182,11 @@ public RangeObservableCollection GamesList } } + public List ImportedGames + { + get; private set; + } + public List AvailableEmulators { get @@ -278,18 +283,6 @@ public RelayCommand ScanGamesCommand }); } - public RelayCommand SelectGameFilesCommand - { - get => new RelayCommand((a) => - { - var files = dialogs.SelectFiles("All files|*.*"); - if (files != null) - { - ImportGamesFiles(files); - } - }); - } - public RelayCommand ScanGamesOpeningCommand { get => new RelayCommand((args) => @@ -377,6 +370,8 @@ public void CloseView(bool? result) public async void SearchEmulators(string path) { + logger.Info($"Scanning {path} for emulators."); + try { IsLoading = true; @@ -400,6 +395,8 @@ public async void SearchEmulators(string path) public async void SearchGames(string path, EmulatorProfile profile) { + logger.Info($"Scanning {path} for emulated games using {profile} profile."); + try { IsLoading = true; @@ -433,18 +430,14 @@ public async void SearchGames(string path, EmulatorProfile profile) } } - public void ImportGamesFiles(List files) - { - - } - - public void AddSelectedGamesToDB() + private void AddSelectedGamesToDB() { if (GamesList == null || GamesList.Count == 0) { return; } + logger.Info($"Adding {GamesList.Count} new emulated games to DB."); foreach (var game in GamesList) { if (!game.Import) @@ -462,16 +455,18 @@ public void AddSelectedGamesToDB() game.Game.State = new GameState() { Installed = true }; } - database.AddGames(GamesList.Where(a => a.Import)?.Select(a => a.Game).ToList()); + ImportedGames = GamesList.Where(a => a.Import)?.Select(a => a.Game).ToList(); + database.AddGames(ImportedGames); } - public void AddSelectedEmulatorsToDB() + private void AddSelectedEmulatorsToDB() { if (EmulatorList == null || EmulatorList.Count == 0) { return; } + logger.Info($"Adding {EmulatorList.Count} new emulators to DB."); foreach (var emulator in EmulatorList) { if (emulator.Import) diff --git a/source/PlayniteUI/ViewModels/FirstTimeStartupViewModel.cs b/source/PlayniteUI/ViewModels/FirstTimeStartupViewModel.cs index b90d38bae..32a7fff2d 100644 --- a/source/PlayniteUI/ViewModels/FirstTimeStartupViewModel.cs +++ b/source/PlayniteUI/ViewModels/FirstTimeStartupViewModel.cs @@ -478,6 +478,7 @@ public void ImportGames(InstalledGamesViewModel model) { if (model.OpenView() == true) { + logger.Debug($"Selected {model.Games} custom games from first time wizard."); ImportedGames = model.Games; } } diff --git a/source/PlayniteUI/ViewModels/FullscreenViewModel.cs b/source/PlayniteUI/ViewModels/FullscreenViewModel.cs index 67b6c0389..2f445c94f 100644 --- a/source/PlayniteUI/ViewModels/FullscreenViewModel.cs +++ b/source/PlayniteUI/ViewModels/FullscreenViewModel.cs @@ -184,6 +184,22 @@ public RelayCommand ToggleSortingOrderCommand }); } + public RelayCommand ToggleInstallFilterCommand + { + get => new RelayCommand((a) => + { + ToggleInstallFilter(); + }); + } + + public RelayCommand ClearSearchCommand + { + get => new RelayCommand((a) => + { + ClearSearch(); + }); + } + public FullscreenViewModel( GameDatabase database, IWindowFactory window, @@ -282,6 +298,33 @@ public void ToggleSortingOrder() } } + public void ToggleInstallFilter() + { + if (!AppSettings.FullScreenFilterSettings.IsInstalled && !AppSettings.FullScreenFilterSettings.IsUnInstalled) + { + AppSettings.FullScreenFilterSettings.IsInstalled = true; + AppSettings.FullScreenFilterSettings.IsUnInstalled = false; + } + else if (AppSettings.FullScreenFilterSettings.IsInstalled) + { + AppSettings.FullScreenFilterSettings.IsInstalled = false; + AppSettings.FullScreenFilterSettings.IsUnInstalled = true; + } + else if (AppSettings.FullScreenFilterSettings.IsUnInstalled) + { + AppSettings.FullScreenFilterSettings.IsInstalled = false; + AppSettings.FullScreenFilterSettings.IsUnInstalled = false; + } + + // TODO: Handle this properly inside of Settings class. + AppSettings.OnPropertyChanged("FullScreenFilterSettings"); + } + + public void ClearSearch() + { + AppSettings.FullScreenFilterSettings.Name = null; + } + protected override void OnClosing(CancelEventArgs args) { if (ignoreCloseActions) @@ -316,7 +359,7 @@ public void SwitchToDesktopMode(bool closeView) Dispose(); } - App.CurrentApp.OpenNormalView(0, false); + App.CurrentApp.OpenNormalView(0, false, true); } public void OpenSearch() diff --git a/source/PlayniteUI/ViewModels/GameEditViewModel.cs b/source/PlayniteUI/ViewModels/GameEditViewModel.cs index 2a764bb15..4dcd6a13c 100644 --- a/source/PlayniteUI/ViewModels/GameEditViewModel.cs +++ b/source/PlayniteUI/ViewModels/GameEditViewModel.cs @@ -19,6 +19,7 @@ using Playnite.SDK; using Playnite.SDK.Models; using Playnite.Web; +using Playnite.Metadata; namespace PlayniteUI.ViewModels { @@ -2149,7 +2150,7 @@ public void UseExeIcon() } var path = EditingGame.ResolveVariables(EditingGame.PlayTask.Path); - if (!File.Exists(path)) + if (!File.Exists(path) && !string.IsNullOrEmpty(EditingGame.PlayTask.WorkingDir)) { path = Path.Combine( EditingGame.ResolveVariables(EditingGame.PlayTask.WorkingDir), @@ -2365,17 +2366,17 @@ await Task.Run(() => if (metadata.Image != null) { - var path = Path.Combine(Paths.TempPath, metadata.Image.Name); + var path = Path.Combine(Paths.TempPath, metadata.Image.FileName); FileSystem.PrepareSaveFile(path); - File.WriteAllBytes(path, metadata.Image.Data); + File.WriteAllBytes(path, metadata.Image.Content); tempGame.Image = path; } if (metadata.Icon != null) { - var path = Path.Combine(Paths.TempPath, metadata.Icon.Name); + var path = Path.Combine(Paths.TempPath, metadata.Icon.FileName); FileSystem.PrepareSaveFile(path); - File.WriteAllBytes(path, metadata.Icon.Data); + File.WriteAllBytes(path, metadata.Icon.Content); tempGame.Icon = path; } diff --git a/source/PlayniteUI/ViewModels/InstalledGamesViewModel.cs b/source/PlayniteUI/ViewModels/InstalledGamesViewModel.cs index d9c340e64..615bca5c0 100644 --- a/source/PlayniteUI/ViewModels/InstalledGamesViewModel.cs +++ b/source/PlayniteUI/ViewModels/InstalledGamesViewModel.cs @@ -186,7 +186,6 @@ public bool IsLoading } private static Logger logger = LogManager.GetCurrentClassLogger(); - private GameDatabase database; private IWindowFactory window; private IDialogsFactory dialogs; private CancellationTokenSource cancelToken; @@ -239,13 +238,6 @@ public RelayCommand CancelProgressCommand }); } - public InstalledGamesViewModel(GameDatabase database, IWindowFactory window, IDialogsFactory dialogs) - { - this.database = database; - this.window = window; - this.dialogs = dialogs; - } - public InstalledGamesViewModel(IWindowFactory window, IDialogsFactory dialogs) { this.window = window; @@ -259,9 +251,13 @@ public InstalledGamesViewModel(IWindowFactory window, IDialogsFactory dialogs) public bool? OpenView(string directory) { + if (!string.IsNullOrEmpty(directory)) + { #pragma warning disable CS4014 - ScanFolder(directory); + ScanFolder(directory); #pragma warning restore CS4014 + } + return window.CreateAndOpenDialog(this); } @@ -327,22 +323,6 @@ public void ConfirmDialog() Games.Add(data); } - if (database != null) - { - foreach (var game in Games) - { - if (game.Icon != null) - { - var iconId = "images/custom/" + game.Icon.Name; - game.Game.Icon = database.AddFileNoDuplicate(iconId, game.Icon.Name, game.Icon.Data); - } - } - - var games = Games.Select(a => a.Game).ToList(); - database.AddGames(games); - database.AssignPcPlatform(games); - } - CloseView(true); } @@ -437,5 +417,22 @@ public void CancelProgress() { cancelToken?.Cancel(); } + + public static List AddImportableGamesToDb(List games, GameDatabase database) + { + foreach (var game in games) + { + if (game.Icon != null) + { + var iconId = "images/custom/" + game.Icon.Name; + game.Game.Icon = database.AddFileNoDuplicate(iconId, game.Icon.Name, game.Icon.Data); + } + } + + var insertGames = games.Select(a => a.Game).ToList(); + database.AddGames(insertGames); + database.AssignPcPlatform(insertGames); + return insertGames; + } } } diff --git a/source/PlayniteUI/ViewModels/MainViewModel.cs b/source/PlayniteUI/ViewModels/MainViewModel.cs index 6bab626a5..61cc07fdc 100644 --- a/source/PlayniteUI/ViewModels/MainViewModel.cs +++ b/source/PlayniteUI/ViewModels/MainViewModel.cs @@ -2,7 +2,7 @@ using Playnite; using Playnite.App; using Playnite.Database; -using Playnite.MetaProviders; +using Playnite.Metadata; using Playnite.Providers.Steam; using Playnite.Scripting; using Playnite.SDK; @@ -439,6 +439,17 @@ private void InitializeCommands() ShutdownCommand = new RelayCommand((a) => { MainMenuOpened = false; + if (GlobalTaskHandler.IsActive) + { + if (Dialogs.ShowMessage( + Resources.FindString("LOCBackgroundProgressCancelAskExit"), + Resources.FindString("LOCCrashClosePlaynite"), + MessageBoxButton.YesNo) != MessageBoxResult.Yes) + { + return; + } + } + ignoreCloseActions = true; ShutdownApp(); }, new KeyGesture(Key.Q, ModifierKeys.Alt)); @@ -487,9 +498,8 @@ private void InitializeCommands() MainMenuOpened = false; ImportInstalledGames( new InstalledGamesViewModel( - Database, InstalledGamesWindowFactory.Instance, - Dialogs)); + Dialogs), null); }, (a) => Database.IsOpen); AddEmulatedGamesCommand = new RelayCommand((a) => @@ -1090,7 +1100,7 @@ public async Task UpdateDatabase(bool updateLibrary, ulong steamImportCatId, boo metaSettings.CoverImage.Source = MetadataSource.IGDBOverStore; metaSettings.Name = new MetadataFieldSettings(true, MetadataSource.Store); var downloader = new MetadataDownloader(AppSettings); - downloader.DownloadMetadataThreaded( + downloader.DownloadMetadataGroupedAsync( addedGames, Database, metaSettings, @@ -1108,29 +1118,8 @@ public async Task UpdateDatabase(bool updateLibrary, ulong steamImportCatId, boo } } - public async Task DownloadMetadata(MetadataDownloaderSettings settings) + public async Task DownloadMetadata(MetadataDownloaderSettings settings, List games) { - List games = null; - if (settings.GamesSource == MetadataGamesSource.Selected) - { - if (SelectedGames != null && SelectedGames.Count() > 0) - { - games = SelectedGames.Select(a => a.Game).Distinct().ToList(); - } - else - { - return; - } - } - else if (settings.GamesSource == MetadataGamesSource.AllFromDB) - { - games = Database.GamesCollection.FindAll().ToList(); - } - else if (settings.GamesSource == MetadataGamesSource.Filtered) - { - games = GamesView.CollectionView.Cast().Select(a => a.Game).Distinct().ToList(); - } - GameAdditionAllowed = false; try @@ -1148,7 +1137,7 @@ public async Task DownloadMetadata(MetadataDownloaderSettings settings) ProgressStatus = Resources.FindString("LOCProgressMetadata"); var downloader = new MetadataDownloader(AppSettings); GlobalTaskHandler.ProgressTask = - downloader.DownloadMetadataThreaded(games, Database, settings, (g, i, t) => ProgressValue = i + 1, GlobalTaskHandler.CancelToken); + downloader.DownloadMetadataGroupedAsync(games, Database, settings, (g, i, t) => ProgressValue = i + 1, GlobalTaskHandler.CancelToken); await GlobalTaskHandler.ProgressTask; } finally @@ -1158,6 +1147,32 @@ public async Task DownloadMetadata(MetadataDownloaderSettings settings) } } + public async Task DownloadMetadata(MetadataDownloaderSettings settings) + { + List games = null; + if (settings.GamesSource == MetadataGamesSource.Selected) + { + if (SelectedGames != null && SelectedGames.Count() > 0) + { + games = SelectedGames.Select(a => a.Game).Distinct().ToList(); + } + else + { + return; + } + } + else if (settings.GamesSource == MetadataGamesSource.AllFromDB) + { + games = Database.GamesCollection.FindAll().ToList(); + } + else if (settings.GamesSource == MetadataGamesSource.Filtered) + { + games = GamesView.CollectionView.Cast().Select(a => a.Game).Distinct().ToList(); + } + + await DownloadMetadata(settings, games); + } + public async void DownloadMetadata(MetadataDownloadViewModel model) { @@ -1194,14 +1209,39 @@ public void AddCustomGame(IWindowFactory window) } } - public void ImportInstalledGames(InstalledGamesViewModel model) + public async void ImportInstalledGames(InstalledGamesViewModel model, string path) { - model.OpenView(); + if (model.OpenView(path) == true && model.Games?.Any() == true) + { + var addedGames = InstalledGamesViewModel.AddImportableGamesToDb(model.Games, Database); + if (!GlobalTaskHandler.IsActive) + { + var settings = new MetadataDownloaderSettings(); + settings.ConfigureFields(MetadataSource.IGDB, true); + await DownloadMetadata(settings, addedGames); + } + else + { + Logger.Warn("Skipping metadata download for manually added games, some global task is already in progress."); + } + } } - public void ImportEmulatedGames(EmulatorImportViewModel model) + public async void ImportEmulatedGames(EmulatorImportViewModel model) { - model.OpenView(); + if (model.OpenView() == true && model.ImportedGames?.Any() == true) + { + if (!GlobalTaskHandler.IsActive) + { + var settings = new MetadataDownloaderSettings(); + settings.ConfigureFields(MetadataSource.IGDB, true); + await DownloadMetadata(settings, model.ImportedGames); + } + else + { + Logger.Warn("Skipping metadata download for manually added emulated games, some global task is already in progress."); + } + } } public void OpenAboutWindow(AboutViewModel model) @@ -1255,6 +1295,18 @@ protected virtual void OnClosing(CancelEventArgs args) } else { + if (GlobalTaskHandler.IsActive) + { + if (Dialogs.ShowMessage( + Resources.FindString("LOCBackgroundProgressCancelAskExit"), + Resources.FindString("LOCCrashClosePlaynite"), + MessageBoxButton.YesNo) != MessageBoxResult.Yes) + { + args.Cancel = true; + return; + } + } + ShutdownApp(); } } @@ -1285,12 +1337,10 @@ private void OnFileDropped(DragEventArgs args) } else if (Directory.Exists(path)) { - var instMode = new InstalledGamesViewModel( - Database, + var instModel = new InstalledGamesViewModel( InstalledGamesWindowFactory.Instance, Dialogs); - - instMode.OpenView(path); + ImportInstalledGames(instModel, path); } } } diff --git a/source/PlayniteUI/ViewModels/MetadataDownloadViewModel.cs b/source/PlayniteUI/ViewModels/MetadataDownloadViewModel.cs index a17ed77ae..d5df4d121 100644 --- a/source/PlayniteUI/ViewModels/MetadataDownloadViewModel.cs +++ b/source/PlayniteUI/ViewModels/MetadataDownloadViewModel.cs @@ -1,6 +1,6 @@ using NLog; using Playnite.SDK; -using Playnite.MetaProviders; +using Playnite.Metadata; using PlayniteUI.Commands; using System; using System.Collections.Generic; diff --git a/source/PlayniteUI/ViewModels/MetadataLookupViewModel.cs b/source/PlayniteUI/ViewModels/MetadataLookupViewModel.cs index 1c6944feb..658c5bf15 100644 --- a/source/PlayniteUI/ViewModels/MetadataLookupViewModel.cs +++ b/source/PlayniteUI/ViewModels/MetadataLookupViewModel.cs @@ -1,6 +1,6 @@ using NLog; using Playnite; -using Playnite.MetaProviders; +using Playnite.Metadata; using Playnite.SDK.Models; using Playnite.SDK; using PlayniteUI.Commands; @@ -10,6 +10,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Playnite.Metadata.Providers; namespace PlayniteUI.ViewModels { @@ -128,16 +129,6 @@ public RelayCommand SearchCommand private IDialogsFactory dialogs; private MetadataProvider provider; private IResourceProvider resources; - private string igdbApiKey; - - public MetadataLookupViewModel(MetadataProvider provider, IWindowFactory window, IDialogsFactory dialogs, IResourceProvider resources, string igdbApiKey) - { - this.provider = provider; - this.window = window; - this.dialogs = dialogs; - this.resources = resources; - this.igdbApiKey = igdbApiKey; - } public MetadataLookupViewModel(MetadataProvider provider, IWindowFactory window, IDialogsFactory dialogs, IResourceProvider resources) { @@ -171,13 +162,13 @@ await Task.Run(() => switch (provider) { case MetadataProvider.Wiki: - var wiki = new Wikipedia(); + var wiki = new WikipediaMetadataProvider(); var page = wiki.GetPage(id); MetadataData = wiki.ParseGamePage(page, searchTerm); break; case MetadataProvider.IGDB: - var igdb = new IGDBMetadataProvider(igdbApiKey); - MetadataData = igdb.GetParsedGame(UInt64.Parse(id)); + var igdb = new IGDBMetadataProvider(); + MetadataData = igdb.GetMetadata(id).GameData; break; } @@ -230,7 +221,7 @@ private ObservableCollection SearchForResults(string keyword, Meta switch (provider) { case MetadataProvider.Wiki: - var wiki = new Wikipedia(); + var wiki = new WikipediaMetadataProvider(); foreach (var page in wiki.Search(keyword)) { searchList.Add(new SearchResult(page.title, page.title, page.snippet)); @@ -239,12 +230,12 @@ private ObservableCollection SearchForResults(string keyword, Meta break; case MetadataProvider.IGDB: - var igdb = new IGDBMetadataProvider(igdbApiKey); - foreach (var page in igdb.Search(keyword)) + var igdb = new IGDBMetadataProvider(); + foreach (var page in igdb.SearchMetadata(new Game(keyword))) { searchList.Add(new SearchResult( - page.id.ToString(), - page.name + (page.first_release_date == 0 ? "" : $" ({DateTimeOffset.FromUnixTimeMilliseconds(page.first_release_date).DateTime.Year.ToString()})"), + page.Id, + page.Name + (page.ReleaseDate == null ? "" : $" ({page.ReleaseDate.Value.Year})"), string.Empty)); } diff --git a/source/PlayniteUI/Windows/AboutWindow.xaml b/source/PlayniteUI/Windows/AboutWindow.xaml index 62d529809..359949b45 100644 --- a/source/PlayniteUI/Windows/AboutWindow.xaml +++ b/source/PlayniteUI/Windows/AboutWindow.xaml @@ -87,7 +87,8 @@ Wargzaxk Ratox00 Vuelos spektor56 -ClearStrelok "/> +ClearStrelok +StarFang208 "/> diff --git a/source/PlayniteUI/Windows/FullscreenWindow.xaml b/source/PlayniteUI/Windows/FullscreenWindow.xaml index c16de3d38..728e962a6 100644 --- a/source/PlayniteUI/Windows/FullscreenWindow.xaml +++ b/source/PlayniteUI/Windows/FullscreenWindow.xaml @@ -41,15 +41,17 @@ - + - - + + + + diff --git a/source/PlayniteUI/Windows/MetadataDownloadWindow.xaml b/source/PlayniteUI/Windows/MetadataDownloadWindow.xaml index 1825d7a84..60c061e0c 100644 --- a/source/PlayniteUI/Windows/MetadataDownloadWindow.xaml +++ b/source/PlayniteUI/Windows/MetadataDownloadWindow.xaml @@ -4,13 +4,10 @@ xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" - xmlns:prism="clr-namespace:Prism.Interactivity;assembly=Prism.Wpf" - xmlns:local="clr-namespace:PlayniteUI.Windows" xmlns:pui="clr-namespace:PlayniteUI" xmlns:controls="clr-namespace:PlayniteUI.Controls" xmlns:pvm="clr-namespace:PlayniteUI.ViewModels" - xmlns:pmp="clr-namespace:Playnite.MetaProviders;assembly=Playnite" + xmlns:pmp="clr-namespace:Playnite.Metadata;assembly=Playnite" mc:Ignorable="d" d:DesignStyle="{StaticResource WindowDesignStyle}" Style="{DynamicResource StandardWindowStyle}" diff --git a/source/PlayniteUI/packages.config b/source/PlayniteUI/packages.config index 2f8427c17..85a6d70bc 100644 --- a/source/PlayniteUI/packages.config +++ b/source/PlayniteUI/packages.config @@ -8,7 +8,6 @@ - diff --git a/source/PlayniteUITests/PlayniteUITests.cs b/source/PlayniteUITests/PlayniteUITests.cs index a06aa4591..f1f82c356 100644 --- a/source/PlayniteUITests/PlayniteUITests.cs +++ b/source/PlayniteUITests/PlayniteUITests.cs @@ -1,4 +1,5 @@ using Playnite.Database; +using Playnite.Metadata; using System; using System.Collections.Generic; using System.IO; @@ -34,14 +35,14 @@ public static string TempPath private static Random random = new Random(); - public static FileDefinition CreateFakeFile() + public static MetadataFile CreateFakeFile() { var file = new byte[20]; random.NextBytes(file); var fileName = Guid.NewGuid().ToString() + ".file"; var filePath = Path.Combine(TempPath, fileName); File.WriteAllBytes(filePath, file); - return new FileDefinition(filePath, fileName, file); + return new MetadataFile(filePath, fileName, file); } } } diff --git a/source/PlayniteUITests/PlayniteUITests.csproj b/source/PlayniteUITests/PlayniteUITests.csproj index e9bee3253..9fdb2c2f2 100644 --- a/source/PlayniteUITests/PlayniteUITests.csproj +++ b/source/PlayniteUITests/PlayniteUITests.csproj @@ -56,7 +56,8 @@ - ..\packages\LiteDB.3.1.4\lib\net462\LiteDB.dll + False + ..\..\references\LiteDB.dll ..\packages\NUnit.3.6.1\lib\net45\nunit.framework.dll diff --git a/source/PlayniteUITests/ViewModels/GameEditViewModelTests.cs b/source/PlayniteUITests/ViewModels/GameEditViewModelTests.cs index c0582afc4..4afcf8b8a 100644 --- a/source/PlayniteUITests/ViewModels/GameEditViewModelTests.cs +++ b/source/PlayniteUITests/ViewModels/GameEditViewModelTests.cs @@ -28,17 +28,17 @@ public void ImageReplaceTest() var origImage = PlayniteUITests.CreateFakeFile(); var origBackground = PlayniteUITests.CreateFakeFile(); - db.AddFile(origIcon.Name, origIcon.Name, origIcon.Data); - db.AddFile(origImage.Name, origImage.Name, origImage.Data); - db.AddFile(origBackground.Name, origBackground.Name, origBackground.Data); + db.AddFile(origIcon.FileName, origIcon.FileName, origIcon.Content); + db.AddFile(origImage.FileName, origImage.FileName, origImage.Content); + db.AddFile(origBackground.FileName, origBackground.FileName, origBackground.Content); var game = new Game() { ProviderId = "testid", Name = "Test Game", - Image = origImage.Name, - Icon = origIcon.Name, - BackgroundImage = origBackground.Name + Image = origImage.FileName, + Icon = origIcon.FileName, + BackgroundImage = origBackground.FileName }; db.AddGame(game); @@ -49,34 +49,34 @@ public void ImageReplaceTest() // Images are replaced var model = new GameEditViewModel(game, db, new MockWindowFactory(), new MockDialogsFactory(), new MockResourceProvider()); - model.EditingGame.Icon = newIcon.Path; - model.EditingGame.Image = newImage.Path; - model.EditingGame.BackgroundImage = newBackground.Path; + model.EditingGame.Icon = newIcon.FileId; + model.EditingGame.Image = newImage.FileId; + model.EditingGame.BackgroundImage = newBackground.FileId; model.ConfirmDialog(); - Assert.AreNotEqual(game.Icon, origIcon.Name); - Assert.AreNotEqual(game.Image, origImage.Name); - Assert.AreNotEqual(game.BackgroundImage, origBackground.Name); - Assert.AreNotEqual(game.Icon, newIcon.Path); - Assert.AreNotEqual(game.Image, newImage.Path); - Assert.AreNotEqual(game.BackgroundImage, newBackground.Path); + Assert.AreNotEqual(game.Icon, origIcon.FileName); + Assert.AreNotEqual(game.Image, origImage.FileName); + Assert.AreNotEqual(game.BackgroundImage, origBackground.FileName); + Assert.AreNotEqual(game.Icon, newIcon.FileId); + Assert.AreNotEqual(game.Image, newImage.FileId); + Assert.AreNotEqual(game.BackgroundImage, newBackground.FileId); var dbFiles = db.Database.FileStorage.FindAll().ToList(); Assert.AreEqual(3, dbFiles.Count()); using (var str = db.GetFileStream(game.Icon)) { - CollectionAssert.AreEqual(newIcon.Data, str.ToArray()); + CollectionAssert.AreEqual(newIcon.Content, str.ToArray()); } using (var str = db.GetFileStream(game.Image)) { - CollectionAssert.AreEqual(newImage.Data, str.ToArray()); + CollectionAssert.AreEqual(newImage.Content, str.ToArray()); } using (var str = db.GetFileStream(game.BackgroundImage)) { - CollectionAssert.AreEqual(newBackground.Data, str.ToArray()); + CollectionAssert.AreEqual(newBackground.Content, str.ToArray()); } // Duplicates are kept and not replaced @@ -85,9 +85,9 @@ public void ImageReplaceTest() var currentBack = game.BackgroundImage; model = new GameEditViewModel(game, db, new MockWindowFactory(), new MockDialogsFactory(), new MockResourceProvider()); - model.EditingGame.Icon = newIcon.Path; - model.EditingGame.Image = newImage.Path; - model.EditingGame.BackgroundImage = newBackground.Path; + model.EditingGame.Icon = newIcon.FileId; + model.EditingGame.Image = newImage.FileId; + model.EditingGame.BackgroundImage = newBackground.FileId; model.ConfirmDialog(); dbFiles = db.Database.FileStorage.FindAll().ToList(); @@ -110,31 +110,31 @@ public void ImageReplaceMultiTest() var origIcon = PlayniteUITests.CreateFakeFile(); var origImage = PlayniteUITests.CreateFakeFile(); var origBackground = PlayniteUITests.CreateFakeFile(); - db.AddFile(origIcon.Name, origIcon.Name, origIcon.Data); - db.AddFile(origImage.Name, origImage.Name, origImage.Data); - db.AddFile(origBackground.Name, origBackground.Name, origBackground.Data); + db.AddFile(origIcon.FileName, origIcon.FileName, origIcon.Content); + db.AddFile(origImage.FileName, origImage.FileName, origImage.Content); + db.AddFile(origBackground.FileName, origBackground.FileName, origBackground.Content); db.AddGame(new Game() { ProviderId = "testid", Name = "Test Game", - Image = origImage.Name, - Icon = origIcon.Name, - BackgroundImage = origBackground.Name + Image = origImage.FileName, + Icon = origIcon.FileName, + BackgroundImage = origBackground.FileName }); origIcon = PlayniteUITests.CreateFakeFile(); origImage = PlayniteUITests.CreateFakeFile(); origBackground = PlayniteUITests.CreateFakeFile(); - db.AddFile(origIcon.Name, origIcon.Name, origIcon.Data); - db.AddFile(origImage.Name, origImage.Name, origImage.Data); - db.AddFile(origBackground.Name, origBackground.Name, origBackground.Data); + db.AddFile(origIcon.FileName, origIcon.FileName, origIcon.Content); + db.AddFile(origImage.FileName, origImage.FileName, origImage.Content); + db.AddFile(origBackground.FileName, origBackground.FileName, origBackground.Content); db.AddGame(new Game() { ProviderId = "testid2", Name = "Test Game 2", - Image = origImage.Name, - Icon = origIcon.Name, - BackgroundImage = origBackground.Name + Image = origImage.FileName, + Icon = origIcon.FileName, + BackgroundImage = origBackground.FileName }); Assert.AreEqual(6, db.Database.FileStorage.FindAll().ToList().Count()); @@ -146,9 +146,9 @@ public void ImageReplaceMultiTest() // Replaces all images for all games var games = db.GamesCollection.FindAll().ToList(); var model = new GameEditViewModel(games, db, new MockWindowFactory(), new MockDialogsFactory(), new MockResourceProvider()); - model.EditingGame.Icon = newIcon.Path; - model.EditingGame.Image = newImage.Path; - model.EditingGame.BackgroundImage = newBackground.Path; + model.EditingGame.Icon = newIcon.FileId; + model.EditingGame.Image = newImage.FileId; + model.EditingGame.BackgroundImage = newBackground.FileId; model.ConfirmDialog(); var dbFiles = db.Database.FileStorage.FindAll().ToList(); @@ -170,21 +170,21 @@ public void ImageReplaceMultiTest() newIcon = PlayniteUITests.CreateFakeFile(); newImage = PlayniteUITests.CreateFakeFile(); newBackground = PlayniteUITests.CreateFakeFile(); - db.AddFile(newIcon.Name, newIcon.Name, newIcon.Data); - db.AddFile(newImage.Name, newImage.Name, newImage.Data); - db.AddFile(newBackground.Name, newBackground.Name, newBackground.Data); - games[0].Icon = newIcon.Name; - games[0].Image = newImage.Name; - games[0].BackgroundImage = newBackground.Name; + db.AddFile(newIcon.FileName, newIcon.FileName, newIcon.Content); + db.AddFile(newImage.FileName, newImage.FileName, newImage.Content); + db.AddFile(newBackground.FileName, newBackground.FileName, newBackground.Content); + games[0].Icon = newIcon.FileName; + games[0].Image = newImage.FileName; + games[0].BackgroundImage = newBackground.FileName; db.UpdateGameInDatabase(games[0]); Assert.AreEqual(6, db.Database.FileStorage.FindAll().ToList().Count()); games = db.GamesCollection.FindAll().ToList(); model = new GameEditViewModel(games, db, new MockWindowFactory(), new MockDialogsFactory(), new MockResourceProvider()); - model.EditingGame.Icon = newIcon.Path; - model.EditingGame.Image = newImage.Path; - model.EditingGame.BackgroundImage = newBackground.Path; + model.EditingGame.Icon = newIcon.FileId; + model.EditingGame.Image = newImage.FileId; + model.EditingGame.BackgroundImage = newBackground.FileId; model.ConfirmDialog(); Assert.AreEqual(3, db.Database.FileStorage.FindAll().ToList().Count()); @@ -192,9 +192,9 @@ public void ImageReplaceMultiTest() games = db.GamesCollection.FindAll().ToList(); foreach (var game in games) { - Assert.AreEqual(newIcon.Name, game.Icon); - Assert.AreEqual(newImage.Name, game.Image); - Assert.AreEqual(newBackground.Name, game.BackgroundImage); + Assert.AreEqual(newIcon.FileName, game.Icon); + Assert.AreEqual(newImage.FileName, game.Image); + Assert.AreEqual(newBackground.FileName, game.BackgroundImage); } } } diff --git a/source/PlayniteUITests/packages.config b/source/PlayniteUITests/packages.config index 4488a5e15..5be68e3d1 100644 --- a/source/PlayniteUITests/packages.config +++ b/source/PlayniteUITests/packages.config @@ -1,5 +1,4 @@  - \ No newline at end of file diff --git a/source/Tools/DbTools/App.config b/source/Tools/DbTools/App.config new file mode 100644 index 000000000..4bba09a55 --- /dev/null +++ b/source/Tools/DbTools/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/source/Tools/DbTools/DbTools.csproj b/source/Tools/DbTools/DbTools.csproj new file mode 100644 index 000000000..59a637cec --- /dev/null +++ b/source/Tools/DbTools/DbTools.csproj @@ -0,0 +1,98 @@ + + + + + Debug + AnyCPU + {D5DFD7C9-E747-45F8-BE7D-E9BE3D463248} + Exe + DbTools + DbTools + v4.7.1 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + true + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + + + + + + + + + + + + + + + + + + + {ad271e73-8a13-4c4e-bfdc-3076646b59e3} + Playnite + + + + \ No newline at end of file diff --git a/source/Tools/DbTools/Program.cs b/source/Tools/DbTools/Program.cs new file mode 100644 index 000000000..e0cab3235 --- /dev/null +++ b/source/Tools/DbTools/Program.cs @@ -0,0 +1,73 @@ +using Playnite; +using Playnite.Database; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DbTools +{ + class Program + { + static void Main(string[] args) + { + if (args.Count() == 2) + { + var sourceDb = args[0]; + var targetDb = args[1]; + + if (!File.Exists(sourceDb)) + { + Console.WriteLine("Source database file not found!"); + return; + } + + try + { + GameDatabase.CloneLibrary(sourceDb, targetDb); + } + catch (Exception e) when (!Debugger.IsAttached) + { + Console.WriteLine($"Failed to clone database: {e.Message}"); + } + } + else + { + try + { + var settings = Settings.LoadSettings(); + var dbPath = settings.DatabasePath; + var dbTempPath = dbPath + "temp"; + + if (File.Exists(dbTempPath)) + { + File.Delete(dbTempPath); + } + + if (!File.Exists(dbPath)) + { + Console.WriteLine($"Couldn't find {dbPath} database file."); + return; + } + + File.Move(dbPath, dbTempPath); + + Console.WriteLine($"Fixing {dbPath} file..."); + GameDatabase.CloneLibrary(dbTempPath, dbPath); + File.Delete(dbTempPath); + Console.WriteLine("Finished fixing database"); + } + catch (Exception e) when (!Debugger.IsAttached) + { + Console.WriteLine($"Failed to clone database: {e.Message}"); + } + } + + Console.WriteLine("Press ENTER to exit..."); + Console.ReadLine(); + } + } +} diff --git a/source/Tools/DbTools/Properties/AssemblyInfo.cs b/source/Tools/DbTools/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..e5572b2a3 --- /dev/null +++ b/source/Tools/DbTools/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DbTools")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DbTools")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d5dfd7c9-e747-45f8-be7d-e9be3d463248")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/source/Tools/PlayniteInstaller/App.config b/source/Tools/PlayniteInstaller/App.config new file mode 100644 index 000000000..4bba09a55 --- /dev/null +++ b/source/Tools/PlayniteInstaller/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/source/Tools/PlayniteInstaller/App.xaml b/source/Tools/PlayniteInstaller/App.xaml new file mode 100644 index 000000000..4020d5355 --- /dev/null +++ b/source/Tools/PlayniteInstaller/App.xaml @@ -0,0 +1,8 @@ + + + + + diff --git a/source/Tools/PlayniteInstaller/App.xaml.cs b/source/Tools/PlayniteInstaller/App.xaml.cs new file mode 100644 index 000000000..326ffdde6 --- /dev/null +++ b/source/Tools/PlayniteInstaller/App.xaml.cs @@ -0,0 +1,26 @@ +using Playnite.Installer; +using Playnite.Installer.ViewModels; +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace PlayniteInstaller +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + private void Application_Startup(object sender, StartupEventArgs e) + { + var window = new MainWindow(); + window.DataContext = new MainViewModel(window); + window.ShowDialog(); + Shutdown(); + } + } +} diff --git a/source/Tools/PlayniteInstaller/MainWindow.xaml b/source/Tools/PlayniteInstaller/MainWindow.xaml new file mode 100644 index 000000000..adc02c7a3 --- /dev/null +++ b/source/Tools/PlayniteInstaller/MainWindow.xaml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/Tools/PlayniteInstaller/ViewModels/MainViewModel.cs b/source/Tools/PlayniteInstaller/ViewModels/MainViewModel.cs new file mode 100644 index 000000000..34344a2f4 --- /dev/null +++ b/source/Tools/PlayniteInstaller/ViewModels/MainViewModel.cs @@ -0,0 +1,97 @@ +using PlayniteUI.Commands; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Forms; + +namespace Playnite.Installer.ViewModels +{ + public class MainViewModel : ObservableObject + { + private Window windowHost; + + private string destinationFolder; + public string DestionationFolder + { + get => destinationFolder; + set + { + destinationFolder = value; + OnPropertyChanged("DestionationFolder"); + } + } + + private bool portable; + public bool Portable + { + get => portable; + set + { + portable = value; + OnPropertyChanged("Portable"); + } + } + + public RelayCommand BrowseCommand + { + get => new RelayCommand((a) => + { + Browse(); + }); + } + + public RelayCommand InstallCommand + { + get => new RelayCommand((a) => + { + Install(); + }); + } + + public RelayCommand CancelCommand + { + get => new RelayCommand((a) => + { + Cancel(); + }); + } + + public MainViewModel(Window window) + { + DestionationFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Playnite"); + windowHost = window; + } + + public void Browse() + { + var dialog = new FolderBrowserDialog() + { + Description = "Select Destination Folder...", + ShowNewFolderButton = true + }; + + if (dialog.ShowDialog() == DialogResult.OK) + { + DestionationFolder = dialog.SelectedPath; + } + } + + public void Install() + { + + + windowHost.Close(); + } + + public void Cancel() + { + windowHost.Close(); + } + } +} diff --git a/web/update/4.40.html b/web/update/4.40.html new file mode 100644 index 000000000..a3a95d881 --- /dev/null +++ b/web/update/4.40.html @@ -0,0 +1,36 @@ + + + + + +
    +
  • New: Major improvements to metadata download from IGDB
  • +
  • New: Automatically download metadata when importing custom and emulated games
  • +
  • New: Option to filter installed games in Fullscreen view
  • +
  • New: Show ask dialog when closing Playnite and background task is in progress
  • +
  • Fix: Wrong name matching when importing emulated games
  • +
  • Fix: Wikipedia metadata scrapper not downloading release dates properly
  • +
  • Fix: Updated RetroArch profiles
  • +
  • Fix: Unable to start Battle.net game if PTR version is installed
  • +
  • Fix: Handling apostrophes in game names when downloading metadata
  • +
  • Fix: First time wizard triggers download of metadata for existing database
  • +
  • Fix: Crash when closing Playnite
  • +
  • Fix: Crash when assigning icon to a game
  • + +
+ +