diff --git a/src/common-tests/file_system_tests.cpp b/src/common-tests/file_system_tests.cpp
index d7fc87ba1..be65b2466 100644
--- a/src/common-tests/file_system_tests.cpp
+++ b/src/common-tests/file_system_tests.cpp
@@ -11,10 +11,18 @@ TEST(FileSystem, GetWin32Path)
   ASSERT_EQ(FileSystem::GetWin32Path("test.txt"), L"test.txt");
   ASSERT_EQ(FileSystem::GetWin32Path("D:\\test.txt"), L"\\\\?\\D:\\test.txt");
   ASSERT_EQ(FileSystem::GetWin32Path("C:\\foo"), L"\\\\?\\C:\\foo");
+  ASSERT_EQ(FileSystem::GetWin32Path("C:\\foo\\bar\\..\\baz"), L"\\\\?\\C:\\foo\\baz");
   ASSERT_EQ(FileSystem::GetWin32Path("\\\\foo\\bar\\baz"), L"\\\\?\\UNC\\foo\\bar\\baz");
+  ASSERT_EQ(FileSystem::GetWin32Path("\\\\foo\\bar\\baz\\sub\\.."), L"\\\\?\\UNC\\foo\\bar\\baz");
   ASSERT_EQ(FileSystem::GetWin32Path("ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱"), L"ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱");
-  ASSERT_EQ(FileSystem::GetWin32Path("C:\\ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱"), L"\\\\?\\C:\\ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱");
-  ASSERT_EQ(FileSystem::GetWin32Path("\\\\foo\\bar\\ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱"), L"\\\\?\\UNC\\foo\\bar\\ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱");
+  ASSERT_EQ(FileSystem::GetWin32Path("C:\\ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱"),
+            L"\\\\?\\C:\\ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱");
+  ASSERT_EQ(FileSystem::GetWin32Path("\\\\foo\\bar\\ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱"),
+            L"\\\\?\\UNC\\foo\\bar\\ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱");
+  ASSERT_EQ(FileSystem::GetWin32Path("C:\\ŻąłóРстуぬね\\のはen🍪\\⟑η∏☉ⴤ\\..\\ℹ︎∩₲ ₱⟑♰⫳🐱"),
+            L"\\\\?\\C:\\ŻąłóРстуぬね\\のはen🍪\\ℹ︎∩₲ ₱⟑♰⫳🐱");
+  ASSERT_EQ(FileSystem::GetWin32Path("\\\\foo\\bar\\ŻąłóРстуぬねのはen🍪\\⟑η∏☉ⴤ\\..\\ℹ︎∩₲ ₱⟑♰⫳🐱"),
+            L"\\\\?\\UNC\\foo\\bar\\ŻąłóРстуぬねのはen🍪\\ℹ︎∩₲ ₱⟑♰⫳🐱");
 }
 
 #endif
diff --git a/src/common/file_system.cpp b/src/common/file_system.cpp
index 9a65cbdd8..ef218c2e0 100644
--- a/src/common/file_system.cpp
+++ b/src/common/file_system.cpp
@@ -26,6 +26,8 @@
 
 #if defined(_WIN32)
 #include "windows_headers.h"
+#include <malloc.h>
+#include <pathcch.h>
 #include <share.h>
 #include <shlobj.h>
 #include <winioctl.h>
@@ -225,32 +227,50 @@ void Path::RemoveLengthLimits(std::string* path)
 
 bool FileSystem::GetWin32Path(std::wstring* dest, std::string_view str)
 {
-  const bool absolute = Path::IsAbsolute(str);
-  const bool unc = IsUNCPath(str);
-  const size_t skip = unc ? 2 : 0;
+  // Just convert to wide if it's a relative path, MAX_PATH still applies.
+  if (!Path::IsAbsolute(str))
+    return StringUtil::UTF8StringToWideString(*dest, str);
 
-  dest->clear();
-  if (str.empty())
-    return true;
-
-  int wlen = MultiByteToWideChar(CP_UTF8, 0, str.data() + skip, static_cast<int>(str.length() - skip), nullptr, 0);
-  if (wlen <= 0)
+  // PathCchCanonicalizeEx() thankfully takes care of everything.
+  // But need to widen the string first, avoid the stack allocation.
+  int wlen = MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast<int>(str.length()), nullptr, 0);
+  if (wlen <= 0) [[unlikely]]
     return false;
 
-  // Can't fix up non-absolute paths. Hopefully they don't go past MAX_PATH.
-  if (absolute)
-    dest->append(unc ? L"\\\\?\\UNC\\" : L"\\\\?\\");
-
-  const size_t start = dest->size();
-  dest->resize(start + static_cast<u32>(wlen));
-
-  wlen = MultiByteToWideChar(CP_UTF8, 0, str.data() + skip, static_cast<int>(str.length() - skip), dest->data() + start,
-                             wlen);
-  if (wlen <= 0)
+  // So copy it to a temp wide buffer first.
+  wchar_t* wstr_buf = static_cast<wchar_t*>(_malloca(sizeof(wchar_t) * (static_cast<size_t>(wlen) + 1)));
+  wlen = MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast<int>(str.length()), wstr_buf, wlen);
+  if (wlen <= 0) [[unlikely]]
+  {
+    _freea(wstr_buf);
     return false;
+  }
 
-  dest->resize(start + static_cast<u32>(wlen));
-  return true;
+  // And use PathCchCanonicalizeEx() to fix up any non-direct elements.
+  wstr_buf[wlen] = '\0';
+  dest->resize(std::max<size_t>(static_cast<size_t>(wlen) + (IsUNCPath(str) ? 9 : 5), 16));
+  for (;;)
+  {
+    const HRESULT hr =
+      PathCchCanonicalizeEx(dest->data(), dest->size(), wstr_buf, PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH);
+    if (SUCCEEDED(hr))
+    {
+      dest->resize(std::wcslen(dest->data()));
+      _freea(wstr_buf);
+      return true;
+    }
+    else if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER))
+    {
+      dest->resize(dest->size() * 2);
+      continue;
+    }
+    else [[unlikely]]
+    {
+      Log_ErrorFmt("PathCchCanonicalizeEx() returned {:08X}", static_cast<unsigned>(hr));
+      _freea(wstr_buf);
+      return false;
+    }
+  }
 }
 
 std::wstring FileSystem::GetWin32Path(std::string_view str)