diff --git a/src/duckstation-qt/gamelistmodel.cpp b/src/duckstation-qt/gamelistmodel.cpp
index d77ef974d..3a369775d 100644
--- a/src/duckstation-qt/gamelistmodel.cpp
+++ b/src/duckstation-qt/gamelistmodel.cpp
@@ -1,13 +1,16 @@
-// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
+// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
 
 #include "gamelistmodel.h"
+#include "qthost.h"
+#include "qtutils.h"
+
+#include "core/system.h"
+
 #include "common/file_system.h"
 #include "common/path.h"
 #include "common/string_util.h"
-#include "core/system.h"
-#include "qthost.h"
-#include "qtutils.h"
+
 #include <QtConcurrent/QtConcurrent>
 #include <QtCore/QDate>
 #include <QtCore/QDateTime>
@@ -26,6 +29,11 @@ static constexpr int COVER_ART_HEIGHT = 512;
 static constexpr int COVER_ART_SPACING = 32;
 static constexpr int MIN_COVER_CACHE_SIZE = 256;
 
+static std::string getMemoryCardIconCachePath()
+{
+  return Path::Combine(EmuFolders::Cache, "memcard_icons.cache");
+}
+
 static int DPRScale(int size, float dpr)
 {
   return static_cast<int>(static_cast<float>(size) * dpr);
@@ -114,15 +122,32 @@ const char* GameListModel::getColumnName(Column col)
   return s_column_names[static_cast<int>(col)];
 }
 
-GameListModel::GameListModel(float cover_scale, bool show_cover_titles, QObject* parent /* = nullptr */)
-  : QAbstractTableModel(parent), m_show_titles_for_covers(show_cover_titles)
+GameListModel::GameListModel(float cover_scale, bool show_cover_titles, bool show_game_icons,
+                             QObject* parent /* = nullptr */)
+  : QAbstractTableModel(parent), m_show_titles_for_covers(show_cover_titles), m_show_game_icons(show_game_icons),
+    m_memcard_icon_cache(getMemoryCardIconCachePath()), m_memcard_pixmap_cache(128)
 {
   loadCommonImages();
   setCoverScale(cover_scale);
   setColumnDisplayNames();
+
+  if (m_show_game_icons)
+    m_memcard_icon_cache.Reload();
 }
+
 GameListModel::~GameListModel() = default;
 
+void GameListModel::setShowGameIcons(bool enabled)
+{
+  m_show_game_icons = enabled;
+
+  beginResetModel();
+  m_memcard_pixmap_cache.Clear();
+  if (enabled)
+    m_memcard_icon_cache.Reload();
+  endResetModel();
+}
+
 void GameListModel::setCoverScale(float scale)
 {
   if (m_cover_scale == scale)
@@ -224,6 +249,31 @@ QString GameListModel::formatTimespan(time_t timespan)
     return qApp->translate("GameList", "%n minutes", "", minutes);
 }
 
+const QPixmap& GameListModel::getIconForEntry(const GameList::Entry* ge) const
+{
+  // We only do this for discs/disc sets for now.
+  if (m_show_game_icons && (!ge->serial.empty() && (ge->IsDisc() || ge->IsDiscSet())))
+  {
+    QPixmap* item = m_memcard_pixmap_cache.Lookup(ge->serial);
+    if (item)
+      return *item;
+
+    const MemoryCardImage::IconFrame* icon = m_memcard_icon_cache.Lookup(ge->serial, ge->path);
+    if (icon)
+    {
+      const QImage image(reinterpret_cast<const uchar*>(icon->pixels), MemoryCardImage::ICON_WIDTH,
+                         MemoryCardImage::ICON_HEIGHT, QImage::Format_RGBA8888);
+      return *m_memcard_pixmap_cache.Insert(ge->serial, QPixmap::fromImage(image));
+    }
+    else
+    {
+      return *m_memcard_pixmap_cache.Insert(ge->serial, m_type_pixmaps[static_cast<u32>(ge->type)]);
+    }
+  }
+
+  return m_type_pixmaps[static_cast<u32>(ge->type)];
+}
+
 int GameListModel::getCoverArtWidth() const
 {
   return std::max(static_cast<int>(static_cast<float>(COVER_ART_WIDTH) * m_cover_scale), 1);
@@ -407,8 +457,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
       {
         case Column_Type:
         {
-          // TODO: Test for settings
-          return m_type_pixmaps[static_cast<u32>(ge->type)];
+          return getIconForEntry(ge);
         }
 
         case Column_Region:
@@ -455,6 +504,10 @@ QVariant GameListModel::headerData(int section, Qt::Orientation orientation, int
 void GameListModel::refresh()
 {
   beginResetModel();
+
+  // Invalidate memcard LRU cache, forcing a re-query of the memcard timestamps.
+  m_memcard_pixmap_cache.Clear();
+
   endResetModel();
 }
 
diff --git a/src/duckstation-qt/gamelistmodel.h b/src/duckstation-qt/gamelistmodel.h
index 7a1e5faf9..60e9b6b9a 100644
--- a/src/duckstation-qt/gamelistmodel.h
+++ b/src/duckstation-qt/gamelistmodel.h
@@ -1,10 +1,11 @@
-// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
+// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
 
 #pragma once
 
 #include "core/game_database.h"
 #include "core/game_list.h"
+#include "core/memory_card_icon_cache.h"
 #include "core/types.h"
 
 #include "common/heterogeneous_containers.h"
@@ -46,7 +47,7 @@ public:
   static std::optional<Column> getColumnIdForName(std::string_view name);
   static const char* getColumnName(Column col);
 
-  GameListModel(float cover_scale, bool show_cover_titles, QObject* parent = nullptr);
+  GameListModel(float cover_scale, bool show_cover_titles, bool show_game_icons, QObject* parent = nullptr);
   ~GameListModel();
 
   int rowCount(const QModelIndex& parent = QModelIndex()) const override;
@@ -66,6 +67,9 @@ public:
   bool getShowCoverTitles() const { return m_show_titles_for_covers; }
   void setShowCoverTitles(bool enabled) { m_show_titles_for_covers = enabled; }
 
+  bool getShowGameIcons() const { return m_show_game_icons; }
+  void setShowGameIcons(bool enabled);
+
   float getCoverScale() const { return m_cover_scale; }
   void setCoverScale(float scale);
   int getCoverArtWidth() const;
@@ -84,10 +88,13 @@ private:
   void loadOrGenerateCover(const GameList::Entry* ge);
   void invalidateCoverForPath(const std::string& path);
 
+  const QPixmap& getIconForEntry(const GameList::Entry* ge) const;
+
   static QString formatTimespan(time_t timespan);
 
   float m_cover_scale = 0.0f;
   bool m_show_titles_for_covers = false;
+  bool m_show_game_icons = false;
 
   std::array<QString, Column_Count> m_column_display_names;
   std::array<QPixmap, static_cast<int>(GameList::EntryType::Count)> m_type_pixmaps;
@@ -98,4 +105,7 @@ private:
   QPixmap m_loading_pixmap;
 
   mutable LRUCache<std::string, QPixmap> m_cover_pixmap_cache;
-};
\ No newline at end of file
+
+  mutable MemoryCardIconCache m_memcard_icon_cache;
+  mutable LRUCache<std::string, QPixmap> m_memcard_pixmap_cache;
+};
diff --git a/src/duckstation-qt/gamelistwidget.cpp b/src/duckstation-qt/gamelistwidget.cpp
index 40034f488..35e987dc8 100644
--- a/src/duckstation-qt/gamelistwidget.cpp
+++ b/src/duckstation-qt/gamelistwidget.cpp
@@ -114,7 +114,8 @@ void GameListWidget::initialize()
   const float cover_scale = Host::GetBaseFloatSettingValue("UI", "GameListCoverArtScale", 0.45f);
   const bool show_cover_titles = Host::GetBaseBoolSettingValue("UI", "GameListShowCoverTitles", true);
   const bool merge_disc_sets = Host::GetBaseBoolSettingValue("UI", "GameListMergeDiscSets", true);
-  m_model = new GameListModel(cover_scale, show_cover_titles, this);
+  const bool show_game_icons = Host::GetBaseBoolSettingValue("UI", "GameListShowGameIcons", true);
+  m_model = new GameListModel(cover_scale, show_cover_titles, show_game_icons, this);
   m_model->updateCacheSize(width(), height());
 
   m_sort_model = new GameListSortModel(m_model);
@@ -242,6 +243,11 @@ bool GameListWidget::isMergingDiscSets() const
   return m_sort_model->isMergingDiscSets();
 }
 
+bool GameListWidget::isShowingGameIcons() const
+{
+  return m_model->getShowGameIcons();
+}
+
 void GameListWidget::refresh(bool invalidate_cache)
 {
   cancelRefresh();
@@ -476,6 +482,16 @@ void GameListWidget::setMergeDiscSets(bool enabled)
   emit layoutChange();
 }
 
+void GameListWidget::setShowGameIcons(bool enabled)
+{
+  if (m_model->getShowGameIcons() == enabled)
+    return;
+
+  Host::SetBaseBoolSettingValue("UI", "GameListShowGameIcons", enabled);
+  Host::CommitBaseSettingChanges();
+  m_model->setShowGameIcons(enabled);
+}
+
 void GameListWidget::updateToolbar()
 {
   const bool grid_view = isShowingGameGrid();
diff --git a/src/duckstation-qt/gamelistwidget.h b/src/duckstation-qt/gamelistwidget.h
index 7f3bb4866..a7334804a 100644
--- a/src/duckstation-qt/gamelistwidget.h
+++ b/src/duckstation-qt/gamelistwidget.h
@@ -53,6 +53,7 @@ public:
   bool isShowingGameGrid() const;
   bool isShowingGridCoverTitles() const;
   bool isMergingDiscSets() const;
+  bool isShowingGameIcons() const;
 
   const GameList::Entry* getSelectedEntry() const;
 
@@ -85,6 +86,7 @@ public Q_SLOTS:
   void showGameGrid();
   void setShowCoverTitles(bool enabled);
   void setMergeDiscSets(bool enabled);
+  void setShowGameIcons(bool enabled);
   void gridZoomIn();
   void gridZoomOut();
   void gridIntScale(int int_scale);
diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp
index e5e8925cf..f71aa9117 100644
--- a/src/duckstation-qt/mainwindow.cpp
+++ b/src/duckstation-qt/mainwindow.cpp
@@ -1637,6 +1637,7 @@ void MainWindow::setupAdditionalUi()
   m_game_list_widget->initialize();
   m_ui.actionGridViewShowTitles->setChecked(m_game_list_widget->isShowingGridCoverTitles());
   m_ui.actionMergeDiscSets->setChecked(m_game_list_widget->isMergingDiscSets());
+  m_ui.actionShowGameIcons->setChecked(m_game_list_widget->isShowingGameIcons());
   if (s_use_central_widget)
   {
     m_ui.mainContainer = nullptr; // setCentralWidget() will delete this
@@ -2096,6 +2097,7 @@ void MainWindow::connectSignals()
   SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableGDBServer, "Debug", "EnableGDBServer", false);
   connect(m_ui.actionOpenDataDirectory, &QAction::triggered, this, &MainWindow::onToolsOpenDataDirectoryTriggered);
   connect(m_ui.actionMergeDiscSets, &QAction::triggered, m_game_list_widget, &GameListWidget::setMergeDiscSets);
+  connect(m_ui.actionShowGameIcons, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowGameIcons);
   connect(m_ui.actionGridViewShowTitles, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowCoverTitles);
   connect(m_ui.actionGridViewZoomIn, &QAction::triggered, m_game_list_widget, [this]() {
     if (isShowingGameList())
diff --git a/src/duckstation-qt/mainwindow.ui b/src/duckstation-qt/mainwindow.ui
index 9c7f436ae..1be6dea29 100644
--- a/src/duckstation-qt/mainwindow.ui
+++ b/src/duckstation-qt/mainwindow.ui
@@ -17,7 +17,7 @@
    <string>DuckStation</string>
   </property>
   <property name="windowIcon">
-   <iconset resource="resources/resources.qrc">
+   <iconset resource="resources/duckstation-qt.qrc">
     <normaloff>:/icons/duck.png</normaloff>:/icons/duck.png</iconset>
   </property>
   <property name="unifiedTitleAndToolBarOnMac">
@@ -220,7 +220,8 @@
     <addaction name="actionFullscreen"/>
     <addaction name="menuWindowSize"/>
     <addaction name="separator"/>
-    <addaction name="actionMergeDiscSets" />
+    <addaction name="actionMergeDiscSets"/>
+    <addaction name="actionShowGameIcons"/>
     <addaction name="actionGridViewShowTitles"/>
     <addaction name="actionGridViewZoomIn"/>
     <addaction name="actionGridViewZoomOut"/>
@@ -444,7 +445,7 @@
   </action>
   <action name="actionGitHubRepository">
    <property name="icon">
-    <iconset resource="resources/resources.qrc">
+    <iconset resource="resources/duckstation-qt.qrc">
      <normaloff>:/icons/github.png</normaloff>:/icons/github.png</iconset>
    </property>
    <property name="text">
@@ -453,7 +454,7 @@
   </action>
   <action name="actionIssueTracker">
    <property name="icon">
-    <iconset resource="resources/resources.qrc">
+    <iconset resource="resources/duckstation-qt.qrc">
      <normaloff>:/icons/IssueTracker.png</normaloff>:/icons/IssueTracker.png</iconset>
    </property>
    <property name="text">
@@ -462,7 +463,7 @@
   </action>
   <action name="actionDiscordServer">
    <property name="icon">
-    <iconset resource="resources/resources.qrc">
+    <iconset resource="resources/duckstation-qt.qrc">
      <normaloff>:/icons/discord.png</normaloff>:/icons/discord.png</iconset>
    </property>
    <property name="text">
@@ -484,7 +485,7 @@
   </action>
   <action name="actionAboutQt">
    <property name="icon">
-    <iconset resource="resources/resources.qrc">
+    <iconset resource="resources/duckstation-qt.qrc">
      <normaloff>:/icons/QT.png</normaloff>:/icons/QT.png</iconset>
    </property>
    <property name="text">
@@ -493,7 +494,7 @@
   </action>
   <action name="actionAbout">
    <property name="icon">
-    <iconset resource="resources/resources.qrc">
+    <iconset resource="resources/duckstation-qt.qrc">
      <normaloff>:/icons/duck_64.png</normaloff>:/icons/duck_64.png</iconset>
    </property>
    <property name="text">
@@ -951,9 +952,20 @@
     <string>Memory &amp;Scanner</string>
    </property>
   </action>
+  <action name="actionShowGameIcons">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="checked">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>Show Game Icons (List View)</string>
+   </property>
+  </action>
  </widget>
  <resources>
-  <include location="resources/resources.qrc"/>
+  <include location="resources/duckstation-qt.qrc"/>
  </resources>
  <connections/>
 </ui>
diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp
index d6e4a27ca..78b5e1bb8 100644
--- a/src/duckstation-qt/qthost.cpp
+++ b/src/duckstation-qt/qthost.cpp
@@ -248,9 +248,10 @@ bool QtHost::SaveGameSettings(SettingsInterface* sif, bool delete_if_empty)
   return true;
 }
 
-QIcon QtHost::GetAppIcon()
+const QIcon& QtHost::GetAppIcon()
 {
-  return QIcon(QStringLiteral(":/icons/duck.png"));
+  static QIcon icon = QIcon(QStringLiteral(":/icons/duck.png"));
+  return icon;
 }
 
 std::optional<bool> QtHost::DownloadFile(QWidget* parent, const QString& title, std::string url, std::vector<u8>* data)
diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h
index 40977268b..22afadf49 100644
--- a/src/duckstation-qt/qthost.h
+++ b/src/duckstation-qt/qthost.h
@@ -270,7 +270,7 @@ QString GetAppNameAndVersion();
 QString GetAppConfigSuffix();
 
 /// Returns the main application icon.
-QIcon GetAppIcon();
+const QIcon& GetAppIcon();
 
 /// Returns the base path for resources. This may be : prefixed, if we're using embedded resources.
 QString GetResourcesBasePath();