From 76ec38ee473ac565d6327da494eadb921b502104 Mon Sep 17 00:00:00 2001
From: Connor McLaughlin <stenzek@gmail.com>
Date: Sun, 24 Jan 2021 14:06:52 +1000
Subject: [PATCH] GameList: Support scanning PSF/MiniPSF files

---
 src/frontend-common/game_list.cpp | 47 +++++++++++++++++++++++++++++--
 src/frontend-common/game_list.h   | 14 ++++++++-
 2 files changed, 58 insertions(+), 3 deletions(-)

diff --git a/src/frontend-common/game_list.cpp b/src/frontend-common/game_list.cpp
index fc1f2328e..4a203439b 100644
--- a/src/frontend-common/game_list.cpp
+++ b/src/frontend-common/game_list.cpp
@@ -9,6 +9,7 @@
 #include "common/string_util.h"
 #include "core/bios.h"
 #include "core/host_interface.h"
+#include "core/psf_loader.h"
 #include "core/settings.h"
 #include "core/system.h"
 #include <algorithm>
@@ -25,7 +26,8 @@ GameList::~GameList() = default;
 
 const char* GameList::EntryTypeToString(GameListEntryType type)
 {
-  static std::array<const char*, 3> names = {{"Disc", "PSExe", "Playlist"}};
+  static std::array<const char*, static_cast<int>(GameListEntryType::Count)> names = {
+    {"Disc", "PSExe", "Playlist", "PSF"}};
   return names[static_cast<int>(type)];
 }
 
@@ -107,6 +109,45 @@ bool GameList::GetExeListEntry(const char* path, GameListEntry* entry)
   return true;
 }
 
+bool GameList::GetPsfListEntry(const char* path, GameListEntry* entry)
+{
+  FILESYSTEM_STAT_DATA ffd;
+  if (!FileSystem::StatFile(path, &ffd))
+    return false;
+
+  PSFLoader::File file;
+  if (!file.Load(path))
+    return false;
+
+  entry->code.clear();
+  entry->path = path;
+  entry->region = file.GetRegion();
+  entry->total_size = ffd.Size;
+  entry->last_modified_time = ffd.ModificationTime.AsUnixTimestamp();
+  entry->type = GameListEntryType::PSF;
+  entry->compatibility_rating = GameListCompatibilityRating::Unknown;
+
+  // Game - Title
+  std::optional<std::string> game(file.GetTagString("game"));
+  if (game.has_value())
+  {
+    entry->title = std::move(game.value());
+    entry->title += " - ";
+  }
+  else
+  {
+    entry->title.clear();
+  }
+
+  std::optional<std::string> title(file.GetTagString("title"));
+  if (title.has_value())
+    entry->title += title.value();
+  else
+    entry->title += GetFileNameFromPath(path);
+
+  return true;
+}
+
 bool GameList::GetM3UListEntry(const char* path, GameListEntry* entry)
 {
   FILESYSTEM_STAT_DATA ffd;
@@ -158,6 +199,8 @@ bool GameList::GetGameListEntry(const std::string& path, GameListEntry* entry)
 {
   if (System::IsExeFileName(path.c_str()))
     return GetExeListEntry(path.c_str(), entry);
+  if (System::IsPsfFileName(path.c_str()))
+    return GetPsfListEntry(path.c_str(), entry);
   if (System::IsM3UFileName(path.c_str()))
     return GetM3UListEntry(path.c_str(), entry);
 
@@ -325,7 +368,7 @@ bool GameList::LoadEntriesFromCache(ByteStream* stream)
     if (!ReadString(stream, &path) || !ReadString(stream, &code) || !ReadString(stream, &title) ||
         !ReadU64(stream, &total_size) || !ReadU64(stream, &last_modified_time) || !ReadU8(stream, &region) ||
         region >= static_cast<u8>(DiscRegion::Count) || !ReadU8(stream, &type) ||
-        type > static_cast<u8>(GameListEntryType::Playlist) || !ReadU8(stream, &compatibility_rating) ||
+        type >= static_cast<u8>(GameListEntryType::Count) || !ReadU8(stream, &compatibility_rating) ||
         compatibility_rating >= static_cast<u8>(GameListCompatibilityRating::Count))
     {
       Log_WarningPrintf("Game list cache entry is corrupted");
diff --git a/src/frontend-common/game_list.h b/src/frontend-common/game_list.h
index 9bc7aa929..d7138287a 100644
--- a/src/frontend-common/game_list.h
+++ b/src/frontend-common/game_list.h
@@ -18,7 +18,9 @@ enum class GameListEntryType
 {
   Disc,
   PSExe,
-  Playlist
+  Playlist,
+  PSF,
+  Count
 };
 
 enum class GameListCompatibilityRating
@@ -71,6 +73,15 @@ public:
   GameList();
   ~GameList();
 
+  /// Returns true if the filename is a PlayStation executable we can inject.
+  static bool IsExeFileName(const char* path);
+
+  /// Returns true if the filename is a Portable Sound Format file we can uncompress/load.
+  static bool IsPsfFileName(const char* path);
+
+  /// Returns true if the filename is a M3U Playlist we can handle.
+  static bool IsM3UFileName(const char* path);
+
   static const char* EntryTypeToString(GameListEntryType type);
   static const char* EntryCompatibilityRatingToString(GameListCompatibilityRating rating);
 
@@ -127,6 +138,7 @@ private:
   GameListEntry* GetMutableEntryForPath(const char* path);
 
   static bool GetExeListEntry(const char* path, GameListEntry* entry);
+  static bool GetPsfListEntry(const char* path, GameListEntry* entry);
   bool GetM3UListEntry(const char* path, GameListEntry* entry);
 
   bool GetGameListEntry(const std::string& path, GameListEntry* entry);