diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h
index 61bac9eba..c54ce7654 100644
--- a/src/common/fs/fs_paths.h
+++ b/src/common/fs/fs_paths.h
@@ -22,6 +22,7 @@
 #define SDMC_DIR "sdmc"
 #define SHADER_DIR "shader"
 #define TAS_DIR "tas"
+#define ICONS_DIR "icons"
 
 // yuzu-specific files
 
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp
index dce219fcf..461c170f7 100644
--- a/src/common/fs/path_util.cpp
+++ b/src/common/fs/path_util.cpp
@@ -128,6 +128,7 @@ public:
         GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
         GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
         GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR);
+        GenerateYuzuPath(YuzuPath::IconsDir, yuzu_path / ICONS_DIR);
     }
 
 private:
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h
index ba28964d0..61593bdf7 100644
--- a/src/common/fs/path_util.h
+++ b/src/common/fs/path_util.h
@@ -24,6 +24,7 @@ enum class YuzuPath {
     SDMCDir,        // Where the emulated SDMC is stored.
     ShaderDir,      // Where shaders are stored.
     TASDir,         // Where TAS scripts are stored.
+    IconsDir,       // Where Icons for Windows shortcuts are stored.
 };
 
 /**
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index f254c1e1c..a9f19e316 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -560,9 +560,9 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
     QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity"));
     QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
     QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
-#ifndef WIN32
     QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut"));
     QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop"));
+#ifndef WIN32
     QAction* create_applications_menu_shortcut =
         shortcut_menu->addAction(tr("Add to Applications Menu"));
 #endif
@@ -638,10 +638,10 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
     connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
         emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
     });
-#ifndef WIN32
     connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() {
         emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop);
     });
+#ifndef WIN32
     connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() {
         emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications);
     });
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 16fa92e2c..7a93921d7 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -98,6 +98,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
 #include "common/scm_rev.h"
 #include "common/scope_exit.h"
 #ifdef _WIN32
+#include <shlobj.h>
 #include "common/windows/timer_resolution.h"
 #endif
 #ifdef ARCHITECTURE_x86_64
@@ -2825,7 +2826,6 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
     const QStringList args = QApplication::arguments();
     std::filesystem::path yuzu_command = args[0].toStdString();
 
-#if defined(__linux__) || defined(__FreeBSD__)
     // If relative path, make it an absolute path
     if (yuzu_command.c_str()[0] == '.') {
         yuzu_command = Common::FS::GetCurrentDir() / yuzu_command;
@@ -2848,12 +2848,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
         UISettings::values.shortcut_already_warned = true;
     }
 #endif // __linux__
-#endif // __linux__ || __FreeBSD__
 
     std::filesystem::path target_directory{};
     // Determine target directory for shortcut
-#if defined(__linux__) || defined(__FreeBSD__)
+#if defined(WIN32)
+    const char* home = std::getenv("USERPROFILE");
+#else
     const char* home = std::getenv("HOME");
+#endif
     const std::filesystem::path home_path = (home == nullptr ? "~" : home);
     const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
 
@@ -2863,7 +2865,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
             QMessageBox::critical(
                 this, tr("Create Shortcut"),
                 tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.")
-                    .arg(QString::fromStdString(target_directory)),
+                    .arg(QString::fromStdString(target_directory.generic_string())),
                 QMessageBox::StandardButton::Ok);
             return;
         }
@@ -2871,15 +2873,15 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
         target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) /
                            "applications";
         if (!Common::FS::CreateDirs(target_directory)) {
-            QMessageBox::critical(this, tr("Create Shortcut"),
-                                  tr("Cannot create shortcut in applications menu. Path \"%1\" "
-                                     "does not exist and cannot be created.")
-                                      .arg(QString::fromStdString(target_directory)),
-                                  QMessageBox::StandardButton::Ok);
+            QMessageBox::critical(
+                this, tr("Create Shortcut"),
+                tr("Cannot create shortcut in applications menu. Path \"%1\" "
+                   "does not exist and cannot be created.")
+                    .arg(QString::fromStdString(target_directory.generic_string())),
+                QMessageBox::StandardButton::Ok);
             return;
         }
     }
-#endif
 
     const std::string game_file_name = std::filesystem::path(game_path).filename().string();
     // Determine full paths for icon and shortcut
@@ -2901,9 +2903,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
     const std::filesystem::path shortcut_path =
         target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name)
                                             : fmt::format("yuzu-{:016X}.desktop", program_id));
+#elif defined(WIN32)
+    std::filesystem::path icons_path =
+        Common::FS::GetYuzuPathString(Common::FS::YuzuPath::IconsDir);
+    std::filesystem::path icon_path =
+        icons_path / ((program_id == 0 ? fmt::format("yuzu-{}.ico", game_file_name)
+                                       : fmt::format("yuzu-{:016X}.ico", program_id)));
 #else
-    const std::filesystem::path icon_path{};
-    const std::filesystem::path shortcut_path{};
+    std::string icon_extension;
 #endif
 
     // Get title from game file
@@ -2928,29 +2935,37 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
         LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
     }
 
-    QImage icon_jpeg =
+    QImage icon_data =
         QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
 #if defined(__linux__) || defined(__FreeBSD__)
     // Convert and write the icon as a PNG
-    if (!icon_jpeg.save(QString::fromStdString(icon_path.string()))) {
+    if (!icon_data.save(QString::fromStdString(icon_path.string()))) {
         LOG_ERROR(Frontend, "Could not write icon as PNG to file");
     } else {
         LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
     }
+#elif defined(WIN32)
+    if (!SaveIconToFile(icon_path.string(), icon_data)) {
+        LOG_ERROR(Frontend, "Could not write icon to file");
+        return;
+    }
 #endif // __linux__
 
-#if defined(__linux__) || defined(__FreeBSD__)
+#ifdef _WIN32
+    // Replace characters that are illegal in Windows filenames by a dash
+    const std::string illegal_chars = "<>:\"/\\|?*";
+    for (char c : illegal_chars) {
+        std::replace(title.begin(), title.end(), c, '_');
+    }
+    const std::filesystem::path shortcut_path = target_directory / (title + ".lnk").c_str();
+#endif
+
     const std::string comment =
         tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString();
     const std::string arguments = fmt::format("-g \"{:s}\"", game_path);
     const std::string categories = "Game;Emulator;Qt;";
     const std::string keywords = "Switch;Nintendo;";
-#else
-    const std::string comment{};
-    const std::string arguments{};
-    const std::string categories{};
-    const std::string keywords{};
-#endif
+
     if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(),
                         yuzu_command.string(), arguments, categories, keywords)) {
         QMessageBox::critical(this, tr("Create Shortcut"),
@@ -3964,6 +3979,34 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st
     shortcut_stream << shortcut_contents;
     shortcut_stream.close();
 
+    return true;
+#elif defined(WIN32)
+    IShellLinkW* shell_link;
+    auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW,
+                                 (void**)&shell_link);
+    if (FAILED(hres)) {
+        return false;
+    }
+    shell_link->SetPath(
+        Common::UTF8ToUTF16W(command).data()); // Path to the object we are referring to
+    shell_link->SetArguments(Common::UTF8ToUTF16W(arguments).data());
+    shell_link->SetDescription(Common::UTF8ToUTF16W(comment).data());
+    shell_link->SetIconLocation(Common::UTF8ToUTF16W(icon_path).data(), 0);
+
+    IPersistFile* persist_file;
+    hres = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file);
+    if (FAILED(hres)) {
+        return false;
+    }
+
+    hres = persist_file->Save(Common::UTF8ToUTF16W(shortcut_path).data(), TRUE);
+    if (FAILED(hres)) {
+        return false;
+    }
+
+    persist_file->Release();
+    shell_link->Release();
+
     return true;
 #endif
     return false;
diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp
index 5c3e4589e..61cf00176 100644
--- a/src/yuzu/util/util.cpp
+++ b/src/yuzu/util/util.cpp
@@ -5,6 +5,10 @@
 #include <cmath>
 #include <QPainter>
 #include "yuzu/util/util.h"
+#ifdef _WIN32
+#include <windows.h>
+#include "common/fs/file.h"
+#endif
 
 QFont GetMonospaceFont() {
     QFont font(QStringLiteral("monospace"));
@@ -37,3 +41,76 @@ QPixmap CreateCirclePixmapFromColor(const QColor& color) {
     painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0);
     return circle_pixmap;
 }
+
+bool SaveIconToFile(const std::string_view path, const QImage& image) {
+#if defined(WIN32)
+#pragma pack(push, 2)
+    struct IconDir {
+        WORD id_reserved;
+        WORD id_type;
+        WORD id_count;
+    };
+
+    struct IconDirEntry {
+        BYTE width;
+        BYTE height;
+        BYTE color_count;
+        BYTE reserved;
+        WORD planes;
+        WORD bit_count;
+        DWORD bytes_in_res;
+        DWORD image_offset;
+    };
+#pragma pack(pop)
+
+    QImage source_image = image.convertToFormat(QImage::Format_RGB32);
+    constexpr int bytes_per_pixel = 4;
+    const int image_size = source_image.width() * source_image.height() * bytes_per_pixel;
+
+    BITMAPINFOHEADER info_header{};
+    info_header.biSize = sizeof(BITMAPINFOHEADER), info_header.biWidth = source_image.width(),
+    info_header.biHeight = source_image.height() * 2, info_header.biPlanes = 1,
+    info_header.biBitCount = bytes_per_pixel * 8, info_header.biCompression = BI_RGB;
+
+    const IconDir icon_dir{.id_reserved = 0, .id_type = 1, .id_count = 1};
+    const IconDirEntry icon_entry{.width = static_cast<BYTE>(source_image.width()),
+                                  .height = static_cast<BYTE>(source_image.height() * 2),
+                                  .color_count = 0,
+                                  .reserved = 0,
+                                  .planes = 1,
+                                  .bit_count = bytes_per_pixel * 8,
+                                  .bytes_in_res =
+                                      static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
+                                  .image_offset = sizeof(IconDir) + sizeof(IconDirEntry)};
+
+    Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write,
+                                 Common::FS::FileType::BinaryFile);
+    if (!icon_file.IsOpen()) {
+        return false;
+    }
+
+    if (!icon_file.Write(icon_dir)) {
+        return false;
+    }
+    if (!icon_file.Write(icon_entry)) {
+        return false;
+    }
+    if (!icon_file.Write(info_header)) {
+        return false;
+    }
+
+    for (int y = 0; y < image.height(); y++) {
+        const auto* line = source_image.scanLine(source_image.height() - 1 - y);
+        std::vector<u8> line_data(source_image.width() * bytes_per_pixel);
+        std::memcpy(line_data.data(), line, line_data.size());
+        if (!icon_file.Write(line_data)) {
+            return false;
+        }
+    }
+    icon_file.Close();
+
+    return true;
+#else
+    return false;
+#endif
+}
diff --git a/src/yuzu/util/util.h b/src/yuzu/util/util.h
index 39dd2d895..09c14ce3f 100644
--- a/src/yuzu/util/util.h
+++ b/src/yuzu/util/util.h
@@ -7,14 +7,22 @@
 #include <QString>
 
 /// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc.
-QFont GetMonospaceFont();
+[[nodiscard]] QFont GetMonospaceFont();
 
 /// Convert a size in bytes into a readable format (KiB, MiB, etc.)
-QString ReadableByteSize(qulonglong size);
+[[nodiscard]] QString ReadableByteSize(qulonglong size);
 
 /**
  * Creates a circle pixmap from a specified color
  * @param color The color the pixmap shall have
  * @return QPixmap circle pixmap
  */
-QPixmap CreateCirclePixmapFromColor(const QColor& color);
+[[nodiscard]] QPixmap CreateCirclePixmapFromColor(const QColor& color);
+
+/**
+ * Saves a windows icon to a file
+ * @param path The icons path
+ * @param image The image to save
+ * @return bool If the operation succeeded
+ */
+[[nodiscard]] bool SaveIconToFile(const std::string_view path, const QImage& image);