diff options
Diffstat (limited to 'src')
76 files changed, 1562 insertions, 634 deletions
diff --git a/src/common/alignment.h b/src/common/alignment.h index b77da4a92..b9dd38746 100644 --- a/src/common/alignment.h +++ b/src/common/alignment.h @@ -9,13 +9,13 @@ namespace Common { template <typename T> constexpr T AlignUp(T value, size_t size) { - static_assert(std::is_unsigned<T>::value, "T must be an unsigned value."); + static_assert(std::is_unsigned_v<T>, "T must be an unsigned value."); return static_cast<T>(value + (size - value % size) % size); } template <typename T> constexpr T AlignDown(T value, size_t size) { - static_assert(std::is_unsigned<T>::value, "T must be an unsigned value."); + static_assert(std::is_unsigned_v<T>, "T must be an unsigned value."); return static_cast<T>(value - value % size); } diff --git a/src/common/bit_set.h b/src/common/bit_set.h index 84e3cbe58..5a197d8c1 100644 --- a/src/common/bit_set.h +++ b/src/common/bit_set.h @@ -96,7 +96,7 @@ static inline int LeastSignificantSetBit(u64 val) { template <typename IntTy> class BitSet { - static_assert(!std::is_signed<IntTy>::value, "BitSet should not be used with signed types"); + static_assert(!std::is_signed_v<IntTy>, "BitSet should not be used with signed types"); public: // A reference to a particular bit, returned from operator[]. diff --git a/src/common/color.h b/src/common/color.h index 24a445dac..0379040be 100644 --- a/src/common/color.h +++ b/src/common/color.h @@ -4,6 +4,8 @@ #pragma once +#include <cstring> + #include "common/common_types.h" #include "common/swap.h" #include "common/vector_math.h" @@ -55,7 +57,7 @@ constexpr u8 Convert8To6(u8 value) { * @param bytes Pointer to encoded source color * @return Result color decoded as Math::Vec4<u8> */ -inline const Math::Vec4<u8> DecodeRGBA8(const u8* bytes) { +inline Math::Vec4<u8> DecodeRGBA8(const u8* bytes) { return {bytes[3], bytes[2], bytes[1], bytes[0]}; } @@ -64,7 +66,7 @@ inline const Math::Vec4<u8> DecodeRGBA8(const u8* bytes) { * @param bytes Pointer to encoded source color * @return Result color decoded as Math::Vec4<u8> */ -inline const Math::Vec4<u8> DecodeRGB8(const u8* bytes) { +inline Math::Vec4<u8> DecodeRGB8(const u8* bytes) { return {bytes[2], bytes[1], bytes[0], 255}; } @@ -73,7 +75,7 @@ inline const Math::Vec4<u8> DecodeRGB8(const u8* bytes) { * @param bytes Pointer to encoded source color * @return Result color decoded as Math::Vec4<u8> */ -inline const Math::Vec4<u8> DecodeRG8(const u8* bytes) { +inline Math::Vec4<u8> DecodeRG8(const u8* bytes) { return {bytes[1], bytes[0], 0, 255}; } @@ -82,8 +84,9 @@ inline const Math::Vec4<u8> DecodeRG8(const u8* bytes) { * @param bytes Pointer to encoded source color * @return Result color decoded as Math::Vec4<u8> */ -inline const Math::Vec4<u8> DecodeRGB565(const u8* bytes) { - const u16_le pixel = *reinterpret_cast<const u16_le*>(bytes); +inline Math::Vec4<u8> DecodeRGB565(const u8* bytes) { + u16_le pixel; + std::memcpy(&pixel, bytes, sizeof(pixel)); return {Convert5To8((pixel >> 11) & 0x1F), Convert6To8((pixel >> 5) & 0x3F), Convert5To8(pixel & 0x1F), 255}; } @@ -93,8 +96,9 @@ inline const Math::Vec4<u8> DecodeRGB565(const u8* bytes) { * @param bytes Pointer to encoded source color * @return Result color decoded as Math::Vec4<u8> */ -inline const Math::Vec4<u8> DecodeRGB5A1(const u8* bytes) { - const u16_le pixel = *reinterpret_cast<const u16_le*>(bytes); +inline Math::Vec4<u8> DecodeRGB5A1(const u8* bytes) { + u16_le pixel; + std::memcpy(&pixel, bytes, sizeof(pixel)); return {Convert5To8((pixel >> 11) & 0x1F), Convert5To8((pixel >> 6) & 0x1F), Convert5To8((pixel >> 1) & 0x1F), Convert1To8(pixel & 0x1)}; } @@ -104,8 +108,9 @@ inline const Math::Vec4<u8> DecodeRGB5A1(const u8* bytes) { * @param bytes Pointer to encoded source color * @return Result color decoded as Math::Vec4<u8> */ -inline const Math::Vec4<u8> DecodeRGBA4(const u8* bytes) { - const u16_le pixel = *reinterpret_cast<const u16_le*>(bytes); +inline Math::Vec4<u8> DecodeRGBA4(const u8* bytes) { + u16_le pixel; + std::memcpy(&pixel, bytes, sizeof(pixel)); return {Convert4To8((pixel >> 12) & 0xF), Convert4To8((pixel >> 8) & 0xF), Convert4To8((pixel >> 4) & 0xF), Convert4To8(pixel & 0xF)}; } @@ -116,7 +121,9 @@ inline const Math::Vec4<u8> DecodeRGBA4(const u8* bytes) { * @return Depth value as an u32 */ inline u32 DecodeD16(const u8* bytes) { - return *reinterpret_cast<const u16_le*>(bytes); + u16_le data; + std::memcpy(&data, bytes, sizeof(data)); + return data; } /** @@ -133,7 +140,7 @@ inline u32 DecodeD24(const u8* bytes) { * @param bytes Pointer to encoded source values * @return Resulting values stored as a Math::Vec2 */ -inline const Math::Vec2<u32> DecodeD24S8(const u8* bytes) { +inline Math::Vec2<u32> DecodeD24S8(const u8* bytes) { return {static_cast<u32>((bytes[2] << 16) | (bytes[1] << 8) | bytes[0]), bytes[3]}; } @@ -175,8 +182,10 @@ inline void EncodeRG8(const Math::Vec4<u8>& color, u8* bytes) { * @param bytes Destination pointer to store encoded color */ inline void EncodeRGB565(const Math::Vec4<u8>& color, u8* bytes) { - *reinterpret_cast<u16_le*>(bytes) = + const u16_le data = (Convert8To5(color.r()) << 11) | (Convert8To6(color.g()) << 5) | Convert8To5(color.b()); + + std::memcpy(bytes, &data, sizeof(data)); } /** @@ -185,9 +194,10 @@ inline void EncodeRGB565(const Math::Vec4<u8>& color, u8* bytes) { * @param bytes Destination pointer to store encoded color */ inline void EncodeRGB5A1(const Math::Vec4<u8>& color, u8* bytes) { - *reinterpret_cast<u16_le*>(bytes) = (Convert8To5(color.r()) << 11) | - (Convert8To5(color.g()) << 6) | - (Convert8To5(color.b()) << 1) | Convert8To1(color.a()); + const u16_le data = (Convert8To5(color.r()) << 11) | (Convert8To5(color.g()) << 6) | + (Convert8To5(color.b()) << 1) | Convert8To1(color.a()); + + std::memcpy(bytes, &data, sizeof(data)); } /** @@ -196,9 +206,10 @@ inline void EncodeRGB5A1(const Math::Vec4<u8>& color, u8* bytes) { * @param bytes Destination pointer to store encoded color */ inline void EncodeRGBA4(const Math::Vec4<u8>& color, u8* bytes) { - *reinterpret_cast<u16_le*>(bytes) = (Convert8To4(color.r()) << 12) | - (Convert8To4(color.g()) << 8) | - (Convert8To4(color.b()) << 4) | Convert8To4(color.a()); + const u16 data = (Convert8To4(color.r()) << 12) | (Convert8To4(color.g()) << 8) | + (Convert8To4(color.b()) << 4) | Convert8To4(color.a()); + + std::memcpy(bytes, &data, sizeof(data)); } /** @@ -207,7 +218,8 @@ inline void EncodeRGBA4(const Math::Vec4<u8>& color, u8* bytes) { * @param bytes Pointer where to store the encoded value */ inline void EncodeD16(u32 value, u8* bytes) { - *reinterpret_cast<u16_le*>(bytes) = value & 0xFFFF; + const u16_le data = static_cast<u16>(value); + std::memcpy(bytes, &data, sizeof(data)); } /** diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 7aeda737f..3ce590062 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -884,11 +884,21 @@ std::string_view RemoveTrailingSlash(std::string_view path) { return path; } -std::string SanitizePath(std::string_view path_) { +std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) { std::string path(path_); - std::replace(path.begin(), path.end(), '\\', '/'); + char type1 = directory_separator == DirectorySeparator::BackwardSlash ? '/' : '\\'; + char type2 = directory_separator == DirectorySeparator::BackwardSlash ? '\\' : '/'; + + if (directory_separator == DirectorySeparator::PlatformDefault) { +#ifdef _WIN32 + type1 = '/'; + type2 = '\\'; +#endif + } + + std::replace(path.begin(), path.end(), type1, type2); path.erase(std::unique(path.begin(), path.end(), - [](char c1, char c2) { return c1 == '/' && c2 == '/'; }), + [type2](char c1, char c2) { return c1 == type2 && c2 == type2; }), path.end()); return std::string(RemoveTrailingSlash(path)); } diff --git a/src/common/file_util.h b/src/common/file_util.h index 430dac41c..2711872ae 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -182,8 +182,12 @@ std::vector<T> SliceVector(const std::vector<T>& vector, size_t first, size_t la return std::vector<T>(vector.begin() + first, vector.begin() + first + last); } -// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. -std::string SanitizePath(std::string_view path); +enum class DirectorySeparator { ForwardSlash, BackwardSlash, PlatformDefault }; + +// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\' +// depending if directory_separator is BackwardSlash or PlatformDefault and running on windows +std::string SanitizePath(std::string_view path, + DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash); // simple wrapper for cstdlib file functions to // hopefully will make error checking easier @@ -208,7 +212,7 @@ public: template <typename T> size_t ReadArray(T* data, size_t length) const { - static_assert(std::is_trivially_copyable<T>(), + static_assert(std::is_trivially_copyable_v<T>, "Given array does not consist of trivially copyable objects"); if (!IsOpen()) { @@ -220,7 +224,7 @@ public: template <typename T> size_t WriteArray(const T* data, size_t length) { - static_assert(std::is_trivially_copyable<T>(), + static_assert(std::is_trivially_copyable_v<T>, "Given array does not consist of trivially copyable objects"); if (!IsOpen()) { return std::numeric_limits<size_t>::max(); @@ -231,19 +235,19 @@ public: template <typename T> size_t ReadBytes(T* data, size_t length) const { - static_assert(std::is_trivially_copyable<T>(), "T must be trivially copyable"); + static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable"); return ReadArray(reinterpret_cast<char*>(data), length); } template <typename T> size_t WriteBytes(const T* data, size_t length) { - static_assert(std::is_trivially_copyable<T>(), "T must be trivially copyable"); + static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable"); return WriteArray(reinterpret_cast<const char*>(data), length); } template <typename T> size_t WriteObject(const T& object) { - static_assert(!std::is_pointer<T>::value, "Given object is a pointer"); + static_assert(!std::is_pointer_v<T>, "WriteObject arguments must not be a pointer"); return WriteArray(&object, 1); } diff --git a/src/common/hash.h b/src/common/hash.h index 73c326980..2c761e545 100644 --- a/src/common/hash.h +++ b/src/common/hash.h @@ -28,7 +28,7 @@ static inline u64 ComputeHash64(const void* data, size_t len) { */ template <typename T> static inline u64 ComputeStructHash64(const T& data) { - static_assert(std::is_trivially_copyable<T>(), + static_assert(std::is_trivially_copyable_v<T>, "Type passed to ComputeStructHash64 must be trivially copyable"); return ComputeHash64(&data, sizeof(data)); } @@ -38,7 +38,7 @@ template <typename T> struct HashableStruct { // In addition to being trivially copyable, T must also have a trivial default constructor, // because any member initialization would be overridden by memset - static_assert(std::is_trivial<T>(), "Type passed to HashableStruct must be trivial"); + static_assert(std::is_trivial_v<T>, "Type passed to HashableStruct must be trivial"); /* * We use a union because "implicitly-defined copy/move constructor for a union X copies the * object representation of X." and "implicitly-defined copy assignment operator for a union X diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 355abd682..e80784c3c 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -171,15 +171,21 @@ void FileBackend::Write(const Entry& entry) { SUB(Service, ARP) \ SUB(Service, BCAT) \ SUB(Service, BPC) \ + SUB(Service, BTDRV) \ SUB(Service, BTM) \ SUB(Service, Capture) \ + SUB(Service, ERPT) \ + SUB(Service, ETicket) \ + SUB(Service, EUPLD) \ SUB(Service, Fatal) \ SUB(Service, FGM) \ SUB(Service, Friend) \ SUB(Service, FS) \ + SUB(Service, GRC) \ SUB(Service, HID) \ SUB(Service, LBL) \ SUB(Service, LDN) \ + SUB(Service, LDR) \ SUB(Service, LM) \ SUB(Service, Migration) \ SUB(Service, Mii) \ @@ -188,11 +194,13 @@ void FileBackend::Write(const Entry& entry) { SUB(Service, NFC) \ SUB(Service, NFP) \ SUB(Service, NIFM) \ + SUB(Service, NIM) \ SUB(Service, NS) \ SUB(Service, NVDRV) \ SUB(Service, PCIE) \ SUB(Service, PCTL) \ SUB(Service, PCV) \ + SUB(Service, PM) \ SUB(Service, PREPO) \ SUB(Service, PSC) \ SUB(Service, SET) \ diff --git a/src/common/logging/log.h b/src/common/logging/log.h index a889ebefa..e12f47f8f 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -58,15 +58,21 @@ enum class Class : ClassType { Service_Audio, ///< The Audio (Audio control) service Service_BCAT, ///< The BCAT service Service_BPC, ///< The BPC service + Service_BTDRV, ///< The Bluetooth driver service Service_BTM, ///< The BTM service Service_Capture, ///< The capture service + Service_ERPT, ///< The error reporting service + Service_ETicket, ///< The ETicket service + Service_EUPLD, ///< The error upload service Service_Fatal, ///< The Fatal service Service_FGM, ///< The FGM service Service_Friend, ///< The friend service Service_FS, ///< The FS (Filesystem) service + Service_GRC, ///< The game recording service Service_HID, ///< The HID (Human interface device) service Service_LBL, ///< The LBL (LCD backlight) service Service_LDN, ///< The LDN (Local domain network) service + Service_LDR, ///< The loader service Service_LM, ///< The LM (Logger) service Service_Migration, ///< The migration service Service_Mii, ///< The Mii service @@ -75,11 +81,13 @@ enum class Class : ClassType { Service_NFC, ///< The NFC (Near-field communication) service Service_NFP, ///< The NFP service Service_NIFM, ///< The NIFM (Network interface) service + Service_NIM, ///< The NIM service Service_NS, ///< The NS services Service_NVDRV, ///< The NVDRV (Nvidia driver) service Service_PCIE, ///< The PCIe service Service_PCTL, ///< The PCTL (Parental control) service Service_PCV, ///< The PCV service + Service_PM, ///< The PM service Service_PREPO, ///< The PREPO (Play report) service Service_PSC, ///< The PSC service Service_SET, ///< The SET (Settings) service diff --git a/src/common/vector_math.h b/src/common/vector_math.h index cca43bd4c..8feb49941 100644 --- a/src/common/vector_math.h +++ b/src/common/vector_math.h @@ -43,139 +43,135 @@ template <typename T> class Vec4; template <typename T> -static inline Vec2<T> MakeVec(const T& x, const T& y); -template <typename T> -static inline Vec3<T> MakeVec(const T& x, const T& y, const T& z); -template <typename T> -static inline Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w); - -template <typename T> class Vec2 { public: T x{}; T y{}; - Vec2() = default; - Vec2(const T& _x, const T& _y) : x(_x), y(_y) {} + constexpr Vec2() = default; + constexpr Vec2(const T& x_, const T& y_) : x(x_), y(y_) {} template <typename T2> - Vec2<T2> Cast() const { - return Vec2<T2>((T2)x, (T2)y); + constexpr Vec2<T2> Cast() const { + return Vec2<T2>(static_cast<T2>(x), static_cast<T2>(y)); } - static Vec2 AssignToAll(const T& f) { - return Vec2<T>(f, f); + static constexpr Vec2 AssignToAll(const T& f) { + return Vec2{f, f}; } - Vec2<decltype(T{} + T{})> operator+(const Vec2& other) const { - return MakeVec(x + other.x, y + other.y); + constexpr Vec2<decltype(T{} + T{})> operator+(const Vec2& other) const { + return {x + other.x, y + other.y}; } - void operator+=(const Vec2& other) { + constexpr Vec2& operator+=(const Vec2& other) { x += other.x; y += other.y; + return *this; } - Vec2<decltype(T{} - T{})> operator-(const Vec2& other) const { - return MakeVec(x - other.x, y - other.y); + constexpr Vec2<decltype(T{} - T{})> operator-(const Vec2& other) const { + return {x - other.x, y - other.y}; } - void operator-=(const Vec2& other) { + constexpr Vec2& operator-=(const Vec2& other) { x -= other.x; y -= other.y; + return *this; } template <typename U = T> - Vec2<std::enable_if_t<std::is_signed<U>::value, U>> operator-() const { - return MakeVec(-x, -y); + constexpr Vec2<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const { + return {-x, -y}; } - Vec2<decltype(T{} * T{})> operator*(const Vec2& other) const { - return MakeVec(x * other.x, y * other.y); + constexpr Vec2<decltype(T{} * T{})> operator*(const Vec2& other) const { + return {x * other.x, y * other.y}; } + template <typename V> - Vec2<decltype(T{} * V{})> operator*(const V& f) const { - return MakeVec(x * f, y * f); + constexpr Vec2<decltype(T{} * V{})> operator*(const V& f) const { + return {x * f, y * f}; } + template <typename V> - void operator*=(const V& f) { + constexpr Vec2& operator*=(const V& f) { *this = *this * f; + return *this; } + template <typename V> - Vec2<decltype(T{} / V{})> operator/(const V& f) const { - return MakeVec(x / f, y / f); + constexpr Vec2<decltype(T{} / V{})> operator/(const V& f) const { + return {x / f, y / f}; } + template <typename V> - void operator/=(const V& f) { + constexpr Vec2& operator/=(const V& f) { *this = *this / f; + return *this; } - T Length2() const { + constexpr T Length2() const { return x * x + y * y; } // Only implemented for T=float float Length() const; - void SetLength(const float l); - Vec2 WithLength(const float l) const; - float Distance2To(Vec2& other); - Vec2 Normalized() const; float Normalize(); // returns the previous length, which is often useful - T& operator[](int i) // allow vector[1] = 3 (vector.y=3) - { + constexpr T& operator[](std::size_t i) { return *((&x) + i); } - T operator[](const int i) const { + constexpr const T& operator[](std::size_t i) const { return *((&x) + i); } - void SetZero() { + constexpr void SetZero() { x = 0; y = 0; } // Common aliases: UV (texel coordinates), ST (texture coordinates) - T& u() { + constexpr T& u() { return x; } - T& v() { + constexpr T& v() { return y; } - T& s() { + constexpr T& s() { return x; } - T& t() { + constexpr T& t() { return y; } - const T& u() const { + constexpr const T& u() const { return x; } - const T& v() const { + constexpr const T& v() const { return y; } - const T& s() const { + constexpr const T& s() const { return x; } - const T& t() const { + constexpr const T& t() const { return y; } // swizzlers - create a subvector of specific components - const Vec2 yx() const { + constexpr Vec2 yx() const { return Vec2(y, x); } - const Vec2 vu() const { + constexpr Vec2 vu() const { return Vec2(y, x); } - const Vec2 ts() const { + constexpr Vec2 ts() const { return Vec2(y, x); } }; template <typename T, typename V> -Vec2<T> operator*(const V& f, const Vec2<T>& vec) { +constexpr Vec2<T> operator*(const V& f, const Vec2<T>& vec) { return Vec2<T>(f * vec.x, f * vec.y); } -typedef Vec2<float> Vec2f; +using Vec2f = Vec2<float>; template <> inline float Vec2<float>::Length() const { @@ -196,147 +192,151 @@ public: T y{}; T z{}; - Vec3() = default; - Vec3(const T& _x, const T& _y, const T& _z) : x(_x), y(_y), z(_z) {} + constexpr Vec3() = default; + constexpr Vec3(const T& x_, const T& y_, const T& z_) : x(x_), y(y_), z(z_) {} template <typename T2> - Vec3<T2> Cast() const { - return MakeVec<T2>((T2)x, (T2)y, (T2)z); + constexpr Vec3<T2> Cast() const { + return Vec3<T2>(static_cast<T2>(x), static_cast<T2>(y), static_cast<T2>(z)); } - // Only implemented for T=int and T=float - static Vec3 FromRGB(unsigned int rgb); - unsigned int ToRGB() const; // alpha bits set to zero - - static Vec3 AssignToAll(const T& f) { - return MakeVec(f, f, f); + static constexpr Vec3 AssignToAll(const T& f) { + return Vec3(f, f, f); } - Vec3<decltype(T{} + T{})> operator+(const Vec3& other) const { - return MakeVec(x + other.x, y + other.y, z + other.z); + constexpr Vec3<decltype(T{} + T{})> operator+(const Vec3& other) const { + return {x + other.x, y + other.y, z + other.z}; } - void operator+=(const Vec3& other) { + + constexpr Vec3& operator+=(const Vec3& other) { x += other.x; y += other.y; z += other.z; + return *this; } - Vec3<decltype(T{} - T{})> operator-(const Vec3& other) const { - return MakeVec(x - other.x, y - other.y, z - other.z); + + constexpr Vec3<decltype(T{} - T{})> operator-(const Vec3& other) const { + return {x - other.x, y - other.y, z - other.z}; } - void operator-=(const Vec3& other) { + + constexpr Vec3& operator-=(const Vec3& other) { x -= other.x; y -= other.y; z -= other.z; + return *this; } template <typename U = T> - Vec3<std::enable_if_t<std::is_signed<U>::value, U>> operator-() const { - return MakeVec(-x, -y, -z); + constexpr Vec3<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const { + return {-x, -y, -z}; } - Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const { - return MakeVec(x * other.x, y * other.y, z * other.z); + + constexpr Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const { + return {x * other.x, y * other.y, z * other.z}; } + template <typename V> - Vec3<decltype(T{} * V{})> operator*(const V& f) const { - return MakeVec(x * f, y * f, z * f); + constexpr Vec3<decltype(T{} * V{})> operator*(const V& f) const { + return {x * f, y * f, z * f}; } + template <typename V> - void operator*=(const V& f) { + constexpr Vec3& operator*=(const V& f) { *this = *this * f; + return *this; } template <typename V> - Vec3<decltype(T{} / V{})> operator/(const V& f) const { - return MakeVec(x / f, y / f, z / f); + constexpr Vec3<decltype(T{} / V{})> operator/(const V& f) const { + return {x / f, y / f, z / f}; } + template <typename V> - void operator/=(const V& f) { + constexpr Vec3& operator/=(const V& f) { *this = *this / f; + return *this; } - T Length2() const { + constexpr T Length2() const { return x * x + y * y + z * z; } // Only implemented for T=float float Length() const; - void SetLength(const float l); - Vec3 WithLength(const float l) const; - float Distance2To(Vec3& other); Vec3 Normalized() const; float Normalize(); // returns the previous length, which is often useful - T& operator[](int i) // allow vector[2] = 3 (vector.z=3) - { + constexpr T& operator[](std::size_t i) { return *((&x) + i); } - T operator[](const int i) const { + + constexpr const T& operator[](std::size_t i) const { return *((&x) + i); } - void SetZero() { + constexpr void SetZero() { x = 0; y = 0; z = 0; } // Common aliases: UVW (texel coordinates), RGB (colors), STQ (texture coordinates) - T& u() { + constexpr T& u() { return x; } - T& v() { + constexpr T& v() { return y; } - T& w() { + constexpr T& w() { return z; } - T& r() { + constexpr T& r() { return x; } - T& g() { + constexpr T& g() { return y; } - T& b() { + constexpr T& b() { return z; } - T& s() { + constexpr T& s() { return x; } - T& t() { + constexpr T& t() { return y; } - T& q() { + constexpr T& q() { return z; } - const T& u() const { + constexpr const T& u() const { return x; } - const T& v() const { + constexpr const T& v() const { return y; } - const T& w() const { + constexpr const T& w() const { return z; } - const T& r() const { + constexpr const T& r() const { return x; } - const T& g() const { + constexpr const T& g() const { return y; } - const T& b() const { + constexpr const T& b() const { return z; } - const T& s() const { + constexpr const T& s() const { return x; } - const T& t() const { + constexpr const T& t() const { return y; } - const T& q() const { + constexpr const T& q() const { return z; } @@ -345,7 +345,7 @@ public: // _DEFINE_SWIZZLER2 defines a single such function, DEFINE_SWIZZLER2 defines all of them for all // component names (x<->r) and permutations (xy<->yx) #define _DEFINE_SWIZZLER2(a, b, name) \ - const Vec2<T> name() const { \ + constexpr Vec2<T> name() const { \ return Vec2<T>(a, b); \ } #define DEFINE_SWIZZLER2(a, b, a2, b2, a3, b3, a4, b4) \ @@ -366,7 +366,7 @@ public: }; template <typename T, typename V> -Vec3<T> operator*(const V& f, const Vec3<T>& vec) { +constexpr Vec3<T> operator*(const V& f, const Vec3<T>& vec) { return Vec3<T>(f * vec.x, f * vec.y, f * vec.z); } @@ -387,7 +387,7 @@ inline float Vec3<float>::Normalize() { return length; } -typedef Vec3<float> Vec3f; +using Vec3f = Vec3<float>; template <typename T> class Vec4 { @@ -397,86 +397,88 @@ public: T z{}; T w{}; - Vec4() = default; - Vec4(const T& _x, const T& _y, const T& _z, const T& _w) : x(_x), y(_y), z(_z), w(_w) {} + constexpr Vec4() = default; + constexpr Vec4(const T& x_, const T& y_, const T& z_, const T& w_) + : x(x_), y(y_), z(z_), w(w_) {} template <typename T2> - Vec4<T2> Cast() const { - return Vec4<T2>((T2)x, (T2)y, (T2)z, (T2)w); + constexpr Vec4<T2> Cast() const { + return Vec4<T2>(static_cast<T2>(x), static_cast<T2>(y), static_cast<T2>(z), + static_cast<T2>(w)); } - // Only implemented for T=int and T=float - static Vec4 FromRGBA(unsigned int rgba); - unsigned int ToRGBA() const; - - static Vec4 AssignToAll(const T& f) { - return Vec4<T>(f, f, f, f); + static constexpr Vec4 AssignToAll(const T& f) { + return Vec4(f, f, f, f); } - Vec4<decltype(T{} + T{})> operator+(const Vec4& other) const { - return MakeVec(x + other.x, y + other.y, z + other.z, w + other.w); + constexpr Vec4<decltype(T{} + T{})> operator+(const Vec4& other) const { + return {x + other.x, y + other.y, z + other.z, w + other.w}; } - void operator+=(const Vec4& other) { + + constexpr Vec4& operator+=(const Vec4& other) { x += other.x; y += other.y; z += other.z; w += other.w; + return *this; } - Vec4<decltype(T{} - T{})> operator-(const Vec4& other) const { - return MakeVec(x - other.x, y - other.y, z - other.z, w - other.w); + + constexpr Vec4<decltype(T{} - T{})> operator-(const Vec4& other) const { + return {x - other.x, y - other.y, z - other.z, w - other.w}; } - void operator-=(const Vec4& other) { + + constexpr Vec4& operator-=(const Vec4& other) { x -= other.x; y -= other.y; z -= other.z; w -= other.w; + return *this; } template <typename U = T> - Vec4<std::enable_if_t<std::is_signed<U>::value, U>> operator-() const { - return MakeVec(-x, -y, -z, -w); + constexpr Vec4<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const { + return {-x, -y, -z, -w}; } - Vec4<decltype(T{} * T{})> operator*(const Vec4& other) const { - return MakeVec(x * other.x, y * other.y, z * other.z, w * other.w); + + constexpr Vec4<decltype(T{} * T{})> operator*(const Vec4& other) const { + return {x * other.x, y * other.y, z * other.z, w * other.w}; } + template <typename V> - Vec4<decltype(T{} * V{})> operator*(const V& f) const { - return MakeVec(x * f, y * f, z * f, w * f); + constexpr Vec4<decltype(T{} * V{})> operator*(const V& f) const { + return {x * f, y * f, z * f, w * f}; } + template <typename V> - void operator*=(const V& f) { + constexpr Vec4& operator*=(const V& f) { *this = *this * f; + return *this; } + template <typename V> - Vec4<decltype(T{} / V{})> operator/(const V& f) const { - return MakeVec(x / f, y / f, z / f, w / f); + constexpr Vec4<decltype(T{} / V{})> operator/(const V& f) const { + return {x / f, y / f, z / f, w / f}; } + template <typename V> - void operator/=(const V& f) { + constexpr Vec4& operator/=(const V& f) { *this = *this / f; + return *this; } - T Length2() const { + constexpr T Length2() const { return x * x + y * y + z * z + w * w; } - // Only implemented for T=float - float Length() const; - void SetLength(const float l); - Vec4 WithLength(const float l) const; - float Distance2To(Vec4& other); - Vec4 Normalized() const; - float Normalize(); // returns the previous length, which is often useful - - T& operator[](int i) // allow vector[2] = 3 (vector.z=3) - { + constexpr T& operator[](std::size_t i) { return *((&x) + i); } - T operator[](const int i) const { + + constexpr const T& operator[](std::size_t i) const { return *((&x) + i); } - void SetZero() { + constexpr void SetZero() { x = 0; y = 0; z = 0; @@ -484,29 +486,29 @@ public: } // Common alias: RGBA (colors) - T& r() { + constexpr T& r() { return x; } - T& g() { + constexpr T& g() { return y; } - T& b() { + constexpr T& b() { return z; } - T& a() { + constexpr T& a() { return w; } - const T& r() const { + constexpr const T& r() const { return x; } - const T& g() const { + constexpr const T& g() const { return y; } - const T& b() const { + constexpr const T& b() const { return z; } - const T& a() const { + constexpr const T& a() const { return w; } @@ -518,7 +520,7 @@ public: // DEFINE_SWIZZLER2_COMP2 defines two component functions for all component names (x<->r) and // permutations (xy<->yx) #define _DEFINE_SWIZZLER2(a, b, name) \ - const Vec2<T> name() const { \ + constexpr Vec2<T> name() const { \ return Vec2<T>(a, b); \ } #define DEFINE_SWIZZLER2_COMP1(a, a2) \ @@ -545,7 +547,7 @@ public: #undef _DEFINE_SWIZZLER2 #define _DEFINE_SWIZZLER3(a, b, c, name) \ - const Vec3<T> name() const { \ + constexpr Vec3<T> name() const { \ return Vec3<T>(a, b, c); \ } #define DEFINE_SWIZZLER3_COMP1(a, a2) \ @@ -579,51 +581,51 @@ public: }; template <typename T, typename V> -Vec4<decltype(V{} * T{})> operator*(const V& f, const Vec4<T>& vec) { - return MakeVec(f * vec.x, f * vec.y, f * vec.z, f * vec.w); +constexpr Vec4<decltype(V{} * T{})> operator*(const V& f, const Vec4<T>& vec) { + return {f * vec.x, f * vec.y, f * vec.z, f * vec.w}; } -typedef Vec4<float> Vec4f; +using Vec4f = Vec4<float>; template <typename T> -static inline decltype(T{} * T{} + T{} * T{}) Dot(const Vec2<T>& a, const Vec2<T>& b) { +constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec2<T>& a, const Vec2<T>& b) { return a.x * b.x + a.y * b.y; } template <typename T> -static inline decltype(T{} * T{} + T{} * T{}) Dot(const Vec3<T>& a, const Vec3<T>& b) { +constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec3<T>& a, const Vec3<T>& b) { return a.x * b.x + a.y * b.y + a.z * b.z; } template <typename T> -static inline decltype(T{} * T{} + T{} * T{}) Dot(const Vec4<T>& a, const Vec4<T>& b) { +constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec4<T>& a, const Vec4<T>& b) { return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; } template <typename T> -static inline Vec3<decltype(T{} * T{} - T{} * T{})> Cross(const Vec3<T>& a, const Vec3<T>& b) { - return MakeVec(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); +constexpr Vec3<decltype(T{} * T{} - T{} * T{})> Cross(const Vec3<T>& a, const Vec3<T>& b) { + return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x}; } // linear interpolation via float: 0.0=begin, 1.0=end template <typename X> -static inline decltype(X{} * float{} + X{} * float{}) Lerp(const X& begin, const X& end, - const float t) { +constexpr decltype(X{} * float{} + X{} * float{}) Lerp(const X& begin, const X& end, + const float t) { return begin * (1.f - t) + end * t; } // linear interpolation via int: 0=begin, base=end template <typename X, int base> -static inline decltype((X{} * int{} + X{} * int{}) / base) LerpInt(const X& begin, const X& end, - const int t) { +constexpr decltype((X{} * int{} + X{} * int{}) / base) LerpInt(const X& begin, const X& end, + const int t) { return (begin * (base - t) + end * t) / base; } // bilinear interpolation. s is for interpolating x00-x01 and x10-x11, and t is for the second // interpolation. template <typename X> -inline auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X& x11, const float s, - const float t) { +constexpr auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X& x11, const float s, + const float t) { auto y0 = Lerp(x00, x01, s); auto y1 = Lerp(x10, x11, s); return Lerp(y0, y1, t); @@ -631,42 +633,42 @@ inline auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X& x1 // Utility vector factories template <typename T> -static inline Vec2<T> MakeVec(const T& x, const T& y) { +constexpr Vec2<T> MakeVec(const T& x, const T& y) { return Vec2<T>{x, y}; } template <typename T> -static inline Vec3<T> MakeVec(const T& x, const T& y, const T& z) { +constexpr Vec3<T> MakeVec(const T& x, const T& y, const T& z) { return Vec3<T>{x, y, z}; } template <typename T> -static inline Vec4<T> MakeVec(const T& x, const T& y, const Vec2<T>& zw) { +constexpr Vec4<T> MakeVec(const T& x, const T& y, const Vec2<T>& zw) { return MakeVec(x, y, zw[0], zw[1]); } template <typename T> -static inline Vec3<T> MakeVec(const Vec2<T>& xy, const T& z) { +constexpr Vec3<T> MakeVec(const Vec2<T>& xy, const T& z) { return MakeVec(xy[0], xy[1], z); } template <typename T> -static inline Vec3<T> MakeVec(const T& x, const Vec2<T>& yz) { +constexpr Vec3<T> MakeVec(const T& x, const Vec2<T>& yz) { return MakeVec(x, yz[0], yz[1]); } template <typename T> -static inline Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w) { +constexpr Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w) { return Vec4<T>{x, y, z, w}; } template <typename T> -static inline Vec4<T> MakeVec(const Vec2<T>& xy, const T& z, const T& w) { +constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const T& z, const T& w) { return MakeVec(xy[0], xy[1], z, w); } template <typename T> -static inline Vec4<T> MakeVec(const T& x, const Vec2<T>& yz, const T& w) { +constexpr Vec4<T> MakeVec(const T& x, const Vec2<T>& yz, const T& w) { return MakeVec(x, yz[0], yz[1], w); } @@ -674,17 +676,17 @@ static inline Vec4<T> MakeVec(const T& x, const Vec2<T>& yz, const T& w) { // Even if someone wanted to use an odd object like Vec2<Vec2<T>>, the compiler would error // out soon enough due to misuse of the returned structure. template <typename T> -static inline Vec4<T> MakeVec(const Vec2<T>& xy, const Vec2<T>& zw) { +constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const Vec2<T>& zw) { return MakeVec(xy[0], xy[1], zw[0], zw[1]); } template <typename T> -static inline Vec4<T> MakeVec(const Vec3<T>& xyz, const T& w) { +constexpr Vec4<T> MakeVec(const Vec3<T>& xyz, const T& w) { return MakeVec(xyz[0], xyz[1], xyz[2], w); } template <typename T> -static inline Vec4<T> MakeVec(const T& x, const Vec3<T>& yzw) { +constexpr Vec4<T> MakeVec(const T& x, const Vec3<T>& yzw) { return MakeVec(x, yzw[0], yzw[1], yzw[2]); } diff --git a/src/common/x64/xbyak_util.h b/src/common/x64/xbyak_util.h index 0f52f704b..ec76e0a47 100644 --- a/src/common/x64/xbyak_util.h +++ b/src/common/x64/xbyak_util.h @@ -34,7 +34,7 @@ inline bool IsWithin2G(const Xbyak::CodeGenerator& code, uintptr_t target) { template <typename T> inline void CallFarFunction(Xbyak::CodeGenerator& code, const T f) { - static_assert(std::is_pointer<T>(), "Argument must be a (function) pointer."); + static_assert(std::is_pointer_v<T>, "Argument must be a (function) pointer."); size_t addr = reinterpret_cast<size_t>(f); if (IsWithin2G(code, addr)) { code.call(f); diff --git a/src/core/core.cpp b/src/core/core.cpp index 085ba68d0..69c45c026 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -89,7 +89,7 @@ System::ResultStatus System::SingleStep() { } System::ResultStatus System::Load(EmuWindow& emu_window, const std::string& filepath) { - app_loader = Loader::GetLoader(std::make_shared<FileSys::RealVfsFile>(filepath)); + app_loader = Loader::GetLoader(virtual_filesystem->OpenFile(filepath, FileSys::Mode::Read)); if (!app_loader) { LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath); @@ -174,6 +174,10 @@ System::ResultStatus System::Init(EmuWindow& emu_window) { CoreTiming::Init(); + // Create a default fs if one doesn't already exist. + if (virtual_filesystem == nullptr) + virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>(); + current_process = Kernel::Process::Create("main"); cpu_barrier = std::make_shared<CpuBarrier>(); @@ -186,7 +190,7 @@ System::ResultStatus System::Init(EmuWindow& emu_window) { service_manager = std::make_shared<Service::SM::ServiceManager>(); Kernel::Init(); - Service::Init(service_manager); + Service::Init(service_manager, virtual_filesystem); GDBStub::Init(); renderer = VideoCore::CreateRenderer(emu_window); diff --git a/src/core/core.h b/src/core/core.h index c8ca4b247..7cf7ea4e1 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -17,6 +17,8 @@ #include "core/memory.h" #include "core/perf_stats.h" #include "core/telemetry_session.h" +#include "file_sys/vfs_real.h" +#include "hle/service/filesystem/filesystem.h" #include "video_core/debug_utils/debug_utils.h" #include "video_core/gpu.h" @@ -211,6 +213,14 @@ public: return debug_context; } + void SetFilesystem(FileSys::VirtualFilesystem vfs) { + virtual_filesystem = std::move(vfs); + } + + FileSys::VirtualFilesystem GetFilesystem() const { + return virtual_filesystem; + } + private: System(); @@ -225,6 +235,8 @@ private: */ ResultStatus Init(EmuWindow& emu_window); + /// RealVfsFilesystem instance + FileSys::VirtualFilesystem virtual_filesystem; /// AppLoader used to load the current executing application std::unique_ptr<Loader::AppLoader> app_loader; std::unique_ptr<VideoCore::RendererBase> renderer; diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index 395eea8ae..e897d9913 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -5,6 +5,7 @@ #include <array> #include <string> #include <core/loader/loader.h> +#include "common/logging/log.h" #include "core/file_sys/card_image.h" #include "core/file_sys/partition_filesystem.h" #include "core/file_sys/vfs_offset.h" diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 3529166ac..d3007d981 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -170,6 +170,10 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting } NCA::NCA(VirtualFile file_) : file(std::move(file_)) { + if (file == nullptr) { + status = Loader::ResultStatus::ErrorInvalidFormat; + return; + } if (sizeof(NCAHeader) != file->ReadObject(&header)) LOG_ERROR(Loader, "File reader errored out during header read."); diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index a8879d9a8..5cfd5031a 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -12,6 +12,7 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" +#include "control_metadata.h" #include "core/crypto/key_manager.h" #include "core/file_sys/partition_filesystem.h" #include "core/loader/loader.h" diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h index cc3b745f7..6582cc240 100644 --- a/src/core/file_sys/control_metadata.h +++ b/src/core/file_sys/control_metadata.h @@ -62,6 +62,13 @@ enum class Language : u8 { Chinese = 14, }; +static constexpr std::array<const char*, 15> LANGUAGE_NAMES = { + "AmericanEnglish", "BritishEnglish", "Japanese", + "French", "German", "LatinAmericanSpanish", + "Spanish", "Italian", "Dutch", + "CanadianFrench", "Portugese", "Russian", + "Korean", "Taiwanese", "Chinese"}; + // A class representing the format used by NX metadata files, typically named Control.nacp. // These store application name, dev name, title id, and other miscellaneous data. class NACP { diff --git a/src/core/file_sys/directory.h b/src/core/file_sys/directory.h index 213ce1826..3759e743a 100644 --- a/src/core/file_sys/directory.h +++ b/src/core/file_sys/directory.h @@ -4,8 +4,9 @@ #pragma once -#include <array> #include <cstddef> +#include <iterator> +#include <string_view> #include "common/common_funcs.h" #include "common/common_types.h" @@ -21,9 +22,14 @@ enum EntryType : u8 { // Structure of a directory entry, from // http://switchbrew.org/index.php?title=Filesystem_services#DirectoryEntry -const size_t FILENAME_LENGTH = 0x300; struct Entry { - char filename[FILENAME_LENGTH]; + Entry(std::string_view view, EntryType entry_type, u64 entry_size) + : type{entry_type}, file_size{entry_size} { + const size_t copy_size = view.copy(filename, std::size(filename) - 1); + filename[copy_size] = '\0'; + } + + char filename[0x300]; INSERT_PADDING_BYTES(4); EntryType type; INSERT_PADDING_BYTES(3); diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h index e3a578c0f..f3cf50d5a 100644 --- a/src/core/file_sys/savedata_factory.h +++ b/src/core/file_sys/savedata_factory.h @@ -7,6 +7,7 @@ #include <memory> #include <string> #include "common/common_types.h" +#include "common/swap.h" #include "core/hle/result.h" namespace FileSys { diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp index dae1c16ef..24e158962 100644 --- a/src/core/file_sys/vfs.cpp +++ b/src/core/file_sys/vfs.cpp @@ -4,12 +4,160 @@ #include <algorithm> #include <numeric> +#include <string> +#include "common/common_paths.h" #include "common/file_util.h" #include "common/logging/backend.h" #include "core/file_sys/vfs.h" namespace FileSys { +VfsFilesystem::VfsFilesystem(VirtualDir root_) : root(std::move(root_)) {} + +VfsFilesystem::~VfsFilesystem() = default; + +std::string VfsFilesystem::GetName() const { + return root->GetName(); +} + +bool VfsFilesystem::IsReadable() const { + return root->IsReadable(); +} + +bool VfsFilesystem::IsWritable() const { + return root->IsWritable(); +} + +VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const { + const auto path = FileUtil::SanitizePath(path_); + if (root->GetFileRelative(path) != nullptr) + return VfsEntryType::File; + if (root->GetDirectoryRelative(path) != nullptr) + return VfsEntryType::Directory; + + return VfsEntryType::None; +} + +VirtualFile VfsFilesystem::OpenFile(std::string_view path_, Mode perms) { + const auto path = FileUtil::SanitizePath(path_); + return root->GetFileRelative(path); +} + +VirtualFile VfsFilesystem::CreateFile(std::string_view path_, Mode perms) { + const auto path = FileUtil::SanitizePath(path_); + return root->CreateFileRelative(path); +} + +VirtualFile VfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) { + const auto old_path = FileUtil::SanitizePath(old_path_); + const auto new_path = FileUtil::SanitizePath(new_path_); + + // VfsDirectory impls are only required to implement copy across the current directory. + if (FileUtil::GetParentPath(old_path) == FileUtil::GetParentPath(new_path)) { + if (!root->Copy(FileUtil::GetFilename(old_path), FileUtil::GetFilename(new_path))) + return nullptr; + return OpenFile(new_path, Mode::ReadWrite); + } + + // Do it using RawCopy. Non-default impls are encouraged to optimize this. + const auto old_file = OpenFile(old_path, Mode::Read); + if (old_file == nullptr) + return nullptr; + auto new_file = OpenFile(new_path, Mode::Read); + if (new_file != nullptr) + return nullptr; + new_file = CreateFile(new_path, Mode::Write); + if (new_file == nullptr) + return nullptr; + if (!VfsRawCopy(old_file, new_file)) + return nullptr; + return new_file; +} + +VirtualFile VfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) { + const auto old_path = FileUtil::SanitizePath(old_path_); + const auto new_path = FileUtil::SanitizePath(new_path_); + + // Again, non-default impls are highly encouraged to provide a more optimized version of this. + auto out = CopyFile(old_path_, new_path_); + if (out == nullptr) + return nullptr; + if (DeleteFile(old_path)) + return out; + return nullptr; +} + +bool VfsFilesystem::DeleteFile(std::string_view path_) { + const auto path = FileUtil::SanitizePath(path_); + auto parent = OpenDirectory(FileUtil::GetParentPath(path), Mode::Write); + if (parent == nullptr) + return false; + return parent->DeleteFile(FileUtil::GetFilename(path)); +} + +VirtualDir VfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) { + const auto path = FileUtil::SanitizePath(path_); + return root->GetDirectoryRelative(path); +} + +VirtualDir VfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) { + const auto path = FileUtil::SanitizePath(path_); + return root->CreateDirectoryRelative(path); +} + +VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_view new_path_) { + const auto old_path = FileUtil::SanitizePath(old_path_); + const auto new_path = FileUtil::SanitizePath(new_path_); + + // Non-default impls are highly encouraged to provide a more optimized version of this. + auto old_dir = OpenDirectory(old_path, Mode::Read); + if (old_dir == nullptr) + return nullptr; + auto new_dir = OpenDirectory(new_path, Mode::Read); + if (new_dir != nullptr) + return nullptr; + new_dir = CreateDirectory(new_path, Mode::Write); + if (new_dir == nullptr) + return nullptr; + + for (const auto& file : old_dir->GetFiles()) { + const auto x = + CopyFile(old_path + DIR_SEP + file->GetName(), new_path + DIR_SEP + file->GetName()); + if (x == nullptr) + return nullptr; + } + + for (const auto& dir : old_dir->GetSubdirectories()) { + const auto x = + CopyDirectory(old_path + DIR_SEP + dir->GetName(), new_path + DIR_SEP + dir->GetName()); + if (x == nullptr) + return nullptr; + } + + return new_dir; +} + +VirtualDir VfsFilesystem::MoveDirectory(std::string_view old_path_, std::string_view new_path_) { + const auto old_path = FileUtil::SanitizePath(old_path_); + const auto new_path = FileUtil::SanitizePath(new_path_); + + // Non-default impls are highly encouraged to provide a more optimized version of this. + auto out = CopyDirectory(old_path_, new_path_); + if (out == nullptr) + return nullptr; + if (DeleteDirectory(old_path)) + return out; + return nullptr; +} + +bool VfsFilesystem::DeleteDirectory(std::string_view path_) { + const auto path = FileUtil::SanitizePath(path_); + auto parent = OpenDirectory(FileUtil::GetParentPath(path), Mode::Write); + if (parent == nullptr) + return false; + return parent->DeleteSubdirectoryRecursive(FileUtil::GetFilename(path)); +} + VfsFile::~VfsFile() = default; std::string VfsFile::GetExtension() const { diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h index fab9e2b45..141a053ce 100644 --- a/src/core/file_sys/vfs.h +++ b/src/core/file_sys/vfs.h @@ -11,14 +11,74 @@ #include <vector> #include "boost/optional.hpp" #include "common/common_types.h" +#include "core/file_sys/mode.h" namespace FileSys { + +struct VfsFilesystem; struct VfsFile; struct VfsDirectory; -// Convenience typedefs to use VfsDirectory and VfsFile -using VirtualDir = std::shared_ptr<FileSys::VfsDirectory>; -using VirtualFile = std::shared_ptr<FileSys::VfsFile>; +// Convenience typedefs to use Vfs* interfaces +using VirtualFilesystem = std::shared_ptr<VfsFilesystem>; +using VirtualDir = std::shared_ptr<VfsDirectory>; +using VirtualFile = std::shared_ptr<VfsFile>; + +// An enumeration representing what can be at the end of a path in a VfsFilesystem +enum class VfsEntryType { + None, + File, + Directory, +}; + +// A class representing an abstract filesystem. A default implementation given the root VirtualDir +// is provided for convenience, but if the Vfs implementation has any additional state or +// functionality, they will need to override. +struct VfsFilesystem : NonCopyable { + VfsFilesystem(VirtualDir root); + virtual ~VfsFilesystem(); + + // Gets the friendly name for the filesystem. + virtual std::string GetName() const; + + // Return whether or not the user has read permissions on this filesystem. + virtual bool IsReadable() const; + // Return whether or not the user has write permission on this filesystem. + virtual bool IsWritable() const; + + // Determine if the entry at path is non-existant, a file, or a directory. + virtual VfsEntryType GetEntryType(std::string_view path) const; + + // Opens the file with path relative to root. If it doesn't exist, returns nullptr. + virtual VirtualFile OpenFile(std::string_view path, Mode perms); + // Creates a new, empty file at path + virtual VirtualFile CreateFile(std::string_view path, Mode perms); + // Copies the file from old_path to new_path, returning the new file on success and nullptr on + // failure. + virtual VirtualFile CopyFile(std::string_view old_path, std::string_view new_path); + // Moves the file from old_path to new_path, returning the moved file on success and nullptr on + // failure. + virtual VirtualFile MoveFile(std::string_view old_path, std::string_view new_path); + // Deletes the file with path relative to root, returing true on success. + virtual bool DeleteFile(std::string_view path); + + // Opens the directory with path relative to root. If it doesn't exist, returns nullptr. + virtual VirtualDir OpenDirectory(std::string_view path, Mode perms); + // Creates a new, empty directory at path + virtual VirtualDir CreateDirectory(std::string_view path, Mode perms); + // Copies the directory from old_path to new_path, returning the new directory on success and + // nullptr on failure. + virtual VirtualDir CopyDirectory(std::string_view old_path, std::string_view new_path); + // Moves the directory from old_path to new_path, returning the moved directory on success and + // nullptr on failure. + virtual VirtualDir MoveDirectory(std::string_view old_path, std::string_view new_path); + // Deletes the directory with path relative to root, returing true on success. + virtual bool DeleteDirectory(std::string_view path); + +protected: + // Root directory in default implementation. + VirtualDir root; +}; // A class representing a file in an abstract filesystem. struct VfsFile : NonCopyable { diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index 82d54da4a..1b5919737 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp @@ -6,7 +6,7 @@ #include <cstddef> #include <iterator> #include <utility> - +#include "common/assert.h" #include "common/common_paths.h" #include "common/logging/log.h" #include "core/file_sys/vfs_real.h" @@ -29,6 +29,8 @@ static std::string ModeFlagsToString(Mode mode) { mode_str = "a"; else if (mode & Mode::Write) mode_str = "w"; + else + UNREACHABLE_MSG("Invalid file open mode: {:02X}", static_cast<u8>(mode)); } mode_str += "b"; @@ -36,8 +38,174 @@ static std::string ModeFlagsToString(Mode mode) { return mode_str; } -RealVfsFile::RealVfsFile(const std::string& path_, Mode perms_) - : backing(path_, ModeFlagsToString(perms_).c_str()), path(path_), +RealVfsFilesystem::RealVfsFilesystem() : VfsFilesystem(nullptr) {} + +std::string RealVfsFilesystem::GetName() const { + return "Real"; +} + +bool RealVfsFilesystem::IsReadable() const { + return true; +} + +bool RealVfsFilesystem::IsWritable() const { + return true; +} + +VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const { + const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); + if (!FileUtil::Exists(path)) + return VfsEntryType::None; + if (FileUtil::IsDirectory(path)) + return VfsEntryType::Directory; + + return VfsEntryType::File; +} + +VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { + const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); + if (cache.find(path) != cache.end()) { + auto weak = cache[path]; + if (!weak.expired()) { + return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, weak.lock(), path, perms)); + } + } + + if (!FileUtil::Exists(path) && (perms & Mode::WriteAppend) != 0) + FileUtil::CreateEmptyFile(path); + + auto backing = std::make_shared<FileUtil::IOFile>(path, ModeFlagsToString(perms).c_str()); + cache[path] = backing; + + // Cannot use make_shared as RealVfsFile constructor is private + return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, backing, path, perms)); +} + +VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { + const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); + if (!FileUtil::Exists(path) && !FileUtil::CreateEmptyFile(path)) + return nullptr; + return OpenFile(path, perms); +} + +VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) { + const auto old_path = + FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault); + const auto new_path = + FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault); + + if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) || + FileUtil::IsDirectory(old_path) || !FileUtil::Copy(old_path, new_path)) + return nullptr; + return OpenFile(new_path, Mode::ReadWrite); +} + +VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) { + const auto old_path = + FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault); + const auto new_path = + FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault); + + if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) || + FileUtil::IsDirectory(old_path) || !FileUtil::Rename(old_path, new_path)) + return nullptr; + + if (cache.find(old_path) != cache.end()) { + auto cached = cache[old_path]; + if (!cached.expired()) { + auto file = cached.lock(); + file->Open(new_path, "r+b"); + cache.erase(old_path); + cache[new_path] = file; + } + } + return OpenFile(new_path, Mode::ReadWrite); +} + +bool RealVfsFilesystem::DeleteFile(std::string_view path_) { + const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); + if (cache.find(path) != cache.end()) { + if (!cache[path].expired()) + cache[path].lock()->Close(); + cache.erase(path); + } + return FileUtil::Delete(path); +} + +VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) { + const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); + // Cannot use make_shared as RealVfsDirectory constructor is private + return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); +} + +VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) { + const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); + if (!FileUtil::Exists(path) && !FileUtil::CreateDir(path)) + return nullptr; + // Cannot use make_shared as RealVfsDirectory constructor is private + return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); +} + +VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_, + std::string_view new_path_) { + const auto old_path = + FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault); + const auto new_path = + FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault); + if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) || + !FileUtil::IsDirectory(old_path)) + return nullptr; + FileUtil::CopyDir(old_path, new_path); + return OpenDirectory(new_path, Mode::ReadWrite); +} + +VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_, + std::string_view new_path_) { + const auto old_path = + FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault); + const auto new_path = + FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault); + if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) || + FileUtil::IsDirectory(old_path) || !FileUtil::Rename(old_path, new_path)) + return nullptr; + + for (auto& kv : cache) { + // Path in cache starts with old_path + if (kv.first.rfind(old_path, 0) == 0) { + const auto file_old_path = + FileUtil::SanitizePath(kv.first, FileUtil::DirectorySeparator::PlatformDefault); + const auto file_new_path = + FileUtil::SanitizePath(new_path + DIR_SEP + kv.first.substr(old_path.size()), + FileUtil::DirectorySeparator::PlatformDefault); + auto cached = cache[file_old_path]; + if (!cached.expired()) { + auto file = cached.lock(); + file->Open(file_new_path, "r+b"); + cache.erase(file_old_path); + cache[file_new_path] = file; + } + } + } + + return OpenDirectory(new_path, Mode::ReadWrite); +} + +bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) { + const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); + for (auto& kv : cache) { + // Path in cache starts with old_path + if (kv.first.rfind(path, 0) == 0) { + if (!cache[kv.first].expired()) + cache[kv.first].lock()->Close(); + cache.erase(kv.first); + } + } + return FileUtil::DeleteDirRecursively(path); +} + +RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FileUtil::IOFile> backing_, + const std::string& path_, Mode perms_) + : base(base_), backing(std::move(backing_)), path(path_), parent_path(FileUtil::GetParentPath(path_)), path_components(FileUtil::SplitPathComponents(path_)), parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)), @@ -48,15 +216,15 @@ std::string RealVfsFile::GetName() const { } size_t RealVfsFile::GetSize() const { - return backing.GetSize(); + return backing->GetSize(); } bool RealVfsFile::Resize(size_t new_size) { - return backing.Resize(new_size); + return backing->Resize(new_size); } std::shared_ptr<VfsDirectory> RealVfsFile::GetContainingDirectory() const { - return std::make_shared<RealVfsDirectory>(parent_path, perms); + return base.OpenDirectory(parent_path, perms); } bool RealVfsFile::IsWritable() const { @@ -68,62 +236,118 @@ bool RealVfsFile::IsReadable() const { } size_t RealVfsFile::Read(u8* data, size_t length, size_t offset) const { - if (!backing.Seek(offset, SEEK_SET)) + if (!backing->Seek(offset, SEEK_SET)) return 0; - return backing.ReadBytes(data, length); + return backing->ReadBytes(data, length); } size_t RealVfsFile::Write(const u8* data, size_t length, size_t offset) { - if (!backing.Seek(offset, SEEK_SET)) + if (!backing->Seek(offset, SEEK_SET)) return 0; - return backing.WriteBytes(data, length); + return backing->WriteBytes(data, length); } bool RealVfsFile::Rename(std::string_view name) { - std::string name_str(name.begin(), name.end()); - const auto out = FileUtil::Rename(GetName(), name_str); + return base.MoveFile(path, parent_path + DIR_SEP + std::string(name)) != nullptr; +} + +bool RealVfsFile::Close() { + return backing->Close(); +} - path = (parent_path + DIR_SEP).append(name); - path_components = parent_components; - path_components.push_back(std::move(name_str)); - backing = FileUtil::IOFile(path, ModeFlagsToString(perms).c_str()); +// TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if +// constexpr' because there is a compile error in the branch not used. + +template <> +std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>() const { + if (perms == Mode::Append) + return {}; + + std::vector<VirtualFile> out; + FileUtil::ForeachDirectoryEntry( + nullptr, path, + [&out, this](u64* entries_out, const std::string& directory, const std::string& filename) { + const std::string full_path = directory + DIR_SEP + filename; + if (!FileUtil::IsDirectory(full_path)) + out.emplace_back(base.OpenFile(full_path, perms)); + return true; + }); return out; } -bool RealVfsFile::Close() { - return backing.Close(); +template <> +std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDirectory>() const { + if (perms == Mode::Append) + return {}; + + std::vector<VirtualDir> out; + FileUtil::ForeachDirectoryEntry( + nullptr, path, + [&out, this](u64* entries_out, const std::string& directory, const std::string& filename) { + const std::string full_path = directory + DIR_SEP + filename; + if (FileUtil::IsDirectory(full_path)) + out.emplace_back(base.OpenDirectory(full_path, perms)); + return true; + }); + + return out; } -RealVfsDirectory::RealVfsDirectory(const std::string& path_, Mode perms_) - : path(FileUtil::RemoveTrailingSlash(path_)), parent_path(FileUtil::GetParentPath(path)), +RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_, Mode perms_) + : base(base_), path(FileUtil::RemoveTrailingSlash(path_)), + parent_path(FileUtil::GetParentPath(path)), path_components(FileUtil::SplitPathComponents(path)), parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)), perms(perms_) { if (!FileUtil::Exists(path) && perms & Mode::WriteAppend) FileUtil::CreateDir(path); +} - if (perms == Mode::Append) - return; +std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const { + const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path)); + if (!FileUtil::Exists(full_path)) + return nullptr; + return base.OpenFile(full_path, perms); +} - FileUtil::ForeachDirectoryEntry( - nullptr, path, - [this](u64* entries_out, const std::string& directory, const std::string& filename) { - std::string full_path = directory + DIR_SEP + filename; - if (FileUtil::IsDirectory(full_path)) - subdirectories.emplace_back(std::make_shared<RealVfsDirectory>(full_path, perms)); - else - files.emplace_back(std::make_shared<RealVfsFile>(full_path, perms)); - return true; - }); +std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string_view path) const { + const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path)); + if (!FileUtil::Exists(full_path)) + return nullptr; + return base.OpenDirectory(full_path, perms); +} + +std::shared_ptr<VfsFile> RealVfsDirectory::GetFile(std::string_view name) const { + return GetFileRelative(name); +} + +std::shared_ptr<VfsDirectory> RealVfsDirectory::GetSubdirectory(std::string_view name) const { + return GetDirectoryRelative(name); +} + +std::shared_ptr<VfsFile> RealVfsDirectory::CreateFileRelative(std::string_view path) { + const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path)); + return base.CreateFile(full_path, perms); +} + +std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateDirectoryRelative(std::string_view path) { + const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path)); + auto parent = std::string(FileUtil::GetParentPath(full_path)); + return base.CreateDirectory(full_path, perms); +} + +bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) { + auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(name)); + return base.DeleteDirectory(full_path); } std::vector<std::shared_ptr<VfsFile>> RealVfsDirectory::GetFiles() const { - return files; + return IterateEntries<RealVfsFile, VfsFile>(); } std::vector<std::shared_ptr<VfsDirectory>> RealVfsDirectory::GetSubdirectories() const { - return subdirectories; + return IterateEntries<RealVfsDirectory, VfsDirectory>(); } bool RealVfsDirectory::IsWritable() const { @@ -142,57 +366,32 @@ std::shared_ptr<VfsDirectory> RealVfsDirectory::GetParentDirectory() const { if (path_components.size() <= 1) return nullptr; - return std::make_shared<RealVfsDirectory>(parent_path, perms); + return base.OpenDirectory(parent_path, perms); } std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateSubdirectory(std::string_view name) { const std::string subdir_path = (path + DIR_SEP).append(name); - - if (!FileUtil::CreateDir(subdir_path)) { - return nullptr; - } - - subdirectories.emplace_back(std::make_shared<RealVfsDirectory>(subdir_path, perms)); - return subdirectories.back(); + return base.CreateDirectory(subdir_path, perms); } std::shared_ptr<VfsFile> RealVfsDirectory::CreateFile(std::string_view name) { const std::string file_path = (path + DIR_SEP).append(name); - - if (!FileUtil::CreateEmptyFile(file_path)) { - return nullptr; - } - - files.emplace_back(std::make_shared<RealVfsFile>(file_path, perms)); - return files.back(); + return base.CreateFile(file_path, perms); } bool RealVfsDirectory::DeleteSubdirectory(std::string_view name) { const std::string subdir_path = (path + DIR_SEP).append(name); - - return FileUtil::DeleteDirRecursively(subdir_path); + return base.DeleteDirectory(subdir_path); } bool RealVfsDirectory::DeleteFile(std::string_view name) { - const auto file = GetFile(name); - - if (file == nullptr) { - return false; - } - - files.erase(std::find(files.begin(), files.end(), file)); - - auto real_file = std::static_pointer_cast<RealVfsFile>(file); - real_file->Close(); - const std::string file_path = (path + DIR_SEP).append(name); - return FileUtil::Delete(file_path); + return base.DeleteFile(file_path); } bool RealVfsDirectory::Rename(std::string_view name) { const std::string new_name = (parent_path + DIR_SEP).append(name); - - return FileUtil::Rename(path, new_name); + return base.MoveFile(path, new_name) != nullptr; } std::string RealVfsDirectory::GetFullPath() const { @@ -202,16 +401,6 @@ std::string RealVfsDirectory::GetFullPath() const { } bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { - const auto iter = std::find(files.begin(), files.end(), file); - if (iter == files.end()) - return false; - - const std::ptrdiff_t offset = std::distance(files.begin(), iter); - files[offset] = files.back(); - files.pop_back(); - - subdirectories.emplace_back(std::move(dir)); - - return true; + return false; } } // namespace FileSys diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h index 243d58576..8a1e79ef6 100644 --- a/src/core/file_sys/vfs_real.h +++ b/src/core/file_sys/vfs_real.h @@ -6,18 +6,45 @@ #include <string_view> +#include <boost/container/flat_map.hpp> #include "common/file_util.h" #include "core/file_sys/mode.h" #include "core/file_sys/vfs.h" namespace FileSys { +class RealVfsFilesystem : public VfsFilesystem { +public: + RealVfsFilesystem(); + + std::string GetName() const override; + bool IsReadable() const override; + bool IsWritable() const override; + VfsEntryType GetEntryType(std::string_view path) const override; + VirtualFile OpenFile(std::string_view path, Mode perms = Mode::Read) override; + VirtualFile CreateFile(std::string_view path, Mode perms = Mode::ReadWrite) override; + VirtualFile CopyFile(std::string_view old_path, std::string_view new_path) override; + VirtualFile MoveFile(std::string_view old_path, std::string_view new_path) override; + bool DeleteFile(std::string_view path) override; + VirtualDir OpenDirectory(std::string_view path, Mode perms = Mode::Read) override; + VirtualDir CreateDirectory(std::string_view path, Mode perms = Mode::ReadWrite) override; + VirtualDir CopyDirectory(std::string_view old_path, std::string_view new_path) override; + VirtualDir MoveDirectory(std::string_view old_path, std::string_view new_path) override; + bool DeleteDirectory(std::string_view path) override; + +private: + boost::container::flat_map<std::string, std::weak_ptr<FileUtil::IOFile>> cache; +}; + // An implmentation of VfsFile that represents a file on the user's computer. -struct RealVfsFile : public VfsFile { - friend struct RealVfsDirectory; +class RealVfsFile : public VfsFile { + friend class RealVfsDirectory; + friend class RealVfsFilesystem; - RealVfsFile(const std::string& name, Mode perms = Mode::Read); + RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<FileUtil::IOFile> backing, + const std::string& path, Mode perms = Mode::Read); +public: std::string GetName() const override; size_t GetSize() const override; bool Resize(size_t new_size) override; @@ -31,7 +58,8 @@ struct RealVfsFile : public VfsFile { private: bool Close(); - FileUtil::IOFile backing; + RealVfsFilesystem& base; + std::shared_ptr<FileUtil::IOFile> backing; std::string path; std::string parent_path; std::vector<std::string> path_components; @@ -40,9 +68,19 @@ private: }; // An implementation of VfsDirectory that represents a directory on the user's computer. -struct RealVfsDirectory : public VfsDirectory { - RealVfsDirectory(const std::string& path, Mode perms = Mode::Read); +class RealVfsDirectory : public VfsDirectory { + friend class RealVfsFilesystem; + RealVfsDirectory(RealVfsFilesystem& base, const std::string& path, Mode perms = Mode::Read); + +public: + std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const override; + std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const override; + std::shared_ptr<VfsFile> GetFile(std::string_view name) const override; + std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const override; + std::shared_ptr<VfsFile> CreateFileRelative(std::string_view path) override; + std::shared_ptr<VfsDirectory> CreateDirectoryRelative(std::string_view path) override; + bool DeleteSubdirectoryRecursive(std::string_view name) override; std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; bool IsWritable() const override; @@ -60,13 +98,15 @@ protected: bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; private: + template <typename T, typename R> + std::vector<std::shared_ptr<R>> IterateEntries() const; + + RealVfsFilesystem& base; std::string path; std::string parent_path; std::vector<std::string> path_components; std::vector<std::string> parent_components; Mode perms; - std::vector<std::shared_ptr<VfsFile>> files; - std::vector<std::shared_ptr<VfsDirectory>> subdirectories; }; } // namespace FileSys diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 5dd1b68d7..82a3fb5a8 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -201,7 +201,7 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdb return RESULT_SUCCESS; } -ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(Thread& thread) { +ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread) { std::array<u32, IPC::COMMAND_BUFFER_LENGTH> dst_cmdbuf; Memory::ReadBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(), dst_cmdbuf.size() * sizeof(u32)); diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index 9ce52db24..f0d07f1b6 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -132,7 +132,7 @@ public: ResultCode PopulateFromIncomingCommandBuffer(u32_le* src_cmdbuf, Process& src_process, HandleTable& src_table); /// Writes data from this context back to the requesting process/thread. - ResultCode WriteToOutgoingCommandBuffer(Thread& thread); + ResultCode WriteToOutgoingCommandBuffer(const Thread& thread); u32_le GetCommand() const { return command; diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index 0a6cac5b7..e74379a24 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -38,7 +38,7 @@ public: {0, &IProfile::Get, "Get"}, {1, &IProfile::GetBase, "GetBase"}, {10, nullptr, "GetImageSize"}, - {11, nullptr, "LoadImage"}, + {11, &IProfile::LoadImage, "LoadImage"}, }; RegisterHandlers(functions); } @@ -72,6 +72,27 @@ private: } } + void LoadImage(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_ACC, "(STUBBED) called"); + // smallest jpeg https://github.com/mathiasbynens/small/blob/master/jpeg.jpg + // TODO(mailwl): load actual profile image from disk, width 256px, max size 0x20000 + const u32 jpeg_size = 107; + static const std::array<u8, jpeg_size> jpeg{ + 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, + 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x08, 0x06, 0x06, 0x05, 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, + 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e, 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, + 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13, 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, + 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x11, 0x00, + 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, + 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9, + }; + ctx.WriteBuffer(jpeg.data(), jpeg_size); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(jpeg_size); + } + ProfileManager& profile_manager; UUID user_id; ///< The user id this profile refers to. }; diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 9404d6b8c..762763463 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -136,7 +136,7 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger {16, &ISelfController::SetOutOfFocusSuspendingEnabled, "SetOutOfFocusSuspendingEnabled"}, {17, nullptr, "SetControllerFirmwareUpdateSection"}, {18, nullptr, "SetRequiresCaptureButtonShortPressedMessage"}, - {19, nullptr, "SetScreenShotImageOrientation"}, + {19, &ISelfController::SetScreenShotImageOrientation, "SetScreenShotImageOrientation"}, {20, nullptr, "SetDesirableKeyboardLayout"}, {40, &ISelfController::CreateManagedDisplayLayer, "CreateManagedDisplayLayer"}, {41, nullptr, "IsSystemBufferSharingEnabled"}, @@ -254,6 +254,13 @@ void ISelfController::GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& LOG_WARNING(Service_AM, "(STUBBED) called"); } +void ISelfController::SetScreenShotImageOrientation(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + void ISelfController::CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx) { // TODO(Subv): Find out how AM determines the display to use, for now just create the layer // in the Default display. diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 8f4f98346..862f338ac 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -83,6 +83,7 @@ private: void LockExit(Kernel::HLERequestContext& ctx); void UnlockExit(Kernel::HLERequestContext& ctx); void GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& ctx); + void SetScreenShotImageOrientation(Kernel::HLERequestContext& ctx); void CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx); void SetScreenShotPermission(Kernel::HLERequestContext& ctx); void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index e17d637e4..5e416cde2 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -59,7 +59,7 @@ ResultCode VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64 ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) const { std::string path(FileUtil::SanitizePath(path_)); auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); - if (path == "/" || path == "\\") { + if (path.empty()) { // TODO(DarkLordZach): Why do games call this and what should it do? Works as is but... return RESULT_SUCCESS; } @@ -281,15 +281,15 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() { return sdmc_factory->Open(); } -void RegisterFileSystems() { +void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) { romfs_factory = nullptr; save_data_factory = nullptr; sdmc_factory = nullptr; - auto nand_directory = std::make_shared<FileSys::RealVfsDirectory>( - FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), FileSys::Mode::ReadWrite); - auto sd_directory = std::make_shared<FileSys::RealVfsDirectory>( - FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), FileSys::Mode::ReadWrite); + auto nand_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), + FileSys::Mode::ReadWrite); + auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), + FileSys::Mode::ReadWrite); auto savedata = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory)); save_data_factory = std::move(savedata); @@ -298,8 +298,8 @@ void RegisterFileSystems() { sdmc_factory = std::move(sdcard); } -void InstallInterfaces(SM::ServiceManager& service_manager) { - RegisterFileSystems(); +void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs) { + RegisterFileSystems(vfs); std::make_shared<FSP_LDR>()->InstallAsService(service_manager); std::make_shared<FSP_PR>()->InstallAsService(service_manager); std::make_shared<FSP_SRV>()->InstallAsService(service_manager); diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index d4483daa5..462c13f20 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -36,7 +36,7 @@ ResultVal<FileSys::VirtualDir> OpenSDMC(); // ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenBIS(); /// Registers all Filesystem services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs); // A class that wraps a VfsDirectory with methods that return ResultVal and ResultCode instead of // pointers and booleans. This makes using a VfsDirectory with switch services much easier and diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index e7ffb6bd1..1470f9017 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -193,13 +193,10 @@ private: template <typename T> static void BuildEntryIndex(std::vector<FileSys::Entry>& entries, const std::vector<T>& new_data, FileSys::EntryType type) { + entries.reserve(entries.size() + new_data.size()); + for (const auto& new_entry : new_data) { - FileSys::Entry entry; - entry.filename[0] = '\0'; - std::strncat(entry.filename, new_entry->GetName().c_str(), FileSys::FILENAME_LENGTH - 1); - entry.type = type; - entry.file_size = new_entry->GetSize(); - entries.emplace_back(std::move(entry)); + entries.emplace_back(new_entry->GetName(), type, new_entry->GetSize()); } } diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index 116dabedb..4cdf7f613 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -147,7 +147,7 @@ u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& outp } params.fence_out.id = 0; params.fence_out.value = 0; - std::memcpy(output.data(), ¶ms, output.size()); + std::memcpy(output.data(), ¶ms, sizeof(IoctlSubmitGpfifo)); return 0; } diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp index e8b30921a..427f4b574 100644 --- a/src/core/hle/service/nvdrv/nvdrv.cpp +++ b/src/core/hle/service/nvdrv/nvdrv.cpp @@ -16,19 +16,18 @@ #include "core/hle/service/nvdrv/interface.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvdrv/nvmemp.h" +#include "core/hle/service/nvflinger/nvflinger.h" namespace Service::Nvidia { -std::weak_ptr<Module> nvdrv; - -void InstallInterfaces(SM::ServiceManager& service_manager) { +void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger) { auto module_ = std::make_shared<Module>(); std::make_shared<NVDRV>(module_, "nvdrv")->InstallAsService(service_manager); std::make_shared<NVDRV>(module_, "nvdrv:a")->InstallAsService(service_manager); std::make_shared<NVDRV>(module_, "nvdrv:s")->InstallAsService(service_manager); std::make_shared<NVDRV>(module_, "nvdrv:t")->InstallAsService(service_manager); std::make_shared<NVMEMP>()->InstallAsService(service_manager); - nvdrv = module_; + nvflinger.SetNVDrvInstance(module_); } Module::Module() { diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h index 184f3c9fc..99eb1128a 100644 --- a/src/core/hle/service/nvdrv/nvdrv.h +++ b/src/core/hle/service/nvdrv/nvdrv.h @@ -10,6 +10,10 @@ #include "common/common_types.h" #include "core/hle/service/service.h" +namespace Service::NVFlinger { +class NVFlinger; +} + namespace Service::Nvidia { namespace Devices { @@ -56,8 +60,6 @@ private: }; /// Registers all NVDRV services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); - -extern std::weak_ptr<Module> nvdrv; +void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger); } // namespace Service::Nvidia diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp index adf180509..ef5713a71 100644 --- a/src/core/hle/service/nvflinger/buffer_queue.cpp +++ b/src/core/hle/service/nvflinger/buffer_queue.cpp @@ -16,7 +16,7 @@ BufferQueue::BufferQueue(u32 id, u64 layer_id) : id(id), layer_id(layer_id) { Kernel::Event::Create(Kernel::ResetType::Sticky, "BufferQueue NativeHandle"); } -void BufferQueue::SetPreallocatedBuffer(u32 slot, IGBPBuffer& igbp_buffer) { +void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer) { Buffer buffer{}; buffer.slot = slot; buffer.igbp_buffer = igbp_buffer; diff --git a/src/core/hle/service/nvflinger/buffer_queue.h b/src/core/hle/service/nvflinger/buffer_queue.h index 004170538..f86e1056c 100644 --- a/src/core/hle/service/nvflinger/buffer_queue.h +++ b/src/core/hle/service/nvflinger/buffer_queue.h @@ -72,7 +72,7 @@ public: MathUtil::Rectangle<int> crop_rect; }; - void SetPreallocatedBuffer(u32 slot, IGBPBuffer& buffer); + void SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer); boost::optional<u32> DequeueBuffer(u32 width, u32 height); const IGBPBuffer& RequestBuffer(u32 slot) const; void QueueBuffer(u32 slot, BufferTransformFlags transform, diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index 570aa8493..a26a5f812 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -46,6 +46,10 @@ NVFlinger::~NVFlinger() { CoreTiming::UnscheduleEvent(composition_event, 0); } +void NVFlinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) { + nvdrv = std::move(instance); +} + u64 NVFlinger::OpenDisplay(std::string_view name) { LOG_WARNING(Service, "Opening display {}", name); @@ -141,9 +145,6 @@ void NVFlinger::Compose() { auto& igbp_buffer = buffer->igbp_buffer; // Now send the buffer to the GPU for drawing. - auto nvdrv = Nvidia::nvdrv.lock(); - ASSERT(nvdrv); - // TODO(Subv): Support more than just disp0. The display device selection is probably based // on which display we're drawing (Default, Internal, External, etc) auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>("/dev/nvdisp_disp0"); diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h index 5374df175..f7112949f 100644 --- a/src/core/hle/service/nvflinger/nvflinger.h +++ b/src/core/hle/service/nvflinger/nvflinger.h @@ -16,6 +16,10 @@ namespace CoreTiming { struct EventType; } +namespace Service::Nvidia { +class Module; +} + namespace Service::NVFlinger { class BufferQueue; @@ -44,6 +48,9 @@ public: NVFlinger(); ~NVFlinger(); + /// Sets the NVDrv module instance to use to send buffers to the GPU. + void SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance); + /// Opens the specified display and returns the id. u64 OpenDisplay(std::string_view name); @@ -70,6 +77,8 @@ private: /// Returns the layer identified by the specified id in the desired display. Layer& GetLayer(u64 display_id, u64 layer_id); + std::shared_ptr<Nvidia::Module> nvdrv; + std::vector<Display> displays; std::vector<std::shared_ptr<BufferQueue>> buffer_queues; diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 889cdd41a..11951adaf 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -198,7 +198,7 @@ void AddNamedPort(std::string name, SharedPtr<ClientPort> port) { } /// Initialize ServiceManager -void Init(std::shared_ptr<SM::ServiceManager>& sm) { +void Init(std::shared_ptr<SM::ServiceManager>& sm, const FileSys::VirtualFilesystem& rfs) { // NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it // here and pass it into the respective InstallInterfaces functions. auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>(); @@ -221,7 +221,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm) { EUPLD::InstallInterfaces(*sm); Fatal::InstallInterfaces(*sm); FGM::InstallInterfaces(*sm); - FileSystem::InstallInterfaces(*sm); + FileSystem::InstallInterfaces(*sm, rfs); Friend::InstallInterfaces(*sm); GRC::InstallInterfaces(*sm); HID::InstallInterfaces(*sm); @@ -238,7 +238,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm) { NIFM::InstallInterfaces(*sm); NIM::InstallInterfaces(*sm); NS::InstallInterfaces(*sm); - Nvidia::InstallInterfaces(*sm); + Nvidia::InstallInterfaces(*sm, *nv_flinger); PCIe::InstallInterfaces(*sm); PCTL::InstallInterfaces(*sm); PCV::InstallInterfaces(*sm); diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 046c5e18d..8a294c0f2 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -22,6 +22,10 @@ class ServerSession; class HLERequestContext; } // namespace Kernel +namespace FileSys { +struct VfsFilesystem; +} + namespace Service { namespace SM { @@ -177,7 +181,8 @@ private: }; /// Initialize ServiceManager -void Init(std::shared_ptr<SM::ServiceManager>& sm); +void Init(std::shared_ptr<SM::ServiceManager>& sm, + const std::shared_ptr<FileSys::VfsFilesystem>& vfs); /// Shutdown ServiceManager void Shutdown(); diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index 9a8cdd0ff..915d525b0 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -7,6 +7,7 @@ #include "common/file_util.h" #include "common/logging/log.h" #include "core/file_sys/content_archive.h" +#include "core/file_sys/control_metadata.h" #include "core/gdbstub/gdbstub.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" @@ -17,8 +18,50 @@ namespace Loader { -AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file) - : AppLoader(std::move(file)) {} +AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_) + : AppLoader(std::move(file_)) { + const auto dir = file->GetContainingDirectory(); + + // Icon + FileSys::VirtualFile icon_file = nullptr; + for (const auto& language : FileSys::LANGUAGE_NAMES) { + icon_file = dir->GetFile("icon_" + std::string(language) + ".dat"); + if (icon_file != nullptr) { + icon_data = icon_file->ReadAllBytes(); + break; + } + } + + if (icon_data.empty()) { + // Any png, jpeg, or bmp file + const auto& files = dir->GetFiles(); + const auto icon_iter = + std::find_if(files.begin(), files.end(), [](const FileSys::VirtualFile& file) { + return file->GetExtension() == "png" || file->GetExtension() == "jpg" || + file->GetExtension() == "bmp" || file->GetExtension() == "jpeg"; + }); + if (icon_iter != files.end()) + icon_data = (*icon_iter)->ReadAllBytes(); + } + + // Metadata + FileSys::VirtualFile nacp_file = dir->GetFile("control.nacp"); + if (nacp_file == nullptr) { + const auto& files = dir->GetFiles(); + const auto nacp_iter = + std::find_if(files.begin(), files.end(), [](const FileSys::VirtualFile& file) { + return file->GetExtension() == "nacp"; + }); + if (nacp_iter != files.end()) + nacp_file = *nacp_iter; + } + + if (nacp_file != nullptr) { + FileSys::NACP nacp(nacp_file); + title_id = nacp.GetTitleId(); + name = nacp.GetApplicationName(); + } +} AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory( FileSys::VirtualDir directory) @@ -105,4 +148,25 @@ ResultStatus AppLoader_DeconstructedRomDirectory::ReadRomFS(FileSys::VirtualFile return ResultStatus::Success; } +ResultStatus AppLoader_DeconstructedRomDirectory::ReadIcon(std::vector<u8>& buffer) { + if (icon_data.empty()) + return ResultStatus::ErrorNotUsed; + buffer = icon_data; + return ResultStatus::Success; +} + +ResultStatus AppLoader_DeconstructedRomDirectory::ReadProgramId(u64& out_program_id) { + if (name.empty()) + return ResultStatus::ErrorNotUsed; + out_program_id = title_id; + return ResultStatus::Success; +} + +ResultStatus AppLoader_DeconstructedRomDirectory::ReadTitle(std::string& title) { + if (name.empty()) + return ResultStatus::ErrorNotUsed; + title = name; + return ResultStatus::Success; +} + } // namespace Loader diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h index 7d5433563..b20804f75 100644 --- a/src/core/loader/deconstructed_rom_directory.h +++ b/src/core/loader/deconstructed_rom_directory.h @@ -39,11 +39,18 @@ public: ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; + ResultStatus ReadIcon(std::vector<u8>& buffer) override; + ResultStatus ReadProgramId(u64& out_program_id) override; + ResultStatus ReadTitle(std::string& title) override; private: FileSys::ProgramMetadata metadata; FileSys::VirtualFile romfs; FileSys::VirtualDir dir; + + std::vector<u8> icon_data; + std::string name; + u64 title_id{}; }; } // namespace Loader diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 57e6c0365..a288654df 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -43,10 +43,6 @@ FileType IdentifyFile(FileSys::VirtualFile file) { return FileType::Unknown; } -FileType IdentifyFile(const std::string& file_name) { - return IdentifyFile(std::make_shared<FileSys::RealVfsFile>(file_name)); -} - FileType GuessFromFilename(const std::string& name) { if (name == "main") return FileType::DeconstructedRomDirectory; @@ -68,7 +64,7 @@ FileType GuessFromFilename(const std::string& name) { return FileType::Unknown; } -const char* GetFileTypeString(FileType type) { +std::string GetFileTypeString(FileType type) { switch (type) { case FileType::ELF: return "ELF"; diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index e69ab85ef..6a9e5a68b 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -43,14 +43,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 * @return FileType of file. Note: this will return FileType::Unknown if it is unable to determine @@ -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 { diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp index dbc67c0b5..46f5cd393 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp @@ -77,8 +77,8 @@ ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { } ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { - if (nca == nullptr) - return ResultStatus::ErrorNotLoaded; + if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) + return ResultStatus::ErrorInvalidFormat; out_program_id = nca->GetTitleId(); return ResultStatus::Success; } diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index 0fd2d0417..7f7d8ea0b 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h @@ -33,7 +33,6 @@ public: ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; - ResultStatus ReadProgramId(u64& out_program_id) override; ~AppLoader_NCA(); @@ -41,6 +40,7 @@ public: private: FileSys::ProgramMetadata metadata; + FileSys::NCAHeader header; std::unique_ptr<FileSys::NCA> nca; std::unique_ptr<AppLoader_DeconstructedRomDirectory> directory_loader; }; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index eb4dee2c2..d3fe24419 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -26,7 +26,25 @@ namespace Loader { AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file) : AppLoader(file), xci(std::make_unique<FileSys::XCI>(file)), nca_loader(std::make_unique<AppLoader_NCA>( - xci->GetNCAFileByType(FileSys::NCAContentType::Program))) {} + xci->GetNCAFileByType(FileSys::NCAContentType::Program))) { + if (xci->GetStatus() != ResultStatus::Success) + return; + const auto control_nca = xci->GetNCAByType(FileSys::NCAContentType::Control); + if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success) + return; + const auto romfs = FileSys::ExtractRomFS(control_nca->GetRomFS()); + if (romfs == nullptr) + return; + for (const auto& language : FileSys::LANGUAGE_NAMES) { + icon_file = romfs->GetFile("icon_" + std::string(language) + ".dat"); + if (icon_file != nullptr) + break; + } + const auto nacp_raw = romfs->GetFile("control.nacp"); + if (nacp_raw == nullptr) + return; + nacp_file = std::make_shared<FileSys::NACP>(nacp_raw); +} AppLoader_XCI::~AppLoader_XCI() = default; @@ -71,4 +89,17 @@ ResultStatus AppLoader_XCI::ReadProgramId(u64& out_program_id) { return nca_loader->ReadProgramId(out_program_id); } +ResultStatus AppLoader_XCI::ReadIcon(std::vector<u8>& buffer) { + if (icon_file == nullptr) + return ResultStatus::ErrorInvalidFormat; + buffer = icon_file->ReadAllBytes(); + return ResultStatus::Success; +} + +ResultStatus AppLoader_XCI::ReadTitle(std::string& title) { + if (nacp_file == nullptr) + return ResultStatus::ErrorInvalidFormat; + title = nacp_file->GetApplicationName(); + return ResultStatus::Success; +} } // namespace Loader diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index 0dbcfbdf8..973833050 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -33,12 +33,17 @@ public: ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; ResultStatus ReadProgramId(u64& out_program_id) override; + ResultStatus ReadIcon(std::vector<u8>& buffer) override; + ResultStatus ReadTitle(std::string& title) override; private: FileSys::ProgramMetadata metadata; std::unique_ptr<FileSys::XCI> xci; std::unique_ptr<AppLoader_NCA> nca_loader; + + FileSys::VirtualFile icon_file; + std::shared_ptr<FileSys::NACP> nacp_file; }; } // namespace Loader diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 5c0ae8009..a46ed4bd7 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -23,12 +23,17 @@ Maxwell3D::Maxwell3D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& : memory_manager(memory_manager), rasterizer{rasterizer}, macro_interpreter(*this) {} void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) { - auto macro_code = uploaded_macros.find(method); + // Reset the current macro. + executing_macro = 0; + // The requested macro must have been uploaded already. - ASSERT_MSG(macro_code != uploaded_macros.end(), "Macro %08X was not uploaded", method); + auto macro_code = uploaded_macros.find(method); + if (macro_code == uploaded_macros.end()) { + LOG_ERROR(HW_GPU, "Macro {:04X} was not uploaded", method); + return; + } - // Reset the current macro and execute it. - executing_macro = 0; + // Execute the current macro. macro_interpreter.Execute(macro_code->second, std::move(parameters)); } @@ -238,6 +243,8 @@ void Maxwell3D::ProcessCBBind(Regs::ShaderStage stage) { auto& buffer = shader.const_buffers[bind_data.index]; + ASSERT(bind_data.index < Regs::MaxConstBuffers); + buffer.enabled = bind_data.valid.Value() != 0; buffer.index = bind_data.index; buffer.address = regs.const_buffer.BufferAddress(); diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 4d0ff96a5..0506ac8fe 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -44,7 +44,7 @@ public: static constexpr size_t MaxShaderProgram = 6; static constexpr size_t MaxShaderStage = 5; // Maximum number of const buffers per shader stage. - static constexpr size_t MaxConstBuffers = 16; + static constexpr size_t MaxConstBuffers = 18; enum class QueryMode : u32 { Write = 0, diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index c7e3fb4b1..3d4557b7e 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -78,6 +78,8 @@ union Attribute { // shader, and a tuple of (TessCoord.x, TessCoord.y, TessCoord.z, ~) when inside a Tess Eval // shader. TessCoordInstanceIDVertexID = 47, + // TODO(bunnei): Figure out what this is used for. Super Mario Odyssey uses this. + Unknown_63 = 63, }; union { @@ -254,20 +256,15 @@ union Instruction { BitField<56, 1, u64> invert_b; } lop32i; - float GetImm20_19() const { - float result{}; + u32 GetImm20_19() const { u32 imm{static_cast<u32>(imm20_19)}; imm <<= 12; imm |= negate_imm ? 0x80000000 : 0; - std::memcpy(&result, &imm, sizeof(imm)); - return result; + return imm; } - float GetImm20_32() const { - float result{}; - s32 imm{static_cast<s32>(imm20_32)}; - std::memcpy(&result, &imm, sizeof(imm)); - return result; + u32 GetImm20_32() const { + return static_cast<u32>(imm20_32); } s32 GetSignedImm20_20() const { diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index b2a83ce0b..4ff4d71c5 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -42,6 +42,7 @@ u32 RenderTargetBytesPerPixel(RenderTargetFormat format) { case RenderTargetFormat::RGB10_A2_UNORM: case RenderTargetFormat::BGRA8_UNORM: case RenderTargetFormat::R32_FLOAT: + case RenderTargetFormat::R11G11B10_FLOAT: return 4; default: UNIMPLEMENTED_MSG("Unimplemented render target format {}", static_cast<u32>(format)); diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index 440505c9d..874eddd78 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -34,6 +34,7 @@ enum class RenderTargetFormat : u32 { RG16_FLOAT = 0xDE, R11G11B10_FLOAT = 0xE0, R32_FLOAT = 0xE5, + B5G6R5_UNORM = 0xE8, R16_FLOAT = 0xF2, R8_UNORM = 0xF3, }; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index c2a931469..8360feb5d 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -161,7 +161,7 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupVertexArrays(u8* array_ptr, // assume every shader uses them all. for (unsigned index = 0; index < 16; ++index) { auto& attrib = regs.vertex_attrib_format[index]; - LOG_DEBUG(HW_GPU, "vertex attrib {}, count={}, size={}, type={}, offset={}, normalize={}", + LOG_TRACE(HW_GPU, "vertex attrib {}, count={}, size={}, type={}, offset={}, normalize={}", index, attrib.ComponentCount(), attrib.SizeString(), attrib.TypeString(), attrib.offset.Value(), attrib.IsNormalized()); @@ -324,11 +324,14 @@ std::pair<Surface, Surface> RasterizerOpenGL::ConfigureFramebuffers(bool using_c bool using_depth_fb) { const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + if (regs.rt[0].format == Tegra::RenderTargetFormat::NONE) { + LOG_ERROR(HW_GPU, "RenderTargetFormat is not configured"); + using_color_fb = false; + } + // TODO(bunnei): Implement this const bool has_stencil = false; - const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[0].GetRect()}; - const bool write_color_fb = state.color_mask.red_enabled == GL_TRUE || state.color_mask.green_enabled == GL_TRUE || state.color_mask.blue_enabled == GL_TRUE || state.color_mask.alpha_enabled == GL_TRUE; @@ -341,9 +344,10 @@ std::pair<Surface, Surface> RasterizerOpenGL::ConfigureFramebuffers(bool using_c Surface depth_surface; MathUtil::Rectangle<u32> surfaces_rect; std::tie(color_surface, depth_surface, surfaces_rect) = - res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb, viewport_rect); + res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb); - MathUtil::Rectangle<u32> draw_rect{ + const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[0].GetRect()}; + const MathUtil::Rectangle<u32> draw_rect{ static_cast<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.left) + viewport_rect.left, surfaces_rect.left, surfaces_rect.right)), // Left static_cast<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.bottom) + viewport_rect.top, @@ -659,7 +663,10 @@ u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, GLuint progr auto& buffer_draw_state = state.draw.const_buffers[static_cast<size_t>(stage)][used_buffer.GetIndex()]; - ASSERT_MSG(buffer.enabled, "Attempted to upload disabled constbuffer"); + if (!buffer.enabled) { + continue; + } + buffer_draw_state.enabled = true; buffer_draw_state.bindpoint = current_bindpoint + bindpoint; @@ -804,9 +811,7 @@ void RasterizerOpenGL::SyncClipCoef() { void RasterizerOpenGL::SyncCullMode() { const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; - // TODO(bunnei): Enable the below once more things work - until then, this may hide regressions - // state.cull.enabled = regs.cull.enabled != 0; - state.cull.enabled = false; + state.cull.enabled = regs.cull.enabled != 0; if (state.cull.enabled) { state.cull.front_face = MaxwellToGL::FrontFace(regs.cull.front_face); diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 257aa9571..9fb734b77 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -109,6 +109,9 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, true}, // DXT45 {GL_COMPRESSED_RED_RGTC1, GL_RED, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, true}, // DXN1 + {GL_COMPRESSED_RG_RGTC2, GL_RG, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, + true}, // DXN2UNORM + {GL_COMPRESSED_SIGNED_RG_RGTC2, GL_RG, GL_INT, ComponentType::SNorm, true}, // DXN2SNORM {GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, true}, // BC7U {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4 @@ -180,36 +183,49 @@ MathUtil::Rectangle<u32> SurfaceParams::GetRect() const { return {0, actual_height, width, 0}; } +/// Returns true if the specified PixelFormat is a BCn format, e.g. DXT or DXN +static bool IsFormatBCn(PixelFormat format) { + switch (format) { + case PixelFormat::DXT1: + case PixelFormat::DXT23: + case PixelFormat::DXT45: + case PixelFormat::DXN1: + case PixelFormat::DXN2SNORM: + case PixelFormat::DXN2UNORM: + case PixelFormat::BC7U: + return true; + } + return false; +} + template <bool morton_to_gl, PixelFormat format> -void MortonCopy(u32 stride, u32 block_height, u32 height, u8* gl_buffer, Tegra::GPUVAddr addr) { +void MortonCopy(u32 stride, u32 block_height, u32 height, std::vector<u8>& gl_buffer, + Tegra::GPUVAddr addr) { constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / CHAR_BIT; constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format); const auto& gpu = Core::System::GetInstance().GPU(); if (morton_to_gl) { - if (SurfaceParams::GetFormatType(format) == SurfaceType::ColorTexture) { - auto data = Tegra::Texture::UnswizzleTexture( - *gpu.memory_manager->GpuToCpuAddress(addr), - SurfaceParams::TextureFormatFromPixelFormat(format), stride, height, block_height); - std::memcpy(gl_buffer, data.data(), data.size()); - } else { - auto data = Tegra::Texture::UnswizzleDepthTexture( - *gpu.memory_manager->GpuToCpuAddress(addr), - SurfaceParams::DepthFormatFromPixelFormat(format), stride, height, block_height); - std::memcpy(gl_buffer, data.data(), data.size()); - } + // With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual + // pixel values. + const u32 tile_size{IsFormatBCn(format) ? 4U : 1U}; + const std::vector<u8> data = + Tegra::Texture::UnswizzleTexture(*gpu.memory_manager->GpuToCpuAddress(addr), tile_size, + bytes_per_pixel, stride, height, block_height); + const size_t size_to_copy{std::min(gl_buffer.size(), data.size())}; + gl_buffer.assign(data.begin(), data.begin() + size_to_copy); } else { // TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should // check the configuration for this and perform more generic un/swizzle LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!"); VideoCore::MortonCopyPixels128( stride, height, bytes_per_pixel, gl_bytes_per_pixel, - Memory::GetPointer(*gpu.memory_manager->GpuToCpuAddress(addr)), gl_buffer, + Memory::GetPointer(*gpu.memory_manager->GpuToCpuAddress(addr)), gl_buffer.data(), morton_to_gl); } } -static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr), +static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPUVAddr), SurfaceParams::MaxPixelFormat> morton_to_gl_fns = { MortonCopy<true, PixelFormat::ABGR8>, MortonCopy<true, PixelFormat::B5G6R5>, @@ -218,6 +234,7 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr), MortonCopy<true, PixelFormat::R11FG11FB10F>, MortonCopy<true, PixelFormat::RGBA32UI>, MortonCopy<true, PixelFormat::DXT1>, MortonCopy<true, PixelFormat::DXT23>, MortonCopy<true, PixelFormat::DXT45>, MortonCopy<true, PixelFormat::DXN1>, + MortonCopy<true, PixelFormat::DXN2UNORM>, MortonCopy<true, PixelFormat::DXN2SNORM>, MortonCopy<true, PixelFormat::BC7U>, MortonCopy<true, PixelFormat::ASTC_2D_4X4>, MortonCopy<true, PixelFormat::G8R8>, MortonCopy<true, PixelFormat::BGRA8>, MortonCopy<true, PixelFormat::RGBA32F>, MortonCopy<true, PixelFormat::RG32F>, @@ -231,7 +248,7 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr), MortonCopy<true, PixelFormat::Z32FS8>, }; -static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr), +static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPUVAddr), SurfaceParams::MaxPixelFormat> gl_to_morton_fns = { MortonCopy<false, PixelFormat::ABGR8>, @@ -242,7 +259,10 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr), MortonCopy<false, PixelFormat::RGBA16F>, MortonCopy<false, PixelFormat::R11FG11FB10F>, MortonCopy<false, PixelFormat::RGBA32UI>, - // TODO(Subv): Swizzling DXT1/DXT23/DXT45/DXN1/BC7U/ASTC_2D_4X4 formats is not supported + // TODO(Subv): Swizzling DXT1/DXT23/DXT45/DXN1/DXN2/BC7U/ASTC_2D_4X4 formats is not + // supported + nullptr, + nullptr, nullptr, nullptr, nullptr, @@ -447,22 +467,24 @@ MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64 void CachedSurface::LoadGLBuffer() { ASSERT(params.type != SurfaceType::Fill); - u8* const texture_src_data = Memory::GetPointer(params.GetCpuAddr()); + const u8* const texture_src_data = Memory::GetPointer(params.GetCpuAddr()); ASSERT(texture_src_data); - gl_buffer.resize(params.width * params.height * GetGLBytesPerPixel(params.pixel_format)); + const u32 bytes_per_pixel = GetGLBytesPerPixel(params.pixel_format); + const u32 copy_size = params.width * params.height * bytes_per_pixel; MICROPROFILE_SCOPE(OpenGL_SurfaceLoad); - if (!params.is_tiled) { - const u32 bytes_per_pixel{params.GetFormatBpp() >> 3}; + if (params.is_tiled) { + gl_buffer.resize(copy_size); - std::memcpy(gl_buffer.data(), texture_src_data, - bytes_per_pixel * params.width * params.height); - } else { morton_to_gl_fns[static_cast<size_t>(params.pixel_format)]( - params.width, params.block_height, params.height, gl_buffer.data(), params.addr); + params.width, params.block_height, params.height, gl_buffer, params.addr); + } else { + const u8* const texture_src_data_end = texture_src_data + copy_size; + + gl_buffer.assign(texture_src_data, texture_src_data_end); } ConvertFormatAsNeeded_LoadGLBuffer(gl_buffer, params.pixel_format, params.width, params.height); @@ -485,7 +507,7 @@ void CachedSurface::FlushGLBuffer() { std::memcpy(dst_buffer, gl_buffer.data(), params.size_in_bytes); } else { gl_to_morton_fns[static_cast<size_t>(params.pixel_format)]( - params.width, params.block_height, params.height, gl_buffer.data(), params.addr); + params.width, params.block_height, params.height, gl_buffer, params.addr); } } @@ -600,8 +622,8 @@ Surface RasterizerCacheOpenGL::GetTextureSurface(const Tegra::Texture::FullTextu return GetSurface(SurfaceParams::CreateForTexture(config)); } -SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces( - bool using_color_fb, bool using_depth_fb, const MathUtil::Rectangle<s32>& viewport) { +SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces(bool using_color_fb, + bool using_depth_fb) { const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; // TODO(bunnei): This is hard corded to use just the first render buffer @@ -757,10 +779,12 @@ void RasterizerCacheOpenGL::FlushRegion(Tegra::GPUVAddr /*addr*/, size_t /*size* } void RasterizerCacheOpenGL::InvalidateRegion(Tegra::GPUVAddr addr, size_t size) { - for (const auto& pair : surface_cache) { - const auto& surface{pair.second}; + for (auto iter = surface_cache.cbegin(); iter != surface_cache.cend();) { + const auto& surface{iter->second}; const auto& params{surface->GetSurfaceParams()}; + ++iter; + if (params.IsOverlappingRegion(addr, size)) { UnregisterSurface(surface); } diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index 0c6652c7a..829a76dfe 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -35,31 +35,33 @@ struct SurfaceParams { DXT23 = 9, DXT45 = 10, DXN1 = 11, // This is also known as BC4 - BC7U = 12, - ASTC_2D_4X4 = 13, - G8R8 = 14, - BGRA8 = 15, - RGBA32F = 16, - RG32F = 17, - R32F = 18, - R16F = 19, - R16UNORM = 20, - RG16 = 21, - RG16F = 22, - RG16UI = 23, - RG16I = 24, - RG16S = 25, - RGB32F = 26, - SRGBA8 = 27, + DXN2UNORM = 12, + DXN2SNORM = 13, + BC7U = 14, + ASTC_2D_4X4 = 15, + G8R8 = 16, + BGRA8 = 17, + RGBA32F = 18, + RG32F = 19, + R32F = 20, + R16F = 21, + R16UNORM = 22, + RG16 = 23, + RG16F = 24, + RG16UI = 25, + RG16I = 26, + RG16S = 27, + RGB32F = 28, + SRGBA8 = 29, MaxColorFormat, // DepthStencil formats - Z24S8 = 28, - S8Z24 = 29, - Z32F = 30, - Z16 = 31, - Z32FS8 = 32, + Z24S8 = 30, + S8Z24 = 31, + Z32F = 32, + Z16 = 33, + Z32FS8 = 34, MaxDepthStencilFormat, @@ -109,6 +111,8 @@ struct SurfaceParams { 4, // DXT23 4, // DXT45 4, // DXN1 + 4, // DXN2UNORM + 4, // DXN2SNORM 4, // BC7U 4, // ASTC_2D_4X4 1, // G8R8 @@ -153,6 +157,8 @@ struct SurfaceParams { 128, // DXT23 128, // DXT45 64, // DXN1 + 128, // DXN2UNORM + 128, // DXN2SNORM 128, // BC7U 32, // ASTC_2D_4X4 16, // G8R8 @@ -221,6 +227,8 @@ struct SurfaceParams { return PixelFormat::RG32F; case Tegra::RenderTargetFormat::R11G11B10_FLOAT: return PixelFormat::R11FG11FB10F; + case Tegra::RenderTargetFormat::B5G6R5_UNORM: + return PixelFormat::B5G6R5; case Tegra::RenderTargetFormat::RGBA32_UINT: return PixelFormat::RGBA32UI; case Tegra::RenderTargetFormat::R8_UNORM: @@ -303,6 +311,16 @@ struct SurfaceParams { return PixelFormat::DXT45; case Tegra::Texture::TextureFormat::DXN1: return PixelFormat::DXN1; + case Tegra::Texture::TextureFormat::DXN2: + switch (component_type) { + case Tegra::Texture::ComponentType::UNORM: + return PixelFormat::DXN2UNORM; + case Tegra::Texture::ComponentType::SNORM: + return PixelFormat::DXN2SNORM; + } + LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", + static_cast<u32>(component_type)); + UNREACHABLE(); case Tegra::Texture::TextureFormat::BC7U: return PixelFormat::BC7U; case Tegra::Texture::TextureFormat::ASTC_2D_4X4: @@ -330,89 +348,6 @@ struct SurfaceParams { } } - static Tegra::Texture::TextureFormat TextureFormatFromPixelFormat(PixelFormat format) { - // TODO(Subv): Properly implement this - switch (format) { - case PixelFormat::ABGR8: - case PixelFormat::SRGBA8: - return Tegra::Texture::TextureFormat::A8R8G8B8; - case PixelFormat::B5G6R5: - return Tegra::Texture::TextureFormat::B5G6R5; - case PixelFormat::A2B10G10R10: - return Tegra::Texture::TextureFormat::A2B10G10R10; - case PixelFormat::A1B5G5R5: - return Tegra::Texture::TextureFormat::A1B5G5R5; - case PixelFormat::R8: - return Tegra::Texture::TextureFormat::R8; - case PixelFormat::G8R8: - return Tegra::Texture::TextureFormat::G8R8; - case PixelFormat::RGBA16F: - return Tegra::Texture::TextureFormat::R16_G16_B16_A16; - case PixelFormat::R11FG11FB10F: - return Tegra::Texture::TextureFormat::BF10GF11RF11; - case PixelFormat::RGBA32UI: - return Tegra::Texture::TextureFormat::R32_G32_B32_A32; - case PixelFormat::DXT1: - return Tegra::Texture::TextureFormat::DXT1; - case PixelFormat::DXT23: - return Tegra::Texture::TextureFormat::DXT23; - case PixelFormat::DXT45: - return Tegra::Texture::TextureFormat::DXT45; - case PixelFormat::DXN1: - return Tegra::Texture::TextureFormat::DXN1; - case PixelFormat::BC7U: - return Tegra::Texture::TextureFormat::BC7U; - case PixelFormat::ASTC_2D_4X4: - return Tegra::Texture::TextureFormat::ASTC_2D_4X4; - case PixelFormat::BGRA8: - // TODO(bunnei): This is fine for unswizzling (since we just need the right component - // sizes), but could be a bug if we used this function in different ways. - return Tegra::Texture::TextureFormat::A8R8G8B8; - case PixelFormat::RGBA32F: - return Tegra::Texture::TextureFormat::R32_G32_B32_A32; - case PixelFormat::RGB32F: - return Tegra::Texture::TextureFormat::R32_G32_B32; - case PixelFormat::RG32F: - return Tegra::Texture::TextureFormat::R32_G32; - case PixelFormat::R32F: - return Tegra::Texture::TextureFormat::R32; - case PixelFormat::R16F: - case PixelFormat::R16UNORM: - return Tegra::Texture::TextureFormat::R16; - case PixelFormat::Z32F: - return Tegra::Texture::TextureFormat::ZF32; - case PixelFormat::Z24S8: - return Tegra::Texture::TextureFormat::Z24S8; - case PixelFormat::RG16F: - case PixelFormat::RG16: - case PixelFormat::RG16UI: - case PixelFormat::RG16I: - case PixelFormat::RG16S: - return Tegra::Texture::TextureFormat::R16_G16; - default: - LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format)); - UNREACHABLE(); - } - } - - static Tegra::DepthFormat DepthFormatFromPixelFormat(PixelFormat format) { - switch (format) { - case PixelFormat::S8Z24: - return Tegra::DepthFormat::S8_Z24_UNORM; - case PixelFormat::Z24S8: - return Tegra::DepthFormat::Z24_S8_UNORM; - case PixelFormat::Z32F: - return Tegra::DepthFormat::Z32_FLOAT; - case PixelFormat::Z16: - return Tegra::DepthFormat::Z16_UNORM; - case PixelFormat::Z32FS8: - return Tegra::DepthFormat::Z32_S8_X24_FLOAT; - default: - LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format)); - UNREACHABLE(); - } - } - static ComponentType ComponentTypeFromTexture(Tegra::Texture::ComponentType type) { // TODO(Subv): Implement more component types switch (type) { @@ -441,6 +376,7 @@ struct SurfaceParams { case Tegra::RenderTargetFormat::RGB10_A2_UNORM: case Tegra::RenderTargetFormat::R8_UNORM: case Tegra::RenderTargetFormat::RG16_UNORM: + case Tegra::RenderTargetFormat::B5G6R5_UNORM: return ComponentType::UNorm; case Tegra::RenderTargetFormat::RG16_SNORM: return ComponentType::SNorm; @@ -612,8 +548,7 @@ public: Surface GetTextureSurface(const Tegra::Texture::FullTextureInfo& config); /// Get the color and depth surfaces based on the framebuffer configuration - SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb, - const MathUtil::Rectangle<s32>& viewport); + SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb); /// Flushes the surface to Switch memory void FlushSurface(const Surface& surface); diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index e3217db81..32f06f409 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -507,6 +507,8 @@ private: /// Build the GLSL register list. void BuildRegisterList() { + regs.reserve(Register::NumRegisters); + for (size_t index = 0; index < Register::NumRegisters; ++index) { regs.emplace_back(index, suffix); } @@ -523,6 +525,11 @@ private: // shader. ASSERT(stage == Maxwell3D::Regs::ShaderStage::Vertex); return "vec4(0, 0, uintBitsToFloat(gl_InstanceID), uintBitsToFloat(gl_VertexID))"; + case Attribute::Index::Unknown_63: + // TODO(bunnei): Figure out what this is used for. Super Mario Odyssey uses this. + LOG_CRITICAL(HW_GPU, "Unhandled input attribute Unknown_63"); + UNREACHABLE(); + break; default: const u32 index{static_cast<u32>(attribute) - static_cast<u32>(Attribute::Index::Attribute_0)}; @@ -534,6 +541,8 @@ private: LOG_CRITICAL(HW_GPU, "Unhandled input attribute: {}", index); UNREACHABLE(); } + + return "vec4(0, 0, 0, 0)"; } /// Generates code representing an output attribute register. @@ -602,12 +611,12 @@ private: /// Generates code representing a 19-bit immediate value static std::string GetImmediate19(const Instruction& instr) { - return std::to_string(instr.alu.GetImm20_19()); + return fmt::format("uintBitsToFloat({})", instr.alu.GetImm20_19()); } /// Generates code representing a 32-bit immediate value static std::string GetImmediate32(const Instruction& instr) { - return std::to_string(instr.alu.GetImm20_32()); + return fmt::format("uintBitsToFloat({})", instr.alu.GetImm20_32()); } /// Generates code representing a texture sampler. @@ -650,16 +659,17 @@ private: * @param instr Instruction to generate the if condition for. * @returns string containing the predicate condition. */ - std::string GetPredicateCondition(u64 index, bool negate) const { + std::string GetPredicateCondition(u64 index, bool negate) { using Tegra::Shader::Pred; std::string variable; // Index 7 is used as an 'Always True' condition. - if (index == static_cast<u64>(Pred::UnusedIndex)) + if (index == static_cast<u64>(Pred::UnusedIndex)) { variable = "true"; - else + } else { variable = 'p' + std::to_string(index) + '_' + suffix; - + declr_predicates.insert(variable); + } if (negate) { return "!(" + variable + ')'; } diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index 24b1d956b..5c7b636e4 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -7,6 +7,10 @@ #include <array> #include <glad/glad.h> +#include "video_core/engines/maxwell_3d.h" + +using Regs = Tegra::Engines::Maxwell3D::Regs; + namespace TextureUnits { struct TextureUnit { @@ -120,7 +124,7 @@ public: GLuint bindpoint; GLuint ssbo; }; - std::array<std::array<ConstBufferConfig, 16>, 5> const_buffers{}; + std::array<std::array<ConstBufferConfig, Regs::MaxConstBuffers>, 5> const_buffers; } draw; struct { diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h index 16b1bd606..c439446b1 100644 --- a/src/video_core/renderer_opengl/maxwell_to_gl.h +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -27,9 +27,11 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) { case Maxwell::VertexAttribute::Type::UnsignedNorm: { switch (attrib.size) { + case Maxwell::VertexAttribute::Size::Size_8_8: case Maxwell::VertexAttribute::Size::Size_8_8_8_8: return GL_UNSIGNED_BYTE; case Maxwell::VertexAttribute::Size::Size_16_16: + case Maxwell::VertexAttribute::Size::Size_16_16_16_16: return GL_UNSIGNED_SHORT; case Maxwell::VertexAttribute::Size::Size_10_10_10_2: return GL_UNSIGNED_INT_2_10_10_10_REV; @@ -43,6 +45,9 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) { case Maxwell::VertexAttribute::Type::SignedNorm: { switch (attrib.size) { + case Maxwell::VertexAttribute::Size::Size_32_32_32: + return GL_INT; + case Maxwell::VertexAttribute::Size::Size_8_8: case Maxwell::VertexAttribute::Size::Size_8_8_8_8: return GL_BYTE; case Maxwell::VertexAttribute::Size::Size_16_16: @@ -84,6 +89,8 @@ inline GLenum IndexFormat(Maxwell::IndexFormat index_format) { inline GLenum PrimitiveTopology(Maxwell::PrimitiveTopology topology) { switch (topology) { + case Maxwell::PrimitiveTopology::Points: + return GL_POINTS; case Maxwell::PrimitiveTopology::Triangles: return GL_TRIANGLES; case Maxwell::PrimitiveTopology::TriangleStrip: diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index bf9131193..899865e3b 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -430,7 +430,7 @@ static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum break; case GL_DEBUG_SEVERITY_NOTIFICATION: case GL_DEBUG_SEVERITY_LOW: - LOG_DEBUG(Render_OpenGL, format, str_source, str_type, id, message); + LOG_TRACE(Render_OpenGL, format, str_source, str_type, id, message); break; } } diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index 65db84ad3..70746a34e 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -54,6 +54,7 @@ u32 BytesPerPixel(TextureFormat format) { return 8; case TextureFormat::DXT23: case TextureFormat::DXT45: + case TextureFormat::DXN2: case TextureFormat::BC7U: // In this case a 'pixel' actually refers to a 4x4 tile. return 16; @@ -85,87 +86,11 @@ u32 BytesPerPixel(TextureFormat format) { } } -static u32 DepthBytesPerPixel(DepthFormat format) { - switch (format) { - case DepthFormat::Z16_UNORM: - return 2; - case DepthFormat::S8_Z24_UNORM: - case DepthFormat::Z24_S8_UNORM: - case DepthFormat::Z32_FLOAT: - return 4; - case DepthFormat::Z32_S8_X24_FLOAT: - return 8; - default: - UNIMPLEMENTED_MSG("Format not implemented"); - break; - } -} - -std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height, - u32 block_height) { - u8* data = Memory::GetPointer(address); - u32 bytes_per_pixel = BytesPerPixel(format); - +std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size, u32 bytes_per_pixel, u32 width, + u32 height, u32 block_height) { std::vector<u8> unswizzled_data(width * height * bytes_per_pixel); - - switch (format) { - case TextureFormat::DXT1: - case TextureFormat::DXT23: - case TextureFormat::DXT45: - case TextureFormat::DXN1: - case TextureFormat::BC7U: - // In the DXT and DXN formats, each 4x4 tile is swizzled instead of just individual pixel - // values. - CopySwizzledData(width / 4, height / 4, bytes_per_pixel, bytes_per_pixel, data, - unswizzled_data.data(), true, block_height); - break; - case TextureFormat::A8R8G8B8: - case TextureFormat::A2B10G10R10: - case TextureFormat::A1B5G5R5: - case TextureFormat::B5G6R5: - case TextureFormat::R8: - case TextureFormat::G8R8: - case TextureFormat::R16_G16_B16_A16: - case TextureFormat::R32_G32_B32_A32: - case TextureFormat::R32_G32: - case TextureFormat::R32: - case TextureFormat::R16: - case TextureFormat::R16_G16: - case TextureFormat::BF10GF11RF11: - case TextureFormat::ASTC_2D_4X4: - case TextureFormat::R32_G32_B32: - CopySwizzledData(width, height, bytes_per_pixel, bytes_per_pixel, data, - unswizzled_data.data(), true, block_height); - break; - default: - UNIMPLEMENTED_MSG("Format not implemented"); - break; - } - - return unswizzled_data; -} - -std::vector<u8> UnswizzleDepthTexture(VAddr address, DepthFormat format, u32 width, u32 height, - u32 block_height) { - u8* data = Memory::GetPointer(address); - u32 bytes_per_pixel = DepthBytesPerPixel(format); - - std::vector<u8> unswizzled_data(width * height * bytes_per_pixel); - - switch (format) { - case DepthFormat::Z16_UNORM: - case DepthFormat::S8_Z24_UNORM: - case DepthFormat::Z24_S8_UNORM: - case DepthFormat::Z32_FLOAT: - case DepthFormat::Z32_S8_X24_FLOAT: - CopySwizzledData(width, height, bytes_per_pixel, bytes_per_pixel, data, - unswizzled_data.data(), true, block_height); - break; - default: - UNIMPLEMENTED_MSG("Format not implemented"); - break; - } - + CopySwizzledData(width / tile_size, height / tile_size, bytes_per_pixel, bytes_per_pixel, + Memory::GetPointer(address), unswizzled_data.data(), true, block_height); return unswizzled_data; } @@ -179,6 +104,7 @@ std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat case TextureFormat::DXT23: case TextureFormat::DXT45: case TextureFormat::DXN1: + case TextureFormat::DXN2: case TextureFormat::BC7U: case TextureFormat::ASTC_2D_4X4: case TextureFormat::A8R8G8B8: diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h index 73a4924d1..1f7b731be 100644 --- a/src/video_core/textures/decoders.h +++ b/src/video_core/textures/decoders.h @@ -13,8 +13,8 @@ namespace Tegra::Texture { /** * Unswizzles a swizzled texture without changing its format. */ -std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height, - u32 block_height = TICEntry::DefaultBlockHeight); +std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size, u32 bytes_per_pixel, u32 width, + u32 height, u32 block_height = TICEntry::DefaultBlockHeight); /** * Unswizzles a swizzled depth texture without changing its format. diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 475556806..46ed232d8 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -17,6 +17,8 @@ add_executable(yuzu configuration/configure_debug.h configuration/configure_dialog.cpp configuration/configure_dialog.h + configuration/configure_gamelist.cpp + configuration/configure_gamelist.h configuration/configure_general.cpp configuration/configure_general.h configuration/configure_graphics.cpp @@ -59,6 +61,7 @@ set(UIS configuration/configure.ui configuration/configure_audio.ui configuration/configure_debug.ui + configuration/configure_gamelist.ui configuration/configure_general.ui configuration/configure_graphics.ui configuration/configure_input.ui diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index bf469ee73..0bd46dbac 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -122,6 +122,13 @@ void Config::ReadValues() { qt_config->beginGroup("UI"); UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString(); + qt_config->beginGroup("UIGameList"); + UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool(); + UISettings::values.icon_size = qt_config->value("icon_size", 48).toUInt(); + UISettings::values.row_1_text_id = qt_config->value("row_1_text_id", 0).toUInt(); + UISettings::values.row_2_text_id = qt_config->value("row_2_text_id", 3).toUInt(); + qt_config->endGroup(); + qt_config->beginGroup("UILayout"); UISettings::values.geometry = qt_config->value("geometry").toByteArray(); UISettings::values.state = qt_config->value("state").toByteArray(); @@ -234,6 +241,13 @@ void Config::SaveValues() { qt_config->beginGroup("UI"); qt_config->setValue("theme", UISettings::values.theme); + qt_config->beginGroup("UIGameList"); + qt_config->setValue("show_unknown", UISettings::values.show_unknown); + qt_config->setValue("icon_size", UISettings::values.icon_size); + qt_config->setValue("row_1_text_id", UISettings::values.row_1_text_id); + qt_config->setValue("row_2_text_id", UISettings::values.row_2_text_id); + qt_config->endGroup(); + qt_config->beginGroup("UILayout"); qt_config->setValue("geometry", UISettings::values.geometry); qt_config->setValue("state", UISettings::values.state); diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index c8e0b88af..20f120134 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui @@ -24,6 +24,11 @@ <string>General</string> </attribute> </widget> + <widget class="ConfigureGameList" name="gameListTab"> + <attribute name="title"> + <string>Game List</string> + </attribute> + </widget> <widget class="ConfigureSystem" name="systemTab"> <attribute name="title"> <string>System</string> @@ -67,6 +72,12 @@ <header>configuration/configure_general.h</header> <container>1</container> </customwidget> + <customwidget> + <class>ConfigureGameList</class> + <extends>QWidget</extends> + <header>configuration/configure_gamelist.h</header> + <container>1</container> + </customwidget> <customwidget> <class>ConfigureSystem</class> <extends>QWidget</extends> diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index cc4b326ae..daa4cc0d9 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -21,6 +21,7 @@ void ConfigureDialog::setConfiguration() {} void ConfigureDialog::applyConfiguration() { ui->generalTab->applyConfiguration(); + ui->gameListTab->applyConfiguration(); ui->systemTab->applyConfiguration(); ui->inputTab->applyConfiguration(); ui->graphicsTab->applyConfiguration(); diff --git a/src/yuzu/configuration/configure_gamelist.cpp b/src/yuzu/configuration/configure_gamelist.cpp new file mode 100644 index 000000000..1ae3423cf --- /dev/null +++ b/src/yuzu/configuration/configure_gamelist.cpp @@ -0,0 +1,63 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/core.h" +#include "core/settings.h" +#include "ui_configure_gamelist.h" +#include "ui_settings.h" +#include "yuzu/configuration/configure_gamelist.h" + +ConfigureGameList::ConfigureGameList(QWidget* parent) + : QWidget(parent), ui(new Ui::ConfigureGameList) { + ui->setupUi(this); + + static const std::vector<std::pair<u32, std::string>> default_icon_sizes{ + std::make_pair(0, "None"), std::make_pair(32, "Small"), + std::make_pair(64, "Standard"), std::make_pair(128, "Large"), + std::make_pair(256, "Full Size"), + }; + + for (const auto& size : default_icon_sizes) { + ui->icon_size_combobox->addItem(QString::fromStdString(size.second + " (" + + std::to_string(size.first) + "x" + + std::to_string(size.first) + ")"), + size.first); + } + + static const std::vector<std::string> row_text_names{ + "Filename", + "Filetype", + "Title ID", + "Title Name", + }; + + for (size_t i = 0; i < row_text_names.size(); ++i) { + ui->row_1_text_combobox->addItem(QString::fromStdString(row_text_names[i]), + QVariant::fromValue(i)); + ui->row_2_text_combobox->addItem(QString::fromStdString(row_text_names[i]), + QVariant::fromValue(i)); + } + + this->setConfiguration(); +} + +ConfigureGameList::~ConfigureGameList() {} + +void ConfigureGameList::setConfiguration() { + ui->show_unknown->setChecked(UISettings::values.show_unknown); + ui->icon_size_combobox->setCurrentIndex( + ui->icon_size_combobox->findData(UISettings::values.icon_size)); + ui->row_1_text_combobox->setCurrentIndex( + ui->row_1_text_combobox->findData(UISettings::values.row_1_text_id)); + ui->row_2_text_combobox->setCurrentIndex( + ui->row_2_text_combobox->findData(UISettings::values.row_2_text_id)); +} + +void ConfigureGameList::applyConfiguration() { + UISettings::values.show_unknown = ui->show_unknown->isChecked(); + UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt(); + UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); + UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt(); + Settings::Apply(); +} diff --git a/src/yuzu/configuration/configure_gamelist.h b/src/yuzu/configuration/configure_gamelist.h new file mode 100644 index 000000000..94fba6373 --- /dev/null +++ b/src/yuzu/configuration/configure_gamelist.h @@ -0,0 +1,28 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QWidget> + +namespace Ui { +class ConfigureGameList; +} + +class ConfigureGameList : public QWidget { + Q_OBJECT + +public: + explicit ConfigureGameList(QWidget* parent = nullptr); + ~ConfigureGameList(); + + void applyConfiguration(); + +private: + void setConfiguration(); + +private: + std::unique_ptr<Ui::ConfigureGameList> ui; +}; diff --git a/src/yuzu/configuration/configure_gamelist.ui b/src/yuzu/configuration/configure_gamelist.ui new file mode 100644 index 000000000..7471fdb60 --- /dev/null +++ b/src/yuzu/configuration/configure_gamelist.ui @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureGameList</class> + <widget class="QWidget" name="ConfigureGeneral"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>300</width> + <height>377</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QHBoxLayout" name="HorizontalLayout"> + <item> + <layout class="QVBoxLayout" name="VerticalLayout"> + <item> + <widget class="QGroupBox" name="GeneralGroupBox"> + <property name="title"> + <string>General</string> + </property> + <layout class="QHBoxLayout" name="GeneralHorizontalLayout"> + <item> + <layout class="QVBoxLayout" name="GeneralVerticalLayout"> + <item> + <widget class="QCheckBox" name="show_unknown"> + <property name="text"> + <string>Show files with type 'Unknown'</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="IconSizeGroupBox"> + <property name="title"> + <string>Icon Size</string> + </property> + <layout class="QHBoxLayout" name="icon_size_qhbox_layout"> + <item> + <layout class="QVBoxLayout" name="icon_size_qvbox_layout"> + <item> + <layout class="QHBoxLayout" name="icon_size_qhbox_layout_2"> + <item> + <widget class="QLabel" name="icon_size_label"> + <property name="text"> + <string>Icon Size:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="icon_size_combobox"/> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="RowGroupBox"> + <property name="title"> + <string>Row Text</string> + </property> + <layout class="QHBoxLayout" name="RowHorizontalLayout"> + <item> + <layout class="QVBoxLayout" name="RowVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="row_1_qhbox_layout"> + <item> + <widget class="QLabel" name="row_1_label"> + <property name="text"> + <string>Row 1 Text:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="row_1_text_combobox"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="row_2_qhbox_layout"> + <item> + <widget class="QLabel" name="row_2_label"> + <property name="text"> + <string>Row 2 Text:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="row_2_text_combobox"/> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp index 3f7103ab9..e037223c2 100644 --- a/src/yuzu/debugger/graphics/graphics_surface.cpp +++ b/src/yuzu/debugger/graphics/graphics_surface.cpp @@ -383,8 +383,10 @@ void GraphicsSurfaceWidget::OnUpdate() { QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32); boost::optional<VAddr> address = gpu.memory_manager->GpuToCpuAddress(surface_address); - auto unswizzled_data = - Tegra::Texture::UnswizzleTexture(*address, surface_format, surface_width, surface_height); + // TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles. + // Needs to be fixed if we plan to use this feature more, otherwise we may remove it. + auto unswizzled_data = Tegra::Texture::UnswizzleTexture( + *address, 1, Tegra::Texture::BytesPerPixel(surface_format), surface_width, surface_height); auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format, surface_width, surface_height); diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 24f38a3c7..1c738d2a4 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -9,9 +9,13 @@ #include <QKeyEvent> #include <QMenu> #include <QThreadPool> +#include <boost/container/flat_map.hpp> #include "common/common_paths.h" #include "common/logging/log.h" #include "common/string_util.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/control_metadata.h" +#include "core/file_sys/romfs.h" #include "core/file_sys/vfs_real.h" #include "core/loader/loader.h" #include "game_list.h" @@ -194,7 +198,8 @@ void GameList::onFilterCloseClicked() { main_window->filterBarSetChecked(false); } -GameList::GameList(GMainWindow* parent) : QWidget{parent} { +GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent) + : QWidget{parent}, vfs(std::move(vfs)) { watcher = new QFileSystemWatcher(this); connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); @@ -338,7 +343,7 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { emit ShouldCancelWorker(); - GameListWorker* worker = new GameListWorker(dir_path, deep_scan); + GameListWorker* worker = new GameListWorker(vfs, dir_path, deep_scan); connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating, @@ -398,8 +403,32 @@ void GameList::RefreshGameDirectory() { } void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { - const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory, - const std::string& virtual_name) -> bool { + boost::container::flat_map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map; + + const auto nca_control_callback = + [this, &nca_control_map](u64* num_entries_out, const std::string& directory, + const std::string& virtual_name) -> bool { + std::string physical_name = directory + DIR_SEP + virtual_name; + + if (stop_processing) + return false; // Breaks the callback loop. + + bool is_dir = FileUtil::IsDirectory(physical_name); + QFileInfo file_info(physical_name.c_str()); + if (!is_dir && file_info.suffix().toStdString() == "nca") { + auto nca = + std::make_shared<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read)); + if (nca->GetType() == FileSys::NCAContentType::Control) + nca_control_map.insert_or_assign(nca->GetTitleId(), nca); + } + return true; + }; + + FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback); + + const auto callback = [this, recursion, + &nca_control_map](u64* num_entries_out, const std::string& directory, + const std::string& virtual_name) -> bool { std::string physical_name = directory + DIR_SEP + virtual_name; if (stop_processing) @@ -409,18 +438,51 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign if (!is_dir && (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { std::unique_ptr<Loader::AppLoader> loader = - Loader::GetLoader(std::make_shared<FileSys::RealVfsFile>(physical_name)); - if (!loader) + Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read)); + if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown || + loader->GetFileType() == Loader::FileType::Error) && + !UISettings::values.show_unknown)) return true; - std::vector<u8> smdh; - loader->ReadIcon(smdh); - - u64 program_id = 0; - loader->ReadProgramId(program_id); + std::vector<u8> icon; + const auto res1 = loader->ReadIcon(icon); + + u64 program_id; + const auto res2 = loader->ReadProgramId(program_id); + + std::string name = " "; + const auto res3 = loader->ReadTitle(name); + + if ((res1 == Loader::ResultStatus::ErrorNotUsed || + res1 == Loader::ResultStatus::ErrorNotImplemented) && + (res3 == Loader::ResultStatus::ErrorNotUsed || + res3 == Loader::ResultStatus::ErrorNotImplemented) && + res2 == Loader::ResultStatus::Success) { + // Use from metadata pool. + if (nca_control_map.find(program_id) != nca_control_map.end()) { + const auto nca = nca_control_map[program_id]; + const auto control_dir = FileSys::ExtractRomFS(nca->GetRomFS()); + + const auto nacp_file = control_dir->GetFile("control.nacp"); + FileSys::NACP nacp(nacp_file); + name = nacp.GetApplicationName(); + + FileSys::VirtualFile icon_file = nullptr; + for (const auto& language : FileSys::LANGUAGE_NAMES) { + icon_file = control_dir->GetFile("icon_" + std::string(language) + ".dat"); + if (icon_file != nullptr) { + icon = icon_file->ReadAllBytes(); + break; + } + } + } + } emit EntryReady({ - new GameListItemPath(FormatGameName(physical_name), smdh, program_id), + new GameListItemPath( + FormatGameName(physical_name), icon, QString::fromStdString(name), + QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), + program_id), new GameListItem( QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), new GameListItemSize(FileUtil::GetSize(physical_name)), diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 3bc14f07f..afe624b32 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -59,7 +59,7 @@ public: QToolButton* button_filter_close = nullptr; }; - explicit GameList(GMainWindow* parent = nullptr); + explicit GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent = nullptr); ~GameList() override; void clearFilter(); @@ -90,6 +90,7 @@ private: void PopupContextMenu(const QPoint& menu_location); void RefreshGameDirectory(); + FileSys::VirtualFilesystem vfs; SearchField* search_field; GMainWindow* main_window = nullptr; QVBoxLayout* layout = nullptr; diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index aa69a098f..114a0fc7f 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -11,6 +11,7 @@ #include <QStandardItem> #include <QString> #include "common/string_util.h" +#include "ui_settings.h" #include "yuzu/util/util.h" /** @@ -18,8 +19,7 @@ * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24) * @return QPixmap default icon */ -static QPixmap GetDefaultIcon(bool large) { - int size = large ? 48 : 24; +static QPixmap GetDefaultIcon(u32 size) { QPixmap icon(size, size); icon.fill(Qt::transparent); return icon; @@ -44,11 +44,25 @@ public: static const int FullPathRole = Qt::UserRole + 1; static const int TitleRole = Qt::UserRole + 2; static const int ProgramIdRole = Qt::UserRole + 3; + static const int FileTypeRole = Qt::UserRole + 4; GameListItemPath() = default; - GameListItemPath(const QString& game_path, const std::vector<u8>& smdh_data, u64 program_id) { + GameListItemPath(const QString& game_path, const std::vector<u8>& picture_data, + const QString& game_name, const QString& game_type, u64 program_id) + : GameListItem() { setData(game_path, FullPathRole); + setData(game_name, TitleRole); setData(qulonglong(program_id), ProgramIdRole); + setData(game_type, FileTypeRole); + + QPixmap picture; + u32 size = UISettings::values.icon_size; + if (!picture.loadFromData(picture_data.data(), picture_data.size())) + picture = GetDefaultIcon(size); + + picture = picture.scaled(size, size); + + setData(picture, Qt::DecorationRole); } QVariant data(int role) const override { @@ -57,7 +71,23 @@ public: Common::SplitPath(data(FullPathRole).toString().toStdString(), nullptr, &filename, nullptr); QString title = data(TitleRole).toString(); - return QString::fromStdString(filename) + (title.isEmpty() ? "" : "\n " + title); + + std::vector<QString> row_data{ + QString::fromStdString(filename), + data(FileTypeRole).toString(), + QString::fromStdString(fmt::format("0x{:016X}", data(ProgramIdRole).toULongLong())), + data(TitleRole).toString(), + }; + + auto row1 = row_data.at(UISettings::values.row_1_text_id); + auto row2 = row_data.at(UISettings::values.row_2_text_id); + + if (row1.isEmpty() || row1 == row2) + return row2; + if (row2.isEmpty()) + return row1; + + return row1 + "\n " + row2; } else { return GameListItem::data(role); } @@ -109,8 +139,8 @@ class GameListWorker : public QObject, public QRunnable { Q_OBJECT public: - GameListWorker(QString dir_path, bool deep_scan) - : dir_path(std::move(dir_path)), deep_scan(deep_scan) {} + GameListWorker(FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan) + : vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan) {} public slots: /// Starts the processing of directory tree information. @@ -133,6 +163,7 @@ signals: void Finished(QStringList watch_list); private: + FileSys::VirtualFilesystem vfs; QStringList watch_list; QString dir_path; bool deep_scan; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 17ed62c72..67e3c6549 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -24,6 +24,7 @@ #include "common/string_util.h" #include "core/core.h" #include "core/crypto/key_manager.h" +#include "core/file_sys/vfs_real.h" #include "core/gdbstub/gdbstub.h" #include "core/loader/loader.h" #include "core/settings.h" @@ -83,7 +84,9 @@ void GMainWindow::ShowCallouts() {} const int GMainWindow::max_recent_files_item; -GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { +GMainWindow::GMainWindow() + : config(new Config()), emu_thread(nullptr), + vfs(std::make_shared<FileSys::RealVfsFilesystem>()) { debug_context = Tegra::DebugContext::Construct(); @@ -132,7 +135,7 @@ void GMainWindow::InitializeWidgets() { render_window = new GRenderWindow(this, emu_thread.get()); render_window->hide(); - game_list = new GameList(this); + game_list = new GameList(vfs, this); ui.horizontalLayout->addWidget(game_list); // Create status bar @@ -406,6 +409,7 @@ bool GMainWindow::LoadROM(const QString& filename) { } Core::System& system{Core::System::GetInstance()}; + system.SetFilesystem(vfs); system.SetGPUDebugContext(debug_context); @@ -768,6 +772,7 @@ void GMainWindow::OnConfigure() { configureDialog.applyConfiguration(); if (UISettings::values.theme != old_theme) UpdateUITheme(); + game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); config->Save(); } } diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 6e335b8f8..74487c58c 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -161,6 +161,9 @@ private: bool emulation_running = false; std::unique_ptr<EmuThread> emu_thread; + // FS + FileSys::VirtualFilesystem vfs; + // Debugger panes ProfilerWidget* profilerWidget; MicroProfileDialog* microProfileDialog; diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h index 2286c2559..051494bc5 100644 --- a/src/yuzu/ui_settings.h +++ b/src/yuzu/ui_settings.h @@ -54,6 +54,12 @@ struct Values { // logging bool show_console; + + // Game List + bool show_unknown; + uint32_t icon_size; + uint8_t row_1_text_id; + uint8_t row_2_text_id; }; extern Values values; diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index d637dbd0c..0605c92e3 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -161,6 +161,7 @@ int main(int argc, char** argv) { } Core::System& system{Core::System::GetInstance()}; + system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); SCOPE_EXIT({ system.Shutdown(); }); |