Просмотр исходного кода

Merge remote-tracking branch 'origin/master' into better-account

David Marcec 8 лет назад
Родитель
Сommit
b76ddb7647
79 измененных файлов с 1570 добавлено и 635 удалено
  1. 3 0
      CMakeModules/CopyYuzuQt5Deps.cmake
  2. 4 0
      appveyor.yml
  3. 1 1
      externals/CMakeLists.txt
  4. 2 2
      src/common/alignment.h
  5. 1 1
      src/common/bit_set.h
  6. 31 19
      src/common/color.h
  7. 13 3
      src/common/file_util.cpp
  8. 11 7
      src/common/file_util.h
  9. 2 2
      src/common/hash.h
  10. 8 0
      src/common/logging/backend.cpp
  11. 8 0
      src/common/logging/log.h
  12. 182 180
      src/common/vector_math.h
  13. 1 1
      src/common/x64/xbyak_util.h
  14. 6 2
      src/core/core.cpp
  15. 12 0
      src/core/core.h
  16. 1 0
      src/core/file_sys/card_image.cpp
  17. 4 0
      src/core/file_sys/content_archive.cpp
  18. 1 0
      src/core/file_sys/content_archive.h
  19. 7 0
      src/core/file_sys/control_metadata.h
  20. 9 3
      src/core/file_sys/directory.h
  21. 1 0
      src/core/file_sys/savedata_factory.h
  22. 148 0
      src/core/file_sys/vfs.cpp
  23. 63 3
      src/core/file_sys/vfs.h
  24. 265 76
      src/core/file_sys/vfs_real.cpp
  25. 48 8
      src/core/file_sys/vfs_real.h
  26. 1 1
      src/core/hle/kernel/hle_ipc.cpp
  27. 1 1
      src/core/hle/kernel/hle_ipc.h
  28. 22 1
      src/core/hle/service/acc/acc.cpp
  29. 8 1
      src/core/hle/service/am/am.cpp
  30. 1 0
      src/core/hle/service/am/am.h
  31. 8 8
      src/core/hle/service/filesystem/filesystem.cpp
  32. 1 1
      src/core/hle/service/filesystem/filesystem.h
  33. 3 6
      src/core/hle/service/filesystem/fsp_srv.cpp
  34. 1 1
      src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
  35. 3 4
      src/core/hle/service/nvdrv/nvdrv.cpp
  36. 5 3
      src/core/hle/service/nvdrv/nvdrv.h
  37. 1 1
      src/core/hle/service/nvflinger/buffer_queue.cpp
  38. 1 1
      src/core/hle/service/nvflinger/buffer_queue.h
  39. 4 3
      src/core/hle/service/nvflinger/nvflinger.cpp
  40. 9 0
      src/core/hle/service/nvflinger/nvflinger.h
  41. 3 3
      src/core/hle/service/service.cpp
  42. 6 1
      src/core/hle/service/service.h
  43. 66 2
      src/core/loader/deconstructed_rom_directory.cpp
  44. 7 0
      src/core/loader/deconstructed_rom_directory.h
  45. 1 5
      src/core/loader/loader.cpp
  46. 1 9
      src/core/loader/loader.h
  47. 2 2
      src/core/loader/nca.cpp
  48. 1 1
      src/core/loader/nca.h
  49. 32 1
      src/core/loader/xci.cpp
  50. 5 0
      src/core/loader/xci.h
  51. 11 4
      src/video_core/engines/maxwell_3d.cpp
  52. 1 1
      src/video_core/engines/maxwell_3d.h
  53. 6 9
      src/video_core/engines/shader_bytecode.h
  54. 1 0
      src/video_core/gpu.cpp
  55. 1 0
      src/video_core/gpu.h
  56. 14 9
      src/video_core/renderer_opengl/gl_rasterizer.cpp
  57. 53 29
      src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
  58. 41 106
      src/video_core/renderer_opengl/gl_rasterizer_cache.h
  59. 16 6
      src/video_core/renderer_opengl/gl_shader_decompiler.cpp
  60. 5 1
      src/video_core/renderer_opengl/gl_state.h
  61. 7 0
      src/video_core/renderer_opengl/maxwell_to_gl.h
  62. 1 1
      src/video_core/renderer_opengl/renderer_opengl.cpp
  63. 6 80
      src/video_core/textures/decoders.cpp
  64. 2 2
      src/video_core/textures/decoders.h
  65. 3 0
      src/yuzu/CMakeLists.txt
  66. 14 0
      src/yuzu/configuration/config.cpp
  67. 11 0
      src/yuzu/configuration/configure.ui
  68. 1 0
      src/yuzu/configuration/configure_dialog.cpp
  69. 63 0
      src/yuzu/configuration/configure_gamelist.cpp
  70. 28 0
      src/yuzu/configuration/configure_gamelist.h
  71. 126 0
      src/yuzu/configuration/configure_gamelist.ui
  72. 4 2
      src/yuzu/debugger/graphics/graphics_surface.cpp
  73. 74 12
      src/yuzu/game_list.cpp
  74. 2 1
      src/yuzu/game_list.h
  75. 37 6
      src/yuzu/game_list_p.h
  76. 7 2
      src/yuzu/main.cpp
  77. 3 0
      src/yuzu/main.h
  78. 6 0
      src/yuzu/ui_settings.h
  79. 1 0
      src/yuzu_cmd/yuzu.cpp

+ 3 - 0
CMakeModules/CopyYuzuQt5Deps.cmake

@@ -4,8 +4,10 @@ function(copy_yuzu_Qt5_deps target_dir)
     set(Qt5_DLL_DIR "${Qt5_DIR}/../../../bin")
     set(Qt5_PLATFORMS_DIR "${Qt5_DIR}/../../../plugins/platforms/")
     set(Qt5_STYLES_DIR "${Qt5_DIR}/../../../plugins/styles/")
+    set(Qt5_IMAGEFORMATS_DIR "${Qt5_DIR}/../../../plugins/imageformats/")
     set(PLATFORMS ${DLL_DEST}platforms/)
     set(STYLES ${DLL_DEST}styles/)
+    set(IMAGEFORMATS ${DLL_DEST}imageformats/)
     windows_copy_files(${target_dir} ${Qt5_DLL_DIR} ${DLL_DEST}
         icudt*.dll
         icuin*.dll
@@ -17,4 +19,5 @@ function(copy_yuzu_Qt5_deps target_dir)
     )
     windows_copy_files(yuzu ${Qt5_PLATFORMS_DIR} ${PLATFORMS} qwindows$<$<CONFIG:Debug>:d>.*)
     windows_copy_files(yuzu ${Qt5_STYLES_DIR} ${STYLES} qwindowsvistastyle$<$<CONFIG:Debug>:d>.*)
+    windows_copy_files(yuzu ${Qt5_IMAGEFORMATS_DIR} ${IMAGEFORMATS} qjpeg$<$<CONFIG:Debug>:d>.*)
 endfunction(copy_yuzu_Qt5_deps)

+ 4 - 0
appveyor.yml

@@ -117,6 +117,7 @@ after_build:
           mkdir $RELEASE_DIST
           mkdir $RELEASE_DIST/platforms
           mkdir $RELEASE_DIST/styles
+          mkdir $RELEASE_DIST/imageformats
 
           # copy the compiled binaries and other release files to the release folder
           Get-ChildItem "$CMAKE_BINARY_DIR" -Filter "yuzu*.exe" | Copy-Item -destination $RELEASE_DIST
@@ -140,6 +141,9 @@ after_build:
           # copy the qt windows vista style dll to platforms
           Copy-Item -path "C:/msys64/mingw64/share/qt5/plugins/styles/qwindowsvistastyle.dll" -force -destination "$RELEASE_DIST/styles"
 
+          # copy the qt jpeg imageformat dll to platforms
+          Copy-Item -path "C:/msys64/mingw64/share/qt5/plugins/imageformats/qjpeg.dll" -force -destination "$RELEASE_DIST/imageformats"
+
           7z a -tzip $MINGW_BUILD_ZIP $RELEASE_DIST\*
           7z a $MINGW_SEVENZIP $RELEASE_DIST
         }

+ 1 - 1
externals/CMakeLists.txt

@@ -32,7 +32,7 @@ add_subdirectory(inih)
 
 # lz4
 set(LZ4_BUNDLED_MODE ON)
-add_subdirectory(lz4/contrib/cmake_unofficial)
+add_subdirectory(lz4/contrib/cmake_unofficial EXCLUDE_FROM_ALL)
 target_include_directories(lz4_static INTERFACE ./lz4/lib)
 
 # mbedtls

+ 2 - 2
src/common/alignment.h

@@ -9,13 +9,13 @@ namespace Common {
 
 template <typename T>
 constexpr T AlignUp(T value, size_t size) {
-    static_assert(std::is_unsigned<T>::value, "T must be an unsigned value.");
+    static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
     return static_cast<T>(value + (size - value % size) % size);
 }
 
 template <typename T>
 constexpr T AlignDown(T value, size_t size) {
-    static_assert(std::is_unsigned<T>::value, "T must be an unsigned value.");
+    static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
     return static_cast<T>(value - value % size);
 }
 

+ 1 - 1
src/common/bit_set.h

@@ -96,7 +96,7 @@ static inline int LeastSignificantSetBit(u64 val) {
 
 template <typename IntTy>
 class BitSet {
-    static_assert(!std::is_signed<IntTy>::value, "BitSet should not be used with signed types");
+    static_assert(!std::is_signed_v<IntTy>, "BitSet should not be used with signed types");
 
 public:
     // A reference to a particular bit, returned from operator[].

+ 31 - 19
src/common/color.h

@@ -4,6 +4,8 @@
 
 #pragma once
 
+#include <cstring>
+
 #include "common/common_types.h"
 #include "common/swap.h"
 #include "common/vector_math.h"
@@ -55,7 +57,7 @@ constexpr u8 Convert8To6(u8 value) {
  * @param bytes Pointer to encoded source color
  * @return Result color decoded as Math::Vec4<u8>
  */
-inline const Math::Vec4<u8> DecodeRGBA8(const u8* bytes) {
+inline Math::Vec4<u8> DecodeRGBA8(const u8* bytes) {
     return {bytes[3], bytes[2], bytes[1], bytes[0]};
 }
 
@@ -64,7 +66,7 @@ inline const Math::Vec4<u8> DecodeRGBA8(const u8* bytes) {
  * @param bytes Pointer to encoded source color
  * @return Result color decoded as Math::Vec4<u8>
  */
-inline const Math::Vec4<u8> DecodeRGB8(const u8* bytes) {
+inline Math::Vec4<u8> DecodeRGB8(const u8* bytes) {
     return {bytes[2], bytes[1], bytes[0], 255};
 }
 
@@ -73,7 +75,7 @@ inline const Math::Vec4<u8> DecodeRGB8(const u8* bytes) {
  * @param bytes Pointer to encoded source color
  * @return Result color decoded as Math::Vec4<u8>
  */
-inline const Math::Vec4<u8> DecodeRG8(const u8* bytes) {
+inline Math::Vec4<u8> DecodeRG8(const u8* bytes) {
     return {bytes[1], bytes[0], 0, 255};
 }
 
@@ -82,8 +84,9 @@ inline const Math::Vec4<u8> DecodeRG8(const u8* bytes) {
  * @param bytes Pointer to encoded source color
  * @return Result color decoded as Math::Vec4<u8>
  */
-inline const Math::Vec4<u8> DecodeRGB565(const u8* bytes) {
-    const u16_le pixel = *reinterpret_cast<const u16_le*>(bytes);
+inline Math::Vec4<u8> DecodeRGB565(const u8* bytes) {
+    u16_le pixel;
+    std::memcpy(&pixel, bytes, sizeof(pixel));
     return {Convert5To8((pixel >> 11) & 0x1F), Convert6To8((pixel >> 5) & 0x3F),
             Convert5To8(pixel & 0x1F), 255};
 }
@@ -93,8 +96,9 @@ inline const Math::Vec4<u8> DecodeRGB565(const u8* bytes) {
  * @param bytes Pointer to encoded source color
  * @return Result color decoded as Math::Vec4<u8>
  */
-inline const Math::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
-    const u16_le pixel = *reinterpret_cast<const u16_le*>(bytes);
+inline Math::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
+    u16_le pixel;
+    std::memcpy(&pixel, bytes, sizeof(pixel));
     return {Convert5To8((pixel >> 11) & 0x1F), Convert5To8((pixel >> 6) & 0x1F),
             Convert5To8((pixel >> 1) & 0x1F), Convert1To8(pixel & 0x1)};
 }
@@ -104,8 +108,9 @@ inline const Math::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
  * @param bytes Pointer to encoded source color
  * @return Result color decoded as Math::Vec4<u8>
  */
-inline const Math::Vec4<u8> DecodeRGBA4(const u8* bytes) {
-    const u16_le pixel = *reinterpret_cast<const u16_le*>(bytes);
+inline Math::Vec4<u8> DecodeRGBA4(const u8* bytes) {
+    u16_le pixel;
+    std::memcpy(&pixel, bytes, sizeof(pixel));
     return {Convert4To8((pixel >> 12) & 0xF), Convert4To8((pixel >> 8) & 0xF),
             Convert4To8((pixel >> 4) & 0xF), Convert4To8(pixel & 0xF)};
 }
@@ -116,7 +121,9 @@ inline const Math::Vec4<u8> DecodeRGBA4(const u8* bytes) {
  * @return Depth value as an u32
  */
 inline u32 DecodeD16(const u8* bytes) {
-    return *reinterpret_cast<const u16_le*>(bytes);
+    u16_le data;
+    std::memcpy(&data, bytes, sizeof(data));
+    return data;
 }
 
 /**
@@ -133,7 +140,7 @@ inline u32 DecodeD24(const u8* bytes) {
  * @param bytes Pointer to encoded source values
  * @return Resulting values stored as a Math::Vec2
  */
-inline const Math::Vec2<u32> DecodeD24S8(const u8* bytes) {
+inline Math::Vec2<u32> DecodeD24S8(const u8* bytes) {
     return {static_cast<u32>((bytes[2] << 16) | (bytes[1] << 8) | bytes[0]), bytes[3]};
 }
 
@@ -175,8 +182,10 @@ inline void EncodeRG8(const Math::Vec4<u8>& color, u8* bytes) {
  * @param bytes Destination pointer to store encoded color
  */
 inline void EncodeRGB565(const Math::Vec4<u8>& color, u8* bytes) {
-    *reinterpret_cast<u16_le*>(bytes) =
+    const u16_le data =
         (Convert8To5(color.r()) << 11) | (Convert8To6(color.g()) << 5) | Convert8To5(color.b());
+
+    std::memcpy(bytes, &data, sizeof(data));
 }
 
 /**
@@ -185,9 +194,10 @@ inline void EncodeRGB565(const Math::Vec4<u8>& color, u8* bytes) {
  * @param bytes Destination pointer to store encoded color
  */
 inline void EncodeRGB5A1(const Math::Vec4<u8>& color, u8* bytes) {
-    *reinterpret_cast<u16_le*>(bytes) = (Convert8To5(color.r()) << 11) |
-                                        (Convert8To5(color.g()) << 6) |
-                                        (Convert8To5(color.b()) << 1) | Convert8To1(color.a());
+    const u16_le data = (Convert8To5(color.r()) << 11) | (Convert8To5(color.g()) << 6) |
+                        (Convert8To5(color.b()) << 1) | Convert8To1(color.a());
+
+    std::memcpy(bytes, &data, sizeof(data));
 }
 
 /**
@@ -196,9 +206,10 @@ inline void EncodeRGB5A1(const Math::Vec4<u8>& color, u8* bytes) {
  * @param bytes Destination pointer to store encoded color
  */
 inline void EncodeRGBA4(const Math::Vec4<u8>& color, u8* bytes) {
-    *reinterpret_cast<u16_le*>(bytes) = (Convert8To4(color.r()) << 12) |
-                                        (Convert8To4(color.g()) << 8) |
-                                        (Convert8To4(color.b()) << 4) | Convert8To4(color.a());
+    const u16 data = (Convert8To4(color.r()) << 12) | (Convert8To4(color.g()) << 8) |
+                     (Convert8To4(color.b()) << 4) | Convert8To4(color.a());
+
+    std::memcpy(bytes, &data, sizeof(data));
 }
 
 /**
@@ -207,7 +218,8 @@ inline void EncodeRGBA4(const Math::Vec4<u8>& color, u8* bytes) {
  * @param bytes Pointer where to store the encoded value
  */
 inline void EncodeD16(u32 value, u8* bytes) {
-    *reinterpret_cast<u16_le*>(bytes) = value & 0xFFFF;
+    const u16_le data = static_cast<u16>(value);
+    std::memcpy(bytes, &data, sizeof(data));
 }
 
 /**

+ 13 - 3
src/common/file_util.cpp

@@ -884,11 +884,21 @@ std::string_view RemoveTrailingSlash(std::string_view path) {
     return path;
 }
 
-std::string SanitizePath(std::string_view path_) {
+std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) {
     std::string path(path_);
-    std::replace(path.begin(), path.end(), '\\', '/');
+    char type1 = directory_separator == DirectorySeparator::BackwardSlash ? '/' : '\\';
+    char type2 = directory_separator == DirectorySeparator::BackwardSlash ? '\\' : '/';
+
+    if (directory_separator == DirectorySeparator::PlatformDefault) {
+#ifdef _WIN32
+        type1 = '/';
+        type2 = '\\';
+#endif
+    }
+
+    std::replace(path.begin(), path.end(), type1, type2);
     path.erase(std::unique(path.begin(), path.end(),
-                           [](char c1, char c2) { return c1 == '/' && c2 == '/'; }),
+                           [type2](char c1, char c2) { return c1 == type2 && c2 == type2; }),
                path.end());
     return std::string(RemoveTrailingSlash(path));
 }

+ 11 - 7
src/common/file_util.h

@@ -182,8 +182,12 @@ std::vector<T> SliceVector(const std::vector<T>& vector, size_t first, size_t la
     return std::vector<T>(vector.begin() + first, vector.begin() + first + last);
 }
 
-// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'.
-std::string SanitizePath(std::string_view path);
+enum class DirectorySeparator { ForwardSlash, BackwardSlash, PlatformDefault };
+
+// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\'
+// depending if directory_separator is BackwardSlash or PlatformDefault and running on windows
+std::string SanitizePath(std::string_view path,
+                         DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
 
 // simple wrapper for cstdlib file functions to
 // hopefully will make error checking easier
@@ -208,7 +212,7 @@ public:
 
     template <typename T>
     size_t ReadArray(T* data, size_t length) const {
-        static_assert(std::is_trivially_copyable<T>(),
+        static_assert(std::is_trivially_copyable_v<T>,
                       "Given array does not consist of trivially copyable objects");
 
         if (!IsOpen()) {
@@ -220,7 +224,7 @@ public:
 
     template <typename T>
     size_t WriteArray(const T* data, size_t length) {
-        static_assert(std::is_trivially_copyable<T>(),
+        static_assert(std::is_trivially_copyable_v<T>,
                       "Given array does not consist of trivially copyable objects");
         if (!IsOpen()) {
             return std::numeric_limits<size_t>::max();
@@ -231,19 +235,19 @@ public:
 
     template <typename T>
     size_t ReadBytes(T* data, size_t length) const {
-        static_assert(std::is_trivially_copyable<T>(), "T must be trivially copyable");
+        static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
         return ReadArray(reinterpret_cast<char*>(data), length);
     }
 
     template <typename T>
     size_t WriteBytes(const T* data, size_t length) {
-        static_assert(std::is_trivially_copyable<T>(), "T must be trivially copyable");
+        static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
         return WriteArray(reinterpret_cast<const char*>(data), length);
     }
 
     template <typename T>
     size_t WriteObject(const T& object) {
-        static_assert(!std::is_pointer<T>::value, "Given object is a pointer");
+        static_assert(!std::is_pointer_v<T>, "WriteObject arguments must not be a pointer");
         return WriteArray(&object, 1);
     }
 

+ 2 - 2
src/common/hash.h

@@ -28,7 +28,7 @@ static inline u64 ComputeHash64(const void* data, size_t len) {
  */
 template <typename T>
 static inline u64 ComputeStructHash64(const T& data) {
-    static_assert(std::is_trivially_copyable<T>(),
+    static_assert(std::is_trivially_copyable_v<T>,
                   "Type passed to ComputeStructHash64 must be trivially copyable");
     return ComputeHash64(&data, sizeof(data));
 }
@@ -38,7 +38,7 @@ template <typename T>
 struct HashableStruct {
     // In addition to being trivially copyable, T must also have a trivial default constructor,
     // because any member initialization would be overridden by memset
-    static_assert(std::is_trivial<T>(), "Type passed to HashableStruct must be trivial");
+    static_assert(std::is_trivial_v<T>, "Type passed to HashableStruct must be trivial");
     /*
      * We use a union because "implicitly-defined copy/move constructor for a union X copies the
      * object representation of X." and "implicitly-defined copy assignment operator for a union X

+ 8 - 0
src/common/logging/backend.cpp

@@ -171,15 +171,21 @@ void FileBackend::Write(const Entry& entry) {
     SUB(Service, ARP)                                                                              \
     SUB(Service, BCAT)                                                                             \
     SUB(Service, BPC)                                                                              \
+    SUB(Service, BTDRV)                                                                            \
     SUB(Service, BTM)                                                                              \
     SUB(Service, Capture)                                                                          \
+    SUB(Service, ERPT)                                                                             \
+    SUB(Service, ETicket)                                                                          \
+    SUB(Service, EUPLD)                                                                            \
     SUB(Service, Fatal)                                                                            \
     SUB(Service, FGM)                                                                              \
     SUB(Service, Friend)                                                                           \
     SUB(Service, FS)                                                                               \
+    SUB(Service, GRC)                                                                              \
     SUB(Service, HID)                                                                              \
     SUB(Service, LBL)                                                                              \
     SUB(Service, LDN)                                                                              \
+    SUB(Service, LDR)                                                                              \
     SUB(Service, LM)                                                                               \
     SUB(Service, Migration)                                                                        \
     SUB(Service, Mii)                                                                              \
@@ -188,11 +194,13 @@ void FileBackend::Write(const Entry& entry) {
     SUB(Service, NFC)                                                                              \
     SUB(Service, NFP)                                                                              \
     SUB(Service, NIFM)                                                                             \
+    SUB(Service, NIM)                                                                              \
     SUB(Service, NS)                                                                               \
     SUB(Service, NVDRV)                                                                            \
     SUB(Service, PCIE)                                                                             \
     SUB(Service, PCTL)                                                                             \
     SUB(Service, PCV)                                                                              \
+    SUB(Service, PM)                                                                               \
     SUB(Service, PREPO)                                                                            \
     SUB(Service, PSC)                                                                              \
     SUB(Service, SET)                                                                              \

+ 8 - 0
src/common/logging/log.h

@@ -58,15 +58,21 @@ enum class Class : ClassType {
     Service_Audio,     ///< The Audio (Audio control) service
     Service_BCAT,      ///< The BCAT service
     Service_BPC,       ///< The BPC service
+    Service_BTDRV,     ///< The Bluetooth driver service
     Service_BTM,       ///< The BTM service
     Service_Capture,   ///< The capture service
+    Service_ERPT,      ///< The error reporting service
+    Service_ETicket,   ///< The ETicket service
+    Service_EUPLD,     ///< The error upload service
     Service_Fatal,     ///< The Fatal service
     Service_FGM,       ///< The FGM service
     Service_Friend,    ///< The friend service
     Service_FS,        ///< The FS (Filesystem) service
+    Service_GRC,       ///< The game recording service
     Service_HID,       ///< The HID (Human interface device) service
     Service_LBL,       ///< The LBL (LCD backlight) service
     Service_LDN,       ///< The LDN (Local domain network) service
+    Service_LDR,       ///< The loader service
     Service_LM,        ///< The LM (Logger) service
     Service_Migration, ///< The migration service
     Service_Mii,       ///< The Mii service
@@ -75,11 +81,13 @@ enum class Class : ClassType {
     Service_NFC,       ///< The NFC (Near-field communication) service
     Service_NFP,       ///< The NFP service
     Service_NIFM,      ///< The NIFM (Network interface) service
+    Service_NIM,       ///< The NIM service
     Service_NS,        ///< The NS services
     Service_NVDRV,     ///< The NVDRV (Nvidia driver) service
     Service_PCIE,      ///< The PCIe service
     Service_PCTL,      ///< The PCTL (Parental control) service
     Service_PCV,       ///< The PCV service
+    Service_PM,        ///< The PM service
     Service_PREPO,     ///< The PREPO (Play report) service
     Service_PSC,       ///< The PSC service
     Service_SET,       ///< The SET (Settings) service

+ 182 - 180
src/common/vector_math.h

@@ -42,140 +42,136 @@ class Vec3;
 template <typename T>
 class Vec4;
 
-template <typename T>
-static inline Vec2<T> MakeVec(const T& x, const T& y);
-template <typename T>
-static inline Vec3<T> MakeVec(const T& x, const T& y, const T& z);
-template <typename T>
-static inline Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w);
-
 template <typename T>
 class Vec2 {
 public:
     T x{};
     T y{};
 
-    Vec2() = default;
-    Vec2(const T& _x, const T& _y) : x(_x), y(_y) {}
+    constexpr Vec2() = default;
+    constexpr Vec2(const T& x_, const T& y_) : x(x_), y(y_) {}
 
     template <typename T2>
-    Vec2<T2> Cast() const {
-        return Vec2<T2>((T2)x, (T2)y);
+    constexpr Vec2<T2> Cast() const {
+        return Vec2<T2>(static_cast<T2>(x), static_cast<T2>(y));
     }
 
-    static Vec2 AssignToAll(const T& f) {
-        return Vec2<T>(f, f);
+    static constexpr Vec2 AssignToAll(const T& f) {
+        return Vec2{f, f};
     }
 
-    Vec2<decltype(T{} + T{})> operator+(const Vec2& other) const {
-        return MakeVec(x + other.x, y + other.y);
+    constexpr Vec2<decltype(T{} + T{})> operator+(const Vec2& other) const {
+        return {x + other.x, y + other.y};
     }
-    void operator+=(const Vec2& other) {
+    constexpr Vec2& operator+=(const Vec2& other) {
         x += other.x;
         y += other.y;
+        return *this;
     }
-    Vec2<decltype(T{} - T{})> operator-(const Vec2& other) const {
-        return MakeVec(x - other.x, y - other.y);
+    constexpr Vec2<decltype(T{} - T{})> operator-(const Vec2& other) const {
+        return {x - other.x, y - other.y};
     }
-    void operator-=(const Vec2& other) {
+    constexpr Vec2& operator-=(const Vec2& other) {
         x -= other.x;
         y -= other.y;
+        return *this;
     }
 
     template <typename U = T>
-    Vec2<std::enable_if_t<std::is_signed<U>::value, U>> operator-() const {
-        return MakeVec(-x, -y);
+    constexpr Vec2<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
+        return {-x, -y};
     }
-    Vec2<decltype(T{} * T{})> operator*(const Vec2& other) const {
-        return MakeVec(x * other.x, y * other.y);
+    constexpr Vec2<decltype(T{} * T{})> operator*(const Vec2& other) const {
+        return {x * other.x, y * other.y};
     }
+
     template <typename V>
-    Vec2<decltype(T{} * V{})> operator*(const V& f) const {
-        return MakeVec(x * f, y * f);
+    constexpr Vec2<decltype(T{} * V{})> operator*(const V& f) const {
+        return {x * f, y * f};
     }
+
     template <typename V>
-    void operator*=(const V& f) {
+    constexpr Vec2& operator*=(const V& f) {
         *this = *this * f;
+        return *this;
     }
+
     template <typename V>
-    Vec2<decltype(T{} / V{})> operator/(const V& f) const {
-        return MakeVec(x / f, y / f);
+    constexpr Vec2<decltype(T{} / V{})> operator/(const V& f) const {
+        return {x / f, y / f};
     }
+
     template <typename V>
-    void operator/=(const V& f) {
+    constexpr Vec2& operator/=(const V& f) {
         *this = *this / f;
+        return *this;
     }
 
-    T Length2() const {
+    constexpr T Length2() const {
         return x * x + y * y;
     }
 
     // Only implemented for T=float
     float Length() const;
-    void SetLength(const float l);
-    Vec2 WithLength(const float l) const;
-    float Distance2To(Vec2& other);
-    Vec2 Normalized() const;
     float Normalize(); // returns the previous length, which is often useful
 
-    T& operator[](int i) // allow vector[1] = 3   (vector.y=3)
-    {
+    constexpr T& operator[](std::size_t i) {
         return *((&x) + i);
     }
-    T operator[](const int i) const {
+    constexpr const T& operator[](std::size_t i) const {
         return *((&x) + i);
     }
 
-    void SetZero() {
+    constexpr void SetZero() {
         x = 0;
         y = 0;
     }
 
     // Common aliases: UV (texel coordinates), ST (texture coordinates)
-    T& u() {
+    constexpr T& u() {
         return x;
     }
-    T& v() {
+    constexpr T& v() {
         return y;
     }
-    T& s() {
+    constexpr T& s() {
         return x;
     }
-    T& t() {
+    constexpr T& t() {
         return y;
     }
 
-    const T& u() const {
+    constexpr const T& u() const {
         return x;
     }
-    const T& v() const {
+    constexpr const T& v() const {
         return y;
     }
-    const T& s() const {
+    constexpr const T& s() const {
         return x;
     }
-    const T& t() const {
+    constexpr const T& t() const {
         return y;
     }
 
     // swizzlers - create a subvector of specific components
-    const Vec2 yx() const {
+    constexpr Vec2 yx() const {
         return Vec2(y, x);
     }
-    const Vec2 vu() const {
+    constexpr Vec2 vu() const {
         return Vec2(y, x);
     }
-    const Vec2 ts() const {
+    constexpr Vec2 ts() const {
         return Vec2(y, x);
     }
 };
 
 template <typename T, typename V>
-Vec2<T> operator*(const V& f, const Vec2<T>& vec) {
+constexpr Vec2<T> operator*(const V& f, const Vec2<T>& vec) {
     return Vec2<T>(f * vec.x, f * vec.y);
 }
 
-typedef Vec2<float> Vec2f;
+using Vec2f = Vec2<float>;
 
 template <>
 inline float Vec2<float>::Length() const {
@@ -196,147 +192,151 @@ public:
     T y{};
     T z{};
 
-    Vec3() = default;
-    Vec3(const T& _x, const T& _y, const T& _z) : x(_x), y(_y), z(_z) {}
+    constexpr Vec3() = default;
+    constexpr Vec3(const T& x_, const T& y_, const T& z_) : x(x_), y(y_), z(z_) {}
 
     template <typename T2>
-    Vec3<T2> Cast() const {
-        return MakeVec<T2>((T2)x, (T2)y, (T2)z);
+    constexpr Vec3<T2> Cast() const {
+        return Vec3<T2>(static_cast<T2>(x), static_cast<T2>(y), static_cast<T2>(z));
     }
 
-    // Only implemented for T=int and T=float
-    static Vec3 FromRGB(unsigned int rgb);
-    unsigned int ToRGB() const; // alpha bits set to zero
-
-    static Vec3 AssignToAll(const T& f) {
-        return MakeVec(f, f, f);
+    static constexpr Vec3 AssignToAll(const T& f) {
+        return Vec3(f, f, f);
     }
 
-    Vec3<decltype(T{} + T{})> operator+(const Vec3& other) const {
-        return MakeVec(x + other.x, y + other.y, z + other.z);
+    constexpr Vec3<decltype(T{} + T{})> operator+(const Vec3& other) const {
+        return {x + other.x, y + other.y, z + other.z};
     }
-    void operator+=(const Vec3& other) {
+
+    constexpr Vec3& operator+=(const Vec3& other) {
         x += other.x;
         y += other.y;
         z += other.z;
+        return *this;
     }
-    Vec3<decltype(T{} - T{})> operator-(const Vec3& other) const {
-        return MakeVec(x - other.x, y - other.y, z - other.z);
+
+    constexpr Vec3<decltype(T{} - T{})> operator-(const Vec3& other) const {
+        return {x - other.x, y - other.y, z - other.z};
     }
-    void operator-=(const Vec3& other) {
+
+    constexpr Vec3& operator-=(const Vec3& other) {
         x -= other.x;
         y -= other.y;
         z -= other.z;
+        return *this;
     }
 
     template <typename U = T>
-    Vec3<std::enable_if_t<std::is_signed<U>::value, U>> operator-() const {
-        return MakeVec(-x, -y, -z);
+    constexpr Vec3<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
+        return {-x, -y, -z};
     }
-    Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const {
-        return MakeVec(x * other.x, y * other.y, z * other.z);
+
+    constexpr Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const {
+        return {x * other.x, y * other.y, z * other.z};
     }
+
     template <typename V>
-    Vec3<decltype(T{} * V{})> operator*(const V& f) const {
-        return MakeVec(x * f, y * f, z * f);
+    constexpr Vec3<decltype(T{} * V{})> operator*(const V& f) const {
+        return {x * f, y * f, z * f};
     }
+
     template <typename V>
-    void operator*=(const V& f) {
+    constexpr Vec3& operator*=(const V& f) {
         *this = *this * f;
+        return *this;
     }
     template <typename V>
-    Vec3<decltype(T{} / V{})> operator/(const V& f) const {
-        return MakeVec(x / f, y / f, z / f);
+    constexpr Vec3<decltype(T{} / V{})> operator/(const V& f) const {
+        return {x / f, y / f, z / f};
     }
+
     template <typename V>
-    void operator/=(const V& f) {
+    constexpr Vec3& operator/=(const V& f) {
         *this = *this / f;
+        return *this;
     }
 
-    T Length2() const {
+    constexpr T Length2() const {
         return x * x + y * y + z * z;
     }
 
     // Only implemented for T=float
     float Length() const;
-    void SetLength(const float l);
-    Vec3 WithLength(const float l) const;
-    float Distance2To(Vec3& other);
     Vec3 Normalized() const;
     float Normalize(); // returns the previous length, which is often useful
 
-    T& operator[](int i) // allow vector[2] = 3   (vector.z=3)
-    {
+    constexpr T& operator[](std::size_t i) {
         return *((&x) + i);
     }
-    T operator[](const int i) const {
+
+    constexpr const T& operator[](std::size_t i) const {
         return *((&x) + i);
     }
 
-    void SetZero() {
+    constexpr void SetZero() {
         x = 0;
         y = 0;
         z = 0;
     }
 
     // Common aliases: UVW (texel coordinates), RGB (colors), STQ (texture coordinates)
-    T& u() {
+    constexpr T& u() {
         return x;
     }
-    T& v() {
+    constexpr T& v() {
         return y;
     }
-    T& w() {
+    constexpr T& w() {
         return z;
     }
 
-    T& r() {
+    constexpr T& r() {
         return x;
     }
-    T& g() {
+    constexpr T& g() {
         return y;
     }
-    T& b() {
+    constexpr T& b() {
         return z;
     }
 
-    T& s() {
+    constexpr T& s() {
         return x;
     }
-    T& t() {
+    constexpr T& t() {
         return y;
     }
-    T& q() {
+    constexpr T& q() {
         return z;
     }
 
-    const T& u() const {
+    constexpr const T& u() const {
         return x;
     }
-    const T& v() const {
+    constexpr const T& v() const {
         return y;
     }
-    const T& w() const {
+    constexpr const T& w() const {
         return z;
     }
 
-    const T& r() const {
+    constexpr const T& r() const {
         return x;
     }
-    const T& g() const {
+    constexpr const T& g() const {
         return y;
     }
-    const T& b() const {
+    constexpr const T& b() const {
         return z;
     }
 
-    const T& s() const {
+    constexpr const T& s() const {
         return x;
     }
-    const T& t() const {
+    constexpr const T& t() const {
         return y;
     }
-    const T& q() const {
+    constexpr const T& q() const {
         return z;
     }
 
@@ -345,7 +345,7 @@ public:
 // _DEFINE_SWIZZLER2 defines a single such function, DEFINE_SWIZZLER2 defines all of them for all
 // component names (x<->r) and permutations (xy<->yx)
 #define _DEFINE_SWIZZLER2(a, b, name)                                                              \
-    const Vec2<T> name() const {                                                                   \
+    constexpr Vec2<T> name() const {                                                               \
         return Vec2<T>(a, b);                                                                      \
     }
 #define DEFINE_SWIZZLER2(a, b, a2, b2, a3, b3, a4, b4)                                             \
@@ -366,7 +366,7 @@ public:
 };
 
 template <typename T, typename V>
-Vec3<T> operator*(const V& f, const Vec3<T>& vec) {
+constexpr Vec3<T> operator*(const V& f, const Vec3<T>& vec) {
     return Vec3<T>(f * vec.x, f * vec.y, f * vec.z);
 }
 
@@ -387,7 +387,7 @@ inline float Vec3<float>::Normalize() {
     return length;
 }
 
-typedef Vec3<float> Vec3f;
+using Vec3f = Vec3<float>;
 
 template <typename T>
 class Vec4 {
@@ -397,86 +397,88 @@ public:
     T z{};
     T w{};
 
-    Vec4() = default;
-    Vec4(const T& _x, const T& _y, const T& _z, const T& _w) : x(_x), y(_y), z(_z), w(_w) {}
+    constexpr Vec4() = default;
+    constexpr Vec4(const T& x_, const T& y_, const T& z_, const T& w_)
+        : x(x_), y(y_), z(z_), w(w_) {}
 
     template <typename T2>
-    Vec4<T2> Cast() const {
-        return Vec4<T2>((T2)x, (T2)y, (T2)z, (T2)w);
+    constexpr Vec4<T2> Cast() const {
+        return Vec4<T2>(static_cast<T2>(x), static_cast<T2>(y), static_cast<T2>(z),
+                        static_cast<T2>(w));
     }
 
-    // Only implemented for T=int and T=float
-    static Vec4 FromRGBA(unsigned int rgba);
-    unsigned int ToRGBA() const;
-
-    static Vec4 AssignToAll(const T& f) {
-        return Vec4<T>(f, f, f, f);
+    static constexpr Vec4 AssignToAll(const T& f) {
+        return Vec4(f, f, f, f);
     }
 
-    Vec4<decltype(T{} + T{})> operator+(const Vec4& other) const {
-        return MakeVec(x + other.x, y + other.y, z + other.z, w + other.w);
+    constexpr Vec4<decltype(T{} + T{})> operator+(const Vec4& other) const {
+        return {x + other.x, y + other.y, z + other.z, w + other.w};
     }
-    void operator+=(const Vec4& other) {
+
+    constexpr Vec4& operator+=(const Vec4& other) {
         x += other.x;
         y += other.y;
         z += other.z;
         w += other.w;
+        return *this;
     }
-    Vec4<decltype(T{} - T{})> operator-(const Vec4& other) const {
-        return MakeVec(x - other.x, y - other.y, z - other.z, w - other.w);
+
+    constexpr Vec4<decltype(T{} - T{})> operator-(const Vec4& other) const {
+        return {x - other.x, y - other.y, z - other.z, w - other.w};
     }
-    void operator-=(const Vec4& other) {
+
+    constexpr Vec4& operator-=(const Vec4& other) {
         x -= other.x;
         y -= other.y;
         z -= other.z;
         w -= other.w;
+        return *this;
     }
 
     template <typename U = T>
-    Vec4<std::enable_if_t<std::is_signed<U>::value, U>> operator-() const {
-        return MakeVec(-x, -y, -z, -w);
+    constexpr Vec4<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
+        return {-x, -y, -z, -w};
     }
-    Vec4<decltype(T{} * T{})> operator*(const Vec4& other) const {
-        return MakeVec(x * other.x, y * other.y, z * other.z, w * other.w);
+
+    constexpr Vec4<decltype(T{} * T{})> operator*(const Vec4& other) const {
+        return {x * other.x, y * other.y, z * other.z, w * other.w};
     }
+
     template <typename V>
-    Vec4<decltype(T{} * V{})> operator*(const V& f) const {
-        return MakeVec(x * f, y * f, z * f, w * f);
+    constexpr Vec4<decltype(T{} * V{})> operator*(const V& f) const {
+        return {x * f, y * f, z * f, w * f};
     }
+
     template <typename V>
-    void operator*=(const V& f) {
+    constexpr Vec4& operator*=(const V& f) {
         *this = *this * f;
+        return *this;
     }
+
     template <typename V>
-    Vec4<decltype(T{} / V{})> operator/(const V& f) const {
-        return MakeVec(x / f, y / f, z / f, w / f);
+    constexpr Vec4<decltype(T{} / V{})> operator/(const V& f) const {
+        return {x / f, y / f, z / f, w / f};
     }
+
     template <typename V>
-    void operator/=(const V& f) {
+    constexpr Vec4& operator/=(const V& f) {
         *this = *this / f;
+        return *this;
     }
 
-    T Length2() const {
+    constexpr T Length2() const {
         return x * x + y * y + z * z + w * w;
     }
 
-    // Only implemented for T=float
-    float Length() const;
-    void SetLength(const float l);
-    Vec4 WithLength(const float l) const;
-    float Distance2To(Vec4& other);
-    Vec4 Normalized() const;
-    float Normalize(); // returns the previous length, which is often useful
-
-    T& operator[](int i) // allow vector[2] = 3   (vector.z=3)
-    {
+    constexpr T& operator[](std::size_t i) {
         return *((&x) + i);
     }
-    T operator[](const int i) const {
+
+    constexpr const T& operator[](std::size_t i) const {
         return *((&x) + i);
     }
 
-    void SetZero() {
+    constexpr void SetZero() {
         x = 0;
         y = 0;
         z = 0;
@@ -484,29 +486,29 @@ public:
     }
 
     // Common alias: RGBA (colors)
-    T& r() {
+    constexpr T& r() {
         return x;
     }
-    T& g() {
+    constexpr T& g() {
         return y;
     }
-    T& b() {
+    constexpr T& b() {
         return z;
     }
-    T& a() {
+    constexpr T& a() {
         return w;
     }
 
-    const T& r() const {
+    constexpr const T& r() const {
         return x;
     }
-    const T& g() const {
+    constexpr const T& g() const {
         return y;
     }
-    const T& b() const {
+    constexpr const T& b() const {
         return z;
     }
-    const T& a() const {
+    constexpr const T& a() const {
         return w;
     }
 
@@ -518,7 +520,7 @@ public:
 // DEFINE_SWIZZLER2_COMP2 defines two component functions for all component names (x<->r) and
 // permutations (xy<->yx)
 #define _DEFINE_SWIZZLER2(a, b, name)                                                              \
-    const Vec2<T> name() const {                                                                   \
+    constexpr Vec2<T> name() const {                                                               \
         return Vec2<T>(a, b);                                                                      \
     }
 #define DEFINE_SWIZZLER2_COMP1(a, a2)                                                              \
@@ -545,7 +547,7 @@ public:
 #undef _DEFINE_SWIZZLER2
 
 #define _DEFINE_SWIZZLER3(a, b, c, name)                                                           \
-    const Vec3<T> name() const {                                                                   \
+    constexpr Vec3<T> name() const {                                                               \
         return Vec3<T>(a, b, c);                                                                   \
     }
 #define DEFINE_SWIZZLER3_COMP1(a, a2)                                                              \
@@ -579,51 +581,51 @@ public:
 };
 
 template <typename T, typename V>
-Vec4<decltype(V{} * T{})> operator*(const V& f, const Vec4<T>& vec) {
-    return MakeVec(f * vec.x, f * vec.y, f * vec.z, f * vec.w);
+constexpr Vec4<decltype(V{} * T{})> operator*(const V& f, const Vec4<T>& vec) {
+    return {f * vec.x, f * vec.y, f * vec.z, f * vec.w};
 }
 
-typedef Vec4<float> Vec4f;
+using Vec4f = Vec4<float>;
 
 template <typename T>
-static inline decltype(T{} * T{} + T{} * T{}) Dot(const Vec2<T>& a, const Vec2<T>& b) {
+constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec2<T>& a, const Vec2<T>& b) {
     return a.x * b.x + a.y * b.y;
 }
 
 template <typename T>
-static inline decltype(T{} * T{} + T{} * T{}) Dot(const Vec3<T>& a, const Vec3<T>& b) {
+constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec3<T>& a, const Vec3<T>& b) {
     return a.x * b.x + a.y * b.y + a.z * b.z;
 }
 
 template <typename T>
-static inline decltype(T{} * T{} + T{} * T{}) Dot(const Vec4<T>& a, const Vec4<T>& b) {
+constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec4<T>& a, const Vec4<T>& b) {
     return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
 }
 
 template <typename T>
-static inline Vec3<decltype(T{} * T{} - T{} * T{})> Cross(const Vec3<T>& a, const Vec3<T>& b) {
-    return MakeVec(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
+constexpr Vec3<decltype(T{} * T{} - T{} * T{})> Cross(const Vec3<T>& a, const Vec3<T>& b) {
+    return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
 }
 
 // linear interpolation via float: 0.0=begin, 1.0=end
 template <typename X>
-static inline decltype(X{} * float{} + X{} * float{}) Lerp(const X& begin, const X& end,
-                                                           const float t) {
+constexpr decltype(X{} * float{} + X{} * float{}) Lerp(const X& begin, const X& end,
+                                                       const float t) {
     return begin * (1.f - t) + end * t;
 }
 
 // linear interpolation via int: 0=begin, base=end
 template <typename X, int base>
-static inline decltype((X{} * int{} + X{} * int{}) / base) LerpInt(const X& begin, const X& end,
-                                                                   const int t) {
+constexpr decltype((X{} * int{} + X{} * int{}) / base) LerpInt(const X& begin, const X& end,
+                                                               const int t) {
     return (begin * (base - t) + end * t) / base;
 }
 
 // bilinear interpolation. s is for interpolating x00-x01 and x10-x11, and t is for the second
 // interpolation.
 template <typename X>
-inline auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X& x11, const float s,
-                           const float t) {
+constexpr auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X& x11, const float s,
+                              const float t) {
     auto y0 = Lerp(x00, x01, s);
     auto y1 = Lerp(x10, x11, s);
     return Lerp(y0, y1, t);
@@ -631,42 +633,42 @@ inline auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X& x1
 
 // Utility vector factories
 template <typename T>
-static inline Vec2<T> MakeVec(const T& x, const T& y) {
+constexpr Vec2<T> MakeVec(const T& x, const T& y) {
     return Vec2<T>{x, y};
 }
 
 template <typename T>
-static inline Vec3<T> MakeVec(const T& x, const T& y, const T& z) {
+constexpr Vec3<T> MakeVec(const T& x, const T& y, const T& z) {
     return Vec3<T>{x, y, z};
 }
 
 template <typename T>
-static inline Vec4<T> MakeVec(const T& x, const T& y, const Vec2<T>& zw) {
+constexpr Vec4<T> MakeVec(const T& x, const T& y, const Vec2<T>& zw) {
     return MakeVec(x, y, zw[0], zw[1]);
 }
 
 template <typename T>
-static inline Vec3<T> MakeVec(const Vec2<T>& xy, const T& z) {
+constexpr Vec3<T> MakeVec(const Vec2<T>& xy, const T& z) {
     return MakeVec(xy[0], xy[1], z);
 }
 
 template <typename T>
-static inline Vec3<T> MakeVec(const T& x, const Vec2<T>& yz) {
+constexpr Vec3<T> MakeVec(const T& x, const Vec2<T>& yz) {
     return MakeVec(x, yz[0], yz[1]);
 }
 
 template <typename T>
-static inline Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w) {
+constexpr Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w) {
     return Vec4<T>{x, y, z, w};
 }
 
 template <typename T>
-static inline Vec4<T> MakeVec(const Vec2<T>& xy, const T& z, const T& w) {
+constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const T& z, const T& w) {
     return MakeVec(xy[0], xy[1], z, w);
 }
 
 template <typename T>
-static inline Vec4<T> MakeVec(const T& x, const Vec2<T>& yz, const T& w) {
+constexpr Vec4<T> MakeVec(const T& x, const Vec2<T>& yz, const T& w) {
     return MakeVec(x, yz[0], yz[1], w);
 }
 
@@ -674,17 +676,17 @@ static inline Vec4<T> MakeVec(const T& x, const Vec2<T>& yz, const T& w) {
 //       Even if someone wanted to use an odd object like Vec2<Vec2<T>>, the compiler would error
 //       out soon enough due to misuse of the returned structure.
 template <typename T>
-static inline Vec4<T> MakeVec(const Vec2<T>& xy, const Vec2<T>& zw) {
+constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const Vec2<T>& zw) {
     return MakeVec(xy[0], xy[1], zw[0], zw[1]);
 }
 
 template <typename T>
-static inline Vec4<T> MakeVec(const Vec3<T>& xyz, const T& w) {
+constexpr Vec4<T> MakeVec(const Vec3<T>& xyz, const T& w) {
     return MakeVec(xyz[0], xyz[1], xyz[2], w);
 }
 
 template <typename T>
-static inline Vec4<T> MakeVec(const T& x, const Vec3<T>& yzw) {
+constexpr Vec4<T> MakeVec(const T& x, const Vec3<T>& yzw) {
     return MakeVec(x, yzw[0], yzw[1], yzw[2]);
 }
 

+ 1 - 1
src/common/x64/xbyak_util.h

@@ -34,7 +34,7 @@ inline bool IsWithin2G(const Xbyak::CodeGenerator& code, uintptr_t target) {
 
 template <typename T>
 inline void CallFarFunction(Xbyak::CodeGenerator& code, const T f) {
-    static_assert(std::is_pointer<T>(), "Argument must be a (function) pointer.");
+    static_assert(std::is_pointer_v<T>, "Argument must be a (function) pointer.");
     size_t addr = reinterpret_cast<size_t>(f);
     if (IsWithin2G(code, addr)) {
         code.call(f);

+ 6 - 2
src/core/core.cpp

@@ -89,7 +89,7 @@ System::ResultStatus System::SingleStep() {
 }
 
 System::ResultStatus System::Load(EmuWindow& emu_window, const std::string& filepath) {
-    app_loader = Loader::GetLoader(std::make_shared<FileSys::RealVfsFile>(filepath));
+    app_loader = Loader::GetLoader(virtual_filesystem->OpenFile(filepath, FileSys::Mode::Read));
 
     if (!app_loader) {
         LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
@@ -174,6 +174,10 @@ System::ResultStatus System::Init(EmuWindow& emu_window) {
 
     CoreTiming::Init();
 
+    // Create a default fs if one doesn't already exist.
+    if (virtual_filesystem == nullptr)
+        virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
+
     current_process = Kernel::Process::Create("main");
 
     cpu_barrier = std::make_shared<CpuBarrier>();
@@ -186,7 +190,7 @@ System::ResultStatus System::Init(EmuWindow& emu_window) {
     service_manager = std::make_shared<Service::SM::ServiceManager>();
 
     Kernel::Init();
-    Service::Init(service_manager);
+    Service::Init(service_manager, virtual_filesystem);
     GDBStub::Init();
 
     renderer = VideoCore::CreateRenderer(emu_window);

+ 12 - 0
src/core/core.h

@@ -17,6 +17,8 @@
 #include "core/memory.h"
 #include "core/perf_stats.h"
 #include "core/telemetry_session.h"
+#include "file_sys/vfs_real.h"
+#include "hle/service/filesystem/filesystem.h"
 #include "video_core/debug_utils/debug_utils.h"
 #include "video_core/gpu.h"
 
@@ -211,6 +213,14 @@ public:
         return debug_context;
     }
 
+    void SetFilesystem(FileSys::VirtualFilesystem vfs) {
+        virtual_filesystem = std::move(vfs);
+    }
+
+    FileSys::VirtualFilesystem GetFilesystem() const {
+        return virtual_filesystem;
+    }
+
 private:
     System();
 
@@ -225,6 +235,8 @@ private:
      */
     ResultStatus Init(EmuWindow& emu_window);
 
+    /// RealVfsFilesystem instance
+    FileSys::VirtualFilesystem virtual_filesystem;
     /// AppLoader used to load the current executing application
     std::unique_ptr<Loader::AppLoader> app_loader;
     std::unique_ptr<VideoCore::RendererBase> renderer;

+ 1 - 0
src/core/file_sys/card_image.cpp

@@ -5,6 +5,7 @@
 #include <array>
 #include <string>
 #include <core/loader/loader.h>
+#include "common/logging/log.h"
 #include "core/file_sys/card_image.h"
 #include "core/file_sys/partition_filesystem.h"
 #include "core/file_sys/vfs_offset.h"

+ 4 - 0
src/core/file_sys/content_archive.cpp

@@ -170,6 +170,10 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting
 }
 
 NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
+    if (file == nullptr) {
+        status = Loader::ResultStatus::ErrorInvalidFormat;
+        return;
+    }
     if (sizeof(NCAHeader) != file->ReadObject(&header))
         LOG_ERROR(Loader, "File reader errored out during header read.");
 

+ 1 - 0
src/core/file_sys/content_archive.h

@@ -12,6 +12,7 @@
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "common/swap.h"
+#include "control_metadata.h"
 #include "core/crypto/key_manager.h"
 #include "core/file_sys/partition_filesystem.h"
 #include "core/loader/loader.h"

+ 7 - 0
src/core/file_sys/control_metadata.h

@@ -62,6 +62,13 @@ enum class Language : u8 {
     Chinese = 14,
 };
 
+static constexpr std::array<const char*, 15> LANGUAGE_NAMES = {
+    "AmericanEnglish", "BritishEnglish", "Japanese",
+    "French",          "German",         "LatinAmericanSpanish",
+    "Spanish",         "Italian",        "Dutch",
+    "CanadianFrench",  "Portugese",      "Russian",
+    "Korean",          "Taiwanese",      "Chinese"};
+
 // A class representing the format used by NX metadata files, typically named Control.nacp.
 // These store application name, dev name, title id, and other miscellaneous data.
 class NACP {

+ 9 - 3
src/core/file_sys/directory.h

@@ -4,8 +4,9 @@
 
 #pragma once
 
-#include <array>
 #include <cstddef>
+#include <iterator>
+#include <string_view>
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 
@@ -21,9 +22,14 @@ enum EntryType : u8 {
 
 // Structure of a directory entry, from
 // http://switchbrew.org/index.php?title=Filesystem_services#DirectoryEntry
-const size_t FILENAME_LENGTH = 0x300;
 struct Entry {
-    char filename[FILENAME_LENGTH];
+    Entry(std::string_view view, EntryType entry_type, u64 entry_size)
+        : type{entry_type}, file_size{entry_size} {
+        const size_t copy_size = view.copy(filename, std::size(filename) - 1);
+        filename[copy_size] = '\0';
+    }
+
+    char filename[0x300];
     INSERT_PADDING_BYTES(4);
     EntryType type;
     INSERT_PADDING_BYTES(3);

+ 1 - 0
src/core/file_sys/savedata_factory.h

@@ -7,6 +7,7 @@
 #include <memory>
 #include <string>
 #include "common/common_types.h"
+#include "common/swap.h"
 #include "core/hle/result.h"
 
 namespace FileSys {

+ 148 - 0
src/core/file_sys/vfs.cpp

@@ -4,12 +4,160 @@
 
 #include <algorithm>
 #include <numeric>
+#include <string>
+#include "common/common_paths.h"
 #include "common/file_util.h"
 #include "common/logging/backend.h"
 #include "core/file_sys/vfs.h"
 
 namespace FileSys {
 
+VfsFilesystem::VfsFilesystem(VirtualDir root_) : root(std::move(root_)) {}
+
+VfsFilesystem::~VfsFilesystem() = default;
+
+std::string VfsFilesystem::GetName() const {
+    return root->GetName();
+}
+
+bool VfsFilesystem::IsReadable() const {
+    return root->IsReadable();
+}
+
+bool VfsFilesystem::IsWritable() const {
+    return root->IsWritable();
+}
+
+VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const {
+    const auto path = FileUtil::SanitizePath(path_);
+    if (root->GetFileRelative(path) != nullptr)
+        return VfsEntryType::File;
+    if (root->GetDirectoryRelative(path) != nullptr)
+        return VfsEntryType::Directory;
+
+    return VfsEntryType::None;
+}
+
+VirtualFile VfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
+    const auto path = FileUtil::SanitizePath(path_);
+    return root->GetFileRelative(path);
+}
+
+VirtualFile VfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
+    const auto path = FileUtil::SanitizePath(path_);
+    return root->CreateFileRelative(path);
+}
+
+VirtualFile VfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) {
+    const auto old_path = FileUtil::SanitizePath(old_path_);
+    const auto new_path = FileUtil::SanitizePath(new_path_);
+
+    // VfsDirectory impls are only required to implement copy across the current directory.
+    if (FileUtil::GetParentPath(old_path) == FileUtil::GetParentPath(new_path)) {
+        if (!root->Copy(FileUtil::GetFilename(old_path), FileUtil::GetFilename(new_path)))
+            return nullptr;
+        return OpenFile(new_path, Mode::ReadWrite);
+    }
+
+    // Do it using RawCopy. Non-default impls are encouraged to optimize this.
+    const auto old_file = OpenFile(old_path, Mode::Read);
+    if (old_file == nullptr)
+        return nullptr;
+    auto new_file = OpenFile(new_path, Mode::Read);
+    if (new_file != nullptr)
+        return nullptr;
+    new_file = CreateFile(new_path, Mode::Write);
+    if (new_file == nullptr)
+        return nullptr;
+    if (!VfsRawCopy(old_file, new_file))
+        return nullptr;
+    return new_file;
+}
+
+VirtualFile VfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) {
+    const auto old_path = FileUtil::SanitizePath(old_path_);
+    const auto new_path = FileUtil::SanitizePath(new_path_);
+
+    // Again, non-default impls are highly encouraged to provide a more optimized version of this.
+    auto out = CopyFile(old_path_, new_path_);
+    if (out == nullptr)
+        return nullptr;
+    if (DeleteFile(old_path))
+        return out;
+    return nullptr;
+}
+
+bool VfsFilesystem::DeleteFile(std::string_view path_) {
+    const auto path = FileUtil::SanitizePath(path_);
+    auto parent = OpenDirectory(FileUtil::GetParentPath(path), Mode::Write);
+    if (parent == nullptr)
+        return false;
+    return parent->DeleteFile(FileUtil::GetFilename(path));
+}
+
+VirtualDir VfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) {
+    const auto path = FileUtil::SanitizePath(path_);
+    return root->GetDirectoryRelative(path);
+}
+
+VirtualDir VfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) {
+    const auto path = FileUtil::SanitizePath(path_);
+    return root->CreateDirectoryRelative(path);
+}
+
+VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_view new_path_) {
+    const auto old_path = FileUtil::SanitizePath(old_path_);
+    const auto new_path = FileUtil::SanitizePath(new_path_);
+
+    // Non-default impls are highly encouraged to provide a more optimized version of this.
+    auto old_dir = OpenDirectory(old_path, Mode::Read);
+    if (old_dir == nullptr)
+        return nullptr;
+    auto new_dir = OpenDirectory(new_path, Mode::Read);
+    if (new_dir != nullptr)
+        return nullptr;
+    new_dir = CreateDirectory(new_path, Mode::Write);
+    if (new_dir == nullptr)
+        return nullptr;
+
+    for (const auto& file : old_dir->GetFiles()) {
+        const auto x =
+            CopyFile(old_path + DIR_SEP + file->GetName(), new_path + DIR_SEP + file->GetName());
+        if (x == nullptr)
+            return nullptr;
+    }
+
+    for (const auto& dir : old_dir->GetSubdirectories()) {
+        const auto x =
+            CopyDirectory(old_path + DIR_SEP + dir->GetName(), new_path + DIR_SEP + dir->GetName());
+        if (x == nullptr)
+            return nullptr;
+    }
+
+    return new_dir;
+}
+
+VirtualDir VfsFilesystem::MoveDirectory(std::string_view old_path_, std::string_view new_path_) {
+    const auto old_path = FileUtil::SanitizePath(old_path_);
+    const auto new_path = FileUtil::SanitizePath(new_path_);
+
+    // Non-default impls are highly encouraged to provide a more optimized version of this.
+    auto out = CopyDirectory(old_path_, new_path_);
+    if (out == nullptr)
+        return nullptr;
+    if (DeleteDirectory(old_path))
+        return out;
+    return nullptr;
+}
+
+bool VfsFilesystem::DeleteDirectory(std::string_view path_) {
+    const auto path = FileUtil::SanitizePath(path_);
+    auto parent = OpenDirectory(FileUtil::GetParentPath(path), Mode::Write);
+    if (parent == nullptr)
+        return false;
+    return parent->DeleteSubdirectoryRecursive(FileUtil::GetFilename(path));
+}
+
 VfsFile::~VfsFile() = default;
 
 std::string VfsFile::GetExtension() const {

+ 63 - 3
src/core/file_sys/vfs.h

@@ -11,14 +11,74 @@
 #include <vector>
 #include "boost/optional.hpp"
 #include "common/common_types.h"
+#include "core/file_sys/mode.h"
 
 namespace FileSys {
+
+struct VfsFilesystem;
 struct VfsFile;
 struct VfsDirectory;
 
-// Convenience typedefs to use VfsDirectory and VfsFile
-using VirtualDir = std::shared_ptr<FileSys::VfsDirectory>;
-using VirtualFile = std::shared_ptr<FileSys::VfsFile>;
+// Convenience typedefs to use Vfs* interfaces
+using VirtualFilesystem = std::shared_ptr<VfsFilesystem>;
+using VirtualDir = std::shared_ptr<VfsDirectory>;
+using VirtualFile = std::shared_ptr<VfsFile>;
+
+// An enumeration representing what can be at the end of a path in a VfsFilesystem
+enum class VfsEntryType {
+    None,
+    File,
+    Directory,
+};
+
+// A class representing an abstract filesystem. A default implementation given the root VirtualDir
+// is provided for convenience, but if the Vfs implementation has any additional state or
+// functionality, they will need to override.
+struct VfsFilesystem : NonCopyable {
+    VfsFilesystem(VirtualDir root);
+    virtual ~VfsFilesystem();
+
+    // Gets the friendly name for the filesystem.
+    virtual std::string GetName() const;
+
+    // Return whether or not the user has read permissions on this filesystem.
+    virtual bool IsReadable() const;
+    // Return whether or not the user has write permission on this filesystem.
+    virtual bool IsWritable() const;
+
+    // Determine if the entry at path is non-existant, a file, or a directory.
+    virtual VfsEntryType GetEntryType(std::string_view path) const;
+
+    // Opens the file with path relative to root. If it doesn't exist, returns nullptr.
+    virtual VirtualFile OpenFile(std::string_view path, Mode perms);
+    // Creates a new, empty file at path
+    virtual VirtualFile CreateFile(std::string_view path, Mode perms);
+    // Copies the file from old_path to new_path, returning the new file on success and nullptr on
+    // failure.
+    virtual VirtualFile CopyFile(std::string_view old_path, std::string_view new_path);
+    // Moves the file from old_path to new_path, returning the moved file on success and nullptr on
+    // failure.
+    virtual VirtualFile MoveFile(std::string_view old_path, std::string_view new_path);
+    // Deletes the file with path relative to root, returing true on success.
+    virtual bool DeleteFile(std::string_view path);
+
+    // Opens the directory with path relative to root. If it doesn't exist, returns nullptr.
+    virtual VirtualDir OpenDirectory(std::string_view path, Mode perms);
+    // Creates a new, empty directory at path
+    virtual VirtualDir CreateDirectory(std::string_view path, Mode perms);
+    // Copies the directory from old_path to new_path, returning the new directory on success and
+    // nullptr on failure.
+    virtual VirtualDir CopyDirectory(std::string_view old_path, std::string_view new_path);
+    // Moves the directory from old_path to new_path, returning the moved directory on success and
+    // nullptr on failure.
+    virtual VirtualDir MoveDirectory(std::string_view old_path, std::string_view new_path);
+    // Deletes the directory with path relative to root, returing true on success.
+    virtual bool DeleteDirectory(std::string_view path);
+
+protected:
+    // Root directory in default implementation.
+    VirtualDir root;
+};
 
 // A class representing a file in an abstract filesystem.
 struct VfsFile : NonCopyable {

+ 265 - 76
src/core/file_sys/vfs_real.cpp

@@ -6,7 +6,7 @@
 #include <cstddef>
 #include <iterator>
 #include <utility>
-
+#include "common/assert.h"
 #include "common/common_paths.h"
 #include "common/logging/log.h"
 #include "core/file_sys/vfs_real.h"
@@ -29,6 +29,8 @@ static std::string ModeFlagsToString(Mode mode) {
             mode_str = "a";
         else if (mode & Mode::Write)
             mode_str = "w";
+        else
+            UNREACHABLE_MSG("Invalid file open mode: {:02X}", static_cast<u8>(mode));
     }
 
     mode_str += "b";
@@ -36,8 +38,174 @@ static std::string ModeFlagsToString(Mode mode) {
     return mode_str;
 }
 
-RealVfsFile::RealVfsFile(const std::string& path_, Mode perms_)
-    : backing(path_, ModeFlagsToString(perms_).c_str()), path(path_),
+RealVfsFilesystem::RealVfsFilesystem() : VfsFilesystem(nullptr) {}
+
+std::string RealVfsFilesystem::GetName() const {
+    return "Real";
+}
+
+bool RealVfsFilesystem::IsReadable() const {
+    return true;
+}
+
+bool RealVfsFilesystem::IsWritable() const {
+    return true;
+}
+
+VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const {
+    const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
+    if (!FileUtil::Exists(path))
+        return VfsEntryType::None;
+    if (FileUtil::IsDirectory(path))
+        return VfsEntryType::Directory;
+
+    return VfsEntryType::File;
+}
+
+VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
+    const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
+    if (cache.find(path) != cache.end()) {
+        auto weak = cache[path];
+        if (!weak.expired()) {
+            return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, weak.lock(), path, perms));
+        }
+    }
+
+    if (!FileUtil::Exists(path) && (perms & Mode::WriteAppend) != 0)
+        FileUtil::CreateEmptyFile(path);
+
+    auto backing = std::make_shared<FileUtil::IOFile>(path, ModeFlagsToString(perms).c_str());
+    cache[path] = backing;
+
+    // Cannot use make_shared as RealVfsFile constructor is private
+    return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, backing, path, perms));
+}
+
+VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
+    const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
+    if (!FileUtil::Exists(path) && !FileUtil::CreateEmptyFile(path))
+        return nullptr;
+    return OpenFile(path, perms);
+}
+
+VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) {
+    const auto old_path =
+        FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault);
+    const auto new_path =
+        FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault);
+
+    if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) ||
+        FileUtil::IsDirectory(old_path) || !FileUtil::Copy(old_path, new_path))
+        return nullptr;
+    return OpenFile(new_path, Mode::ReadWrite);
+}
+
+VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) {
+    const auto old_path =
+        FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault);
+    const auto new_path =
+        FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault);
+
+    if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) ||
+        FileUtil::IsDirectory(old_path) || !FileUtil::Rename(old_path, new_path))
+        return nullptr;
+
+    if (cache.find(old_path) != cache.end()) {
+        auto cached = cache[old_path];
+        if (!cached.expired()) {
+            auto file = cached.lock();
+            file->Open(new_path, "r+b");
+            cache.erase(old_path);
+            cache[new_path] = file;
+        }
+    }
+    return OpenFile(new_path, Mode::ReadWrite);
+}
+
+bool RealVfsFilesystem::DeleteFile(std::string_view path_) {
+    const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
+    if (cache.find(path) != cache.end()) {
+        if (!cache[path].expired())
+            cache[path].lock()->Close();
+        cache.erase(path);
+    }
+    return FileUtil::Delete(path);
+}
+
+VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) {
+    const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
+    // Cannot use make_shared as RealVfsDirectory constructor is private
+    return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
+}
+
+VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) {
+    const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
+    if (!FileUtil::Exists(path) && !FileUtil::CreateDir(path))
+        return nullptr;
+    // Cannot use make_shared as RealVfsDirectory constructor is private
+    return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
+}
+
+VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_,
+                                            std::string_view new_path_) {
+    const auto old_path =
+        FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault);
+    const auto new_path =
+        FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault);
+    if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) ||
+        !FileUtil::IsDirectory(old_path))
+        return nullptr;
+    FileUtil::CopyDir(old_path, new_path);
+    return OpenDirectory(new_path, Mode::ReadWrite);
+}
+
+VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_,
+                                            std::string_view new_path_) {
+    const auto old_path =
+        FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault);
+    const auto new_path =
+        FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault);
+    if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) ||
+        FileUtil::IsDirectory(old_path) || !FileUtil::Rename(old_path, new_path))
+        return nullptr;
+
+    for (auto& kv : cache) {
+        // Path in cache starts with old_path
+        if (kv.first.rfind(old_path, 0) == 0) {
+            const auto file_old_path =
+                FileUtil::SanitizePath(kv.first, FileUtil::DirectorySeparator::PlatformDefault);
+            const auto file_new_path =
+                FileUtil::SanitizePath(new_path + DIR_SEP + kv.first.substr(old_path.size()),
+                                       FileUtil::DirectorySeparator::PlatformDefault);
+            auto cached = cache[file_old_path];
+            if (!cached.expired()) {
+                auto file = cached.lock();
+                file->Open(file_new_path, "r+b");
+                cache.erase(file_old_path);
+                cache[file_new_path] = file;
+            }
+        }
+    }
+
+    return OpenDirectory(new_path, Mode::ReadWrite);
+}
+
+bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) {
+    const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
+    for (auto& kv : cache) {
+        // Path in cache starts with old_path
+        if (kv.first.rfind(path, 0) == 0) {
+            if (!cache[kv.first].expired())
+                cache[kv.first].lock()->Close();
+            cache.erase(kv.first);
+        }
+    }
+    return FileUtil::DeleteDirRecursively(path);
+}
+
+RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FileUtil::IOFile> backing_,
+                         const std::string& path_, Mode perms_)
+    : base(base_), backing(std::move(backing_)), path(path_),
       parent_path(FileUtil::GetParentPath(path_)),
       path_components(FileUtil::SplitPathComponents(path_)),
       parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)),
@@ -48,15 +216,15 @@ std::string RealVfsFile::GetName() const {
 }
 
 size_t RealVfsFile::GetSize() const {
-    return backing.GetSize();
+    return backing->GetSize();
 }
 
 bool RealVfsFile::Resize(size_t new_size) {
-    return backing.Resize(new_size);
+    return backing->Resize(new_size);
 }
 
 std::shared_ptr<VfsDirectory> RealVfsFile::GetContainingDirectory() const {
-    return std::make_shared<RealVfsDirectory>(parent_path, perms);
+    return base.OpenDirectory(parent_path, perms);
 }
 
 bool RealVfsFile::IsWritable() const {
@@ -68,62 +236,118 @@ bool RealVfsFile::IsReadable() const {
 }
 
 size_t RealVfsFile::Read(u8* data, size_t length, size_t offset) const {
-    if (!backing.Seek(offset, SEEK_SET))
+    if (!backing->Seek(offset, SEEK_SET))
         return 0;
-    return backing.ReadBytes(data, length);
+    return backing->ReadBytes(data, length);
 }
 
 size_t RealVfsFile::Write(const u8* data, size_t length, size_t offset) {
-    if (!backing.Seek(offset, SEEK_SET))
+    if (!backing->Seek(offset, SEEK_SET))
         return 0;
-    return backing.WriteBytes(data, length);
+    return backing->WriteBytes(data, length);
 }
 
 bool RealVfsFile::Rename(std::string_view name) {
-    std::string name_str(name.begin(), name.end());
-    const auto out = FileUtil::Rename(GetName(), name_str);
+    return base.MoveFile(path, parent_path + DIR_SEP + std::string(name)) != nullptr;
+}
+
+bool RealVfsFile::Close() {
+    return backing->Close();
+}
 
-    path = (parent_path + DIR_SEP).append(name);
-    path_components = parent_components;
-    path_components.push_back(std::move(name_str));
-    backing = FileUtil::IOFile(path, ModeFlagsToString(perms).c_str());
+// TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if
+// constexpr' because there is a compile error in the branch not used.
+
+template <>
+std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>() const {
+    if (perms == Mode::Append)
+        return {};
+
+    std::vector<VirtualFile> out;
+    FileUtil::ForeachDirectoryEntry(
+        nullptr, path,
+        [&out, this](u64* entries_out, const std::string& directory, const std::string& filename) {
+            const std::string full_path = directory + DIR_SEP + filename;
+            if (!FileUtil::IsDirectory(full_path))
+                out.emplace_back(base.OpenFile(full_path, perms));
+            return true;
+        });
 
     return out;
 }
 
-bool RealVfsFile::Close() {
-    return backing.Close();
+template <>
+std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDirectory>() const {
+    if (perms == Mode::Append)
+        return {};
+
+    std::vector<VirtualDir> out;
+    FileUtil::ForeachDirectoryEntry(
+        nullptr, path,
+        [&out, this](u64* entries_out, const std::string& directory, const std::string& filename) {
+            const std::string full_path = directory + DIR_SEP + filename;
+            if (FileUtil::IsDirectory(full_path))
+                out.emplace_back(base.OpenDirectory(full_path, perms));
+            return true;
+        });
+
+    return out;
 }
 
-RealVfsDirectory::RealVfsDirectory(const std::string& path_, Mode perms_)
-    : path(FileUtil::RemoveTrailingSlash(path_)), parent_path(FileUtil::GetParentPath(path)),
+RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_, Mode perms_)
+    : base(base_), path(FileUtil::RemoveTrailingSlash(path_)),
+      parent_path(FileUtil::GetParentPath(path)),
       path_components(FileUtil::SplitPathComponents(path)),
       parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)),
       perms(perms_) {
     if (!FileUtil::Exists(path) && perms & Mode::WriteAppend)
         FileUtil::CreateDir(path);
+}
 
-    if (perms == Mode::Append)
-        return;
+std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const {
+    const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
+    if (!FileUtil::Exists(full_path))
+        return nullptr;
+    return base.OpenFile(full_path, perms);
+}
 
-    FileUtil::ForeachDirectoryEntry(
-        nullptr, path,
-        [this](u64* entries_out, const std::string& directory, const std::string& filename) {
-            std::string full_path = directory + DIR_SEP + filename;
-            if (FileUtil::IsDirectory(full_path))
-                subdirectories.emplace_back(std::make_shared<RealVfsDirectory>(full_path, perms));
-            else
-                files.emplace_back(std::make_shared<RealVfsFile>(full_path, perms));
-            return true;
-        });
+std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string_view path) const {
+    const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
+    if (!FileUtil::Exists(full_path))
+        return nullptr;
+    return base.OpenDirectory(full_path, perms);
+}
+
+std::shared_ptr<VfsFile> RealVfsDirectory::GetFile(std::string_view name) const {
+    return GetFileRelative(name);
+}
+
+std::shared_ptr<VfsDirectory> RealVfsDirectory::GetSubdirectory(std::string_view name) const {
+    return GetDirectoryRelative(name);
+}
+
+std::shared_ptr<VfsFile> RealVfsDirectory::CreateFileRelative(std::string_view path) {
+    const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
+    return base.CreateFile(full_path, perms);
+}
+
+std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateDirectoryRelative(std::string_view path) {
+    const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
+    auto parent = std::string(FileUtil::GetParentPath(full_path));
+    return base.CreateDirectory(full_path, perms);
+}
+
+bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
+    auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(name));
+    return base.DeleteDirectory(full_path);
 }
 
 std::vector<std::shared_ptr<VfsFile>> RealVfsDirectory::GetFiles() const {
-    return files;
+    return IterateEntries<RealVfsFile, VfsFile>();
 }
 
 std::vector<std::shared_ptr<VfsDirectory>> RealVfsDirectory::GetSubdirectories() const {
-    return subdirectories;
+    return IterateEntries<RealVfsDirectory, VfsDirectory>();
 }
 
 bool RealVfsDirectory::IsWritable() const {
@@ -142,57 +366,32 @@ std::shared_ptr<VfsDirectory> RealVfsDirectory::GetParentDirectory() const {
     if (path_components.size() <= 1)
         return nullptr;
 
-    return std::make_shared<RealVfsDirectory>(parent_path, perms);
+    return base.OpenDirectory(parent_path, perms);
 }
 
 std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateSubdirectory(std::string_view name) {
     const std::string subdir_path = (path + DIR_SEP).append(name);
-
-    if (!FileUtil::CreateDir(subdir_path)) {
-        return nullptr;
-    }
-
-    subdirectories.emplace_back(std::make_shared<RealVfsDirectory>(subdir_path, perms));
-    return subdirectories.back();
+    return base.CreateDirectory(subdir_path, perms);
 }
 
 std::shared_ptr<VfsFile> RealVfsDirectory::CreateFile(std::string_view name) {
     const std::string file_path = (path + DIR_SEP).append(name);
-
-    if (!FileUtil::CreateEmptyFile(file_path)) {
-        return nullptr;
-    }
-
-    files.emplace_back(std::make_shared<RealVfsFile>(file_path, perms));
-    return files.back();
+    return base.CreateFile(file_path, perms);
 }
 
 bool RealVfsDirectory::DeleteSubdirectory(std::string_view name) {
     const std::string subdir_path = (path + DIR_SEP).append(name);
-
-    return FileUtil::DeleteDirRecursively(subdir_path);
+    return base.DeleteDirectory(subdir_path);
 }
 
 bool RealVfsDirectory::DeleteFile(std::string_view name) {
-    const auto file = GetFile(name);
-
-    if (file == nullptr) {
-        return false;
-    }
-
-    files.erase(std::find(files.begin(), files.end(), file));
-
-    auto real_file = std::static_pointer_cast<RealVfsFile>(file);
-    real_file->Close();
-
     const std::string file_path = (path + DIR_SEP).append(name);
-    return FileUtil::Delete(file_path);
+    return base.DeleteFile(file_path);
 }
 
 bool RealVfsDirectory::Rename(std::string_view name) {
     const std::string new_name = (parent_path + DIR_SEP).append(name);
-
-    return FileUtil::Rename(path, new_name);
+    return base.MoveFile(path, new_name) != nullptr;
 }
 
 std::string RealVfsDirectory::GetFullPath() const {
@@ -202,16 +401,6 @@ std::string RealVfsDirectory::GetFullPath() const {
 }
 
 bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
-    const auto iter = std::find(files.begin(), files.end(), file);
-    if (iter == files.end())
-        return false;
-
-    const std::ptrdiff_t offset = std::distance(files.begin(), iter);
-    files[offset] = files.back();
-    files.pop_back();
-
-    subdirectories.emplace_back(std::move(dir));
-
-    return true;
+    return false;
 }
 } // namespace FileSys

+ 48 - 8
src/core/file_sys/vfs_real.h

@@ -6,18 +6,45 @@
 
 #include <string_view>
 
+#include <boost/container/flat_map.hpp>
 #include "common/file_util.h"
 #include "core/file_sys/mode.h"
 #include "core/file_sys/vfs.h"
 
 namespace FileSys {
 
+class RealVfsFilesystem : public VfsFilesystem {
+public:
+    RealVfsFilesystem();
+
+    std::string GetName() const override;
+    bool IsReadable() const override;
+    bool IsWritable() const override;
+    VfsEntryType GetEntryType(std::string_view path) const override;
+    VirtualFile OpenFile(std::string_view path, Mode perms = Mode::Read) override;
+    VirtualFile CreateFile(std::string_view path, Mode perms = Mode::ReadWrite) override;
+    VirtualFile CopyFile(std::string_view old_path, std::string_view new_path) override;
+    VirtualFile MoveFile(std::string_view old_path, std::string_view new_path) override;
+    bool DeleteFile(std::string_view path) override;
+    VirtualDir OpenDirectory(std::string_view path, Mode perms = Mode::Read) override;
+    VirtualDir CreateDirectory(std::string_view path, Mode perms = Mode::ReadWrite) override;
+    VirtualDir CopyDirectory(std::string_view old_path, std::string_view new_path) override;
+    VirtualDir MoveDirectory(std::string_view old_path, std::string_view new_path) override;
+    bool DeleteDirectory(std::string_view path) override;
+
+private:
+    boost::container::flat_map<std::string, std::weak_ptr<FileUtil::IOFile>> cache;
+};
+
 // An implmentation of VfsFile that represents a file on the user's computer.
-struct RealVfsFile : public VfsFile {
-    friend struct RealVfsDirectory;
+class RealVfsFile : public VfsFile {
+    friend class RealVfsDirectory;
+    friend class RealVfsFilesystem;
 
-    RealVfsFile(const std::string& name, Mode perms = Mode::Read);
+    RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<FileUtil::IOFile> backing,
+                const std::string& path, Mode perms = Mode::Read);
 
+public:
     std::string GetName() const override;
     size_t GetSize() const override;
     bool Resize(size_t new_size) override;
@@ -31,7 +58,8 @@ struct RealVfsFile : public VfsFile {
 private:
     bool Close();
 
-    FileUtil::IOFile backing;
+    RealVfsFilesystem& base;
+    std::shared_ptr<FileUtil::IOFile> backing;
     std::string path;
     std::string parent_path;
     std::vector<std::string> path_components;
@@ -40,9 +68,19 @@ private:
 };
 
 // An implementation of VfsDirectory that represents a directory on the user's computer.
-struct RealVfsDirectory : public VfsDirectory {
-    RealVfsDirectory(const std::string& path, Mode perms = Mode::Read);
+class RealVfsDirectory : public VfsDirectory {
+    friend class RealVfsFilesystem;
 
+    RealVfsDirectory(RealVfsFilesystem& base, const std::string& path, Mode perms = Mode::Read);
+
+public:
+    std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const override;
+    std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const override;
+    std::shared_ptr<VfsFile> GetFile(std::string_view name) const override;
+    std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const override;
+    std::shared_ptr<VfsFile> CreateFileRelative(std::string_view path) override;
+    std::shared_ptr<VfsDirectory> CreateDirectoryRelative(std::string_view path) override;
+    bool DeleteSubdirectoryRecursive(std::string_view name) override;
     std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
     std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
     bool IsWritable() const override;
@@ -60,13 +98,15 @@ protected:
     bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
 
 private:
+    template <typename T, typename R>
+    std::vector<std::shared_ptr<R>> IterateEntries() const;
+
+    RealVfsFilesystem& base;
     std::string path;
     std::string parent_path;
     std::vector<std::string> path_components;
     std::vector<std::string> parent_components;
     Mode perms;
-    std::vector<std::shared_ptr<VfsFile>> files;
-    std::vector<std::shared_ptr<VfsDirectory>> subdirectories;
 };
 
 } // namespace FileSys

+ 1 - 1
src/core/hle/kernel/hle_ipc.cpp

@@ -201,7 +201,7 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdb
     return RESULT_SUCCESS;
 }
 
-ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(Thread& thread) {
+ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread) {
     std::array<u32, IPC::COMMAND_BUFFER_LENGTH> dst_cmdbuf;
     Memory::ReadBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(),
                       dst_cmdbuf.size() * sizeof(u32));

+ 1 - 1
src/core/hle/kernel/hle_ipc.h

@@ -132,7 +132,7 @@ public:
     ResultCode PopulateFromIncomingCommandBuffer(u32_le* src_cmdbuf, Process& src_process,
                                                  HandleTable& src_table);
     /// Writes data from this context back to the requesting process/thread.
-    ResultCode WriteToOutgoingCommandBuffer(Thread& thread);
+    ResultCode WriteToOutgoingCommandBuffer(const Thread& thread);
 
     u32_le GetCommand() const {
         return command;

+ 22 - 1
src/core/hle/service/acc/acc.cpp

@@ -38,7 +38,7 @@ public:
             {0, &IProfile::Get, "Get"},
             {1, &IProfile::GetBase, "GetBase"},
             {10, nullptr, "GetImageSize"},
-            {11, nullptr, "LoadImage"},
+            {11, &IProfile::LoadImage, "LoadImage"},
         };
         RegisterHandlers(functions);
     }
@@ -72,6 +72,27 @@ private:
         }
     }
 
+    void LoadImage(Kernel::HLERequestContext& ctx) {
+        LOG_WARNING(Service_ACC, "(STUBBED) called");
+        // smallest jpeg https://github.com/mathiasbynens/small/blob/master/jpeg.jpg
+        // TODO(mailwl): load actual profile image from disk, width 256px, max size 0x20000
+        const u32 jpeg_size = 107;
+        static const std::array<u8, jpeg_size> jpeg{
+            0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03,
+            0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04,
+            0x08, 0x06, 0x06, 0x05, 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a,
+            0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e, 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f,
+            0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13, 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10,
+            0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x11, 0x00,
+            0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01,
+            0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
+        };
+        ctx.WriteBuffer(jpeg.data(), jpeg_size);
+        IPC::ResponseBuilder rb{ctx, 3};
+        rb.Push(RESULT_SUCCESS);
+        rb.Push<u32>(jpeg_size);
+    }
+
     ProfileManager& profile_manager;
     UUID user_id; ///< The user id this profile refers to.
 };

+ 8 - 1
src/core/hle/service/am/am.cpp

@@ -136,7 +136,7 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
         {16, &ISelfController::SetOutOfFocusSuspendingEnabled, "SetOutOfFocusSuspendingEnabled"},
         {17, nullptr, "SetControllerFirmwareUpdateSection"},
         {18, nullptr, "SetRequiresCaptureButtonShortPressedMessage"},
-        {19, nullptr, "SetScreenShotImageOrientation"},
+        {19, &ISelfController::SetScreenShotImageOrientation, "SetScreenShotImageOrientation"},
         {20, nullptr, "SetDesirableKeyboardLayout"},
         {40, &ISelfController::CreateManagedDisplayLayer, "CreateManagedDisplayLayer"},
         {41, nullptr, "IsSystemBufferSharingEnabled"},
@@ -254,6 +254,13 @@ void ISelfController::GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext&
     LOG_WARNING(Service_AM, "(STUBBED) called");
 }
 
+void ISelfController::SetScreenShotImageOrientation(Kernel::HLERequestContext& ctx) {
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(RESULT_SUCCESS);
+
+    LOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
 void ISelfController::CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx) {
     // TODO(Subv): Find out how AM determines the display to use, for now just create the layer
     // in the Default display.

+ 1 - 0
src/core/hle/service/am/am.h

@@ -83,6 +83,7 @@ private:
     void LockExit(Kernel::HLERequestContext& ctx);
     void UnlockExit(Kernel::HLERequestContext& ctx);
     void GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& ctx);
+    void SetScreenShotImageOrientation(Kernel::HLERequestContext& ctx);
     void CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx);
     void SetScreenShotPermission(Kernel::HLERequestContext& ctx);
     void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx);

+ 8 - 8
src/core/hle/service/filesystem/filesystem.cpp

@@ -59,7 +59,7 @@ ResultCode VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64
 ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) const {
     std::string path(FileUtil::SanitizePath(path_));
     auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
-    if (path == "/" || path == "\\") {
+    if (path.empty()) {
         // TODO(DarkLordZach): Why do games call this and what should it do? Works as is but...
         return RESULT_SUCCESS;
     }
@@ -281,15 +281,15 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() {
     return sdmc_factory->Open();
 }
 
-void RegisterFileSystems() {
+void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) {
     romfs_factory = nullptr;
     save_data_factory = nullptr;
     sdmc_factory = nullptr;
 
-    auto nand_directory = std::make_shared<FileSys::RealVfsDirectory>(
-        FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), FileSys::Mode::ReadWrite);
-    auto sd_directory = std::make_shared<FileSys::RealVfsDirectory>(
-        FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), FileSys::Mode::ReadWrite);
+    auto nand_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir),
+                                             FileSys::Mode::ReadWrite);
+    auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir),
+                                           FileSys::Mode::ReadWrite);
 
     auto savedata = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory));
     save_data_factory = std::move(savedata);
@@ -298,8 +298,8 @@ void RegisterFileSystems() {
     sdmc_factory = std::move(sdcard);
 }
 
-void InstallInterfaces(SM::ServiceManager& service_manager) {
-    RegisterFileSystems();
+void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs) {
+    RegisterFileSystems(vfs);
     std::make_shared<FSP_LDR>()->InstallAsService(service_manager);
     std::make_shared<FSP_PR>()->InstallAsService(service_manager);
     std::make_shared<FSP_SRV>()->InstallAsService(service_manager);

+ 1 - 1
src/core/hle/service/filesystem/filesystem.h

@@ -36,7 +36,7 @@ ResultVal<FileSys::VirtualDir> OpenSDMC();
 // ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenBIS();
 
 /// Registers all Filesystem services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs);
 
 // A class that wraps a VfsDirectory with methods that return ResultVal and ResultCode instead of
 // pointers and booleans. This makes using a VfsDirectory with switch services much easier and

+ 3 - 6
src/core/hle/service/filesystem/fsp_srv.cpp

@@ -193,13 +193,10 @@ private:
 template <typename T>
 static void BuildEntryIndex(std::vector<FileSys::Entry>& entries, const std::vector<T>& new_data,
                             FileSys::EntryType type) {
+    entries.reserve(entries.size() + new_data.size());
+
     for (const auto& new_entry : new_data) {
-        FileSys::Entry entry;
-        entry.filename[0] = '\0';
-        std::strncat(entry.filename, new_entry->GetName().c_str(), FileSys::FILENAME_LENGTH - 1);
-        entry.type = type;
-        entry.file_size = new_entry->GetSize();
-        entries.emplace_back(std::move(entry));
+        entries.emplace_back(new_entry->GetName(), type, new_entry->GetSize());
     }
 }
 

+ 1 - 1
src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp

@@ -147,7 +147,7 @@ u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& outp
     }
     params.fence_out.id = 0;
     params.fence_out.value = 0;
-    std::memcpy(output.data(), &params, output.size());
+    std::memcpy(output.data(), &params, sizeof(IoctlSubmitGpfifo));
     return 0;
 }
 

+ 3 - 4
src/core/hle/service/nvdrv/nvdrv.cpp

@@ -16,19 +16,18 @@
 #include "core/hle/service/nvdrv/interface.h"
 #include "core/hle/service/nvdrv/nvdrv.h"
 #include "core/hle/service/nvdrv/nvmemp.h"
+#include "core/hle/service/nvflinger/nvflinger.h"
 
 namespace Service::Nvidia {
 
-std::weak_ptr<Module> nvdrv;
-
-void InstallInterfaces(SM::ServiceManager& service_manager) {
+void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger) {
     auto module_ = std::make_shared<Module>();
     std::make_shared<NVDRV>(module_, "nvdrv")->InstallAsService(service_manager);
     std::make_shared<NVDRV>(module_, "nvdrv:a")->InstallAsService(service_manager);
     std::make_shared<NVDRV>(module_, "nvdrv:s")->InstallAsService(service_manager);
     std::make_shared<NVDRV>(module_, "nvdrv:t")->InstallAsService(service_manager);
     std::make_shared<NVMEMP>()->InstallAsService(service_manager);
-    nvdrv = module_;
+    nvflinger.SetNVDrvInstance(module_);
 }
 
 Module::Module() {

+ 5 - 3
src/core/hle/service/nvdrv/nvdrv.h

@@ -10,6 +10,10 @@
 #include "common/common_types.h"
 #include "core/hle/service/service.h"
 
+namespace Service::NVFlinger {
+class NVFlinger;
+}
+
 namespace Service::Nvidia {
 
 namespace Devices {
@@ -56,8 +60,6 @@ private:
 };
 
 /// Registers all NVDRV services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
-
-extern std::weak_ptr<Module> nvdrv;
+void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger);
 
 } // namespace Service::Nvidia

+ 1 - 1
src/core/hle/service/nvflinger/buffer_queue.cpp

@@ -16,7 +16,7 @@ BufferQueue::BufferQueue(u32 id, u64 layer_id) : id(id), layer_id(layer_id) {
         Kernel::Event::Create(Kernel::ResetType::Sticky, "BufferQueue NativeHandle");
 }
 
-void BufferQueue::SetPreallocatedBuffer(u32 slot, IGBPBuffer& igbp_buffer) {
+void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer) {
     Buffer buffer{};
     buffer.slot = slot;
     buffer.igbp_buffer = igbp_buffer;

+ 1 - 1
src/core/hle/service/nvflinger/buffer_queue.h

@@ -72,7 +72,7 @@ public:
         MathUtil::Rectangle<int> crop_rect;
     };
 
-    void SetPreallocatedBuffer(u32 slot, IGBPBuffer& buffer);
+    void SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer);
     boost::optional<u32> DequeueBuffer(u32 width, u32 height);
     const IGBPBuffer& RequestBuffer(u32 slot) const;
     void QueueBuffer(u32 slot, BufferTransformFlags transform,

+ 4 - 3
src/core/hle/service/nvflinger/nvflinger.cpp

@@ -46,6 +46,10 @@ NVFlinger::~NVFlinger() {
     CoreTiming::UnscheduleEvent(composition_event, 0);
 }
 
+void NVFlinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) {
+    nvdrv = std::move(instance);
+}
+
 u64 NVFlinger::OpenDisplay(std::string_view name) {
     LOG_WARNING(Service, "Opening display {}", name);
 
@@ -141,9 +145,6 @@ void NVFlinger::Compose() {
         auto& igbp_buffer = buffer->igbp_buffer;
 
         // Now send the buffer to the GPU for drawing.
-        auto nvdrv = Nvidia::nvdrv.lock();
-        ASSERT(nvdrv);
-
         // TODO(Subv): Support more than just disp0. The display device selection is probably based
         // on which display we're drawing (Default, Internal, External, etc)
         auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>("/dev/nvdisp_disp0");

+ 9 - 0
src/core/hle/service/nvflinger/nvflinger.h

@@ -16,6 +16,10 @@ namespace CoreTiming {
 struct EventType;
 }
 
+namespace Service::Nvidia {
+class Module;
+}
+
 namespace Service::NVFlinger {
 
 class BufferQueue;
@@ -44,6 +48,9 @@ public:
     NVFlinger();
     ~NVFlinger();
 
+    /// Sets the NVDrv module instance to use to send buffers to the GPU.
+    void SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance);
+
     /// Opens the specified display and returns the id.
     u64 OpenDisplay(std::string_view name);
 
@@ -70,6 +77,8 @@ private:
     /// Returns the layer identified by the specified id in the desired display.
     Layer& GetLayer(u64 display_id, u64 layer_id);
 
+    std::shared_ptr<Nvidia::Module> nvdrv;
+
     std::vector<Display> displays;
     std::vector<std::shared_ptr<BufferQueue>> buffer_queues;
 

+ 3 - 3
src/core/hle/service/service.cpp

@@ -198,7 +198,7 @@ void AddNamedPort(std::string name, SharedPtr<ClientPort> port) {
 }
 
 /// Initialize ServiceManager
-void Init(std::shared_ptr<SM::ServiceManager>& sm) {
+void Init(std::shared_ptr<SM::ServiceManager>& sm, const FileSys::VirtualFilesystem& rfs) {
     // NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it
     // here and pass it into the respective InstallInterfaces functions.
     auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>();
@@ -221,7 +221,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm) {
     EUPLD::InstallInterfaces(*sm);
     Fatal::InstallInterfaces(*sm);
     FGM::InstallInterfaces(*sm);
-    FileSystem::InstallInterfaces(*sm);
+    FileSystem::InstallInterfaces(*sm, rfs);
     Friend::InstallInterfaces(*sm);
     GRC::InstallInterfaces(*sm);
     HID::InstallInterfaces(*sm);
@@ -238,7 +238,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm) {
     NIFM::InstallInterfaces(*sm);
     NIM::InstallInterfaces(*sm);
     NS::InstallInterfaces(*sm);
-    Nvidia::InstallInterfaces(*sm);
+    Nvidia::InstallInterfaces(*sm, *nv_flinger);
     PCIe::InstallInterfaces(*sm);
     PCTL::InstallInterfaces(*sm);
     PCV::InstallInterfaces(*sm);

+ 6 - 1
src/core/hle/service/service.h

@@ -22,6 +22,10 @@ class ServerSession;
 class HLERequestContext;
 } // namespace Kernel
 
+namespace FileSys {
+struct VfsFilesystem;
+}
+
 namespace Service {
 
 namespace SM {
@@ -177,7 +181,8 @@ private:
 };
 
 /// Initialize ServiceManager
-void Init(std::shared_ptr<SM::ServiceManager>& sm);
+void Init(std::shared_ptr<SM::ServiceManager>& sm,
+          const std::shared_ptr<FileSys::VfsFilesystem>& vfs);
 
 /// Shutdown ServiceManager
 void Shutdown();

+ 66 - 2
src/core/loader/deconstructed_rom_directory.cpp

@@ -7,6 +7,7 @@
 #include "common/file_util.h"
 #include "common/logging/log.h"
 #include "core/file_sys/content_archive.h"
+#include "core/file_sys/control_metadata.h"
 #include "core/gdbstub/gdbstub.h"
 #include "core/hle/kernel/process.h"
 #include "core/hle/kernel/resource_limit.h"
@@ -17,8 +18,50 @@
 
 namespace Loader {
 
-AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file)
-    : AppLoader(std::move(file)) {}
+AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_)
+    : AppLoader(std::move(file_)) {
+    const auto dir = file->GetContainingDirectory();
+
+    // Icon
+    FileSys::VirtualFile icon_file = nullptr;
+    for (const auto& language : FileSys::LANGUAGE_NAMES) {
+        icon_file = dir->GetFile("icon_" + std::string(language) + ".dat");
+        if (icon_file != nullptr) {
+            icon_data = icon_file->ReadAllBytes();
+            break;
+        }
+    }
+
+    if (icon_data.empty()) {
+        // Any png, jpeg, or bmp file
+        const auto& files = dir->GetFiles();
+        const auto icon_iter =
+            std::find_if(files.begin(), files.end(), [](const FileSys::VirtualFile& file) {
+                return file->GetExtension() == "png" || file->GetExtension() == "jpg" ||
+                       file->GetExtension() == "bmp" || file->GetExtension() == "jpeg";
+            });
+        if (icon_iter != files.end())
+            icon_data = (*icon_iter)->ReadAllBytes();
+    }
+
+    // Metadata
+    FileSys::VirtualFile nacp_file = dir->GetFile("control.nacp");
+    if (nacp_file == nullptr) {
+        const auto& files = dir->GetFiles();
+        const auto nacp_iter =
+            std::find_if(files.begin(), files.end(), [](const FileSys::VirtualFile& file) {
+                return file->GetExtension() == "nacp";
+            });
+        if (nacp_iter != files.end())
+            nacp_file = *nacp_iter;
+    }
+
+    if (nacp_file != nullptr) {
+        FileSys::NACP nacp(nacp_file);
+        title_id = nacp.GetTitleId();
+        name = nacp.GetApplicationName();
+    }
+}
 
 AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(
     FileSys::VirtualDir directory)
@@ -105,4 +148,25 @@ ResultStatus AppLoader_DeconstructedRomDirectory::ReadRomFS(FileSys::VirtualFile
     return ResultStatus::Success;
 }
 
+ResultStatus AppLoader_DeconstructedRomDirectory::ReadIcon(std::vector<u8>& buffer) {
+    if (icon_data.empty())
+        return ResultStatus::ErrorNotUsed;
+    buffer = icon_data;
+    return ResultStatus::Success;
+}
+
+ResultStatus AppLoader_DeconstructedRomDirectory::ReadProgramId(u64& out_program_id) {
+    if (name.empty())
+        return ResultStatus::ErrorNotUsed;
+    out_program_id = title_id;
+    return ResultStatus::Success;
+}
+
+ResultStatus AppLoader_DeconstructedRomDirectory::ReadTitle(std::string& title) {
+    if (name.empty())
+        return ResultStatus::ErrorNotUsed;
+    title = name;
+    return ResultStatus::Success;
+}
+
 } // namespace Loader

+ 7 - 0
src/core/loader/deconstructed_rom_directory.h

@@ -39,11 +39,18 @@ public:
     ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
 
     ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
+    ResultStatus ReadIcon(std::vector<u8>& buffer) override;
+    ResultStatus ReadProgramId(u64& out_program_id) override;
+    ResultStatus ReadTitle(std::string& title) override;
 
 private:
     FileSys::ProgramMetadata metadata;
     FileSys::VirtualFile romfs;
     FileSys::VirtualDir dir;
+
+    std::vector<u8> icon_data;
+    std::string name;
+    u64 title_id{};
 };
 
 } // namespace Loader

+ 1 - 5
src/core/loader/loader.cpp

@@ -43,10 +43,6 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
     return FileType::Unknown;
 }
 
-FileType IdentifyFile(const std::string& file_name) {
-    return IdentifyFile(std::make_shared<FileSys::RealVfsFile>(file_name));
-}
-
 FileType GuessFromFilename(const std::string& name) {
     if (name == "main")
         return FileType::DeconstructedRomDirectory;
@@ -68,7 +64,7 @@ FileType GuessFromFilename(const std::string& name) {
     return FileType::Unknown;
 }
 
-const char* GetFileTypeString(FileType type) {
+std::string GetFileTypeString(FileType type) {
     switch (type) {
     case FileType::ELF:
         return "ELF";

+ 1 - 9
src/core/loader/loader.h

@@ -42,14 +42,6 @@ enum class FileType {
  */
 FileType IdentifyFile(FileSys::VirtualFile file);
 
-/**
- * Identifies the type of a bootable file based on the magic value in its header.
- * @param file_name path to file
- * @return FileType of file. Note: this will return FileType::Unknown if it is unable to determine
- * a filetype, and will never return FileType::Error.
- */
-FileType IdentifyFile(const std::string& file_name);
-
 /**
  * Guess the type of a bootable file from its name
  * @param name String name of bootable file
@@ -61,7 +53,7 @@ FileType GuessFromFilename(const std::string& name);
 /**
  * Convert a FileType into a string which can be displayed to the user.
  */
-const char* GetFileTypeString(FileType type);
+std::string GetFileTypeString(FileType type);
 
 /// Return type for functions in Loader namespace
 enum class ResultStatus {

+ 2 - 2
src/core/loader/nca.cpp

@@ -77,8 +77,8 @@ ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) {
 }
 
 ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) {
-    if (nca == nullptr)
-        return ResultStatus::ErrorNotLoaded;
+    if (nca == nullptr || nca->GetStatus() != ResultStatus::Success)
+        return ResultStatus::ErrorInvalidFormat;
     out_program_id = nca->GetTitleId();
     return ResultStatus::Success;
 }

+ 1 - 1
src/core/loader/nca.h

@@ -33,7 +33,6 @@ public:
     ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
 
     ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
-
     ResultStatus ReadProgramId(u64& out_program_id) override;
 
     ~AppLoader_NCA();
@@ -41,6 +40,7 @@ public:
 private:
     FileSys::ProgramMetadata metadata;
 
+    FileSys::NCAHeader header;
     std::unique_ptr<FileSys::NCA> nca;
     std::unique_ptr<AppLoader_DeconstructedRomDirectory> directory_loader;
 };

+ 32 - 1
src/core/loader/xci.cpp

@@ -26,7 +26,25 @@ namespace Loader {
 AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file)
     : AppLoader(file), xci(std::make_unique<FileSys::XCI>(file)),
       nca_loader(std::make_unique<AppLoader_NCA>(
-          xci->GetNCAFileByType(FileSys::NCAContentType::Program))) {}
+          xci->GetNCAFileByType(FileSys::NCAContentType::Program))) {
+    if (xci->GetStatus() != ResultStatus::Success)
+        return;
+    const auto control_nca = xci->GetNCAByType(FileSys::NCAContentType::Control);
+    if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success)
+        return;
+    const auto romfs = FileSys::ExtractRomFS(control_nca->GetRomFS());
+    if (romfs == nullptr)
+        return;
+    for (const auto& language : FileSys::LANGUAGE_NAMES) {
+        icon_file = romfs->GetFile("icon_" + std::string(language) + ".dat");
+        if (icon_file != nullptr)
+            break;
+    }
+    const auto nacp_raw = romfs->GetFile("control.nacp");
+    if (nacp_raw == nullptr)
+        return;
+    nacp_file = std::make_shared<FileSys::NACP>(nacp_raw);
+}
 
 AppLoader_XCI::~AppLoader_XCI() = default;
 
@@ -71,4 +89,17 @@ ResultStatus AppLoader_XCI::ReadProgramId(u64& out_program_id) {
     return nca_loader->ReadProgramId(out_program_id);
 }
 
+ResultStatus AppLoader_XCI::ReadIcon(std::vector<u8>& buffer) {
+    if (icon_file == nullptr)
+        return ResultStatus::ErrorInvalidFormat;
+    buffer = icon_file->ReadAllBytes();
+    return ResultStatus::Success;
+}
+
+ResultStatus AppLoader_XCI::ReadTitle(std::string& title) {
+    if (nacp_file == nullptr)
+        return ResultStatus::ErrorInvalidFormat;
+    title = nacp_file->GetApplicationName();
+    return ResultStatus::Success;
+}
 } // namespace Loader

+ 5 - 0
src/core/loader/xci.h

@@ -33,12 +33,17 @@ public:
 
     ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
     ResultStatus ReadProgramId(u64& out_program_id) override;
+    ResultStatus ReadIcon(std::vector<u8>& buffer) override;
+    ResultStatus ReadTitle(std::string& title) override;
 
 private:
     FileSys::ProgramMetadata metadata;
 
     std::unique_ptr<FileSys::XCI> xci;
     std::unique_ptr<AppLoader_NCA> nca_loader;
+
+    FileSys::VirtualFile icon_file;
+    std::shared_ptr<FileSys::NACP> nacp_file;
 };
 
 } // namespace Loader

+ 11 - 4
src/video_core/engines/maxwell_3d.cpp

@@ -23,12 +23,17 @@ Maxwell3D::Maxwell3D(VideoCore::RasterizerInterface& rasterizer, MemoryManager&
     : memory_manager(memory_manager), rasterizer{rasterizer}, macro_interpreter(*this) {}
 
 void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) {
-    auto macro_code = uploaded_macros.find(method);
+    // Reset the current macro.
+    executing_macro = 0;
+
     // The requested macro must have been uploaded already.
-    ASSERT_MSG(macro_code != uploaded_macros.end(), "Macro %08X was not uploaded", method);
+    auto macro_code = uploaded_macros.find(method);
+    if (macro_code == uploaded_macros.end()) {
+        LOG_ERROR(HW_GPU, "Macro {:04X} was not uploaded", method);
+        return;
+    }
 
-    // Reset the current macro and execute it.
-    executing_macro = 0;
+    // Execute the current macro.
     macro_interpreter.Execute(macro_code->second, std::move(parameters));
 }
 
@@ -238,6 +243,8 @@ void Maxwell3D::ProcessCBBind(Regs::ShaderStage stage) {
 
     auto& buffer = shader.const_buffers[bind_data.index];
 
+    ASSERT(bind_data.index < Regs::MaxConstBuffers);
+
     buffer.enabled = bind_data.valid.Value() != 0;
     buffer.index = bind_data.index;
     buffer.address = regs.const_buffer.BufferAddress();

+ 1 - 1
src/video_core/engines/maxwell_3d.h

@@ -44,7 +44,7 @@ public:
         static constexpr size_t MaxShaderProgram = 6;
         static constexpr size_t MaxShaderStage = 5;
         // Maximum number of const buffers per shader stage.
-        static constexpr size_t MaxConstBuffers = 16;
+        static constexpr size_t MaxConstBuffers = 18;
 
         enum class QueryMode : u32 {
             Write = 0,

+ 6 - 9
src/video_core/engines/shader_bytecode.h

@@ -78,6 +78,8 @@ union Attribute {
         // shader, and a tuple of (TessCoord.x, TessCoord.y, TessCoord.z, ~) when inside a Tess Eval
         // shader.
         TessCoordInstanceIDVertexID = 47,
+        // TODO(bunnei): Figure out what this is used for. Super Mario Odyssey uses this.
+        Unknown_63 = 63,
     };
 
     union {
@@ -254,20 +256,15 @@ union Instruction {
             BitField<56, 1, u64> invert_b;
         } lop32i;
 
-        float GetImm20_19() const {
-            float result{};
+        u32 GetImm20_19() const {
             u32 imm{static_cast<u32>(imm20_19)};
             imm <<= 12;
             imm |= negate_imm ? 0x80000000 : 0;
-            std::memcpy(&result, &imm, sizeof(imm));
-            return result;
+            return imm;
         }
 
-        float GetImm20_32() const {
-            float result{};
-            s32 imm{static_cast<s32>(imm20_32)};
-            std::memcpy(&result, &imm, sizeof(imm));
-            return result;
+        u32 GetImm20_32() const {
+            return static_cast<u32>(imm20_32);
         }
 
         s32 GetSignedImm20_20() const {

+ 1 - 0
src/video_core/gpu.cpp

@@ -42,6 +42,7 @@ u32 RenderTargetBytesPerPixel(RenderTargetFormat format) {
     case RenderTargetFormat::RGB10_A2_UNORM:
     case RenderTargetFormat::BGRA8_UNORM:
     case RenderTargetFormat::R32_FLOAT:
+    case RenderTargetFormat::R11G11B10_FLOAT:
         return 4;
     default:
         UNIMPLEMENTED_MSG("Unimplemented render target format {}", static_cast<u32>(format));

+ 1 - 0
src/video_core/gpu.h

@@ -34,6 +34,7 @@ enum class RenderTargetFormat : u32 {
     RG16_FLOAT = 0xDE,
     R11G11B10_FLOAT = 0xE0,
     R32_FLOAT = 0xE5,
+    B5G6R5_UNORM = 0xE8,
     R16_FLOAT = 0xF2,
     R8_UNORM = 0xF3,
 };

+ 14 - 9
src/video_core/renderer_opengl/gl_rasterizer.cpp

@@ -161,7 +161,7 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupVertexArrays(u8* array_ptr,
     // assume every shader uses them all.
     for (unsigned index = 0; index < 16; ++index) {
         auto& attrib = regs.vertex_attrib_format[index];
-        LOG_DEBUG(HW_GPU, "vertex attrib {}, count={}, size={}, type={}, offset={}, normalize={}",
+        LOG_TRACE(HW_GPU, "vertex attrib {}, count={}, size={}, type={}, offset={}, normalize={}",
                   index, attrib.ComponentCount(), attrib.SizeString(), attrib.TypeString(),
                   attrib.offset.Value(), attrib.IsNormalized());
 
@@ -324,11 +324,14 @@ std::pair<Surface, Surface> RasterizerOpenGL::ConfigureFramebuffers(bool using_c
                                                                     bool using_depth_fb) {
     const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
 
+    if (regs.rt[0].format == Tegra::RenderTargetFormat::NONE) {
+        LOG_ERROR(HW_GPU, "RenderTargetFormat is not configured");
+        using_color_fb = false;
+    }
+
     // TODO(bunnei): Implement this
     const bool has_stencil = false;
 
-    const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[0].GetRect()};
-
     const bool write_color_fb =
         state.color_mask.red_enabled == GL_TRUE || state.color_mask.green_enabled == GL_TRUE ||
         state.color_mask.blue_enabled == GL_TRUE || state.color_mask.alpha_enabled == GL_TRUE;
@@ -341,9 +344,10 @@ std::pair<Surface, Surface> RasterizerOpenGL::ConfigureFramebuffers(bool using_c
     Surface depth_surface;
     MathUtil::Rectangle<u32> surfaces_rect;
     std::tie(color_surface, depth_surface, surfaces_rect) =
-        res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb, viewport_rect);
+        res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb);
 
-    MathUtil::Rectangle<u32> draw_rect{
+    const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[0].GetRect()};
+    const MathUtil::Rectangle<u32> draw_rect{
         static_cast<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.left) + viewport_rect.left,
                                          surfaces_rect.left, surfaces_rect.right)), // Left
         static_cast<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.bottom) + viewport_rect.top,
@@ -659,7 +663,10 @@ u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, GLuint progr
         auto& buffer_draw_state =
             state.draw.const_buffers[static_cast<size_t>(stage)][used_buffer.GetIndex()];
 
-        ASSERT_MSG(buffer.enabled, "Attempted to upload disabled constbuffer");
+        if (!buffer.enabled) {
+            continue;
+        }
+
         buffer_draw_state.enabled = true;
         buffer_draw_state.bindpoint = current_bindpoint + bindpoint;
 
@@ -804,9 +811,7 @@ void RasterizerOpenGL::SyncClipCoef() {
 void RasterizerOpenGL::SyncCullMode() {
     const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
 
-    // TODO(bunnei): Enable the below once more things work - until then, this may hide regressions
-    // state.cull.enabled = regs.cull.enabled != 0;
-    state.cull.enabled = false;
+    state.cull.enabled = regs.cull.enabled != 0;
 
     if (state.cull.enabled) {
         state.cull.front_face = MaxwellToGL::FrontFace(regs.cull.front_face);

+ 53 - 29
src/video_core/renderer_opengl/gl_rasterizer_cache.cpp

@@ -109,6 +109,9 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
     {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
      true},                                                                                 // DXT45
     {GL_COMPRESSED_RED_RGTC1, GL_RED, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, true}, // DXN1
+    {GL_COMPRESSED_RG_RGTC2, GL_RG, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
+     true},                                                                     // DXN2UNORM
+    {GL_COMPRESSED_SIGNED_RG_RGTC2, GL_RG, GL_INT, ComponentType::SNorm, true}, // DXN2SNORM
     {GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
      true},                                                                    // BC7U
     {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false},        // ASTC_2D_4X4
@@ -180,36 +183,49 @@ MathUtil::Rectangle<u32> SurfaceParams::GetRect() const {
     return {0, actual_height, width, 0};
 }
 
+/// Returns true if the specified PixelFormat is a BCn format, e.g. DXT or DXN
+static bool IsFormatBCn(PixelFormat format) {
+    switch (format) {
+    case PixelFormat::DXT1:
+    case PixelFormat::DXT23:
+    case PixelFormat::DXT45:
+    case PixelFormat::DXN1:
+    case PixelFormat::DXN2SNORM:
+    case PixelFormat::DXN2UNORM:
+    case PixelFormat::BC7U:
+        return true;
+    }
+    return false;
+}
+
 template <bool morton_to_gl, PixelFormat format>
-void MortonCopy(u32 stride, u32 block_height, u32 height, u8* gl_buffer, Tegra::GPUVAddr addr) {
+void MortonCopy(u32 stride, u32 block_height, u32 height, std::vector<u8>& gl_buffer,
+                Tegra::GPUVAddr addr) {
     constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / CHAR_BIT;
     constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format);
     const auto& gpu = Core::System::GetInstance().GPU();
 
     if (morton_to_gl) {
-        if (SurfaceParams::GetFormatType(format) == SurfaceType::ColorTexture) {
-            auto data = Tegra::Texture::UnswizzleTexture(
-                *gpu.memory_manager->GpuToCpuAddress(addr),
-                SurfaceParams::TextureFormatFromPixelFormat(format), stride, height, block_height);
-            std::memcpy(gl_buffer, data.data(), data.size());
-        } else {
-            auto data = Tegra::Texture::UnswizzleDepthTexture(
-                *gpu.memory_manager->GpuToCpuAddress(addr),
-                SurfaceParams::DepthFormatFromPixelFormat(format), stride, height, block_height);
-            std::memcpy(gl_buffer, data.data(), data.size());
-        }
+        // With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
+        // pixel values.
+        const u32 tile_size{IsFormatBCn(format) ? 4U : 1U};
+        const std::vector<u8> data =
+            Tegra::Texture::UnswizzleTexture(*gpu.memory_manager->GpuToCpuAddress(addr), tile_size,
+                                             bytes_per_pixel, stride, height, block_height);
+        const size_t size_to_copy{std::min(gl_buffer.size(), data.size())};
+        gl_buffer.assign(data.begin(), data.begin() + size_to_copy);
     } else {
         // TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should
         // check the configuration for this and perform more generic un/swizzle
         LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!");
         VideoCore::MortonCopyPixels128(
             stride, height, bytes_per_pixel, gl_bytes_per_pixel,
-            Memory::GetPointer(*gpu.memory_manager->GpuToCpuAddress(addr)), gl_buffer,
+            Memory::GetPointer(*gpu.memory_manager->GpuToCpuAddress(addr)), gl_buffer.data(),
             morton_to_gl);
     }
 }
 
-static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr),
+static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPUVAddr),
                             SurfaceParams::MaxPixelFormat>
     morton_to_gl_fns = {
         MortonCopy<true, PixelFormat::ABGR8>,        MortonCopy<true, PixelFormat::B5G6R5>,
@@ -218,6 +234,7 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr),
         MortonCopy<true, PixelFormat::R11FG11FB10F>, MortonCopy<true, PixelFormat::RGBA32UI>,
         MortonCopy<true, PixelFormat::DXT1>,         MortonCopy<true, PixelFormat::DXT23>,
         MortonCopy<true, PixelFormat::DXT45>,        MortonCopy<true, PixelFormat::DXN1>,
+        MortonCopy<true, PixelFormat::DXN2UNORM>,    MortonCopy<true, PixelFormat::DXN2SNORM>,
         MortonCopy<true, PixelFormat::BC7U>,         MortonCopy<true, PixelFormat::ASTC_2D_4X4>,
         MortonCopy<true, PixelFormat::G8R8>,         MortonCopy<true, PixelFormat::BGRA8>,
         MortonCopy<true, PixelFormat::RGBA32F>,      MortonCopy<true, PixelFormat::RG32F>,
@@ -231,7 +248,7 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr),
         MortonCopy<true, PixelFormat::Z32FS8>,
 };
 
-static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr),
+static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPUVAddr),
                             SurfaceParams::MaxPixelFormat>
     gl_to_morton_fns = {
         MortonCopy<false, PixelFormat::ABGR8>,
@@ -242,7 +259,10 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr),
         MortonCopy<false, PixelFormat::RGBA16F>,
         MortonCopy<false, PixelFormat::R11FG11FB10F>,
         MortonCopy<false, PixelFormat::RGBA32UI>,
-        // TODO(Subv): Swizzling DXT1/DXT23/DXT45/DXN1/BC7U/ASTC_2D_4X4 formats is not supported
+        // TODO(Subv): Swizzling DXT1/DXT23/DXT45/DXN1/DXN2/BC7U/ASTC_2D_4X4 formats is not
+        // supported
+        nullptr,
+        nullptr,
         nullptr,
         nullptr,
         nullptr,
@@ -447,22 +467,24 @@ MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64
 void CachedSurface::LoadGLBuffer() {
     ASSERT(params.type != SurfaceType::Fill);
 
-    u8* const texture_src_data = Memory::GetPointer(params.GetCpuAddr());
+    const u8* const texture_src_data = Memory::GetPointer(params.GetCpuAddr());
 
     ASSERT(texture_src_data);
 
-    gl_buffer.resize(params.width * params.height * GetGLBytesPerPixel(params.pixel_format));
+    const u32 bytes_per_pixel = GetGLBytesPerPixel(params.pixel_format);
+    const u32 copy_size = params.width * params.height * bytes_per_pixel;
 
     MICROPROFILE_SCOPE(OpenGL_SurfaceLoad);
 
-    if (!params.is_tiled) {
-        const u32 bytes_per_pixel{params.GetFormatBpp() >> 3};
+    if (params.is_tiled) {
+        gl_buffer.resize(copy_size);
 
-        std::memcpy(gl_buffer.data(), texture_src_data,
-                    bytes_per_pixel * params.width * params.height);
-    } else {
         morton_to_gl_fns[static_cast<size_t>(params.pixel_format)](
-            params.width, params.block_height, params.height, gl_buffer.data(), params.addr);
+            params.width, params.block_height, params.height, gl_buffer, params.addr);
+    } else {
+        const u8* const texture_src_data_end = texture_src_data + copy_size;
+
+        gl_buffer.assign(texture_src_data, texture_src_data_end);
     }
 
     ConvertFormatAsNeeded_LoadGLBuffer(gl_buffer, params.pixel_format, params.width, params.height);
@@ -485,7 +507,7 @@ void CachedSurface::FlushGLBuffer() {
         std::memcpy(dst_buffer, gl_buffer.data(), params.size_in_bytes);
     } else {
         gl_to_morton_fns[static_cast<size_t>(params.pixel_format)](
-            params.width, params.block_height, params.height, gl_buffer.data(), params.addr);
+            params.width, params.block_height, params.height, gl_buffer, params.addr);
     }
 }
 
@@ -600,8 +622,8 @@ Surface RasterizerCacheOpenGL::GetTextureSurface(const Tegra::Texture::FullTextu
     return GetSurface(SurfaceParams::CreateForTexture(config));
 }
 
-SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces(
-    bool using_color_fb, bool using_depth_fb, const MathUtil::Rectangle<s32>& viewport) {
+SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces(bool using_color_fb,
+                                                                       bool using_depth_fb) {
     const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
 
     // TODO(bunnei): This is hard corded to use just the first render buffer
@@ -757,10 +779,12 @@ void RasterizerCacheOpenGL::FlushRegion(Tegra::GPUVAddr /*addr*/, size_t /*size*
 }
 
 void RasterizerCacheOpenGL::InvalidateRegion(Tegra::GPUVAddr addr, size_t size) {
-    for (const auto& pair : surface_cache) {
-        const auto& surface{pair.second};
+    for (auto iter = surface_cache.cbegin(); iter != surface_cache.cend();) {
+        const auto& surface{iter->second};
         const auto& params{surface->GetSurfaceParams()};
 
+        ++iter;
+
         if (params.IsOverlappingRegion(addr, size)) {
             UnregisterSurface(surface);
         }

+ 41 - 106
src/video_core/renderer_opengl/gl_rasterizer_cache.h

@@ -35,31 +35,33 @@ struct SurfaceParams {
         DXT23 = 9,
         DXT45 = 10,
         DXN1 = 11, // This is also known as BC4
-        BC7U = 12,
-        ASTC_2D_4X4 = 13,
-        G8R8 = 14,
-        BGRA8 = 15,
-        RGBA32F = 16,
-        RG32F = 17,
-        R32F = 18,
-        R16F = 19,
-        R16UNORM = 20,
-        RG16 = 21,
-        RG16F = 22,
-        RG16UI = 23,
-        RG16I = 24,
-        RG16S = 25,
-        RGB32F = 26,
-        SRGBA8 = 27,
+        DXN2UNORM = 12,
+        DXN2SNORM = 13,
+        BC7U = 14,
+        ASTC_2D_4X4 = 15,
+        G8R8 = 16,
+        BGRA8 = 17,
+        RGBA32F = 18,
+        RG32F = 19,
+        R32F = 20,
+        R16F = 21,
+        R16UNORM = 22,
+        RG16 = 23,
+        RG16F = 24,
+        RG16UI = 25,
+        RG16I = 26,
+        RG16S = 27,
+        RGB32F = 28,
+        SRGBA8 = 29,
 
         MaxColorFormat,
 
         // DepthStencil formats
-        Z24S8 = 28,
-        S8Z24 = 29,
-        Z32F = 30,
-        Z16 = 31,
-        Z32FS8 = 32,
+        Z24S8 = 30,
+        S8Z24 = 31,
+        Z32F = 32,
+        Z16 = 33,
+        Z32FS8 = 34,
 
         MaxDepthStencilFormat,
 
@@ -109,6 +111,8 @@ struct SurfaceParams {
             4, // DXT23
             4, // DXT45
             4, // DXN1
+            4, // DXN2UNORM
+            4, // DXN2SNORM
             4, // BC7U
             4, // ASTC_2D_4X4
             1, // G8R8
@@ -153,6 +157,8 @@ struct SurfaceParams {
             128, // DXT23
             128, // DXT45
             64,  // DXN1
+            128, // DXN2UNORM
+            128, // DXN2SNORM
             128, // BC7U
             32,  // ASTC_2D_4X4
             16,  // G8R8
@@ -221,6 +227,8 @@ struct SurfaceParams {
             return PixelFormat::RG32F;
         case Tegra::RenderTargetFormat::R11G11B10_FLOAT:
             return PixelFormat::R11FG11FB10F;
+        case Tegra::RenderTargetFormat::B5G6R5_UNORM:
+            return PixelFormat::B5G6R5;
         case Tegra::RenderTargetFormat::RGBA32_UINT:
             return PixelFormat::RGBA32UI;
         case Tegra::RenderTargetFormat::R8_UNORM:
@@ -303,6 +311,16 @@ struct SurfaceParams {
             return PixelFormat::DXT45;
         case Tegra::Texture::TextureFormat::DXN1:
             return PixelFormat::DXN1;
+        case Tegra::Texture::TextureFormat::DXN2:
+            switch (component_type) {
+            case Tegra::Texture::ComponentType::UNORM:
+                return PixelFormat::DXN2UNORM;
+            case Tegra::Texture::ComponentType::SNORM:
+                return PixelFormat::DXN2SNORM;
+            }
+            LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
+                         static_cast<u32>(component_type));
+            UNREACHABLE();
         case Tegra::Texture::TextureFormat::BC7U:
             return PixelFormat::BC7U;
         case Tegra::Texture::TextureFormat::ASTC_2D_4X4:
@@ -330,89 +348,6 @@ struct SurfaceParams {
         }
     }
 
-    static Tegra::Texture::TextureFormat TextureFormatFromPixelFormat(PixelFormat format) {
-        // TODO(Subv): Properly implement this
-        switch (format) {
-        case PixelFormat::ABGR8:
-        case PixelFormat::SRGBA8:
-            return Tegra::Texture::TextureFormat::A8R8G8B8;
-        case PixelFormat::B5G6R5:
-            return Tegra::Texture::TextureFormat::B5G6R5;
-        case PixelFormat::A2B10G10R10:
-            return Tegra::Texture::TextureFormat::A2B10G10R10;
-        case PixelFormat::A1B5G5R5:
-            return Tegra::Texture::TextureFormat::A1B5G5R5;
-        case PixelFormat::R8:
-            return Tegra::Texture::TextureFormat::R8;
-        case PixelFormat::G8R8:
-            return Tegra::Texture::TextureFormat::G8R8;
-        case PixelFormat::RGBA16F:
-            return Tegra::Texture::TextureFormat::R16_G16_B16_A16;
-        case PixelFormat::R11FG11FB10F:
-            return Tegra::Texture::TextureFormat::BF10GF11RF11;
-        case PixelFormat::RGBA32UI:
-            return Tegra::Texture::TextureFormat::R32_G32_B32_A32;
-        case PixelFormat::DXT1:
-            return Tegra::Texture::TextureFormat::DXT1;
-        case PixelFormat::DXT23:
-            return Tegra::Texture::TextureFormat::DXT23;
-        case PixelFormat::DXT45:
-            return Tegra::Texture::TextureFormat::DXT45;
-        case PixelFormat::DXN1:
-            return Tegra::Texture::TextureFormat::DXN1;
-        case PixelFormat::BC7U:
-            return Tegra::Texture::TextureFormat::BC7U;
-        case PixelFormat::ASTC_2D_4X4:
-            return Tegra::Texture::TextureFormat::ASTC_2D_4X4;
-        case PixelFormat::BGRA8:
-            // TODO(bunnei): This is fine for unswizzling (since we just need the right component
-            // sizes), but could be a bug if we used this function in different ways.
-            return Tegra::Texture::TextureFormat::A8R8G8B8;
-        case PixelFormat::RGBA32F:
-            return Tegra::Texture::TextureFormat::R32_G32_B32_A32;
-        case PixelFormat::RGB32F:
-            return Tegra::Texture::TextureFormat::R32_G32_B32;
-        case PixelFormat::RG32F:
-            return Tegra::Texture::TextureFormat::R32_G32;
-        case PixelFormat::R32F:
-            return Tegra::Texture::TextureFormat::R32;
-        case PixelFormat::R16F:
-        case PixelFormat::R16UNORM:
-            return Tegra::Texture::TextureFormat::R16;
-        case PixelFormat::Z32F:
-            return Tegra::Texture::TextureFormat::ZF32;
-        case PixelFormat::Z24S8:
-            return Tegra::Texture::TextureFormat::Z24S8;
-        case PixelFormat::RG16F:
-        case PixelFormat::RG16:
-        case PixelFormat::RG16UI:
-        case PixelFormat::RG16I:
-        case PixelFormat::RG16S:
-            return Tegra::Texture::TextureFormat::R16_G16;
-        default:
-            LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
-            UNREACHABLE();
-        }
-    }
-
-    static Tegra::DepthFormat DepthFormatFromPixelFormat(PixelFormat format) {
-        switch (format) {
-        case PixelFormat::S8Z24:
-            return Tegra::DepthFormat::S8_Z24_UNORM;
-        case PixelFormat::Z24S8:
-            return Tegra::DepthFormat::Z24_S8_UNORM;
-        case PixelFormat::Z32F:
-            return Tegra::DepthFormat::Z32_FLOAT;
-        case PixelFormat::Z16:
-            return Tegra::DepthFormat::Z16_UNORM;
-        case PixelFormat::Z32FS8:
-            return Tegra::DepthFormat::Z32_S8_X24_FLOAT;
-        default:
-            LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
-            UNREACHABLE();
-        }
-    }
-
     static ComponentType ComponentTypeFromTexture(Tegra::Texture::ComponentType type) {
         // TODO(Subv): Implement more component types
         switch (type) {
@@ -441,6 +376,7 @@ struct SurfaceParams {
         case Tegra::RenderTargetFormat::RGB10_A2_UNORM:
         case Tegra::RenderTargetFormat::R8_UNORM:
         case Tegra::RenderTargetFormat::RG16_UNORM:
+        case Tegra::RenderTargetFormat::B5G6R5_UNORM:
             return ComponentType::UNorm;
         case Tegra::RenderTargetFormat::RG16_SNORM:
             return ComponentType::SNorm;
@@ -612,8 +548,7 @@ public:
     Surface GetTextureSurface(const Tegra::Texture::FullTextureInfo& config);
 
     /// Get the color and depth surfaces based on the framebuffer configuration
-    SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb,
-                                                    const MathUtil::Rectangle<s32>& viewport);
+    SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb);
 
     /// Flushes the surface to Switch memory
     void FlushSurface(const Surface& surface);

+ 16 - 6
src/video_core/renderer_opengl/gl_shader_decompiler.cpp

@@ -507,6 +507,8 @@ private:
 
     /// Build the GLSL register list.
     void BuildRegisterList() {
+        regs.reserve(Register::NumRegisters);
+
         for (size_t index = 0; index < Register::NumRegisters; ++index) {
             regs.emplace_back(index, suffix);
         }
@@ -523,6 +525,11 @@ private:
             // shader.
             ASSERT(stage == Maxwell3D::Regs::ShaderStage::Vertex);
             return "vec4(0, 0, uintBitsToFloat(gl_InstanceID), uintBitsToFloat(gl_VertexID))";
+        case Attribute::Index::Unknown_63:
+            // TODO(bunnei): Figure out what this is used for. Super Mario Odyssey uses this.
+            LOG_CRITICAL(HW_GPU, "Unhandled input attribute Unknown_63");
+            UNREACHABLE();
+            break;
         default:
             const u32 index{static_cast<u32>(attribute) -
                             static_cast<u32>(Attribute::Index::Attribute_0)};
@@ -534,6 +541,8 @@ private:
             LOG_CRITICAL(HW_GPU, "Unhandled input attribute: {}", index);
             UNREACHABLE();
         }
+
+        return "vec4(0, 0, 0, 0)";
     }
 
     /// Generates code representing an output attribute register.
@@ -602,12 +611,12 @@ private:
 
     /// Generates code representing a 19-bit immediate value
     static std::string GetImmediate19(const Instruction& instr) {
-        return std::to_string(instr.alu.GetImm20_19());
+        return fmt::format("uintBitsToFloat({})", instr.alu.GetImm20_19());
     }
 
     /// Generates code representing a 32-bit immediate value
     static std::string GetImmediate32(const Instruction& instr) {
-        return std::to_string(instr.alu.GetImm20_32());
+        return fmt::format("uintBitsToFloat({})", instr.alu.GetImm20_32());
     }
 
     /// Generates code representing a texture sampler.
@@ -650,16 +659,17 @@ private:
      * @param instr Instruction to generate the if condition for.
      * @returns string containing the predicate condition.
      */
-    std::string GetPredicateCondition(u64 index, bool negate) const {
+    std::string GetPredicateCondition(u64 index, bool negate) {
         using Tegra::Shader::Pred;
         std::string variable;
 
         // Index 7 is used as an 'Always True' condition.
-        if (index == static_cast<u64>(Pred::UnusedIndex))
+        if (index == static_cast<u64>(Pred::UnusedIndex)) {
             variable = "true";
-        else
+        } else {
             variable = 'p' + std::to_string(index) + '_' + suffix;
-
+            declr_predicates.insert(variable);
+        }
         if (negate) {
             return "!(" + variable + ')';
         }

+ 5 - 1
src/video_core/renderer_opengl/gl_state.h

@@ -7,6 +7,10 @@
 #include <array>
 #include <glad/glad.h>
 
+#include "video_core/engines/maxwell_3d.h"
+
+using Regs = Tegra::Engines::Maxwell3D::Regs;
+
 namespace TextureUnits {
 
 struct TextureUnit {
@@ -120,7 +124,7 @@ public:
             GLuint bindpoint;
             GLuint ssbo;
         };
-        std::array<std::array<ConstBufferConfig, 16>, 5> const_buffers{};
+        std::array<std::array<ConstBufferConfig, Regs::MaxConstBuffers>, 5> const_buffers;
     } draw;
 
     struct {

+ 7 - 0
src/video_core/renderer_opengl/maxwell_to_gl.h

@@ -27,9 +27,11 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
     case Maxwell::VertexAttribute::Type::UnsignedNorm: {
 
         switch (attrib.size) {
+        case Maxwell::VertexAttribute::Size::Size_8_8:
         case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
             return GL_UNSIGNED_BYTE;
         case Maxwell::VertexAttribute::Size::Size_16_16:
+        case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
             return GL_UNSIGNED_SHORT;
         case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
             return GL_UNSIGNED_INT_2_10_10_10_REV;
@@ -43,6 +45,9 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
     case Maxwell::VertexAttribute::Type::SignedNorm: {
 
         switch (attrib.size) {
+        case Maxwell::VertexAttribute::Size::Size_32_32_32:
+            return GL_INT;
+        case Maxwell::VertexAttribute::Size::Size_8_8:
         case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
             return GL_BYTE;
         case Maxwell::VertexAttribute::Size::Size_16_16:
@@ -84,6 +89,8 @@ inline GLenum IndexFormat(Maxwell::IndexFormat index_format) {
 
 inline GLenum PrimitiveTopology(Maxwell::PrimitiveTopology topology) {
     switch (topology) {
+    case Maxwell::PrimitiveTopology::Points:
+        return GL_POINTS;
     case Maxwell::PrimitiveTopology::Triangles:
         return GL_TRIANGLES;
     case Maxwell::PrimitiveTopology::TriangleStrip:

+ 1 - 1
src/video_core/renderer_opengl/renderer_opengl.cpp

@@ -430,7 +430,7 @@ static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum
         break;
     case GL_DEBUG_SEVERITY_NOTIFICATION:
     case GL_DEBUG_SEVERITY_LOW:
-        LOG_DEBUG(Render_OpenGL, format, str_source, str_type, id, message);
+        LOG_TRACE(Render_OpenGL, format, str_source, str_type, id, message);
         break;
     }
 }

+ 6 - 80
src/video_core/textures/decoders.cpp

@@ -54,6 +54,7 @@ u32 BytesPerPixel(TextureFormat format) {
         return 8;
     case TextureFormat::DXT23:
     case TextureFormat::DXT45:
+    case TextureFormat::DXN2:
     case TextureFormat::BC7U:
         // In this case a 'pixel' actually refers to a 4x4 tile.
         return 16;
@@ -85,87 +86,11 @@ u32 BytesPerPixel(TextureFormat format) {
     }
 }
 
-static u32 DepthBytesPerPixel(DepthFormat format) {
-    switch (format) {
-    case DepthFormat::Z16_UNORM:
-        return 2;
-    case DepthFormat::S8_Z24_UNORM:
-    case DepthFormat::Z24_S8_UNORM:
-    case DepthFormat::Z32_FLOAT:
-        return 4;
-    case DepthFormat::Z32_S8_X24_FLOAT:
-        return 8;
-    default:
-        UNIMPLEMENTED_MSG("Format not implemented");
-        break;
-    }
-}
-
-std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height,
-                                 u32 block_height) {
-    u8* data = Memory::GetPointer(address);
-    u32 bytes_per_pixel = BytesPerPixel(format);
-
+std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size, u32 bytes_per_pixel, u32 width,
+                                 u32 height, u32 block_height) {
     std::vector<u8> unswizzled_data(width * height * bytes_per_pixel);
-
-    switch (format) {
-    case TextureFormat::DXT1:
-    case TextureFormat::DXT23:
-    case TextureFormat::DXT45:
-    case TextureFormat::DXN1:
-    case TextureFormat::BC7U:
-        // In the DXT and DXN formats, each 4x4 tile is swizzled instead of just individual pixel
-        // values.
-        CopySwizzledData(width / 4, height / 4, bytes_per_pixel, bytes_per_pixel, data,
-                         unswizzled_data.data(), true, block_height);
-        break;
-    case TextureFormat::A8R8G8B8:
-    case TextureFormat::A2B10G10R10:
-    case TextureFormat::A1B5G5R5:
-    case TextureFormat::B5G6R5:
-    case TextureFormat::R8:
-    case TextureFormat::G8R8:
-    case TextureFormat::R16_G16_B16_A16:
-    case TextureFormat::R32_G32_B32_A32:
-    case TextureFormat::R32_G32:
-    case TextureFormat::R32:
-    case TextureFormat::R16:
-    case TextureFormat::R16_G16:
-    case TextureFormat::BF10GF11RF11:
-    case TextureFormat::ASTC_2D_4X4:
-    case TextureFormat::R32_G32_B32:
-        CopySwizzledData(width, height, bytes_per_pixel, bytes_per_pixel, data,
-                         unswizzled_data.data(), true, block_height);
-        break;
-    default:
-        UNIMPLEMENTED_MSG("Format not implemented");
-        break;
-    }
-
-    return unswizzled_data;
-}
-
-std::vector<u8> UnswizzleDepthTexture(VAddr address, DepthFormat format, u32 width, u32 height,
-                                      u32 block_height) {
-    u8* data = Memory::GetPointer(address);
-    u32 bytes_per_pixel = DepthBytesPerPixel(format);
-
-    std::vector<u8> unswizzled_data(width * height * bytes_per_pixel);
-
-    switch (format) {
-    case DepthFormat::Z16_UNORM:
-    case DepthFormat::S8_Z24_UNORM:
-    case DepthFormat::Z24_S8_UNORM:
-    case DepthFormat::Z32_FLOAT:
-    case DepthFormat::Z32_S8_X24_FLOAT:
-        CopySwizzledData(width, height, bytes_per_pixel, bytes_per_pixel, data,
-                         unswizzled_data.data(), true, block_height);
-        break;
-    default:
-        UNIMPLEMENTED_MSG("Format not implemented");
-        break;
-    }
-
+    CopySwizzledData(width / tile_size, height / tile_size, bytes_per_pixel, bytes_per_pixel,
+                     Memory::GetPointer(address), unswizzled_data.data(), true, block_height);
     return unswizzled_data;
 }
 
@@ -179,6 +104,7 @@ std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat
     case TextureFormat::DXT23:
     case TextureFormat::DXT45:
     case TextureFormat::DXN1:
+    case TextureFormat::DXN2:
     case TextureFormat::BC7U:
     case TextureFormat::ASTC_2D_4X4:
     case TextureFormat::A8R8G8B8:

+ 2 - 2
src/video_core/textures/decoders.h

@@ -13,8 +13,8 @@ namespace Tegra::Texture {
 /**
  * Unswizzles a swizzled texture without changing its format.
  */
-std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height,
-                                 u32 block_height = TICEntry::DefaultBlockHeight);
+std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size, u32 bytes_per_pixel, u32 width,
+                                 u32 height, u32 block_height = TICEntry::DefaultBlockHeight);
 
 /**
  * Unswizzles a swizzled depth texture without changing its format.

+ 3 - 0
src/yuzu/CMakeLists.txt

@@ -17,6 +17,8 @@ add_executable(yuzu
     configuration/configure_debug.h
     configuration/configure_dialog.cpp
     configuration/configure_dialog.h
+    configuration/configure_gamelist.cpp
+    configuration/configure_gamelist.h
     configuration/configure_general.cpp
     configuration/configure_general.h
     configuration/configure_graphics.cpp
@@ -59,6 +61,7 @@ set(UIS
     configuration/configure.ui
     configuration/configure_audio.ui
     configuration/configure_debug.ui
+    configuration/configure_gamelist.ui
     configuration/configure_general.ui
     configuration/configure_graphics.ui
     configuration/configure_input.ui

+ 14 - 0
src/yuzu/configuration/config.cpp

@@ -122,6 +122,13 @@ void Config::ReadValues() {
     qt_config->beginGroup("UI");
     UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();
 
+    qt_config->beginGroup("UIGameList");
+    UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool();
+    UISettings::values.icon_size = qt_config->value("icon_size", 48).toUInt();
+    UISettings::values.row_1_text_id = qt_config->value("row_1_text_id", 0).toUInt();
+    UISettings::values.row_2_text_id = qt_config->value("row_2_text_id", 3).toUInt();
+    qt_config->endGroup();
+
     qt_config->beginGroup("UILayout");
     UISettings::values.geometry = qt_config->value("geometry").toByteArray();
     UISettings::values.state = qt_config->value("state").toByteArray();
@@ -234,6 +241,13 @@ void Config::SaveValues() {
     qt_config->beginGroup("UI");
     qt_config->setValue("theme", UISettings::values.theme);
 
+    qt_config->beginGroup("UIGameList");
+    qt_config->setValue("show_unknown", UISettings::values.show_unknown);
+    qt_config->setValue("icon_size", UISettings::values.icon_size);
+    qt_config->setValue("row_1_text_id", UISettings::values.row_1_text_id);
+    qt_config->setValue("row_2_text_id", UISettings::values.row_2_text_id);
+    qt_config->endGroup();
+
     qt_config->beginGroup("UILayout");
     qt_config->setValue("geometry", UISettings::values.geometry);
     qt_config->setValue("state", UISettings::values.state);

+ 11 - 0
src/yuzu/configuration/configure.ui

@@ -24,6 +24,11 @@
        <string>General</string>
       </attribute>
      </widget>
+      <widget class="ConfigureGameList" name="gameListTab">
+        <attribute name="title">
+          <string>Game List</string>
+        </attribute>
+      </widget>
      <widget class="ConfigureSystem" name="systemTab">
       <attribute name="title">
        <string>System</string>
@@ -67,6 +72,12 @@
    <header>configuration/configure_general.h</header>
    <container>1</container>
   </customwidget>
+   <customwidget>
+     <class>ConfigureGameList</class>
+     <extends>QWidget</extends>
+     <header>configuration/configure_gamelist.h</header>
+     <container>1</container>
+   </customwidget>
   <customwidget>
    <class>ConfigureSystem</class>
    <extends>QWidget</extends>

+ 1 - 0
src/yuzu/configuration/configure_dialog.cpp

@@ -21,6 +21,7 @@ void ConfigureDialog::setConfiguration() {}
 
 void ConfigureDialog::applyConfiguration() {
     ui->generalTab->applyConfiguration();
+    ui->gameListTab->applyConfiguration();
     ui->systemTab->applyConfiguration();
     ui->inputTab->applyConfiguration();
     ui->graphicsTab->applyConfiguration();

+ 63 - 0
src/yuzu/configuration/configure_gamelist.cpp

@@ -0,0 +1,63 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/core.h"
+#include "core/settings.h"
+#include "ui_configure_gamelist.h"
+#include "ui_settings.h"
+#include "yuzu/configuration/configure_gamelist.h"
+
+ConfigureGameList::ConfigureGameList(QWidget* parent)
+    : QWidget(parent), ui(new Ui::ConfigureGameList) {
+    ui->setupUi(this);
+
+    static const std::vector<std::pair<u32, std::string>> default_icon_sizes{
+        std::make_pair(0, "None"),        std::make_pair(32, "Small"),
+        std::make_pair(64, "Standard"),   std::make_pair(128, "Large"),
+        std::make_pair(256, "Full Size"),
+    };
+
+    for (const auto& size : default_icon_sizes) {
+        ui->icon_size_combobox->addItem(QString::fromStdString(size.second + " (" +
+                                                               std::to_string(size.first) + "x" +
+                                                               std::to_string(size.first) + ")"),
+                                        size.first);
+    }
+
+    static const std::vector<std::string> row_text_names{
+        "Filename",
+        "Filetype",
+        "Title ID",
+        "Title Name",
+    };
+
+    for (size_t i = 0; i < row_text_names.size(); ++i) {
+        ui->row_1_text_combobox->addItem(QString::fromStdString(row_text_names[i]),
+                                         QVariant::fromValue(i));
+        ui->row_2_text_combobox->addItem(QString::fromStdString(row_text_names[i]),
+                                         QVariant::fromValue(i));
+    }
+
+    this->setConfiguration();
+}
+
+ConfigureGameList::~ConfigureGameList() {}
+
+void ConfigureGameList::setConfiguration() {
+    ui->show_unknown->setChecked(UISettings::values.show_unknown);
+    ui->icon_size_combobox->setCurrentIndex(
+        ui->icon_size_combobox->findData(UISettings::values.icon_size));
+    ui->row_1_text_combobox->setCurrentIndex(
+        ui->row_1_text_combobox->findData(UISettings::values.row_1_text_id));
+    ui->row_2_text_combobox->setCurrentIndex(
+        ui->row_2_text_combobox->findData(UISettings::values.row_2_text_id));
+}
+
+void ConfigureGameList::applyConfiguration() {
+    UISettings::values.show_unknown = ui->show_unknown->isChecked();
+    UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt();
+    UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt();
+    UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt();
+    Settings::Apply();
+}

+ 28 - 0
src/yuzu/configuration/configure_gamelist.h

@@ -0,0 +1,28 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QWidget>
+
+namespace Ui {
+class ConfigureGameList;
+}
+
+class ConfigureGameList : public QWidget {
+    Q_OBJECT
+
+public:
+    explicit ConfigureGameList(QWidget* parent = nullptr);
+    ~ConfigureGameList();
+
+    void applyConfiguration();
+
+private:
+    void setConfiguration();
+
+private:
+    std::unique_ptr<Ui::ConfigureGameList> ui;
+};

+ 126 - 0
src/yuzu/configuration/configure_gamelist.ui

@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureGameList</class>
+  <widget class="QWidget" name="ConfigureGeneral">
+    <property name="geometry">
+      <rect>
+        <x>0</x>
+        <y>0</y>
+        <width>300</width>
+        <height>377</height>
+      </rect>
+    </property>
+    <property name="windowTitle">
+      <string>Form</string>
+    </property>
+    <layout class="QHBoxLayout" name="HorizontalLayout">
+      <item>
+        <layout class="QVBoxLayout" name="VerticalLayout">
+          <item>
+            <widget class="QGroupBox" name="GeneralGroupBox">
+              <property name="title">
+                <string>General</string>
+              </property>
+              <layout class="QHBoxLayout" name="GeneralHorizontalLayout">
+                <item>
+                  <layout class="QVBoxLayout" name="GeneralVerticalLayout">
+                    <item>
+                      <widget class="QCheckBox" name="show_unknown">
+                        <property name="text">
+                          <string>Show files with type 'Unknown'</string>
+                        </property>
+                      </widget>
+                    </item>
+                  </layout>
+                </item>
+              </layout>
+            </widget>
+          </item>
+          <item>
+            <widget class="QGroupBox" name="IconSizeGroupBox">
+              <property name="title">
+                <string>Icon Size</string>
+              </property>
+              <layout class="QHBoxLayout" name="icon_size_qhbox_layout">
+                <item>
+                  <layout class="QVBoxLayout" name="icon_size_qvbox_layout">
+                    <item>
+                      <layout class="QHBoxLayout" name="icon_size_qhbox_layout_2">
+                        <item>
+                          <widget class="QLabel" name="icon_size_label">
+                            <property name="text">
+                              <string>Icon Size:</string>
+                            </property>
+                          </widget>
+                        </item>
+                        <item>
+                          <widget class="QComboBox" name="icon_size_combobox"/>
+                        </item>
+                      </layout>
+                    </item>
+                  </layout>
+                </item>
+              </layout>
+            </widget>
+          </item>
+          <item>
+            <widget class="QGroupBox" name="RowGroupBox">
+              <property name="title">
+                <string>Row Text</string>
+              </property>
+              <layout class="QHBoxLayout" name="RowHorizontalLayout">
+                <item>
+                  <layout class="QVBoxLayout" name="RowVerticalLayout">
+                    <item>
+                      <layout class="QHBoxLayout" name="row_1_qhbox_layout">
+                        <item>
+                          <widget class="QLabel" name="row_1_label">
+                            <property name="text">
+                              <string>Row 1 Text:</string>
+                            </property>
+                          </widget>
+                        </item>
+                        <item>
+                          <widget class="QComboBox" name="row_1_text_combobox"/>
+                        </item>
+                      </layout>
+                    </item>
+                    <item>
+                      <layout class="QHBoxLayout" name="row_2_qhbox_layout">
+                        <item>
+                          <widget class="QLabel" name="row_2_label">
+                            <property name="text">
+                              <string>Row 2 Text:</string>
+                            </property>
+                          </widget>
+                        </item>
+                        <item>
+                          <widget class="QComboBox" name="row_2_text_combobox"/>
+                        </item>
+                      </layout>
+                    </item>
+                  </layout>
+                </item>
+              </layout>
+            </widget>
+          </item>
+          <item>
+            <spacer name="verticalSpacer">
+              <property name="orientation">
+                <enum>Qt::Vertical</enum>
+              </property>
+              <property name="sizeHint" stdset="0">
+                <size>
+                  <width>20</width>
+                  <height>40</height>
+                </size>
+              </property>
+            </spacer>
+          </item>
+        </layout>
+      </item>
+    </layout>
+  </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 4 - 2
src/yuzu/debugger/graphics/graphics_surface.cpp

@@ -383,8 +383,10 @@ void GraphicsSurfaceWidget::OnUpdate() {
     QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32);
     boost::optional<VAddr> address = gpu.memory_manager->GpuToCpuAddress(surface_address);
 
-    auto unswizzled_data =
-        Tegra::Texture::UnswizzleTexture(*address, surface_format, surface_width, surface_height);
+    // TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles.
+    // Needs to be fixed if we plan to use this feature more, otherwise we may remove it.
+    auto unswizzled_data = Tegra::Texture::UnswizzleTexture(
+        *address, 1, Tegra::Texture::BytesPerPixel(surface_format), surface_width, surface_height);
 
     auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format,
                                                       surface_width, surface_height);

+ 74 - 12
src/yuzu/game_list.cpp

@@ -9,9 +9,13 @@
 #include <QKeyEvent>
 #include <QMenu>
 #include <QThreadPool>
+#include <boost/container/flat_map.hpp>
 #include "common/common_paths.h"
 #include "common/logging/log.h"
 #include "common/string_util.h"
+#include "core/file_sys/content_archive.h"
+#include "core/file_sys/control_metadata.h"
+#include "core/file_sys/romfs.h"
 #include "core/file_sys/vfs_real.h"
 #include "core/loader/loader.h"
 #include "game_list.h"
@@ -194,7 +198,8 @@ void GameList::onFilterCloseClicked() {
     main_window->filterBarSetChecked(false);
 }
 
-GameList::GameList(GMainWindow* parent) : QWidget{parent} {
+GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent)
+    : QWidget{parent}, vfs(std::move(vfs)) {
     watcher = new QFileSystemWatcher(this);
     connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
 
@@ -338,7 +343,7 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
 
     emit ShouldCancelWorker();
 
-    GameListWorker* worker = new GameListWorker(dir_path, deep_scan);
+    GameListWorker* worker = new GameListWorker(vfs, dir_path, deep_scan);
 
     connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
     connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating,
@@ -398,8 +403,32 @@ void GameList::RefreshGameDirectory() {
 }
 
 void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
-    const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
-                                            const std::string& virtual_name) -> bool {
+    boost::container::flat_map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
+
+    const auto nca_control_callback =
+        [this, &nca_control_map](u64* num_entries_out, const std::string& directory,
+                                 const std::string& virtual_name) -> bool {
+        std::string physical_name = directory + DIR_SEP + virtual_name;
+
+        if (stop_processing)
+            return false; // Breaks the callback loop.
+
+        bool is_dir = FileUtil::IsDirectory(physical_name);
+        QFileInfo file_info(physical_name.c_str());
+        if (!is_dir && file_info.suffix().toStdString() == "nca") {
+            auto nca =
+                std::make_shared<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
+            if (nca->GetType() == FileSys::NCAContentType::Control)
+                nca_control_map.insert_or_assign(nca->GetTitleId(), nca);
+        }
+        return true;
+    };
+
+    FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
+
+    const auto callback = [this, recursion,
+                           &nca_control_map](u64* num_entries_out, const std::string& directory,
+                                             const std::string& virtual_name) -> bool {
         std::string physical_name = directory + DIR_SEP + virtual_name;
 
         if (stop_processing)
@@ -409,18 +438,51 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
         if (!is_dir &&
             (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
             std::unique_ptr<Loader::AppLoader> loader =
-                Loader::GetLoader(std::make_shared<FileSys::RealVfsFile>(physical_name));
-            if (!loader)
+                Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read));
+            if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown ||
+                             loader->GetFileType() == Loader::FileType::Error) &&
+                            !UISettings::values.show_unknown))
                 return true;
 
-            std::vector<u8> smdh;
-            loader->ReadIcon(smdh);
-
-            u64 program_id = 0;
-            loader->ReadProgramId(program_id);
+            std::vector<u8> icon;
+            const auto res1 = loader->ReadIcon(icon);
+
+            u64 program_id;
+            const auto res2 = loader->ReadProgramId(program_id);
+
+            std::string name = " ";
+            const auto res3 = loader->ReadTitle(name);
+
+            if ((res1 == Loader::ResultStatus::ErrorNotUsed ||
+                 res1 == Loader::ResultStatus::ErrorNotImplemented) &&
+                (res3 == Loader::ResultStatus::ErrorNotUsed ||
+                 res3 == Loader::ResultStatus::ErrorNotImplemented) &&
+                res2 == Loader::ResultStatus::Success) {
+                // Use from metadata pool.
+                if (nca_control_map.find(program_id) != nca_control_map.end()) {
+                    const auto nca = nca_control_map[program_id];
+                    const auto control_dir = FileSys::ExtractRomFS(nca->GetRomFS());
+
+                    const auto nacp_file = control_dir->GetFile("control.nacp");
+                    FileSys::NACP nacp(nacp_file);
+                    name = nacp.GetApplicationName();
+
+                    FileSys::VirtualFile icon_file = nullptr;
+                    for (const auto& language : FileSys::LANGUAGE_NAMES) {
+                        icon_file = control_dir->GetFile("icon_" + std::string(language) + ".dat");
+                        if (icon_file != nullptr) {
+                            icon = icon_file->ReadAllBytes();
+                            break;
+                        }
+                    }
+                }
+            }
 
             emit EntryReady({
-                new GameListItemPath(FormatGameName(physical_name), smdh, program_id),
+                new GameListItemPath(
+                    FormatGameName(physical_name), icon, QString::fromStdString(name),
+                    QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
+                    program_id),
                 new GameListItem(
                     QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
                 new GameListItemSize(FileUtil::GetSize(physical_name)),

+ 2 - 1
src/yuzu/game_list.h

@@ -59,7 +59,7 @@ public:
         QToolButton* button_filter_close = nullptr;
     };
 
-    explicit GameList(GMainWindow* parent = nullptr);
+    explicit GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent = nullptr);
     ~GameList() override;
 
     void clearFilter();
@@ -90,6 +90,7 @@ private:
     void PopupContextMenu(const QPoint& menu_location);
     void RefreshGameDirectory();
 
+    FileSys::VirtualFilesystem vfs;
     SearchField* search_field;
     GMainWindow* main_window = nullptr;
     QVBoxLayout* layout = nullptr;

+ 37 - 6
src/yuzu/game_list_p.h

@@ -11,6 +11,7 @@
 #include <QStandardItem>
 #include <QString>
 #include "common/string_util.h"
+#include "ui_settings.h"
 #include "yuzu/util/util.h"
 
 /**
@@ -18,8 +19,7 @@
  * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24)
  * @return QPixmap default icon
  */
-static QPixmap GetDefaultIcon(bool large) {
-    int size = large ? 48 : 24;
+static QPixmap GetDefaultIcon(u32 size) {
     QPixmap icon(size, size);
     icon.fill(Qt::transparent);
     return icon;
@@ -44,11 +44,25 @@ public:
     static const int FullPathRole = Qt::UserRole + 1;
     static const int TitleRole = Qt::UserRole + 2;
     static const int ProgramIdRole = Qt::UserRole + 3;
+    static const int FileTypeRole = Qt::UserRole + 4;
 
     GameListItemPath() = default;
-    GameListItemPath(const QString& game_path, const std::vector<u8>& smdh_data, u64 program_id) {
+    GameListItemPath(const QString& game_path, const std::vector<u8>& picture_data,
+                     const QString& game_name, const QString& game_type, u64 program_id)
+        : GameListItem() {
         setData(game_path, FullPathRole);
+        setData(game_name, TitleRole);
         setData(qulonglong(program_id), ProgramIdRole);
+        setData(game_type, FileTypeRole);
+
+        QPixmap picture;
+        u32 size = UISettings::values.icon_size;
+        if (!picture.loadFromData(picture_data.data(), picture_data.size()))
+            picture = GetDefaultIcon(size);
+
+        picture = picture.scaled(size, size);
+
+        setData(picture, Qt::DecorationRole);
     }
 
     QVariant data(int role) const override {
@@ -57,7 +71,23 @@ public:
             Common::SplitPath(data(FullPathRole).toString().toStdString(), nullptr, &filename,
                               nullptr);
             QString title = data(TitleRole).toString();
-            return QString::fromStdString(filename) + (title.isEmpty() ? "" : "\n    " + title);
+
+            std::vector<QString> row_data{
+                QString::fromStdString(filename),
+                data(FileTypeRole).toString(),
+                QString::fromStdString(fmt::format("0x{:016X}", data(ProgramIdRole).toULongLong())),
+                data(TitleRole).toString(),
+            };
+
+            auto row1 = row_data.at(UISettings::values.row_1_text_id);
+            auto row2 = row_data.at(UISettings::values.row_2_text_id);
+
+            if (row1.isEmpty() || row1 == row2)
+                return row2;
+            if (row2.isEmpty())
+                return row1;
+
+            return row1 + "\n    " + row2;
         } else {
             return GameListItem::data(role);
         }
@@ -109,8 +139,8 @@ class GameListWorker : public QObject, public QRunnable {
     Q_OBJECT
 
 public:
-    GameListWorker(QString dir_path, bool deep_scan)
-        : dir_path(std::move(dir_path)), deep_scan(deep_scan) {}
+    GameListWorker(FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan)
+        : vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan) {}
 
 public slots:
     /// Starts the processing of directory tree information.
@@ -133,6 +163,7 @@ signals:
     void Finished(QStringList watch_list);
 
 private:
+    FileSys::VirtualFilesystem vfs;
     QStringList watch_list;
     QString dir_path;
     bool deep_scan;

+ 7 - 2
src/yuzu/main.cpp

@@ -24,6 +24,7 @@
 #include "common/string_util.h"
 #include "core/core.h"
 #include "core/crypto/key_manager.h"
+#include "core/file_sys/vfs_real.h"
 #include "core/gdbstub/gdbstub.h"
 #include "core/loader/loader.h"
 #include "core/settings.h"
@@ -83,7 +84,9 @@ void GMainWindow::ShowCallouts() {}
 
 const int GMainWindow::max_recent_files_item;
 
-GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
+GMainWindow::GMainWindow()
+    : config(new Config()), emu_thread(nullptr),
+      vfs(std::make_shared<FileSys::RealVfsFilesystem>()) {
 
     debug_context = Tegra::DebugContext::Construct();
 
@@ -132,7 +135,7 @@ void GMainWindow::InitializeWidgets() {
     render_window = new GRenderWindow(this, emu_thread.get());
     render_window->hide();
 
-    game_list = new GameList(this);
+    game_list = new GameList(vfs, this);
     ui.horizontalLayout->addWidget(game_list);
 
     // Create status bar
@@ -406,6 +409,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
     }
 
     Core::System& system{Core::System::GetInstance()};
+    system.SetFilesystem(vfs);
 
     system.SetGPUDebugContext(debug_context);
 
@@ -768,6 +772,7 @@ void GMainWindow::OnConfigure() {
         configureDialog.applyConfiguration();
         if (UISettings::values.theme != old_theme)
             UpdateUITheme();
+        game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
         config->Save();
     }
 }

+ 3 - 0
src/yuzu/main.h

@@ -161,6 +161,9 @@ private:
     bool emulation_running = false;
     std::unique_ptr<EmuThread> emu_thread;
 
+    // FS
+    FileSys::VirtualFilesystem vfs;
+
     // Debugger panes
     ProfilerWidget* profilerWidget;
     MicroProfileDialog* microProfileDialog;

+ 6 - 0
src/yuzu/ui_settings.h

@@ -54,6 +54,12 @@ struct Values {
 
     // logging
     bool show_console;
+
+    // Game List
+    bool show_unknown;
+    uint32_t icon_size;
+    uint8_t row_1_text_id;
+    uint8_t row_2_text_id;
 };
 
 extern Values values;

+ 1 - 0
src/yuzu_cmd/yuzu.cpp

@@ -161,6 +161,7 @@ int main(int argc, char** argv) {
     }
 
     Core::System& system{Core::System::GetInstance()};
+    system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
 
     SCOPE_EXIT({ system.Shutdown(); });