mirror of
				https://git.zaroz.cloud/nintendo-back-up/yuzu/yuzu.git
				synced 2025-05-12 00:45:25 +00:00 
			
		
		
		
	Merge remote-tracking branch 'origin/master' into better-account
This commit is contained in:
		
						commit
						b76ddb7647
					
				| @ -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) | ||||
|  | ||||
| @ -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 | ||||
|         } | ||||
|  | ||||
							
								
								
									
										2
									
								
								externals/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								externals/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							| @ -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 | ||||
|  | ||||
| @ -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); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -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[].
 | ||||
|  | ||||
| @ -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)); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  | ||||
| @ -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)); | ||||
| } | ||||
|  | ||||
| @ -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); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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)                                                                              \ | ||||
|  | ||||
| @ -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
 | ||||
|  | ||||
| @ -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); | ||||
|     } | ||||
|     template <typename V> | ||||
|     Vec2<decltype(T{} * V{})> operator*(const V& f) const { | ||||
|         return MakeVec(x * f, y * f); | ||||
|     } | ||||
|     template <typename V> | ||||
|     void operator*=(const V& f) { | ||||
|         *this = *this * f; | ||||
|     } | ||||
|     template <typename V> | ||||
|     Vec2<decltype(T{} / V{})> operator/(const V& f) const { | ||||
|         return MakeVec(x / f, y / f); | ||||
|     } | ||||
|     template <typename V> | ||||
|     void operator/=(const V& f) { | ||||
|         *this = *this / f; | ||||
|     constexpr Vec2<decltype(T{} * T{})> operator*(const Vec2& other) const { | ||||
|         return {x * other.x, y * other.y}; | ||||
|     } | ||||
| 
 | ||||
|     T Length2() const { | ||||
|     template <typename V> | ||||
|     constexpr Vec2<decltype(T{} * V{})> operator*(const V& f) const { | ||||
|         return {x * f, y * f}; | ||||
|     } | ||||
| 
 | ||||
|     template <typename V> | ||||
|     constexpr Vec2& operator*=(const V& f) { | ||||
|         *this = *this * f; | ||||
|         return *this; | ||||
|     } | ||||
| 
 | ||||
|     template <typename V> | ||||
|     constexpr Vec2<decltype(T{} / V{})> operator/(const V& f) const { | ||||
|         return {x / f, y / f}; | ||||
|     } | ||||
| 
 | ||||
|     template <typename V> | ||||
|     constexpr Vec2& operator/=(const V& f) { | ||||
|         *this = *this / f; | ||||
|         return *this; | ||||
|     } | ||||
| 
 | ||||
|     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); | ||||
|     } | ||||
|     Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const { | ||||
|         return MakeVec(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); | ||||
|     } | ||||
|     template <typename V> | ||||
|     void operator*=(const V& f) { | ||||
|         *this = *this * f; | ||||
|     } | ||||
|     template <typename V> | ||||
|     Vec3<decltype(T{} / V{})> operator/(const V& f) const { | ||||
|         return MakeVec(x / f, y / f, z / f); | ||||
|     } | ||||
|     template <typename V> | ||||
|     void operator/=(const V& f) { | ||||
|         *this = *this / f; | ||||
|     constexpr Vec3<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const { | ||||
|         return {-x, -y, -z}; | ||||
|     } | ||||
| 
 | ||||
|     T Length2() const { | ||||
|     constexpr Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const { | ||||
|         return {x * other.x, y * other.y, z * other.z}; | ||||
|     } | ||||
| 
 | ||||
|     template <typename V> | ||||
|     constexpr Vec3<decltype(T{} * V{})> operator*(const V& f) const { | ||||
|         return {x * f, y * f, z * f}; | ||||
|     } | ||||
| 
 | ||||
|     template <typename V> | ||||
|     constexpr Vec3& operator*=(const V& f) { | ||||
|         *this = *this * f; | ||||
|         return *this; | ||||
|     } | ||||
|     template <typename V> | ||||
|     constexpr Vec3<decltype(T{} / V{})> operator/(const V& f) const { | ||||
|         return {x / f, y / f, z / f}; | ||||
|     } | ||||
| 
 | ||||
|     template <typename V> | ||||
|     constexpr Vec3& operator/=(const V& f) { | ||||
|         *this = *this / f; | ||||
|         return *this; | ||||
|     } | ||||
| 
 | ||||
|     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)
 | ||||
|     { | ||||
|         return *((&x) + i); | ||||
|     } | ||||
|     T operator[](const int i) const { | ||||
|     constexpr T& operator[](std::size_t i) { | ||||
|         return *((&x) + i); | ||||
|     } | ||||
| 
 | ||||
|     void SetZero() { | ||||
|     constexpr const T& operator[](std::size_t i) const { | ||||
|         return *((&x) + i); | ||||
|     } | ||||
| 
 | ||||
|     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); | ||||
|     } | ||||
|     Vec4<decltype(T{} * T{})> operator*(const Vec4& other) const { | ||||
|         return MakeVec(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); | ||||
|     } | ||||
|     template <typename V> | ||||
|     void operator*=(const V& f) { | ||||
|         *this = *this * f; | ||||
|     } | ||||
|     template <typename V> | ||||
|     Vec4<decltype(T{} / V{})> operator/(const V& f) const { | ||||
|         return MakeVec(x / f, y / f, z / f, w / f); | ||||
|     } | ||||
|     template <typename V> | ||||
|     void operator/=(const V& f) { | ||||
|         *this = *this / f; | ||||
|     constexpr Vec4<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const { | ||||
|         return {-x, -y, -z, -w}; | ||||
|     } | ||||
| 
 | ||||
|     T Length2() const { | ||||
|     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> | ||||
|     constexpr Vec4<decltype(T{} * V{})> operator*(const V& f) const { | ||||
|         return {x * f, y * f, z * f, w * f}; | ||||
|     } | ||||
| 
 | ||||
|     template <typename V> | ||||
|     constexpr Vec4& operator*=(const V& f) { | ||||
|         *this = *this * f; | ||||
|         return *this; | ||||
|     } | ||||
| 
 | ||||
|     template <typename V> | ||||
|     constexpr Vec4<decltype(T{} / V{})> operator/(const V& f) const { | ||||
|         return {x / f, y / f, z / f, w / f}; | ||||
|     } | ||||
| 
 | ||||
|     template <typename V> | ||||
|     constexpr Vec4& operator/=(const V& f) { | ||||
|         *this = *this / f; | ||||
|         return *this; | ||||
|     } | ||||
| 
 | ||||
|     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)
 | ||||
|     { | ||||
|         return *((&x) + i); | ||||
|     } | ||||
|     T operator[](const int i) const { | ||||
|     constexpr T& operator[](std::size_t i) { | ||||
|         return *((&x) + i); | ||||
|     } | ||||
| 
 | ||||
|     void SetZero() { | ||||
|     constexpr const T& operator[](std::size_t i) const { | ||||
|         return *((&x) + i); | ||||
|     } | ||||
| 
 | ||||
|     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]); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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" | ||||
|  | ||||
| @ -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."); | ||||
| 
 | ||||
|  | ||||
| @ -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" | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -7,6 +7,7 @@ | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include "common/common_types.h" | ||||
| #include "common/swap.h" | ||||
| #include "core/hle/result.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
| @ -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; | ||||
| } | ||||
| 
 | ||||
|     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()); | ||||
| bool RealVfsFile::Close() { | ||||
|     return backing->Close(); | ||||
| } | ||||
| 
 | ||||
| // 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
 | ||||
|  | ||||
| @ -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
 | ||||
|  | ||||
| @ -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)); | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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.
 | ||||
| }; | ||||
|  | ||||
| @ -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.
 | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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
 | ||||
|  | ||||
| @ -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()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -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(), ¶ms, output.size()); | ||||
|     std::memcpy(output.data(), ¶ms, sizeof(IoctlSubmitGpfifo)); | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -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() { | ||||
|  | ||||
| @ -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
 | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
| @ -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"); | ||||
|  | ||||
| @ -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; | ||||
| 
 | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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(); | ||||
|  | ||||
| @ -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
 | ||||
|  | ||||
| @ -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
 | ||||
|  | ||||
| @ -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"; | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
| @ -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; | ||||
| } | ||||
|  | ||||
| @ -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; | ||||
| }; | ||||
|  | ||||
| @ -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
 | ||||
|  | ||||
| @ -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
 | ||||
|  | ||||
| @ -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); | ||||
|     // The requested macro must have been uploaded already.
 | ||||
|     ASSERT_MSG(macro_code != uploaded_macros.end(), "Macro %08X was not uploaded", method); | ||||
| 
 | ||||
|     // Reset the current macro and execute it.
 | ||||
|     // Reset the current macro.
 | ||||
|     executing_macro = 0; | ||||
| 
 | ||||
|     // The requested macro must have been uploaded already.
 | ||||
|     auto macro_code = uploaded_macros.find(method); | ||||
|     if (macro_code == uploaded_macros.end()) { | ||||
|         LOG_ERROR(HW_GPU, "Macro {:04X} was not uploaded", method); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // 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(); | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
| @ -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)); | ||||
|  | ||||
| @ -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, | ||||
| }; | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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); | ||||
|         } | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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 + ')'; | ||||
|         } | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
| @ -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: | ||||
|  | ||||
| @ -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; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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: | ||||
|  | ||||
| @ -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. | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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> | ||||
|  | ||||
| @ -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
									
								
								src/yuzu/configuration/configure_gamelist.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/yuzu/configuration/configure_gamelist.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								src/yuzu/configuration/configure_gamelist.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/yuzu/configuration/configure_gamelist.h
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								src/yuzu/configuration/configure_gamelist.ui
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								src/yuzu/configuration/configure_gamelist.ui
									
									
									
									
									
										Normal file
									
								
							| @ -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> | ||||
| @ -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); | ||||
|  | ||||
| @ -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); | ||||
|             std::vector<u8> icon; | ||||
|             const auto res1 = loader->ReadIcon(icon); | ||||
| 
 | ||||
|             u64 program_id = 0; | ||||
|             loader->ReadProgramId(program_id); | ||||
|             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)), | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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(); }); | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 David Marcec
						David Marcec