From da501b9294f5b338ba74fc6d196108c084891b74 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 15:04:25 +1000 Subject: [PATCH 01/69] StringUtil: Add ParseFixedHexString() --- src/common/string_util.h | 21 +++++++++++++++++++++ src/core/bios.cpp | 16 +--------------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/common/string_util.h b/src/common/string_util.h index cd259f504..e5b821840 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -260,6 +260,27 @@ ALWAYS_INLINE static bool IsHexDigit(T ch) (ch >= static_cast('0') && ch <= static_cast('9'))); } +/// Returns a byte array from the provided hex string, computed at compile-time. +template +static constexpr std::array ParseFixedHexString(const char str[]) +{ + std::array h{}; + for (int i = 0; str[i] != '\0'; i++) + { + u8 nibble = 0; + char ch = str[i]; + if (ch >= '0' && ch <= '9') + nibble = str[i] - '0'; + else if (ch >= 'a' && ch <= 'z') + nibble = 0xA + (str[i] - 'a'); + else if (ch >= 'A' && ch <= 'Z') + nibble = 0xA + (str[i] - 'A'); + + h[i / 2] |= nibble << (((i & 1) ^ 1) * 4); + } + return h; +} + /// StartsWith/EndsWith variants which aren't case sensitive. ALWAYS_INLINE static bool StartsWithNoCase(const std::string_view str, const std::string_view prefix) { diff --git a/src/core/bios.cpp b/src/core/bios.cpp index 767b5f53d..c8b0c7eab 100644 --- a/src/core/bios.cpp +++ b/src/core/bios.cpp @@ -21,21 +21,7 @@ namespace BIOS { static constexpr ImageInfo::Hash MakeHashFromString(const char str[]) { - ImageInfo::Hash h{}; - for (int i = 0; str[i] != '\0'; i++) - { - u8 nibble = 0; - char ch = str[i]; - if (ch >= '0' && ch <= '9') - nibble = str[i] - '0'; - else if (ch >= 'a' && ch <= 'z') - nibble = 0xA + (str[i] - 'a'); - else if (ch >= 'A' && ch <= 'Z') - nibble = 0xA + (str[i] - 'A'); - - h[i / 2] |= nibble << (((i & 1) ^ 1) * 4); - } - return h; + return StringUtil::ParseFixedHexString(str); } // clang-format off From bbe6612b256b049b72ec022fe21fadd4386c05c3 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 15:20:33 +1000 Subject: [PATCH 02/69] Common: Add SHA256Digest --- src/common-tests/CMakeLists.txt | 1 + src/common-tests/common-tests.vcxproj | 1 + src/common-tests/common-tests.vcxproj.filters | 1 + src/common-tests/sha256_tests.cpp | 34 +++ src/common/CMakeLists.txt | 2 + src/common/common.vcxproj | 2 + src/common/common.vcxproj.filters | 2 + src/common/sha1_digest.cpp | 18 +- src/common/sha256_digest.cpp | 193 ++++++++++++++++++ src/common/sha256_digest.h | 43 ++++ 10 files changed, 281 insertions(+), 16 deletions(-) create mode 100644 src/common-tests/sha256_tests.cpp create mode 100644 src/common/sha256_digest.cpp create mode 100644 src/common/sha256_digest.h diff --git a/src/common-tests/CMakeLists.txt b/src/common-tests/CMakeLists.txt index b8d19a0a5..4badd66ba 100644 --- a/src/common-tests/CMakeLists.txt +++ b/src/common-tests/CMakeLists.txt @@ -4,6 +4,7 @@ add_executable(common-tests gsvector_yuvtorgb_test.cpp path_tests.cpp rectangle_tests.cpp + sha256_tests.cpp string_tests.cpp ) diff --git a/src/common-tests/common-tests.vcxproj b/src/common-tests/common-tests.vcxproj index 96740161f..125e17687 100644 --- a/src/common-tests/common-tests.vcxproj +++ b/src/common-tests/common-tests.vcxproj @@ -7,6 +7,7 @@ + diff --git a/src/common-tests/common-tests.vcxproj.filters b/src/common-tests/common-tests.vcxproj.filters index 7ca72133d..ca02bfbf1 100644 --- a/src/common-tests/common-tests.vcxproj.filters +++ b/src/common-tests/common-tests.vcxproj.filters @@ -8,5 +8,6 @@ + \ No newline at end of file diff --git a/src/common-tests/sha256_tests.cpp b/src/common-tests/sha256_tests.cpp new file mode 100644 index 000000000..9f79d6004 --- /dev/null +++ b/src/common-tests/sha256_tests.cpp @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#include "common/sha256_digest.h" + +#include + +TEST(SHA256Digest, Simple) +{ + // https://github.com/B-Con/crypto-algorithms/blob/master/sha256_test.c + + static constexpr const char text1[] = "abc"; + static constexpr const char text2[] = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"; + static constexpr const char text3[] = "aaaaaaaaaa"; + + static constexpr SHA256Digest::Digest hash1 = {{0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, + 0xde, 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, + 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad}}; + static constexpr SHA256Digest::Digest hash2 = {{0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8, 0xe5, 0xc0, 0x26, + 0x93, 0x0c, 0x3e, 0x60, 0x39, 0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff, + 0x21, 0x67, 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1}}; + static constexpr SHA256Digest::Digest hash3 = {{0xcd, 0xc7, 0x6e, 0x5c, 0x99, 0x14, 0xfb, 0x92, 0x81, 0xa1, 0xc7, + 0xe2, 0x84, 0xd7, 0x3e, 0x67, 0xf1, 0x80, 0x9a, 0x48, 0xa4, 0x97, + 0x20, 0x0e, 0x04, 0x6d, 0x39, 0xcc, 0xc7, 0x11, 0x2c, 0xd0}}; + + ASSERT_EQ(SHA256Digest::GetDigest(text1, std::size(text1) - 1), hash1); + ASSERT_EQ(SHA256Digest::GetDigest(text2, std::size(text2) - 1), hash2); + + SHA256Digest ldigest; + for (u32 i = 0; i < 100000; i++) + ldigest.Update(text3, std::size(text3) - 1); + + ASSERT_EQ(ldigest.Final(), hash3); +} diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index c966446e4..783ccd288 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -50,6 +50,8 @@ add_library(common settings_interface.h sha1_digest.cpp sha1_digest.h + sha256_digest.cpp + sha256_digest.h small_string.cpp small_string.h string_util.cpp diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj index ab8e27fa0..925c5486c 100644 --- a/src/common/common.vcxproj +++ b/src/common/common.vcxproj @@ -36,6 +36,7 @@ + @@ -64,6 +65,7 @@ + diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters index 90a750fea..6f329bd67 100644 --- a/src/common/common.vcxproj.filters +++ b/src/common/common.vcxproj.filters @@ -50,6 +50,7 @@ + @@ -78,6 +79,7 @@ + diff --git a/src/common/sha1_digest.cpp b/src/common/sha1_digest.cpp index 57cf2846b..83015163d 100644 --- a/src/common/sha1_digest.cpp +++ b/src/common/sha1_digest.cpp @@ -3,6 +3,7 @@ #include "sha1_digest.h" #include "assert.h" +#include "string_util.h" #include @@ -162,22 +163,7 @@ void SHA1Digest::Reset() std::string SHA1Digest::DigestToString(const std::span digest) { - std::string ret; - ret.reserve(DIGEST_SIZE * 2); - for (u32 i = 0; i < DIGEST_SIZE; i++) - { - u8 nibble = digest[i] >> 4; - if (nibble >= 0xA) - ret.push_back('A' + (nibble - 0xA)); - else - ret.push_back('0' + nibble); - nibble = digest[i] & 0xF; - if (nibble >= 0xA) - ret.push_back('A' + (nibble - 0xA)); - else - ret.push_back('0' + nibble); - } - return ret; + return StringUtil::EncodeHex(digest); } std::array SHA1Digest::GetDigest(const void* data, size_t len) diff --git a/src/common/sha256_digest.cpp b/src/common/sha256_digest.cpp new file mode 100644 index 000000000..36be0d033 --- /dev/null +++ b/src/common/sha256_digest.cpp @@ -0,0 +1,193 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +// Based on https://github.com/B-Con/crypto-algorithms/blob/master/sha256.c +// By Brad Conte (brad AT bradconte.com) + +#include "sha256_digest.h" +#include "string_util.h" + +#include + +SHA256Digest::SHA256Digest() +{ + Reset(); +} + +std::string SHA256Digest::DigestToString(const std::span digest) +{ + return StringUtil::EncodeHex(digest); +} + +SHA256Digest::Digest SHA256Digest::GetDigest(const void* data, size_t len) +{ + Digest ret; + SHA256Digest digest; + digest.Update(data, len); + digest.Final(ret); + return ret; +} + +SHA256Digest::Digest SHA256Digest::GetDigest(std::span data) +{ + Digest ret; + SHA256Digest digest; + digest.Update(data); + digest.Final(ret); + return ret; +} + +#define ROTLEFT(a, b) (((a) << (b)) | ((a) >> (32 - (b)))) +#define ROTRIGHT(a, b) (((a) >> (b)) | ((a) << (32 - (b)))) + +#define CH(x, y, z) (((x) & (y)) ^ (~(x) & (z))) +#define MAJ(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) +#define EP0(x) (ROTRIGHT(x, 2) ^ ROTRIGHT(x, 13) ^ ROTRIGHT(x, 22)) +#define EP1(x) (ROTRIGHT(x, 6) ^ ROTRIGHT(x, 11) ^ ROTRIGHT(x, 25)) +#define SIG0(x) (ROTRIGHT(x, 7) ^ ROTRIGHT(x, 18) ^ ((x) >> 3)) +#define SIG1(x) (ROTRIGHT(x, 17) ^ ROTRIGHT(x, 19) ^ ((x) >> 10)) + +static constexpr std::array k = { + {0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}}; + +void SHA256Digest::TransformBlock() +{ + std::array m; + + size_t i = 0; + for (size_t j = 0; i < 16; ++i, j += 4) + m[i] = (m_block[j] << 24) | (m_block[j + 1] << 16) | (m_block[j + 2] << 8) | (m_block[j + 3]); + for (; i < 64; ++i) + m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16]; + + u32 a = m_state[0]; + u32 b = m_state[1]; + u32 c = m_state[2]; + u32 d = m_state[3]; + u32 e = m_state[4]; + u32 f = m_state[5]; + u32 g = m_state[6]; + u32 h = m_state[7]; + + for (i = 0; i < 64; ++i) + { + u32 t1 = h + EP1(e) + CH(e, f, g) + k[i] + m[i]; + u32 t2 = EP0(a) + MAJ(a, b, c); + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + + m_state[0] += a; + m_state[1] += b; + m_state[2] += c; + m_state[3] += d; + m_state[4] += e; + m_state[5] += f; + m_state[6] += g; + m_state[7] += h; +} + +void SHA256Digest::Reset() +{ + m_block_length = 0; + m_bit_length = 0; + m_state[0] = 0x6a09e667; + m_state[1] = 0xbb67ae85; + m_state[2] = 0x3c6ef372; + m_state[3] = 0xa54ff53a; + m_state[4] = 0x510e527f; + m_state[5] = 0x9b05688c; + m_state[6] = 0x1f83d9ab; + m_state[7] = 0x5be0cd19; +} + +void SHA256Digest::Update(std::span data) +{ + const size_t len = data.size(); + for (size_t pos = 0; pos < len;) + { + const u32 copy_len = static_cast(std::min(len - pos, BLOCK_SIZE - m_block_length)); + std::memcpy(&m_block[m_block_length], &data[pos], copy_len); + m_block_length += copy_len; + pos += copy_len; + + if (m_block_length == BLOCK_SIZE) + { + TransformBlock(); + m_bit_length += 512; + m_block_length = 0; + } + } +} + +void SHA256Digest::Update(const void* data, size_t len) +{ + Update(std::span(static_cast(data), len)); +} + +void SHA256Digest::Final(std::span digest) +{ + // Pad whatever data is left in the buffer. + if (m_block_length < 56) + { + size_t i = m_block_length; + m_block[i++] = 0x80; + while (i < 56) + m_block[i++] = 0x00; + } + else + { + size_t i = m_block_length; + m_block[i++] = 0x80; + while (i < 64) + m_block[i++] = 0x00; + TransformBlock(); + m_block = {}; + } + + // Append to the padding the total message's length in bits and transform. + m_bit_length += m_block_length * 8; + m_block[63] = static_cast(m_bit_length); + m_block[62] = static_cast(m_bit_length >> 8); + m_block[61] = static_cast(m_bit_length >> 16); + m_block[60] = static_cast(m_bit_length >> 24); + m_block[59] = static_cast(m_bit_length >> 32); + m_block[58] = static_cast(m_bit_length >> 40); + m_block[57] = static_cast(m_bit_length >> 48); + m_block[56] = static_cast(m_bit_length >> 56); + TransformBlock(); + + // Since this implementation uses little endian byte ordering and SHA uses big endian, + // reverse all the bytes when copying the final state to the output hash. + for (size_t i = 0; i < 4; ++i) + { + digest[i] = (m_state[0] >> (24 - i * 8)) & 0x000000ff; + digest[i + 4] = (m_state[1] >> (24 - i * 8)) & 0x000000ff; + digest[i + 8] = (m_state[2] >> (24 - i * 8)) & 0x000000ff; + digest[i + 12] = (m_state[3] >> (24 - i * 8)) & 0x000000ff; + digest[i + 16] = (m_state[4] >> (24 - i * 8)) & 0x000000ff; + digest[i + 20] = (m_state[5] >> (24 - i * 8)) & 0x000000ff; + digest[i + 24] = (m_state[6] >> (24 - i * 8)) & 0x000000ff; + digest[i + 28] = (m_state[7] >> (24 - i * 8)) & 0x000000ff; + } +} + +SHA256Digest::Digest SHA256Digest::Final() +{ + Digest ret; + Final(ret); + return ret; +} diff --git a/src/common/sha256_digest.h b/src/common/sha256_digest.h new file mode 100644 index 000000000..0ffb0be6b --- /dev/null +++ b/src/common/sha256_digest.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#pragma once + +#include "types.h" + +#include +#include +#include + +class SHA256Digest +{ +public: + enum : u32 + { + DIGEST_SIZE = 32, + BLOCK_SIZE = 64, + }; + + using Digest = std::array; + + SHA256Digest(); + + void Update(const void* data, size_t len); + void Update(std::span data); + void Final(std::span digest); + Digest Final(); + void Reset(); + + static std::string DigestToString(const std::span digest); + + static Digest GetDigest(const void* data, size_t len); + static Digest GetDigest(std::span data); + +private: + void TransformBlock(); + + u64 m_bit_length = 0; + std::array m_state = {}; + u32 m_block_length = 0; + std::array m_block = {}; +}; From c0b4627c1158cb29edc7316165f5a0edb1312889 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 15:25:36 +1000 Subject: [PATCH 03/69] StringUtil: Drop old MacOS workaround --- src/common/string_util.h | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/common/string_util.h b/src/common/string_util.h index e5b821840..fee55fa51 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -22,9 +22,6 @@ #if !defined(_MSC_VER) #include #include -#ifdef __APPLE__ -#include -#endif #endif namespace StringUtil { @@ -175,8 +172,6 @@ inline std::optional FromChars(const std::string_view str, std::string_view* template::value, bool> = true> inline std::string ToChars(T value, int base = 10) { - // to_chars() requires macOS 10.15+. -#if !defined(__APPLE__) || MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_15 constexpr size_t MAX_SIZE = 32; char buf[MAX_SIZE]; std::string ret; @@ -186,12 +181,6 @@ inline std::string ToChars(T value, int base = 10) ret.append(buf, result.ptr - buf); return ret; -#else - std::ostringstream ss; - ss.imbue(std::locale::classic()); - ss << std::setbase(base) << value; - return ss.str(); -#endif } template::value, bool> = true> From b39f1558ecd4b1f76c67b1d617bfc3ec8dcedd3e Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 15:58:33 +1000 Subject: [PATCH 04/69] StringUtil: Add Base64 decode/encode functions --- src/common-tests/string_tests.cpp | 38 +++++++++++ src/common/string_util.cpp | 101 ++++++++++++++++++++++++++++++ src/common/string_util.h | 28 +++++++++ 3 files changed, 167 insertions(+) diff --git a/src/common-tests/string_tests.cpp b/src/common-tests/string_tests.cpp index 4f3da18d6..ddf84ba7e 100644 --- a/src/common-tests/string_tests.cpp +++ b/src/common-tests/string_tests.cpp @@ -33,3 +33,41 @@ TEST(StringUtil, EllipsiseInPlace) StringUtil::EllipsiseInPlace(s, 10, "..."); ASSERT_EQ(s, "Hello"); } + +TEST(StringUtil, Base64EncodeDecode) +{ + struct TestCase + { + const char* hexString; + const char* base64String; + }; + static const TestCase testCases[] = { + {"33326a6f646933326a68663937683732383368", "MzJqb2RpMzJqaGY5N2g3MjgzaA=="}, + {"32753965333268756979386672677537366967723839683432703075693132393065755c5d0931325c335c31323439303438753839333272", + "MnU5ZTMyaHVpeThmcmd1NzZpZ3I4OWg0MnAwdWkxMjkwZXVcXQkxMlwzXDEyNDkwNDh1ODkzMnI="}, + {"3332726a33323738676838666233326830393233386637683938323139", "MzJyajMyNzhnaDhmYjMyaDA5MjM4ZjdoOTgyMTk="}, + {"9956967BE9C96E10B27FF8897A5B768A2F4B103CE934718D020FE6B5B770", "mVaWe+nJbhCyf/iJelt2ii9LEDzpNHGNAg/mtbdw"}, + {"BC94251814827A5D503D62D5EE6CBAB0FD55D2E2FCEDBB2261D6010084B95DD648766D8983F03AFA3908956D8201E26BB09FE52B515A61A9E" + "1D3ADC207BD9E622128F22929CDED456B595A410F7168B0BA6370289E6291E38E47C18278561C79A7297C21D23C06BB2F694DC2F65FAAF994" + "59E3FC14B1FA415A3320AF00ACE54C00BE", + "vJQlGBSCel1QPWLV7my6sP1V0uL87bsiYdYBAIS5XdZIdm2Jg/A6+jkIlW2CAeJrsJ/" + "lK1FaYanh063CB72eYiEo8ikpze1Fa1laQQ9xaLC6Y3AonmKR445HwYJ4Vhx5pyl8IdI8BrsvaU3C9l+q+ZRZ4/wUsfpBWjMgrwCs5UwAvg=="}, + {"192B42CB0F66F69BE8A5", "GStCyw9m9pvopQ=="}, + {"38ABD400F3BB6960EB60C056719B5362", "OKvUAPO7aWDrYMBWcZtTYg=="}, + {"776FAB27DC7F8DA86F298D55B69F8C278D53871F8CBCCF", "d2+rJ9x/jahvKY1Vtp+MJ41Thx+MvM8="}, + {"B1ED3EA2E35EE69C7E16707B05042A", "se0+ouNe5px+FnB7BQQq"}, + }; + + for (const TestCase& tc : testCases) + { + std::optional> bytes = StringUtil::DecodeHex(tc.hexString); + ASSERT_TRUE(bytes.has_value()); + + std::string encoded_b64 = StringUtil::EncodeBase64(bytes.value()); + ASSERT_EQ(encoded_b64, tc.base64String); + + std::optional> dbytes = StringUtil::DecodeBase64(tc.base64String); + ASSERT_TRUE(dbytes.has_value()); + ASSERT_EQ(dbytes.value(), bytes.value()); + } +} diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index ccb7b4181..18cc8110d 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -197,6 +197,107 @@ std::string StringUtil::EncodeHex(const void* data, size_t length) return ret; } +size_t StringUtil::EncodeBase64(const std::span dest, const std::span data) +{ + const size_t expected_length = EncodedBase64Length(data); + Assert(dest.size() <= expected_length); + + static constexpr std::array table = { + {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', + 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}}; + + const size_t dataLength = data.size(); + size_t dest_pos = 0; + + for (size_t i = 0; i < dataLength;) + { + const size_t bytes_in_sequence = std::min(dataLength - i, 3); + switch (bytes_in_sequence) + { + case 1: + dest[dest_pos++] = table[(data[i] >> 2) & 63]; + dest[dest_pos++] = table[(data[i] & 3) << 4]; + dest[dest_pos++] = '='; + dest[dest_pos++] = '='; + break; + + case 2: + dest[dest_pos++] = table[(data[i] >> 2) & 63]; + dest[dest_pos++] = table[((data[i] & 3) << 4) | ((data[i + 1] >> 4) & 15)]; + dest[dest_pos++] = table[(data[i + 1] & 15) << 2]; + dest[dest_pos++] = '='; + break; + + case 3: + dest[dest_pos++] = table[(data[i] >> 2) & 63]; + dest[dest_pos++] = table[((data[i] & 3) << 4) | ((data[i + 1] >> 4) & 15)]; + dest[dest_pos++] = table[((data[i + 1] & 15) << 2) | ((data[i + 2] >> 6) & 3)]; + dest[dest_pos++] = table[data[i + 2] & 63]; + break; + + DefaultCaseIsUnreachable(); + } + + i += bytes_in_sequence; + } + + DebugAssert(dest_pos == expected_length); + return dest_pos; +} + +size_t StringUtil::DecodeBase64(const std::span data, const std::string_view str) +{ + static constexpr std::array table = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 64, 64, 64, 0, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, 64, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64}; + + const size_t str_length = str.length(); + if ((str_length % 4) != 0) + return 0; + + size_t data_pos = 0; + for (size_t i = 0; i < str_length;) + { + const u8 byte1 = table[str[i++] & 0x7F]; + const u8 byte2 = table[str[i++] & 0x7F]; + const u8 byte3 = table[str[i++] & 0x7F]; + const u8 byte4 = table[str[i++] & 0x7F]; + + if (byte1 == 64 || byte2 == 64 || byte3 == 64 || byte4 == 64) + break; + + data[data_pos++] = (byte1 << 2) | (byte2 >> 4); + if (str[i - 2] != '=') + data[data_pos++] = ((byte2 << 4) | (byte3 >> 2)); + if (str[i - 1] != '=') + data[data_pos++] = ((byte3 << 6) | byte4); + } + + return data_pos; +} + +std::optional> StringUtil::DecodeBase64(const std::string_view str) +{ + std::vector ret; + const size_t len = DecodedBase64Length(str); + ret.resize(len); + if (DecodeBase64(ret, str) != len) + ret = {}; + return ret; +} + +std::string StringUtil::EncodeBase64(const std::span data) +{ + std::string ret; + ret.resize(EncodedBase64Length(data)); + ret.resize(EncodeBase64(ret, data)); + return ret; +} + std::string_view StringUtil::StripWhitespace(const std::string_view str) { std::string_view::size_type start = 0; diff --git a/src/common/string_util.h b/src/common/string_util.h index fee55fa51..8c01ec17e 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -4,6 +4,7 @@ #pragma once #include "types.h" +#include #include #include #include @@ -270,6 +271,33 @@ static constexpr std::array ParseFixedHexString(const char str[]) return h; } +/// Encode/decode Base64 buffers. +static constexpr size_t DecodedBase64Length(const std::string_view str) +{ + // Should be a multiple of 4. + const size_t str_length = str.length(); + if ((str_length % 4) != 0) + return 0; + + // Reverse padding. + size_t padding = 0; + if (str.length() >= 2) + { + padding += static_cast(str[str_length - 1] == '='); + padding += static_cast(str[str_length - 2] == '='); + } + + return (str_length / 4) * 3 - padding; +} +static constexpr size_t EncodedBase64Length(const std::span data) +{ + return ((data.size() + 2) / 3) * 4; +} +size_t DecodeBase64(const std::span data, const std::string_view str); +size_t EncodeBase64(const std::span dest, const std::span data); +std::string EncodeBase64(const std::span data); +std::optional> DecodeBase64(const std::string_view str); + /// StartsWith/EndsWith variants which aren't case sensitive. ALWAYS_INLINE static bool StartsWithNoCase(const std::string_view str, const std::string_view prefix) { From 83274c7e3b3ac384ee47be37733dbb19ce6b9ab2 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 17:05:16 +1000 Subject: [PATCH 05/69] ThirdParty/SmallVector: Compile fixes --- src/common/thirdparty/SmallVector.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/thirdparty/SmallVector.h b/src/common/thirdparty/SmallVector.h index ad831b0e9..bda7105b0 100644 --- a/src/common/thirdparty/SmallVector.h +++ b/src/common/thirdparty/SmallVector.h @@ -183,7 +183,7 @@ protected: /// NewSize. bool isSafeToReferenceAfterResize(const void *Elt, size_t NewSize) { // Past the end. - if (LLVM_LIKELY(!isReferenceToStorage(Elt))) + if (!isReferenceToStorage(Elt)) [[likely]] return true; // Return false if Elt will be destroyed by shrinking. @@ -946,7 +946,7 @@ public: } template reference emplace_back(ArgTypes &&... Args) { - if (LLVM_UNLIKELY(this->size() >= this->capacity())) + if (this->size() >= this->capacity()) [[unlikely]] return this->growAndEmplaceBack(std::forward(Args)...); ::new ((void *)this->end()) T(std::forward(Args)...); From d3246deb77605a9ef33de0e76ffb70a3c246cdd5 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 17:06:06 +1000 Subject: [PATCH 06/69] ThirdParty: Add aes.cpp --- src/common/CMakeLists.txt | 2 + src/common/common.vcxproj | 2 + src/common/common.vcxproj.filters | 2 + src/common/thirdparty/aes.cpp | 1173 +++++++++++++++++++++++++++++ src/common/thirdparty/aes.h | 122 +++ 5 files changed, 1301 insertions(+) create mode 100644 src/common/thirdparty/aes.cpp create mode 100644 src/common/thirdparty/aes.h diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 783ccd288..f67e649e1 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -58,6 +58,8 @@ add_library(common string_util.h thirdparty/SmallVector.cpp thirdparty/SmallVector.h + thirdparty/aes.cpp + thirdparty/aes.h threading.cpp threading.h timer.cpp diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj index 925c5486c..d08734283 100644 --- a/src/common/common.vcxproj +++ b/src/common/common.vcxproj @@ -41,6 +41,7 @@ + @@ -69,6 +70,7 @@ + diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters index 6f329bd67..7e60a05f0 100644 --- a/src/common/common.vcxproj.filters +++ b/src/common/common.vcxproj.filters @@ -51,6 +51,7 @@ + @@ -80,6 +81,7 @@ + diff --git a/src/common/thirdparty/aes.cpp b/src/common/thirdparty/aes.cpp new file mode 100644 index 000000000..974f700e5 --- /dev/null +++ b/src/common/thirdparty/aes.cpp @@ -0,0 +1,1173 @@ +/********************************************************************* +* Filename: aes.c +* Author: Brad Conte (brad AT bradconte.com) +* Copyright: +* Disclaimer: This code is presented "as is" without any guarantees. +* Details: This code is the implementation of the AES algorithm and + the CTR, CBC, and CCM modes of operation it can be used in. + AES is, specified by the NIST in in publication FIPS PUB 197, + availible at: + * http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf . + The CBC and CTR modes of operation are specified by + NIST SP 800-38 A, available at: + * http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf . + The CCM mode of operation is specified by NIST SP80-38 C, available at: + * http://csrc.nist.gov/publications/nistpubs/800-38C/SP800-38C_updated-July20_2007.pdf +*********************************************************************/ + +/*************************** HEADER FILES ***************************/ +#include "aes.h" + +#include +#include + +/****************************** MACROS ******************************/ +// The least significant byte of the word is rotated to the end. +#define KE_ROTWORD(x) (((x) << 8) | ((x) >> 24)) + +/**************************** DATA TYPES ****************************/ +#define AES_128_ROUNDS 10 +#define AES_192_ROUNDS 12 +#define AES_256_ROUNDS 14 + +/*********************** FUNCTION DECLARATIONS **********************/ +#if 0 +static void ccm_prepare_first_ctr_blk(uint8_t counter[], const uint8_t nonce[], int nonce_len, + int payload_len_store_size); +static void ccm_prepare_first_format_blk(uint8_t buf[], int assoc_len, int payload_len, int payload_len_store_size, + int mac_len, const uint8_t nonce[], int nonce_len); +static void ccm_format_assoc_data(uint8_t buf[], int* end_of_buf, const uint8_t assoc[], int assoc_len); +static void ccm_format_payload_data(uint8_t buf[], int* end_of_buf, const uint8_t payload[], int payload_len); +#endif + +/**************************** VARIABLES *****************************/ +// This is the specified AES SBox. To look up a substitution value, put the first +// nibble in the first index (row) and the second nibble in the second index (column). +static const uint8_t aes_sbox[16][16] = { + {0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76}, + {0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0}, + {0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15}, + {0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75}, + {0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84}, + {0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF}, + {0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8}, + {0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2}, + {0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73}, + {0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB}, + {0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79}, + {0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08}, + {0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A}, + {0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E}, + {0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF}, + {0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16}}; + +static const uint8_t aes_invsbox[16][16] = { + {0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB}, + {0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB}, + {0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E}, + {0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25}, + {0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92}, + {0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84}, + {0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06}, + {0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B}, + {0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73}, + {0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E}, + {0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B}, + {0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4}, + {0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F}, + {0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF}, + {0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61}, + {0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D}}; + +// This table stores pre-calculated values for all possible GF(2^8) calculations.This +// table is only used by the (Inv)MixColumns steps. +// USAGE: The second index (column) is the coefficient of multiplication. Only 7 different +// coefficients are used: 0x01, 0x02, 0x03, 0x09, 0x0b, 0x0d, 0x0e, but multiplication by +// 1 is negligible leaving only 6 coefficients. Each column of the table is devoted to one +// of these coefficients, in the ascending order of value, from values 0x00 to 0xFF. +static const uint8_t gf_mul[256][6] = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x02, 0x03, 0x09, 0x0b, 0x0d, 0x0e}, {0x04, 0x06, 0x12, 0x16, 0x1a, 0x1c}, + {0x06, 0x05, 0x1b, 0x1d, 0x17, 0x12}, {0x08, 0x0c, 0x24, 0x2c, 0x34, 0x38}, {0x0a, 0x0f, 0x2d, 0x27, 0x39, 0x36}, + {0x0c, 0x0a, 0x36, 0x3a, 0x2e, 0x24}, {0x0e, 0x09, 0x3f, 0x31, 0x23, 0x2a}, {0x10, 0x18, 0x48, 0x58, 0x68, 0x70}, + {0x12, 0x1b, 0x41, 0x53, 0x65, 0x7e}, {0x14, 0x1e, 0x5a, 0x4e, 0x72, 0x6c}, {0x16, 0x1d, 0x53, 0x45, 0x7f, 0x62}, + {0x18, 0x14, 0x6c, 0x74, 0x5c, 0x48}, {0x1a, 0x17, 0x65, 0x7f, 0x51, 0x46}, {0x1c, 0x12, 0x7e, 0x62, 0x46, 0x54}, + {0x1e, 0x11, 0x77, 0x69, 0x4b, 0x5a}, {0x20, 0x30, 0x90, 0xb0, 0xd0, 0xe0}, {0x22, 0x33, 0x99, 0xbb, 0xdd, 0xee}, + {0x24, 0x36, 0x82, 0xa6, 0xca, 0xfc}, {0x26, 0x35, 0x8b, 0xad, 0xc7, 0xf2}, {0x28, 0x3c, 0xb4, 0x9c, 0xe4, 0xd8}, + {0x2a, 0x3f, 0xbd, 0x97, 0xe9, 0xd6}, {0x2c, 0x3a, 0xa6, 0x8a, 0xfe, 0xc4}, {0x2e, 0x39, 0xaf, 0x81, 0xf3, 0xca}, + {0x30, 0x28, 0xd8, 0xe8, 0xb8, 0x90}, {0x32, 0x2b, 0xd1, 0xe3, 0xb5, 0x9e}, {0x34, 0x2e, 0xca, 0xfe, 0xa2, 0x8c}, + {0x36, 0x2d, 0xc3, 0xf5, 0xaf, 0x82}, {0x38, 0x24, 0xfc, 0xc4, 0x8c, 0xa8}, {0x3a, 0x27, 0xf5, 0xcf, 0x81, 0xa6}, + {0x3c, 0x22, 0xee, 0xd2, 0x96, 0xb4}, {0x3e, 0x21, 0xe7, 0xd9, 0x9b, 0xba}, {0x40, 0x60, 0x3b, 0x7b, 0xbb, 0xdb}, + {0x42, 0x63, 0x32, 0x70, 0xb6, 0xd5}, {0x44, 0x66, 0x29, 0x6d, 0xa1, 0xc7}, {0x46, 0x65, 0x20, 0x66, 0xac, 0xc9}, + {0x48, 0x6c, 0x1f, 0x57, 0x8f, 0xe3}, {0x4a, 0x6f, 0x16, 0x5c, 0x82, 0xed}, {0x4c, 0x6a, 0x0d, 0x41, 0x95, 0xff}, + {0x4e, 0x69, 0x04, 0x4a, 0x98, 0xf1}, {0x50, 0x78, 0x73, 0x23, 0xd3, 0xab}, {0x52, 0x7b, 0x7a, 0x28, 0xde, 0xa5}, + {0x54, 0x7e, 0x61, 0x35, 0xc9, 0xb7}, {0x56, 0x7d, 0x68, 0x3e, 0xc4, 0xb9}, {0x58, 0x74, 0x57, 0x0f, 0xe7, 0x93}, + {0x5a, 0x77, 0x5e, 0x04, 0xea, 0x9d}, {0x5c, 0x72, 0x45, 0x19, 0xfd, 0x8f}, {0x5e, 0x71, 0x4c, 0x12, 0xf0, 0x81}, + {0x60, 0x50, 0xab, 0xcb, 0x6b, 0x3b}, {0x62, 0x53, 0xa2, 0xc0, 0x66, 0x35}, {0x64, 0x56, 0xb9, 0xdd, 0x71, 0x27}, + {0x66, 0x55, 0xb0, 0xd6, 0x7c, 0x29}, {0x68, 0x5c, 0x8f, 0xe7, 0x5f, 0x03}, {0x6a, 0x5f, 0x86, 0xec, 0x52, 0x0d}, + {0x6c, 0x5a, 0x9d, 0xf1, 0x45, 0x1f}, {0x6e, 0x59, 0x94, 0xfa, 0x48, 0x11}, {0x70, 0x48, 0xe3, 0x93, 0x03, 0x4b}, + {0x72, 0x4b, 0xea, 0x98, 0x0e, 0x45}, {0x74, 0x4e, 0xf1, 0x85, 0x19, 0x57}, {0x76, 0x4d, 0xf8, 0x8e, 0x14, 0x59}, + {0x78, 0x44, 0xc7, 0xbf, 0x37, 0x73}, {0x7a, 0x47, 0xce, 0xb4, 0x3a, 0x7d}, {0x7c, 0x42, 0xd5, 0xa9, 0x2d, 0x6f}, + {0x7e, 0x41, 0xdc, 0xa2, 0x20, 0x61}, {0x80, 0xc0, 0x76, 0xf6, 0x6d, 0xad}, {0x82, 0xc3, 0x7f, 0xfd, 0x60, 0xa3}, + {0x84, 0xc6, 0x64, 0xe0, 0x77, 0xb1}, {0x86, 0xc5, 0x6d, 0xeb, 0x7a, 0xbf}, {0x88, 0xcc, 0x52, 0xda, 0x59, 0x95}, + {0x8a, 0xcf, 0x5b, 0xd1, 0x54, 0x9b}, {0x8c, 0xca, 0x40, 0xcc, 0x43, 0x89}, {0x8e, 0xc9, 0x49, 0xc7, 0x4e, 0x87}, + {0x90, 0xd8, 0x3e, 0xae, 0x05, 0xdd}, {0x92, 0xdb, 0x37, 0xa5, 0x08, 0xd3}, {0x94, 0xde, 0x2c, 0xb8, 0x1f, 0xc1}, + {0x96, 0xdd, 0x25, 0xb3, 0x12, 0xcf}, {0x98, 0xd4, 0x1a, 0x82, 0x31, 0xe5}, {0x9a, 0xd7, 0x13, 0x89, 0x3c, 0xeb}, + {0x9c, 0xd2, 0x08, 0x94, 0x2b, 0xf9}, {0x9e, 0xd1, 0x01, 0x9f, 0x26, 0xf7}, {0xa0, 0xf0, 0xe6, 0x46, 0xbd, 0x4d}, + {0xa2, 0xf3, 0xef, 0x4d, 0xb0, 0x43}, {0xa4, 0xf6, 0xf4, 0x50, 0xa7, 0x51}, {0xa6, 0xf5, 0xfd, 0x5b, 0xaa, 0x5f}, + {0xa8, 0xfc, 0xc2, 0x6a, 0x89, 0x75}, {0xaa, 0xff, 0xcb, 0x61, 0x84, 0x7b}, {0xac, 0xfa, 0xd0, 0x7c, 0x93, 0x69}, + {0xae, 0xf9, 0xd9, 0x77, 0x9e, 0x67}, {0xb0, 0xe8, 0xae, 0x1e, 0xd5, 0x3d}, {0xb2, 0xeb, 0xa7, 0x15, 0xd8, 0x33}, + {0xb4, 0xee, 0xbc, 0x08, 0xcf, 0x21}, {0xb6, 0xed, 0xb5, 0x03, 0xc2, 0x2f}, {0xb8, 0xe4, 0x8a, 0x32, 0xe1, 0x05}, + {0xba, 0xe7, 0x83, 0x39, 0xec, 0x0b}, {0xbc, 0xe2, 0x98, 0x24, 0xfb, 0x19}, {0xbe, 0xe1, 0x91, 0x2f, 0xf6, 0x17}, + {0xc0, 0xa0, 0x4d, 0x8d, 0xd6, 0x76}, {0xc2, 0xa3, 0x44, 0x86, 0xdb, 0x78}, {0xc4, 0xa6, 0x5f, 0x9b, 0xcc, 0x6a}, + {0xc6, 0xa5, 0x56, 0x90, 0xc1, 0x64}, {0xc8, 0xac, 0x69, 0xa1, 0xe2, 0x4e}, {0xca, 0xaf, 0x60, 0xaa, 0xef, 0x40}, + {0xcc, 0xaa, 0x7b, 0xb7, 0xf8, 0x52}, {0xce, 0xa9, 0x72, 0xbc, 0xf5, 0x5c}, {0xd0, 0xb8, 0x05, 0xd5, 0xbe, 0x06}, + {0xd2, 0xbb, 0x0c, 0xde, 0xb3, 0x08}, {0xd4, 0xbe, 0x17, 0xc3, 0xa4, 0x1a}, {0xd6, 0xbd, 0x1e, 0xc8, 0xa9, 0x14}, + {0xd8, 0xb4, 0x21, 0xf9, 0x8a, 0x3e}, {0xda, 0xb7, 0x28, 0xf2, 0x87, 0x30}, {0xdc, 0xb2, 0x33, 0xef, 0x90, 0x22}, + {0xde, 0xb1, 0x3a, 0xe4, 0x9d, 0x2c}, {0xe0, 0x90, 0xdd, 0x3d, 0x06, 0x96}, {0xe2, 0x93, 0xd4, 0x36, 0x0b, 0x98}, + {0xe4, 0x96, 0xcf, 0x2b, 0x1c, 0x8a}, {0xe6, 0x95, 0xc6, 0x20, 0x11, 0x84}, {0xe8, 0x9c, 0xf9, 0x11, 0x32, 0xae}, + {0xea, 0x9f, 0xf0, 0x1a, 0x3f, 0xa0}, {0xec, 0x9a, 0xeb, 0x07, 0x28, 0xb2}, {0xee, 0x99, 0xe2, 0x0c, 0x25, 0xbc}, + {0xf0, 0x88, 0x95, 0x65, 0x6e, 0xe6}, {0xf2, 0x8b, 0x9c, 0x6e, 0x63, 0xe8}, {0xf4, 0x8e, 0x87, 0x73, 0x74, 0xfa}, + {0xf6, 0x8d, 0x8e, 0x78, 0x79, 0xf4}, {0xf8, 0x84, 0xb1, 0x49, 0x5a, 0xde}, {0xfa, 0x87, 0xb8, 0x42, 0x57, 0xd0}, + {0xfc, 0x82, 0xa3, 0x5f, 0x40, 0xc2}, {0xfe, 0x81, 0xaa, 0x54, 0x4d, 0xcc}, {0x1b, 0x9b, 0xec, 0xf7, 0xda, 0x41}, + {0x19, 0x98, 0xe5, 0xfc, 0xd7, 0x4f}, {0x1f, 0x9d, 0xfe, 0xe1, 0xc0, 0x5d}, {0x1d, 0x9e, 0xf7, 0xea, 0xcd, 0x53}, + {0x13, 0x97, 0xc8, 0xdb, 0xee, 0x79}, {0x11, 0x94, 0xc1, 0xd0, 0xe3, 0x77}, {0x17, 0x91, 0xda, 0xcd, 0xf4, 0x65}, + {0x15, 0x92, 0xd3, 0xc6, 0xf9, 0x6b}, {0x0b, 0x83, 0xa4, 0xaf, 0xb2, 0x31}, {0x09, 0x80, 0xad, 0xa4, 0xbf, 0x3f}, + {0x0f, 0x85, 0xb6, 0xb9, 0xa8, 0x2d}, {0x0d, 0x86, 0xbf, 0xb2, 0xa5, 0x23}, {0x03, 0x8f, 0x80, 0x83, 0x86, 0x09}, + {0x01, 0x8c, 0x89, 0x88, 0x8b, 0x07}, {0x07, 0x89, 0x92, 0x95, 0x9c, 0x15}, {0x05, 0x8a, 0x9b, 0x9e, 0x91, 0x1b}, + {0x3b, 0xab, 0x7c, 0x47, 0x0a, 0xa1}, {0x39, 0xa8, 0x75, 0x4c, 0x07, 0xaf}, {0x3f, 0xad, 0x6e, 0x51, 0x10, 0xbd}, + {0x3d, 0xae, 0x67, 0x5a, 0x1d, 0xb3}, {0x33, 0xa7, 0x58, 0x6b, 0x3e, 0x99}, {0x31, 0xa4, 0x51, 0x60, 0x33, 0x97}, + {0x37, 0xa1, 0x4a, 0x7d, 0x24, 0x85}, {0x35, 0xa2, 0x43, 0x76, 0x29, 0x8b}, {0x2b, 0xb3, 0x34, 0x1f, 0x62, 0xd1}, + {0x29, 0xb0, 0x3d, 0x14, 0x6f, 0xdf}, {0x2f, 0xb5, 0x26, 0x09, 0x78, 0xcd}, {0x2d, 0xb6, 0x2f, 0x02, 0x75, 0xc3}, + {0x23, 0xbf, 0x10, 0x33, 0x56, 0xe9}, {0x21, 0xbc, 0x19, 0x38, 0x5b, 0xe7}, {0x27, 0xb9, 0x02, 0x25, 0x4c, 0xf5}, + {0x25, 0xba, 0x0b, 0x2e, 0x41, 0xfb}, {0x5b, 0xfb, 0xd7, 0x8c, 0x61, 0x9a}, {0x59, 0xf8, 0xde, 0x87, 0x6c, 0x94}, + {0x5f, 0xfd, 0xc5, 0x9a, 0x7b, 0x86}, {0x5d, 0xfe, 0xcc, 0x91, 0x76, 0x88}, {0x53, 0xf7, 0xf3, 0xa0, 0x55, 0xa2}, + {0x51, 0xf4, 0xfa, 0xab, 0x58, 0xac}, {0x57, 0xf1, 0xe1, 0xb6, 0x4f, 0xbe}, {0x55, 0xf2, 0xe8, 0xbd, 0x42, 0xb0}, + {0x4b, 0xe3, 0x9f, 0xd4, 0x09, 0xea}, {0x49, 0xe0, 0x96, 0xdf, 0x04, 0xe4}, {0x4f, 0xe5, 0x8d, 0xc2, 0x13, 0xf6}, + {0x4d, 0xe6, 0x84, 0xc9, 0x1e, 0xf8}, {0x43, 0xef, 0xbb, 0xf8, 0x3d, 0xd2}, {0x41, 0xec, 0xb2, 0xf3, 0x30, 0xdc}, + {0x47, 0xe9, 0xa9, 0xee, 0x27, 0xce}, {0x45, 0xea, 0xa0, 0xe5, 0x2a, 0xc0}, {0x7b, 0xcb, 0x47, 0x3c, 0xb1, 0x7a}, + {0x79, 0xc8, 0x4e, 0x37, 0xbc, 0x74}, {0x7f, 0xcd, 0x55, 0x2a, 0xab, 0x66}, {0x7d, 0xce, 0x5c, 0x21, 0xa6, 0x68}, + {0x73, 0xc7, 0x63, 0x10, 0x85, 0x42}, {0x71, 0xc4, 0x6a, 0x1b, 0x88, 0x4c}, {0x77, 0xc1, 0x71, 0x06, 0x9f, 0x5e}, + {0x75, 0xc2, 0x78, 0x0d, 0x92, 0x50}, {0x6b, 0xd3, 0x0f, 0x64, 0xd9, 0x0a}, {0x69, 0xd0, 0x06, 0x6f, 0xd4, 0x04}, + {0x6f, 0xd5, 0x1d, 0x72, 0xc3, 0x16}, {0x6d, 0xd6, 0x14, 0x79, 0xce, 0x18}, {0x63, 0xdf, 0x2b, 0x48, 0xed, 0x32}, + {0x61, 0xdc, 0x22, 0x43, 0xe0, 0x3c}, {0x67, 0xd9, 0x39, 0x5e, 0xf7, 0x2e}, {0x65, 0xda, 0x30, 0x55, 0xfa, 0x20}, + {0x9b, 0x5b, 0x9a, 0x01, 0xb7, 0xec}, {0x99, 0x58, 0x93, 0x0a, 0xba, 0xe2}, {0x9f, 0x5d, 0x88, 0x17, 0xad, 0xf0}, + {0x9d, 0x5e, 0x81, 0x1c, 0xa0, 0xfe}, {0x93, 0x57, 0xbe, 0x2d, 0x83, 0xd4}, {0x91, 0x54, 0xb7, 0x26, 0x8e, 0xda}, + {0x97, 0x51, 0xac, 0x3b, 0x99, 0xc8}, {0x95, 0x52, 0xa5, 0x30, 0x94, 0xc6}, {0x8b, 0x43, 0xd2, 0x59, 0xdf, 0x9c}, + {0x89, 0x40, 0xdb, 0x52, 0xd2, 0x92}, {0x8f, 0x45, 0xc0, 0x4f, 0xc5, 0x80}, {0x8d, 0x46, 0xc9, 0x44, 0xc8, 0x8e}, + {0x83, 0x4f, 0xf6, 0x75, 0xeb, 0xa4}, {0x81, 0x4c, 0xff, 0x7e, 0xe6, 0xaa}, {0x87, 0x49, 0xe4, 0x63, 0xf1, 0xb8}, + {0x85, 0x4a, 0xed, 0x68, 0xfc, 0xb6}, {0xbb, 0x6b, 0x0a, 0xb1, 0x67, 0x0c}, {0xb9, 0x68, 0x03, 0xba, 0x6a, 0x02}, + {0xbf, 0x6d, 0x18, 0xa7, 0x7d, 0x10}, {0xbd, 0x6e, 0x11, 0xac, 0x70, 0x1e}, {0xb3, 0x67, 0x2e, 0x9d, 0x53, 0x34}, + {0xb1, 0x64, 0x27, 0x96, 0x5e, 0x3a}, {0xb7, 0x61, 0x3c, 0x8b, 0x49, 0x28}, {0xb5, 0x62, 0x35, 0x80, 0x44, 0x26}, + {0xab, 0x73, 0x42, 0xe9, 0x0f, 0x7c}, {0xa9, 0x70, 0x4b, 0xe2, 0x02, 0x72}, {0xaf, 0x75, 0x50, 0xff, 0x15, 0x60}, + {0xad, 0x76, 0x59, 0xf4, 0x18, 0x6e}, {0xa3, 0x7f, 0x66, 0xc5, 0x3b, 0x44}, {0xa1, 0x7c, 0x6f, 0xce, 0x36, 0x4a}, + {0xa7, 0x79, 0x74, 0xd3, 0x21, 0x58}, {0xa5, 0x7a, 0x7d, 0xd8, 0x2c, 0x56}, {0xdb, 0x3b, 0xa1, 0x7a, 0x0c, 0x37}, + {0xd9, 0x38, 0xa8, 0x71, 0x01, 0x39}, {0xdf, 0x3d, 0xb3, 0x6c, 0x16, 0x2b}, {0xdd, 0x3e, 0xba, 0x67, 0x1b, 0x25}, + {0xd3, 0x37, 0x85, 0x56, 0x38, 0x0f}, {0xd1, 0x34, 0x8c, 0x5d, 0x35, 0x01}, {0xd7, 0x31, 0x97, 0x40, 0x22, 0x13}, + {0xd5, 0x32, 0x9e, 0x4b, 0x2f, 0x1d}, {0xcb, 0x23, 0xe9, 0x22, 0x64, 0x47}, {0xc9, 0x20, 0xe0, 0x29, 0x69, 0x49}, + {0xcf, 0x25, 0xfb, 0x34, 0x7e, 0x5b}, {0xcd, 0x26, 0xf2, 0x3f, 0x73, 0x55}, {0xc3, 0x2f, 0xcd, 0x0e, 0x50, 0x7f}, + {0xc1, 0x2c, 0xc4, 0x05, 0x5d, 0x71}, {0xc7, 0x29, 0xdf, 0x18, 0x4a, 0x63}, {0xc5, 0x2a, 0xd6, 0x13, 0x47, 0x6d}, + {0xfb, 0x0b, 0x31, 0xca, 0xdc, 0xd7}, {0xf9, 0x08, 0x38, 0xc1, 0xd1, 0xd9}, {0xff, 0x0d, 0x23, 0xdc, 0xc6, 0xcb}, + {0xfd, 0x0e, 0x2a, 0xd7, 0xcb, 0xc5}, {0xf3, 0x07, 0x15, 0xe6, 0xe8, 0xef}, {0xf1, 0x04, 0x1c, 0xed, 0xe5, 0xe1}, + {0xf7, 0x01, 0x07, 0xf0, 0xf2, 0xf3}, {0xf5, 0x02, 0x0e, 0xfb, 0xff, 0xfd}, {0xeb, 0x13, 0x79, 0x92, 0xb4, 0xa7}, + {0xe9, 0x10, 0x70, 0x99, 0xb9, 0xa9}, {0xef, 0x15, 0x6b, 0x84, 0xae, 0xbb}, {0xed, 0x16, 0x62, 0x8f, 0xa3, 0xb5}, + {0xe3, 0x1f, 0x5d, 0xbe, 0x80, 0x9f}, {0xe1, 0x1c, 0x54, 0xb5, 0x8d, 0x91}, {0xe7, 0x19, 0x4f, 0xa8, 0x9a, 0x83}, + {0xe5, 0x1a, 0x46, 0xa3, 0x97, 0x8d}}; + +/*********************** FUNCTION DEFINITIONS ***********************/ +// XORs the in and out buffers, storing the result in out. Length is in bytes. +static void xor_buf(const uint8_t in[], uint8_t out[], size_t len) +{ + for (size_t idx = 0; idx < len; idx++) + out[idx] ^= in[idx]; +} + +/******************* + * AES - CBC + *******************/ +bool aes_encrypt_cbc(const uint8_t in[], size_t in_len, uint8_t out[], const uint32_t key[], int keysize, + const uint8_t iv[]) +{ + uint8_t buf_in[AES_BLOCK_SIZE], buf_out[AES_BLOCK_SIZE], iv_buf[AES_BLOCK_SIZE]; + + if (in_len % AES_BLOCK_SIZE != 0) + return false; + + const size_t blocks = in_len / AES_BLOCK_SIZE; + + std::memcpy(iv_buf, iv, AES_BLOCK_SIZE); + + for (size_t idx = 0; idx < blocks; idx++) + { + std::memcpy(buf_in, &in[idx * AES_BLOCK_SIZE], AES_BLOCK_SIZE); + xor_buf(iv_buf, buf_in, AES_BLOCK_SIZE); + aes_encrypt(buf_in, buf_out, key, keysize); + std::memcpy(&out[idx * AES_BLOCK_SIZE], buf_out, AES_BLOCK_SIZE); + std::memcpy(iv_buf, buf_out, AES_BLOCK_SIZE); + } + + return true; +} + +#if 0 +bool aes_encrypt_cbc_mac(const uint8_t in[], size_t in_len, uint8_t out[], const uint32_t key[], int keysize, + const uint8_t iv[]) +{ + uint8_t buf_in[AES_BLOCK_SIZE], buf_out[AES_BLOCK_SIZE], iv_buf[AES_BLOCK_SIZE]; + + if (in_len % AES_BLOCK_SIZE != 0) + return false; + + const size_t blocks = in_len / AES_BLOCK_SIZE; + + std::memcpy(iv_buf, iv, AES_BLOCK_SIZE); + + for (size_t idx = 0; idx < blocks; idx++) + { + std::memcpy(buf_in, &in[idx * AES_BLOCK_SIZE], AES_BLOCK_SIZE); + xor_buf(iv_buf, buf_in, AES_BLOCK_SIZE); + aes_encrypt(buf_in, buf_out, key, keysize); + std::memcpy(iv_buf, buf_out, AES_BLOCK_SIZE); + // Do not output all encrypted blocks. + } + + std::memcpy(out, buf_out, AES_BLOCK_SIZE); // Only output the last block. + + return true; +} +#endif + +bool aes_decrypt_cbc(const uint8_t in[], size_t in_len, uint8_t out[], const uint32_t key[], int keysize, + const uint8_t iv[]) +{ + uint8_t buf_in[AES_BLOCK_SIZE], buf_out[AES_BLOCK_SIZE], iv_buf[AES_BLOCK_SIZE]; + + if (in_len % AES_BLOCK_SIZE != 0) + return false; + + const size_t blocks = in_len / AES_BLOCK_SIZE; + + std::memcpy(iv_buf, iv, AES_BLOCK_SIZE); + + for (size_t idx = 0; idx < blocks; idx++) + { + std::memcpy(buf_in, &in[idx * AES_BLOCK_SIZE], AES_BLOCK_SIZE); + aes_decrypt(buf_in, buf_out, key, keysize); + xor_buf(iv_buf, buf_out, AES_BLOCK_SIZE); + std::memcpy(&out[idx * AES_BLOCK_SIZE], buf_out, AES_BLOCK_SIZE); + std::memcpy(iv_buf, buf_in, AES_BLOCK_SIZE); + } + + return true; +} + +#if 0 +/******************* + * AES - CTR + *******************/ +void aes_increment_iv(uint8_t iv[], int counter_size) +{ + // Use counter_size bytes at the end of the IV as the big-endian integer to increment. + for (uint32_t idx = AES_BLOCK_SIZE - 1; idx >= AES_BLOCK_SIZE - counter_size; idx--) + { + iv[idx]++; + if (iv[idx] != 0 || idx == AES_BLOCK_SIZE - counter_size) + break; + } +} + +// Performs the encryption in-place, the input and output buffers may be the same. +// Input may be an arbitrary length (in bytes). +void aes_encrypt_ctr(const uint8_t in[], size_t in_len, uint8_t out[], const uint32_t key[], int keysize, + const uint8_t iv[]) +{ + size_t idx = 0, last_block_length; + uint8_t iv_buf[AES_BLOCK_SIZE], out_buf[AES_BLOCK_SIZE]; + + if (in != out) + std::memcpy(out, in, in_len); + + std::memcpy(iv_buf, iv, AES_BLOCK_SIZE); + last_block_length = in_len - AES_BLOCK_SIZE; + + if (in_len > AES_BLOCK_SIZE) + { + for (idx = 0; idx < last_block_length; idx += AES_BLOCK_SIZE) + { + aes_encrypt(iv_buf, out_buf, key, keysize); + xor_buf(out_buf, &out[idx], AES_BLOCK_SIZE); + aes_increment_iv(iv_buf, AES_BLOCK_SIZE); + } + } + + aes_encrypt(iv_buf, out_buf, key, keysize); + xor_buf(out_buf, &out[idx], in_len - idx); // Use the Most Significant bytes. +} + +void aes_decrypt_ctr(const uint8_t in[], size_t in_len, uint8_t out[], const uint32_t key[], int keysize, + const uint8_t iv[]) +{ + // CTR encryption is its own inverse function. + aes_encrypt_ctr(in, in_len, out, key, keysize, iv); +} + +/******************* + * AES - CCM + *******************/ +// out_len = payload_len + assoc_len +bool aes_encrypt_ccm(const uint8_t payload[], uint32_t payload_len, const uint8_t assoc[], unsigned short assoc_len, + const uint8_t nonce[], unsigned short nonce_len, uint8_t out[], uint32_t* out_len, + uint32_t mac_len, const uint8_t key_str[], int keysize) +{ + uint8_t temp_iv[AES_BLOCK_SIZE], counter[AES_BLOCK_SIZE], mac[16], *buf; + int end_of_buf, payload_len_store_size; + uint32_t key[60]; + + if (mac_len != 4 && mac_len != 6 && mac_len != 8 && mac_len != 10 && mac_len != 12 && mac_len != 14 && mac_len != 16) + return false; + + if (nonce_len < 7 || nonce_len > 13) + return false; + + if (assoc_len > 32768 /* = 2^15 */) + return false; + + buf = (uint8_t*)malloc(payload_len + assoc_len + + 48 /*Round both payload and associated data up a block size and add an extra block.*/); + if (!buf) + return false; + + // Prepare the key for usage. + aes_key_setup(key_str, key, keysize); + + // Format the first block of the formatted data. + payload_len_store_size = AES_BLOCK_SIZE - 1 - nonce_len; + ccm_prepare_first_format_blk(buf, assoc_len, payload_len, payload_len_store_size, mac_len, nonce, nonce_len); + end_of_buf = AES_BLOCK_SIZE; + + // Format the Associated Data, aka, assoc[]. + ccm_format_assoc_data(buf, &end_of_buf, assoc, assoc_len); + + // Format the Payload, aka payload[]. + ccm_format_payload_data(buf, &end_of_buf, payload, payload_len); + + // Create the first counter block. + ccm_prepare_first_ctr_blk(counter, nonce, nonce_len, payload_len_store_size); + + // Perform the CBC operation with an IV of zeros on the formatted buffer to calculate the MAC. + std::memset(temp_iv, 0, AES_BLOCK_SIZE); + aes_encrypt_cbc_mac(buf, end_of_buf, mac, key, keysize, temp_iv); + + // Copy the Payload and MAC to the output buffer. + std::memcpy(out, payload, payload_len); + std::memcpy(&out[payload_len], mac, mac_len); + + // Encrypt the Payload with CTR mode with a counter starting at 1. + std::memcpy(temp_iv, counter, AES_BLOCK_SIZE); + aes_increment_iv(temp_iv, + AES_BLOCK_SIZE - 1 - + mac_len); // Last argument is the byte size of the counting portion of the counter block. /*BUG?*/ + aes_encrypt_ctr(out, payload_len, out, key, keysize, temp_iv); + + // Encrypt the MAC with CTR mode with a counter starting at 0. + aes_encrypt_ctr(&out[payload_len], mac_len, &out[payload_len], key, keysize, counter); + + free(buf); + *out_len = payload_len + mac_len; + + return true; +} + +// plaintext_len = ciphertext_len - mac_len +// Needs a flag for whether the MAC matches. +bool aes_decrypt_ccm(const uint8_t ciphertext[], uint32_t ciphertext_len, const uint8_t assoc[], + unsigned short assoc_len, const uint8_t nonce[], unsigned short nonce_len, uint8_t plaintext[], + uint32_t* plaintext_len, uint32_t mac_len, int* mac_auth, const uint8_t key_str[], int keysize) +{ + uint8_t temp_iv[AES_BLOCK_SIZE], counter[AES_BLOCK_SIZE], mac[16], mac_buf[16], *buf; + int end_of_buf, plaintext_len_store_size; + uint32_t key[60]; + + if (ciphertext_len <= mac_len) + return false; + + buf = (uint8_t*)malloc(assoc_len + ciphertext_len /*ciphertext_len = plaintext_len + mac_len*/ + 48); + if (!buf) + return false; + + // Prepare the key for usage. + aes_key_setup(key_str, key, keysize); + + // Copy the plaintext and MAC to the output buffers. + *plaintext_len = ciphertext_len - mac_len; + plaintext_len_store_size = AES_BLOCK_SIZE - 1 - nonce_len; + std::memcpy(plaintext, ciphertext, *plaintext_len); + std::memcpy(mac, &ciphertext[*plaintext_len], mac_len); + + // Prepare the first counter block for use in decryption. + ccm_prepare_first_ctr_blk(counter, nonce, nonce_len, plaintext_len_store_size); + + // Decrypt the Payload with CTR mode with a counter starting at 1. + std::memcpy(temp_iv, counter, AES_BLOCK_SIZE); + aes_increment_iv( + temp_iv, + AES_BLOCK_SIZE - 1 - + mac_len); // (AES_BLOCK_SIZE - 1 - mac_len) is the byte size of the counting portion of the counter block. + aes_decrypt_ctr(plaintext, *plaintext_len, plaintext, key, keysize, temp_iv); + + // Setting mac_auth to NULL disables the authentication check. + if (mac_auth != NULL) + { + // Decrypt the MAC with CTR mode with a counter starting at 0. + aes_decrypt_ctr(mac, mac_len, mac, key, keysize, counter); + + // Format the first block of the formatted data. + plaintext_len_store_size = AES_BLOCK_SIZE - 1 - nonce_len; + ccm_prepare_first_format_blk(buf, assoc_len, *plaintext_len, plaintext_len_store_size, mac_len, nonce, nonce_len); + end_of_buf = AES_BLOCK_SIZE; + + // Format the Associated Data into the authentication buffer. + ccm_format_assoc_data(buf, &end_of_buf, assoc, assoc_len); + + // Format the Payload into the authentication buffer. + ccm_format_payload_data(buf, &end_of_buf, plaintext, *plaintext_len); + + // Perform the CBC operation with an IV of zeros on the formatted buffer to calculate the MAC. + std::memset(temp_iv, 0, AES_BLOCK_SIZE); + aes_encrypt_cbc_mac(buf, end_of_buf, mac_buf, key, keysize, temp_iv); + + // Compare the calculated MAC against the MAC embedded in the ciphertext to see if they are the same. + if (!memcmp(mac, mac_buf, mac_len)) + { + *mac_auth = true; + } + else + { + *mac_auth = false; + std::memset(plaintext, 0, *plaintext_len); + } + } + + free(buf); + + return true; +} + +// Creates the first counter block. First byte is flags, then the nonce, then the incremented part. +void ccm_prepare_first_ctr_blk(uint8_t counter[], const uint8_t nonce[], int nonce_len, int payload_len_store_size) +{ + std::memset(counter, 0, AES_BLOCK_SIZE); + counter[0] = (payload_len_store_size - 1) & 0x07; + std::memcpy(&counter[1], nonce, nonce_len); +} + +void ccm_prepare_first_format_blk(uint8_t buf[], int assoc_len, int payload_len, int payload_len_store_size, + int mac_len, const uint8_t nonce[], int nonce_len) +{ + // Set the flags for the first byte of the first block. + buf[0] = ((((mac_len - 2) / 2) & 0x07) << 3) | ((payload_len_store_size - 1) & 0x07); + if (assoc_len > 0) + buf[0] += 0x40; + // Format the rest of the first block, storing the nonce and the size of the payload. + std::memcpy(&buf[1], nonce, nonce_len); + std::memset(&buf[1 + nonce_len], 0, AES_BLOCK_SIZE - 1 - nonce_len); + buf[15] = payload_len & 0x000000FF; + buf[14] = (payload_len >> 8) & 0x000000FF; +} + +void ccm_format_assoc_data(uint8_t buf[], int* end_of_buf, const uint8_t assoc[], int assoc_len) +{ + int pad; + + buf[*end_of_buf + 1] = assoc_len & 0x00FF; + buf[*end_of_buf] = (assoc_len >> 8) & 0x00FF; + *end_of_buf += 2; + std::memcpy(&buf[*end_of_buf], assoc, assoc_len); + *end_of_buf += assoc_len; + pad = AES_BLOCK_SIZE - (*end_of_buf % AES_BLOCK_SIZE); /*BUG?*/ + std::memset(&buf[*end_of_buf], 0, pad); + *end_of_buf += pad; +} + +void ccm_format_payload_data(uint8_t buf[], int* end_of_buf, const uint8_t payload[], int payload_len) +{ + int pad; + + std::memcpy(&buf[*end_of_buf], payload, payload_len); + *end_of_buf += payload_len; + pad = *end_of_buf % AES_BLOCK_SIZE; + if (pad != 0) + pad = AES_BLOCK_SIZE - pad; + std::memset(&buf[*end_of_buf], 0, pad); + *end_of_buf += pad; +} +#endif + +/******************* + * AES + *******************/ +///////////////// +// KEY EXPANSION +///////////////// + +// Substitutes a word using the AES S-Box. +static uint32_t SubWord(uint32_t word) +{ + unsigned int result; + + result = (int)aes_sbox[(word >> 4) & 0x0000000F][word & 0x0000000F]; + result += (int)aes_sbox[(word >> 12) & 0x0000000F][(word >> 8) & 0x0000000F] << 8; + result += (int)aes_sbox[(word >> 20) & 0x0000000F][(word >> 16) & 0x0000000F] << 16; + result += (int)aes_sbox[(word >> 28) & 0x0000000F][(word >> 24) & 0x0000000F] << 24; + return (result); +} + +// Performs the action of generating the keys that will be used in every round of +// encryption. "key" is the user-supplied input key, "w" is the output key schedule, +// "keysize" is the length in bits of "key", must be 128, 192, or 256. +void aes_key_setup(const uint8_t key[], uint32_t w[], int keysize) +{ + int Nb = 4, Nr, Nk, idx; + uint32_t temp, + Rcon[] = {0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, 0x40000000, 0x80000000, + 0x1b000000, 0x36000000, 0x6c000000, 0xd8000000, 0xab000000, 0x4d000000, 0x9a000000}; + + switch (keysize) + { + case 128: + Nr = 10; + Nk = 4; + break; + case 192: + Nr = 12; + Nk = 6; + break; + case 256: + Nr = 14; + Nk = 8; + break; + default: + return; + } + + for (idx = 0; idx < Nk; ++idx) + { + w[idx] = ((key[4 * idx]) << 24) | ((key[4 * idx + 1]) << 16) | ((key[4 * idx + 2]) << 8) | ((key[4 * idx + 3])); + } + + for (idx = Nk; idx < Nb * (Nr + 1); ++idx) + { + temp = w[idx - 1]; + if ((idx % Nk) == 0) + temp = SubWord(KE_ROTWORD(temp)) ^ Rcon[(idx - 1) / Nk]; + else if (Nk > 6 && (idx % Nk) == 4) + temp = SubWord(temp); + w[idx] = w[idx - Nk] ^ temp; + } +} + +///////////////// +// ADD ROUND KEY +///////////////// + +// Performs the AddRoundKey step. Each round has its own pre-generated 16-byte key in the +// form of 4 integers (the "w" array). Each integer is XOR'd by one column of the state. +// Also performs the job of InvAddRoundKey(); since the function is a simple XOR process, +// it is its own inverse. +static void AddRoundKey(uint8_t state[][4], const uint32_t w[]) +{ + uint8_t subkey[4]; + + // std::memcpy(subkey,&w[idx],4); // Not accurate for big endian machines + // Subkey 1 + subkey[0] = static_cast(w[0] >> 24); + subkey[1] = static_cast(w[0] >> 16); + subkey[2] = static_cast(w[0] >> 8); + subkey[3] = static_cast(w[0]); + state[0][0] ^= subkey[0]; + state[1][0] ^= subkey[1]; + state[2][0] ^= subkey[2]; + state[3][0] ^= subkey[3]; + // Subkey 2 + subkey[0] = static_cast(w[1] >> 24); + subkey[1] = static_cast(w[1] >> 16); + subkey[2] = static_cast(w[1] >> 8); + subkey[3] = static_cast(w[1]); + state[0][1] ^= subkey[0]; + state[1][1] ^= subkey[1]; + state[2][1] ^= subkey[2]; + state[3][1] ^= subkey[3]; + // Subkey 3 + subkey[0] = static_cast(w[2] >> 24); + subkey[1] = static_cast(w[2] >> 16); + subkey[2] = static_cast(w[2] >> 8); + subkey[3] = static_cast(w[2]); + state[0][2] ^= subkey[0]; + state[1][2] ^= subkey[1]; + state[2][2] ^= subkey[2]; + state[3][2] ^= subkey[3]; + // Subkey 4 + subkey[0] = static_cast(w[3] >> 24); + subkey[1] = static_cast(w[3] >> 16); + subkey[2] = static_cast(w[3] >> 8); + subkey[3] = static_cast(w[3]); + state[0][3] ^= subkey[0]; + state[1][3] ^= subkey[1]; + state[2][3] ^= subkey[2]; + state[3][3] ^= subkey[3]; +} + +///////////////// +// (Inv)SubBytes +///////////////// + +// Performs the SubBytes step. All bytes in the state are substituted with a +// pre-calculated value from a lookup table. +static void SubBytes(uint8_t state[][4]) +{ + state[0][0] = aes_sbox[state[0][0] >> 4][state[0][0] & 0x0F]; + state[0][1] = aes_sbox[state[0][1] >> 4][state[0][1] & 0x0F]; + state[0][2] = aes_sbox[state[0][2] >> 4][state[0][2] & 0x0F]; + state[0][3] = aes_sbox[state[0][3] >> 4][state[0][3] & 0x0F]; + state[1][0] = aes_sbox[state[1][0] >> 4][state[1][0] & 0x0F]; + state[1][1] = aes_sbox[state[1][1] >> 4][state[1][1] & 0x0F]; + state[1][2] = aes_sbox[state[1][2] >> 4][state[1][2] & 0x0F]; + state[1][3] = aes_sbox[state[1][3] >> 4][state[1][3] & 0x0F]; + state[2][0] = aes_sbox[state[2][0] >> 4][state[2][0] & 0x0F]; + state[2][1] = aes_sbox[state[2][1] >> 4][state[2][1] & 0x0F]; + state[2][2] = aes_sbox[state[2][2] >> 4][state[2][2] & 0x0F]; + state[2][3] = aes_sbox[state[2][3] >> 4][state[2][3] & 0x0F]; + state[3][0] = aes_sbox[state[3][0] >> 4][state[3][0] & 0x0F]; + state[3][1] = aes_sbox[state[3][1] >> 4][state[3][1] & 0x0F]; + state[3][2] = aes_sbox[state[3][2] >> 4][state[3][2] & 0x0F]; + state[3][3] = aes_sbox[state[3][3] >> 4][state[3][3] & 0x0F]; +} + +static void InvSubBytes(uint8_t state[][4]) +{ + state[0][0] = aes_invsbox[state[0][0] >> 4][state[0][0] & 0x0F]; + state[0][1] = aes_invsbox[state[0][1] >> 4][state[0][1] & 0x0F]; + state[0][2] = aes_invsbox[state[0][2] >> 4][state[0][2] & 0x0F]; + state[0][3] = aes_invsbox[state[0][3] >> 4][state[0][3] & 0x0F]; + state[1][0] = aes_invsbox[state[1][0] >> 4][state[1][0] & 0x0F]; + state[1][1] = aes_invsbox[state[1][1] >> 4][state[1][1] & 0x0F]; + state[1][2] = aes_invsbox[state[1][2] >> 4][state[1][2] & 0x0F]; + state[1][3] = aes_invsbox[state[1][3] >> 4][state[1][3] & 0x0F]; + state[2][0] = aes_invsbox[state[2][0] >> 4][state[2][0] & 0x0F]; + state[2][1] = aes_invsbox[state[2][1] >> 4][state[2][1] & 0x0F]; + state[2][2] = aes_invsbox[state[2][2] >> 4][state[2][2] & 0x0F]; + state[2][3] = aes_invsbox[state[2][3] >> 4][state[2][3] & 0x0F]; + state[3][0] = aes_invsbox[state[3][0] >> 4][state[3][0] & 0x0F]; + state[3][1] = aes_invsbox[state[3][1] >> 4][state[3][1] & 0x0F]; + state[3][2] = aes_invsbox[state[3][2] >> 4][state[3][2] & 0x0F]; + state[3][3] = aes_invsbox[state[3][3] >> 4][state[3][3] & 0x0F]; +} + +///////////////// +// (Inv)ShiftRows +///////////////// + +// Performs the ShiftRows step. All rows are shifted cylindrically to the left. +static void ShiftRows(uint8_t state[][4]) +{ + int t; + + // Shift left by 1 + t = state[1][0]; + state[1][0] = state[1][1]; + state[1][1] = state[1][2]; + state[1][2] = state[1][3]; + state[1][3] = static_cast(t); + // Shift left by 2 + t = state[2][0]; + state[2][0] = state[2][2]; + state[2][2] = static_cast(t); + t = state[2][1]; + state[2][1] = state[2][3]; + state[2][3] = static_cast(t); + // Shift left by 3 + t = state[3][0]; + state[3][0] = state[3][3]; + state[3][3] = state[3][2]; + state[3][2] = state[3][1]; + state[3][1] = static_cast(t); +} + +// All rows are shifted cylindrically to the right. +static void InvShiftRows(uint8_t state[][4]) +{ + int t; + + // Shift right by 1 + t = state[1][3]; + state[1][3] = state[1][2]; + state[1][2] = state[1][1]; + state[1][1] = state[1][0]; + state[1][0] = static_cast(t); + // Shift right by 2 + t = state[2][3]; + state[2][3] = state[2][1]; + state[2][1] = static_cast(t); + t = state[2][2]; + state[2][2] = state[2][0]; + state[2][0] = static_cast(t); + // Shift right by 3 + t = state[3][3]; + state[3][3] = state[3][0]; + state[3][0] = state[3][1]; + state[3][1] = state[3][2]; + state[3][2] = static_cast(t); +} + +///////////////// +// (Inv)MixColumns +///////////////// + +// Performs the MixColums step. The state is multiplied by itself using matrix +// multiplication in a Galios Field 2^8. All multiplication is pre-computed in a table. +// Addition is equivilent to XOR. (Must always make a copy of the column as the original +// values will be destoyed.) +static void MixColumns(uint8_t state[][4]) +{ + uint8_t col[4]; + + // Column 1 + col[0] = state[0][0]; + col[1] = state[1][0]; + col[2] = state[2][0]; + col[3] = state[3][0]; + state[0][0] = gf_mul[col[0]][0]; + state[0][0] ^= gf_mul[col[1]][1]; + state[0][0] ^= col[2]; + state[0][0] ^= col[3]; + state[1][0] = col[0]; + state[1][0] ^= gf_mul[col[1]][0]; + state[1][0] ^= gf_mul[col[2]][1]; + state[1][0] ^= col[3]; + state[2][0] = col[0]; + state[2][0] ^= col[1]; + state[2][0] ^= gf_mul[col[2]][0]; + state[2][0] ^= gf_mul[col[3]][1]; + state[3][0] = gf_mul[col[0]][1]; + state[3][0] ^= col[1]; + state[3][0] ^= col[2]; + state[3][0] ^= gf_mul[col[3]][0]; + // Column 2 + col[0] = state[0][1]; + col[1] = state[1][1]; + col[2] = state[2][1]; + col[3] = state[3][1]; + state[0][1] = gf_mul[col[0]][0]; + state[0][1] ^= gf_mul[col[1]][1]; + state[0][1] ^= col[2]; + state[0][1] ^= col[3]; + state[1][1] = col[0]; + state[1][1] ^= gf_mul[col[1]][0]; + state[1][1] ^= gf_mul[col[2]][1]; + state[1][1] ^= col[3]; + state[2][1] = col[0]; + state[2][1] ^= col[1]; + state[2][1] ^= gf_mul[col[2]][0]; + state[2][1] ^= gf_mul[col[3]][1]; + state[3][1] = gf_mul[col[0]][1]; + state[3][1] ^= col[1]; + state[3][1] ^= col[2]; + state[3][1] ^= gf_mul[col[3]][0]; + // Column 3 + col[0] = state[0][2]; + col[1] = state[1][2]; + col[2] = state[2][2]; + col[3] = state[3][2]; + state[0][2] = gf_mul[col[0]][0]; + state[0][2] ^= gf_mul[col[1]][1]; + state[0][2] ^= col[2]; + state[0][2] ^= col[3]; + state[1][2] = col[0]; + state[1][2] ^= gf_mul[col[1]][0]; + state[1][2] ^= gf_mul[col[2]][1]; + state[1][2] ^= col[3]; + state[2][2] = col[0]; + state[2][2] ^= col[1]; + state[2][2] ^= gf_mul[col[2]][0]; + state[2][2] ^= gf_mul[col[3]][1]; + state[3][2] = gf_mul[col[0]][1]; + state[3][2] ^= col[1]; + state[3][2] ^= col[2]; + state[3][2] ^= gf_mul[col[3]][0]; + // Column 4 + col[0] = state[0][3]; + col[1] = state[1][3]; + col[2] = state[2][3]; + col[3] = state[3][3]; + state[0][3] = gf_mul[col[0]][0]; + state[0][3] ^= gf_mul[col[1]][1]; + state[0][3] ^= col[2]; + state[0][3] ^= col[3]; + state[1][3] = col[0]; + state[1][3] ^= gf_mul[col[1]][0]; + state[1][3] ^= gf_mul[col[2]][1]; + state[1][3] ^= col[3]; + state[2][3] = col[0]; + state[2][3] ^= col[1]; + state[2][3] ^= gf_mul[col[2]][0]; + state[2][3] ^= gf_mul[col[3]][1]; + state[3][3] = gf_mul[col[0]][1]; + state[3][3] ^= col[1]; + state[3][3] ^= col[2]; + state[3][3] ^= gf_mul[col[3]][0]; +} + +static void InvMixColumns(uint8_t state[][4]) +{ + uint8_t col[4]; + + // Column 1 + col[0] = state[0][0]; + col[1] = state[1][0]; + col[2] = state[2][0]; + col[3] = state[3][0]; + state[0][0] = gf_mul[col[0]][5]; + state[0][0] ^= gf_mul[col[1]][3]; + state[0][0] ^= gf_mul[col[2]][4]; + state[0][0] ^= gf_mul[col[3]][2]; + state[1][0] = gf_mul[col[0]][2]; + state[1][0] ^= gf_mul[col[1]][5]; + state[1][0] ^= gf_mul[col[2]][3]; + state[1][0] ^= gf_mul[col[3]][4]; + state[2][0] = gf_mul[col[0]][4]; + state[2][0] ^= gf_mul[col[1]][2]; + state[2][0] ^= gf_mul[col[2]][5]; + state[2][0] ^= gf_mul[col[3]][3]; + state[3][0] = gf_mul[col[0]][3]; + state[3][0] ^= gf_mul[col[1]][4]; + state[3][0] ^= gf_mul[col[2]][2]; + state[3][0] ^= gf_mul[col[3]][5]; + // Column 2 + col[0] = state[0][1]; + col[1] = state[1][1]; + col[2] = state[2][1]; + col[3] = state[3][1]; + state[0][1] = gf_mul[col[0]][5]; + state[0][1] ^= gf_mul[col[1]][3]; + state[0][1] ^= gf_mul[col[2]][4]; + state[0][1] ^= gf_mul[col[3]][2]; + state[1][1] = gf_mul[col[0]][2]; + state[1][1] ^= gf_mul[col[1]][5]; + state[1][1] ^= gf_mul[col[2]][3]; + state[1][1] ^= gf_mul[col[3]][4]; + state[2][1] = gf_mul[col[0]][4]; + state[2][1] ^= gf_mul[col[1]][2]; + state[2][1] ^= gf_mul[col[2]][5]; + state[2][1] ^= gf_mul[col[3]][3]; + state[3][1] = gf_mul[col[0]][3]; + state[3][1] ^= gf_mul[col[1]][4]; + state[3][1] ^= gf_mul[col[2]][2]; + state[3][1] ^= gf_mul[col[3]][5]; + // Column 3 + col[0] = state[0][2]; + col[1] = state[1][2]; + col[2] = state[2][2]; + col[3] = state[3][2]; + state[0][2] = gf_mul[col[0]][5]; + state[0][2] ^= gf_mul[col[1]][3]; + state[0][2] ^= gf_mul[col[2]][4]; + state[0][2] ^= gf_mul[col[3]][2]; + state[1][2] = gf_mul[col[0]][2]; + state[1][2] ^= gf_mul[col[1]][5]; + state[1][2] ^= gf_mul[col[2]][3]; + state[1][2] ^= gf_mul[col[3]][4]; + state[2][2] = gf_mul[col[0]][4]; + state[2][2] ^= gf_mul[col[1]][2]; + state[2][2] ^= gf_mul[col[2]][5]; + state[2][2] ^= gf_mul[col[3]][3]; + state[3][2] = gf_mul[col[0]][3]; + state[3][2] ^= gf_mul[col[1]][4]; + state[3][2] ^= gf_mul[col[2]][2]; + state[3][2] ^= gf_mul[col[3]][5]; + // Column 4 + col[0] = state[0][3]; + col[1] = state[1][3]; + col[2] = state[2][3]; + col[3] = state[3][3]; + state[0][3] = gf_mul[col[0]][5]; + state[0][3] ^= gf_mul[col[1]][3]; + state[0][3] ^= gf_mul[col[2]][4]; + state[0][3] ^= gf_mul[col[3]][2]; + state[1][3] = gf_mul[col[0]][2]; + state[1][3] ^= gf_mul[col[1]][5]; + state[1][3] ^= gf_mul[col[2]][3]; + state[1][3] ^= gf_mul[col[3]][4]; + state[2][3] = gf_mul[col[0]][4]; + state[2][3] ^= gf_mul[col[1]][2]; + state[2][3] ^= gf_mul[col[2]][5]; + state[2][3] ^= gf_mul[col[3]][3]; + state[3][3] = gf_mul[col[0]][3]; + state[3][3] ^= gf_mul[col[1]][4]; + state[3][3] ^= gf_mul[col[2]][2]; + state[3][3] ^= gf_mul[col[3]][5]; +} + +///////////////// +// (En/De)Crypt +///////////////// + +void aes_encrypt(const uint8_t in[], uint8_t out[], const uint32_t key[], int keysize) +{ + uint8_t state[4][4]; + + // Copy input array (should be 16 bytes long) to a matrix (sequential bytes are ordered + // by row, not col) called "state" for processing. + // *** Implementation note: The official AES documentation references the state by + // column, then row. Accessing an element in C requires row then column. Thus, all state + // references in AES must have the column and row indexes reversed for C implementation. + state[0][0] = in[0]; + state[1][0] = in[1]; + state[2][0] = in[2]; + state[3][0] = in[3]; + state[0][1] = in[4]; + state[1][1] = in[5]; + state[2][1] = in[6]; + state[3][1] = in[7]; + state[0][2] = in[8]; + state[1][2] = in[9]; + state[2][2] = in[10]; + state[3][2] = in[11]; + state[0][3] = in[12]; + state[1][3] = in[13]; + state[2][3] = in[14]; + state[3][3] = in[15]; + + // Perform the necessary number of rounds. The round key is added first. + // The last round does not perform the MixColumns step. + AddRoundKey(state, &key[0]); + SubBytes(state); + ShiftRows(state); + MixColumns(state); + AddRoundKey(state, &key[4]); + SubBytes(state); + ShiftRows(state); + MixColumns(state); + AddRoundKey(state, &key[8]); + SubBytes(state); + ShiftRows(state); + MixColumns(state); + AddRoundKey(state, &key[12]); + SubBytes(state); + ShiftRows(state); + MixColumns(state); + AddRoundKey(state, &key[16]); + SubBytes(state); + ShiftRows(state); + MixColumns(state); + AddRoundKey(state, &key[20]); + SubBytes(state); + ShiftRows(state); + MixColumns(state); + AddRoundKey(state, &key[24]); + SubBytes(state); + ShiftRows(state); + MixColumns(state); + AddRoundKey(state, &key[28]); + SubBytes(state); + ShiftRows(state); + MixColumns(state); + AddRoundKey(state, &key[32]); + SubBytes(state); + ShiftRows(state); + MixColumns(state); + AddRoundKey(state, &key[36]); + if (keysize != 128) + { + SubBytes(state); + ShiftRows(state); + MixColumns(state); + AddRoundKey(state, &key[40]); + SubBytes(state); + ShiftRows(state); + MixColumns(state); + AddRoundKey(state, &key[44]); + if (keysize != 192) + { + SubBytes(state); + ShiftRows(state); + MixColumns(state); + AddRoundKey(state, &key[48]); + SubBytes(state); + ShiftRows(state); + MixColumns(state); + AddRoundKey(state, &key[52]); + SubBytes(state); + ShiftRows(state); + AddRoundKey(state, &key[56]); + } + else + { + SubBytes(state); + ShiftRows(state); + AddRoundKey(state, &key[48]); + } + } + else + { + SubBytes(state); + ShiftRows(state); + AddRoundKey(state, &key[40]); + } + + // Copy the state to the output array. + out[0] = state[0][0]; + out[1] = state[1][0]; + out[2] = state[2][0]; + out[3] = state[3][0]; + out[4] = state[0][1]; + out[5] = state[1][1]; + out[6] = state[2][1]; + out[7] = state[3][1]; + out[8] = state[0][2]; + out[9] = state[1][2]; + out[10] = state[2][2]; + out[11] = state[3][2]; + out[12] = state[0][3]; + out[13] = state[1][3]; + out[14] = state[2][3]; + out[15] = state[3][3]; +} + +void aes_decrypt(const uint8_t in[], uint8_t out[], const uint32_t key[], int keysize) +{ + uint8_t state[4][4]; + + // Copy the input to the state. + state[0][0] = in[0]; + state[1][0] = in[1]; + state[2][0] = in[2]; + state[3][0] = in[3]; + state[0][1] = in[4]; + state[1][1] = in[5]; + state[2][1] = in[6]; + state[3][1] = in[7]; + state[0][2] = in[8]; + state[1][2] = in[9]; + state[2][2] = in[10]; + state[3][2] = in[11]; + state[0][3] = in[12]; + state[1][3] = in[13]; + state[2][3] = in[14]; + state[3][3] = in[15]; + + // Perform the necessary number of rounds. The round key is added first. + // The last round does not perform the MixColumns step. + if (keysize > 128) + { + if (keysize > 192) + { + AddRoundKey(state, &key[56]); + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(state, &key[52]); + InvMixColumns(state); + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(state, &key[48]); + InvMixColumns(state); + } + else + { + AddRoundKey(state, &key[48]); + } + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(state, &key[44]); + InvMixColumns(state); + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(state, &key[40]); + InvMixColumns(state); + } + else + { + AddRoundKey(state, &key[40]); + } + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(state, &key[36]); + InvMixColumns(state); + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(state, &key[32]); + InvMixColumns(state); + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(state, &key[28]); + InvMixColumns(state); + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(state, &key[24]); + InvMixColumns(state); + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(state, &key[20]); + InvMixColumns(state); + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(state, &key[16]); + InvMixColumns(state); + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(state, &key[12]); + InvMixColumns(state); + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(state, &key[8]); + InvMixColumns(state); + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(state, &key[4]); + InvMixColumns(state); + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(state, &key[0]); + + // Copy the state to the output array. + out[0] = state[0][0]; + out[1] = state[1][0]; + out[2] = state[2][0]; + out[3] = state[3][0]; + out[4] = state[0][1]; + out[5] = state[1][1]; + out[6] = state[2][1]; + out[7] = state[3][1]; + out[8] = state[0][2]; + out[9] = state[1][2]; + out[10] = state[2][2]; + out[11] = state[3][2]; + out[12] = state[0][3]; + out[13] = state[1][3]; + out[14] = state[2][3]; + out[15] = state[3][3]; +} + +/******************* +** AES DEBUGGING FUNCTIONS +*******************/ +/* +// This prints the "state" grid as a linear hex string. +void print_state(BYTE state[][4]) +{ + int idx,idx2; + + for (idx=0; idx < 4; idx++) + for (idx2=0; idx2 < 4; idx2++) + printf("%02x",state[idx2][idx]); + printf("\n"); +} + +// This prints the key (4 consecutive ints) used for a given round as a linear hex string. +void print_rnd_key(WORD key[]) +{ + int idx; + + for (idx=0; idx < 4; idx++) + printf("%08x",key[idx]); + printf("\n"); +} +*/ diff --git a/src/common/thirdparty/aes.h b/src/common/thirdparty/aes.h new file mode 100644 index 000000000..ab24e0aee --- /dev/null +++ b/src/common/thirdparty/aes.h @@ -0,0 +1,122 @@ +/********************************************************************* + * Filename: aes.h + * Author: Brad Conte (brad AT bradconte.com) + * Copyright: + * Disclaimer: This code is presented "as is" without any guarantees. + * Details: Defines the API for the corresponding AES implementation. + *********************************************************************/ + +#pragma once + +/*************************** HEADER FILES ***************************/ +#include +#include + +/****************************** MACROS ******************************/ +static constexpr uint32_t AES_BLOCK_SIZE = 16; // AES operates on 16 bytes at a time +static constexpr uint32_t AES_KEY_SCHEDULE_SIZE = 60; + +/*********************** FUNCTION DECLARATIONS **********************/ +/////////////////// +// AES +/////////////////// +// Key setup must be done before any AES en/de-cryption functions can be used. +void aes_key_setup(const uint8_t key[], // The key, must be 128, 192, or 256 bits + uint32_t w[], // Output key schedule to be used later + int keysize); // Bit length of the key, 128, 192, or 256 + +void aes_encrypt(const uint8_t in[], // 16 bytes of plaintext + uint8_t out[], // 16 bytes of ciphertext + const uint32_t key[], // From the key setup + int keysize); // Bit length of the key, 128, 192, or 256 + +void aes_decrypt(const uint8_t in[], // 16 bytes of ciphertext + uint8_t out[], // 16 bytes of plaintext + const uint32_t key[], // From the key setup + int keysize); // Bit length of the key, 128, 192, or 256 + +/////////////////// +// AES - CBC +/////////////////// +bool aes_encrypt_cbc(const uint8_t in[], // Plaintext + size_t in_len, // Must be a multiple of AES_BLOCK_SIZE + uint8_t out[], // Ciphertext, same length as plaintext + const uint32_t key[], // From the key setup + int keysize, // Bit length of the key, 128, 192, or 256 + const uint8_t iv[]); // IV, must be AES_BLOCK_SIZE bytes long +bool aes_decrypt_cbc(const uint8_t in[], // Ciphertext + size_t in_len, // Must be a multiple of AES_BLOCK_SIZE + uint8_t out[], // Plaintext, same length as ciphertext + const uint32_t key[], // From the key setup + int keysize, // Bit length of the key, 128, 192, or 256 + const uint8_t iv[]); // IV, must be AES_BLOCK_SIZE bytes long + +#if 0 +// Disabled since it's not being used. +// Only output the CBC-MAC of the input. +bool aes_encrypt_cbc_mac(const uint8_t in[], // plaintext + size_t in_len, // Must be a multiple of AES_BLOCK_SIZE + uint8_t out[], // Output MAC + const uint32_t key[], // From the key setup + int keysize, // Bit length of the key, 128, 192, or 256 + const uint8_t iv[]); // IV, must be AES_BLOCK_SIZE bytes long + +/////////////////// +// AES - CTR +/////////////////// +void aes_increment_iv(uint8_t iv[], // Must be a multiple of AES_BLOCK_SIZE + int counter_size); // Bytes of the IV used for counting (low end) + +void aes_encrypt_ctr(const uint8_t in[], // Plaintext + size_t in_len, // Any byte length + uint8_t out[], // Ciphertext, same length as plaintext + const uint32_t key[], // From the key setup + int keysize, // Bit length of the key, 128, 192, or 256 + const uint8_t iv[]); // IV, must be AES_BLOCK_SIZE bytes long + +void aes_decrypt_ctr(const uint8_t in[], // Ciphertext + size_t in_len, // Any byte length + uint8_t out[], // Plaintext, same length as ciphertext + const uint32_t key[], // From the key setup + int keysize, // Bit length of the key, 128, 192, or 256 + const uint8_t iv[]); // IV, must be AES_BLOCK_SIZE bytes long + +/////////////////// +// AES - CCM +/////////////////// +// Returns True if the input parameters do not violate any constraint. +bool aes_encrypt_ccm( + const uint8_t plaintext[], // IN - Plaintext. + uint32_t plaintext_len, // IN - Plaintext length. + const uint8_t associated_data[], // IN - Associated Data included in authentication, but not encryption. + unsigned short associated_data_len, // IN - Associated Data length in bytes. + const uint8_t nonce[], // IN - The Nonce to be used for encryption. + unsigned short nonce_len, // IN - Nonce length in bytes. + uint8_t ciphertext[], // OUT - Ciphertext, a concatination of the plaintext and the MAC. + uint32_t* ciphertext_len, // OUT - The length of the ciphertext, always plaintext_len + mac_len. + uint32_t mac_len, // IN - The desired length of the MAC, must be 4, 6, 8, 10, 12, 14, or 16. + const uint8_t key[], // IN - The AES key for encryption. + int keysize); // IN - The length of the key in bits. Valid values are 128, 192, 256. + +// Returns True if the input parameters do not violate any constraint. +// Use mac_auth to ensure decryption/validation was preformed correctly. +// If authentication does not succeed, the plaintext is zeroed out. To overwride +// this, call with mac_auth = NULL. The proper proceedure is to decrypt with +// authentication enabled (mac_auth != NULL) and make a second call to that +// ignores authentication explicitly if the first call failes. +bool aes_decrypt_ccm( + const uint8_t ciphertext[], // IN - Ciphertext, the concatination of encrypted plaintext and MAC. + uint32_t ciphertext_len, // IN - Ciphertext length in bytes. + const uint8_t assoc[], // IN - The Associated Data, required for authentication. + unsigned short assoc_len, // IN - Associated Data length in bytes. + const uint8_t nonce[], // IN - The Nonce to use for decryption, same one as for encryption. + unsigned short nonce_len, // IN - Nonce length in bytes. + uint8_t plaintext[], // OUT - The plaintext that was decrypted. Will need to be large enough to hold ciphertext_len - + // mac_len. + uint32_t* plaintext_len, // OUT - Length in bytes of the output plaintext, always ciphertext_len - mac_len . + uint32_t mac_len, // IN - The length of the MAC that was calculated. + int* mac_auth, // OUT - TRUE if authentication succeeded, FALSE if it did not. NULL pointer will ignore the + // authentication. + const uint8_t key[], // IN - The AES key for decryption. + int keysize); // IN - The length of the key in BITS. Valid values are 128, 192, 256. +#endif \ No newline at end of file From ff3214b8f7ac4dda3568e912400e3cca369be179 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 17:06:20 +1000 Subject: [PATCH 07/69] SmallString: Add span helpers --- src/common/small_string.cpp | 20 ++++++++++++++++++++ src/common/small_string.h | 9 +++++++++ 2 files changed, 29 insertions(+) diff --git a/src/common/small_string.cpp b/src/common/small_string.cpp index d1d8b4537..f539e1f8a 100644 --- a/src/common/small_string.cpp +++ b/src/common/small_string.cpp @@ -506,6 +506,26 @@ std::wstring SmallStringBase::wstring() const #endif +std::span SmallStringBase::cspan() const +{ + return std::span(m_buffer, m_length); +} + +std::span SmallStringBase::span() +{ + return std::span(m_buffer, m_length); +} + +std::span SmallStringBase::cbspan() const +{ + return std::span(reinterpret_cast(m_buffer), m_length); +} + +std::span SmallStringBase::bspan() +{ + return std::span(reinterpret_cast(m_buffer), m_length); +} + void SmallStringBase::vformat(fmt::string_view fmt, fmt::format_args args) { clear(); diff --git a/src/common/small_string.h b/src/common/small_string.h index 67fea7a18..a631bd393 100644 --- a/src/common/small_string.h +++ b/src/common/small_string.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -207,10 +208,18 @@ public: std::wstring wstring() const; #endif + // span creators + std::span cspan() const; + std::span span(); + std::span cbspan() const; + std::span bspan(); + // accessor operators ALWAYS_INLINE operator const char*() const { return c_str(); } ALWAYS_INLINE operator char*() { return data(); } ALWAYS_INLINE operator std::string_view() const { return view(); } + ALWAYS_INLINE operator std::span() const { return cspan(); } + ALWAYS_INLINE operator std::span() { return span(); } // comparative operators ALWAYS_INLINE bool operator==(const char* str) const { return equals(str); } From 5401dc8d52572b11b20245cc210e2350839a4f2e Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 17:06:40 +1000 Subject: [PATCH 08/69] Settings: Add EmuFolders::IsRunningInPortableMode() --- src/core/settings.cpp | 5 +++++ src/core/settings.h | 3 +++ 2 files changed, 8 insertions(+) diff --git a/src/core/settings.cpp b/src/core/settings.cpp index c089466e3..f3fe7078b 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -2335,3 +2335,8 @@ std::string EmuFolders::GetOverridableResourcePath(std::string_view name) return upath; } + +bool EmuFolders::IsRunningInPortableMode() +{ + return (AppRoot == DataRoot); +} diff --git a/src/core/settings.h b/src/core/settings.h index 70f054cf0..1a1b25db8 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -593,4 +593,7 @@ void Update(); /// Returns the path to a resource file, allowing the user to override it. std::string GetOverridableResourcePath(std::string_view name); + +/// Returns true if the application is running in portable mode. +bool IsRunningInPortableMode(); } // namespace EmuFolders From 9970944da21068cad87702f41a3c815df9fb99eb Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 17:07:09 +1000 Subject: [PATCH 09/69] Achievements: Add encryption of login tokens in ini Super simple key derived from the machine's UUID. The idea isn't to provide a ton of security, but prevent users from accidentially exposing their tokens when sharing their ini for debugging purposes. The use of the machine UUID is disabled in portable mode for those who actually move it between computers. Instead, the key is derived from the username alone, which is trivially computable. --- src/core/achievements.cpp | 178 +++++++++++++++++++++++++++++++++++++- 1 file changed, 175 insertions(+), 3 deletions(-) diff --git a/src/core/achievements.cpp b/src/core/achievements.cpp index 54f560701..48d80212a 100644 --- a/src/core/achievements.cpp +++ b/src/core/achievements.cpp @@ -22,6 +22,7 @@ #include "common/md5_digest.h" #include "common/path.h" #include "common/scoped_guard.h" +#include "common/sha256_digest.h" #include "common/small_string.h" #include "common/string_util.h" #include "common/timer.h" @@ -144,6 +145,9 @@ static std::string GetLocalImagePath(const std::string_view image_name, int type static void DownloadImage(std::string url, std::string cache_filename); static void UpdateGlyphRanges(); +static TinyString DecryptLoginToken(std::string_view encrypted_token, std::string_view username); +static TinyString EncryptLoginToken(std::string_view token, std::string_view username); + static bool CreateClient(rc_client_t** client, std::unique_ptr* http); static void DestroyClient(rc_client_t** client, std::unique_ptr* http); static void ClientMessageCallback(const char* message, const rc_client_t* client); @@ -569,8 +573,18 @@ bool Achievements::Initialize() if (!username.empty() && !api_token.empty()) { INFO_LOG("Attempting login with user '{}'...", username); - s_login_request = rc_client_begin_login_with_token(s_client, username.c_str(), api_token.c_str(), - ClientLoginWithTokenCallback, nullptr); + + // If we can't decrypt the token, it was an old config and we need to re-login. + if (const TinyString decrypted_api_token = DecryptLoginToken(api_token, username); !decrypted_api_token.empty()) + { + s_login_request = rc_client_begin_login_with_token(s_client, username.c_str(), decrypted_api_token.c_str(), + ClientLoginWithTokenCallback, nullptr); + } + else + { + WARNING_LOG("Invalid encrypted login token, requesitng a new one."); + Host::OnAchievementsLoginRequested(LoginRequestReason::TokenInvalid); + } } // Hardcore mode isn't enabled when achievements first starts, if a game is already running. @@ -1884,7 +1898,7 @@ void Achievements::ClientLoginWithPasswordCallback(int result, const char* error // Store configuration. Host::SetBaseStringSettingValue("Cheevos", "Username", params->username); - Host::SetBaseStringSettingValue("Cheevos", "Token", user->token); + Host::SetBaseStringSettingValue("Cheevos", "Token", EncryptLoginToken(user->token, params->username)); Host::SetBaseStringSettingValue("Cheevos", "LoginTimestamp", fmt::format("{}", std::time(nullptr)).c_str()); Host::CommitBaseSettingChanges(); @@ -3335,6 +3349,164 @@ void Achievements::CloseLeaderboard() ImGuiFullscreen::QueueResetFocus(ImGuiFullscreen::FocusResetType::Other); } +#if defined(_WIN32) +#include "common/windows_headers.h" +#elif !defined(__ANDROID__) +#include +#endif + +#include "common/thirdparty/SmallVector.h" +#include "common/thirdparty/aes.h" + +#ifndef __ANDROID__ + +static TinyString GetLoginEncryptionMachineKey() +{ + TinyString ret; + +#ifdef _WIN32 + + HKEY hKey; + DWORD error; + if ((error = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Cryptography", 0, KEY_READ, &hKey)) != + ERROR_SUCCESS) + { + WARNING_LOG("Open SOFTWARE\\Microsoft\\Cryptography failed for machine key failed: {}", error); + return ret; + } + + DWORD machine_guid_length; + if ((error = RegGetValueA(hKey, NULL, "MachineGuid", RRF_RT_REG_SZ, NULL, NULL, &machine_guid_length)) != + ERROR_SUCCESS) + { + WARNING_LOG("Get MachineGuid failed: {}", error); + RegCloseKey(hKey); + return 0; + } + + ret.resize(machine_guid_length); + if ((error = RegGetValueA(hKey, NULL, "MachineGuid", RRF_RT_REG_SZ, NULL, ret.data(), &machine_guid_length)) != + ERROR_SUCCESS || + machine_guid_length <= 1) + { + WARNING_LOG("Read MachineGuid failed: {}", error); + ret = {}; + RegCloseKey(hKey); + return 0; + } + + ret.resize(machine_guid_length); + RegCloseKey(hKey); +#elif !defined(__ANDROID__) +#ifdef __linux__ + // use /etc/machine-id on Linux + std::optional machine_id = FileSystem::ReadFileToString("/etc/machine-id"); + if (machine_id.has_value()) + ret = std::string_view(machine_id.value()); +#endif + + if (ret.empty()) + { + WARNING_LOG("Falling back to gethostid()"); + + // fallback to POSIX gethostid() + const long hostid = gethostid(); + ret.format("{:08X}", hostid); + } +#endif + + return ret; +} + +#endif + +static std::array GetLoginEncryptionKey(std::string_view username) +{ + // super basic key stretching + static constexpr u32 EXTRA_ROUNDS = 100; + + SHA256Digest digest; + +#ifndef __ANDROID__ + // Only use machine key if we're not running in portable mode. + if (!EmuFolders::IsRunningInPortableMode()) + { + const TinyString machine_key = GetLoginEncryptionMachineKey(); + if (!machine_key.empty()) + digest.Update(machine_key.cbspan()); + else + WARNING_LOG("Failed to get machine key, token will be decipherable."); + } +#endif + + // salt with username + digest.Update(username.data(), username.length()); + + std::array key = digest.Final(); + + for (u32 i = 0; i < EXTRA_ROUNDS; i++) + key = SHA256Digest::GetDigest(key); + + return key; +} + +TinyString Achievements::EncryptLoginToken(std::string_view token, std::string_view username) +{ + TinyString ret; + if (token.empty() || username.empty()) + return ret; + + const auto key = GetLoginEncryptionKey(username); + std::array key_schedule; + aes_key_setup(&key[0], key_schedule.data(), 128); + + // has to be padded to the block size + llvm::SmallVector data(reinterpret_cast(token.data()), + reinterpret_cast(token.data() + token.length())); + data.resize(Common::AlignUpPow2(token.length(), AES_BLOCK_SIZE), 0); + aes_encrypt_cbc(data.data(), data.size(), data.data(), key_schedule.data(), 128, &key[16]); + + // base64 encode it + const std::span data_span(data.data(), data.size()); + ret.resize(static_cast(StringUtil::EncodedBase64Length(data_span))); + StringUtil::EncodeBase64(ret.span(), data_span); + return ret; +} + +TinyString Achievements::DecryptLoginToken(std::string_view encrypted_token, std::string_view username) +{ + TinyString ret; + if (encrypted_token.empty() || username.empty()) + return ret; + + const size_t encrypted_data_length = StringUtil::DecodedBase64Length(encrypted_token); + if (encrypted_data_length == 0 || (encrypted_data_length % AES_BLOCK_SIZE) != 0) + return ret; + + const auto key = GetLoginEncryptionKey(username); + std::array key_schedule; + aes_key_setup(&key[0], key_schedule.data(), 128); + + // has to be padded to the block size + llvm::SmallVector encrypted_data; + encrypted_data.resize(encrypted_data_length); + if (StringUtil::DecodeBase64(std::span(encrypted_data.data(), encrypted_data.size()), encrypted_token) != + encrypted_data_length) + { + WARNING_LOG("Failed to base64 decode encrypted login token."); + return ret; + } + + aes_decrypt_cbc(encrypted_data.data(), encrypted_data.size(), encrypted_data.data(), key_schedule.data(), 128, + &key[16]); + + // remove any trailing null bytes + const size_t real_length = + StringUtil::Strnlen(reinterpret_cast(encrypted_data.data()), encrypted_data_length); + ret.append(reinterpret_cast(encrypted_data.data()), static_cast(real_length)); + return ret; +} + #ifdef ENABLE_RAINTEGRATION #include "RA_Consoles.h" From 6cbfab6eca94af1fbec7f5e51e66fd473258005e Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 17:10:57 +1000 Subject: [PATCH 10/69] MetalDevice: Warning fix --- src/util/metal_device.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/src/util/metal_device.mm b/src/util/metal_device.mm index 14584bbea..afbc19776 100644 --- a/src/util/metal_device.mm +++ b/src/util/metal_device.mm @@ -2514,7 +2514,6 @@ void MetalDevice::Dispatch(u32 threads_x, u32 threads_y, u32 threads_z, u32 grou } DebugAssert(m_current_pipeline && m_current_pipeline->IsComputePipeline()); - id pipeline = m_current_pipeline->GetComputePipelineState(); // TODO: We could remap to the optimal group size.. [m_compute_encoder dispatchThreads:MTLSizeMake(threads_x, threads_y, threads_z) From 19eee76aeccea0fe2bdddfee9f38bb141bbef47d Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 17:30:53 +1000 Subject: [PATCH 11/69] AnalogController: Reduce log spam --- src/core/analog_controller.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/analog_controller.cpp b/src/core/analog_controller.cpp index a290fa4eb..14e5e44a0 100644 --- a/src/core/analog_controller.cpp +++ b/src/core/analog_controller.cpp @@ -621,7 +621,7 @@ bool AnalogController::Transfer(const u8 data_in, u8* data_out) m_status_byte = 0x5A; } - DEV_LOG("0x{:02x}({}) config mode", m_rx_buffer[2], m_configuration_mode ? "enter" : "leave"); + DEBUG_LOG("0x{:02x}({}) config mode", m_rx_buffer[2], m_configuration_mode ? "enter" : "leave"); } } break; @@ -706,9 +706,9 @@ bool AnalogController::Transfer(const u8 data_in, u8* data_out) m_rumble_config[index] = data_in; if (data_in == LargeMotor) - WARNING_LOG("Large mapped to byte index {}", index); + DEBUG_LOG("Large motor mapped to byte index {}", index); else if (data_in == SmallMotor) - WARNING_LOG("Small mapped to byte index {}", index); + DEBUG_LOG("Small motor mapped to byte index {}", index); } else if (m_command_step == 7) { From d5b9b54a6989235e75253312fc16515f7267a235 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 18:05:49 +1000 Subject: [PATCH 12/69] GPU/SW: Vectorize VRAM writes/copies --- src/core/gpu_sw_rasterizer.inl | 86 ++++++++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 8 deletions(-) diff --git a/src/core/gpu_sw_rasterizer.inl b/src/core/gpu_sw_rasterizer.inl index 3343862a9..803dbfdef 100644 --- a/src/core/gpu_sw_rasterizer.inl +++ b/src/core/gpu_sw_rasterizer.inl @@ -1639,8 +1639,6 @@ static void FillVRAMImpl(u32 x, u32 y, u32 width, u32 height, u32 color, bool in static void WriteVRAMImpl(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask) { - // TODO: Vector implementation - // Fast path when the copy is not oversized. if ((x + width) <= VRAM_WIDTH && (y + height) <= VRAM_HEIGHT && !set_mask && !check_mask) { @@ -1661,10 +1659,49 @@ static void WriteVRAMImpl(u32 x, u32 y, u32 width, u32 height, const void* data, const u16 mask_and = check_mask ? 0x8000u : 0x0000u; const u16 mask_or = set_mask ? 0x8000u : 0x0000u; +#ifdef USE_VECTOR + constexpr u32 write_pixels_per_vec = sizeof(GSVectorNi) / sizeof(u16); + const u32 aligned_width = Common::AlignDownPow2(std::min(width, VRAM_WIDTH - x), write_pixels_per_vec); + const GSVectorNi mask_or_vec = GSVectorNi::cxpr16(mask_or); + const GSVectorNi mask_and_vec = GSVectorNi::cxpr16(mask_and); +#endif + for (u32 row = 0; row < height;) { u16* dst_row_ptr = &g_vram[((y + row++) % VRAM_HEIGHT) * VRAM_WIDTH]; - for (u32 col = 0; col < width;) + + u32 col = 0; + +#ifdef USE_VECTOR + // This doesn't do wraparound. + if (mask_and != 0) + { + for (; col < aligned_width; col += write_pixels_per_vec) + { + const GSVectorNi src = GSVectorNi::load(src_ptr); + src_ptr += write_pixels_per_vec; + + GSVectorNi dst = GSVectorNi::load(&dst_row_ptr[x + col]); + + const GSVectorNi mask = (dst & mask_and_vec).sra16<15>(); + dst = (dst & mask) | src.andnot(mask) | mask_or_vec; + + GSVectorNi::store(&dst_row_ptr[x + col], dst); + } + } + else + { + for (; col < aligned_width; col += write_pixels_per_vec) + { + const GSVectorNi src = GSVectorNi::load(src_ptr); + src_ptr += write_pixels_per_vec; + + GSVectorNi::store(&dst_row_ptr[x + col], src | mask_or_vec); + } + } +#endif + + for (; col < width;) { // TODO: Handle unaligned reads... u16* pixel_ptr = &dst_row_ptr[(x + col++) % VRAM_WIDTH]; @@ -1678,8 +1715,6 @@ static void WriteVRAMImpl(u32 x, u32 y, u32 width, u32 height, const void* data, static void CopyVRAMImpl(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height, bool set_mask, bool check_mask) { - // TODO: Vector implementation. - // Break up oversized copies. This behavior has not been verified on console. if ((src_x + width) > VRAM_WIDTH || (dst_x + width) > VRAM_WIDTH) { @@ -1698,8 +1733,8 @@ static void CopyVRAMImpl(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, { const u32 columns_to_copy = std::min(remaining_columns, std::min(VRAM_WIDTH - current_src_x, VRAM_WIDTH - current_dst_x)); - CopyVRAM(current_src_x, current_src_y, current_dst_x, current_dst_y, columns_to_copy, rows_to_copy, set_mask, - check_mask); + CopyVRAMImpl(current_src_x, current_src_y, current_dst_x, current_dst_y, columns_to_copy, rows_to_copy, + set_mask, check_mask); current_src_x = (current_src_x + columns_to_copy) % VRAM_WIDTH; current_dst_x = (current_dst_x + columns_to_copy) % VRAM_WIDTH; remaining_columns -= columns_to_copy; @@ -1735,12 +1770,47 @@ static void CopyVRAMImpl(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, } else { +#ifdef USE_VECTOR + constexpr u32 copy_pixels_per_vec = sizeof(GSVectorNi) / sizeof(u16); + const u32 aligned_width = Common::AlignDownPow2( + std::min(width, std::min(VRAM_WIDTH - src_x, VRAM_WIDTH - dst_x)), copy_pixels_per_vec); + const GSVectorNi mask_or_vec = GSVectorNi::cxpr16(mask_or); + const GSVectorNi mask_and_vec = GSVectorNi::cxpr16(mask_and); +#endif + for (u32 row = 0; row < height; row++) { const u16* src_row_ptr = &g_vram[((src_y + row) % VRAM_HEIGHT) * VRAM_WIDTH]; u16* dst_row_ptr = &g_vram[((dst_y + row) % VRAM_HEIGHT) * VRAM_WIDTH]; - for (u32 col = 0; col < width; col++) + u32 col = 0; + +#ifdef USE_VECTOR + // This doesn't do wraparound. + if (mask_and != 0) + { + for (; col < aligned_width; col += copy_pixels_per_vec) + { + const GSVectorNi src = GSVectorNi::load(&src_row_ptr[src_x + col]); + GSVectorNi dst = GSVectorNi::load(&dst_row_ptr[dst_x + col]); + + const GSVectorNi mask = (dst & mask_and_vec).sra16<15>(); + dst = (dst & mask) | src.andnot(mask) | mask_or_vec; + + GSVectorNi::store(&dst_row_ptr[dst_x + col], dst); + } + } + else + { + for (; col < aligned_width; col += copy_pixels_per_vec) + { + const GSVectorNi src = GSVectorNi::load(&src_row_ptr[src_x + col]); + GSVectorNi::store(&dst_row_ptr[dst_x + col], src | mask_or_vec); + } + } +#endif + + for (; col < width; col++) { const u16 src_pixel = src_row_ptr[(src_x + col) % VRAM_WIDTH]; u16* dst_pixel_ptr = &dst_row_ptr[(dst_x + col) % VRAM_WIDTH]; From 682ba7131932ad20d71b9cd3d975f13bb5b6c48d Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 18:36:32 +1000 Subject: [PATCH 13/69] GPU: Fix aspect ratio with Show VRAM enabled --- src/core/gpu.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index c0748bb6e..7b43faa56 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -629,7 +629,9 @@ float GPU::ComputeDisplayAspectRatio() const float GPU::ComputeSourceAspectRatio() const { const float source_aspect_ratio = - static_cast(m_crtc_state.display_width) / static_cast(m_crtc_state.display_height); + (g_settings.debugging.show_vram ? + (static_cast(VRAM_WIDTH) / static_cast(VRAM_HEIGHT)) : + static_cast(m_crtc_state.display_width) / static_cast(m_crtc_state.display_height)); // Correction is applied to the GTE for stretch to fit, that way it fills the window. const float source_aspect_ratio_correction = @@ -643,7 +645,8 @@ float GPU::ComputeAspectRatioCorrection() const const CRTCState& cs = m_crtc_state; float relative_width = static_cast(cs.horizontal_visible_end - cs.horizontal_visible_start); float relative_height = static_cast(cs.vertical_visible_end - cs.vertical_visible_start); - if (relative_width <= 0 || relative_height <= 0 || g_settings.display_aspect_ratio == DisplayAspectRatio::PAR1_1 || + if (relative_width <= 0 || relative_height <= 0 || g_settings.debugging.show_vram || + g_settings.display_aspect_ratio == DisplayAspectRatio::PAR1_1 || g_settings.display_crop_mode == DisplayCropMode::OverscanUncorrected || g_settings.display_crop_mode == DisplayCropMode::BordersUncorrected) { From ae18db92713176e18694fdd594796ff74cfae092 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 19:10:18 +1000 Subject: [PATCH 14/69] RegTest: Use filename instead of database title --- src/duckstation-regtest/regtest_host.cpp | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp index cad29ca38..12a245e41 100644 --- a/src/duckstation-regtest/regtest_host.cpp +++ b/src/duckstation-regtest/regtest_host.cpp @@ -700,22 +700,12 @@ bool RegTestHost::SetNewDataRoot(const std::string& filename) return false; } - const GameDatabase::Entry* dbentry = GameDatabase::GetEntryForDisc(image.get()); - std::string_view game_name; - if (dbentry) - { - game_name = dbentry->title; - INFO_LOG("Game name from database: {}", game_name); - } - else - { - game_name = Path::GetFileTitle(filename); - WARNING_LOG("Game not found in database, using filename: {}", game_name); - } - if (!s_dump_base_directory.empty()) { - std::string dump_directory = Path::Combine(s_dump_base_directory, game_name); + std::string game_subdir = Path::SanitizeFileName(Path::GetFileTitle(filename)); + INFO_LOG("Writing to subdirectory '{}'", game_subdir); + + std::string dump_directory = Path::Combine(s_dump_base_directory, game_subdir); if (!FileSystem::DirectoryExists(dump_directory.c_str())) { INFO_LOG("Creating directory '{}'...", dump_directory); From aafc02968240b47803ba1fb81a7e9d474356c4c2 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 19:55:49 +1000 Subject: [PATCH 15/69] Misc: Un-namespace Timer --- src/common/file_system.cpp | 2 +- src/common/log.cpp | 5 +- src/common/timer.cpp | 4 - src/common/timer.h | 4 - src/core/achievements.cpp | 8 +- src/core/cdrom_async_reader.cpp | 6 +- src/core/fullscreen_ui.cpp | 5 +- src/core/game_database.cpp | 4 +- src/core/game_list.cpp | 2 +- src/core/gpu_backend.cpp | 8 +- src/core/gpu_dump.cpp | 2 +- src/core/gpu_hw.cpp | 18 +-- src/core/gpu_hw_texture_cache.cpp | 2 +- src/core/host_interface_progress_callback.h | 2 +- src/core/performance_counters.cpp | 20 +-- src/core/system.cpp | 128 ++++++++++---------- src/duckstation-qt/coverdownloaddialog.h | 2 +- src/duckstation-qt/gamelistrefreshthread.h | 2 +- src/duckstation-qt/qtprogresscallback.h | 2 +- src/duckstation-regtest/regtest_host.cpp | 6 +- src/util/audio_stream.cpp | 4 +- src/util/gpu_device.cpp | 10 +- src/util/http_downloader.cpp | 11 +- src/util/http_downloader_curl.cpp | 4 +- src/util/http_downloader_winhttp.cpp | 4 +- src/util/imgui_fullscreen.cpp | 29 ++--- src/util/imgui_manager.cpp | 31 +++-- src/util/input_manager.cpp | 16 +-- src/util/metal_device.mm | 4 +- src/util/postprocessing.cpp | 4 +- src/util/postprocessing.h | 4 +- src/util/postprocessing_shader_fx.h | 2 +- 32 files changed, 168 insertions(+), 187 deletions(-) diff --git a/src/common/file_system.cpp b/src/common/file_system.cpp index d195cbca4..364199b26 100644 --- a/src/common/file_system.cpp +++ b/src/common/file_system.cpp @@ -923,7 +923,7 @@ std::FILE* FileSystem::OpenExistingOrCreateCFile(const char* path, s32 retry_ms, // if there's a sharing violation, keep retrying if (file == INVALID_HANDLE_VALUE && GetLastError() == ERROR_SHARING_VIOLATION && retry_ms >= 0) { - Common::Timer timer; + Timer timer; while (retry_ms == 0 || timer.GetTimeMilliseconds() <= retry_ms) { Sleep(1); diff --git a/src/common/log.cpp b/src/common/log.cpp index 87bbfa54f..dc5e0acd3 100644 --- a/src/common/log.cpp +++ b/src/common/log.cpp @@ -81,7 +81,7 @@ struct State std::vector callbacks; std::mutex callbacks_mutex; - Common::Timer::Value start_timestamp = Common::Timer::GetCurrentValue(); + Timer::Value start_timestamp = Timer::GetCurrentValue(); FileSystem::ManagedCFilePtr file_handle; @@ -146,8 +146,7 @@ const std::array(Log::Channel::MaxCount)>& Log: float Log::GetCurrentMessageTime() { - return static_cast( - Common::Timer::ConvertValueToSeconds(Common::Timer::GetCurrentValue() - s_state.start_timestamp)); + return static_cast(Timer::ConvertValueToSeconds(Timer::GetCurrentValue() - s_state.start_timestamp)); } bool Log::AreTimestampsEnabled() diff --git a/src/common/timer.cpp b/src/common/timer.cpp index 6b74097d8..38939389f 100644 --- a/src/common/timer.cpp +++ b/src/common/timer.cpp @@ -15,8 +15,6 @@ #include #endif -namespace Common { - #ifdef _WIN32 static double s_counter_frequency; @@ -399,5 +397,3 @@ void Timer::NanoSleep(std::uint64_t ns) nanosleep(&ts, nullptr); #endif } - -} // namespace Common diff --git a/src/common/timer.h b/src/common/timer.h index 33db85db8..11958966b 100644 --- a/src/common/timer.h +++ b/src/common/timer.h @@ -4,8 +4,6 @@ #pragma once #include -namespace Common { - class Timer { public: @@ -47,5 +45,3 @@ public: private: Value m_tvStartValue; }; - -} // namespace Common \ No newline at end of file diff --git a/src/core/achievements.cpp b/src/core/achievements.cpp index 48d80212a..61e3dbe5d 100644 --- a/src/core/achievements.cpp +++ b/src/core/achievements.cpp @@ -103,7 +103,7 @@ struct LeaderboardTrackerIndicator { u32 tracker_id; std::string text; - Common::Timer show_hide_time; + Timer show_hide_time; bool active; }; @@ -111,7 +111,7 @@ struct AchievementChallengeIndicator { const rc_client_achievement_t* achievement; std::string badge_path; - Common::Timer show_hide_time; + Timer show_hide_time; bool active; }; @@ -119,7 +119,7 @@ struct AchievementProgressIndicator { const rc_client_achievement_t* achievement; std::string badge_path; - Common::Timer show_hide_time; + Timer show_hide_time; bool active; }; } // namespace @@ -219,7 +219,7 @@ static bool s_has_achievements = false; static bool s_has_leaderboards = false; static bool s_has_rich_presence = false; static std::string s_rich_presence_string; -static Common::Timer s_rich_presence_poll_time; +static Timer s_rich_presence_poll_time; static rc_client_async_handle_t* s_login_request; static rc_client_async_handle_t* s_load_game_request; diff --git a/src/core/cdrom_async_reader.cpp b/src/core/cdrom_async_reader.cpp index 7d2c21e17..3e31b4892 100644 --- a/src/core/cdrom_async_reader.cpp +++ b/src/core/cdrom_async_reader.cpp @@ -187,7 +187,7 @@ bool CDROMAsyncReader::WaitForReadToComplete() return m_buffers[m_buffer_front.load()].result; } - Common::Timer wait_timer; + Timer wait_timer; DEBUG_LOG("Sector read pending, waiting"); std::unique_lock lock(m_mutex); @@ -226,7 +226,7 @@ void CDROMAsyncReader::EmptyBuffers() bool CDROMAsyncReader::ReadSectorIntoBuffer(std::unique_lock& lock) { - Common::Timer timer; + Timer timer; const u32 slot = m_buffer_back.load(); m_buffer_back.store((slot + 1) % static_cast(m_buffers.size())); @@ -259,7 +259,7 @@ bool CDROMAsyncReader::ReadSectorIntoBuffer(std::unique_lock& lock) void CDROMAsyncReader::ReadSectorNonThreaded(CDImage::LBA lba) { - Common::Timer timer; + Timer timer; m_buffers.resize(1); m_seek_error.store(false); diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 6b3c97e2c..2bc52997f 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -422,7 +422,7 @@ static std::string s_input_binding_key; static std::string s_input_binding_display_name; static std::vector s_input_binding_new_bindings; static std::vector>> s_input_binding_value_ranges; -static Common::Timer s_input_binding_timer; +static Timer s_input_binding_timer; static bool s_controller_macro_expanded[NUM_CONTROLLER_AND_CARD_PORTS][InputManager::NUM_MACRO_BUTTONS_PER_CONTROLLER] = {}; @@ -4323,7 +4323,8 @@ void FullscreenUI::DrawGraphicsSettingsPage() DrawEnumSetting(bsi, FSUI_ICONSTR(ICON_FA_CROP_ALT, "Crop Mode"), FSUI_CSTR("Determines how much of the area typically not visible on a consumer TV set to crop/hide."), "Display", "CropMode", Settings::DEFAULT_DISPLAY_CROP_MODE, &Settings::ParseDisplayCropMode, - &Settings::GetDisplayCropModeName, &Settings::GetDisplayCropModeDisplayName, DisplayCropMode::MaxCount); + &Settings::GetDisplayCropModeName, &Settings::GetDisplayCropModeDisplayName, + DisplayCropMode::MaxCount); DrawEnumSetting( bsi, FSUI_ICONSTR(ICON_FA_EXPAND, "Scaling"), diff --git a/src/core/game_database.cpp b/src/core/game_database.cpp index 6d81b5da9..1b10aed67 100644 --- a/src/core/game_database.cpp +++ b/src/core/game_database.cpp @@ -163,7 +163,7 @@ void GameDatabase::EnsureLoaded() if (s_loaded) return; - Common::Timer timer; + Timer timer; s_loaded = true; @@ -1417,7 +1417,7 @@ void GameDatabase::EnsureTrackHashesMapLoaded() bool GameDatabase::LoadTrackHashes() { - Common::Timer load_timer; + Timer load_timer; Error error; std::optional gamedb_data(Host::ReadResourceFileToString(DISCDB_YAML_FILENAME, false, &error)); diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp index bf7f93877..8894a7d9a 100644 --- a/src/core/game_list.cpp +++ b/src/core/game_list.cpp @@ -1690,7 +1690,7 @@ FileSystem::ManagedCFilePtr GameList::OpenMemoryCardTimestampCache(bool for_writ if (errno != EACCES) return nullptr; - Common::Timer timer; + Timer timer; while (timer.GetTimeMilliseconds() <= 100.0f) { fp = FileSystem::OpenManagedSharedCFile(filename.c_str(), mode, share_mode, nullptr); diff --git a/src/core/gpu_backend.cpp b/src/core/gpu_backend.cpp index a8b17818a..b24c3f6b0 100644 --- a/src/core/gpu_backend.cpp +++ b/src/core/gpu_backend.cpp @@ -217,7 +217,7 @@ void GPUBackend::Sync(bool allow_sleep) void GPUBackend::RunGPULoop() { static constexpr double SPIN_TIME_NS = 1 * 1000000; - Common::Timer::Value last_command_time = 0; + Timer::Value last_command_time = 0; for (;;) { @@ -225,8 +225,8 @@ void GPUBackend::RunGPULoop() u32 read_ptr = m_command_fifo_read_ptr.load(); if (read_ptr == write_ptr) { - const Common::Timer::Value current_time = Common::Timer::GetCurrentValue(); - if (Common::Timer::ConvertValueToNanoseconds(current_time - last_command_time) < SPIN_TIME_NS) + const Timer::Value current_time = Timer::GetCurrentValue(); + if (Timer::ConvertValueToNanoseconds(current_time - last_command_time) < SPIN_TIME_NS) continue; std::unique_lock lock(m_sync_mutex); @@ -273,7 +273,7 @@ void GPUBackend::RunGPULoop() } } - last_command_time = allow_sleep ? 0 : Common::Timer::GetCurrentValue(); + last_command_time = allow_sleep ? 0 : Timer::GetCurrentValue(); m_command_fifo_read_ptr.store(read_ptr); } } diff --git a/src/core/gpu_dump.cpp b/src/core/gpu_dump.cpp index 3d8a93cf7..216b232f5 100644 --- a/src/core/gpu_dump.cpp +++ b/src/core/gpu_dump.cpp @@ -320,7 +320,7 @@ std::unique_ptr GPUDump::Player::Open(std::string path, Error* { std::unique_ptr ret; - Common::Timer timer; + Timer timer; std::optional> data; if (StringUtil::EndsWithNoCase(path, ".psxgpu.zst") || StringUtil::EndsWithNoCase(path, ".psxgpu.xz")) diff --git a/src/core/gpu_hw.cpp b/src/core/gpu_hw.cpp index 8540f4328..7abdf31f4 100644 --- a/src/core/gpu_hw.cpp +++ b/src/core/gpu_hw.cpp @@ -152,8 +152,8 @@ class ShaderCompileProgressTracker { public: ShaderCompileProgressTracker(std::string title, u32 total) - : m_title(std::move(title)), m_min_time(Common::Timer::ConvertSecondsToValue(1.0)), - m_update_interval(Common::Timer::ConvertSecondsToValue(0.1)), m_start_time(Common::Timer::GetCurrentValue()), + : m_title(std::move(title)), m_min_time(Timer::ConvertSecondsToValue(1.0)), + m_update_interval(Timer::ConvertSecondsToValue(0.1)), m_start_time(Timer::GetCurrentValue()), m_last_update_time(0), m_progress(0), m_total(total) { } @@ -161,14 +161,14 @@ public: double GetElapsedMilliseconds() const { - return Common::Timer::ConvertValueToMilliseconds(Common::Timer::GetCurrentValue() - m_start_time); + return Timer::ConvertValueToMilliseconds(Timer::GetCurrentValue() - m_start_time); } void Increment(u32 progress = 1) { m_progress += progress; - const u64 tv = Common::Timer::GetCurrentValue(); + const u64 tv = Timer::GetCurrentValue(); if ((tv - m_start_time) >= m_min_time && (tv - m_last_update_time) >= m_update_interval) { Host::DisplayLoadingScreen(m_title.c_str(), 0, static_cast(m_total), static_cast(m_progress)); @@ -178,10 +178,10 @@ public: private: std::string m_title; - Common::Timer::Value m_min_time; - Common::Timer::Value m_update_interval; - Common::Timer::Value m_start_time; - Common::Timer::Value m_last_update_time; + Timer::Value m_min_time; + Timer::Value m_update_interval; + Timer::Value m_start_time; + Timer::Value m_last_update_time; u32 m_progress; u32 m_total; }; @@ -1612,7 +1612,7 @@ bool GPU_HW::CompilePipelines(Error* error) bool GPU_HW::CompileResolutionDependentPipelines(Error* error) { - Common::Timer timer; + Timer timer; m_vram_readback_pipeline.reset(); for (std::unique_ptr& p : m_vram_extract_pipeline) diff --git a/src/core/gpu_hw_texture_cache.cpp b/src/core/gpu_hw_texture_cache.cpp index 5c2484914..bf0bca50d 100644 --- a/src/core/gpu_hw_texture_cache.cpp +++ b/src/core/gpu_hw_texture_cache.cpp @@ -3100,7 +3100,7 @@ void GPUTextureCache::PreloadReplacementTextures() { static constexpr float UPDATE_INTERVAL = 1.0f; - Common::Timer last_update_time; + Timer last_update_time; u32 num_textures_loaded = 0; const size_t total_textures = s_state.vram_replacements.size() + s_state.vram_write_texture_replacements.size() + s_state.texture_page_texture_replacements.size(); diff --git a/src/core/host_interface_progress_callback.h b/src/core/host_interface_progress_callback.h index 3af69e7fd..b46ac13d3 100644 --- a/src/core/host_interface_progress_callback.h +++ b/src/core/host_interface_progress_callback.h @@ -28,7 +28,7 @@ public: private: void Redraw(bool force); - Common::Timer m_open_time; + Timer m_open_time; float m_open_delay = 1.0f; int m_last_progress_percent = -1; }; diff --git a/src/core/performance_counters.cpp b/src/core/performance_counters.cpp index 5a6ad2a20..14a93fc95 100644 --- a/src/core/performance_counters.cpp +++ b/src/core/performance_counters.cpp @@ -22,8 +22,8 @@ namespace { struct State { - Common::Timer::Value last_update_time; - Common::Timer::Value last_frame_time; + Timer::Value last_update_time; + Timer::Value last_frame_time; u32 last_frame_number; u32 last_internal_frame_number; @@ -142,7 +142,7 @@ void PerformanceCounters::Clear() void PerformanceCounters::Reset() { - const Common::Timer::Value now_ticks = Common::Timer::GetCurrentValue(); + const Timer::Value now_ticks = Timer::GetCurrentValue(); s_state.last_frame_time = now_ticks; s_state.last_update_time = now_ticks; @@ -162,10 +162,10 @@ void PerformanceCounters::Reset() void PerformanceCounters::Update(u32 frame_number, u32 internal_frame_number) { - const Common::Timer::Value now_ticks = Common::Timer::GetCurrentValue(); + const Timer::Value now_ticks = Timer::GetCurrentValue(); const float frame_time = static_cast( - Common::Timer::ConvertValueToMilliseconds(now_ticks - std::exchange(s_state.last_frame_time, now_ticks))); + Timer::ConvertValueToMilliseconds(now_ticks - std::exchange(s_state.last_frame_time, now_ticks))); s_state.minimum_frame_time_accumulator = (s_state.minimum_frame_time_accumulator == 0.0f) ? frame_time : std::min(s_state.minimum_frame_time_accumulator, frame_time); @@ -175,8 +175,8 @@ void PerformanceCounters::Update(u32 frame_number, u32 internal_frame_number) s_state.frame_time_history_pos = (s_state.frame_time_history_pos + 1) % NUM_FRAME_TIME_SAMPLES; // update fps counter - const Common::Timer::Value ticks_diff = now_ticks - s_state.last_update_time; - const float time = static_cast(Common::Timer::ConvertValueToSeconds(ticks_diff)); + const Timer::Value ticks_diff = now_ticks - s_state.last_update_time; + const float time = static_cast(Timer::ConvertValueToSeconds(ticks_diff)); if (time < PERFORMANCE_COUNTER_UPDATE_INTERVAL) return; @@ -190,7 +190,7 @@ void PerformanceCounters::Update(u32 frame_number, u32 internal_frame_number) // TODO: Make the math here less rubbish const double pct_divider = 100.0 * (1.0 / ((static_cast(ticks_diff) * static_cast(Threading::GetThreadTicksPerSecond())) / - Common::Timer::GetFrequency() / 1000000000.0)); + Timer::GetFrequency() / 1000000000.0)); const double time_divider = 1000.0 * (1.0 / static_cast(Threading::GetThreadTicksPerSecond())) * (1.0 / static_cast(frames_runf)); @@ -230,8 +230,8 @@ void PerformanceCounters::Update(u32 frame_number, u32 internal_frame_number) if (g_settings.display_show_gpu_stats) g_gpu->UpdateStatistics(frames_run); - VERBOSE_LOG("FPS: {:.2f} VPS: {:.2f} CPU: {:.2f} GPU: {:.2f} Avg: {:.2f}ms Min: {:.2f}ms Max: {:.2f}ms", - s_state.fps, s_state.vps, s_state.cpu_thread_usage, s_state.gpu_usage, s_state.average_frame_time, + VERBOSE_LOG("FPS: {:.2f} VPS: {:.2f} CPU: {:.2f} GPU: {:.2f} Avg: {:.2f}ms Min: {:.2f}ms Max: {:.2f}ms", s_state.fps, + s_state.vps, s_state.cpu_thread_usage, s_state.gpu_usage, s_state.average_frame_time, s_state.minimum_frame_time, s_state.maximum_frame_time); Host::OnPerformanceCountersUpdated(); diff --git a/src/core/system.cpp b/src/core/system.cpp index 4b425f1c8..df399ce76 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -181,8 +181,8 @@ static void UpdateThrottlePeriod(); static void ResetThrottler(); /// Throttles the system, i.e. sleeps until it's time to execute the next frame. -static void Throttle(Common::Timer::Value current_time); -static void AccumulatePreFrameSleepTime(Common::Timer::Value current_time); +static void Throttle(Timer::Value current_time); +static void AccumulatePreFrameSleepTime(Timer::Value current_time); static void UpdateDisplayVSync(); static bool UpdateGameSettingsLayer(); @@ -270,14 +270,14 @@ struct ALIGN_TO_CACHE_LINE StateVars float video_frame_rate = 0.0f; float target_speed = 0.0f; - Common::Timer::Value frame_period = 0; - Common::Timer::Value next_frame_time = 0; + Timer::Value frame_period = 0; + Timer::Value next_frame_time = 0; - Common::Timer::Value frame_start_time = 0; - Common::Timer::Value last_active_frame_time = 0; - Common::Timer::Value pre_frame_sleep_time = 0; - Common::Timer::Value max_active_frame_time = 0; - Common::Timer::Value last_pre_frame_sleep_update_time = 0; + Timer::Value frame_start_time = 0; + Timer::Value last_active_frame_time = 0; + Timer::Value pre_frame_sleep_time = 0; + Timer::Value max_active_frame_time = 0; + Timer::Value last_pre_frame_sleep_update_time = 0; std::unique_ptr media_capture; std::unique_ptr gpu_dump_player; @@ -468,7 +468,7 @@ void System::LogStartupInformation() bool System::ProcessStartup(Error* error) { - Common::Timer timer; + Timer timer; // Allocate JIT memory as soon as possible. if (!CPU::CodeCache::ProcessStartup(error)) @@ -1243,15 +1243,15 @@ bool System::RecreateGPU(GPURenderer renderer, bool force_recreate_device, bool void System::HandleHostGPUDeviceLost() { - static Common::Timer::Value s_last_gpu_reset_time = 0; + static Timer::Value s_last_gpu_reset_time = 0; static constexpr float MIN_TIME_BETWEEN_RESETS = 15.0f; // If we're constantly crashing on something in particular, we don't want to end up in an // endless reset loop.. that'd probably end up leaking memory and/or crashing us for other // reasons. So just abort in such case. - const Common::Timer::Value current_time = Common::Timer::GetCurrentValue(); + const Timer::Value current_time = Timer::GetCurrentValue(); if (s_last_gpu_reset_time != 0 && - Common::Timer::ConvertValueToSeconds(current_time - s_last_gpu_reset_time) < MIN_TIME_BETWEEN_RESETS) + Timer::ConvertValueToSeconds(current_time - s_last_gpu_reset_time) < MIN_TIME_BETWEEN_RESETS) { Panic("Host GPU lost too many times, device is probably completely wedged."); } @@ -2197,10 +2197,10 @@ void System::FrameDone() } } - Common::Timer::Value current_time = Common::Timer::GetCurrentValue(); + Timer::Value current_time = Timer::GetCurrentValue(); // pre-frame sleep accounting (input lag reduction) - const Common::Timer::Value pre_frame_sleep_until = s_state.next_frame_time + s_state.pre_frame_sleep_time; + const Timer::Value pre_frame_sleep_until = s_state.next_frame_time + s_state.pre_frame_sleep_time; s_state.last_active_frame_time = current_time - s_state.frame_start_time; if (s_state.pre_frame_sleep) AccumulatePreFrameSleepTime(current_time); @@ -2255,15 +2255,15 @@ void System::FrameDone() } // pre-frame sleep (input lag reduction) - current_time = Common::Timer::GetCurrentValue(); + current_time = Timer::GetCurrentValue(); if (s_state.pre_frame_sleep) { // don't sleep if it's under 1ms, because we're just going to overshoot (or spin). if (pre_frame_sleep_until > current_time && - Common::Timer::ConvertValueToMilliseconds(pre_frame_sleep_until - current_time) >= 1) + Timer::ConvertValueToMilliseconds(pre_frame_sleep_until - current_time) >= 1) { - Common::Timer::SleepUntil(pre_frame_sleep_until, true); - current_time = Common::Timer::GetCurrentValue(); + Timer::SleepUntil(pre_frame_sleep_until, true); + current_time = Timer::GetCurrentValue(); } } @@ -2306,7 +2306,7 @@ void System::UpdateThrottlePeriod() const double target_speed = std::max(static_cast(s_state.target_speed), std::numeric_limits::epsilon()); s_state.frame_period = - Common::Timer::ConvertSecondsToValue(1.0 / (static_cast(s_state.video_frame_rate) * target_speed)); + Timer::ConvertSecondsToValue(1.0 / (static_cast(s_state.video_frame_rate) * target_speed)); } else { @@ -2318,18 +2318,18 @@ void System::UpdateThrottlePeriod() void System::ResetThrottler() { - s_state.next_frame_time = Common::Timer::GetCurrentValue() + s_state.frame_period; + s_state.next_frame_time = Timer::GetCurrentValue() + s_state.frame_period; s_state.pre_frame_sleep_time = 0; } -void System::Throttle(Common::Timer::Value current_time) +void System::Throttle(Timer::Value current_time) { // If we're running too slow, advance the next frame time based on the time we lost. Effectively skips // running those frames at the intended time, because otherwise if we pause in the debugger, we'll run // hundreds of frames when we resume. if (current_time > s_state.next_frame_time) { - const Common::Timer::Value diff = static_cast(current_time) - static_cast(s_state.next_frame_time); + const Timer::Value diff = static_cast(current_time) - static_cast(s_state.next_frame_time); s_state.next_frame_time += (diff / s_state.frame_period) * s_state.frame_period + s_state.frame_period; return; } @@ -2339,13 +2339,13 @@ void System::Throttle(Common::Timer::Value current_time) // That way in a query->response->query->response chain, we don't process only one message per frame. if (s_state.socket_multiplexer && s_state.socket_multiplexer->HasAnyClientSockets()) { - Common::Timer::Value poll_start_time = current_time; + Timer::Value poll_start_time = current_time; for (;;) { const u32 sleep_ms = - static_cast(Common::Timer::ConvertValueToMilliseconds(s_state.next_frame_time - poll_start_time)); + static_cast(Timer::ConvertValueToMilliseconds(s_state.next_frame_time - poll_start_time)); s_state.socket_multiplexer->PollEventsWithTimeout(sleep_ms); - poll_start_time = Common::Timer::GetCurrentValue(); + poll_start_time = Timer::GetCurrentValue(); if (poll_start_time >= s_state.next_frame_time || (!g_settings.display_optimal_frame_pacing && sleep_ms == 0)) break; } @@ -2355,25 +2355,25 @@ void System::Throttle(Common::Timer::Value current_time) // Use a spinwait if we undersleep for all platforms except android.. don't want to burn battery. // Linux also seems to do a much better job of waking up at the requested time. #if !defined(__linux__) - Common::Timer::SleepUntil(s_state.next_frame_time, g_settings.display_optimal_frame_pacing); + Timer::SleepUntil(s_state.next_frame_time, g_settings.display_optimal_frame_pacing); #else - Common::Timer::SleepUntil(s_state.next_frame_time, false); + Timer::SleepUntil(s_state.next_frame_time, false); #endif } #else // No spinwait on Android, see above. - Common::Timer::SleepUntil(s_state.next_frame_time, false); + Timer::SleepUntil(s_state.next_frame_time, false); #endif #if 0 - const Common::Timer::Value time_after_sleep = Common::Timer::GetCurrentValue(); + const Timer::Value time_after_sleep = Timer::GetCurrentValue(); DEV_LOG("Asked for {:.2f} ms, slept for {:.2f} ms, {:.2f} ms {}", - Common::Timer::ConvertValueToMilliseconds(s_next_frame_time - current_time), - Common::Timer::ConvertValueToMilliseconds(time_after_sleep - current_time), - Common::Timer::ConvertValueToMilliseconds((time_after_sleep < s_next_frame_time) ? - (s_next_frame_time - time_after_sleep) : - (time_after_sleep - s_next_frame_time)), - (time_after_sleep < s_next_frame_time) ? "early" : "late"); + Timer::ConvertValueToMilliseconds(s_state.next_frame_time - current_time), + Timer::ConvertValueToMilliseconds(time_after_sleep - current_time), + Timer::ConvertValueToMilliseconds((time_after_sleep < s_state.next_frame_time) ? + (s_state.next_frame_time - time_after_sleep) : + (time_after_sleep - s_state.next_frame_time)), + (time_after_sleep < s_state.next_frame_time) ? "early" : "late"); #endif s_state.next_frame_time += s_state.frame_period; @@ -2726,7 +2726,7 @@ bool System::LoadState(const char* path, Error* error, bool save_undo_state) return true; } - Common::Timer load_timer; + Timer load_timer; auto fp = FileSystem::OpenManagedCFile(path, "rb", error); if (!fp) @@ -3026,7 +3026,7 @@ bool System::SaveState(const char* path, Error* error, bool backup_existing_save return false; } - Common::Timer save_timer; + Timer save_timer; SaveStateBuffer buffer; if (!SaveStateToBuffer(&buffer, error, 256)) @@ -3301,38 +3301,37 @@ float System::GetAudioNominalRate() return (s_state.throttler_enabled || s_state.syncing_to_host_with_vsync) ? s_state.target_speed : 1.0f; } -void System::AccumulatePreFrameSleepTime(Common::Timer::Value current_time) +void System::AccumulatePreFrameSleepTime(Timer::Value current_time) { DebugAssert(s_state.pre_frame_sleep); s_state.max_active_frame_time = std::max(s_state.max_active_frame_time, s_state.last_active_frame_time); // in case one frame runs over, adjust to compensate - const Common::Timer::Value max_sleep_time_for_this_frame = + const Timer::Value max_sleep_time_for_this_frame = s_state.frame_period - std::min(s_state.last_active_frame_time, s_state.frame_period); if (max_sleep_time_for_this_frame < s_state.pre_frame_sleep_time) { - s_state.pre_frame_sleep_time = Common::AlignDown( - max_sleep_time_for_this_frame, static_cast(Common::Timer::ConvertMillisecondsToValue(1))); + s_state.pre_frame_sleep_time = + Common::AlignDown(max_sleep_time_for_this_frame, static_cast(Timer::ConvertMillisecondsToValue(1))); DEV_LOG("Adjust pre-frame time to {} ms due to overrun of {} ms", - Common::Timer::ConvertValueToMilliseconds(s_state.pre_frame_sleep_time), - Common::Timer::ConvertValueToMilliseconds(s_state.last_active_frame_time)); + Timer::ConvertValueToMilliseconds(s_state.pre_frame_sleep_time), + Timer::ConvertValueToMilliseconds(s_state.last_active_frame_time)); } - if (Common::Timer::ConvertValueToSeconds(current_time - s_state.last_pre_frame_sleep_update_time) >= + if (Timer::ConvertValueToSeconds(current_time - s_state.last_pre_frame_sleep_update_time) >= PRE_FRAME_SLEEP_UPDATE_INTERVAL) { s_state.last_pre_frame_sleep_update_time = current_time; - const Common::Timer::Value expected_frame_time = - s_state.max_active_frame_time + - Common::Timer::ConvertMillisecondsToValue(g_settings.display_pre_frame_sleep_buffer); + const Timer::Value expected_frame_time = + s_state.max_active_frame_time + Timer::ConvertMillisecondsToValue(g_settings.display_pre_frame_sleep_buffer); s_state.pre_frame_sleep_time = Common::AlignDown(s_state.frame_period - std::min(expected_frame_time, s_state.frame_period), - static_cast(Common::Timer::ConvertMillisecondsToValue(1))); + static_cast(Timer::ConvertMillisecondsToValue(1))); DEV_LOG("Set pre-frame time to {} ms (expected frame time of {} ms)", - Common::Timer::ConvertValueToMilliseconds(s_state.pre_frame_sleep_time), - Common::Timer::ConvertValueToMilliseconds(expected_frame_time)); + Timer::ConvertValueToMilliseconds(s_state.pre_frame_sleep_time), + Timer::ConvertValueToMilliseconds(expected_frame_time)); s_state.max_active_frame_time = 0; } @@ -3344,12 +3343,11 @@ void System::FormatLatencyStats(SmallStringBase& str) const u32 audio_latency = AudioStream::GetMSForBufferSize(audio_stream->GetSampleRate(), audio_stream->GetBufferedFramesRelaxed()); - const double active_frame_time = std::ceil(Common::Timer::ConvertValueToMilliseconds(s_state.last_active_frame_time)); - const double pre_frame_time = std::ceil(Common::Timer::ConvertValueToMilliseconds(s_state.pre_frame_sleep_time)); - const double input_latency = - std::ceil(Common::Timer::ConvertValueToMilliseconds(s_state.frame_period - s_state.pre_frame_sleep_time) - - Common::Timer::ConvertValueToMilliseconds(static_cast(s_state.runahead_frames) * - s_state.frame_period)); + const double active_frame_time = std::ceil(Timer::ConvertValueToMilliseconds(s_state.last_active_frame_time)); + const double pre_frame_time = std::ceil(Timer::ConvertValueToMilliseconds(s_state.pre_frame_sleep_time)); + const double input_latency = std::ceil( + Timer::ConvertValueToMilliseconds(s_state.frame_period - s_state.pre_frame_sleep_time) - + Timer::ConvertValueToMilliseconds(static_cast(s_state.runahead_frames) * s_state.frame_period)); str.format("AF: {:.0f}ms | PF: {:.0f}ms | IL: {:.0f}ms | AL: {}ms", active_frame_time, pre_frame_time, input_latency, audio_latency); @@ -4859,7 +4857,7 @@ bool System::SaveMemoryState(MemorySaveState* mss) bool System::SaveRewindState() { #ifdef PROFILE_MEMORY_SAVE_STATES - Common::Timer save_timer; + Timer save_timer; #endif // try to reuse the frontmost slot @@ -4897,7 +4895,7 @@ bool System::LoadRewindState(u32 skip_saves /*= 0*/, bool consume_state /*=true return false; #ifdef PROFILE_MEMORY_SAVE_STATES - Common::Timer load_timer; + Timer load_timer; #endif if (!LoadMemoryState(s_state.rewind_states.back())) @@ -4961,7 +4959,7 @@ void System::DoRewind() Host::PumpMessagesOnCPUThread(); IdlePollUpdate(); - Throttle(Common::Timer::GetCurrentValue()); + Throttle(Timer::GetCurrentValue()); } void System::SaveRunaheadState() @@ -4986,7 +4984,7 @@ void System::SaveRunaheadState() bool System::DoRunahead() { #ifdef PROFILE_MEMORY_SAVE_STATES - static Common::Timer replay_timer; + static Timer replay_timer; #endif if (s_state.runahead_replay_pending) @@ -5839,12 +5837,12 @@ bool System::ChangeGPUDump(std::string new_path) void System::UpdateSessionTime(const std::string& prev_serial) { - const u64 ctime = Common::Timer::GetCurrentValue(); + const Timer::Value ctime = Timer::GetCurrentValue(); if (!prev_serial.empty() && GameList::IsGameListLoaded()) { // round up to seconds const std::time_t etime = - static_cast(std::round(Common::Timer::ConvertValueToSeconds(ctime - s_state.session_start_time))); + static_cast(std::round(Timer::ConvertValueToSeconds(ctime - s_state.session_start_time))); const std::time_t wtime = std::time(nullptr); GameList::AddPlayedTimeForSerial(prev_serial, wtime, etime); } @@ -5854,8 +5852,8 @@ void System::UpdateSessionTime(const std::string& prev_serial) u64 System::GetSessionPlayedTime() { - const u64 ctime = Common::Timer::GetCurrentValue(); - return static_cast(std::round(Common::Timer::ConvertValueToSeconds(ctime - s_state.session_start_time))); + const Timer::Value ctime = Timer::GetCurrentValue(); + return static_cast(std::round(Timer::ConvertValueToSeconds(ctime - s_state.session_start_time))); } void System::QueueTaskOnThread(std::function task) diff --git a/src/duckstation-qt/coverdownloaddialog.h b/src/duckstation-qt/coverdownloaddialog.h index 2174287bd..ff1ebbe13 100644 --- a/src/duckstation-qt/coverdownloaddialog.h +++ b/src/duckstation-qt/coverdownloaddialog.h @@ -53,5 +53,5 @@ private: Ui::CoverDownloadDialog m_ui; std::unique_ptr m_thread; - Common::Timer m_last_refresh_time; + Timer m_last_refresh_time; }; diff --git a/src/duckstation-qt/gamelistrefreshthread.h b/src/duckstation-qt/gamelistrefreshthread.h index 5fb61a41f..9c148ad67 100644 --- a/src/duckstation-qt/gamelistrefreshthread.h +++ b/src/duckstation-qt/gamelistrefreshthread.h @@ -35,7 +35,7 @@ private: void fireUpdate(); GameListRefreshThread* m_parent; - Common::Timer m_start_time; + Timer m_start_time; QString m_status_text; int m_last_range = 1; int m_last_value = 0; diff --git a/src/duckstation-qt/qtprogresscallback.h b/src/duckstation-qt/qtprogresscallback.h index f7215881a..69058bc70 100644 --- a/src/duckstation-qt/qtprogresscallback.h +++ b/src/duckstation-qt/qtprogresscallback.h @@ -42,7 +42,7 @@ private: void checkForDelayedShow(); QProgressDialog m_dialog; - Common::Timer m_show_timer; + Timer m_show_timer; float m_show_delay; }; diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp index 12a245e41..0a6731acd 100644 --- a/src/duckstation-regtest/regtest_host.cpp +++ b/src/duckstation-regtest/regtest_host.cpp @@ -788,12 +788,12 @@ int main(int argc, char* argv[]) s_frames_remaining = s_frames_to_run; { - const Common::Timer::Value start_time = Common::Timer::GetCurrentValue(); + const Timer::Value start_time = Timer::GetCurrentValue(); System::Execute(); - const Common::Timer::Value elapsed_time = Common::Timer::GetCurrentValue() - start_time; - const double elapsed_time_ms = Common::Timer::ConvertValueToMilliseconds(elapsed_time); + const Timer::Value elapsed_time = Timer::GetCurrentValue() - start_time; + const double elapsed_time_ms = Timer::ConvertValueToMilliseconds(elapsed_time); INFO_LOG("Total execution time: {:.2f}ms, average frame time {:.2f}ms, {:.2f} FPS", elapsed_time_ms, elapsed_time_ms / static_cast(s_frames_to_run), static_cast(s_frames_to_run) / elapsed_time_ms * 1000.0); diff --git a/src/util/audio_stream.cpp b/src/util/audio_stream.cpp index 31ce20236..bd4ebc51d 100644 --- a/src/util/audio_stream.cpp +++ b/src/util/audio_stream.cpp @@ -788,9 +788,9 @@ void AudioStream::UpdateStretchTempo() static int iterations = 0; static u64 last_log_time = 0; - const u64 now = Common::Timer::GetCurrentValue(); + const u64 now = Timer::GetCurrentValue(); - if (Common::Timer::ConvertValueToSeconds(now - last_log_time) > 1.0f) + if (Timer::ConvertValueToSeconds(now - last_log_time) > 1.0f) { VERBOSE_LOG("buffers: {:4d} ms ({:3.0f}%), tempo: {}, comp: {:2.3f}, iters: {}, reset:{}", (ibuffer_usage * 1000u) / m_sample_rate, 100.0f * buffer_usage / base_target_usage, tempo, diff --git a/src/util/gpu_device.cpp b/src/util/gpu_device.cpp index bd7acad9e..de710f6e3 100644 --- a/src/util/gpu_device.cpp +++ b/src/util/gpu_device.cpp @@ -242,8 +242,8 @@ bool GPUSwapChain::ShouldSkipPresentingFrame() const float throttle_rate = (m_window_info.surface_refresh_rate > 0.0f) ? m_window_info.surface_refresh_rate : 60.0f; const float throttle_period = 1.0f / throttle_rate; - const u64 now = Common::Timer::GetCurrentValue(); - const double diff = Common::Timer::ConvertValueToSeconds(now - m_last_frame_displayed_time); + const u64 now = Timer::GetCurrentValue(); + const double diff = Timer::ConvertValueToSeconds(now - m_last_frame_displayed_time); if (diff < throttle_period) return true; @@ -255,8 +255,8 @@ void GPUSwapChain::ThrottlePresentation() { const float throttle_rate = (m_window_info.surface_refresh_rate > 0.0f) ? m_window_info.surface_refresh_rate : 60.0f; - const u64 sleep_period = Common::Timer::ConvertNanosecondsToValue(1e+9f / static_cast(throttle_rate)); - const u64 current_ts = Common::Timer::GetCurrentValue(); + const u64 sleep_period = Timer::ConvertNanosecondsToValue(1e+9f / static_cast(throttle_rate)); + const u64 current_ts = Timer::GetCurrentValue(); // Allow it to fall behind/run ahead up to 2*period. Sleep isn't that precise, plus we need to // allow time for the actual rendering. @@ -266,7 +266,7 @@ void GPUSwapChain::ThrottlePresentation() else m_last_frame_displayed_time += sleep_period; - Common::Timer::SleepUntil(m_last_frame_displayed_time, false); + Timer::SleepUntil(m_last_frame_displayed_time, false); } GPUDevice::GPUDevice() diff --git a/src/util/http_downloader.cpp b/src/util/http_downloader.cpp index 11ef826b9..8a97eb8a2 100644 --- a/src/util/http_downloader.cpp +++ b/src/util/http_downloader.cpp @@ -43,7 +43,7 @@ void HTTPDownloader::CreateRequest(std::string url, Request::Callback callback, req->url = std::move(url); req->callback = std::move(callback); req->progress = progress; - req->start_time = Common::Timer::GetCurrentValue(); + req->start_time = Timer::GetCurrentValue(); std::unique_lock lock(m_pending_http_request_lock); if (LockedGetActiveRequestCount() < m_max_active_requests) @@ -65,7 +65,7 @@ void HTTPDownloader::CreatePostRequest(std::string url, std::string post_data, R req->post_data = std::move(post_data); req->callback = std::move(callback); req->progress = progress; - req->start_time = Common::Timer::GetCurrentValue(); + req->start_time = Timer::GetCurrentValue(); std::unique_lock lock(m_pending_http_request_lock); if (LockedGetActiveRequestCount() < m_max_active_requests) @@ -84,7 +84,7 @@ void HTTPDownloader::LockedPollRequests(std::unique_lock& lock) InternalPollRequests(); - const Common::Timer::Value current_time = Common::Timer::GetCurrentValue(); + const Timer::Value current_time = Timer::GetCurrentValue(); u32 active_requests = 0; u32 unstarted_requests = 0; @@ -99,8 +99,7 @@ void HTTPDownloader::LockedPollRequests(std::unique_lock& lock) } if ((req->state == Request::State::Started || req->state == Request::State::Receiving) && - current_time >= req->start_time && - Common::Timer::ConvertValueToSeconds(current_time - req->start_time) >= m_timeout) + current_time >= req->start_time && Timer::ConvertValueToSeconds(current_time - req->start_time) >= m_timeout) { // request timed out ERROR_LOG("Request for '{}' timed out", req->url); @@ -207,7 +206,7 @@ void HTTPDownloader::WaitForAllRequests() while (!m_pending_http_requests.empty()) { // Don't burn too much CPU. - Common::Timer::NanoSleep(1000000); + Timer::NanoSleep(1000000); LockedPollRequests(lock); } } diff --git a/src/util/http_downloader_curl.cpp b/src/util/http_downloader_curl.cpp index cece67103..4cf11a912 100644 --- a/src/util/http_downloader_curl.cpp +++ b/src/util/http_downloader_curl.cpp @@ -105,7 +105,7 @@ size_t HTTPDownloaderCurl::WriteCallback(char* ptr, size_t size, size_t nmemb, v const size_t transfer_size = size * nmemb; const size_t new_size = current_size + transfer_size; req->data.resize(new_size); - req->start_time = Common::Timer::GetCurrentValue(); + req->start_time = Timer::GetCurrentValue(); std::memcpy(&req->data[current_size], ptr, transfer_size); if (req->content_length == 0) @@ -211,7 +211,7 @@ bool HTTPDownloaderCurl::StartRequest(HTTPDownloader::Request* request) DEV_LOG("Started HTTP request for '{}'", req->url); req->state.store(Request::State::Started, std::memory_order_release); - req->start_time = Common::Timer::GetCurrentValue(); + req->start_time = Timer::GetCurrentValue(); const CURLMcode err = curl_multi_add_handle(m_multi_handle, req->handle); if (err != CURLM_OK) diff --git a/src/util/http_downloader_winhttp.cpp b/src/util/http_downloader_winhttp.cpp index e0b7fdc58..6c0c46bb6 100644 --- a/src/util/http_downloader_winhttp.cpp +++ b/src/util/http_downloader_winhttp.cpp @@ -237,7 +237,7 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR const u32 new_size = req->io_position + dwStatusInformationLength; Assert(new_size <= req->data.size()); req->data.resize(new_size); - req->start_time = Common::Timer::GetCurrentValue(); + req->start_time = Timer::GetCurrentValue(); if (!WinHttpQueryDataAvailable(hRequest, nullptr) && GetLastError() != ERROR_IO_PENDING) { @@ -345,7 +345,7 @@ bool HTTPDownloaderWinHttp::StartRequest(HTTPDownloader::Request* request) DEV_LOG("Started HTTP request for '{}'", req->url); req->state = Request::State::Started; - req->start_time = Common::Timer::GetCurrentValue(); + req->start_time = Timer::GetCurrentValue(); return true; } diff --git a/src/util/imgui_fullscreen.cpp b/src/util/imgui_fullscreen.cpp index 39b3705ef..90a24a127 100644 --- a/src/util/imgui_fullscreen.cpp +++ b/src/util/imgui_fullscreen.cpp @@ -175,8 +175,8 @@ struct Notification std::string title; std::string text; std::string badge_path; - Common::Timer::Value start_time; - Common::Timer::Value move_time; + Timer::Value start_time; + Timer::Value move_time; float duration; float target_y; float last_y; @@ -187,7 +187,7 @@ static std::vector s_notifications; static std::string s_toast_title; static std::string s_toast_message; -static Common::Timer::Value s_toast_start_time; +static Timer::Value s_toast_start_time; static float s_toast_duration; namespace { @@ -357,7 +357,8 @@ std::optional ImGuiFullscreen::LoadTextureImage(std::string_view path, u3 std::shared_ptr ImGuiFullscreen::UploadTexture(std::string_view path, const Image& image) { Error error; - std::unique_ptr texture = g_gpu_device->FetchAndUploadTextureImage(image, GPUTexture::Flags::None, &error); + std::unique_ptr texture = + g_gpu_device->FetchAndUploadTextureImage(image, GPUTexture::Flags::None, &error); if (!texture) { ERROR_LOG("Failed to upload texture '{}': {}", Path::GetFileTitle(path), error.GetDescription()); @@ -2937,7 +2938,7 @@ void ImGuiFullscreen::DrawBackgroundProgressDialogs(ImVec2& position, float spac void ImGuiFullscreen::AddNotification(std::string key, float duration, std::string title, std::string text, std::string image_path) { - const Common::Timer::Value current_time = Common::Timer::GetCurrentValue(); + const Timer::Value current_time = Timer::GetCurrentValue(); if (!key.empty()) { @@ -2951,10 +2952,8 @@ void ImGuiFullscreen::AddNotification(std::string key, float duration, std::stri it->badge_path = std::move(image_path); // Don't fade it in again - const float time_passed = - static_cast(Common::Timer::ConvertValueToSeconds(current_time - it->start_time)); - it->start_time = - current_time - Common::Timer::ConvertSecondsToValue(std::min(time_passed, NOTIFICATION_FADE_IN_TIME)); + const float time_passed = static_cast(Timer::ConvertValueToSeconds(current_time - it->start_time)); + it->start_time = current_time - Timer::ConvertSecondsToValue(std::min(time_passed, NOTIFICATION_FADE_IN_TIME)); return; } } @@ -2984,7 +2983,7 @@ void ImGuiFullscreen::DrawNotifications(ImVec2& position, float spacing) return; static constexpr float MOVE_DURATION = 0.5f; - const Common::Timer::Value current_time = Common::Timer::GetCurrentValue(); + const Timer::Value current_time = Timer::GetCurrentValue(); const float horizontal_padding = ImGuiFullscreen::LayoutScale(20.0f); const float vertical_padding = ImGuiFullscreen::LayoutScale(10.0f); @@ -3009,7 +3008,7 @@ void ImGuiFullscreen::DrawNotifications(ImVec2& position, float spacing) for (u32 index = 0; index < static_cast(s_notifications.size());) { Notification& notif = s_notifications[index]; - const float time_passed = static_cast(Common::Timer::ConvertValueToSeconds(current_time - notif.start_time)); + const float time_passed = static_cast(Timer::ConvertValueToSeconds(current_time - notif.start_time)); if (time_passed >= notif.duration) { s_notifications.erase(s_notifications.begin() + index); @@ -3047,8 +3046,7 @@ void ImGuiFullscreen::DrawNotifications(ImVec2& position, float spacing) } else if (actual_y != expected_y) { - const float time_since_move = - static_cast(Common::Timer::ConvertValueToSeconds(current_time - notif.move_time)); + const float time_since_move = static_cast(Timer::ConvertValueToSeconds(current_time - notif.move_time)); if (time_since_move >= MOVE_DURATION) { notif.move_time = current_time; @@ -3108,7 +3106,7 @@ void ImGuiFullscreen::ShowToast(std::string title, std::string message, float du { s_toast_title = std::move(title); s_toast_message = std::move(message); - s_toast_start_time = Common::Timer::GetCurrentValue(); + s_toast_start_time = Timer::GetCurrentValue(); s_toast_duration = duration; } @@ -3125,8 +3123,7 @@ void ImGuiFullscreen::DrawToast() if (s_toast_title.empty() && s_toast_message.empty()) return; - const float elapsed = - static_cast(Common::Timer::ConvertValueToSeconds(Common::Timer::GetCurrentValue() - s_toast_start_time)); + const float elapsed = static_cast(Timer::ConvertValueToSeconds(Timer::GetCurrentValue() - s_toast_start_time)); if (elapsed >= s_toast_duration) { ClearToast(); diff --git a/src/util/imgui_manager.cpp b/src/util/imgui_manager.cpp index 9a68bf6c2..3a7e32272 100644 --- a/src/util/imgui_manager.cpp +++ b/src/util/imgui_manager.cpp @@ -54,8 +54,8 @@ struct OSDMessage { std::string key; std::string text; - Common::Timer::Value start_time; - Common::Timer::Value move_time; + Timer::Value start_time; + Timer::Value move_time; float duration; float target_y; float last_y; @@ -82,8 +82,8 @@ static void SetClipboardTextImpl(void* userdata, const char* text); static void AddOSDMessage(std::string key, std::string message, float duration, bool is_warning); static void RemoveKeyedOSDMessage(std::string key, bool is_warning); static void ClearOSDMessages(bool clear_warnings); -static void AcquirePendingOSDMessages(Common::Timer::Value current_time); -static void DrawOSDMessages(Common::Timer::Value current_time); +static void AcquirePendingOSDMessages(Timer::Value current_time); +static void DrawOSDMessages(Timer::Value current_time); static void CreateSoftwareCursorTextures(); static void UpdateSoftwareCursorTexture(u32 index); static void DestroySoftwareCursorTextures(); @@ -114,7 +114,7 @@ static DynamicHeapArray s_emoji_font_data; static float s_window_width; static float s_window_height; -static Common::Timer s_last_render_time; +static Timer s_last_render_time; // cached copies of WantCaptureKeyboard/Mouse, used to know when to dispatch events static std::atomic_bool s_imgui_wants_keyboard{false}; @@ -781,7 +781,7 @@ void ImGuiManager::AddOSDMessage(std::string key, std::string message, float dur if (!s_show_osd_messages && !is_warning) return; - const Common::Timer::Value current_time = Common::Timer::GetCurrentValue(); + const Timer::Value current_time = Timer::GetCurrentValue(); OSDMessage msg; msg.key = std::move(key); @@ -847,7 +847,7 @@ void ImGuiManager::ClearOSDMessages(bool clear_warnings) } } -void ImGuiManager::AcquirePendingOSDMessages(Common::Timer::Value current_time) +void ImGuiManager::AcquirePendingOSDMessages(Timer::Value current_time) { std::atomic_thread_fence(std::memory_order_consume); if (s_osd_posted_messages.empty()) @@ -870,9 +870,8 @@ void ImGuiManager::AcquirePendingOSDMessages(Common::Timer::Value current_time) iter->duration = new_msg.duration; // Don't fade it in again - const float time_passed = - static_cast(Common::Timer::ConvertValueToSeconds(current_time - iter->start_time)); - iter->start_time = current_time - Common::Timer::ConvertSecondsToValue(std::min(time_passed, OSD_FADE_IN_TIME)); + const float time_passed = static_cast(Timer::ConvertValueToSeconds(current_time - iter->start_time)); + iter->start_time = current_time - Timer::ConvertSecondsToValue(std::min(time_passed, OSD_FADE_IN_TIME)); } else { @@ -887,7 +886,7 @@ void ImGuiManager::AcquirePendingOSDMessages(Common::Timer::Value current_time) } } -void ImGuiManager::DrawOSDMessages(Common::Timer::Value current_time) +void ImGuiManager::DrawOSDMessages(Timer::Value current_time) { static constexpr float MOVE_DURATION = 0.5f; @@ -905,7 +904,7 @@ void ImGuiManager::DrawOSDMessages(Common::Timer::Value current_time) while (iter != s_osd_active_messages.end()) { OSDMessage& msg = *iter; - const float time_passed = static_cast(Common::Timer::ConvertValueToSeconds(current_time - msg.start_time)); + const float time_passed = static_cast(Timer::ConvertValueToSeconds(current_time - msg.start_time)); if (time_passed >= msg.duration) { iter = s_osd_active_messages.erase(iter); @@ -934,8 +933,7 @@ void ImGuiManager::DrawOSDMessages(Common::Timer::Value current_time) else { // We got repositioned, probably due to another message above getting removed. - const float time_since_move = - static_cast(Common::Timer::ConvertValueToSeconds(current_time - msg.move_time)); + const float time_since_move = static_cast(Timer::ConvertValueToSeconds(current_time - msg.move_time)); const float frac = Easing::OutExpo(time_since_move / MOVE_DURATION); msg.last_y = std::floor(msg.last_y - ((msg.last_y - msg.target_y) * frac)); } @@ -946,8 +944,7 @@ void ImGuiManager::DrawOSDMessages(Common::Timer::Value current_time) } else if (actual_y != expected_y) { - const float time_since_move = - static_cast(Common::Timer::ConvertValueToSeconds(current_time - msg.move_time)); + const float time_since_move = static_cast(Timer::ConvertValueToSeconds(current_time - msg.move_time)); if (time_since_move >= MOVE_DURATION) { msg.move_time = current_time; @@ -981,7 +978,7 @@ void ImGuiManager::DrawOSDMessages(Common::Timer::Value current_time) void ImGuiManager::RenderOSDMessages() { - const Common::Timer::Value current_time = Common::Timer::GetCurrentValue(); + const Timer::Value current_time = Timer::GetCurrentValue(); AcquirePendingOSDMessages(current_time); DrawOSDMessages(current_time); } diff --git a/src/util/input_manager.cpp b/src/util/input_manager.cpp index e088a899a..8253e50ae 100644 --- a/src/util/input_manager.cpp +++ b/src/util/input_manager.cpp @@ -66,7 +66,7 @@ struct PadVibrationBinding struct Motor { InputBindingKey binding; - u64 last_update_time; + Timer::Value last_update_time; InputSource* source; float last_intensity; }; @@ -1607,14 +1607,14 @@ void InputManager::SetPadVibrationIntensity(u32 pad_index, float large_or_single const float report_intensity = std::max(large_or_single_motor_intensity, small_motor_intensity); if (large_motor.source) { - large_motor.last_update_time = Common::Timer::GetCurrentValue(); + large_motor.last_update_time = Timer::GetCurrentValue(); large_motor.source->UpdateMotorState(large_motor.binding, report_intensity); } } else if (large_motor.source == small_motor.source) { // both motors are bound to the same source, do an optimal update - large_motor.last_update_time = Common::Timer::GetCurrentValue(); + large_motor.last_update_time = Timer::GetCurrentValue(); large_motor.source->UpdateMotorState(large_motor.binding, small_motor.binding, large_or_single_motor_intensity, small_motor_intensity); } @@ -1623,12 +1623,12 @@ void InputManager::SetPadVibrationIntensity(u32 pad_index, float large_or_single // update motors independently if (large_motor.source && large_motor.last_intensity != large_or_single_motor_intensity) { - large_motor.last_update_time = Common::Timer::GetCurrentValue(); + large_motor.last_update_time = Timer::GetCurrentValue(); large_motor.source->UpdateMotorState(large_motor.binding, large_or_single_motor_intensity); } if (small_motor.source && small_motor.last_intensity != small_motor_intensity) { - small_motor.last_update_time = Common::Timer::GetCurrentValue(); + small_motor.last_update_time = Timer::GetCurrentValue(); small_motor.source->UpdateMotorState(small_motor.binding, small_motor_intensity); } } @@ -1658,7 +1658,7 @@ void InputManager::PauseVibration() void InputManager::UpdateContinuedVibration() { // update vibration intensities, so if the game does a long effect, it continues - const u64 current_time = Common::Timer::GetCurrentValue(); + const u64 current_time = Timer::GetCurrentValue(); for (PadVibrationBinding& pad : s_pad_vibration_array) { if (pad.AreMotorsCombined()) @@ -1669,7 +1669,7 @@ void InputManager::UpdateContinuedVibration() continue; // so only check the first one - const double dt = Common::Timer::ConvertValueToSeconds(current_time - large_motor.last_update_time); + const double dt = Timer::ConvertValueToSeconds(current_time - large_motor.last_update_time); if (dt < VIBRATION_UPDATE_INTERVAL_SECONDS) continue; @@ -1690,7 +1690,7 @@ void InputManager::UpdateContinuedVibration() if (!motor.source || motor.last_intensity == 0.0f) continue; - const double dt = Common::Timer::ConvertValueToSeconds(current_time - motor.last_update_time); + const double dt = Timer::ConvertValueToSeconds(current_time - motor.last_update_time); if (dt < VIBRATION_UPDATE_INTERVAL_SECONDS) continue; diff --git a/src/util/metal_device.mm b/src/util/metal_device.mm index afbc19776..343469013 100644 --- a/src/util/metal_device.mm +++ b/src/util/metal_device.mm @@ -2593,8 +2593,8 @@ void MetalDevice::EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u6 DebugAssert(m_num_current_render_targets == 0 && !m_current_depth_target); EndAnyEncoding(); - Common::Timer::Value current_time; - if (present_time != 0 && (current_time = Common::Timer::GetCurrentValue()) < present_time) + Timer::Value current_time; + if (present_time != 0 && (current_time = Timer::GetCurrentValue()) < present_time) { // Need to convert to mach absolute time. Time values should already be in nanoseconds. const u64 mach_time_nanoseconds = CocoaTools::ConvertMachTimeBaseToNanoseconds(mach_absolute_time()); diff --git a/src/util/postprocessing.cpp b/src/util/postprocessing.cpp index 02f208fc4..030a77f40 100644 --- a/src/util/postprocessing.cpp +++ b/src/util/postprocessing.cpp @@ -53,7 +53,7 @@ ALWAYS_INLINE void ForAllChains(const T& F) Chain DisplayChain(Config::DISPLAY_CHAIN_SECTION); Chain InternalChain(Config::INTERNAL_CHAIN_SECTION); -static Common::Timer s_timer; +static Timer s_timer; static std::unordered_map> s_samplers; static std::unique_ptr s_dummy_texture; @@ -782,7 +782,7 @@ SettingsInterface& PostProcessing::GetLoadSettingsInterface(const char* section) return *Host::Internal::GetBaseSettingsLayer(); } -const Common::Timer& PostProcessing::GetTimer() +const Timer& PostProcessing::GetTimer() { return s_timer; } diff --git a/src/util/postprocessing.h b/src/util/postprocessing.h index c3d9c18fa..11c9d9444 100644 --- a/src/util/postprocessing.h +++ b/src/util/postprocessing.h @@ -11,9 +11,7 @@ #include #include -namespace Common { class Timer; -} class GPUSampler; class GPUTexture; @@ -171,7 +169,7 @@ void Shutdown(); GPUSampler* GetSampler(const GPUSampler::Config& config); GPUTexture* GetDummyTexture(); -const Common::Timer& GetTimer(); +const Timer& GetTimer(); extern Chain DisplayChain; extern Chain InternalChain; diff --git a/src/util/postprocessing_shader_fx.h b/src/util/postprocessing_shader_fx.h index f966c7210..ecc2c09d2 100644 --- a/src/util/postprocessing_shader_fx.h +++ b/src/util/postprocessing_shader_fx.h @@ -144,7 +144,7 @@ private: bool m_valid = false; bool m_wants_depth_buffer = false; - Common::Timer m_frame_timer; + Timer m_frame_timer; u32 m_frame_count = 0; // Specifically using a fixed seed, so that it's consistent from run-to-run. From b059cda8d5592203befdc01281ac64ba97060b93 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 20:07:04 +1000 Subject: [PATCH 16/69] Achievements: Pack state in struct --- src/core/achievements.cpp | 692 ++++++++++++++++++++------------------ 1 file changed, 357 insertions(+), 335 deletions(-) diff --git a/src/core/achievements.cpp b/src/core/achievements.cpp index 61e3dbe5d..a214d4f37 100644 --- a/src/core/achievements.cpp +++ b/src/core/achievements.cpp @@ -196,62 +196,72 @@ static void DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& entry, boo float name_column_width, float time_column_width, float column_spacing); #endif -static bool s_hardcore_mode = false; +struct State +{ + rc_client_t* client = nullptr; + bool hardcore_mode = false; + bool has_achievements = false; + bool has_leaderboards = false; + bool has_rich_presence = false; #ifdef ENABLE_RAINTEGRATION -static bool s_using_raintegration = false; + bool using_raintegration = false; #endif -static std::recursive_mutex s_achievements_mutex; -static rc_client_t* s_client; -static std::unique_ptr s_http_downloader; + std::recursive_mutex mutex; // large -static std::string s_game_path; -static std::string s_game_hash; -static std::string s_game_title; -static std::string s_game_icon; -static std::string s_game_icon_url; -static rc_client_user_game_summary_t s_game_summary; -static u32 s_game_id = 0; -static DynamicHeapArray s_state_buffer; + std::string rich_presence_string; + Timer::Value rich_presence_poll_time = 0; -static bool s_has_achievements = false; -static bool s_has_leaderboards = false; -static bool s_has_rich_presence = false; -static std::string s_rich_presence_string; -static Timer s_rich_presence_poll_time; + std::vector active_leaderboard_trackers; + std::vector active_challenge_indicators; + std::optional active_progress_indicator; -static rc_client_async_handle_t* s_login_request; -static rc_client_async_handle_t* s_load_game_request; + rc_client_user_game_summary_t game_summary = {}; + u32 game_id = 0; -static rc_client_achievement_list_t* s_achievement_list; -static rc_client_leaderboard_list_t* s_leaderboard_list; -static std::vector> s_achievement_badge_paths; -static const rc_client_leaderboard_t* s_open_leaderboard = nullptr; -static rc_client_async_handle_t* s_leaderboard_fetch_handle = nullptr; -static std::vector s_leaderboard_entry_lists; -static rc_client_leaderboard_entry_list_t* s_leaderboard_nearby_entries; -static std::vector> s_leaderboard_user_icon_paths; -static bool s_is_showing_all_leaderboard_entries = false; + std::unique_ptr http_downloader; + + std::string game_path; + std::string game_hash; + std::string game_title; + std::string game_icon; + std::string game_icon_url; + + DynamicHeapArray state_buffer; + + rc_client_async_handle_t* login_request = nullptr; + rc_client_async_handle_t* load_game_request = nullptr; + + rc_client_achievement_list_t* achievement_list = nullptr; + std::vector> achievement_badge_paths; + + rc_client_leaderboard_list_t* leaderboard_list = nullptr; + const rc_client_leaderboard_t* open_leaderboard = nullptr; + rc_client_async_handle_t* leaderboard_fetch_handle = nullptr; + std::vector leaderboard_entry_lists; + std::vector> leaderboard_user_icon_paths; + rc_client_leaderboard_entry_list_t* leaderboard_nearby_entries; + bool is_showing_all_leaderboard_entries = false; +}; + +ALIGN_TO_CACHE_LINE static State s_state; -static std::vector s_active_leaderboard_trackers; -static std::vector s_active_challenge_indicators; -static std::optional s_active_progress_indicator; } // namespace Achievements std::unique_lock Achievements::GetLock() { - return std::unique_lock(s_achievements_mutex); + return std::unique_lock(s_state.mutex); } rc_client_t* Achievements::GetClient() { - return s_client; + return s_state.client; } const rc_client_user_game_summary_t& Achievements::GetGameSummary() { - return s_game_summary; + return s_state.game_summary; } void Achievements::ReportError(std::string_view sv) @@ -375,7 +385,7 @@ void Achievements::DownloadImage(std::string url, std::string cache_filename) ImGuiFullscreen::InvalidateCachedTexture(cache_filename); }; - s_http_downloader->CreateRequest(std::move(url), std::move(callback)); + s_state.http_downloader->CreateRequest(std::move(url), std::move(callback)); } void Achievements::UpdateGlyphRanges() @@ -397,7 +407,7 @@ void Achievements::UpdateGlyphRanges() } }; - if (rc_client_has_rich_presence(s_client)) + if (rc_client_has_rich_presence(s_state.client)) { std::vector rp_strings; for (;;) @@ -405,7 +415,7 @@ void Achievements::UpdateGlyphRanges() rp_strings.resize(std::max(rp_strings.size() * 2, 512)); size_t count; - const int err = rc_client_get_rich_presence_strings(s_client, rp_strings.data(), rp_strings.size(), &count); + const int err = rc_client_get_rich_presence_strings(s_state.client, rp_strings.data(), rp_strings.size(), &count); if (err == RC_INSUFFICIENT_BUFFER) continue; else if (err != RC_OK) @@ -420,10 +430,10 @@ void Achievements::UpdateGlyphRanges() add_string(str, codepoints); } - if (rc_client_has_achievements(s_client)) + if (rc_client_has_achievements(s_state.client)) { rc_client_achievement_list_t* const achievements = - rc_client_create_achievement_list(s_client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL, 0); + rc_client_create_achievement_list(s_state.client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL, 0); if (achievements) { for (u32 i = 0; i < achievements->num_buckets; i++) @@ -442,10 +452,10 @@ void Achievements::UpdateGlyphRanges() } } - if (rc_client_has_leaderboards(s_client)) + if (rc_client_has_leaderboards(s_state.client)) { rc_client_leaderboard_list_t* const leaderboards = - rc_client_create_leaderboard_list(s_client, RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE); + rc_client_create_leaderboard_list(s_state.client, RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE); if (leaderboards) { for (u32 i = 0; i < leaderboards->num_buckets; i++) @@ -476,9 +486,9 @@ void Achievements::UpdateGlyphRanges() bool Achievements::IsActive() { #ifdef ENABLE_RAINTEGRATION - return (s_client != nullptr) || s_using_raintegration; + return (s_state.client != nullptr) || s_state.using_raintegration; #else - return (s_client != nullptr); + return (s_state.client != nullptr); #endif } @@ -489,57 +499,57 @@ bool Achievements::IsHardcoreModeActive() return RA_HardcoreModeIsActive() != 0; #endif - return s_hardcore_mode; + return s_state.hardcore_mode; } bool Achievements::HasActiveGame() { - return s_game_id != 0; + return s_state.game_id != 0; } u32 Achievements::GetGameID() { - return s_game_id; + return s_state.game_id; } bool Achievements::HasAchievementsOrLeaderboards() { - return s_has_achievements || s_has_leaderboards; + return s_state.has_achievements || s_state.has_leaderboards; } bool Achievements::HasAchievements() { - return s_has_achievements; + return s_state.has_achievements; } bool Achievements::HasLeaderboards() { - return s_has_leaderboards; + return s_state.has_leaderboards; } bool Achievements::HasRichPresence() { - return s_has_rich_presence; + return s_state.has_rich_presence; } const std::string& Achievements::GetGameTitle() { - return s_game_title; + return s_state.game_title; } const std::string& Achievements::GetGameIconPath() { - return s_game_icon; + return s_state.game_icon; } const std::string& Achievements::GetGameIconURL() { - return s_game_icon_url; + return s_state.game_icon_url; } const std::string& Achievements::GetRichPresenceString() { - return s_rich_presence_string; + return s_state.rich_presence_string; } bool Achievements::Initialize() @@ -549,20 +559,20 @@ bool Achievements::Initialize() auto lock = GetLock(); AssertMsg(g_settings.achievements_enabled, "Achievements are enabled"); - Assert(!s_client && !s_http_downloader); + Assert(!s_state.client && !s_state.http_downloader); - if (!CreateClient(&s_client, &s_http_downloader)) + if (!CreateClient(&s_state.client, &s_state.http_downloader)) return false; // Hardcore starts off. We enable it on first boot. - s_hardcore_mode = false; + s_state.hardcore_mode = false; - rc_client_set_event_handler(s_client, ClientEventHandler); + rc_client_set_event_handler(s_state.client, ClientEventHandler); - rc_client_set_hardcore_enabled(s_client, s_hardcore_mode); - rc_client_set_encore_mode_enabled(s_client, g_settings.achievements_encore_mode); - rc_client_set_unofficial_enabled(s_client, g_settings.achievements_unofficial_test_mode); - rc_client_set_spectator_mode_enabled(s_client, g_settings.achievements_spectator_mode); + rc_client_set_hardcore_enabled(s_state.client, s_state.hardcore_mode); + rc_client_set_encore_mode_enabled(s_state.client, g_settings.achievements_encore_mode); + rc_client_set_unofficial_enabled(s_state.client, g_settings.achievements_unofficial_test_mode); + rc_client_set_spectator_mode_enabled(s_state.client, g_settings.achievements_spectator_mode); // Begin disc identification early, before the login finishes. if (System::IsValid()) @@ -577,8 +587,8 @@ bool Achievements::Initialize() // If we can't decrypt the token, it was an old config and we need to re-login. if (const TinyString decrypted_api_token = DecryptLoginToken(api_token, username); !decrypted_api_token.empty()) { - s_login_request = rc_client_begin_login_with_token(s_client, username.c_str(), decrypted_api_token.c_str(), - ClientLoginWithTokenCallback, nullptr); + s_state.login_request = rc_client_begin_login_with_token( + s_state.client, username.c_str(), decrypted_api_token.c_str(), ClientLoginWithTokenCallback, nullptr); } else { @@ -658,11 +668,11 @@ void Achievements::UpdateSettings(const Settings& old_config) if (g_settings.achievements_hardcore_mode != old_config.achievements_hardcore_mode) { // Hardcore mode can only be enabled through reset (ResetChallengeMode()). - if (s_hardcore_mode && !g_settings.achievements_hardcore_mode) + if (s_state.hardcore_mode && !g_settings.achievements_hardcore_mode) { ResetHardcoreMode(false); } - else if (!s_hardcore_mode && g_settings.achievements_hardcore_mode) + else if (!s_state.hardcore_mode && g_settings.achievements_hardcore_mode) { if (HasActiveGame()) DisplayHardcoreDeferredMessage(); @@ -684,11 +694,11 @@ void Achievements::UpdateSettings(const Settings& old_config) else { if (g_settings.achievements_encore_mode != old_config.achievements_encore_mode) - rc_client_set_encore_mode_enabled(s_client, g_settings.achievements_encore_mode); + rc_client_set_encore_mode_enabled(s_state.client, g_settings.achievements_encore_mode); if (g_settings.achievements_spectator_mode != old_config.achievements_spectator_mode) - rc_client_set_spectator_mode_enabled(s_client, g_settings.achievements_spectator_mode); + rc_client_set_spectator_mode_enabled(s_state.client, g_settings.achievements_spectator_mode); if (g_settings.achievements_unofficial_test_mode != old_config.achievements_unofficial_test_mode) - rc_client_set_unofficial_enabled(s_client, g_settings.achievements_unofficial_test_mode); + rc_client_set_unofficial_enabled(s_state.client, g_settings.achievements_unofficial_test_mode); } } @@ -710,26 +720,26 @@ bool Achievements::Shutdown(bool allow_cancel) return true; auto lock = GetLock(); - Assert(s_client && s_http_downloader); + Assert(s_state.client && s_state.http_downloader); ClearGameInfo(); ClearGameHash(); DisableHardcoreMode(); UpdateGlyphRanges(); - if (s_load_game_request) + if (s_state.load_game_request) { - rc_client_abort_async(s_client, s_load_game_request); - s_load_game_request = nullptr; + rc_client_abort_async(s_state.client, s_state.load_game_request); + s_state.load_game_request = nullptr; } - if (s_login_request) + if (s_state.login_request) { - rc_client_abort_async(s_client, s_login_request); - s_login_request = nullptr; + rc_client_abort_async(s_state.client, s_state.login_request); + s_state.login_request = nullptr; } - s_hardcore_mode = false; - DestroyClient(&s_client, &s_http_downloader); + s_state.hardcore_mode = false; + DestroyClient(&s_state.client, &s_state.http_downloader); Host::OnAchievementsRefreshed(); return true; @@ -814,8 +824,8 @@ void Achievements::IdleUpdate() const auto lock = GetLock(); - s_http_downloader->PollRequests(); - rc_client_idle(s_client); + s_state.http_downloader->PollRequests(); + rc_client_idle(s_state.client); } bool Achievements::NeedsIdleUpdate() @@ -824,7 +834,7 @@ bool Achievements::NeedsIdleUpdate() return false; const auto lock = GetLock(); - return (s_http_downloader && s_http_downloader->HasAnyRequests()); + return (s_state.http_downloader && s_state.http_downloader->HasAnyRequests()); } void Achievements::FrameUpdate() @@ -842,8 +852,8 @@ void Achievements::FrameUpdate() auto lock = GetLock(); - s_http_downloader->PollRequests(); - rc_client_do_frame(s_client); + s_state.http_downloader->PollRequests(); + rc_client_do_frame(s_state.client); UpdateRichPresence(lock); } @@ -932,24 +942,30 @@ void Achievements::ClientEventHandler(const rc_client_event_t* event, rc_client_ void Achievements::UpdateGameSummary() { - rc_client_get_user_game_summary(s_client, &s_game_summary); + rc_client_get_user_game_summary(s_state.client, &s_state.game_summary); } void Achievements::UpdateRichPresence(std::unique_lock& lock) { // Limit rich presence updates to once per second, since it could change per frame. - if (!s_has_rich_presence || !s_rich_presence_poll_time.ResetIfSecondsPassed(1.0)) + if (!s_state.has_rich_presence) return; + const Timer::Value now = Timer::GetCurrentValue(); + if (Timer::ConvertValueToSeconds(now - s_state.rich_presence_poll_time) < 1) + return; + + s_state.rich_presence_poll_time = now; + char buffer[512]; - const size_t res = rc_client_get_rich_presence_message(s_client, buffer, std::size(buffer)); + const size_t res = rc_client_get_rich_presence_message(s_state.client, buffer, std::size(buffer)); const std::string_view sv(buffer, res); - if (s_rich_presence_string == sv) + if (s_state.rich_presence_string == sv) return; - s_rich_presence_string.assign(sv); + s_state.rich_presence_string.assign(sv); - INFO_LOG("Rich presence updated: {}", s_rich_presence_string); + INFO_LOG("Rich presence updated: {}", s_state.rich_presence_string); Host::OnAchievementsRefreshed(); lock.unlock(); @@ -959,7 +975,7 @@ void Achievements::UpdateRichPresence(std::unique_lock& lo void Achievements::GameChanged(const std::string& path, CDImage* image) { - std::unique_lock lock(s_achievements_mutex); + std::unique_lock lock(s_state.mutex); if (!IsActive()) return; @@ -969,7 +985,7 @@ void Achievements::GameChanged(const std::string& path, CDImage* image) void Achievements::IdentifyGame(const std::string& path, CDImage* image) { - if (s_game_path == path) + if (s_state.game_path == path) { WARNING_LOG("Game path is unchanged."); return; @@ -988,18 +1004,18 @@ void Achievements::IdentifyGame(const std::string& path, CDImage* image) if (image) game_hash = GetGameHash(image); - if (s_game_hash == game_hash) + if (s_state.game_hash == game_hash) { // only the path has changed - different format/save state/etc. - INFO_LOG("Detected path change from '{}' to '{}'", s_game_path, path); - s_game_path = path; + INFO_LOG("Detected path change from '{}' to '{}'", s_state.game_path, path); + s_state.game_path = path; return; } ClearGameHash(); - s_game_path = path; - s_game_hash = std::move(game_hash); - s_state_buffer.deallocate(); + s_state.game_path = path; + s_state.game_hash = std::move(game_hash); + s_state.state_buffer.deallocate(); #ifdef ENABLE_RAINTEGRATION if (IsUsingRAIntegration()) @@ -1010,7 +1026,7 @@ void Achievements::IdentifyGame(const std::string& path, CDImage* image) #endif // shouldn't have a load game request when we're not logged in. - Assert(IsLoggedInOrLoggingIn() || !s_load_game_request); + Assert(IsLoggedInOrLoggingIn() || !s_state.load_game_request); // bail out if we're not logged in, just save the hash if (!IsLoggedInOrLoggingIn()) @@ -1020,7 +1036,7 @@ void Achievements::IdentifyGame(const std::string& path, CDImage* image) return; } - if (!rc_client_is_game_loaded(s_client)) + if (!rc_client_is_game_loaded(s_state.client)) BeginLoadGame(); else BeginChangeDisc(); @@ -1030,10 +1046,10 @@ void Achievements::BeginLoadGame() { ClearGameInfo(); - if (s_game_hash.empty()) + if (s_state.game_hash.empty()) { // when we're booting the bios, this will fail - if (!s_game_path.empty()) + if (!s_state.game_path.empty()) { Host::AddKeyedOSDMessage( "retroachievements_disc_read_failed", @@ -1046,22 +1062,23 @@ void Achievements::BeginLoadGame() return; } - s_load_game_request = rc_client_begin_load_game(s_client, s_game_hash.c_str(), ClientLoadGameCallback, nullptr); + s_state.load_game_request = + rc_client_begin_load_game(s_state.client, s_state.game_hash.c_str(), ClientLoadGameCallback, nullptr); } void Achievements::BeginChangeDisc() { // cancel previous requests - if (s_load_game_request) + if (s_state.load_game_request) { - rc_client_abort_async(s_client, s_load_game_request); - s_load_game_request = nullptr; + rc_client_abort_async(s_state.client, s_state.load_game_request); + s_state.load_game_request = nullptr; } - if (s_game_hash.empty()) + if (s_state.game_hash.empty()) { // when we're booting the bios, this will fail - if (!s_game_path.empty()) + if (!s_state.game_path.empty()) { Host::AddKeyedOSDMessage( "retroachievements_disc_read_failed", @@ -1075,21 +1092,22 @@ void Achievements::BeginChangeDisc() return; } - s_load_game_request = rc_client_begin_change_media_from_hash(s_client, s_game_hash.c_str(), ClientLoadGameCallback, - reinterpret_cast(static_cast(1))); + s_state.load_game_request = + rc_client_begin_change_media_from_hash(s_state.client, s_state.game_hash.c_str(), ClientLoadGameCallback, + reinterpret_cast(static_cast(1))); } void Achievements::ClientLoadGameCallback(int result, const char* error_message, rc_client_t* client, void* userdata) { const bool was_disc_change = (userdata != nullptr); - s_load_game_request = nullptr; - s_state_buffer.deallocate(); + s_state.load_game_request = nullptr; + s_state.state_buffer.deallocate(); if (result == RC_NO_GAME_LOADED) { // Unknown game. - INFO_LOG("Unknown game '{}', disabling achievements.", s_game_hash); + INFO_LOG("Unknown game '{}', disabling achievements.", s_state.game_hash); if (was_disc_change) { ClearGameInfo(); @@ -1125,7 +1143,7 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message, DisableHardcoreMode(); } - const rc_client_game_t* info = rc_client_get_game_info(s_client); + const rc_client_game_t* info = rc_client_get_game_info(s_state.client); if (!info) { ReportError("rc_client_get_game_info() returned NULL"); @@ -1143,7 +1161,7 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message, const bool has_leaderboards = rc_client_has_leaderboards(client); // Only display summary if the game title has changed across discs. - const bool display_summary = (s_game_id != info->id || s_game_title != info->title); + const bool display_summary = (s_state.game_id != info->id || s_state.game_title != info->title); // If the game has a RetroAchievements entry but no achievements or leaderboards, // enforcing hardcore mode is pointless. @@ -1151,13 +1169,13 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message, DisableHardcoreMode(); // We should have matched hardcore mode state. - Assert(s_hardcore_mode == (rc_client_get_hardcore_enabled(client) != 0)); + Assert(s_state.hardcore_mode == (rc_client_get_hardcore_enabled(client) != 0)); - s_game_id = info->id; - s_game_title = info->title; - s_has_achievements = has_achievements; - s_has_leaderboards = has_leaderboards; - s_has_rich_presence = rc_client_has_rich_presence(client); + s_state.game_id = info->id; + s_state.game_title = info->title; + s_state.has_achievements = has_achievements; + s_state.has_leaderboards = has_leaderboards; + s_state.has_rich_presence = rc_client_has_rich_presence(client); // update ranges before initializing fsui UpdateGlyphRanges(); @@ -1168,13 +1186,14 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message, char url_buf[URL_BUFFER_SIZE]; if (int err = rc_client_game_get_image_url(info, url_buf, std::size(url_buf)); err == RC_OK) - s_game_icon_url = url_buf; + s_state.game_icon_url = url_buf; else ReportRCError(err, "rc_client_game_get_image_url() failed: "); - s_game_icon = GetLocalImagePath(info->badge_name, RC_IMAGE_TYPE_GAME); - if (!s_game_icon.empty() && !s_game_icon_url.empty() && !FileSystem::FileExists(s_game_icon.c_str())) - DownloadImage(s_game_icon_url, s_game_icon); + s_state.game_icon = GetLocalImagePath(info->badge_name, RC_IMAGE_TYPE_GAME); + if (!s_state.game_icon.empty() && !s_state.game_icon_url.empty() && + !FileSystem::FileExists(s_state.game_icon.c_str())) + DownloadImage(s_state.game_icon_url, s_state.game_icon); UpdateGameSummary(); if (display_summary) @@ -1187,34 +1206,34 @@ void Achievements::ClearGameInfo() { ClearUIState(); - if (s_load_game_request) + if (s_state.load_game_request) { - rc_client_abort_async(s_client, s_load_game_request); - s_load_game_request = nullptr; + rc_client_abort_async(s_state.client, s_state.load_game_request); + s_state.load_game_request = nullptr; } - rc_client_unload_game(s_client); + rc_client_unload_game(s_state.client); - s_active_leaderboard_trackers = {}; - s_active_challenge_indicators = {}; - s_active_progress_indicator.reset(); - s_game_id = 0; - s_game_title = {}; - s_game_icon = {}; - s_game_icon_url = {}; - s_state_buffer.deallocate(); - s_has_achievements = false; - s_has_leaderboards = false; - s_has_rich_presence = false; - s_rich_presence_string = {}; - s_game_summary = {}; + s_state.active_leaderboard_trackers = {}; + s_state.active_challenge_indicators = {}; + s_state.active_progress_indicator.reset(); + s_state.game_id = 0; + s_state.game_title = {}; + s_state.game_icon = {}; + s_state.game_icon_url = {}; + s_state.state_buffer.deallocate(); + s_state.has_achievements = false; + s_state.has_leaderboards = false; + s_state.has_rich_presence = false; + s_state.rich_presence_string = {}; + s_state.game_summary = {}; Host::OnAchievementsRefreshed(); } void Achievements::ClearGameHash() { - s_game_path = {}; - std::string().swap(s_game_hash); + s_state.game_path = {}; + std::string().swap(s_state.game_hash); } void Achievements::DisplayAchievementSummary() @@ -1223,21 +1242,21 @@ void Achievements::DisplayAchievementSummary() { std::string title; if (IsHardcoreModeActive()) - title = fmt::format(TRANSLATE_FS("Achievements", "{} (Hardcore Mode)"), s_game_title); + title = fmt::format(TRANSLATE_FS("Achievements", "{} (Hardcore Mode)"), s_state.game_title); else - title = s_game_title; + title = s_state.game_title; std::string summary; - if (s_game_summary.num_core_achievements > 0) + if (s_state.game_summary.num_core_achievements > 0) { summary = fmt::format( TRANSLATE_FS("Achievements", "{0}, {1}."), SmallString::from_format(TRANSLATE_PLURAL_FS("Achievements", "You have unlocked {} of %n achievements", - "Achievement popup", s_game_summary.num_core_achievements), - s_game_summary.num_unlocked_achievements), + "Achievement popup", s_state.game_summary.num_core_achievements), + s_state.game_summary.num_unlocked_achievements), SmallString::from_format(TRANSLATE_PLURAL_FS("Achievements", "and earned {} of %n points", "Achievement popup", - s_game_summary.points_core), - s_game_summary.points_unlocked)); + s_state.game_summary.points_core), + s_state.game_summary.points_unlocked)); } else { @@ -1245,7 +1264,7 @@ void Achievements::DisplayAchievementSummary() } ImGuiFullscreen::AddNotification("achievement_summary", ACHIEVEMENT_SUMMARY_NOTIFICATION_TIME, std::move(title), - std::move(summary), s_game_icon); + std::move(summary), s_state.game_icon); } // Technically not going through the resource API, but since we're passing this to something else, we can't. @@ -1255,7 +1274,8 @@ void Achievements::DisplayAchievementSummary() void Achievements::DisplayHardcoreDeferredMessage() { - if (g_settings.achievements_hardcore_mode && !s_hardcore_mode && System::IsValid() && FullscreenUI::Initialize()) + if (g_settings.achievements_hardcore_mode && !s_state.hardcore_mode && System::IsValid() && + FullscreenUI::Initialize()) { ImGuiFullscreen::ShowToast(std::string(), TRANSLATE_STR("Achievements", "Hardcore mode will be enabled on system reset."), @@ -1267,7 +1287,7 @@ void Achievements::HandleResetEvent(const rc_client_event_t* event) { // We handle system resets ourselves, but still need to reset the client's state. INFO_LOG("Resetting runtime due to reset event"); - rc_client_reset(s_client); + rc_client_reset(s_state.client); if (HasActiveGame()) UpdateGameSummary(); @@ -1278,7 +1298,7 @@ void Achievements::HandleUnlockEvent(const rc_client_event_t* event) const rc_client_achievement_t* cheevo = event->achievement; DebugAssert(cheevo); - INFO_LOG("Achievement {} ({}) for game {} unlocked", cheevo->title, cheevo->id, s_game_id); + INFO_LOG("Achievement {} ({}) for game {} unlocked", cheevo->title, cheevo->id, s_state.game_id); UpdateGameSummary(); if (g_settings.achievements_notifications && FullscreenUI::Initialize()) @@ -1302,20 +1322,20 @@ void Achievements::HandleUnlockEvent(const rc_client_event_t* event) void Achievements::HandleGameCompleteEvent(const rc_client_event_t* event) { - INFO_LOG("Game {} complete", s_game_id); + INFO_LOG("Game {} complete", s_state.game_id); UpdateGameSummary(); if (g_settings.achievements_notifications && FullscreenUI::Initialize()) { - std::string title = fmt::format(TRANSLATE_FS("Achievements", "Mastered {}"), s_game_title); + std::string title = fmt::format(TRANSLATE_FS("Achievements", "Mastered {}"), s_state.game_title); std::string message = fmt::format( TRANSLATE_FS("Achievements", "{0}, {1}"), TRANSLATE_PLURAL_STR("Achievements", "%n achievements", "Mastery popup", - s_game_summary.num_unlocked_achievements), - TRANSLATE_PLURAL_STR("Achievements", "%n points", "Achievement points", s_game_summary.points_unlocked)); + s_state.game_summary.num_unlocked_achievements), + TRANSLATE_PLURAL_STR("Achievements", "%n points", "Achievement points", s_state.game_summary.points_unlocked)); ImGuiFullscreen::AddNotification("achievement_mastery", GAME_COMPLETE_NOTIFICATION_TIME, std::move(title), - std::move(message), s_game_icon); + std::move(message), s_state.game_icon); } } @@ -1330,7 +1350,7 @@ void Achievements::HandleLeaderboardStartedEvent(const rc_client_event_t* event) ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id), LEADERBOARD_STARTED_NOTIFICATION_TIME, std::move(title), std::move(message), - s_game_icon); + s_state.game_icon); } } @@ -1345,7 +1365,7 @@ void Achievements::HandleLeaderboardFailedEvent(const rc_client_event_t* event) ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id), LEADERBOARD_FAILED_NOTIFICATION_TIME, std::move(title), std::move(message), - s_game_icon); + s_state.game_icon); } } @@ -1371,7 +1391,7 @@ void Achievements::HandleLeaderboardSubmittedEvent(const rc_client_event_t* even ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id), static_cast(g_settings.achievements_leaderboard_duration), std::move(title), - std::move(message), s_game_icon); + std::move(message), s_state.game_icon); } if (g_settings.achievements_sound_effects) @@ -1402,7 +1422,7 @@ void Achievements::HandleLeaderboardScoreboardEvent(const rc_client_event_t* eve ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id), static_cast(g_settings.achievements_leaderboard_duration), std::move(title), - std::move(message), s_game_icon); + std::move(message), s_state.game_icon); } } @@ -1420,15 +1440,15 @@ void Achievements::HandleLeaderboardTrackerShowEvent(const rc_client_event_t* ev indicator.tracker_id = event->leaderboard_tracker->id; indicator.text = event->leaderboard_tracker->display; indicator.active = true; - s_active_leaderboard_trackers.push_back(std::move(indicator)); + s_state.active_leaderboard_trackers.push_back(std::move(indicator)); } void Achievements::HandleLeaderboardTrackerHideEvent(const rc_client_event_t* event) { const u32 id = event->leaderboard_tracker->id; - auto it = std::find_if(s_active_leaderboard_trackers.begin(), s_active_leaderboard_trackers.end(), + auto it = std::find_if(s_state.active_leaderboard_trackers.begin(), s_state.active_leaderboard_trackers.end(), [id](const auto& it) { return it.tracker_id == id; }); - if (it == s_active_leaderboard_trackers.end()) + if (it == s_state.active_leaderboard_trackers.end()) return; DEV_LOG("Hiding leaderboard tracker: {}", id); @@ -1439,9 +1459,9 @@ void Achievements::HandleLeaderboardTrackerHideEvent(const rc_client_event_t* ev void Achievements::HandleLeaderboardTrackerUpdateEvent(const rc_client_event_t* event) { const u32 id = event->leaderboard_tracker->id; - auto it = std::find_if(s_active_leaderboard_trackers.begin(), s_active_leaderboard_trackers.end(), + auto it = std::find_if(s_state.active_leaderboard_trackers.begin(), s_state.active_leaderboard_trackers.end(), [id](const auto& it) { return it.tracker_id == id; }); - if (it == s_active_leaderboard_trackers.end()) + if (it == s_state.active_leaderboard_trackers.end()) return; DEV_LOG("Updating leaderboard tracker: {}: {}", event->leaderboard_tracker->id, event->leaderboard_tracker->display); @@ -1453,9 +1473,9 @@ void Achievements::HandleLeaderboardTrackerUpdateEvent(const rc_client_event_t* void Achievements::HandleAchievementChallengeIndicatorShowEvent(const rc_client_event_t* event) { if (auto it = - std::find_if(s_active_challenge_indicators.begin(), s_active_challenge_indicators.end(), + std::find_if(s_state.active_challenge_indicators.begin(), s_state.active_challenge_indicators.end(), [event](const AchievementChallengeIndicator& it) { return it.achievement == event->achievement; }); - it != s_active_challenge_indicators.end()) + it != s_state.active_challenge_indicators.end()) { it->show_hide_time.Reset(); it->active = true; @@ -1466,7 +1486,7 @@ void Achievements::HandleAchievementChallengeIndicatorShowEvent(const rc_client_ indicator.achievement = event->achievement; indicator.badge_path = GetAchievementBadgePath(event->achievement, RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED); indicator.active = true; - s_active_challenge_indicators.push_back(std::move(indicator)); + s_state.active_challenge_indicators.push_back(std::move(indicator)); DEV_LOG("Show challenge indicator for {} ({})", event->achievement->id, event->achievement->title); } @@ -1474,9 +1494,9 @@ void Achievements::HandleAchievementChallengeIndicatorShowEvent(const rc_client_ void Achievements::HandleAchievementChallengeIndicatorHideEvent(const rc_client_event_t* event) { auto it = - std::find_if(s_active_challenge_indicators.begin(), s_active_challenge_indicators.end(), + std::find_if(s_state.active_challenge_indicators.begin(), s_state.active_challenge_indicators.end(), [event](const AchievementChallengeIndicator& it) { return it.achievement == event->achievement; }); - if (it == s_active_challenge_indicators.end()) + if (it == s_state.active_challenge_indicators.end()) return; DEV_LOG("Hide challenge indicator for {} ({})", event->achievement->id, event->achievement->title); @@ -1489,33 +1509,33 @@ void Achievements::HandleAchievementProgressIndicatorShowEvent(const rc_client_e DEV_LOG("Showing progress indicator: {} ({}): {}", event->achievement->id, event->achievement->title, event->achievement->measured_progress); - if (!s_active_progress_indicator.has_value()) - s_active_progress_indicator.emplace(); + if (!s_state.active_progress_indicator.has_value()) + s_state.active_progress_indicator.emplace(); else - s_active_progress_indicator->show_hide_time.Reset(); + s_state.active_progress_indicator->show_hide_time.Reset(); - s_active_progress_indicator->achievement = event->achievement; - s_active_progress_indicator->badge_path = + s_state.active_progress_indicator->achievement = event->achievement; + s_state.active_progress_indicator->badge_path = GetAchievementBadgePath(event->achievement, RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED); - s_active_progress_indicator->active = true; + s_state.active_progress_indicator->active = true; } void Achievements::HandleAchievementProgressIndicatorHideEvent(const rc_client_event_t* event) { - if (!s_active_progress_indicator.has_value()) + if (!s_state.active_progress_indicator.has_value()) return; DEV_LOG("Hiding progress indicator"); - s_active_progress_indicator->show_hide_time.Reset(); - s_active_progress_indicator->active = false; + s_state.active_progress_indicator->show_hide_time.Reset(); + s_state.active_progress_indicator->active = false; } void Achievements::HandleAchievementProgressIndicatorUpdateEvent(const rc_client_event_t* event) { DEV_LOG("Updating progress indicator: {} ({}): {}", event->achievement->id, event->achievement->title, event->achievement->measured_progress); - s_active_progress_indicator->achievement = event->achievement; - s_active_progress_indicator->active = true; + s_state.active_progress_indicator->achievement = event->achievement; + s_state.active_progress_indicator->active = true; } void Achievements::HandleServerErrorEvent(const rc_client_event_t* event) @@ -1568,7 +1588,7 @@ void Achievements::ResetClient() return; DEV_LOG("Reset client"); - rc_client_reset(s_client); + rc_client_reset(s_state.client); } void Achievements::OnSystemPaused(bool paused) @@ -1594,7 +1614,7 @@ void Achievements::DisableHardcoreMode() } #endif - if (!s_hardcore_mode) + if (!s_state.hardcore_mode) return; SetHardcoreMode(false, true); @@ -1610,8 +1630,8 @@ bool Achievements::ResetHardcoreMode(bool is_booting) // If we're not logged in, don't apply hardcore mode restrictions. // If we later log in, we'll start with it off anyway. const bool wanted_hardcore_mode = - (IsLoggedInOrLoggingIn() || s_load_game_request) && g_settings.achievements_hardcore_mode; - if (s_hardcore_mode == wanted_hardcore_mode) + (IsLoggedInOrLoggingIn() || s_state.load_game_request) && g_settings.achievements_hardcore_mode; + if (s_state.hardcore_mode == wanted_hardcore_mode) return false; if (!is_booting && wanted_hardcore_mode && !CanEnableHardcoreMode()) @@ -1623,11 +1643,11 @@ bool Achievements::ResetHardcoreMode(bool is_booting) void Achievements::SetHardcoreMode(bool enabled, bool force_display_message) { - if (enabled == s_hardcore_mode) + if (enabled == s_state.hardcore_mode) return; // new mode - s_hardcore_mode = enabled; + s_state.hardcore_mode = enabled; if (System::IsValid() && (HasActiveGame() || force_display_message) && FullscreenUI::Initialize()) { @@ -1637,8 +1657,8 @@ void Achievements::SetHardcoreMode(bool enabled, bool force_display_message) Host::OSD_INFO_DURATION); } - rc_client_set_hardcore_enabled(s_client, enabled); - DebugAssert((rc_client_get_hardcore_enabled(s_client) != 0) == enabled); + rc_client_set_hardcore_enabled(s_state.client, enabled); + DebugAssert((rc_client_get_hardcore_enabled(s_state.client) != 0) == enabled); if (HasActiveGame()) { UpdateGameSummary(); @@ -1668,16 +1688,16 @@ bool Achievements::DoState(StateWrapper& sw) return !sw.HasError(); } - std::unique_lock lock(s_achievements_mutex); + std::unique_lock lock(s_state.mutex); if (sw.IsReading()) { // if we're active, make sure we've downloaded and activated all the achievements // before deserializing, otherwise that state's going to get lost. - if (!IsUsingRAIntegration() && s_load_game_request) + if (!IsUsingRAIntegration() && s_state.load_game_request) { Host::DisplayLoadingScreen("Downloading achievements data..."); - s_http_downloader->WaitForAllRequests(); + s_state.http_downloader->WaitForAllRequests(); } u32 data_size = 0; @@ -1690,33 +1710,33 @@ bool Achievements::DoState(StateWrapper& sw) if (IsUsingRAIntegration()) RA_OnReset(); else - rc_client_reset(s_client); + rc_client_reset(s_state.client); #else - rc_client_reset(s_client); + rc_client_reset(s_state.client); #endif return !sw.HasError(); } - if (data_size > s_state_buffer.size()) - s_state_buffer.resize(data_size); + if (data_size > s_state.state_buffer.size()) + s_state.state_buffer.resize(data_size); if (data_size > 0) - sw.DoBytes(s_state_buffer.data(), data_size); + sw.DoBytes(s_state.state_buffer.data(), data_size); if (sw.HasError()) return false; #ifdef ENABLE_RAINTEGRATION if (IsUsingRAIntegration()) { - RA_RestoreState(reinterpret_cast(s_state_buffer.data())); + RA_RestoreState(reinterpret_cast(s_state.state_buffer.data())); } else { - const int result = rc_client_deserialize_progress_sized(s_client, s_state_buffer.data(), data_size); + const int result = rc_client_deserialize_progress_sized(s_state.client, s_state.state_buffer.data(), data_size); if (result != RC_OK) { WARNING_LOG("Failed to deserialize cheevos state ({}), resetting", result); - rc_client_reset(s_client); + rc_client_reset(s_state.client); } } #endif @@ -1733,11 +1753,12 @@ bool Achievements::DoState(StateWrapper& sw) const int size = RA_CaptureState(nullptr, 0); data_size = (size >= 0) ? static_cast(size) : 0; - s_state_buffer.resize(data_size); + s_state.state_buffer.resize(data_size); if (data_size > 0) { - const int result = RA_CaptureState(reinterpret_cast(s_state_buffer.data()), static_cast(data_size)); + const int result = + RA_CaptureState(reinterpret_cast(s_state.state_buffer.data()), static_cast(data_size)); if (result != static_cast(data_size)) { WARNING_LOG("Failed to serialize cheevos state from RAIntegration."); @@ -1748,13 +1769,13 @@ bool Achievements::DoState(StateWrapper& sw) else #endif { - data_size = rc_client_progress_size(s_client); + data_size = rc_client_progress_size(s_state.client); if (data_size > 0) { - if (s_state_buffer.size() < data_size) - s_state_buffer.resize(data_size); + if (s_state.state_buffer.size() < data_size) + s_state.state_buffer.resize(data_size); - const int result = rc_client_serialize_progress_sized(s_client, s_state_buffer.data(), data_size); + const int result = rc_client_serialize_progress_sized(s_state.client, s_state.state_buffer.data(), data_size); if (result != RC_OK) { // set data to zero, effectively serializing nothing @@ -1766,7 +1787,7 @@ bool Achievements::DoState(StateWrapper& sw) sw.Do(&data_size); if (data_size > 0) - sw.DoBytes(s_state_buffer.data(), data_size); + sw.DoBytes(s_state.state_buffer.data(), data_size); return !sw.HasError(); } @@ -1811,12 +1832,12 @@ std::string Achievements::GetLeaderboardUserBadgePath(const rc_client_leaderboar bool Achievements::IsLoggedInOrLoggingIn() { - return (rc_client_get_user_info(s_client) != nullptr || s_login_request); + return (rc_client_get_user_info(s_state.client) != nullptr || s_state.login_request); } bool Achievements::CanEnableHardcoreMode() { - return (s_load_game_request || s_has_achievements || s_has_leaderboards); + return (s_state.load_game_request || s_state.has_achievements || s_state.has_leaderboards); } bool Achievements::Login(const char* username, const char* password, Error* error) @@ -1824,8 +1845,8 @@ bool Achievements::Login(const char* username, const char* password, Error* erro auto lock = GetLock(); // We need to use a temporary client if achievements aren't currently active. - rc_client_t* client = s_client; - HTTPDownloader* http = s_http_downloader.get(); + rc_client_t* client = s_state.client; + HTTPDownloader* http = s_state.http_downloader.get(); const bool is_temporary_client = (client == nullptr); std::unique_ptr temporary_downloader; ScopedGuard temporary_client_guard = [&client, is_temporary_client, &temporary_downloader]() { @@ -1908,7 +1929,7 @@ void Achievements::ClientLoginWithPasswordCallback(int result, const char* error void Achievements::ClientLoginWithTokenCallback(int result, const char* error_message, rc_client_t* client, void* userdata) { - s_login_request = nullptr; + s_state.login_request = nullptr; if (result != RC_OK) { @@ -1934,14 +1955,14 @@ void Achievements::ShowLoginSuccess(const rc_client_t* client) if (System::IsValid()) { const auto lock = GetLock(); - if (s_client == client) + if (s_state.client == client) Host::RunOnCPUThread(ShowLoginNotification); } } void Achievements::ShowLoginNotification() { - const rc_client_user_t* user = rc_client_get_user_info(s_client); + const rc_client_user_t* user = rc_client_get_user_info(s_state.client); if (!user) return; @@ -1961,7 +1982,7 @@ void Achievements::ShowLoginNotification() const char* Achievements::GetLoggedInUserName() { - const rc_client_user_t* user = rc_client_get_user_info(s_client); + const rc_client_user_t* user = rc_client_get_user_info(s_state.client); if (!user) [[unlikely]] return nullptr; @@ -1972,7 +1993,7 @@ std::string Achievements::GetLoggedInUserBadgePath() { std::string badge_path; - const rc_client_user_t* user = rc_client_get_user_info(s_client); + const rc_client_user_t* user = rc_client_get_user_info(s_state.client); if (!user) [[unlikely]] return badge_path; @@ -1996,7 +2017,7 @@ u32 Achievements::GetPauseThrottleFrames() return 0; u32 frames_remaining = 0; - return rc_client_can_pause(s_client, &frames_remaining) ? 0 : frames_remaining; + return rc_client_can_pause(s_state.client, &frames_remaining) ? 0 : frames_remaining; } void Achievements::Logout() @@ -2012,7 +2033,7 @@ void Achievements::Logout() } INFO_LOG("Logging out..."); - rc_client_logout(s_client); + rc_client_logout(s_state.client); } INFO_LOG("Clearing credentials..."); @@ -2104,20 +2125,20 @@ void Achievements::ClearUIState() CloseLeaderboard(); #endif - s_achievement_badge_paths = {}; + s_state.achievement_badge_paths = {}; - s_leaderboard_user_icon_paths = {}; - s_leaderboard_entry_lists = {}; - if (s_leaderboard_list) + s_state.leaderboard_user_icon_paths = {}; + s_state.leaderboard_entry_lists = {}; + if (s_state.leaderboard_list) { - rc_client_destroy_leaderboard_list(s_leaderboard_list); - s_leaderboard_list = nullptr; + rc_client_destroy_leaderboard_list(s_state.leaderboard_list); + s_state.leaderboard_list = nullptr; } - if (s_achievement_list) + if (s_state.achievement_list) { - rc_client_destroy_achievement_list(s_achievement_list); - s_achievement_list = nullptr; + rc_client_destroy_achievement_list(s_state.achievement_list); + s_state.achievement_list = nullptr; } } @@ -2148,12 +2169,12 @@ void Achievements::DrawGameOverlays() ImVec2 position = ImVec2(io.DisplaySize.x - padding, io.DisplaySize.y - padding); ImDrawList* dl = ImGui::GetBackgroundDrawList(); - if (!s_active_challenge_indicators.empty()) + if (!s_state.active_challenge_indicators.empty()) { const float x_advance = image_size.x + spacing; ImVec2 current_position = ImVec2(position.x - image_size.x, position.y - image_size.y); - for (auto it = s_active_challenge_indicators.begin(); it != s_active_challenge_indicators.end();) + for (auto it = s_state.active_challenge_indicators.begin(); it != s_state.active_challenge_indicators.end();) { const AchievementChallengeIndicator& indicator = *it; const float opacity = IndicatorOpacity(indicator); @@ -2170,7 +2191,7 @@ void Achievements::DrawGameOverlays() if (!indicator.active && opacity <= 0.01f) { DEV_LOG("Remove challenge indicator"); - it = s_active_challenge_indicators.erase(it); + it = s_state.active_challenge_indicators.erase(it); } else { @@ -2181,13 +2202,13 @@ void Achievements::DrawGameOverlays() position.y -= image_size.y + padding; } - if (s_active_progress_indicator.has_value()) + if (s_state.active_progress_indicator.has_value()) { - const AchievementProgressIndicator& indicator = s_active_progress_indicator.value(); + const AchievementProgressIndicator& indicator = s_state.active_progress_indicator.value(); const float opacity = IndicatorOpacity(indicator); const u32 col = ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 1.0f, opacity)); - const char* text_start = s_active_progress_indicator->achievement->measured_progress; + const char* text_start = s_state.active_progress_indicator->achievement->measured_progress; const char* text_end = text_start + std::strlen(text_start); const ImVec2 text_size = g_medium_font->CalcTextSizeA(g_medium_font->FontSize, FLT_MAX, 0.0f, text_start, text_end); @@ -2214,15 +2235,15 @@ void Achievements::DrawGameOverlays() if (!indicator.active && opacity <= 0.01f) { DEV_LOG("Remove progress indicator"); - s_active_progress_indicator.reset(); + s_state.active_progress_indicator.reset(); } position.y -= image_size.y - padding * 3.0f; } - if (!s_active_leaderboard_trackers.empty()) + if (!s_state.active_leaderboard_trackers.empty()) { - for (auto it = s_active_leaderboard_trackers.begin(); it != s_active_leaderboard_trackers.end();) + for (auto it = s_state.active_leaderboard_trackers.begin(); it != s_state.active_leaderboard_trackers.end();) { const LeaderboardTrackerIndicator& indicator = *it; const float opacity = IndicatorOpacity(indicator); @@ -2257,7 +2278,7 @@ void Achievements::DrawGameOverlays() if (!indicator.active && opacity <= 0.01f) { DEV_LOG("Remove tracker indicator"); - it = s_active_leaderboard_trackers.erase(it); + it = s_state.active_leaderboard_trackers.erase(it); } else { @@ -2285,7 +2306,7 @@ void Achievements::DrawPauseMenuOverlays() const auto lock = GetLock(); - if (s_active_challenge_indicators.empty() && !s_active_progress_indicator.has_value()) + if (s_state.active_challenge_indicators.empty() && !s_state.active_progress_indicator.has_value()) return; const ImGuiIO& io = ImGui::GetIO(); @@ -2302,12 +2323,12 @@ void Achievements::DrawPauseMenuOverlays() const float row_width = max_text_width + padding + padding + image_size.x + spacing; const float title_height = padding + font->FontSize + padding; - if (!s_active_challenge_indicators.empty()) + if (!s_state.active_challenge_indicators.empty()) { const ImVec2 box_min(io.DisplaySize.x - row_width - margin, start_y + margin); const ImVec2 box_max(box_min.x + row_width, box_min.y + title_height + - (static_cast(s_active_challenge_indicators.size()) * (image_size.y + padding))); + (static_cast(s_state.active_challenge_indicators.size()) * (image_size.y + padding))); ImDrawList* dl = ImGui::GetBackgroundDrawList(); dl->AddRectFilled(box_min, box_max, IM_COL32(0x21, 0x21, 0x21, 200), LayoutScale(10.0f)); @@ -2319,7 +2340,7 @@ void Achievements::DrawPauseMenuOverlays() const float max_non_ellipised_text_width = max_text_width - LayoutScale(10.0f); ImVec2 position(box_min.x + padding, box_min.y + title_height); - for (const AchievementChallengeIndicator& indicator : s_active_challenge_indicators) + for (const AchievementChallengeIndicator& indicator : s_state.active_challenge_indicators) { GPUTexture* badge = ImGuiFullscreen::GetCachedTextureAsync(indicator.badge_path); if (!badge) @@ -2359,14 +2380,14 @@ bool Achievements::PrepareAchievementsWindow() { auto lock = Achievements::GetLock(); - s_achievement_badge_paths = {}; + s_state.achievement_badge_paths = {}; - if (s_achievement_list) - rc_client_destroy_achievement_list(s_achievement_list); - s_achievement_list = rc_client_create_achievement_list( - s_client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL, + if (s_state.achievement_list) + rc_client_destroy_achievement_list(s_state.achievement_list); + s_state.achievement_list = rc_client_create_achievement_list( + s_state.client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL, RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS /*RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE*/); - if (!s_achievement_list) + if (!s_state.achievement_list) { ERROR_LOG("rc_client_create_achievement_list() returned null"); return false; @@ -2381,7 +2402,7 @@ void Achievements::DrawAchievementsWindow() using ImGuiFullscreen::g_medium_font; using ImGuiFullscreen::LayoutScale; - if (!s_achievement_list) + if (!s_state.achievement_list) return; auto lock = Achievements::GetLock(); @@ -2413,9 +2434,9 @@ void Achievements::DrawAchievementsWindow() const ImVec2 icon_min(bb.Min + ImVec2(padding, padding)); const ImVec2 icon_max(icon_min + ImVec2(image_height, image_height)); - if (!s_game_icon.empty()) + if (!s_state.game_icon.empty()) { - GPUTexture* badge = ImGuiFullscreen::GetCachedTextureAsync(s_game_icon.c_str()); + GPUTexture* badge = ImGuiFullscreen::GetCachedTextureAsync(s_state.game_icon.c_str()); if (badge) { ImGui::GetWindowDrawList()->AddImage(badge, icon_min, icon_max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), @@ -2435,9 +2456,9 @@ void Achievements::DrawAchievementsWindow() ImGuiFullscreen::WantsToCloseMenu()); const ImRect title_bb(ImVec2(left, top), ImVec2(right, top + g_large_font->FontSize)); - text.assign(s_game_title); + text.assign(s_state.game_title); - if (s_hardcore_mode) + if (s_state.hardcore_mode) text.append(TRANSLATE_SV("Achievements", " (Hardcore Mode)")); top += g_large_font->FontSize + spacing; @@ -2448,19 +2469,19 @@ void Achievements::DrawAchievementsWindow() ImGui::PopFont(); const ImRect summary_bb(ImVec2(left, top), ImVec2(right, top + g_medium_font->FontSize)); - if (s_game_summary.num_core_achievements > 0) + if (s_state.game_summary.num_core_achievements > 0) { - if (s_game_summary.num_unlocked_achievements == s_game_summary.num_core_achievements) + if (s_state.game_summary.num_unlocked_achievements == s_state.game_summary.num_core_achievements) { text = TRANSLATE_PLURAL_SSTR("Achievements", "You have unlocked all achievements and earned %n points!", - "Point count", s_game_summary.points_unlocked); + "Point count", s_state.game_summary.points_unlocked); } else { text.format(TRANSLATE_FS("Achievements", "You have unlocked {0} of {1} achievements, earning {2} of {3} possible points."), - s_game_summary.num_unlocked_achievements, s_game_summary.num_core_achievements, - s_game_summary.points_unlocked, s_game_summary.points_core); + s_state.game_summary.num_unlocked_achievements, s_state.game_summary.num_core_achievements, + s_state.game_summary.points_unlocked, s_state.game_summary.points_core); } } else @@ -2475,12 +2496,12 @@ void Achievements::DrawAchievementsWindow() ImVec2(0.0f, 0.0f), &summary_bb); ImGui::PopFont(); - if (s_game_summary.num_core_achievements > 0) + if (s_state.game_summary.num_core_achievements > 0) { const float progress_height = ImGuiFullscreen::LayoutScale(20.0f); const ImRect progress_bb(ImVec2(left, top), ImVec2(right, top + progress_height)); - const float fraction = static_cast(s_game_summary.num_unlocked_achievements) / - static_cast(s_game_summary.num_core_achievements); + const float fraction = static_cast(s_state.game_summary.num_unlocked_achievements) / + static_cast(s_state.game_summary.num_core_achievements); dl->AddRectFilled(progress_bb.Min, progress_bb.Max, ImGui::GetColorU32(ImGuiFullscreen::UIPrimaryDarkColor)); dl->AddRectFilled(progress_bb.Min, ImVec2(progress_bb.Min.x + fraction * progress_bb.GetWidth(), progress_bb.Max.y), @@ -2526,9 +2547,9 @@ void Achievements::DrawAchievementsWindow() RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE, RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED, RC_CLIENT_ACHIEVEMENT_BUCKET_UNOFFICIAL, RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED}) { - for (u32 bucket_idx = 0; bucket_idx < s_achievement_list->num_buckets; bucket_idx++) + for (u32 bucket_idx = 0; bucket_idx < s_state.achievement_list->num_buckets; bucket_idx++) { - const rc_client_achievement_bucket_t& bucket = s_achievement_list->buckets[bucket_idx]; + const rc_client_achievement_bucket_t& bucket = s_state.achievement_list->buckets[bucket_idx]; if (bucket.bucket_type != bucket_type) continue; @@ -2606,16 +2627,16 @@ void Achievements::DrawAchievement(const rc_client_achievement_t* cheevo) return; std::string* badge_path; - if (const auto badge_it = std::find_if(s_achievement_badge_paths.begin(), s_achievement_badge_paths.end(), + if (const auto badge_it = std::find_if(s_state.achievement_badge_paths.begin(), s_state.achievement_badge_paths.end(), [cheevo](const auto& it) { return (it.first == cheevo); }); - badge_it != s_achievement_badge_paths.end()) + badge_it != s_state.achievement_badge_paths.end()) { badge_path = &badge_it->second; } else { std::string new_badge_path = Achievements::GetAchievementBadgePath(cheevo, cheevo->state); - badge_path = &s_achievement_badge_paths.emplace_back(cheevo, std::move(new_badge_path)).second; + badge_path = &s_state.achievement_badge_paths.emplace_back(cheevo, std::move(new_badge_path)).second; } const ImVec2 image_size( @@ -2730,14 +2751,14 @@ void Achievements::DrawAchievement(const rc_client_achievement_t* cheevo) bool Achievements::PrepareLeaderboardsWindow() { auto lock = Achievements::GetLock(); - rc_client_t* const client = s_client; + rc_client_t* const client = s_state.client; - s_achievement_badge_paths = {}; + s_state.achievement_badge_paths = {}; CloseLeaderboard(); - if (s_leaderboard_list) - rc_client_destroy_leaderboard_list(s_leaderboard_list); - s_leaderboard_list = rc_client_create_leaderboard_list(client, RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE); - if (!s_leaderboard_list) + if (s_state.leaderboard_list) + rc_client_destroy_leaderboard_list(s_state.leaderboard_list); + s_state.leaderboard_list = rc_client_create_leaderboard_list(client, RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE); + if (!s_state.leaderboard_list) { ERROR_LOG("rc_client_create_leaderboard_list() returned null"); return false; @@ -2759,7 +2780,7 @@ void Achievements::DrawLeaderboardsWindow() auto lock = Achievements::GetLock(); - const bool is_leaderboard_open = (s_open_leaderboard != nullptr); + const bool is_leaderboard_open = (s_state.open_leaderboard != nullptr); bool close_leaderboard_on_exit = false; ImRect bb; @@ -2806,9 +2827,9 @@ void Achievements::DrawLeaderboardsWindow() const ImVec2 icon_min(bb.Min + ImVec2(padding, padding)); const ImVec2 icon_max(icon_min + ImVec2(image_height, image_height)); - if (!s_game_icon.empty()) + if (!s_state.game_icon.empty()) { - GPUTexture* badge = ImGuiFullscreen::GetCachedTextureAsync(s_game_icon.c_str()); + GPUTexture* badge = ImGuiFullscreen::GetCachedTextureAsync(s_state.game_icon.c_str()); if (badge) { ImGui::GetWindowDrawList()->AddImage(badge, icon_min, icon_max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), @@ -2853,7 +2874,7 @@ void Achievements::DrawLeaderboardsWindow() if (is_leaderboard_open) { const ImRect subtitle_bb(ImVec2(left, top), ImVec2(right, top + g_large_font->FontSize)); - text.assign(s_open_leaderboard->title); + text.assign(s_state.open_leaderboard->title); top += g_large_font->FontSize + spacing_small; @@ -2862,13 +2883,13 @@ void Achievements::DrawLeaderboardsWindow() ImVec2(0.0f, 0.0f), &subtitle_bb); ImGui::PopFont(); - text.assign(s_open_leaderboard->description); + text.assign(s_state.open_leaderboard->description); } else { u32 count = 0; - for (u32 i = 0; i < s_leaderboard_list->num_buckets; i++) - count += s_leaderboard_list->buckets[i].num_leaderboards; + for (u32 i = 0; i < s_state.leaderboard_list->num_buckets; i++) + count += s_state.leaderboard_list->buckets[i].num_leaderboards; text = TRANSLATE_PLURAL_SSTR("Achievements", "This game has %n leaderboards.", "Leaderboard count", count); } @@ -2903,7 +2924,7 @@ void Achievements::DrawLeaderboardsWindow() ImGui::IsKeyPressed(ImGuiKey_LeftArrow, false) || ImGui::IsKeyPressed(ImGuiKey_GamepadDpadRight, false) || ImGui::IsKeyPressed(ImGuiKey_NavGamepadTweakFast, false) || ImGui::IsKeyPressed(ImGuiKey_RightArrow, false)) { - s_is_showing_all_leaderboard_entries = !s_is_showing_all_leaderboard_entries; + s_state.is_showing_all_leaderboard_entries = !s_state.is_showing_all_leaderboard_entries; ImGuiFullscreen::QueueResetFocus(ImGuiFullscreen::FocusResetType::Other); } @@ -2911,10 +2932,10 @@ void Achievements::DrawLeaderboardsWindow() { const char* title = show_all ? TRANSLATE("Achievements", "Show Best") : TRANSLATE("Achievements", "Show Nearby"); - if (ImGuiFullscreen::NavTab(title, s_is_showing_all_leaderboard_entries == show_all, true, tab_width, + if (ImGuiFullscreen::NavTab(title, s_state.is_showing_all_leaderboard_entries == show_all, true, tab_width, tab_height_unscaled, heading_background)) { - s_is_showing_all_leaderboard_entries = show_all; + s_state.is_showing_all_leaderboard_entries = show_all; } } @@ -2958,7 +2979,7 @@ void Achievements::DrawLeaderboardsWindow() score_bb.Min, score_bb.Max, Host::TranslateToCString( "Achievements", - value_headings[std::min(s_open_leaderboard->format, NUM_RC_CLIENT_LEADERBOARD_FORMATS - 1)]), + value_headings[std::min(s_state.open_leaderboard->format, NUM_RC_CLIENT_LEADERBOARD_FORMATS - 1)]), nullptr, nullptr, ImVec2(0.0f, 0.0f), &score_bb); text_start_x += time_column_width + column_spacing; @@ -2993,9 +3014,9 @@ void Achievements::DrawLeaderboardsWindow() ImGuiFullscreen::ResetFocusHere(); ImGuiFullscreen::BeginMenuButtons(); - for (u32 bucket_index = 0; bucket_index < s_leaderboard_list->num_buckets; bucket_index++) + for (u32 bucket_index = 0; bucket_index < s_state.leaderboard_list->num_buckets; bucket_index++) { - const rc_client_leaderboard_bucket_t& bucket = s_leaderboard_list->buckets[bucket_index]; + const rc_client_leaderboard_bucket_t& bucket = s_state.leaderboard_list->buckets[bucket_index]; for (u32 i = 0; i < bucket.num_leaderboards; i++) DrawLeaderboardListEntry(bucket.leaderboards[i]); } @@ -3021,23 +3042,23 @@ void Achievements::DrawLeaderboardsWindow() "leaderboard", background, 0.0f, ImVec2(ImGuiFullscreen::LAYOUT_MENU_WINDOW_X_PADDING, 0.0f), 0)) { // Defer focus reset until loading finishes. - if (!s_is_showing_all_leaderboard_entries || - (ImGuiFullscreen::IsFocusResetFromWindowChange() && !s_leaderboard_entry_lists.empty())) + if (!s_state.is_showing_all_leaderboard_entries || + (ImGuiFullscreen::IsFocusResetFromWindowChange() && !s_state.leaderboard_entry_lists.empty())) { ImGuiFullscreen::ResetFocusHere(); } ImGuiFullscreen::BeginMenuButtons(); - if (!s_is_showing_all_leaderboard_entries) + if (!s_state.is_showing_all_leaderboard_entries) { - if (s_leaderboard_nearby_entries) + if (s_state.leaderboard_nearby_entries) { - for (u32 i = 0; i < s_leaderboard_nearby_entries->num_entries; i++) + for (u32 i = 0; i < s_state.leaderboard_nearby_entries->num_entries; i++) { - DrawLeaderboardEntry(s_leaderboard_nearby_entries->entries[i], - static_cast(i) == s_leaderboard_nearby_entries->user_index, rank_column_width, - name_column_width, time_column_width, column_spacing); + DrawLeaderboardEntry(s_state.leaderboard_nearby_entries->entries[i], + static_cast(i) == s_state.leaderboard_nearby_entries->user_index, + rank_column_width, name_column_width, time_column_width, column_spacing); } } else @@ -3055,7 +3076,7 @@ void Achievements::DrawLeaderboardsWindow() } else { - for (const rc_client_leaderboard_entry_list_t* list : s_leaderboard_entry_lists) + for (const rc_client_leaderboard_entry_list_t* list : s_state.leaderboard_entry_lists) { for (u32 i = 0; i < list->num_entries; i++) { @@ -3079,7 +3100,7 @@ void Achievements::DrawLeaderboardsWindow() nullptr, ImVec2(0, 0), &title_bb); ImGui::PopFont(); - if (!s_leaderboard_fetch_handle) + if (!s_state.leaderboard_fetch_handle) FetchNextLeaderboardEntries(); } } @@ -3141,9 +3162,9 @@ void Achievements::DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& ent const float icon_size = bb.Max.y - bb.Min.y; const ImRect icon_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint)); GPUTexture* icon_tex = nullptr; - if (auto it = std::find_if(s_leaderboard_user_icon_paths.begin(), s_leaderboard_user_icon_paths.end(), + if (auto it = std::find_if(s_state.leaderboard_user_icon_paths.begin(), s_state.leaderboard_user_icon_paths.end(), [&entry](const auto& it) { return it.first == &entry; }); - it != s_leaderboard_user_icon_paths.end()) + it != s_state.leaderboard_user_icon_paths.end()) { if (!it->second.empty()) icon_tex = ImGuiFullscreen::GetCachedTextureAsync(it->second); @@ -3154,7 +3175,7 @@ void Achievements::DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& ent if (!path.empty()) { icon_tex = ImGuiFullscreen::GetCachedTextureAsync(path); - s_leaderboard_user_icon_paths.emplace_back(&entry, std::move(path)); + s_state.leaderboard_user_icon_paths.emplace_back(&entry, std::move(path)); } } if (icon_tex) @@ -3236,16 +3257,16 @@ void Achievements::OpenLeaderboard(const rc_client_leaderboard_t* lboard) CloseLeaderboard(); - s_open_leaderboard = lboard; - s_is_showing_all_leaderboard_entries = false; - s_leaderboard_fetch_handle = rc_client_begin_fetch_leaderboard_entries_around_user( - s_client, lboard->id, LEADERBOARD_NEARBY_ENTRIES_TO_FETCH, LeaderboardFetchNearbyCallback, nullptr); + s_state.open_leaderboard = lboard; + s_state.is_showing_all_leaderboard_entries = false; + s_state.leaderboard_fetch_handle = rc_client_begin_fetch_leaderboard_entries_around_user( + s_state.client, lboard->id, LEADERBOARD_NEARBY_ENTRIES_TO_FETCH, LeaderboardFetchNearbyCallback, nullptr); ImGuiFullscreen::QueueResetFocus(ImGuiFullscreen::FocusResetType::Other); } bool Achievements::OpenLeaderboardById(u32 leaderboard_id) { - const rc_client_leaderboard_t* lb = rc_client_get_leaderboard_info(s_client, leaderboard_id); + const rc_client_leaderboard_t* lb = rc_client_get_leaderboard_info(s_state.client, leaderboard_id); if (!lb) return false; @@ -3255,22 +3276,22 @@ bool Achievements::OpenLeaderboardById(u32 leaderboard_id) u32 Achievements::GetOpenLeaderboardId() { - return s_open_leaderboard ? s_open_leaderboard->id : 0; + return s_state.open_leaderboard ? s_state.open_leaderboard->id : 0; } bool Achievements::IsShowingAllLeaderboardEntries() { - return s_is_showing_all_leaderboard_entries; + return s_state.is_showing_all_leaderboard_entries; } const std::vector& Achievements::GetLeaderboardEntryLists() { - return s_leaderboard_entry_lists; + return s_state.leaderboard_entry_lists; } const rc_client_leaderboard_entry_list_t* Achievements::GetLeaderboardNearbyEntries() { - return s_leaderboard_nearby_entries; + return s_state.leaderboard_nearby_entries; } void Achievements::LeaderboardFetchNearbyCallback(int result, const char* error_message, @@ -3279,7 +3300,7 @@ void Achievements::LeaderboardFetchNearbyCallback(int result, const char* error_ { const auto lock = GetLock(); - s_leaderboard_fetch_handle = nullptr; + s_state.leaderboard_fetch_handle = nullptr; if (result != RC_OK) { @@ -3288,9 +3309,9 @@ void Achievements::LeaderboardFetchNearbyCallback(int result, const char* error_ return; } - if (s_leaderboard_nearby_entries) - rc_client_destroy_leaderboard_entry_list(s_leaderboard_nearby_entries); - s_leaderboard_nearby_entries = list; + if (s_state.leaderboard_nearby_entries) + rc_client_destroy_leaderboard_entry_list(s_state.leaderboard_nearby_entries); + s_state.leaderboard_nearby_entries = list; } void Achievements::LeaderboardFetchAllCallback(int result, const char* error_message, @@ -3299,7 +3320,7 @@ void Achievements::LeaderboardFetchAllCallback(int result, const char* error_mes { const auto lock = GetLock(); - s_leaderboard_fetch_handle = nullptr; + s_state.leaderboard_fetch_handle = nullptr; if (result != RC_OK) { @@ -3308,44 +3329,45 @@ void Achievements::LeaderboardFetchAllCallback(int result, const char* error_mes return; } - s_leaderboard_entry_lists.push_back(list); + s_state.leaderboard_entry_lists.push_back(list); } void Achievements::FetchNextLeaderboardEntries() { u32 start = 1; - for (rc_client_leaderboard_entry_list_t* list : s_leaderboard_entry_lists) + for (rc_client_leaderboard_entry_list_t* list : s_state.leaderboard_entry_lists) start += list->num_entries; DEV_LOG("Fetching entries {} to {}", start, start + LEADERBOARD_ALL_FETCH_SIZE); - if (s_leaderboard_fetch_handle) - rc_client_abort_async(s_client, s_leaderboard_fetch_handle); - s_leaderboard_fetch_handle = rc_client_begin_fetch_leaderboard_entries( - s_client, s_open_leaderboard->id, start, LEADERBOARD_ALL_FETCH_SIZE, LeaderboardFetchAllCallback, nullptr); + if (s_state.leaderboard_fetch_handle) + rc_client_abort_async(s_state.client, s_state.leaderboard_fetch_handle); + s_state.leaderboard_fetch_handle = + rc_client_begin_fetch_leaderboard_entries(s_state.client, s_state.open_leaderboard->id, start, + LEADERBOARD_ALL_FETCH_SIZE, LeaderboardFetchAllCallback, nullptr); } void Achievements::CloseLeaderboard() { - s_leaderboard_user_icon_paths.clear(); + s_state.leaderboard_user_icon_paths.clear(); - for (auto iter = s_leaderboard_entry_lists.rbegin(); iter != s_leaderboard_entry_lists.rend(); ++iter) + for (auto iter = s_state.leaderboard_entry_lists.rbegin(); iter != s_state.leaderboard_entry_lists.rend(); ++iter) rc_client_destroy_leaderboard_entry_list(*iter); - s_leaderboard_entry_lists.clear(); + s_state.leaderboard_entry_lists.clear(); - if (s_leaderboard_nearby_entries) + if (s_state.leaderboard_nearby_entries) { - rc_client_destroy_leaderboard_entry_list(s_leaderboard_nearby_entries); - s_leaderboard_nearby_entries = nullptr; + rc_client_destroy_leaderboard_entry_list(s_state.leaderboard_nearby_entries); + s_state.leaderboard_nearby_entries = nullptr; } - if (s_leaderboard_fetch_handle) + if (s_state.leaderboard_fetch_handle) { - rc_client_abort_async(s_client, s_leaderboard_fetch_handle); - s_leaderboard_fetch_handle = nullptr; + rc_client_abort_async(s_state.client, s_state.leaderboard_fetch_handle); + s_state.leaderboard_fetch_handle = nullptr; } - s_open_leaderboard = nullptr; + s_state.open_leaderboard = nullptr; ImGuiFullscreen::QueueResetFocus(ImGuiFullscreen::FocusResetType::Other); } @@ -3513,7 +3535,7 @@ TinyString Achievements::DecryptLoginToken(std::string_view encrypted_token, std bool Achievements::IsUsingRAIntegration() { - return s_using_raintegration; + return s_state.using_raintegration; } namespace Achievements::RAIntegration { @@ -3538,7 +3560,7 @@ static bool s_raintegration_initialized = false; void Achievements::SwitchToRAIntegration() { - s_using_raintegration = true; + s_state.using_raintegration = true; } void Achievements::RAIntegration::InitializeRAIntegration(void* main_window_handle) @@ -3579,8 +3601,8 @@ void Achievements::RAIntegration::MainWindowChanged(void* new_handle) void Achievements::RAIntegration::GameChanged() { - s_game_id = s_game_hash.empty() ? 0 : RA_IdentifyHash(s_game_hash.c_str()); - RA_ActivateGame(s_game_id); + s_state.game_id = s_state.game_hash.empty() ? 0 : RA_IdentifyHash(s_state.game_hash.c_str()); + RA_ActivateGame(s_state.game_id); } std::vector> Achievements::RAIntegration::GetMenuItems() From cbc16bee9e63dc01fa39f01d883976abf887b9d4 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 21:04:50 +1000 Subject: [PATCH 17/69] GPU: Display scanout resolution regardless of crop mode --- src/core/gpu.cpp | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index 7b43faa56..1108d9599 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -171,7 +171,39 @@ void GPU::UpdateResolutionScale() std::tuple GPU::GetFullDisplayResolution() const { - return std::tie(m_crtc_state.display_width, m_crtc_state.display_height); + u32 width, height; + if (IsDisplayDisabled()) + { + width = 0; + height = 0; + } + else + { + s32 xmin, xmax, ymin, ymax; + if (!m_GPUSTAT.pal_mode) + { + xmin = NTSC_HORIZONTAL_ACTIVE_START; + xmax = NTSC_HORIZONTAL_ACTIVE_END; + ymin = NTSC_VERTICAL_ACTIVE_START; + ymax = NTSC_VERTICAL_ACTIVE_END; + } + else + { + xmin = PAL_HORIZONTAL_ACTIVE_START; + xmax = PAL_HORIZONTAL_ACTIVE_END; + ymin = PAL_VERTICAL_ACTIVE_START; + ymax = PAL_VERTICAL_ACTIVE_END; + } + + width = static_cast(std::max(std::clamp(m_crtc_state.regs.X2, xmin, xmax) - + std::clamp(m_crtc_state.regs.X1, xmin, xmax), + 0) / + m_crtc_state.dot_clock_divider); + height = static_cast(std::max( + std::clamp(m_crtc_state.regs.Y2, ymin, ymax) - std::clamp(m_crtc_state.regs.Y1, ymin, ymax), 0)); + } + + return std::tie(width, height); } void GPU::Reset(bool clear_vram) From b7fff840c84ec40348b144721b7914a6be129533 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 21:06:33 +1000 Subject: [PATCH 18/69] System: Move thread name init to host Prevents funky thread names for regtest on Linux. --- src/core/system.cpp | 3 --- src/duckstation-qt/qthost.cpp | 3 +++ src/duckstation-regtest/regtest_host.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/system.cpp b/src/core/system.cpp index df399ce76..e508c2c24 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -61,7 +61,6 @@ #include "common/memmap.h" #include "common/path.h" #include "common/string_util.h" -#include "common/threading.h" #include "IconsEmoji.h" #include "IconsFontAwesome5.h" @@ -499,8 +498,6 @@ void System::ProcessShutdown() bool System::CPUThreadInitialize(Error* error) { - Threading::SetNameOfCurrentThread("CPU Thread"); - #ifdef _WIN32 // On Win32, we have a bunch of things which use COM (e.g. SDL, Cubeb, etc). // We need to initialize COM first, before anything else does, because otherwise they might diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 9946875a2..b590eb1f1 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -37,6 +37,7 @@ #include "common/path.h" #include "common/scoped_guard.h" #include "common/string_util.h" +#include "common/threading.h" #include "util/audio_stream.h" #include "util/http_downloader.h" @@ -1791,6 +1792,8 @@ void EmuThread::stopInThread() void EmuThread::run() { + Threading::SetNameOfCurrentThread("CPU Thread"); + m_event_loop = new QEventLoop(); m_started_semaphore.release(); diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp index 0a6731acd..c7923c9ac 100644 --- a/src/duckstation-regtest/regtest_host.cpp +++ b/src/duckstation-regtest/regtest_host.cpp @@ -121,7 +121,7 @@ bool RegTestHost::InitializeConfig() EmuFolders::EnsureFoldersExist(); // imgui setup, make sure it doesn't bug out - ImGuiManager::SetFontPathAndRange(std::string(), {0x0020, 0x00FF, 0, 0}); + ImGuiManager::SetFontPathAndRange(std::string(), {0x0020, 0x00FF, 0x2022, 0x2022, 0, 0}); return true; } From 0f51472d64d951b4bac7e530ba6db616fbcecc3b Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 30 Nov 2024 01:05:17 +1000 Subject: [PATCH 19/69] Misc: Android build fix --- src/common/log.cpp | 2 +- src/core/game_database.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/log.cpp b/src/common/log.cpp index dc5e0acd3..2a93b838d 100644 --- a/src/common/log.cpp +++ b/src/common/log.cpp @@ -376,7 +376,7 @@ void Log::DebugOutputLogCallback(void* pUserParam, MessageCategory cat, const ch if (message.empty()) return; - static constexpr int logPriority[static_cast(Level::Count)] = { + static constexpr int logPriority[static_cast(Level::MaxCount)] = { ANDROID_LOG_INFO, // None ANDROID_LOG_ERROR, // Error ANDROID_LOG_WARN, // Warning diff --git a/src/core/game_database.cpp b/src/core/game_database.cpp index 1b10aed67..901af7be0 100644 --- a/src/core/game_database.cpp +++ b/src/core/game_database.cpp @@ -646,7 +646,7 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes #else Host::AddIconOSDWarning("gamedb_force_pgxp_cpu", ICON_EMOJI_WARNING, "This game requires PGXP CPU mode, which increases system requirements.\n" - " If the game runs too slow, disable PGXP for this game.", + " If the game runs too slow, disable PGXP for this game.", Host::OSD_WARNING_DURATION); #endif } From ee750b44e39e4f4dfc389a3492321de5e9940b9c Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 30 Nov 2024 01:05:37 +1000 Subject: [PATCH 20/69] Settings: Normalize texture replacment option titles --- src/core/fullscreen_ui.cpp | 2 +- src/duckstation-qt/graphicssettingswidget.ui | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 2bc52997f..77a99f247 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -4603,7 +4603,7 @@ void FullscreenUI::DrawGraphicsSettingsPage() FSUI_CSTR("Dumps textures that have replacements already loaded."), "TextureReplacements", "DumpReplacedTextures", false, texture_cache_enabled && GetEffectiveBoolSetting(bsi, "TextureReplacements", "DumpTextures", false)); - DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_FILE_ALT, "Enable VRAM Write Texture Replacement"), + DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_FILE_ALT, "Enable VRAM Write Replacement"), FSUI_CSTR("Enables the replacement of background textures in supported games."), "TextureReplacements", "EnableVRAMWriteReplacements", false); diff --git a/src/duckstation-qt/graphicssettingswidget.ui b/src/duckstation-qt/graphicssettingswidget.ui index d80818d6b..4ed1bace1 100644 --- a/src/duckstation-qt/graphicssettingswidget.ui +++ b/src/duckstation-qt/graphicssettingswidget.ui @@ -1127,7 +1127,7 @@ - Enable Texture Replacements + Enable Texture Replacement From dfacf9e8db8a01a7cedb310a3ef0f9f6caa2da83 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 30 Nov 2024 01:08:50 +1000 Subject: [PATCH 21/69] VulkanDevice: Only create swap chain framebuffer without dynamic rendering --- src/util/vulkan_swap_chain.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/util/vulkan_swap_chain.cpp b/src/util/vulkan_swap_chain.cpp index 3ba8ec3f3..34bceaf26 100644 --- a/src/util/vulkan_swap_chain.cpp +++ b/src/util/vulkan_swap_chain.cpp @@ -534,13 +534,13 @@ bool VulkanSwapChain::CreateSwapChainImages(VulkanDevice& dev, Error* error) return false; } - Vulkan::FramebufferBuilder fbb; m_images.reserve(image_count); m_current_image = 0; for (u32 i = 0; i < image_count; i++) { Image& image = m_images.emplace_back(); image.image = images[i]; + image.framebuffer = VK_NULL_HANDLE; const VkImageViewCreateInfo view_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, @@ -560,15 +560,19 @@ bool VulkanSwapChain::CreateSwapChainImages(VulkanDevice& dev, Error* error) return false; } - fbb.AddAttachment(image.view); - fbb.SetRenderPass(render_pass); - fbb.SetSize(m_window_info.surface_width, m_window_info.surface_height, 1); - if ((image.framebuffer = fbb.Create(vkdev)) == VK_NULL_HANDLE) + if (!dev.GetOptionalExtensions().vk_khr_dynamic_rendering) { - Error::SetStringView(error, "Failed to create swap chain image framebuffer."); - vkDestroyImageView(vkdev, image.view, nullptr); - m_images.pop_back(); - return false; + Vulkan::FramebufferBuilder fbb; + fbb.AddAttachment(image.view); + fbb.SetRenderPass(render_pass); + fbb.SetSize(m_window_info.surface_width, m_window_info.surface_height, 1); + if ((image.framebuffer = fbb.Create(vkdev)) == VK_NULL_HANDLE) + { + Error::SetStringView(error, "Failed to create swap chain image framebuffer."); + vkDestroyImageView(vkdev, image.view, nullptr); + m_images.pop_back(); + return false; + } } } @@ -621,7 +625,8 @@ void VulkanSwapChain::DestroySwapChainImages() for (const auto& it : m_images) { // don't defer view destruction, images are no longer valid - vkDestroyFramebuffer(vkdev, it.framebuffer, nullptr); + if (it.framebuffer != VK_NULL_HANDLE) + vkDestroyFramebuffer(vkdev, it.framebuffer, nullptr); vkDestroyImageView(vkdev, it.view, nullptr); } m_images.clear(); From fa4dc381ed87a796fab042b6674fa525a6c63379 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 30 Nov 2024 14:31:50 +1000 Subject: [PATCH 22/69] GPUDevice: Rename RGBA5551 to RGB5A1 And fix the incorrect format for Vulkan. --- src/core/gpu_sw.cpp | 22 ++++----- src/util/d3d_common.cpp | 2 +- src/util/gpu_texture.cpp | 82 ++++++++++++++++----------------- src/util/gpu_texture.h | 2 +- src/util/image.cpp | 28 +++++------ src/util/image.h | 2 +- src/util/metal_device.mm | 4 +- src/util/opengl_context_egl.cpp | 4 +- src/util/opengl_texture.cpp | 4 +- src/util/vulkan_device.cpp | 2 +- 10 files changed, 76 insertions(+), 76 deletions(-) diff --git a/src/core/gpu_sw.cpp b/src/core/gpu_sw.cpp index c8b12fa87..fd781af63 100644 --- a/src/core/gpu_sw.cpp +++ b/src/core/gpu_sw.cpp @@ -41,10 +41,10 @@ bool GPU_SW::Initialize(Error* error) if (!GPU::Initialize(error) || !m_backend.Initialize(g_settings.gpu_use_thread)) return false; - static constexpr const std::array formats_for_16bit = {GPUTexture::Format::RGB565, GPUTexture::Format::RGBA5551, + static constexpr const std::array formats_for_16bit = {GPUTexture::Format::RGB565, GPUTexture::Format::RGB5A1, GPUTexture::Format::RGBA8, GPUTexture::Format::BGRA8}; static constexpr const std::array formats_for_24bit = {GPUTexture::Format::RGBA8, GPUTexture::Format::BGRA8, - GPUTexture::Format::RGB565, GPUTexture::Format::RGBA5551}; + GPUTexture::Format::RGB565, GPUTexture::Format::RGB5A1}; for (const GPUTexture::Format format : formats_for_16bit) { if (g_gpu_device->SupportsTextureFormat(format)) @@ -115,7 +115,7 @@ template static out_type VRAM16ToOutput(u16 value); template<> -ALWAYS_INLINE u16 VRAM16ToOutput(u16 value) +ALWAYS_INLINE u16 VRAM16ToOutput(u16 value) { return (value & 0x3E0) | ((value >> 10) & 0x1F) | ((value & 0x1F) << 10); } @@ -148,7 +148,7 @@ ALWAYS_INLINE u32 VRAM16ToOutput(u16 value) } template<> -ALWAYS_INLINE void CopyOutRow16(const u16* src_ptr, u16* dst_ptr, u32 width) +ALWAYS_INLINE void CopyOutRow16(const u16* src_ptr, u16* dst_ptr, u32 width) { u32 col = 0; @@ -167,7 +167,7 @@ ALWAYS_INLINE void CopyOutRow16(const u16* sr } for (; col < width; col++) - *(dst_ptr++) = VRAM16ToOutput(*(src_ptr++)); + *(dst_ptr++) = VRAM16ToOutput(*(src_ptr++)); } template<> @@ -317,7 +317,7 @@ ALWAYS_INLINE_RELEASE bool GPU_SW::CopyOut24Bit(u32 src_x, u32 src_y, u32 skip_x src_row_ptr += 3; } } - else if constexpr (display_format == GPUTexture::Format::RGBA5551) + else if constexpr (display_format == GPUTexture::Format::RGB5A1) { const u8* src_row_ptr = src_ptr; u16* dst_row_ptr = reinterpret_cast(dst_ptr); @@ -362,7 +362,7 @@ ALWAYS_INLINE_RELEASE bool GPU_SW::CopyOut24Bit(u32 src_x, u32 src_y, u32 skip_x { *(dst_row_ptr++) = ((rgb >> 3) & 0x1F) | (((rgb >> 10) << 5) & 0x7E0) | (((rgb >> 19) << 11) & 0x3E0000); } - else if constexpr (display_format == GPUTexture::Format::RGBA5551) + else if constexpr (display_format == GPUTexture::Format::RGB5A1) { *(dst_row_ptr++) = ((rgb >> 3) & 0x1F) | (((rgb >> 11) << 5) & 0x3E0) | (((rgb >> 19) << 10) & 0x1F0000); } @@ -389,8 +389,8 @@ bool GPU_SW::CopyOut(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height, u3 switch (m_16bit_display_format) { - case GPUTexture::Format::RGBA5551: - return CopyOut15Bit(src_x, src_y, width, height, line_skip); + case GPUTexture::Format::RGB5A1: + return CopyOut15Bit(src_x, src_y, width, height, line_skip); case GPUTexture::Format::RGB565: return CopyOut15Bit(src_x, src_y, width, height, line_skip); @@ -409,8 +409,8 @@ bool GPU_SW::CopyOut(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height, u3 { switch (m_24bit_display_format) { - case GPUTexture::Format::RGBA5551: - return CopyOut24Bit(src_x, src_y, skip_x, width, height, line_skip); + case GPUTexture::Format::RGB5A1: + return CopyOut24Bit(src_x, src_y, skip_x, width, height, line_skip); case GPUTexture::Format::RGB565: return CopyOut24Bit(src_x, src_y, skip_x, width, height, line_skip); diff --git a/src/util/d3d_common.cpp b/src/util/d3d_common.cpp index e99d8ce46..ce5ac6ea0 100644 --- a/src/util/d3d_common.cpp +++ b/src/util/d3d_common.cpp @@ -629,7 +629,7 @@ static constexpr std::array(GPUTe {DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_UNKNOWN }, // RGBA8 {DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_UNKNOWN }, // BGRA8 {DXGI_FORMAT_B5G6R5_UNORM, DXGI_FORMAT_B5G6R5_UNORM, DXGI_FORMAT_B5G6R5_UNORM, DXGI_FORMAT_UNKNOWN }, // RGB565 - {DXGI_FORMAT_B5G5R5A1_UNORM, DXGI_FORMAT_B5G5R5A1_UNORM, DXGI_FORMAT_B5G5R5A1_UNORM, DXGI_FORMAT_UNKNOWN }, // RGBA5551 + {DXGI_FORMAT_B5G5R5A1_UNORM, DXGI_FORMAT_B5G5R5A1_UNORM, DXGI_FORMAT_B5G5R5A1_UNORM, DXGI_FORMAT_UNKNOWN }, // RGB5A1 {DXGI_FORMAT_R8_UNORM, DXGI_FORMAT_R8_UNORM, DXGI_FORMAT_R8_UNORM, DXGI_FORMAT_UNKNOWN }, // R8 {DXGI_FORMAT_R16_TYPELESS, DXGI_FORMAT_R16_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_D16_UNORM }, // D16 {DXGI_FORMAT_R24G8_TYPELESS, DXGI_FORMAT_R24_UNORM_X8_TYPELESS, DXGI_FORMAT_R24_UNORM_X8_TYPELESS, DXGI_FORMAT_D24_UNORM_S8_UINT }, // D24S8 diff --git a/src/util/gpu_texture.cpp b/src/util/gpu_texture.cpp index c29e5482c..deeaec939 100644 --- a/src/util/gpu_texture.cpp +++ b/src/util/gpu_texture.cpp @@ -30,7 +30,7 @@ const char* GPUTexture::GetFormatName(Format format) "RGBA8", // RGBA8 "BGRA8", // BGRA8 "RGB565", // RGB565 - "RGB5551", // RGBA5551 + "RGB5A1", // RGB5A1 "R8", // R8 "D16", // D16 "D24S8", // D24S8 @@ -147,16 +147,16 @@ void GPUTexture::CopyTextureDataForUpload(u32 width, u32 height, Format format, GPUTexture::Format GPUTexture::GetTextureFormatForImageFormat(ImageFormat format) { static constexpr const std::array mapping = { - Format::Unknown, // None - Format::RGBA8, // RGBA8 - Format::BGRA8, // BGRA8 - Format::RGB565, // RGB565 - Format::RGBA5551, // RGBA5551 - Format::Unknown, // BGR8 - Format::BC1, // BC1 - Format::BC2, // BC2 - Format::BC3, // BC3 - Format::BC7, // BC7 + Format::Unknown, // None + Format::RGBA8, // RGBA8 + Format::BGRA8, // BGRA8 + Format::RGB565, // RGB565 + Format::RGB5A1, // RGB5A1 + Format::Unknown, // BGR8 + Format::BC1, // BC1 + Format::BC2, // BC2 + Format::BC3, // BC3 + Format::BC7, // BC7 }; static_assert(mapping.size() == static_cast(ImageFormat::MaxCount)); @@ -166,35 +166,35 @@ GPUTexture::Format GPUTexture::GetTextureFormatForImageFormat(ImageFormat format ImageFormat GPUTexture::GetImageFormatForTextureFormat(Format format) { static constexpr const std::array mapping = { - ImageFormat::None, // Unknown - ImageFormat::RGBA8, // RGBA8 - ImageFormat::BGRA8, // BGRA8 - ImageFormat::RGB565, // RGB565 - ImageFormat::RGBA5551, // RGBA5551 - ImageFormat::None, // R8 - ImageFormat::None, // D16 - ImageFormat::None, // D24S8 - ImageFormat::None, // D32F - ImageFormat::None, // D32FS8 - ImageFormat::None, // R16 - ImageFormat::None, // R16I - ImageFormat::None, // R16U - ImageFormat::None, // R16F - ImageFormat::None, // R32I - ImageFormat::None, // R32U - ImageFormat::None, // R32F - ImageFormat::None, // RG8 - ImageFormat::None, // RG16 - ImageFormat::None, // RG16F - ImageFormat::None, // RG32F - ImageFormat::None, // RGBA16 - ImageFormat::None, // RGBA16F - ImageFormat::None, // RGBA32F - ImageFormat::None, // RGB10A2 - ImageFormat::BC1, // BC1 - ImageFormat::BC2, // BC2 - ImageFormat::BC3, // BC3 - ImageFormat::BC7, // BC7 + ImageFormat::None, // Unknown + ImageFormat::RGBA8, // RGBA8 + ImageFormat::BGRA8, // BGRA8 + ImageFormat::RGB565, // RGB565 + ImageFormat::RGB5A1, // RGB5A1 + ImageFormat::None, // R8 + ImageFormat::None, // D16 + ImageFormat::None, // D24S8 + ImageFormat::None, // D32F + ImageFormat::None, // D32FS8 + ImageFormat::None, // R16 + ImageFormat::None, // R16I + ImageFormat::None, // R16U + ImageFormat::None, // R16F + ImageFormat::None, // R32I + ImageFormat::None, // R32U + ImageFormat::None, // R32F + ImageFormat::None, // RG8 + ImageFormat::None, // RG16 + ImageFormat::None, // RG16F + ImageFormat::None, // RG32F + ImageFormat::None, // RGBA16 + ImageFormat::None, // RGBA16F + ImageFormat::None, // RGBA32F + ImageFormat::None, // RGB10A2 + ImageFormat::BC1, // BC1 + ImageFormat::BC2, // BC2 + ImageFormat::BC3, // BC3 + ImageFormat::BC7, // BC7 }; static_assert(mapping.size() == static_cast(Format::MaxCount)); @@ -252,7 +252,7 @@ u32 GPUTexture::GetPixelSize(GPUTexture::Format format) 4, // RGBA8 4, // BGRA8 2, // RGB565 - 2, // RGBA5551 + 2, // RGB5A1 1, // R8 2, // D16 4, // D24S8 diff --git a/src/util/gpu_texture.h b/src/util/gpu_texture.h index 386034ba8..dc04d9f59 100644 --- a/src/util/gpu_texture.h +++ b/src/util/gpu_texture.h @@ -40,7 +40,7 @@ public: RGBA8, BGRA8, RGB565, - RGBA5551, + RGB5A1, R8, D16, D24S8, diff --git a/src/util/image.cpp b/src/util/image.cpp index 7d5e91d27..632e243b0 100644 --- a/src/util/image.cpp +++ b/src/util/image.cpp @@ -163,16 +163,16 @@ Image& Image::operator=(Image&& move) const char* Image::GetFormatName(ImageFormat format) { static constexpr std::array names = { - "None", // None - "RGBA8", // RGBA8 - "BGRA8", // BGRA8 - "RGB565", // RGB565 - "RGB5551", // RGBA5551 - "BGR8", // BGR8 - "BC1", // BC1 - "BC2", // BC2 - "BC3", // BC3 - "BC7", // BC7 + "None", // None + "RGBA8", // RGBA8 + "BGRA8", // BGRA8 + "RGB565", // RGB565 + "RGB5A1", // RGB5A1 + "BGR8", // BGR8 + "BC1", // BC1 + "BC2", // BC2 + "BC3", // BC3 + "BC7", // BC7 }; static_assert(names.size() == static_cast(ImageFormat::MaxCount)); @@ -186,7 +186,7 @@ u32 Image::GetPixelSize(ImageFormat format) 4, // RGBA8 4, // BGRA8 2, // RGB565 - 2, // RGBA5551 + 2, // RGB5A1 3, // BGR8 8, // BC1 - 16 pixels in 64 bits 16, // BC2 - 16 pixels in 128 bits @@ -298,7 +298,7 @@ bool Image::SetAllPixelsOpaque() return true; } - else if (m_format == ImageFormat::RGBA5551) + else if (m_format == ImageFormat::RGB5A1) { for (u32 y = 0; y < m_height; y++) { @@ -604,7 +604,7 @@ std::optional Image::ConvertToRGBA8(Error* error) const } break; - case ImageFormat::RGBA5551: + case ImageFormat::RGB5A1: { ret = Image(m_width, m_height, ImageFormat::RGBA8); for (u32 y = 0; y < m_height; y++) @@ -614,7 +614,7 @@ std::optional Image::ConvertToRGBA8(Error* error) const for (u32 x = 0; x < m_width; x++) { - // RGBA5551 -> RGBA8 + // RGB5A1 -> RGBA8 u16 pixel_in; std::memcpy(&pixel_in, pixels_in, sizeof(u16)); pixels_in += sizeof(u16); diff --git a/src/util/image.h b/src/util/image.h index 400ec0b42..c57548e0f 100644 --- a/src/util/image.h +++ b/src/util/image.h @@ -20,7 +20,7 @@ enum class ImageFormat : u8 RGBA8, BGRA8, RGB565, - RGBA5551, + RGB5A1, BGR8, BC1, BC2, diff --git a/src/util/metal_device.mm b/src/util/metal_device.mm index 343469013..26b504230 100644 --- a/src/util/metal_device.mm +++ b/src/util/metal_device.mm @@ -50,7 +50,7 @@ static constexpr std::array(GPUTexture::Format: MTLPixelFormatRGBA8Unorm, // RGBA8 MTLPixelFormatBGRA8Unorm, // BGRA8 MTLPixelFormatB5G6R5Unorm, // RGB565 - MTLPixelFormatA1BGR5Unorm, // RGBA5551 + MTLPixelFormatA1BGR5Unorm, // RGB5A1 MTLPixelFormatR8Unorm, // R8 MTLPixelFormatDepth16Unorm, // D16 MTLPixelFormatDepth24Unorm_Stencil8, // D24S8 @@ -1490,7 +1490,7 @@ std::unique_ptr MetalDevice::CreateSampler(const GPUSampler::Config& bool MetalDevice::SupportsTextureFormat(GPUTexture::Format format) const { - if (format == GPUTexture::Format::RGB565 || format == GPUTexture::Format::RGBA5551) + if (format == GPUTexture::Format::RGB565 || format == GPUTexture::Format::RGB5A1) { // These formats require an Apple Silicon GPU. // See https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf diff --git a/src/util/opengl_context_egl.cpp b/src/util/opengl_context_egl.cpp index 372b4e321..43f6d8ab8 100644 --- a/src/util/opengl_context_egl.cpp +++ b/src/util/opengl_context_egl.cpp @@ -423,7 +423,7 @@ bool OpenGLContextEGL::CheckConfigSurfaceFormat(EGLConfig config, GPUTexture::Fo case GPUTexture::Format::RGB565: return (red_size == 5 && green_size == 6 && blue_size == 5); - case GPUTexture::Format::RGBA5551: + case GPUTexture::Format::RGB5A1: return (red_size == 5 && green_size == 5 && blue_size == 5 && alpha_size == 1); case GPUTexture::Format::Unknown: @@ -461,7 +461,7 @@ void OpenGLContextEGL::UpdateWindowInfoSize(WindowInfo& wi, EGLSurface surface) } else if (red_size == 5 && green_size == 5 && blue_size == 5 && alpha_size == 1) { - wi.surface_format = GPUTexture::Format::RGBA5551; + wi.surface_format = GPUTexture::Format::RGB5A1; } else if (red_size == 8 && green_size == 8 && blue_size == 8 && alpha_size == 8) { diff --git a/src/util/opengl_texture.cpp b/src/util/opengl_texture.cpp index f26b68c39..9aa63f020 100644 --- a/src/util/opengl_texture.cpp +++ b/src/util/opengl_texture.cpp @@ -37,7 +37,7 @@ const std::tuple& OpenGLTexture::GetPixelFormatMapping(G {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}, // RGBA8 {GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE}, // BGRA8 {GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // RGB565 - {GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // RGBA5551 + {GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // RGB5A1 {GL_R8, GL_RED, GL_UNSIGNED_BYTE}, // R8 {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_SHORT}, // D16 {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT}, // D24S8 @@ -71,7 +71,7 @@ const std::tuple& OpenGLTexture::GetPixelFormatMapping(G {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}, // RGBA8 {GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE}, // BGRA8 {GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // RGB565 - {GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // RGBA5551 + {GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // RGB5A1 {GL_R8, GL_RED, GL_UNSIGNED_BYTE}, // R8 {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_SHORT}, // D16 {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT}, // D24S8 diff --git a/src/util/vulkan_device.cpp b/src/util/vulkan_device.cpp index b6061b8a6..6f99076cd 100644 --- a/src/util/vulkan_device.cpp +++ b/src/util/vulkan_device.cpp @@ -75,7 +75,7 @@ const std::array(GPUTexture::Format::MaxCount)> Vulka VK_FORMAT_R8G8B8A8_UNORM, // RGBA8 VK_FORMAT_B8G8R8A8_UNORM, // BGRA8 VK_FORMAT_R5G6B5_UNORM_PACK16, // RGB565 - VK_FORMAT_R5G5B5A1_UNORM_PACK16, // RGBA5551 + VK_FORMAT_A1R5G5B5_UNORM_PACK16, // RGB5A1 VK_FORMAT_R8_UNORM, // R8 VK_FORMAT_D16_UNORM, // D16 VK_FORMAT_D24_UNORM_S8_UINT, // D24S8 From c6e2235ee2126d15a58bbaae88c0146ecd6bb28c Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 30 Nov 2024 14:01:58 +1000 Subject: [PATCH 23/69] GPU/HW: Vectorize texture conversion routines ~100% speed up for RGBA8, ~50% for RGB5A1. --- src/core/gpu_hw_texture_cache.cpp | 279 ++++++++++++++++++++++++------ 1 file changed, 227 insertions(+), 52 deletions(-) diff --git a/src/core/gpu_hw_texture_cache.cpp b/src/core/gpu_hw_texture_cache.cpp index bf0bca50d..389e3c443 100644 --- a/src/core/gpu_hw_texture_cache.cpp +++ b/src/core/gpu_hw_texture_cache.cpp @@ -242,6 +242,7 @@ static bool ShouldTrackVRAMWrites(); static bool IsDumpingVRAMWriteTextures(); static void UpdateVRAMTrackingState(); +static void SetHashCacheTextureFormat(); static bool CompilePipelines(); static void DestroyPipelines(); @@ -280,11 +281,14 @@ static void RemoveVRAMWrite(VRAMWrite* entry); static void DumpTexturesFromVRAMWrite(VRAMWrite* entry); static void DumpTextureFromPage(const Source* src); -static void DecodeTexture(GPUTextureMode mode, const u16* page_ptr, const u16* palette, u32* dest, u32 dest_stride, - u32 width, u32 height); -static void DecodeTexture4(const u16* page, const u16* palette, u32 width, u32 height, u32* dest, u32 dest_stride); -static void DecodeTexture8(const u16* page, const u16* palette, u32 width, u32 height, u32* dest, u32 dest_stride); -static void DecodeTexture16(const u16* page, u32 width, u32 height, u32* dest, u32 dest_stride); +static void DecodeTexture(GPUTextureMode mode, const u16* page_ptr, const u16* palette, u8* dest, u32 dest_stride, + u32 width, u32 height, GPUTexture::Format dest_format); +template +static void DecodeTexture4(const u16* page, const u16* palette, u32 width, u32 height, u8* dest, u32 dest_stride); +template +static void DecodeTexture8(const u16* page, const u16* palette, u32 width, u32 height, u8* dest, u32 dest_stride); +template +static void DecodeTexture16(const u16* page, u32 width, u32 height, u8* dest, u32 dest_stride); static void DecodeTexture(u8 page, GPUTexturePaletteReg palette, GPUTextureMode mode, GPUTexture* texture); static std::optional GetTextureReplacementTypeFromFileTitle(const std::string_view file_title); @@ -512,6 +516,7 @@ struct GPUTextureCacheState VRAMWrite* last_vram_write = nullptr; bool track_vram_writes = false; + GPUTexture::Format hash_cache_texture_format = GPUTexture::Format::Unknown; HashCache hash_cache; /// List of candidates for purging when the hash cache gets too large. @@ -568,6 +573,7 @@ bool GPUTextureCache::IsDumpingVRAMWriteTextures() bool GPUTextureCache::Initialize() { + SetHashCacheTextureFormat(); LoadLocalConfiguration(false, false); UpdateVRAMTrackingState(); if (!CompilePipelines()) @@ -770,6 +776,21 @@ void GPUTextureCache::Shutdown() s_state.game_id = {}; } +void GPUTextureCache::SetHashCacheTextureFormat() +{ +#if 0 + // Prefer 16-bit texture formats where possible. + if (g_gpu_device->SupportsTextureFormat(GPUTexture::Format::RGB5A1)) + s_state.hash_cache_texture_format = GPUTexture::Format::RGB5A1; + else + s_state.hash_cache_texture_format = GPUTexture::Format::RGBA8; + + INFO_LOG("Using {} format for hash cache entries.", GPUTexture::GetFormatName(s_state.hash_cache_texture_format)); +#else + s_state.hash_cache_texture_format = GPUTexture::Format::RGBA8; +#endif +} + bool GPUTextureCache::CompilePipelines() { if (!g_settings.texture_replacements.enable_texture_replacements) @@ -1063,29 +1084,116 @@ ALWAYS_INLINE_RELEASE static const u16* VRAMPalettePointer(GPUTexturePaletteReg return &g_vram[VRAM_WIDTH * palette.GetYBase() + palette.GetXBase()]; } -// TODO: Vectorize these with gather. -void GPUTextureCache::DecodeTexture4(const u16* page, const u16* palette, u32 width, u32 height, u32* dest, +template +ALWAYS_INLINE static void WriteDecodedTexel(u8*& dest, u16 c16) +{ + if constexpr (format == GPUTexture::Format::RGBA8) + { + const u32 c32 = VRAMRGBA5551ToRGBA8888(c16); + std::memcpy(std::assume_aligned(dest), &c32, sizeof(c32)); + dest += sizeof(c32); + } + else if constexpr (format == GPUTexture::Format::RGB5A1) + { + const u16 repacked = (c16 & 0x83E0) | ((c16 >> 10) & 0x1F) | ((c16 & 0x1F) << 10); + std::memcpy(std::assume_aligned(dest), &repacked, sizeof(repacked)); + dest += sizeof(repacked); + } +} + +#ifdef CPU_ARCH_SIMD + +ALWAYS_INLINE static GSVector4i VRAM5BitTo8Bit(GSVector4i val) +{ + return val.mul32l(GSVector4i::cxpr(527)).add32(GSVector4i::cxpr(23)).srl32<6>(); +} + +ALWAYS_INLINE static GSVector4i VRAMRGB5A1ToRGBA8888(GSVector4i val) +{ + static constexpr GSVector4i cmask = GSVector4i::cxpr(0x1F); + + const GSVector4i r = VRAM5BitTo8Bit(val & cmask); + const GSVector4i g = VRAM5BitTo8Bit((val.srl32<5>() & cmask)); + const GSVector4i b = VRAM5BitTo8Bit((val.srl32<10>() & cmask)); + const GSVector4i a = val.srl32<15>().sll32<31>().sra32<7>(); + + return r | g.sll32<8>() | b.sll32<16>() | b.sll32<24>() | a; +} + +template +ALWAYS_INLINE static void WriteDecodedTexels(u8*& dest, GSVector4i c16) +{ + if constexpr (format == GPUTexture::Format::RGBA8) + { + const GSVector4i low = VRAMRGB5A1ToRGBA8888(c16.upl16()); + const GSVector4i high = VRAMRGB5A1ToRGBA8888(c16.uph16()); + + GSVector4i::store(dest, low); + dest += sizeof(GSVector4i); + + GSVector4i::store(dest, high); + dest += sizeof(GSVector4i); + } + else if constexpr (format == GPUTexture::Format::RGB5A1) + { + static constexpr GSVector4i cmask = GSVector4i::cxpr16(0x1F); + + const GSVector4i repacked = + (c16 & GSVector4i::cxpr16(static_cast(0x83E0))) | (c16.srl16<10>() & cmask) | (c16 & cmask).sll16<10>(); + + GSVector4i::store(dest, repacked); + dest += sizeof(GSVector4i); + } +} + +#endif + +template +void GPUTextureCache::DecodeTexture4(const u16* page, const u16* palette, u32 width, u32 height, u8* dest, u32 dest_stride) { if ((width % 4u) == 0) { const u32 vram_width = width / 4; + [[maybe_unused]] constexpr u32 vram_pixels_per_vec = 2; + [[maybe_unused]] const u32 aligned_vram_width = Common::AlignDownPow2(vram_width, vram_pixels_per_vec); + for (u32 y = 0; y < height; y++) { const u16* page_ptr = page; - u32* dest_ptr = dest; + u8* dest_ptr = dest; + u32 x = 0; - for (u32 x = 0; x < vram_width; x++) +#ifdef CPU_ARCH_SIMD + for (; x < aligned_vram_width; x += vram_pixels_per_vec) + { + // No variable shift without AVX, kinda pointless to vectorize the extract... + alignas(VECTOR_ALIGNMENT) u16 c16[vram_pixels_per_vec * 4]; + u32 pp = *(page_ptr++); + c16[0] = palette[pp & 0x0F]; + c16[1] = palette[(pp >> 4) & 0x0F]; + c16[2] = palette[(pp >> 8) & 0x0F]; + c16[3] = palette[pp >> 12]; + pp = *(page_ptr++); + c16[4] = palette[pp & 0x0F]; + c16[5] = palette[(pp >> 4) & 0x0F]; + c16[6] = palette[(pp >> 8) & 0x0F]; + c16[7] = palette[pp >> 12]; + WriteDecodedTexels(dest_ptr, GSVector4i::load(c16)); + } +#endif + + for (; x < vram_width; x++) { const u32 pp = *(page_ptr++); - *(dest_ptr++) = VRAMRGBA5551ToRGBA8888(palette[pp & 0x0F]); - *(dest_ptr++) = VRAMRGBA5551ToRGBA8888(palette[(pp >> 4) & 0x0F]); - *(dest_ptr++) = VRAMRGBA5551ToRGBA8888(palette[(pp >> 8) & 0x0F]); - *(dest_ptr++) = VRAMRGBA5551ToRGBA8888(palette[pp >> 12]); + WriteDecodedTexel(dest_ptr, palette[pp & 0x0F]); + WriteDecodedTexel(dest_ptr, palette[(pp >> 4) & 0x0F]); + WriteDecodedTexel(dest_ptr, palette[(pp >> 8) & 0x0F]); + WriteDecodedTexel(dest_ptr, palette[pp >> 12]); } page += VRAM_WIDTH; - dest = reinterpret_cast(reinterpret_cast(dest) + dest_stride); + dest += dest_stride; } } else @@ -1093,7 +1201,7 @@ void GPUTextureCache::DecodeTexture4(const u16* page, const u16* palette, u32 wi for (u32 y = 0; y < height; y++) { const u16* page_ptr = page; - u32* dest_ptr = dest; + u8* dest_ptr = dest; u32 offs = 0; u16 texel = 0; @@ -1102,37 +1210,64 @@ void GPUTextureCache::DecodeTexture4(const u16* page, const u16* palette, u32 wi if (offs == 0) texel = *(page_ptr++); - *(dest_ptr++) = VRAMRGBA5551ToRGBA8888(palette[texel & 0x0F]); + WriteDecodedTexel(dest_ptr, palette[texel & 0x0F]); texel >>= 4; offs = (offs + 1) % 4; } page += VRAM_WIDTH; - dest = reinterpret_cast(reinterpret_cast(dest) + dest_stride); + dest += dest_stride; } } } -void GPUTextureCache::DecodeTexture8(const u16* page, const u16* palette, u32 width, u32 height, u32* dest, + +template +void GPUTextureCache::DecodeTexture8(const u16* page, const u16* palette, u32 width, u32 height, u8* dest, u32 dest_stride) { if ((width % 2u) == 0) { const u32 vram_width = width / 2; + [[maybe_unused]] constexpr u32 vram_pixels_per_vec = 4; + [[maybe_unused]] const u32 aligned_vram_width = Common::AlignDownPow2(vram_width, vram_pixels_per_vec); + for (u32 y = 0; y < height; y++) { const u16* page_ptr = page; - u32* dest_ptr = dest; + u8* dest_ptr = dest; + u32 x = 0; - for (u32 x = 0; x < vram_width; x++) +#ifdef CPU_ARCH_SIMD + for (; x < aligned_vram_width; x += vram_pixels_per_vec) + { + // No variable shift without AVX, kinda pointless to vectorize the extract... + alignas(VECTOR_ALIGNMENT) u16 c16[vram_pixels_per_vec * 2]; + u32 pp = *(page_ptr++); + c16[0] = palette[pp & 0xFF]; + c16[1] = palette[(pp >> 8) & 0xFF]; + pp = *(page_ptr++); + c16[2] = palette[pp & 0xFF]; + c16[3] = palette[(pp >> 8) & 0xFF]; + pp = *(page_ptr++); + c16[4] = palette[pp & 0xFF]; + c16[5] = palette[(pp >> 8) & 0xFF]; + pp = *(page_ptr++); + c16[6] = palette[pp & 0xFF]; + c16[7] = palette[(pp >> 8) & 0xFF]; + WriteDecodedTexels(dest_ptr, GSVector4i::load(c16)); + } +#endif + + for (; x < vram_width; x++) { const u32 pp = *(page_ptr++); - *(dest_ptr++) = VRAMRGBA5551ToRGBA8888(palette[pp & 0xFF]); - *(dest_ptr++) = VRAMRGBA5551ToRGBA8888(palette[pp >> 8]); + WriteDecodedTexel(dest_ptr, palette[pp & 0xFF]); + WriteDecodedTexel(dest_ptr, palette[pp >> 8]); } page += VRAM_WIDTH; - dest = reinterpret_cast(reinterpret_cast(dest) + dest_stride); + dest += dest_stride; } } else @@ -1140,7 +1275,7 @@ void GPUTextureCache::DecodeTexture8(const u16* page, const u16* palette, u32 wi for (u32 y = 0; y < height; y++) { const u16* page_ptr = page; - u32* dest_ptr = dest; + u8* dest_ptr = dest; u32 offs = 0; u16 texel = 0; @@ -1149,70 +1284,110 @@ void GPUTextureCache::DecodeTexture8(const u16* page, const u16* palette, u32 wi if (offs == 0) texel = *(page_ptr++); - *(dest_ptr++) = VRAMRGBA5551ToRGBA8888(palette[texel & 0xFF]); + WriteDecodedTexel(dest_ptr, palette[texel & 0xFF]); texel >>= 8; offs ^= 1; } page += VRAM_WIDTH; - dest = reinterpret_cast(reinterpret_cast(dest) + dest_stride); + dest += dest_stride; } } } -void GPUTextureCache::DecodeTexture16(const u16* page, u32 width, u32 height, u32* dest, u32 dest_stride) +template +void GPUTextureCache::DecodeTexture16(const u16* page, u32 width, u32 height, u8* dest, u32 dest_stride) { + [[maybe_unused]] constexpr u32 pixels_per_vec = 8; + [[maybe_unused]] const u32 aligned_width = Common::AlignDownPow2(width, pixels_per_vec); + for (u32 y = 0; y < height; y++) { const u16* page_ptr = page; - u32* dest_ptr = dest; + u8* dest_ptr = dest; + u32 x = 0; - for (u32 x = 0; x < width; x++) - *(dest_ptr++) = VRAMRGBA5551ToRGBA8888(*(page_ptr++)); +#ifdef CPU_ARCH_SIMD + for (; x < aligned_width; x += pixels_per_vec) + { + WriteDecodedTexels(dest_ptr, GSVector4i::load(page_ptr)); + page_ptr += pixels_per_vec; + } +#endif + + for (; x < width; x++) + WriteDecodedTexel(dest_ptr, *(page_ptr++)); page += VRAM_WIDTH; - dest = reinterpret_cast(reinterpret_cast(dest) + dest_stride); + dest += dest_stride; } } -void GPUTextureCache::DecodeTexture(GPUTextureMode mode, const u16* page_ptr, const u16* palette, u32* dest, - u32 dest_stride, u32 width, u32 height) +void GPUTextureCache::DecodeTexture(GPUTextureMode mode, const u16* page_ptr, const u16* palette, u8* dest, + u32 dest_stride, u32 width, u32 height, GPUTexture::Format dest_format) { - switch (mode) + if (dest_format == GPUTexture::Format::RGBA8) { - case GPUTextureMode::Palette4Bit: - DecodeTexture4(page_ptr, palette, width, height, dest, dest_stride); - break; - case GPUTextureMode::Palette8Bit: - DecodeTexture8(page_ptr, palette, width, height, dest, dest_stride); - break; - case GPUTextureMode::Direct16Bit: - case GPUTextureMode::Reserved_Direct16Bit: - DecodeTexture16(page_ptr, width, height, dest, dest_stride); - break; + switch (mode) + { + case GPUTextureMode::Palette4Bit: + DecodeTexture4(page_ptr, palette, width, height, dest, dest_stride); + break; + case GPUTextureMode::Palette8Bit: + DecodeTexture8(page_ptr, palette, width, height, dest, dest_stride); + break; + case GPUTextureMode::Direct16Bit: + case GPUTextureMode::Reserved_Direct16Bit: + DecodeTexture16(page_ptr, width, height, dest, dest_stride); + break; - DefaultCaseIsUnreachable() + DefaultCaseIsUnreachable() + } + } + else if (dest_format == GPUTexture::Format::RGB5A1) + { + switch (mode) + { + case GPUTextureMode::Palette4Bit: + DecodeTexture4(page_ptr, palette, width, height, dest, dest_stride); + break; + case GPUTextureMode::Palette8Bit: + DecodeTexture8(page_ptr, palette, width, height, dest, dest_stride); + break; + case GPUTextureMode::Direct16Bit: + case GPUTextureMode::Reserved_Direct16Bit: + DecodeTexture16(page_ptr, width, height, dest, dest_stride); + break; + + DefaultCaseIsUnreachable() + } + } + else + { + Panic("Unsupported texture format."); } } void GPUTextureCache::DecodeTexture(u8 page, GPUTexturePaletteReg palette, GPUTextureMode mode, GPUTexture* texture) { - alignas(16) static u32 s_temp_buffer[TEXTURE_PAGE_WIDTH * TEXTURE_PAGE_HEIGHT]; + alignas(16) static u8 s_temp_buffer[TEXTURE_PAGE_WIDTH * TEXTURE_PAGE_HEIGHT * sizeof(u32)]; - u32* tex_map; + const u32 ps = texture->GetPixelSize(); + u8* tex_map; u32 tex_stride; const bool mapped = texture->Map(reinterpret_cast(&tex_map), &tex_stride, 0, 0, TEXTURE_PAGE_WIDTH, TEXTURE_PAGE_HEIGHT); if (!mapped) { tex_map = s_temp_buffer; - tex_stride = sizeof(u32) * TEXTURE_PAGE_WIDTH; + tex_stride = Common::AlignUpPow2(ps * TEXTURE_PAGE_WIDTH, 4); } const u16* page_ptr = VRAMPagePointer(page); const u16* palette_ptr = TextureModeHasPalette(mode) ? VRAMPalettePointer(palette) : nullptr; - DecodeTexture(mode, page_ptr, palette_ptr, tex_map, tex_stride, TEXTURE_PAGE_WIDTH, TEXTURE_PAGE_HEIGHT); + DecodeTexture(mode, page_ptr, palette_ptr, tex_map, tex_stride, TEXTURE_PAGE_WIDTH, TEXTURE_PAGE_HEIGHT, + texture->GetFormat()); if (mapped) texture->Unmap(); @@ -2064,7 +2239,7 @@ GPUTextureCache::HashCacheEntry* GPUTextureCache::LookupHashCache(SourceKey key, entry.sources = {}; entry.texture = g_gpu_device->FetchTexture(TEXTURE_PAGE_WIDTH, TEXTURE_PAGE_HEIGHT, 1, 1, 1, GPUTexture::Type::Texture, - GPUTexture::Format::RGBA8, GPUTexture::Flags::None); + s_state.hash_cache_texture_format, GPUTexture::Flags::None); if (!entry.texture) { ERROR_LOG("Failed to create texture."); @@ -2616,8 +2791,8 @@ void GPUTextureCache::DumpTexture(TextureReplacementType type, u32 offset_x, u32 DEV_LOG("Dumping VRAM write {:016X} [{}x{}] at {}", src_hash, width, height, rect); Image image(width, height, ImageFormat::RGBA8); - GPUTextureCache::DecodeTexture(mode, &g_vram[rect.top * VRAM_WIDTH + rect.left], palette_data, - reinterpret_cast(image.GetPixels()), image.GetPitch(), width, height); + GPUTextureCache::DecodeTexture(mode, &g_vram[rect.top * VRAM_WIDTH + rect.left], palette_data, image.GetPixels(), + image.GetPitch(), width, height, GPUTexture::Format::RGBA8); // TODO: Vectorize this. u32* image_pixels = reinterpret_cast(image.GetPixels()); From 53008eb34a725d6c46483c525f7e28be3301ee67 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 30 Nov 2024 14:03:16 +1000 Subject: [PATCH 24/69] GPU/HW: Allow use of RGB5A1 for texture cache Reduces bandwidth and storage requirements by 50%. --- src/core/gpu_hw_texture_cache.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/core/gpu_hw_texture_cache.cpp b/src/core/gpu_hw_texture_cache.cpp index 389e3c443..b37cad53b 100644 --- a/src/core/gpu_hw_texture_cache.cpp +++ b/src/core/gpu_hw_texture_cache.cpp @@ -778,7 +778,6 @@ void GPUTextureCache::Shutdown() void GPUTextureCache::SetHashCacheTextureFormat() { -#if 0 // Prefer 16-bit texture formats where possible. if (g_gpu_device->SupportsTextureFormat(GPUTexture::Format::RGB5A1)) s_state.hash_cache_texture_format = GPUTexture::Format::RGB5A1; @@ -786,9 +785,6 @@ void GPUTextureCache::SetHashCacheTextureFormat() s_state.hash_cache_texture_format = GPUTexture::Format::RGBA8; INFO_LOG("Using {} format for hash cache entries.", GPUTexture::GetFormatName(s_state.hash_cache_texture_format)); -#else - s_state.hash_cache_texture_format = GPUTexture::Format::RGBA8; -#endif } bool GPUTextureCache::CompilePipelines() From f1435dcf6784a7b7d6bda646c85fa4bd3e2b92ab Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 30 Nov 2024 14:04:55 +1000 Subject: [PATCH 25/69] Settings: Bump maximum hash cache size Hopefully mobile will be okay with it.. I know Adreno GL craps out around 8,000 texture objects. --- src/core/settings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/settings.h b/src/core/settings.h index 1a1b25db8..d63345e7b 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -234,7 +234,7 @@ struct Settings { struct Configuration { - static constexpr u32 DEFAULT_MAX_HASH_CACHE_ENTRIES = 500; + static constexpr u32 DEFAULT_MAX_HASH_CACHE_ENTRIES = 1200; static constexpr u32 DEFAULT_MAX_HASH_CACHE_VRAM_USAGE_MB = 2048; static constexpr u32 DEFAULT_MAX_REPLACEMENT_CACHE_VRAM_USAGE_MB = 512; From 9dec34c8c013c22d9441ba4b16be3408babd91a6 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 30 Nov 2024 14:42:49 +1000 Subject: [PATCH 26/69] Settings: Disable texture replacements if TC disabled Stops replacements being enumerated in the software renderer as well. --- src/core/settings.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/settings.cpp b/src/core/settings.cpp index f3fe7078b..b89fb15a9 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -996,6 +996,11 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages) g_settings.gpu_pgxp_disable_2d = false; } + // texture replacements are not available without the TC or with the software renderer + g_settings.texture_replacements.enable_texture_replacements &= + (g_settings.gpu_renderer != GPURenderer::Software && g_settings.gpu_texture_cache); + g_settings.texture_replacements.enable_vram_write_replacements &= (g_settings.gpu_renderer != GPURenderer::Software); + #ifndef ENABLE_MMAP_FASTMEM if (g_settings.cpu_fastmem_mode == CPUFastmemMode::MMap) { From 145ad2db2776b66262085ceb188b2d7d88700c46 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 1 Dec 2024 13:10:20 +1000 Subject: [PATCH 27/69] GameList: Fix scanning of ELF files --- src/core/game_list.cpp | 13 +++++++++++++ src/util/elf_file.cpp | 35 ++++++++++++++++++++++++++++++++--- src/util/elf_file.h | 3 +++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp index 8894a7d9a..3f3520ed5 100644 --- a/src/core/game_list.cpp +++ b/src/core/game_list.cpp @@ -11,6 +11,7 @@ #include "system.h" #include "util/cd_image.h" +#include "util/elf_file.h" #include "util/http_downloader.h" #include "util/image.h" #include "util/ini_settings_interface.h" @@ -199,6 +200,18 @@ bool GameList::GetExeListEntry(const std::string& path, GameList::Entry* entry) // Who knows entry->region = DiscRegion::Other; } + else if (StringUtil::EndsWithNoCase(filename, ".elf")) + { + ELFFile::Elf32_Ehdr header; + if (std::fread(&header, sizeof(header), 1, fp.get()) != 1 || !ELFFile::IsValidElfHeader(header)) + { + WARNING_LOG("{} is not a valid ELF.", path); + return false; + } + + // Who knows + entry->region = DiscRegion::Other; + } else { BIOS::PSEXEHeader header; diff --git a/src/util/elf_file.cpp b/src/util/elf_file.cpp index 0e92a8f33..1ae3f298f 100644 --- a/src/util/elf_file.cpp +++ b/src/util/elf_file.cpp @@ -9,6 +9,7 @@ LOG_CHANNEL(FileLoader); +static constexpr const u8 EXPECTED_ELF_HEADER[4] = {'\177', 'E', 'L', 'F'}; static constexpr s64 MAX_ELF_FILE_SIZE = 32 * 1024 * 1024; ELFFile::ELFFile() = default; @@ -113,9 +114,8 @@ bool ELFFile::Open(DataArray data, Error* error) { m_data = std::move(data); - static constexpr const u8 EXPECTED_HEADER[4] = {'\177', 'E', 'L', 'F'}; - - if (m_data.size() < sizeof(Elf32_Ehdr) || std::memcmp(m_data.data(), EXPECTED_HEADER, sizeof(EXPECTED_HEADER)) != 0) + if (m_data.size() < sizeof(Elf32_Ehdr) || + std::memcmp(m_data.data(), EXPECTED_ELF_HEADER, sizeof(EXPECTED_ELF_HEADER)) != 0) { Error::SetStringView(error, "Invalid header."); return false; @@ -202,3 +202,32 @@ bool ELFFile::LoadExecutableSections(const LoadExecutableSectionCallback& callba return true; } + +bool ELFFile::IsValidElfHeader(const std::span data, Error* error /*= nullptr*/) +{ + if (data.size() < sizeof(Elf32_Ehdr)) + { + Error::SetStringView(error, "Invalid header."); + return false; + } + + return IsValidElfHeader(reinterpret_cast(*data.data()), error); +} + +bool ELFFile::IsValidElfHeader(const Elf32_Ehdr& header, Error* error /* = nullptr */) +{ + if (std::memcmp(header.e_ident, EXPECTED_ELF_HEADER, sizeof(EXPECTED_ELF_HEADER)) != 0) + { + Error::SetStringView(error, "Invalid header."); + return false; + } + + if (header.e_machine != EM_MIPS) + { + Error::SetStringFmt(error, "Unsupported machine type {}.", header.e_machine); + return false; + } + + // probably fine + return true; +} diff --git a/src/util/elf_file.h b/src/util/elf_file.h index 29d5205de..7f4fb33bf 100644 --- a/src/util/elf_file.h +++ b/src/util/elf_file.h @@ -94,6 +94,9 @@ public: ELFFile(); ~ELFFile(); + static bool IsValidElfHeader(const std::span data, Error* error = nullptr); + static bool IsValidElfHeader(const Elf32_Ehdr& header, Error* error = nullptr); + const Elf32_Ehdr& GetELFHeader() const; u32 GetEntryPoint() const; From 67041d217b780c1ceedd15795d20bad11909d61a Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 1 Dec 2024 13:32:24 +1000 Subject: [PATCH 28/69] Qt: Improve shortcuts - CTRL/+, CTRL/- no longer show in menu for zooming, but still activate. - CTRL+O will open a new disc/game from file. - F5 will refresh the game list (i.e. scan for new games). - F3/CTRL+F will send focus to the game list search box. - Pressing Enter in the search box will send focus to the first game list row. - ALT+ENTER in the game list will open Game Properties. --- src/duckstation-qt/autoupdaterdialog.cpp | 10 ++++- src/duckstation-qt/gamelistwidget.cpp | 33 ++++++++++++++++- src/duckstation-qt/gamelistwidget.h | 2 + src/duckstation-qt/mainwindow.cpp | 47 +++++++++++++++++++----- src/duckstation-qt/mainwindow.h | 13 +++++++ src/duckstation-qt/mainwindow.ui | 16 -------- src/duckstation-qt/qthost.cpp | 7 +--- 7 files changed, 95 insertions(+), 33 deletions(-) diff --git a/src/duckstation-qt/autoupdaterdialog.cpp b/src/duckstation-qt/autoupdaterdialog.cpp index e5f7d8365..140742cd0 100644 --- a/src/duckstation-qt/autoupdaterdialog.cpp +++ b/src/duckstation-qt/autoupdaterdialog.cpp @@ -138,8 +138,14 @@ bool AutoUpdaterDialog::warnAboutUnofficialBuild() #if !__has_include("scmversion/tag.h") && !defined(_DEBUG) constexpr const char* CONFIG_SECTION = "UI"; constexpr const char* CONFIG_KEY = "UnofficialBuildWarningConfirmed"; - if (Host::GetBaseBoolSettingValue(CONFIG_SECTION, CONFIG_KEY, false)) + if ( +#ifndef _WIN32 + !StringUtil::StartsWithNoCase(EmuFolders::AppRoot, "/usr") && +#endif + Host::GetBaseBoolSettingValue(CONFIG_SECTION, CONFIG_KEY, false)) + { return true; + } constexpr int DELAY_SECONDS = 5; @@ -155,6 +161,8 @@ bool AutoUpdaterDialog::warnAboutUnofficialBuild() mbox.setIcon(QMessageBox::Warning); mbox.setWindowTitle(QStringLiteral("Unofficial Build Warning")); mbox.setWindowIcon(QtHost::GetAppIcon()); + mbox.setWindowFlag(Qt::CustomizeWindowHint, true); + mbox.setWindowFlag(Qt::WindowCloseButtonHint, false); mbox.setTextFormat(Qt::RichText); mbox.setText(message); diff --git a/src/duckstation-qt/gamelistwidget.cpp b/src/duckstation-qt/gamelistwidget.cpp index 0162fdaee..b912c9529 100644 --- a/src/duckstation-qt/gamelistwidget.cpp +++ b/src/duckstation-qt/gamelistwidget.cpp @@ -6,6 +6,7 @@ #include "gamelistrefreshthread.h" #include "qthost.h" #include "qtutils.h" +#include "settingswindow.h" #include "core/game_list.h" #include "core/host.h" @@ -181,6 +182,7 @@ void GameListWidget::initialize() }); connect(m_ui.searchText, &QLineEdit::textChanged, this, [this](const QString& text) { m_sort_model->setFilterName(text); }); + connect(m_ui.searchText, &QLineEdit::returnPressed, this, &GameListWidget::onSearchReturnPressed); m_table_view = new QTableView(m_ui.stack); m_table_view->setModel(m_sort_model); @@ -368,7 +370,17 @@ void GameListWidget::onTableViewItemActivated(const QModelIndex& index) if (!source_index.isValid() || source_index.row() >= static_cast(GameList::GetEntryCount())) return; - emit entryActivated(); + if (qApp->keyboardModifiers().testFlag(Qt::AltModifier)) + { + const auto lock = GameList::GetLock(); + const GameList::Entry* entry = GameList::GetEntryByIndex(static_cast(source_index.row())); + if (entry) + SettingsWindow::openGamePropertiesDialog(entry->path, entry->title, entry->serial, entry->hash, entry->region); + } + else + { + emit entryActivated(); + } } void GameListWidget::onTableViewContextMenuRequested(const QPoint& point) @@ -466,6 +478,25 @@ void GameListWidget::refreshGridCovers() m_model->refreshCovers(); } +void GameListWidget::focusSearchWidget() +{ + m_ui.searchText->setFocus(Qt::ShortcutFocusReason); +} + +void GameListWidget::onSearchReturnPressed() +{ + // Anything to switch focus to? + const int rows = m_sort_model->rowCount(); + if (rows == 0) + return; + + QAbstractItemView* const target = + isShowingGameGrid() ? static_cast(m_list_view) : static_cast(m_table_view); + target->selectionModel()->select(m_sort_model->index(0, 0), + QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + target->setFocus(Qt::ShortcutFocusReason); +} + void GameListWidget::showGameList() { if (m_ui.stack->currentIndex() == 0 || m_model->rowCount() == 0) diff --git a/src/duckstation-qt/gamelistwidget.h b/src/duckstation-qt/gamelistwidget.h index 758181e72..02ef50783 100644 --- a/src/duckstation-qt/gamelistwidget.h +++ b/src/duckstation-qt/gamelistwidget.h @@ -80,6 +80,7 @@ private Q_SLOTS: void onListViewItemActivated(const QModelIndex& index); void onListViewContextMenuRequested(const QPoint& point); void onCoverScaleChanged(); + void onSearchReturnPressed(); public Q_SLOTS: void showGameList(); @@ -91,6 +92,7 @@ public Q_SLOTS: void gridZoomOut(); void gridIntScale(int int_scale); void refreshGridCovers(); + void focusSearchWidget(); protected: void resizeEvent(QResizeEvent* event); diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 237cc1f3c..44e349107 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -1263,6 +1264,11 @@ void MainWindow::onRemoveDiscActionTriggered() g_emu_thread->changeDisc(QString(), false, true); } +void MainWindow::onScanForNewGamesTriggered() +{ + refreshGameList(false); +} + void MainWindow::onViewToolbarActionToggled(bool checked) { Host::SetBaseBoolSettingValue("UI", "ShowToolbar", checked); @@ -1302,6 +1308,18 @@ void MainWindow::onViewSystemDisplayTriggered() switchToEmulationView(); } +void MainWindow::onViewGameGridZoomInActionTriggered() +{ + if (isShowingGameList()) + m_game_list_widget->gridZoomIn(); +} + +void MainWindow::onViewGameGridZoomOutActionTriggered() +{ + if (isShowingGameList()) + m_game_list_widget->gridZoomOut(); +} + void MainWindow::onGitHubRepositoryActionTriggered() { QtUtils::OpenURL(this, "https://github.com/stenzek/duckstation/"); @@ -1626,6 +1644,17 @@ void MainWindow::setupAdditionalUi() updateDebugMenuVisibility(); + m_shortcuts.open_file = + new QShortcut(Qt::ControlModifier | Qt::Key_O, this, this, &MainWindow::onStartFileActionTriggered); + m_shortcuts.game_list_refresh = new QShortcut(Qt::Key_F5, this, this, &MainWindow::onScanForNewGamesTriggered); + m_shortcuts.game_list_search = new QShortcut(this); + m_shortcuts.game_list_search->setKeys({Qt::ControlModifier | Qt::Key_F, Qt::Key_F3}); + connect(m_shortcuts.game_list_search, &QShortcut::activated, m_game_list_widget, &GameListWidget::focusSearchWidget); + m_shortcuts.game_grid_zoom_in = + new QShortcut(Qt::ControlModifier | Qt::Key_Plus, this, this, &MainWindow::onViewGameGridZoomInActionTriggered); + m_shortcuts.game_grid_zoom_out = + new QShortcut(Qt::ControlModifier | Qt::Key_Minus, this, this, &MainWindow::onViewGameGridZoomOutActionTriggered); + #ifdef ENABLE_RAINTEGRATION if (Achievements::IsUsingRAIntegration()) { @@ -1691,6 +1720,12 @@ void MainWindow::updateEmulationActions(bool starting, bool running, bool cheevo m_ui.actionViewGameProperties->setDisabled(starting_or_not_running); + m_shortcuts.open_file->setEnabled(!starting_or_running); + m_shortcuts.game_list_refresh->setEnabled(!starting_or_running); + m_shortcuts.game_list_search->setEnabled(!starting_or_running); + m_shortcuts.game_grid_zoom_in->setEnabled(!starting_or_running); + m_shortcuts.game_grid_zoom_out->setEnabled(!starting_or_running); + if (starting_or_running) { if (!m_ui.toolBar->actions().contains(m_ui.actionPowerOff)) @@ -1916,7 +1951,7 @@ void MainWindow::connectSignals() connect(m_ui.actionReset, &QAction::triggered, this, []() { g_emu_thread->resetSystem(true); }); connect(m_ui.actionPause, &QAction::toggled, this, [](bool active) { g_emu_thread->setSystemPaused(active); }); connect(m_ui.actionScreenshot, &QAction::triggered, g_emu_thread, &EmuThread::saveScreenshot); - connect(m_ui.actionScanForNewGames, &QAction::triggered, this, [this]() { refreshGameList(false); }); + connect(m_ui.actionScanForNewGames, &QAction::triggered, this, &MainWindow::onScanForNewGamesTriggered); connect(m_ui.actionRescanAllGames, &QAction::triggered, this, [this]() { refreshGameList(true); }); connect(m_ui.actionLoadState, &QAction::triggered, this, [this]() { m_ui.menuLoadState->exec(QCursor::pos()); }); connect(m_ui.actionSaveState, &QAction::triggered, this, [this]() { m_ui.menuSaveState->exec(QCursor::pos()); }); @@ -1968,14 +2003,8 @@ void MainWindow::connectSignals() connect(m_ui.actionMergeDiscSets, &QAction::triggered, m_game_list_widget, &GameListWidget::setMergeDiscSets); connect(m_ui.actionShowGameIcons, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowGameIcons); connect(m_ui.actionGridViewShowTitles, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowCoverTitles); - connect(m_ui.actionGridViewZoomIn, &QAction::triggered, m_game_list_widget, [this]() { - if (isShowingGameList()) - m_game_list_widget->gridZoomIn(); - }); - connect(m_ui.actionGridViewZoomOut, &QAction::triggered, m_game_list_widget, [this]() { - if (isShowingGameList()) - m_game_list_widget->gridZoomOut(); - }); + connect(m_ui.actionGridViewZoomIn, &QAction::triggered, this, &MainWindow::onViewGameGridZoomInActionTriggered); + connect(m_ui.actionGridViewZoomOut, &QAction::triggered, this, &MainWindow::onViewGameGridZoomOutActionTriggered); connect(m_ui.actionGridViewRefreshCovers, &QAction::triggered, m_game_list_widget, &GameListWidget::refreshGridCovers); diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index 32551795f..2d8d06faa 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -24,6 +24,7 @@ class QLabel; class QThread; class QProgressBar; +class QShortcut; class MainWindow; class GameListWidget; @@ -169,12 +170,15 @@ private Q_SLOTS: void onStartFullscreenUITriggered(); void onFullscreenUIStateChange(bool running); void onRemoveDiscActionTriggered(); + void onScanForNewGamesTriggered(); void onViewToolbarActionToggled(bool checked); void onViewLockToolbarActionToggled(bool checked); void onViewStatusBarActionToggled(bool checked); void onViewGameListActionTriggered(); void onViewGameGridActionTriggered(); void onViewSystemDisplayTriggered(); + void onViewGameGridZoomInActionTriggered(); + void onViewGameGridZoomOutActionTriggered(); void onGitHubRepositoryActionTriggered(); void onIssueTrackerActionTriggered(); void onDiscordServerActionTriggered(); @@ -296,6 +300,15 @@ private: QMenu* m_settings_toolbar_menu = nullptr; + struct + { + QShortcut* open_file = nullptr; + QShortcut* game_list_refresh = nullptr; + QShortcut* game_list_search = nullptr; + QShortcut* game_grid_zoom_in = nullptr; + QShortcut* game_grid_zoom_out = nullptr; + } m_shortcuts; + SettingsWindow* m_settings_window = nullptr; ControllerSettingsWindow* m_controller_settings_window = nullptr; diff --git a/src/duckstation-qt/mainwindow.ui b/src/duckstation-qt/mainwindow.ui index 6e97fba02..eafb2593f 100644 --- a/src/duckstation-qt/mainwindow.ui +++ b/src/duckstation-qt/mainwindow.ui @@ -836,17 +836,11 @@ Zoom &In (Grid View) - - Ctrl++ - Zoom &Out (Grid View) - - Ctrl+- - @@ -931,16 +925,6 @@ Capture GPU Frame - - - asdf - - - - - aaa - - diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index b590eb1f1..90156681b 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -501,12 +501,7 @@ bool QtHost::SetCriticalFolders() // the resources directory should exist, bail out if not const std::string rcc_path = Path::Combine(EmuFolders::Resources, "duckstation-qt.rcc"); if (!FileSystem::FileExists(rcc_path.c_str()) || !QResource::registerResource(QString::fromStdString(rcc_path)) || -#if defined(_WIN32) || defined(__APPLE__) - !FileSystem::DirectoryExists(EmuFolders::Resources.c_str()) -#else - !FileSystem::IsRealDirectory(EmuFolders::Resources.c_str()) -#endif - ) + !FileSystem::DirectoryExists(EmuFolders::Resources.c_str())) { QMessageBox::critical(nullptr, QStringLiteral("Error"), QStringLiteral("Resources are missing, your installation is incomplete.")); From 62414b0c4c95cdaac9af695fc0f2ad2f51f2da60 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Thu, 28 Nov 2024 00:20:08 +1000 Subject: [PATCH 29/69] CPU/Interpreter: IBE should not set BD/BT Apparently. Nothing relies on this. :P --- src/core/cpu_core.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/core/cpu_core.cpp b/src/core/cpu_core.cpp index a6b3cb375..582045abd 100644 --- a/src/core/cpu_core.cpp +++ b/src/core/cpu_core.cpp @@ -605,7 +605,8 @@ ALWAYS_INLINE_RELEASE void CPU::WriteCop0Reg(Cop0Reg reg, u32 value) } break; - [[unlikely]] default : DEV_LOG("Unknown COP0 reg write {} ({:08X})", static_cast(reg), value); + [[unlikely]] default: + DEV_LOG("Unknown COP0 reg write {} ({:08X})", static_cast(reg), value); break; } } @@ -2840,10 +2841,7 @@ ALWAYS_INLINE_RELEASE bool CPU::FetchInstruction() case 0x07: // KSEG2 default: { - CPU::RaiseException(Cop0Registers::CAUSE::MakeValueForException(Exception::IBE, - g_state.current_instruction_in_branch_delay_slot, - g_state.current_instruction_was_branch_taken, 0), - address); + CPU::RaiseException(Cop0Registers::CAUSE::MakeValueForException(Exception::IBE, false, false, 0), address); return false; } } From c6746e76f181d950167f37c7d44ad6c72024724e Mon Sep 17 00:00:00 2001 From: Stenzek Date: Thu, 28 Nov 2024 00:44:31 +1000 Subject: [PATCH 30/69] CPU/Intepreter: Raise #RI on invalid COP0 move --- src/core/cpu_core.cpp | 257 ++++++++++++++++++++++-------------------- 1 file changed, 132 insertions(+), 125 deletions(-) diff --git a/src/core/cpu_core.cpp b/src/core/cpu_core.cpp index 582045abd..fbbf39875 100644 --- a/src/core/cpu_core.cpp +++ b/src/core/cpu_core.cpp @@ -48,9 +48,6 @@ static u32 ReadReg(Reg rs); static void WriteReg(Reg rd, u32 value); static void WriteRegDelayed(Reg rd, u32 value); -static u32 ReadCop0Reg(Cop0Reg reg); -static void WriteCop0Reg(Cop0Reg reg, u32 value); - static void DispatchCop0Breakpoint(); static bool IsCop0ExecutionBreakpointUnmasked(); static void Cop0ExecutionBreakpointCheck(); @@ -494,123 +491,6 @@ ALWAYS_INLINE_RELEASE void CPU::WriteRegDelayed(Reg rd, u32 value) g_state.next_load_delay_value = value; } -ALWAYS_INLINE_RELEASE u32 CPU::ReadCop0Reg(Cop0Reg reg) -{ - switch (reg) - { - case Cop0Reg::BPC: - return g_state.cop0_regs.BPC; - - case Cop0Reg::BPCM: - return g_state.cop0_regs.BPCM; - - case Cop0Reg::BDA: - return g_state.cop0_regs.BDA; - - case Cop0Reg::BDAM: - return g_state.cop0_regs.BDAM; - - case Cop0Reg::DCIC: - return g_state.cop0_regs.dcic.bits; - - case Cop0Reg::JUMPDEST: - return g_state.cop0_regs.TAR; - - case Cop0Reg::BadVaddr: - return g_state.cop0_regs.BadVaddr; - - case Cop0Reg::SR: - return g_state.cop0_regs.sr.bits; - - case Cop0Reg::CAUSE: - return g_state.cop0_regs.cause.bits; - - case Cop0Reg::EPC: - return g_state.cop0_regs.EPC; - - case Cop0Reg::PRID: - return g_state.cop0_regs.PRID; - - default: - return 0; - } -} - -ALWAYS_INLINE_RELEASE void CPU::WriteCop0Reg(Cop0Reg reg, u32 value) -{ - switch (reg) - { - case Cop0Reg::BPC: - { - g_state.cop0_regs.BPC = value; - DEV_LOG("COP0 BPC <- {:08X}", value); - } - break; - - case Cop0Reg::BPCM: - { - g_state.cop0_regs.BPCM = value; - DEV_LOG("COP0 BPCM <- {:08X}", value); - if (UpdateDebugDispatcherFlag()) - ExitExecution(); - } - break; - - case Cop0Reg::BDA: - { - g_state.cop0_regs.BDA = value; - DEV_LOG("COP0 BDA <- {:08X}", value); - } - break; - - case Cop0Reg::BDAM: - { - g_state.cop0_regs.BDAM = value; - DEV_LOG("COP0 BDAM <- {:08X}", value); - } - break; - - case Cop0Reg::JUMPDEST: - { - WARNING_LOG("Ignoring write to Cop0 JUMPDEST"); - } - break; - - case Cop0Reg::DCIC: - { - g_state.cop0_regs.dcic.bits = - (g_state.cop0_regs.dcic.bits & ~Cop0Registers::DCIC::WRITE_MASK) | (value & Cop0Registers::DCIC::WRITE_MASK); - DEV_LOG("COP0 DCIC <- {:08X} (now {:08X})", value, g_state.cop0_regs.dcic.bits); - if (UpdateDebugDispatcherFlag()) - ExitExecution(); - } - break; - - case Cop0Reg::SR: - { - g_state.cop0_regs.sr.bits = - (g_state.cop0_regs.sr.bits & ~Cop0Registers::SR::WRITE_MASK) | (value & Cop0Registers::SR::WRITE_MASK); - DEBUG_LOG("COP0 SR <- {:08X} (now {:08X})", value, g_state.cop0_regs.sr.bits); - UpdateMemoryPointers(); - CheckForPendingInterrupt(); - } - break; - - case Cop0Reg::CAUSE: - { - g_state.cop0_regs.cause.bits = - (g_state.cop0_regs.cause.bits & ~Cop0Registers::CAUSE::WRITE_MASK) | (value & Cop0Registers::CAUSE::WRITE_MASK); - DEBUG_LOG("COP0 CAUSE <- {:08X} (now {:08X})", value, g_state.cop0_regs.cause.bits); - CheckForPendingInterrupt(); - } - break; - - [[unlikely]] default: - DEV_LOG("Unknown COP0 reg write {} ({:08X})", static_cast(reg), value); - break; - } -} - ALWAYS_INLINE_RELEASE bool CPU::IsCop0ExecutionBreakpointUnmasked() { static constexpr const u32 code_address_ranges[][2] = { @@ -1776,7 +1656,59 @@ restart_instruction: { case CopCommonInstruction::mfcn: { - const u32 value = ReadCop0Reg(static_cast(inst.r.rd.GetValue())); + u32 value; + + switch (static_cast(inst.r.rd.GetValue())) + { + case Cop0Reg::BPC: + value = g_state.cop0_regs.BPC; + break; + + case Cop0Reg::BPCM: + value = g_state.cop0_regs.BPCM; + break; + + case Cop0Reg::BDA: + value = g_state.cop0_regs.BDA; + break; + + case Cop0Reg::BDAM: + value = g_state.cop0_regs.BDAM; + break; + + case Cop0Reg::DCIC: + value = g_state.cop0_regs.dcic.bits; + break; + + case Cop0Reg::JUMPDEST: + value = g_state.cop0_regs.TAR; + break; + + case Cop0Reg::BadVaddr: + value = g_state.cop0_regs.BadVaddr; + break; + + case Cop0Reg::SR: + value = g_state.cop0_regs.sr.bits; + break; + + case Cop0Reg::CAUSE: + value = g_state.cop0_regs.cause.bits; + break; + + case Cop0Reg::EPC: + value = g_state.cop0_regs.EPC; + break; + + case Cop0Reg::PRID: + value = g_state.cop0_regs.PRID; + break; + + default: + RaiseException(Exception::RI); + return; + } + WriteRegDelayed(inst.r.rt, value); if constexpr (pgxp_mode == PGXPMode::CPU) @@ -1786,11 +1718,86 @@ restart_instruction: case CopCommonInstruction::mtcn: { - const u32 rtVal = ReadReg(inst.r.rt); - WriteCop0Reg(static_cast(inst.r.rd.GetValue()), rtVal); + u32 value = ReadReg(inst.r.rt); + [[maybe_unused]] const u32 orig_value = value; + + switch (static_cast(inst.r.rd.GetValue())) + { + case Cop0Reg::BPC: + { + g_state.cop0_regs.BPC = value; + DEV_LOG("COP0 BPC <- {:08X}", value); + } + break; + + case Cop0Reg::BPCM: + { + g_state.cop0_regs.BPCM = value; + DEV_LOG("COP0 BPCM <- {:08X}", value); + if (UpdateDebugDispatcherFlag()) + ExitExecution(); + } + break; + + case Cop0Reg::BDA: + { + g_state.cop0_regs.BDA = value; + DEV_LOG("COP0 BDA <- {:08X}", value); + } + break; + + case Cop0Reg::BDAM: + { + g_state.cop0_regs.BDAM = value; + DEV_LOG("COP0 BDAM <- {:08X}", value); + } + break; + + case Cop0Reg::JUMPDEST: + { + WARNING_LOG("Ignoring write to Cop0 JUMPDEST"); + } + break; + + case Cop0Reg::DCIC: + { + g_state.cop0_regs.dcic.bits = (g_state.cop0_regs.dcic.bits & ~Cop0Registers::DCIC::WRITE_MASK) | + (value & Cop0Registers::DCIC::WRITE_MASK); + DEV_LOG("COP0 DCIC <- {:08X} (now {:08X})", value, g_state.cop0_regs.dcic.bits); + value = g_state.cop0_regs.dcic.bits; + if (UpdateDebugDispatcherFlag()) + ExitExecution(); + } + break; + + case Cop0Reg::SR: + { + g_state.cop0_regs.sr.bits = (g_state.cop0_regs.sr.bits & ~Cop0Registers::SR::WRITE_MASK) | + (value & Cop0Registers::SR::WRITE_MASK); + DEBUG_LOG("COP0 SR <- {:08X} (now {:08X})", value, g_state.cop0_regs.sr.bits); + value = g_state.cop0_regs.sr.bits; + UpdateMemoryPointers(); + CheckForPendingInterrupt(); + } + break; + + case Cop0Reg::CAUSE: + { + g_state.cop0_regs.cause.bits = (g_state.cop0_regs.cause.bits & ~Cop0Registers::CAUSE::WRITE_MASK) | + (value & Cop0Registers::CAUSE::WRITE_MASK); + DEBUG_LOG("COP0 CAUSE <- {:08X} (now {:08X})", value, g_state.cop0_regs.cause.bits); + value = g_state.cop0_regs.cause.bits; + CheckForPendingInterrupt(); + } + break; + + [[unlikely]] default: + RaiseException(Exception::RI); + return; + } if constexpr (pgxp_mode == PGXPMode::CPU) - PGXP::CPU_MTC0(inst, ReadCop0Reg(static_cast(inst.r.rd.GetValue())), rtVal); + PGXP::CPU_MTC0(inst, value, orig_value); } break; @@ -1818,7 +1825,7 @@ restart_instruction: case Cop0Instruction::tlbwr: case Cop0Instruction::tlbp: RaiseException(Exception::RI); - break; + return; default: [[unlikely]] ERROR_LOG("Unhandled instruction at {:08X}: {:08X}", g_state.current_instruction_pc, From 42c5f9169f87ab1e1773604661597039d221fd56 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 1 Dec 2024 14:08:59 +1000 Subject: [PATCH 31/69] CI: Merge all packaging scripts to one directory --- .github/workflows/linux-appimage-build.yml | 8 ++++---- .github/workflows/linux-flatpak-build.yml | 12 ++++++------ scripts/clang-toolchain.cmake | 5 ----- .../appimage/apprun-hooks/default-to-x11.sh | 0 .../{ => packaging}/appimage/install-packages.sh | 0 scripts/{ => packaging}/appimage/make-appimage.sh | 4 ++-- scripts/{ => packaging}/flatpak/.gitignore | 0 .../flatpak/modules/10-libbacktrace.yaml | 0 .../{ => packaging}/flatpak/modules/11-libzip.yaml | 0 .../{ => packaging}/flatpak/modules/20-sdl2.yaml | 0 .../{ => packaging}/flatpak/modules/21-shaderc.yaml | 0 .../flatpak/modules/22-spirv-cross.yaml | 0 .../{ => packaging}/flatpak/modules/23-cpuinfo.yaml | 0 .../flatpak/modules/24-discord-rpc.yaml | 0 .../flatpak/modules/25-soundtouch.yaml | 0 .../{ => packaging}/flatpak/modules/26-lunasvg.yaml | 0 .../flatpak/org.duckstation.DuckStation.yaml | 8 ++++---- scripts/{ => packaging}/generate-metainfo.sh | 0 .../org.duckstation.DuckStation.desktop | 0 .../org.duckstation.DuckStation.metainfo.xml.in | 0 .../{ => packaging}/org.duckstation.DuckStation.png | Bin 21 files changed, 16 insertions(+), 21 deletions(-) delete mode 100644 scripts/clang-toolchain.cmake rename scripts/{ => packaging}/appimage/apprun-hooks/default-to-x11.sh (100%) rename scripts/{ => packaging}/appimage/install-packages.sh (100%) rename scripts/{ => packaging}/appimage/make-appimage.sh (97%) rename scripts/{ => packaging}/flatpak/.gitignore (100%) rename scripts/{ => packaging}/flatpak/modules/10-libbacktrace.yaml (100%) rename scripts/{ => packaging}/flatpak/modules/11-libzip.yaml (100%) rename scripts/{ => packaging}/flatpak/modules/20-sdl2.yaml (100%) rename scripts/{ => packaging}/flatpak/modules/21-shaderc.yaml (100%) rename scripts/{ => packaging}/flatpak/modules/22-spirv-cross.yaml (100%) rename scripts/{ => packaging}/flatpak/modules/23-cpuinfo.yaml (100%) rename scripts/{ => packaging}/flatpak/modules/24-discord-rpc.yaml (100%) rename scripts/{ => packaging}/flatpak/modules/25-soundtouch.yaml (100%) rename scripts/{ => packaging}/flatpak/modules/26-lunasvg.yaml (100%) rename scripts/{ => packaging}/flatpak/org.duckstation.DuckStation.yaml (90%) rename scripts/{ => packaging}/generate-metainfo.sh (100%) rename scripts/{ => packaging}/org.duckstation.DuckStation.desktop (100%) rename scripts/{ => packaging}/org.duckstation.DuckStation.metainfo.xml.in (100%) rename scripts/{ => packaging}/org.duckstation.DuckStation.png (100%) diff --git a/.github/workflows/linux-appimage-build.yml b/.github/workflows/linux-appimage-build.yml index 9fc059ff0..51ec4b9cd 100644 --- a/.github/workflows/linux-appimage-build.yml +++ b/.github/workflows/linux-appimage-build.yml @@ -14,7 +14,7 @@ jobs: fetch-depth: 0 - name: Install Packages - run: scripts/appimage/install-packages.sh + run: scripts/packaging/appimage/install-packages.sh - name: Cache Dependencies id: cache-deps @@ -66,7 +66,7 @@ jobs: cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON -DCMAKE_PREFIX_PATH="$HOME/deps" -DCMAKE_C_COMPILER=clang-18 -DCMAKE_CXX_COMPILER=clang++-18 -DCMAKE_EXE_LINKER_FLAGS_INIT="-fuse-ld=lld" -DCMAKE_MODULE_LINKER_FLAGS_INIT="-fuse-ld=lld" -DCMAKE_SHARED_LINKER_FLAGS_INIT="-fuse-ld=lld" .. cmake --build . --parallel cd .. - scripts/appimage/make-appimage.sh $(realpath .) $(realpath ./build) $HOME/deps DuckStation-x64 + scripts/packaging/appimage/make-appimage.sh $(realpath .) $(realpath ./build) $HOME/deps DuckStation-x64 - name: Upload Qt AppImage uses: actions/upload-artifact@v4.3.3 @@ -85,7 +85,7 @@ jobs: fetch-depth: 0 - name: Install Packages - run: scripts/appimage/install-packages.sh + run: scripts/packaging/appimage/install-packages.sh - name: Cache Dependencies id: cache-deps @@ -137,7 +137,7 @@ jobs: cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DDISABLE_SSE4=ON -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON -DCMAKE_PREFIX_PATH="$HOME/deps" -DCMAKE_C_COMPILER=clang-18 -DCMAKE_CXX_COMPILER=clang++-18 -DCMAKE_EXE_LINKER_FLAGS_INIT="-fuse-ld=lld" -DCMAKE_MODULE_LINKER_FLAGS_INIT="-fuse-ld=lld" -DCMAKE_SHARED_LINKER_FLAGS_INIT="-fuse-ld=lld" .. cmake --build . --parallel cd .. - scripts/appimage/make-appimage.sh $(realpath .) $(realpath ./build) $HOME/deps DuckStation-x64-SSE2 + scripts/packaging/appimage/make-appimage.sh $(realpath .) $(realpath ./build) $HOME/deps DuckStation-x64-SSE2 - name: Upload Qt AppImage uses: actions/upload-artifact@v4.3.3 diff --git a/.github/workflows/linux-flatpak-build.yml b/.github/workflows/linux-flatpak-build.yml index debef9eee..5f3162099 100644 --- a/.github/workflows/linux-flatpak-build.yml +++ b/.github/workflows/linux-flatpak-build.yml @@ -59,21 +59,21 @@ jobs: - name: Generate AppStream XML run: | - scripts/generate-metainfo.sh scripts/flatpak - cat scripts/flatpak/org.duckstation.DuckStation.metainfo.xml + scripts/packaging/generate-metainfo.sh scripts/packaging/flatpak + cat scripts/packaging/flatpak/org.duckstation.DuckStation.metainfo.xml - name: Validate AppStream XML - run: flatpak-builder-lint appstream scripts/flatpak/org.duckstation.DuckStation.metainfo.xml + run: flatpak-builder-lint appstream scripts/packaging/flatpak/org.duckstation.DuckStation.metainfo.xml - name: Validate Manifest - run: flatpak-builder-lint manifest scripts/flatpak/org.duckstation.DuckStation.yaml + run: flatpak-builder-lint manifest scripts/packaging/flatpak/org.duckstation.DuckStation.yaml - name: Build Flatpak uses: flathub-infra/flatpak-github-actions/flatpak-builder@23796715b3dfa4c86ddf50cf29c3cc8b3c82dca8 with: bundle: duckstation-x64.flatpak upload-artifact: false - manifest-path: scripts/flatpak/org.duckstation.DuckStation.yaml + manifest-path: scripts/packaging/flatpak/org.duckstation.DuckStation.yaml arch: x86_64 build-bundle: true verbose: true @@ -81,7 +81,7 @@ jobs: branch: stable cache: true restore-cache: true - cache-key: flatpak-x64-${{ hashFiles('scripts/flatpak/**/*.yaml') }} + cache-key: flatpak-x64-${{ hashFiles('scripts/packaging/flatpak/**/*.yaml') }} - name: Validate Build run: | diff --git a/scripts/clang-toolchain.cmake b/scripts/clang-toolchain.cmake deleted file mode 100644 index 7a601d668..000000000 --- a/scripts/clang-toolchain.cmake +++ /dev/null @@ -1,5 +0,0 @@ -set(CMAKE_C_COMPILER /usr/bin/clang-16) -set(CMAKE_CXX_COMPILER /usr/bin/clang++-16) -set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=lld") -set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=lld") -set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=lld") diff --git a/scripts/appimage/apprun-hooks/default-to-x11.sh b/scripts/packaging/appimage/apprun-hooks/default-to-x11.sh similarity index 100% rename from scripts/appimage/apprun-hooks/default-to-x11.sh rename to scripts/packaging/appimage/apprun-hooks/default-to-x11.sh diff --git a/scripts/appimage/install-packages.sh b/scripts/packaging/appimage/install-packages.sh similarity index 100% rename from scripts/appimage/install-packages.sh rename to scripts/packaging/appimage/install-packages.sh diff --git a/scripts/appimage/make-appimage.sh b/scripts/packaging/appimage/make-appimage.sh similarity index 97% rename from scripts/appimage/make-appimage.sh rename to scripts/packaging/appimage/make-appimage.sh index 76333214c..96f417150 100755 --- a/scripts/appimage/make-appimage.sh +++ b/scripts/packaging/appimage/make-appimage.sh @@ -127,8 +127,8 @@ DEPLOY_PLATFORM_THEMES="1" \ QMAKE="$DEPSDIR/bin/qmake" \ NO_STRIP="1" \ $LINUXDEPLOY --plugin qt --appdir="$OUTDIR" --executable="$BUILDDIR/bin/duckstation-qt" ${EXTRA_LIBS_ARGS[@]} \ ---desktop-file="$ROOTDIR/scripts/org.duckstation.DuckStation.desktop" \ ---icon-file="$ROOTDIR/scripts/org.duckstation.DuckStation.png" \ +--desktop-file="$ROOTDIR/scripts/packaging/org.duckstation.DuckStation.desktop" \ +--icon-file="$ROOTDIR/scripts/packaging/org.duckstation.DuckStation.png" \ echo "Copying resources into AppDir..." cp -a "$BUILDDIR/bin/resources" "$OUTDIR/usr/bin" diff --git a/scripts/flatpak/.gitignore b/scripts/packaging/flatpak/.gitignore similarity index 100% rename from scripts/flatpak/.gitignore rename to scripts/packaging/flatpak/.gitignore diff --git a/scripts/flatpak/modules/10-libbacktrace.yaml b/scripts/packaging/flatpak/modules/10-libbacktrace.yaml similarity index 100% rename from scripts/flatpak/modules/10-libbacktrace.yaml rename to scripts/packaging/flatpak/modules/10-libbacktrace.yaml diff --git a/scripts/flatpak/modules/11-libzip.yaml b/scripts/packaging/flatpak/modules/11-libzip.yaml similarity index 100% rename from scripts/flatpak/modules/11-libzip.yaml rename to scripts/packaging/flatpak/modules/11-libzip.yaml diff --git a/scripts/flatpak/modules/20-sdl2.yaml b/scripts/packaging/flatpak/modules/20-sdl2.yaml similarity index 100% rename from scripts/flatpak/modules/20-sdl2.yaml rename to scripts/packaging/flatpak/modules/20-sdl2.yaml diff --git a/scripts/flatpak/modules/21-shaderc.yaml b/scripts/packaging/flatpak/modules/21-shaderc.yaml similarity index 100% rename from scripts/flatpak/modules/21-shaderc.yaml rename to scripts/packaging/flatpak/modules/21-shaderc.yaml diff --git a/scripts/flatpak/modules/22-spirv-cross.yaml b/scripts/packaging/flatpak/modules/22-spirv-cross.yaml similarity index 100% rename from scripts/flatpak/modules/22-spirv-cross.yaml rename to scripts/packaging/flatpak/modules/22-spirv-cross.yaml diff --git a/scripts/flatpak/modules/23-cpuinfo.yaml b/scripts/packaging/flatpak/modules/23-cpuinfo.yaml similarity index 100% rename from scripts/flatpak/modules/23-cpuinfo.yaml rename to scripts/packaging/flatpak/modules/23-cpuinfo.yaml diff --git a/scripts/flatpak/modules/24-discord-rpc.yaml b/scripts/packaging/flatpak/modules/24-discord-rpc.yaml similarity index 100% rename from scripts/flatpak/modules/24-discord-rpc.yaml rename to scripts/packaging/flatpak/modules/24-discord-rpc.yaml diff --git a/scripts/flatpak/modules/25-soundtouch.yaml b/scripts/packaging/flatpak/modules/25-soundtouch.yaml similarity index 100% rename from scripts/flatpak/modules/25-soundtouch.yaml rename to scripts/packaging/flatpak/modules/25-soundtouch.yaml diff --git a/scripts/flatpak/modules/26-lunasvg.yaml b/scripts/packaging/flatpak/modules/26-lunasvg.yaml similarity index 100% rename from scripts/flatpak/modules/26-lunasvg.yaml rename to scripts/packaging/flatpak/modules/26-lunasvg.yaml diff --git a/scripts/flatpak/org.duckstation.DuckStation.yaml b/scripts/packaging/flatpak/org.duckstation.DuckStation.yaml similarity index 90% rename from scripts/flatpak/org.duckstation.DuckStation.yaml rename to scripts/packaging/flatpak/org.duckstation.DuckStation.yaml index cf66b339e..0be7574fb 100644 --- a/scripts/flatpak/org.duckstation.DuckStation.yaml +++ b/scripts/packaging/flatpak/org.duckstation.DuckStation.yaml @@ -80,21 +80,21 @@ modules: - "-DCMAKE_SHARED_LINKER_FLAGS_INIT=-fuse-ld=lld" sources: - type: dir - path: ../.. + path: ../../.. post-install: # Manually copy desktop file/metadata, it's not done as part of the regular build. - >- install -Dm644 - "${FLATPAK_BUILDER_BUILDDIR}/scripts/org.duckstation.DuckStation.png" + "${FLATPAK_BUILDER_BUILDDIR}/scripts/packaging/org.duckstation.DuckStation.png" "${FLATPAK_DEST}/share/icons/hicolor/512x512/apps/org.duckstation.DuckStation.png" - >- install -Dm644 - "${FLATPAK_BUILDER_BUILDDIR}/scripts/org.duckstation.DuckStation.desktop" + "${FLATPAK_BUILDER_BUILDDIR}/scripts/packaging/org.duckstation.DuckStation.desktop" "${FLATPAK_DEST}/share/applications/org.duckstation.DuckStation.desktop" - >- install -Dm644 - "${FLATPAK_BUILDER_BUILDDIR}/scripts/flatpak/org.duckstation.DuckStation.metainfo.xml" + "${FLATPAK_BUILDER_BUILDDIR}/scripts/packaging/flatpak/org.duckstation.DuckStation.metainfo.xml" "${FLATPAK_DEST}/share/metainfo/org.duckstation.DuckStation.metainfo.xml" # Ensure ffmpeg-full mount point exists. diff --git a/scripts/generate-metainfo.sh b/scripts/packaging/generate-metainfo.sh similarity index 100% rename from scripts/generate-metainfo.sh rename to scripts/packaging/generate-metainfo.sh diff --git a/scripts/org.duckstation.DuckStation.desktop b/scripts/packaging/org.duckstation.DuckStation.desktop similarity index 100% rename from scripts/org.duckstation.DuckStation.desktop rename to scripts/packaging/org.duckstation.DuckStation.desktop diff --git a/scripts/org.duckstation.DuckStation.metainfo.xml.in b/scripts/packaging/org.duckstation.DuckStation.metainfo.xml.in similarity index 100% rename from scripts/org.duckstation.DuckStation.metainfo.xml.in rename to scripts/packaging/org.duckstation.DuckStation.metainfo.xml.in diff --git a/scripts/org.duckstation.DuckStation.png b/scripts/packaging/org.duckstation.DuckStation.png similarity index 100% rename from scripts/org.duckstation.DuckStation.png rename to scripts/packaging/org.duckstation.DuckStation.png From 9c2244f40eaa0b6076eea1fd2385b24c82764c8b Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 1 Dec 2024 14:27:19 +1000 Subject: [PATCH 32/69] Qt: CustomizeWindowHint should be set for no-close-button --- src/duckstation-qt/logwindow.cpp | 1 + src/duckstation-qt/qtprogresscallback.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/duckstation-qt/logwindow.cpp b/src/duckstation-qt/logwindow.cpp index 0291ee7f2..25e220a35 100644 --- a/src/duckstation-qt/logwindow.cpp +++ b/src/duckstation-qt/logwindow.cpp @@ -111,6 +111,7 @@ void LogWindow::createUi() QIcon icon; icon.addFile(QString::fromUtf8(":/icons/duck.png"), QSize(), QIcon::Normal, QIcon::Off); setWindowIcon(icon); + setWindowFlag(Qt::CustomizeWindowHint, true); setWindowFlag(Qt::WindowCloseButtonHint, false); updateWindowTitle(); diff --git a/src/duckstation-qt/qtprogresscallback.cpp b/src/duckstation-qt/qtprogresscallback.cpp index 18a0ce524..e23a5e951 100644 --- a/src/duckstation-qt/qtprogresscallback.cpp +++ b/src/duckstation-qt/qtprogresscallback.cpp @@ -19,6 +19,7 @@ QtModalProgressCallback::QtModalProgressCallback(QWidget* parent_widget, float s m_dialog.setModal(parent_widget != nullptr); m_dialog.setAutoClose(false); m_dialog.setAutoReset(false); + m_dialog.setWindowFlag(Qt::CustomizeWindowHint, true); m_dialog.setWindowFlag(Qt::WindowCloseButtonHint, false); connect(&m_dialog, &QProgressDialog::canceled, this, &QtModalProgressCallback::dialogCancelled); checkForDelayedShow(); From 00132c60706f9ec70cbebae566ba2c3b929f4fab Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 1 Dec 2024 14:49:38 +1000 Subject: [PATCH 33/69] AnalogController: Fix more log spam --- src/core/analog_controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/analog_controller.cpp b/src/core/analog_controller.cpp index 14e5e44a0..c064325ab 100644 --- a/src/core/analog_controller.cpp +++ b/src/core/analog_controller.cpp @@ -416,7 +416,7 @@ void AnalogController::UpdateHostVibration() hvalues[motor] = (state != 0) ? static_cast(strength / 65535.0) : 0.0f; } - WARNING_LOG("Set small to {}, large to {}", hvalues[SmallMotor], hvalues[LargeMotor]); + DEV_LOG("Set small motor to {}, large motor to {}", hvalues[SmallMotor], hvalues[LargeMotor]); InputManager::SetPadVibrationIntensity(m_index, hvalues[LargeMotor], hvalues[SmallMotor]); } From 9fc9f4b9e9d83e69a13143e5171606eae17a23ce Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 1 Dec 2024 16:53:25 +1000 Subject: [PATCH 34/69] Settings: Fix hash cache fields not being compared --- src/core/settings.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/settings.cpp b/src/core/settings.cpp index b89fb15a9..ef2cf778e 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -782,6 +782,9 @@ bool Settings::TextureReplacementSettings::Configuration::operator==(const Confi dump_c16_textures == rhs.dump_c16_textures && reduce_palette_range == rhs.reduce_palette_range && convert_copies_to_writes == rhs.convert_copies_to_writes && replacement_scale_linear_filter == rhs.replacement_scale_linear_filter && + max_hash_cache_entries == rhs.max_hash_cache_entries && + max_hash_cache_vram_usage_mb == rhs.max_hash_cache_vram_usage_mb && + max_replacement_cache_vram_usage_mb == rhs.max_replacement_cache_vram_usage_mb && max_vram_write_splits == rhs.max_vram_write_splits && max_vram_write_coalesce_width == rhs.max_vram_write_coalesce_width && max_vram_write_coalesce_height == rhs.max_vram_write_coalesce_height && From 4fe3e1147d4e98347ac0d1cee8039595a2a32494 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 1 Dec 2024 17:47:31 +1000 Subject: [PATCH 35/69] Controller: Add GetPortDisplayName() that takes pad index --- src/core/controller.cpp | 6 ++++++ src/core/controller.h | 1 + 2 files changed, 7 insertions(+) diff --git a/src/core/controller.cpp b/src/core/controller.cpp index 1fdfbc47a..4608f2528 100644 --- a/src/core/controller.cpp +++ b/src/core/controller.cpp @@ -238,6 +238,12 @@ const char* Controller::GetPortDisplayName(u32 port, u32 slot, bool mtap) return mtap ? mtap_labels[port][slot] : no_mtap_labels[port]; } +const char* Controller::GetPortDisplayName(u32 index) +{ + const auto& [port, slot] = ConvertPadToPortAndSlot(index); + return GetPortDisplayName(port, slot, g_settings.IsMultitapPortEnabled(port)); +} + std::string Controller::GetSettingsSection(u32 pad) { return fmt::format("Pad{}", pad + 1u); diff --git a/src/core/controller.h b/src/core/controller.h index bf2bc766d..9ff91a88c 100644 --- a/src/core/controller.h +++ b/src/core/controller.h @@ -139,6 +139,7 @@ public: /// Returns a printable label for a given port. static const char* GetPortDisplayName(u32 port, u32 slot, bool mtap); + static const char* GetPortDisplayName(u32 index); /// List of controller indices in the order that they should be displayed. static const std::array PortDisplayOrder; From b87c6dde6c26956fc8c125fed9a1eb71bca2b47d Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 1 Dec 2024 18:15:42 +1000 Subject: [PATCH 36/69] CDROM: More logging tidy-up --- src/core/cdrom.cpp | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp index 4cec6c0f2..2245c9b7c 100644 --- a/src/core/cdrom.cpp +++ b/src/core/cdrom.cpp @@ -1610,7 +1610,7 @@ TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba, bool ignore_speed_change) 1.0f, 72.0f)))); const float seconds = (lba_diff < switch_point) ? 0.05f : 0.1f; ticks += static_cast(seconds * static_cast(System::MASTER_CLOCK)); - seek_type = (new_lba > current_lba) ? "NT forward" : "NT backward"; + seek_type = (new_lba > current_lba) ? "2N forward" : "2N backward"; } else { @@ -1625,7 +1625,7 @@ TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba, bool ignore_speed_change) (((SLED_VARIABLE_COST * (std::log(static_cast(lba_diff)) / std::log(MAX_SLED_LBA)))) * LOG_WEIGHT) + ((SLED_VARIABLE_COST * (lba_diff / MAX_SLED_LBA)) * (1.0f - LOG_WEIGHT)); ticks += static_cast(seconds * static_cast(System::MASTER_CLOCK)); - seek_type = (new_lba > current_lba) ? "2N/sled forward" : "2N/sled backward"; + seek_type = (new_lba > current_lba) ? "sled forward" : "sled backward"; } if (g_settings.cdrom_seek_speedup > 1) @@ -2101,11 +2101,6 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late) case Command::Pause: { - if (IsReadingOrPlaying()) - DEV_COLOR_LOG(StrongOrange, "Pause"); - else - DEV_COLOR_LOG(StrongRed, "Pause Not Reading"); - const TickCount pause_time = GetTicksForPause(); if (IsReading() && s_state.last_subq.IsData()) { @@ -2124,22 +2119,34 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late) ((s_state.drive_state == DriveState::Reading || s_state.drive_state == DriveState::Playing) && s_state.secondary_status.seeking)) { - WARNING_LOG("CDROM Pause command while seeking - sending error response"); + if (Log::GetLogLevel() >= Log::Level::Dev) + DEV_COLOR_LOG(StrongRed, "Pause Seeking => Error"); + else + WARNING_LOG("CDROM Pause command while seeking - sending error response"); + SendErrorResponse(STAT_ERROR, ERROR_REASON_NOT_READY); EndCommand(); return; } - else - { - // Small window of time when another INT1 could sneak in, don't let it. - ClearAsyncInterrupt(); - // Stop reading. - s_state.drive_state = DriveState::Idle; - s_state.drive_event.Deactivate(); - s_state.secondary_status.ClearActiveBits(); + if (Log::GetLogLevel() >= Log::Level::Dev) + { + const double pause_time_ms = + static_cast(pause_time) / (static_cast(System::MASTER_CLOCK) / 1000.0); + if (IsReadingOrPlaying()) + DEV_COLOR_LOG(StrongOrange, "Pause {:.2f}ms", pause_time_ms); + else + DEV_COLOR_LOG(Yellow, "Pause Not Reading {:.2f}ms", pause_time_ms); } + // Small window of time when another INT1 could sneak in, don't let it. + ClearAsyncInterrupt(); + + // Stop reading. + s_state.drive_state = DriveState::Idle; + s_state.drive_event.Deactivate(); + s_state.secondary_status.ClearActiveBits(); + // Reset audio buffer here - control room cutscene audio repeats in Dino Crisis otherwise. ResetAudioDecoder(); From d7d028ac5cf283b5a5b4dbb9d1879b37c13d208b Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 1 Dec 2024 18:15:59 +1000 Subject: [PATCH 37/69] GameDB: Recompiler ICache for Resident Evil 3 CD code gets super screwed up and sends multiple commands without waiting for them to finish. --- data/resources/gamedb.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/data/resources/gamedb.yaml b/data/resources/gamedb.yaml index 3ae15e4fa..eacaad2a6 100644 --- a/data/resources/gamedb.yaml +++ b/data/resources/gamedb.yaml @@ -15751,6 +15751,7 @@ SLPS-02300: traits: - DisableWidescreen - ForcePGXPCPUMode # Fixes jitter in character models. + - ForceRecompilerICache # Fixes hangs in CD code. metadata: publisher: "Capcom" developer: "Capcom Production Studio 4" @@ -15772,6 +15773,7 @@ SLPM-80485: traits: - DisableWidescreen - ForcePGXPCPUMode # Fixes jitter in character models. + - ForceRecompilerICache # Fixes hangs in CD code. SLPM-87224: name: "Biohazard 3 - Last Escape (Japan) (Rev 1)" controllers: @@ -15780,6 +15782,7 @@ SLPM-87224: traits: - DisableWidescreen - ForcePGXPCPUMode # Fixes jitter in character models. + - ForceRecompilerICache # Fixes hangs in CD code. metadata: publisher: "Capcom" developer: "Capcom Production Studio 4" @@ -135716,6 +135719,7 @@ SLES-02529: traits: - DisableWidescreen - ForcePGXPCPUMode # Fixes jitter in character models. + - ForceRecompilerICache # Fixes hangs in CD code. libcrypt: true metadata: publisher: "Eidos Interactive" @@ -135738,6 +135742,7 @@ SLED-02541: traits: - DisableWidescreen - ForcePGXPCPUMode # Fixes jitter in character models. + - ForceRecompilerICache # Fixes hangs in CD code. SLES-02530: name: "Resident Evil 3 - Nemesis (France)" controllers: @@ -135746,6 +135751,7 @@ SLES-02530: traits: - DisableWidescreen - ForcePGXPCPUMode # Fixes jitter in character models. + - ForceRecompilerICache # Fixes hangs in CD code. libcrypt: true metadata: publisher: "Eidos Interactive" @@ -135769,6 +135775,7 @@ SLES-02531: traits: - DisableWidescreen - ForcePGXPCPUMode # Fixes jitter in character models. + - ForceRecompilerICache # Fixes hangs in CD code. libcrypt: true metadata: publisher: "Eidos Interactive" @@ -135792,6 +135799,7 @@ SLES-02698: traits: - DisableWidescreen - ForcePGXPCPUMode # Fixes jitter in character models. + - ForceRecompilerICache # Fixes hangs in CD code. libcrypt: true metadata: publisher: "Eidos Interactive" @@ -135815,6 +135823,7 @@ SLES-02533: traits: - DisableWidescreen - ForcePGXPCPUMode # Fixes jitter in character models. + - ForceRecompilerICache # Fixes hangs in CD code. libcrypt: true metadata: publisher: "Eidos Interactive" @@ -135841,6 +135850,7 @@ SLES-02532: traits: - DisableWidescreen - ForcePGXPCPUMode # Fixes jitter in character models. + - ForceRecompilerICache # Fixes hangs in CD code. libcrypt: true metadata: publisher: "Proein / Eidos Interactive" @@ -135867,6 +135877,7 @@ SLUS-00923: traits: - DisableWidescreen - ForcePGXPCPUMode # Fixes jitter in character models. + - ForceRecompilerICache # Fixes hangs in CD code. metadata: publisher: "Capcom" developer: "Capcom Production Studio 4" @@ -135888,6 +135899,7 @@ SLUS-90064: traits: - DisableWidescreen - ForcePGXPCPUMode # Fixes jitter in character models. + - ForceRecompilerICache # Fixes hangs in CD code. SLPS-01974: name: "Restaurant Dream (Japan)" controllers: From f9c125c1a1a406d102f5782be70381fd730f1d0f Mon Sep 17 00:00:00 2001 From: Stenzek Date: Mon, 29 Apr 2024 00:48:54 +1000 Subject: [PATCH 38/69] InputManager: Add ForceFeedbackDevice interface --- src/core/fullscreen_ui.cpp | 5 +- src/core/input_types.h | 1 + .../controllerbindingwidgets.cpp | 3 +- src/util/dinput_source.cpp | 12 ++ src/util/dinput_source.h | 3 + src/util/input_manager.cpp | 38 +++++- src/util/input_manager.h | 20 +++ src/util/input_source.h | 7 + src/util/sdl_input_source.cpp | 129 ++++++++++++++++++ src/util/sdl_input_source.h | 23 ++++ src/util/win32_raw_input_source.cpp | 13 ++ src/util/win32_raw_input_source.h | 3 + src/util/xinput_source.cpp | 12 ++ src/util/xinput_source.h | 3 + 14 files changed, 263 insertions(+), 9 deletions(-) diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 77a99f247..ca8bf079b 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -1651,7 +1651,7 @@ void FullscreenUI::DrawInputBindingButton(SettingsInterface* bsi, InputBindingIn if (!visible) return; - if (oneline) + if (oneline && type != InputBindingInfo::Type::Pointer && type != InputBindingInfo::Type::Device) InputManager::PrettifyInputBinding(value); if (show_type) @@ -1677,6 +1677,9 @@ void FullscreenUI::DrawInputBindingButton(SettingsInterface* bsi, InputBindingIn case InputBindingInfo::Type::Macro: title.format(ICON_FA_PIZZA_SLICE " {}", display_name); break; + case InputBindingInfo::Type::Device: + title.format(ICON_FA_GAMEPAD " {}", display_name); + break; default: title = display_name; break; diff --git a/src/core/input_types.h b/src/core/input_types.h index a4ec75d6c..0e805759e 100644 --- a/src/core/input_types.h +++ b/src/core/input_types.h @@ -17,6 +17,7 @@ struct InputBindingInfo Motor, Pointer, // Absolute pointer, does not receive any events, but is queryable. RelativePointer, // Receive relative mouse movement events, bind_index is offset by the axis. + Device, // Used for special-purpose device selection, e.g. force feedback. Macro, }; diff --git a/src/duckstation-qt/controllerbindingwidgets.cpp b/src/duckstation-qt/controllerbindingwidgets.cpp index b0a79fd99..500956015 100644 --- a/src/duckstation-qt/controllerbindingwidgets.cpp +++ b/src/duckstation-qt/controllerbindingwidgets.cpp @@ -405,7 +405,8 @@ void ControllerBindingWidget::createBindingWidgets(QWidget* parent) for (const Controller::ControllerBindingInfo& bi : m_controller_info->bindings) { if (bi.type == InputBindingInfo::Type::Axis || bi.type == InputBindingInfo::Type::HalfAxis || - bi.type == InputBindingInfo::Type::Pointer || bi.type == InputBindingInfo::Type::RelativePointer) + bi.type == InputBindingInfo::Type::Pointer || bi.type == InputBindingInfo::Type::RelativePointer || + bi.type == InputBindingInfo::Type::Device) { if (!axis_gbox) { diff --git a/src/util/dinput_source.cpp b/src/util/dinput_source.cpp index 0a3cba4f9..09289ca8d 100644 --- a/src/util/dinput_source.cpp +++ b/src/util/dinput_source.cpp @@ -8,6 +8,7 @@ #include "platform_misc.h" #include "common/assert.h" +#include "common/error.h" #include "common/log.h" #include "common/string_util.h" @@ -338,6 +339,11 @@ void DInputSource::UpdateMotorState(InputBindingKey large_key, InputBindingKey s // not supported } +bool DInputSource::ContainsDevice(std::string_view device) const +{ + return device.starts_with("DInput-"); +} + std::optional DInputSource::ParseKeyString(std::string_view device, std::string_view binding) { if (!device.starts_with("DInput-") || binding.empty()) @@ -444,6 +450,12 @@ TinyString DInputSource::ConvertKeyToIcon(InputBindingKey key) return {}; } +std::unique_ptr DInputSource::CreateForceFeedbackDevice(std::string_view device, Error* error) +{ + Error::SetStringView(error, "Not supported on this input source."); + return {}; +} + void DInputSource::CheckForStateChanges(size_t index, const DIJOYSTATE& new_state) { ControllerData& cd = m_controllers[index]; diff --git a/src/util/dinput_source.h b/src/util/dinput_source.h index f22dd8c73..458a6716e 100644 --- a/src/util/dinput_source.h +++ b/src/util/dinput_source.h @@ -46,10 +46,13 @@ public: void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity, float small_intensity) override; + bool ContainsDevice(std::string_view device) const override; std::optional ParseKeyString(std::string_view device, std::string_view binding) override; TinyString ConvertKeyToString(InputBindingKey key) override; TinyString ConvertKeyToIcon(InputBindingKey key) override; + std::unique_ptr CreateForceFeedbackDevice(std::string_view device, Error* error) override; + private: template using ComPtr = Microsoft::WRL::ComPtr; diff --git a/src/util/input_manager.cpp b/src/util/input_manager.cpp index 8253e50ae..b8e8e22ae 100644 --- a/src/util/input_manager.cpp +++ b/src/util/input_manager.cpp @@ -2,17 +2,20 @@ // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "input_manager.h" +#include "imgui_manager.h" +#include "input_source.h" + +#include "core/controller.h" +#include "core/host.h" +#include "core/system.h" + #include "common/assert.h" +#include "common/error.h" #include "common/file_system.h" #include "common/log.h" #include "common/path.h" #include "common/string_util.h" #include "common/timer.h" -#include "core/controller.h" -#include "core/host.h" -#include "core/system.h" -#include "imgui_manager.h" -#include "input_source.h" #include "IconsPromptFont.h" @@ -303,7 +306,8 @@ bool InputManager::ParseBindingAndGetSource(std::string_view binding, InputBindi std::string InputManager::ConvertInputBindingKeyToString(InputBindingInfo::Type binding_type, InputBindingKey key) { - if (binding_type == InputBindingInfo::Type::Pointer || binding_type == InputBindingInfo::Type::RelativePointer) + if (binding_type == InputBindingInfo::Type::Pointer || binding_type == InputBindingInfo::Type::RelativePointer || + binding_type == InputBindingInfo::Type::Device) { // pointer and device bindings don't have a data part if (key.source_type == InputSourceType::Pointer) @@ -356,7 +360,8 @@ std::string InputManager::ConvertInputBindingKeysToString(InputBindingInfo::Type const InputBindingKey* keys, size_t num_keys) { // can't have a chord of devices/pointers - if (binding_type == InputBindingInfo::Type::Pointer || binding_type == InputBindingInfo::Type::Pointer) + if (binding_type == InputBindingInfo::Type::Pointer || binding_type == InputBindingInfo::Type::RelativePointer || + binding_type == InputBindingInfo::Type::Device) { // so only take the first if (num_keys > 0) @@ -888,6 +893,8 @@ void InputManager::AddPadBindings(const SettingsInterface& si, const std::string break; case InputBindingInfo::Type::Pointer: + case InputBindingInfo::Type::Device: + // handled in device break; default: @@ -1583,6 +1590,19 @@ void InputManager::OnInputDeviceDisconnected(InputBindingKey key, std::string_vi Host::OnInputDeviceDisconnected(key, identifier); } +std::unique_ptr InputManager::CreateForceFeedbackDevice(const std::string_view device, + Error* error) +{ + for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++) + { + if (s_input_sources[i] && s_input_sources[i]->ContainsDevice(device)) + return s_input_sources[i]->CreateForceFeedbackDevice(device, error); + } + + Error::SetStringFmt(error, "No input source matched device '{}'", device); + return {}; +} + // ------------------------------------------------------------------------ // Vibration // ------------------------------------------------------------------------ @@ -2104,3 +2124,7 @@ void InputManager::ReloadSources(const SettingsInterface& si, std::unique_lock CreateForceFeedbackDevice(const std::string_view device, Error* error = nullptr); } // namespace InputManager namespace Host { diff --git a/src/util/input_source.h b/src/util/input_source.h index 29fcf3689..0c3e0d07c 100644 --- a/src/util/input_source.h +++ b/src/util/input_source.h @@ -14,8 +14,11 @@ #include "common/types.h" #include "input_manager.h" +class Error; class SettingsInterface; +class ForceFeedbackDevice; + class InputSource { public: @@ -29,6 +32,7 @@ public: virtual void PollEvents() = 0; + virtual bool ContainsDevice(std::string_view device) const = 0; virtual std::optional ParseKeyString(std::string_view device, std::string_view binding) = 0; virtual TinyString ConvertKeyToString(InputBindingKey key) = 0; virtual TinyString ConvertKeyToIcon(InputBindingKey key) = 0; @@ -50,6 +54,9 @@ public: virtual void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity, float small_intensity); + /// Creates a force-feedback device from this source. + virtual std::unique_ptr CreateForceFeedbackDevice(std::string_view device, Error* error) = 0; + /// Creates a key for a generic controller axis event. static InputBindingKey MakeGenericControllerAxisKey(InputSourceType clazz, u32 controller_index, s32 axis_index); diff --git a/src/util/sdl_input_source.cpp b/src/util/sdl_input_source.cpp index 7ae97599c..353441ff5 100644 --- a/src/util/sdl_input_source.cpp +++ b/src/util/sdl_input_source.cpp @@ -9,6 +9,7 @@ #include "common/assert.h" #include "common/bitutils.h" +#include "common/error.h" #include "common/file_system.h" #include "common/log.h" #include "common/path.h" @@ -360,6 +361,11 @@ std::vector> SDLInputSource::EnumerateDevice return ret; } +bool SDLInputSource::ContainsDevice(std::string_view device) const +{ + return device.starts_with("SDL-"); +} + std::optional SDLInputSource::ParseKeyString(std::string_view device, std::string_view binding) { if (!device.starts_with("SDL-") || binding.empty()) @@ -1092,3 +1098,126 @@ std::unique_ptr InputSource::CreateSDLSource() { return std::make_unique(); } + +std::unique_ptr SDLInputSource::CreateForceFeedbackDevice(std::string_view device, Error* error) +{ + SDL_Joystick* joystick = GetJoystickForDevice(device); + if (!joystick) + { + Error::SetStringFmt(error, "No SDL_Joystick for {}", device); + return nullptr; + } + + SDL_Haptic* haptic = SDL_HapticOpenFromJoystick(joystick); + if (!haptic) + { + Error::SetStringFmt(error, "Haptic is not supported on {} ({})", device, SDL_JoystickName(joystick)); + return nullptr; + } + + return std::unique_ptr(new SDLForceFeedbackDevice(joystick, haptic)); +} + +SDLForceFeedbackDevice::SDLForceFeedbackDevice(SDL_Joystick* joystick, SDL_Haptic* haptic) : m_haptic(haptic) +{ + std::memset(&m_constant_effect, 0, sizeof(m_constant_effect)); +} + +SDLForceFeedbackDevice::~SDLForceFeedbackDevice() +{ + if (m_haptic) + { + DestroyEffects(); + + SDL_HapticClose(m_haptic); + m_haptic = nullptr; + } +} + +void SDLForceFeedbackDevice::CreateEffects(SDL_Joystick* joystick) +{ + constexpr u32 length = 10000; // 10 seconds since NFS games seem to not issue new commands while rotating. + + const unsigned int supported = SDL_HapticQuery(m_haptic); + if (supported & SDL_HAPTIC_CONSTANT) + { + m_constant_effect.type = SDL_HAPTIC_CONSTANT; + m_constant_effect.constant.direction.type = SDL_HAPTIC_STEERING_AXIS; + m_constant_effect.constant.length = length; + + m_constant_effect_id = SDL_HapticNewEffect(m_haptic, &m_constant_effect); + if (m_constant_effect_id < 0) + ERROR_LOG("SDL_HapticNewEffect() for constant failed: {}", SDL_GetError()); + } + else + { + WARNING_LOG("Constant effect is not supported on '{}'", SDL_JoystickName(joystick)); + } +} + +void SDLForceFeedbackDevice::DestroyEffects() +{ + if (m_constant_effect_id >= 0) + { + if (m_constant_effect_running) + { + SDL_HapticStopEffect(m_haptic, m_constant_effect_id); + m_constant_effect_running = false; + } + SDL_HapticDestroyEffect(m_haptic, m_constant_effect_id); + m_constant_effect_id = -1; + } +} + +template +[[maybe_unused]] static u16 ClampU16(T val) +{ + return static_cast(std::clamp(val, 0, 65535)); +} + +template +[[maybe_unused]] static u16 ClampS16(T val) +{ + return static_cast(std::clamp(val, -32768, 32767)); +} + +void SDLForceFeedbackDevice::SetConstantForce(s32 level) +{ + if (m_constant_effect_id < 0) + return; + + const s16 new_level = ClampS16(level); + if (m_constant_effect.constant.level != new_level) + { + m_constant_effect.constant.level = new_level; + if (SDL_HapticUpdateEffect(m_haptic, m_constant_effect_id, &m_constant_effect) != 0) + ERROR_LOG("SDL_HapticUpdateEffect() for constant failed: {}", SDL_GetError()); + } + + if (!m_constant_effect_running) + { + if (SDL_HapticRunEffect(m_haptic, m_constant_effect_id, SDL_HAPTIC_INFINITY) == 0) + m_constant_effect_running = true; + else + ERROR_LOG("SDL_HapticRunEffect() for constant failed: {}", SDL_GetError()); + } +} + +void SDLForceFeedbackDevice::DisableForce(Effect force) +{ + switch (force) + { + case Effect::Constant: + { + if (m_constant_effect_running) + { + SDL_HapticStopEffect(m_haptic, m_constant_effect_id); + m_constant_effect_running = false; + } + } + break; + + default: + break; + } +} diff --git a/src/util/sdl_input_source.h b/src/util/sdl_input_source.h index 6f0940b90..6d7259284 100644 --- a/src/util/sdl_input_source.h +++ b/src/util/sdl_input_source.h @@ -34,10 +34,13 @@ public: void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity, float small_intensity) override; + bool ContainsDevice(std::string_view device) const override; std::optional ParseKeyString(std::string_view device, std::string_view binding) override; TinyString ConvertKeyToString(InputBindingKey key) override; TinyString ConvertKeyToIcon(InputBindingKey key) override; + std::unique_ptr CreateForceFeedbackDevice(std::string_view device, Error* error) override; + bool ProcessSDLEvent(const SDL_Event* event); SDL_Joystick* GetJoystickForDevice(std::string_view device); @@ -103,3 +106,23 @@ private: bool m_enable_mfi_driver = false; #endif }; + +class SDLForceFeedbackDevice : public ForceFeedbackDevice +{ +public: + SDLForceFeedbackDevice(SDL_Joystick* joystick, SDL_Haptic* haptic); + ~SDLForceFeedbackDevice() override; + + void SetConstantForce(s32 level) override; + void DisableForce(Effect force) override; + +private: + void CreateEffects(SDL_Joystick* joystick); + void DestroyEffects(); + + SDL_Haptic* m_haptic = nullptr; + + SDL_HapticEffect m_constant_effect; + int m_constant_effect_id = -1; + bool m_constant_effect_running = false; +}; diff --git a/src/util/win32_raw_input_source.cpp b/src/util/win32_raw_input_source.cpp index 30ce21844..e34a7fd71 100644 --- a/src/util/win32_raw_input_source.cpp +++ b/src/util/win32_raw_input_source.cpp @@ -5,6 +5,7 @@ #include "input_manager.h" #include "common/assert.h" +#include "common/error.h" #include "common/log.h" #include "common/string_util.h" @@ -90,6 +91,11 @@ void Win32RawInputSource::UpdateMotorState(InputBindingKey large_key, InputBindi { } +bool Win32RawInputSource::ContainsDevice(std::string_view device) const +{ + return false; +} + std::optional Win32RawInputSource::ParseKeyString(std::string_view device, std::string_view binding) { return std::nullopt; @@ -105,6 +111,13 @@ TinyString Win32RawInputSource::ConvertKeyToIcon(InputBindingKey key) return {}; } +std::unique_ptr Win32RawInputSource::CreateForceFeedbackDevice(std::string_view device, + Error* error) +{ + Error::SetStringView(error, "Not supported on this input source."); + return {}; +} + std::vector Win32RawInputSource::EnumerateMotors() { return {}; diff --git a/src/util/win32_raw_input_source.h b/src/util/win32_raw_input_source.h index bac1574f1..43f85bf1d 100644 --- a/src/util/win32_raw_input_source.h +++ b/src/util/win32_raw_input_source.h @@ -30,10 +30,13 @@ public: void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity, float small_intensity) override; + bool ContainsDevice(std::string_view device) const override; std::optional ParseKeyString(std::string_view device, std::string_view binding) override; TinyString ConvertKeyToString(InputBindingKey key) override; TinyString ConvertKeyToIcon(InputBindingKey key) override; + std::unique_ptr CreateForceFeedbackDevice(std::string_view device, Error* error) override; + private: struct MouseState { diff --git a/src/util/xinput_source.cpp b/src/util/xinput_source.cpp index 22a687a3d..ff766604f 100644 --- a/src/util/xinput_source.cpp +++ b/src/util/xinput_source.cpp @@ -5,6 +5,7 @@ #include "input_manager.h" #include "common/assert.h" +#include "common/error.h" #include "common/log.h" #include "common/string_util.h" @@ -251,6 +252,11 @@ std::vector> XInputSource::EnumerateDevices( return ret; } +bool XInputSource::ContainsDevice(std::string_view device) const +{ + return device.starts_with("XInput-"); +} + std::optional XInputSource::ParseKeyString(std::string_view device, std::string_view binding) { if (!device.starts_with("XInput-") || binding.empty()) @@ -364,6 +370,12 @@ TinyString XInputSource::ConvertKeyToIcon(InputBindingKey key) return ret; } +std::unique_ptr XInputSource::CreateForceFeedbackDevice(std::string_view device, Error* error) +{ + Error::SetStringView(error, "Not supported on this input source."); + return {}; +} + std::vector XInputSource::EnumerateMotors() { std::vector ret; diff --git a/src/util/xinput_source.h b/src/util/xinput_source.h index dd087d56e..1e1e558d2 100644 --- a/src/util/xinput_source.h +++ b/src/util/xinput_source.h @@ -48,10 +48,13 @@ public: void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity, float small_intensity) override; + bool ContainsDevice(std::string_view device) const override; std::optional ParseKeyString(std::string_view device, std::string_view binding) override; TinyString ConvertKeyToString(InputBindingKey key) override; TinyString ConvertKeyToIcon(InputBindingKey key) override; + std::unique_ptr CreateForceFeedbackDevice(std::string_view device, Error* error) override; + private: struct ControllerData { From 7c627a8c832b9e3d454a28adc3f8faf856feb308 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 23 Nov 2024 17:53:05 +1000 Subject: [PATCH 39/69] Controller: Add JogCon This is probably wrong, but I have no way of testing it with an actual force feedback wheel. PRs welcome to improve it further. --- src/core/CMakeLists.txt | 2 + src/core/controller.cpp | 5 + src/core/core.vcxproj | 2 + src/core/core.vcxproj.filters | 2 + src/core/jogcon.cpp | 638 ++++++++++++++++++++++++++++++++ src/core/jogcon.h | 150 ++++++++ src/core/types.h | 1 + src/util/imgui_glyph_ranges.inl | 2 +- 8 files changed, 801 insertions(+), 1 deletion(-) create mode 100644 src/core/jogcon.cpp create mode 100644 src/core/jogcon.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 8ae032d3f..1b4d025ce 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -81,6 +81,8 @@ add_library(core imgui_overlays.h interrupt_controller.cpp interrupt_controller.h + jogcon.cpp + jogcon.h justifier.cpp justifier.h mdec.cpp diff --git a/src/core/controller.cpp b/src/core/controller.cpp index 4608f2528..d6d568ce0 100644 --- a/src/core/controller.cpp +++ b/src/core/controller.cpp @@ -8,6 +8,7 @@ #include "game_database.h" #include "guncon.h" #include "host.h" +#include "jogcon.h" #include "justifier.h" #include "negcon.h" #include "negcon_rumble.h" @@ -38,6 +39,7 @@ static const Controller::ControllerInfo* s_controller_info[] = { &Justifier::INFO, &DigitalController::INFO_POPN, &DigitalController::INFO_DDGO, + &JogCon::INFO, }; const std::array Controller::PortDisplayOrder = {{0, 2, 3, 4, 1, 5, 6, 7}}; @@ -140,6 +142,9 @@ std::unique_ptr Controller::Create(ControllerType type, u32 index) case ControllerType::NeGconRumble: return NeGconRumble::Create(index); + case ControllerType::JogCon: + return JogCon::Create(index); + case ControllerType::None: default: return {}; diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj index 1de21f683..e3f9feaf0 100644 --- a/src/core/core.vcxproj +++ b/src/core/core.vcxproj @@ -51,6 +51,7 @@ + @@ -131,6 +132,7 @@ + diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters index e1dc75c5c..1068324cc 100644 --- a/src/core/core.vcxproj.filters +++ b/src/core/core.vcxproj.filters @@ -65,6 +65,7 @@ + @@ -139,6 +140,7 @@ + diff --git a/src/core/jogcon.cpp b/src/core/jogcon.cpp new file mode 100644 index 000000000..0f1e29719 --- /dev/null +++ b/src/core/jogcon.cpp @@ -0,0 +1,638 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#include "jogcon.h" +#include "host.h" +#include "system.h" + +#include "util/imgui_manager.h" +#include "util/input_manager.h" +#include "util/state_wrapper.h" + +#include "common/assert.h" +#include "common/bitutils.h" +#include "common/error.h" +#include "common/log.h" + +#include "IconsEmoji.h" +#include "IconsPromptFont.h" +#include "fmt/format.h" + +LOG_CHANNEL(AnalogController); + +JogCon::JogCon(u32 index) : Controller(index) +{ +} + +JogCon::~JogCon() = default; + +ControllerType JogCon::GetType() const +{ + return ControllerType::JogCon; +} + +void JogCon::Reset() +{ + // Reset starts in jogcon mode? + m_jogcon_mode = true; + ResetTransferState(); + ResetMotorConfig(); +} + +bool JogCon::DoState(StateWrapper& sw, bool apply_input_state) +{ + if (!Controller::DoState(sw, apply_input_state)) + return false; + + u16 button_state = m_button_state; + s8 steering_state = m_steering_state; + sw.Do(&button_state); + sw.Do(&steering_state); + if (apply_input_state) + { + m_button_state = button_state; + m_steering_state = steering_state; + } + + sw.Do(&m_command); + sw.Do(&m_command_step); + sw.Do(&m_status_byte); + sw.Do(&m_last_steering_state); + sw.Do(&m_last_motor_command); + sw.Do(&m_steering_hold_position); + sw.Do(&m_steering_hold_strength); + + sw.Do(&m_configuration_mode); + + bool jogcon_mode = m_jogcon_mode; + sw.Do(&jogcon_mode); + if (jogcon_mode != m_jogcon_mode) + SetJogConMode(jogcon_mode, true); + + sw.Do(&m_rx_buffer); + sw.Do(&m_tx_buffer); + sw.Do(&m_rumble_config); + + return true; +} + +float JogCon::GetBindState(u32 index) const +{ + if (index >= static_cast(Button::MaxCount)) + { + const u32 sub_index = index - static_cast(Button::MaxCount); + if (sub_index >= static_cast(m_half_axis_state.size())) + return 0.0f; + + return static_cast(m_half_axis_state[sub_index]) * (1.0f / 255.0f); + } + else if (index < static_cast(Button::Mode)) + { + return static_cast(((m_button_state >> index) & 1u) ^ 1u); + } + else + { + return 0.0f; + } +} + +void JogCon::SetBindState(u32 index, float value) +{ + if (index == static_cast(Button::Mode)) + { + // analog toggle + if (value >= m_button_deadzone) + { + if (m_command == Command::Idle) + SetJogConMode(!m_jogcon_mode, true); + else + m_mode_toggle_queued = true; + } + + return; + } + else if (index >= static_cast(Button::MaxCount)) + { + const u32 sub_index = index - static_cast(Button::MaxCount); + if (sub_index >= static_cast(m_half_axis_state.size())) + return; + + const u8 u8_value = static_cast( + std::clamp(((value < m_analog_deadzone) ? 0.0f : value) * m_analog_sensitivity * 255.0f, 0.0f, 255.0f)); + if (u8_value == m_half_axis_state[sub_index]) + return; + + m_half_axis_state[sub_index] = u8_value; + System::SetRunaheadReplayFlag(); + + m_steering_state = + (m_half_axis_state[static_cast(HalfAxis::SteeringRight)] != 0) ? + static_cast((m_half_axis_state[static_cast(HalfAxis::SteeringRight)] / 2)) : + -static_cast((static_cast(m_half_axis_state[static_cast(HalfAxis::SteeringLeft)]) + 1) / 2); + } + + const u16 bit = u16(1) << static_cast(index); + + if (value >= m_button_deadzone) + { + if (m_button_state & bit) + System::SetRunaheadReplayFlag(); + + m_button_state &= ~(bit); + } + else + { + if (!(m_button_state & bit)) + System::SetRunaheadReplayFlag(); + + m_button_state |= bit; + } +} + +u32 JogCon::GetButtonStateBits() const +{ + return m_button_state ^ 0xFFFF; +} + +void JogCon::ResetTransferState() +{ + if (m_mode_toggle_queued) + { + SetJogConMode(!m_jogcon_mode, true); + m_mode_toggle_queued = false; + } + + m_command = Command::Idle; + m_command_step = 0; +} + +u32 JogCon::GetInputOverlayIconColor() const +{ + return m_jogcon_mode ? 0xFF2534F0u : 0xFFCCCCCCu; +} + +void JogCon::SetJogConMode(bool enabled, bool show_message) +{ + if (m_jogcon_mode == enabled) + return; + + m_jogcon_mode = enabled; + m_configuration_mode = enabled && m_configuration_mode; + + INFO_LOG("Controller {} switched to {} mode.", m_index + 1u, m_jogcon_mode ? "JogCon" : "Digital"); + if (show_message) + { + Host::AddIconOSDMessage( + fmt::format("Controller{}JogConMode", m_index), ICON_PF_GAMEPAD_ALT, + m_jogcon_mode ? fmt::format(TRANSLATE_FS("Controller", "Controller {} switched to JogCon mode."), m_index + 1u) : + fmt::format(TRANSLATE_FS("Controller", "Controller {} switched to Digital mode."), m_index + 1u)); + } +} + +u8 JogCon::GetIDByte() const +{ + return Truncate8((GetModeID() << 4) | GetResponseNumHalfwords()); +} + +u8 JogCon::GetModeID() const +{ + if (m_configuration_mode) + return 0xF; + else if (m_jogcon_mode) + return 0xE; + else + return 0x4; +} + +u8 JogCon::GetResponseNumHalfwords() const +{ + return m_jogcon_mode ? 3 : 1; +} + +void JogCon::SetMotorState(u8 value) +{ + const u8 command = (value >> 4); + const u8 strength = (value & 0x0F); + + DEV_LOG("0x{:02X} command=0x{:X} force={}", value, command, strength); + + switch (command) + { + case MOTOR_COMMAND_STOP: + { + m_steering_hold_strength = 0; + SetMotorDirection(MOTOR_COMMAND_STOP, 0); + } + break; + + case MOTOR_COMMAND_RIGHT: + case MOTOR_COMMAND_LEFT: + { + m_steering_hold_strength = 0; + SetMotorDirection(command, strength); + } + break; + + case MOTOR_COMMAND_HOLD: + case MOTOR_COMMAND_DROP_REVOLUTIONS_AND_HOLD: + { + DEV_LOG("Hold wheel in position {} with {} strength.", m_steering_hold_position, strength); + m_steering_hold_strength = strength; + UpdateSteeringHold(); + + if (command == MOTOR_COMMAND_DROP_REVOLUTIONS_AND_HOLD) + ERROR_LOG("JogCon Drop revolutions and hold command is not handled."); + } + break; + + case MOTOR_COMMAND_DROP_REVOLUTIONS: + { + ERROR_LOG("JogCon drop revolutions command is not handled."); + } + break; + + case MOTOR_COMMAND_NEW_HOLD: + { + ERROR_LOG("JogCon new hold position {}", m_steering_state); + m_steering_hold_position = m_steering_state; + } + break; + + default: + { + ERROR_LOG("Unknown JogCon command 0x{:X}", command); + } + break; + } + + m_last_motor_command = command; +} + +void JogCon::SetMotorDirection(u8 direction_command, u8 strength) +{ + if (direction_command == MOTOR_COMMAND_STOP || strength == 0) + { + DEV_LOG("Stop motor"); + if (m_force_feedback_device) + m_force_feedback_device->DisableForce(ForceFeedbackDevice::Effect::Constant); + InputManager::SetPadVibrationIntensity(m_index, 0.0f, 0.0f); + return; + } + + DEV_LOG("Turn wheel {} with {} strength", (direction_command == MOTOR_COMMAND_LEFT) ? "LEFT" : "RIGHT", strength); + + const float f_strength = (static_cast(strength) / 15.0f); + if (m_force_feedback_device) + { + // 0->15 => -32768..32767, direction is flipped because it's indicating where the force is coming _from_. + const s32 ffb_value = + static_cast(f_strength * ((direction_command == MOTOR_COMMAND_LEFT) ? 32767.0f : -32768.0f)); + m_force_feedback_device->SetConstantForce(ffb_value); + } + + InputManager::SetPadVibrationIntensity(m_index, f_strength, 0.0f); +} + +void JogCon::UpdateSteeringHold() +{ + if (m_steering_hold_strength > 0) + { + const u8 direction_command = + (std::abs(static_cast(m_steering_state) - static_cast(m_steering_hold_position)) < + m_steering_hold_deadzone) ? + MOTOR_COMMAND_STOP : + ((m_steering_state < m_steering_hold_position) ? MOTOR_COMMAND_RIGHT : MOTOR_COMMAND_LEFT); + DEV_LOG("Hold strength {} pos {} hold {} dir {}", m_steering_hold_strength, m_steering_state, + m_steering_hold_position, direction_command); + SetMotorDirection(direction_command, m_steering_hold_strength); + } +} + +void JogCon::ResetMotorConfig() +{ + m_rumble_config.fill(0xFF); + SetMotorState(0); +} + +void JogCon::Poll() +{ + m_tx_buffer[2] = Truncate8(m_button_state); + m_tx_buffer[3] = Truncate8(m_button_state >> 8); + + m_tx_buffer[4] = Truncate8(m_steering_state); + m_tx_buffer[5] = Truncate8(m_steering_state >> 8); // 0xFF if negative, otherwise 0x00 + + u8 rotation_state = 0; + if (m_steering_state > m_last_steering_state) + rotation_state = 1; + else if (m_steering_state < m_last_steering_state) + rotation_state = 2; + + m_tx_buffer[6] = rotation_state | (m_last_motor_command << 4); + + m_last_steering_state = m_steering_state; + UpdateSteeringHold(); +} + +bool JogCon::Transfer(const u8 data_in, u8* data_out) +{ + bool ack; + m_rx_buffer[m_command_step] = data_in; + + switch (m_command) + { + case Command::Idle: + { + *data_out = 0xFF; + + if (data_in == 0x01) + { + DEBUG_LOG("ACK controller access"); + m_command = Command::Ready; + m_tx_buffer.fill(0); + m_rx_buffer.fill(0); + return true; + } + + return false; + } + break; + + case Command::Ready: + { + Assert(m_command_step == 0); + + if (data_in == 0x42) + { + m_response_length = (GetResponseNumHalfwords() + 1) * 2; + m_command = Command::ReadPad; + m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + Poll(); + } + else if (m_jogcon_mode && data_in == 0x43) + { + m_response_length = (GetResponseNumHalfwords() + 1) * 2; + m_command = Command::SetMode; + m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + Poll(); + } + else if (m_configuration_mode && data_in == 0x45) + { + m_response_length = (GetResponseNumHalfwords() + 1) * 2; + m_command = Command::GetAnalogMode; + m_tx_buffer = {GetIDByte(), m_status_byte, 0x01, 0x02, BoolToUInt8(m_jogcon_mode), 0x01, 0x01, 0x00}; + } + else if (m_configuration_mode && data_in == 0x46) + { + m_response_length = (GetResponseNumHalfwords() + 1) * 2; + m_command = Command::Command46; + m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + } + else if (m_configuration_mode && data_in == 0x47) + { + m_response_length = (GetResponseNumHalfwords() + 1) * 2; + m_command = Command::Command47; + m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00}; + } + else if (m_configuration_mode && data_in == 0x4C) + { + m_response_length = (GetResponseNumHalfwords() + 1) * 2; + m_command = Command::Command4C; + m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + } + else if (m_configuration_mode && data_in == 0x4D) + { + m_response_length = (GetResponseNumHalfwords() + 1) * 2; + m_command = Command::GetSetRumble; + m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + } + else + { + ERROR_LOG("Unimplemented command 0x{:02X}", data_in); + + *data_out = 0xFF; + return false; + } + } + break; + + case Command::ReadPad: + { + if (m_command_step >= 2 && m_command_step < 7 && m_rumble_config[m_command_step - 2] == 0x00) + SetMotorState(data_in); + } + break; + + case Command::GetAnalogMode: + { + // just send the byte, nothing special to do here + } + break; + + case Command::SetMode: + { + m_configuration_mode = (m_rx_buffer[2] == 1 && m_jogcon_mode); + + if (m_configuration_mode) + m_status_byte = 0x5A; + + DEV_LOG("0x{:02x}({}) config mode", m_rx_buffer[2], m_configuration_mode ? "enter" : "leave"); + } + break; + + case Command::GetSetRumble: + { + if (m_command_step >= 2 && m_command_step < 7) + { + const u8 index = m_command_step - 2; + if (index >= 0) + { + m_tx_buffer[m_command_step] = m_rumble_config[index]; + m_rumble_config[index] = data_in; + + if (data_in == 0x00) + WARNING_LOG("Motor mapped to byte index {}", index); + } + } + else + { + // reset motor value if we're no longer mapping it + if (std::find(m_rumble_config.begin(), m_rumble_config.end(), 0) == m_rumble_config.end()) + SetMotorState(0); + } + } + break; + + case Command::Command46: + { + if (m_command_step == 2) + { + if (data_in == 0x00) + { + m_tx_buffer[4] = 0x01; + m_tx_buffer[5] = 0x02; + m_tx_buffer[6] = 0x00; + m_tx_buffer[7] = 0x0A; + } + else if (data_in == 0x01) + { + m_tx_buffer[4] = 0x01; + m_tx_buffer[5] = 0x01; + m_tx_buffer[6] = 0x01; + m_tx_buffer[7] = 0x14; + } + } + } + break; + + case Command::Command47: + { + if (m_command_step == 2 && data_in != 0x00) + { + m_tx_buffer[4] = 0x00; + m_tx_buffer[5] = 0x00; + m_tx_buffer[6] = 0x00; + m_tx_buffer[7] = 0x00; + } + } + break; + + case Command::Command4C: + { + if (m_command_step == 2) + { + if (data_in == 0x00) + m_tx_buffer[5] = 0x04; + else if (data_in == 0x01) + m_tx_buffer[4] = 0x03; + } + } + break; + + DefaultCaseIsUnreachable(); + } + + *data_out = m_tx_buffer[m_command_step]; + + m_command_step = (m_command_step + 1) % m_response_length; + ack = (m_command_step != 0); + + if (m_command_step == 0) + { + m_command = Command::Idle; + + DEBUG_LOG("Rx: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", m_rx_buffer[0], m_rx_buffer[1], + m_rx_buffer[2], m_rx_buffer[3], m_rx_buffer[4], m_rx_buffer[5], m_rx_buffer[6], m_rx_buffer[7]); + DEBUG_LOG("Tx: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", m_tx_buffer[0], m_tx_buffer[1], + m_tx_buffer[2], m_tx_buffer[3], m_tx_buffer[4], m_tx_buffer[5], m_tx_buffer[6], m_tx_buffer[7]); + } + + return ack; +} + +void JogCon::LoadSettings(const SettingsInterface& si, const char* section, bool initial) +{ + Controller::LoadSettings(si, section, initial); + + m_analog_deadzone = std::clamp(si.GetFloatValue(section, "AnalogDeadzone", DEFAULT_STICK_DEADZONE), 0.0f, 1.0f); + m_analog_sensitivity = + std::clamp(si.GetFloatValue(section, "AnalogSensitivity", DEFAULT_STICK_SENSITIVITY), 0.01f, 3.0f); + m_button_deadzone = std::clamp(si.GetFloatValue(section, "ButtonDeadzone", DEFAULT_BUTTON_DEADZONE), 0.01f, 1.0f); + m_steering_hold_deadzone = static_cast(std::ceil( + std::clamp(si.GetFloatValue(section, "SteeringHoldDeadzone", DEFAULT_STEERING_HOLD_DEADZONE), 0.0f, 1.0f) * + 127.0f)); + + std::string force_feedback_device_name = si.GetStringValue(section, "ForceFeedbackDevice"); + if (m_force_feedback_device_name != force_feedback_device_name) + { + m_force_feedback_device_name = std::move(force_feedback_device_name); + m_force_feedback_device.reset(); + if (!m_force_feedback_device_name.empty()) + { + Error error; + m_force_feedback_device = InputManager::CreateForceFeedbackDevice(m_force_feedback_device_name, &error); + if (!m_force_feedback_device) + { + ERROR_LOG("Failed to create force feedback device: {}", error.GetDescription()); + if (initial) + { + Host::AddIconOSDWarning( + fmt::format("NoFFDevice{}", m_index), ICON_EMOJI_WARNING, + fmt::format(TRANSLATE_FS("JogCon", "Failed to create force feedback device for Port {}:\n{}"), + Controller::GetPortDisplayName(m_index), error.GetDescription()), + Host::OSD_WARNING_DURATION); + } + } + } + } +} + +std::unique_ptr JogCon::Create(u32 index) +{ + return std::make_unique(index); +} + +static const Controller::ControllerBindingInfo s_binding_info[] = { +#define BUTTON(name, display_name, icon_name, button, genb) \ + {name, display_name, icon_name, static_cast(button), InputBindingInfo::Type::Button, genb} +#define AXIS(name, display_name, icon_name, halfaxis, genb) \ + {name, \ + display_name, \ + icon_name, \ + static_cast(JogCon::Button::MaxCount) + static_cast(halfaxis), \ + InputBindingInfo::Type::HalfAxis, \ + genb} + + // clang-format off + BUTTON("Up", TRANSLATE_NOOP("JogCon", "D-Pad Up"), ICON_PF_DPAD_UP, JogCon::Button::Up, GenericInputBinding::DPadUp), + BUTTON("Right", TRANSLATE_NOOP("JogCon", "D-Pad Right"), ICON_PF_DPAD_RIGHT, JogCon::Button::Right, GenericInputBinding::DPadRight), + BUTTON("Down", TRANSLATE_NOOP("JogCon", "D-Pad Down"), ICON_PF_DPAD_DOWN, JogCon::Button::Down, GenericInputBinding::DPadDown), + BUTTON("Left", TRANSLATE_NOOP("JogCon", "D-Pad Left"), ICON_PF_DPAD_LEFT, JogCon::Button::Left, GenericInputBinding::DPadLeft), + BUTTON("Triangle", TRANSLATE_NOOP("JogCon", "Triangle"), ICON_PF_BUTTON_TRIANGLE, JogCon::Button::Triangle, GenericInputBinding::Triangle), + BUTTON("Circle", TRANSLATE_NOOP("JogCon", "Circle"), ICON_PF_BUTTON_CIRCLE, JogCon::Button::Circle, GenericInputBinding::Circle), + BUTTON("Cross", TRANSLATE_NOOP("JogCon", "Cross"), ICON_PF_BUTTON_CROSS, JogCon::Button::Cross, GenericInputBinding::Cross), + BUTTON("Square", TRANSLATE_NOOP("JogCon", "Square"), ICON_PF_BUTTON_SQUARE, JogCon::Button::Square, GenericInputBinding::Square), + BUTTON("Select", TRANSLATE_NOOP("JogCon", "Select"), ICON_PF_SELECT_SHARE, JogCon::Button::Select, GenericInputBinding::Select), + BUTTON("Start", TRANSLATE_NOOP("JogCon", "Start"), ICON_PF_START, JogCon::Button::Start, GenericInputBinding::Start), + BUTTON("L1", TRANSLATE_NOOP("JogCon", "L1"), ICON_PF_LEFT_SHOULDER_L1, JogCon::Button::L1, GenericInputBinding::L1), + BUTTON("R1", TRANSLATE_NOOP("JogCon", "R1"), ICON_PF_RIGHT_SHOULDER_R1, JogCon::Button::R1, GenericInputBinding::R1), + BUTTON("L2", TRANSLATE_NOOP("JogCon", "L2"), ICON_PF_LEFT_TRIGGER_L2, JogCon::Button::L2, GenericInputBinding::L2), + BUTTON("R2", TRANSLATE_NOOP("JogCon", "R2"), ICON_PF_RIGHT_TRIGGER_R2, JogCon::Button::R2, GenericInputBinding::R2), + BUTTON("Mode", TRANSLATE_NOOP("JogCon", "Mode"), ICON_PF_ANALOG_LEFT_RIGHT, JogCon::Button::Mode, GenericInputBinding::System), + + AXIS("SteeringLeft", TRANSLATE_NOOP("JogCon", "Steering Left"), ICON_PF_ANALOG_LEFT, JogCon::HalfAxis::SteeringLeft, GenericInputBinding::LeftStickLeft), + AXIS("SteeringRight", TRANSLATE_NOOP("JogCon", "Steering Right"), ICON_PF_ANALOG_RIGHT, JogCon::HalfAxis::SteeringRight, GenericInputBinding::LeftStickRight), + + // clang-format on + + {"ForceFeedbackDevice", TRANSLATE_NOOP("JogCon", "Force Feedback Device"), nullptr, + static_cast(JogCon::Button::MaxCount) + static_cast(JogCon::HalfAxis::MaxCount), + InputBindingInfo::Type::Device, GenericInputBinding::Unknown}, + +#undef BUTTON +#undef AXIS +}; + +static const SettingInfo s_settings[] = { + {SettingInfo::Type::Float, "AnalogDeadzone", TRANSLATE_NOOP("JogCon", "Analog Deadzone"), + TRANSLATE_NOOP("JogCon", + "Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored."), + "0.00f", "0.00f", "1.00f", "0.01f", "%.0f%%", nullptr, 100.0f}, + {SettingInfo::Type::Float, "AnalogSensitivity", TRANSLATE_NOOP("JogCon", "Analog Sensitivity"), + TRANSLATE_NOOP("JogCon", "Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended " + "when using recent controllers, e.g. DualShock 4, Xbox One Controller."), + "1.33f", "0.01f", "2.00f", "0.01f", "%.0f%%", nullptr, 100.0f}, + {SettingInfo::Type::Float, "ButtonDeadzone", TRANSLATE_NOOP("JogCon", "Button/Trigger Deadzone"), + TRANSLATE_NOOP( + "JogCon", + "Sets the deadzone for activating buttons/triggers, i.e. the fraction of the trigger which will be ignored."), + "0.25", "0.01", "1.00", "0.01", "%.0f%%", nullptr, 100.0f}, + {SettingInfo::Type::Float, "SteeringHoldDeadzone", TRANSLATE_NOOP("JogCon", "Steering Hold Deadzone"), + TRANSLATE_NOOP( + "JogCon", "Sets the deadzone for holding the wheel at the set position, i.e. when it will not trigger an effect."), + "0.03", "0.01", "1.00", "0.01", "%.0f%%", nullptr, 100.0f}, +}; + +const Controller::ControllerInfo JogCon::INFO = { + ControllerType::JogCon, "JogCon", TRANSLATE_NOOP("ControllerType", "JogCon"), ICON_PF_STEERING_WHEEL, + s_binding_info, s_settings, Controller::VibrationCapabilities::SingleMotor}; diff --git a/src/core/jogcon.h b/src/core/jogcon.h new file mode 100644 index 000000000..ebbc1d43a --- /dev/null +++ b/src/core/jogcon.h @@ -0,0 +1,150 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#pragma once + +#include "controller.h" + +#include + +class ForceFeedbackDevice; + +class JogCon final : public Controller +{ +public: + enum class Button : u8 + { + Select = 0, + L3 = 1, + R3 = 2, + Start = 3, + Up = 4, + Right = 5, + Down = 6, + Left = 7, + L2 = 8, + R2 = 9, + L1 = 10, + R1 = 11, + Triangle = 12, + Circle = 13, + Cross = 14, + Square = 15, + Mode = 16, + MaxCount + }; + + enum class HalfAxis : u8 + { + SteeringLeft, + SteeringRight, + MaxCount, + }; + + static const Controller::ControllerInfo INFO; + + JogCon(u32 index); + ~JogCon() override; + + static std::unique_ptr Create(u32 index); + + ControllerType GetType() const override; + + void Reset() override; + bool DoState(StateWrapper& sw, bool apply_input_state) override; + + float GetBindState(u32 index) const override; + void SetBindState(u32 index, float value) override; + u32 GetButtonStateBits() const override; + u32 GetInputOverlayIconColor() const override; + + void ResetTransferState() override; + bool Transfer(const u8 data_in, u8* data_out) override; + + void LoadSettings(const SettingsInterface& si, const char* section, bool initial) override; + +private: + enum class Command : u8 + { + Idle, + Ready, + ReadPad, + SetMode, + GetAnalogMode, + GetSetRumble, + Command46, + Command47, + Command4C, + }; + + enum : u8 + { + LargeMotor = 0, + SmallMotor = 1 + }; + + enum : u8 + { + MOTOR_COMMAND_STOP = 0x0, + MOTOR_COMMAND_RIGHT = 0x1, + MOTOR_COMMAND_LEFT = 0x2, + MOTOR_COMMAND_HOLD = 0x3, + MOTOR_COMMAND_DROP_REVOLUTIONS = 0x8, + MOTOR_COMMAND_DROP_REVOLUTIONS_AND_HOLD = 0xB, + MOTOR_COMMAND_NEW_HOLD = 0xC, + }; + + static constexpr float DEFAULT_STEERING_HOLD_DEADZONE = 0.03f; + + u8 GetIDByte() const; + u8 GetModeID() const; + + // Get number of response halfwords (excluding the initial controller info halfword) + u8 GetResponseNumHalfwords() const; + + void Poll(); + void UpdateSteeringHold(); + + void SetMotorState(u8 value); + void SetMotorDirection(u8 direction_command, u8 strength); + void ResetMotorConfig(); + + void SetJogConMode(bool enabled, bool show_message); + + // buttons are active low + u16 m_button_state = UINT16_C(0xFFFF); + s8 m_steering_state = 0; + + // both directions of axis state, merged to m_steering_state + std::array(HalfAxis::MaxCount)> m_half_axis_state{}; + + Command m_command = Command::Idle; + u8 m_command_step = 0; + u8 m_response_length = 0; + u8 m_status_byte = 0x5A; + + s8 m_last_steering_state = 0; + u8 m_last_motor_command = 0; + s8 m_steering_hold_position = 0; + u8 m_steering_hold_strength = 0; + + bool m_configuration_mode = false; + bool m_jogcon_mode = false; + bool m_mode_toggle_queued = false; + + std::array m_rumble_config{}; + + // Transmit and receive buffers, not including the first Hi-Z/ack response byte + static constexpr u32 MAX_RESPONSE_LENGTH = 8; + std::array m_rx_buffer; + std::array m_tx_buffer; + + s8 m_steering_hold_deadzone = 0; + + float m_analog_deadzone = 0.0f; + float m_analog_sensitivity = 1.33f; + float m_button_deadzone = 0.0f; + + std::string m_force_feedback_device_name; + std::unique_ptr m_force_feedback_device; +}; diff --git a/src/core/types.h b/src/core/types.h index 3c532b73b..90eb27b7c 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -224,6 +224,7 @@ enum class ControllerType : u8 Justifier, PopnController, DDGoController, + JogCon, Count }; diff --git a/src/util/imgui_glyph_ranges.inl b/src/util/imgui_glyph_ranges.inl index 95c81ba6a..28babefd5 100644 --- a/src/util/imgui_glyph_ranges.inl +++ b/src/util/imgui_glyph_ranges.inl @@ -3,6 +3,6 @@ static constexpr ImWchar FA_ICON_RANGE[] = { 0xe06f,0xe070,0xe086,0xe086,0xf002,0xf002,0xf005,0xf005,0xf007,0xf007,0xf00c,0xf00e,0xf011,0xf013,0xf017,0xf017,0xf019,0xf019,0xf01c,0xf01c,0xf021,0xf021,0xf023,0xf023,0xf025,0xf026,0xf028,0xf028,0xf02e,0xf02e,0xf030,0xf030,0xf03a,0xf03a,0xf03d,0xf03d,0xf04a,0xf04c,0xf050,0xf050,0xf056,0xf056,0xf05e,0xf05e,0xf062,0xf063,0xf065,0xf067,0xf071,0xf071,0xf075,0xf075,0xf077,0xf078,0xf07b,0xf07c,0xf083,0xf085,0xf091,0xf091,0xf0ac,0xf0ae,0xf0b2,0xf0b2,0xf0c3,0xf0c3,0xf0c5,0xf0c5,0xf0c7,0xf0c9,0xf0cb,0xf0cb,0xf0d0,0xf0d0,0xf0dc,0xf0dc,0xf0e0,0xf0e0,0xf0e2,0xf0e2,0xf0e7,0xf0e8,0xf0eb,0xf0eb,0xf0f1,0xf0f1,0xf0f3,0xf0f3,0xf0fe,0xf0fe,0xf110,0xf110,0xf119,0xf119,0xf11b,0xf11c,0xf140,0xf140,0xf144,0xf144,0xf146,0xf146,0xf14a,0xf14a,0xf15b,0xf15d,0xf191,0xf192,0xf1ab,0xf1ab,0xf1c0,0xf1c0,0xf1c5,0xf1c5,0xf1de,0xf1de,0xf1e6,0xf1e6,0xf1eb,0xf1eb,0xf1f8,0xf1f8,0xf1fb,0xf1fc,0xf201,0xf201,0xf240,0xf240,0xf242,0xf242,0xf245,0xf245,0xf26c,0xf26c,0xf279,0xf279,0xf2c1,0xf2c1,0xf2d0,0xf2d0,0xf2db,0xf2db,0xf2f1,0xf2f2,0xf302,0xf302,0xf31e,0xf31e,0xf338,0xf338,0xf35d,0xf35d,0xf360,0xf360,0xf362,0xf362,0xf3fd,0xf3fd,0xf410,0xf410,0xf422,0xf422,0xf424,0xf424,0xf462,0xf462,0xf466,0xf466,0xf4ce,0xf4ce,0xf500,0xf500,0xf51f,0xf51f,0xf538,0xf538,0xf53f,0xf53f,0xf545,0xf545,0xf547,0xf548,0xf54c,0xf54c,0xf55b,0xf55b,0xf55d,0xf55d,0xf565,0xf565,0xf56e,0xf570,0xf575,0xf575,0xf5a2,0xf5a2,0xf5aa,0xf5aa,0xf5c7,0xf5c7,0xf5cb,0xf5cb,0xf5e7,0xf5e7,0xf5ee,0xf5ee,0xf61f,0xf61f,0xf65d,0xf65e,0xf6a9,0xf6a9,0xf6cf,0xf6cf,0xf70c,0xf70c,0xf70e,0xf70e,0xf78c,0xf78c,0xf794,0xf794,0xf7a0,0xf7a0,0xf7a4,0xf7a5,0xf7c2,0xf7c2,0xf807,0xf807,0xf815,0xf815,0xf818,0xf818,0xf84c,0xf84c,0xf87d,0xf87d,0xf8cc,0xf8cc,0x0,0x0 }; -static constexpr ImWchar PF_ICON_RANGE[] = { 0x2196,0x2199,0x219e,0x21a1,0x21b0,0x21b3,0x21ba,0x21c3,0x21c7,0x21ca,0x21d0,0x21d4,0x21dc,0x21dd,0x21e0,0x21e3,0x21ed,0x21ee,0x21f7,0x21f8,0x21fa,0x21fb,0x227a,0x227f,0x2284,0x2284,0x2349,0x2349,0x235e,0x235e,0x2360,0x2361,0x2364,0x2366,0x23b2,0x23b4,0x23ce,0x23ce,0x23f4,0x23f7,0x2427,0x243a,0x243c,0x243e,0x2460,0x246b,0x248f,0x248f,0x24f5,0x24fd,0x24ff,0x24ff,0x2717,0x2717,0x278a,0x278e,0x27fc,0x27fc,0xe001,0xe001,0xff21,0xff3a,0x1f52b,0x1f52b,0x0,0x0 }; +static constexpr ImWchar PF_ICON_RANGE[] = { 0x2196,0x2199,0x219e,0x21a1,0x21b0,0x21b3,0x21ba,0x21c3,0x21c7,0x21ca,0x21d0,0x21d4,0x21dc,0x21dd,0x21e0,0x21e3,0x21ed,0x21ee,0x21f7,0x21f8,0x21fa,0x21fb,0x227a,0x227f,0x2284,0x2284,0x2349,0x2349,0x235e,0x235e,0x2360,0x2361,0x2364,0x2366,0x23b2,0x23b4,0x23ce,0x23ce,0x23f4,0x23f7,0x2427,0x243a,0x243c,0x243e,0x2460,0x246b,0x248f,0x248f,0x24f5,0x24fd,0x24ff,0x24ff,0x2717,0x2717,0x278a,0x278e,0x27fc,0x27fc,0xe001,0xe001,0xff21,0xff3a,0x1f52b,0x1f52b,0x1f578,0x1f578,0x0,0x0 }; static constexpr ImWchar EMOJI_ICON_RANGE[] = { 0x2139,0x2139,0x23e9,0x23ea,0x23f8,0x23f8,0x26a0,0x26a0,0x1f4be,0x1f4be,0x1f4c2,0x1f4c2,0x1f4f7,0x1f4f8,0x1f504,0x1f504,0x1f507,0x1f507,0x1f509,0x1f50a,0x1f50d,0x1f50d,0x1f513,0x1f513,0x0,0x0 }; From aff623b7725a45260d7cd4a6d1bf479b18000a23 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 1 Dec 2024 21:12:44 +1000 Subject: [PATCH 40/69] GameDB: JogCon games --- data/resources/gamedb.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/data/resources/gamedb.yaml b/data/resources/gamedb.yaml index eacaad2a6..4388c4cd5 100644 --- a/data/resources/gamedb.yaml +++ b/data/resources/gamedb.yaml @@ -19211,6 +19211,7 @@ SLUS-01170: - AnalogController - DigitalController - PlayStationMouse + - JogCon metadata: publisher: "Atari / Hasbro Interactive" developer: "Supersonic Software Ltd" @@ -19231,6 +19232,7 @@ SLES-02854: - AnalogController - DigitalController - PlayStationMouse + - JogCon metadata: publisher: "Atari / Hasbro Interactive" developer: "Supersonic Software Ltd" @@ -113123,6 +113125,7 @@ SLUS-01003: - AnalogController - DigitalController - NeGcon + - JogCon metadata: publisher: "Electronic Arts / Infogrames" developer: "Eden Studios" @@ -131668,6 +131671,7 @@ SCPS-45354: - AnalogController - DigitalController - NeGcon + - JogCon codes: - SCPS-45354 - SCPS-45355 @@ -131691,6 +131695,7 @@ SLPS-01798: - AnalogController - DigitalController - NeGcon + - JogCon metadata: publisher: "Namco" developer: "Namco" @@ -131711,6 +131716,7 @@ SCPS-45356: - AnalogController - DigitalController - NeGcon + - JogCon codes: - SCPS-45356 - SLPS-01800 @@ -131736,6 +131742,7 @@ SLUS-00797: - AnalogController - DigitalController - NeGcon + - JogCon metadata: publisher: "Namco" developer: "Namco" @@ -131756,6 +131763,7 @@ SLPS-91463: - AnalogController - DigitalController - NeGcon + - JogCon metadata: publisher: "Namco" developer: "Namco" @@ -136483,6 +136491,7 @@ SCES-01706: - AnalogController - DigitalController - NeGcon + - JogCon metadata: publisher: "Sony Computer Entertaiment Europe" developer: "Namco" @@ -179410,6 +179419,7 @@ SLES-01907: - AnalogController - DigitalController - NeGcon + - JogCon libcrypt: true metadata: publisher: "Infogrames" @@ -179433,6 +179443,7 @@ SLPS-02516: - AnalogController - DigitalController - NeGcon + - JogCon codes: - SLPS-02516 - SLPS-91467 From 4aa9857c531e296d717defd1d74ab134c902e4ba Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 1 Dec 2024 19:23:38 +1000 Subject: [PATCH 41/69] Qt: Fix fallback binding layout not including motors --- .../controllerbindingwidgets.cpp | 40 +++++++++++++++++-- src/duckstation-qt/inputbindingwidgets.cpp | 4 +- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/duckstation-qt/controllerbindingwidgets.cpp b/src/duckstation-qt/controllerbindingwidgets.cpp index 500956015..226d2c578 100644 --- a/src/duckstation-qt/controllerbindingwidgets.cpp +++ b/src/duckstation-qt/controllerbindingwidgets.cpp @@ -426,6 +426,41 @@ void ControllerBindingWidget::createBindingWidgets(QWidget* parent) } } } + if (m_controller_info->vibration_caps != Controller::VibrationCapabilities::NoVibration) + { + const bool dual_motors = (m_controller_info->vibration_caps == Controller::VibrationCapabilities::LargeSmallMotors); + if (!axis_gbox) + { + axis_gbox = new QGroupBox(tr("Axes"), scrollarea_widget); + axis_layout = new QGridLayout(axis_gbox); + } + + QGroupBox* gbox = new QGroupBox(dual_motors ? tr("Large Motor") : tr("Vibration"), axis_gbox); + QVBoxLayout* temp = new QVBoxLayout(gbox); + InputVibrationBindingWidget* widget = + new InputVibrationBindingWidget(gbox, getDialog(), getConfigSection(), dual_motors ? "LargeMotor" : "Motor"); + temp->addWidget(widget); + axis_layout->addWidget(gbox, row, column); + if ((++column) == NUM_AXIS_COLUMNS) + { + column = 0; + row++; + } + + if (m_controller_info->vibration_caps == Controller::VibrationCapabilities::LargeSmallMotors) + { + gbox = new QGroupBox(tr("Small Motor"), axis_gbox); + temp = new QVBoxLayout(gbox); + widget = new InputVibrationBindingWidget(gbox, getDialog(), getConfigSection(), "SmallMotor"); + temp->addWidget(widget); + axis_layout->addWidget(gbox, row, column); + if ((++column) == NUM_AXIS_COLUMNS) + { + column = 0; + row++; + } + } + } if (axis_gbox) axis_layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding), ++row, 0); @@ -467,10 +502,9 @@ void ControllerBindingWidget::createBindingWidgets(QWidget* parent) QHBoxLayout* layout = new QHBoxLayout(scrollarea_widget); if (axis_gbox) - layout->addWidget(axis_gbox); + layout->addWidget(axis_gbox, 1); if (button_gbox) - layout->addWidget(button_gbox); - layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum)); + layout->addWidget(button_gbox, 1); QHBoxLayout* main_layout = new QHBoxLayout(parent); main_layout->addWidget(scrollarea); diff --git a/src/duckstation-qt/inputbindingwidgets.cpp b/src/duckstation-qt/inputbindingwidgets.cpp index 27d5ee7b3..cc54a81ff 100644 --- a/src/duckstation-qt/inputbindingwidgets.cpp +++ b/src/duckstation-qt/inputbindingwidgets.cpp @@ -29,8 +29,8 @@ InputBindingWidget::InputBindingWidget(QWidget* parent, SettingsInterface* sif, std::string section_name, std::string key_name) : QPushButton(parent) { - setMinimumWidth(225); - setMaximumWidth(225); + setMinimumWidth(220); + setMaximumWidth(220); connect(this, &QPushButton::clicked, this, &InputBindingWidget::onClicked); From 2a7625e67c1a7fd9e97cfa4d85333c21f5d5db16 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 1 Dec 2024 23:21:11 +1000 Subject: [PATCH 42/69] CI: Add workflow dispatch triggers for all jobs Useful for running just one platform on a branch. --- .github/workflows/linux-appimage-build.yml | 1 + .github/workflows/linux-flatpak-build.yml | 1 + .github/workflows/macos-build.yml | 1 + .github/workflows/main.yml | 1 + .github/workflows/windows-build.yml | 1 + 5 files changed, 5 insertions(+) diff --git a/.github/workflows/linux-appimage-build.yml b/.github/workflows/linux-appimage-build.yml index 51ec4b9cd..56daf2bcf 100644 --- a/.github/workflows/linux-appimage-build.yml +++ b/.github/workflows/linux-appimage-build.yml @@ -2,6 +2,7 @@ name: 🐧 Linux AppImage on: workflow_call: + workflow_dispatch: jobs: linux-x64-appimage-build: diff --git a/.github/workflows/linux-flatpak-build.yml b/.github/workflows/linux-flatpak-build.yml index 5f3162099..8f34e9a8f 100644 --- a/.github/workflows/linux-flatpak-build.yml +++ b/.github/workflows/linux-flatpak-build.yml @@ -11,6 +11,7 @@ on: required: false type: string default: "stable" + workflow_dispatch: jobs: linux-flatpak-build: diff --git a/.github/workflows/macos-build.yml b/.github/workflows/macos-build.yml index 85c3c0a01..af08b3dbc 100644 --- a/.github/workflows/macos-build.yml +++ b/.github/workflows/macos-build.yml @@ -2,6 +2,7 @@ name: 🍎 MacOS on: workflow_call: + workflow_dispatch: jobs: macos-build: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2ec9885a7..2f4d38e90 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,7 @@ name: Automated Builds on: + workflow_dispatch: pull_request: paths-ignore: - '**.md' diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index de88a9028..73d76b603 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -2,6 +2,7 @@ name: 💻 Windows on: workflow_call: + workflow_dispatch: jobs: windows-x64-build: From 0faa9cf650c5b081858c25e4172265fd8d23ab4c Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 1 Dec 2024 22:23:46 +1000 Subject: [PATCH 43/69] Build: Add Devel configuration Gets you debug assertions and logging, while still producing an optimized executable. --- CMakeLists.txt | 8 +- CMakeModules/DuckStationBuildOptions.cmake | 14 ++ dep/msvc/vsprops/Base.props | 5 + dep/msvc/vsprops/Configurations.props | 8 + duckstation.sln | 162 ++++++++++++++++----- src/common/assert.h | 4 +- src/common/log.h | 4 +- src/core/CMakeLists.txt | 2 +- src/core/achievements.cpp | 2 +- src/core/bus.cpp | 4 +- src/core/cdrom.cpp | 2 +- src/core/core.vcxproj | 6 +- src/core/cpu_code_cache.cpp | 6 +- src/core/cpu_code_cache_private.h | 2 +- src/core/cpu_core.cpp | 2 +- src/core/cpu_recompiler.cpp | 8 +- src/core/dma.cpp | 4 +- src/core/gpu_dump.cpp | 2 +- src/core/gpu_hw.cpp | 6 +- src/core/gpu_hw_texture_cache.cpp | 6 +- src/core/guncon.cpp | 2 +- src/core/interrupt_controller.cpp | 4 +- src/core/memory_card.cpp | 2 +- src/core/timing_event.cpp | 2 +- src/duckstation-qt/CMakeLists.txt | 2 +- src/duckstation-qt/autoupdaterdialog.cpp | 2 +- src/duckstation-qt/qthost.cpp | 4 +- src/util/CMakeLists.txt | 2 +- src/util/cd_image_pbp.cpp | 12 +- src/util/d3d11_device.cpp | 10 +- src/util/d3d12_builders.cpp | 2 +- src/util/d3d12_builders.h | 2 +- src/util/d3d12_descriptor_heap_manager.cpp | 2 +- src/util/d3d12_device.cpp | 12 +- src/util/gpu_device.h | 2 +- src/util/imgui_fullscreen.cpp | 2 +- src/util/media_capture.cpp | 4 +- src/util/opengl_device.cpp | 6 +- src/util/opengl_pipeline.cpp | 6 +- src/util/opengl_pipeline.h | 2 +- src/util/opengl_stream_buffer.cpp | 2 +- src/util/opengl_texture.cpp | 6 +- src/util/postprocessing_shader_fx.cpp | 2 +- src/util/postprocessing_shader_fx.h | 2 +- src/util/sdl_input_source.cpp | 2 +- src/util/sockets.cpp | 2 +- src/util/util.props | 2 +- src/util/vulkan_builders.h | 2 +- src/util/vulkan_device.cpp | 12 +- 49 files changed, 247 insertions(+), 124 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bdc61007d..8e8d37371 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,10 +42,6 @@ if(LINUX OR BSD) set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) endif() -# Set _DEBUG macro for Debug builds. -set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D_DEBUG") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG") - # Release build optimizations for MSVC. if(MSVC) add_definitions("/D_CRT_SECURE_NO_WARNINGS") @@ -59,14 +55,18 @@ if(MSVC) # RelWithDebInfo is set to Ob1 instead of Ob2. string(REPLACE "/Ob1" "/Ob2" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") + string(REPLACE "/Ob1" "/Ob2" CMAKE_C_FLAGS_DEVEL "${CMAKE_C_FLAGS_DEVEL}") string(REPLACE "/Ob1" "/Ob2" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") + string(REPLACE "/Ob1" "/Ob2" CMAKE_CXX_FLAGS_DEVEL "${CMAKE_CXX_FLAGS_DEVEL}") # Disable incremental linking in RelWithDebInfo. string(REPLACE "/INCREMENTAL" "/INCREMENTAL:NO" CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO}") + string(REPLACE "/INCREMENTAL" "/INCREMENTAL:NO" CMAKE_EXE_LINKER_FLAGS_DEVEL "${CMAKE_EXE_LINKER_FLAGS_DEVEL}") # COMDAT folding/remove unused functions. set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /OPT:REF /OPT:ICF") set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} /OPT:REF /OPT:ICF") + set(CMAKE_EXE_LINKER_FLAGS_DEVEL "${CMAKE_EXE_LINKER_FLAGS_DEVEL} /OPT:REF /OPT:ICF") endif() # Warning disables. diff --git a/CMakeModules/DuckStationBuildOptions.cmake b/CMakeModules/DuckStationBuildOptions.cmake index 19b8d2a6f..0bc28c52a 100644 --- a/CMakeModules/DuckStationBuildOptions.cmake +++ b/CMakeModules/DuckStationBuildOptions.cmake @@ -16,3 +16,17 @@ endif() if(APPLE) option(SKIP_POSTPROCESS_BUNDLE "Disable bundle post-processing, including Qt additions" OFF) endif() + +# Set _DEBUG macro for Debug builds. +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D_DEBUG") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG") + +# Create the Devel build type based on RelWithDebInfo. +set(CMAKE_C_FLAGS_DEVEL "${CMAKE_C_FLAGS_RELWITHDEBINFO} -D_DEVEL" CACHE STRING "Flags used by the C compiler during DEVEL builds." FORCE) +set(CMAKE_CXX_FLAGS_DEVEL "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -D_DEVEL" CACHE STRING "Flags used by the CXX compiler during DEVEL builds." FORCE) +set(CMAKE_EXE_LINKER_FLAGS_DEVEL "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO}" CACHE STRING "Flags used for the linker during DEVEL builds." FORCE) +set(CMAKE_MODULE_LINKER_FLAGS_DEVEL "${CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO}" CACHE STRING "Flags used by the linker during the creation of modules during DEVEL builds." FORCE) +set(CMAKE_SHARED_LINKER_FLAGS_DEVEL "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO}" CACHE STRING "Flags used by the linker during the creation of shared libraries during DEVEL builds." FORCE) +set(CMAKE_STATIC_LINKER_FLAGS_DEVEL "${CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO}" CACHE STRING "Flags used by the linker during the creation of static libraries during DEVEL builds." FORCE) +list(APPEND CMAKE_CONFIGURATION_TYPES "Devel") +mark_as_advanced(CMAKE_C_FLAGS_DEVEL CMAKE_CXX_FLAGS_DEVEL CMAKE_EXE_LINKER_FLAGS_DEVEL CMAKE_MODULE_LINKER_FLAGS_DEVEL CMAKE_SHARED_LINKER_FLAGS_DEVEL CMAKE_STATIC_LINKER_FLAGS_DEVEL) diff --git a/dep/msvc/vsprops/Base.props b/dep/msvc/vsprops/Base.props index 75895dfe0..8fe2ec131 100644 --- a/dep/msvc/vsprops/Base.props +++ b/dep/msvc/vsprops/Base.props @@ -82,4 +82,9 @@ UseLinkTimeCodeGeneration + + + _DEVEL;%(PreprocessorDefinitions) + + \ No newline at end of file diff --git a/dep/msvc/vsprops/Configurations.props b/dep/msvc/vsprops/Configurations.props index ad161b73a..8be0c0609 100644 --- a/dep/msvc/vsprops/Configurations.props +++ b/dep/msvc/vsprops/Configurations.props @@ -57,6 +57,14 @@ Release-Clang x64 + + Devel-Clang + ARM64 + + + Devel-Clang + x64 + diff --git a/duckstation.sln b/duckstation.sln index fdf139d29..d168d2f34 100644 --- a/duckstation.sln +++ b/duckstation.sln @@ -67,6 +67,8 @@ Global DebugFast|x64 = DebugFast|x64 DebugFast-Clang|ARM64 = DebugFast-Clang|ARM64 DebugFast-Clang|x64 = DebugFast-Clang|x64 + Devel-Clang|ARM64 = Devel-Clang|ARM64 + Devel-Clang|x64 = Devel-Clang|x64 Release|ARM64 = Release|ARM64 Release|x64 = Release|x64 Release-Clang|ARM64 = Release-Clang|ARM64 @@ -79,6 +81,44 @@ Global ReleaseLTCG-Clang-SSE2|x64 = ReleaseLTCG-Clang-SSE2|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug|ARM64.ActiveCfg = Debug-Clang|ARM64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug|x64.ActiveCfg = Debug|x64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug|x64.Build.0 = Debug|x64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug-Clang|ARM64.ActiveCfg = Debug-Clang|ARM64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug-Clang|ARM64.Build.0 = Debug-Clang|ARM64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug-Clang|x64.ActiveCfg = Debug-Clang|x64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug-Clang|x64.Build.0 = Debug-Clang|x64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug-Clang-SSE2|ARM64.ActiveCfg = Debug-Clang|ARM64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug-Clang-SSE2|x64.ActiveCfg = Debug-Clang-SSE2|x64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug-Clang-SSE2|x64.Build.0 = Debug-Clang-SSE2|x64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.DebugFast|ARM64.ActiveCfg = DebugFast-Clang|ARM64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.DebugFast|x64.ActiveCfg = DebugFast|x64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.DebugFast|x64.Build.0 = DebugFast|x64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.DebugFast-Clang|ARM64.ActiveCfg = DebugFast-Clang|ARM64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.Devel-Clang|ARM64.Build.0 = Devel-Clang|ARM64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.Devel-Clang|x64.Build.0 = Devel-Clang|x64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.Release|x64.ActiveCfg = Release|x64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.Release|x64.Build.0 = Release|x64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.Release-Clang|ARM64.ActiveCfg = Release-Clang|ARM64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.Release-Clang|ARM64.Build.0 = Release-Clang|ARM64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.Release-Clang|x64.ActiveCfg = Release-Clang|x64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.Release-Clang|x64.Build.0 = Release-Clang|x64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG|ARM64.ActiveCfg = ReleaseLTCG-Clang|ARM64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG|x64.ActiveCfg = ReleaseLTCG|x64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG|x64.Build.0 = ReleaseLTCG|x64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG-Clang|ARM64.ActiveCfg = ReleaseLTCG-Clang|ARM64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG-Clang|ARM64.Build.0 = ReleaseLTCG-Clang|ARM64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG-Clang|x64.ActiveCfg = ReleaseLTCG-Clang|x64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG-Clang|x64.Build.0 = ReleaseLTCG-Clang|x64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG-Clang-SSE2|ARM64.ActiveCfg = ReleaseLTCG-Clang|ARM64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG-Clang-SSE2|x64.ActiveCfg = ReleaseLTCG-Clang-SSE2|x64 + {28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG-Clang-SSE2|x64.Build.0 = ReleaseLTCG-Clang-SSE2|x64 {43540154-9E1E-409C-834F-B84BE5621388}.Debug|ARM64.ActiveCfg = Debug-Clang|ARM64 {43540154-9E1E-409C-834F-B84BE5621388}.Debug|x64.ActiveCfg = Debug|x64 {43540154-9E1E-409C-834F-B84BE5621388}.Debug|x64.Build.0 = Debug|x64 @@ -96,6 +136,10 @@ Global {43540154-9E1E-409C-834F-B84BE5621388}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 {43540154-9E1E-409C-834F-B84BE5621388}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 {43540154-9E1E-409C-834F-B84BE5621388}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 + {43540154-9E1E-409C-834F-B84BE5621388}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {43540154-9E1E-409C-834F-B84BE5621388}.Devel-Clang|ARM64.Build.0 = Devel-Clang|ARM64 + {43540154-9E1E-409C-834F-B84BE5621388}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 + {43540154-9E1E-409C-834F-B84BE5621388}.Devel-Clang|x64.Build.0 = Devel-Clang|x64 {43540154-9E1E-409C-834F-B84BE5621388}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {43540154-9E1E-409C-834F-B84BE5621388}.Release|x64.ActiveCfg = Release|x64 {43540154-9E1E-409C-834F-B84BE5621388}.Release|x64.Build.0 = Release|x64 @@ -130,6 +174,10 @@ Global {BB08260F-6FBC-46AF-8924-090EE71360C6}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 {BB08260F-6FBC-46AF-8924-090EE71360C6}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 {BB08260F-6FBC-46AF-8924-090EE71360C6}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 + {BB08260F-6FBC-46AF-8924-090EE71360C6}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {BB08260F-6FBC-46AF-8924-090EE71360C6}.Devel-Clang|ARM64.Build.0 = Devel-Clang|ARM64 + {BB08260F-6FBC-46AF-8924-090EE71360C6}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 + {BB08260F-6FBC-46AF-8924-090EE71360C6}.Devel-Clang|x64.Build.0 = Devel-Clang|x64 {BB08260F-6FBC-46AF-8924-090EE71360C6}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {BB08260F-6FBC-46AF-8924-090EE71360C6}.Release|x64.ActiveCfg = Release|x64 {BB08260F-6FBC-46AF-8924-090EE71360C6}.Release|x64.Build.0 = Release|x64 @@ -164,6 +212,10 @@ Global {EE054E08-3799-4A59-A422-18259C105FFD}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 {EE054E08-3799-4A59-A422-18259C105FFD}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 {EE054E08-3799-4A59-A422-18259C105FFD}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 + {EE054E08-3799-4A59-A422-18259C105FFD}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {EE054E08-3799-4A59-A422-18259C105FFD}.Devel-Clang|ARM64.Build.0 = Devel-Clang|ARM64 + {EE054E08-3799-4A59-A422-18259C105FFD}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 + {EE054E08-3799-4A59-A422-18259C105FFD}.Devel-Clang|x64.Build.0 = Devel-Clang|x64 {EE054E08-3799-4A59-A422-18259C105FFD}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {EE054E08-3799-4A59-A422-18259C105FFD}.Release|x64.ActiveCfg = Release|x64 {EE054E08-3799-4A59-A422-18259C105FFD}.Release|x64.Build.0 = Release|x64 @@ -198,6 +250,10 @@ Global {868B98C8-65A1-494B-8346-250A73A48C0A}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 {868B98C8-65A1-494B-8346-250A73A48C0A}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 {868B98C8-65A1-494B-8346-250A73A48C0A}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 + {868B98C8-65A1-494B-8346-250A73A48C0A}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {868B98C8-65A1-494B-8346-250A73A48C0A}.Devel-Clang|ARM64.Build.0 = Devel-Clang|ARM64 + {868B98C8-65A1-494B-8346-250A73A48C0A}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 + {868B98C8-65A1-494B-8346-250A73A48C0A}.Devel-Clang|x64.Build.0 = Devel-Clang|x64 {868B98C8-65A1-494B-8346-250A73A48C0A}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {868B98C8-65A1-494B-8346-250A73A48C0A}.Release|x64.ActiveCfg = Release|x64 {868B98C8-65A1-494B-8346-250A73A48C0A}.Release|x64.Build.0 = Release|x64 @@ -232,6 +288,10 @@ Global {3773F4CC-614E-4028-8595-22E08CA649E3}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 {3773F4CC-614E-4028-8595-22E08CA649E3}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 {3773F4CC-614E-4028-8595-22E08CA649E3}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 + {3773F4CC-614E-4028-8595-22E08CA649E3}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {3773F4CC-614E-4028-8595-22E08CA649E3}.Devel-Clang|ARM64.Build.0 = Devel-Clang|ARM64 + {3773F4CC-614E-4028-8595-22E08CA649E3}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 + {3773F4CC-614E-4028-8595-22E08CA649E3}.Devel-Clang|x64.Build.0 = Devel-Clang|x64 {3773F4CC-614E-4028-8595-22E08CA649E3}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {3773F4CC-614E-4028-8595-22E08CA649E3}.Release|x64.ActiveCfg = Release|x64 {3773F4CC-614E-4028-8595-22E08CA649E3}.Release|x64.Build.0 = Release|x64 @@ -249,40 +309,6 @@ Global {3773F4CC-614E-4028-8595-22E08CA649E3}.ReleaseLTCG-Clang-SSE2|ARM64.ActiveCfg = ReleaseLTCG-Clang|ARM64 {3773F4CC-614E-4028-8595-22E08CA649E3}.ReleaseLTCG-Clang-SSE2|x64.ActiveCfg = ReleaseLTCG-Clang-SSE2|x64 {3773F4CC-614E-4028-8595-22E08CA649E3}.ReleaseLTCG-Clang-SSE2|x64.Build.0 = ReleaseLTCG-Clang-SSE2|x64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug|ARM64.ActiveCfg = Debug-Clang|ARM64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug|x64.ActiveCfg = Debug|x64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug|x64.Build.0 = Debug|x64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug-Clang|ARM64.ActiveCfg = Debug-Clang|ARM64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug-Clang|ARM64.Build.0 = Debug-Clang|ARM64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug-Clang|x64.ActiveCfg = Debug-Clang|x64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug-Clang|x64.Build.0 = Debug-Clang|x64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug-Clang-SSE2|ARM64.ActiveCfg = Debug-Clang|ARM64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug-Clang-SSE2|x64.ActiveCfg = Debug-Clang-SSE2|x64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug-Clang-SSE2|x64.Build.0 = Debug-Clang-SSE2|x64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.DebugFast|ARM64.ActiveCfg = DebugFast-Clang|ARM64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.DebugFast|x64.ActiveCfg = DebugFast|x64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.DebugFast|x64.Build.0 = DebugFast|x64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.DebugFast-Clang|ARM64.ActiveCfg = DebugFast-Clang|ARM64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.Release|x64.ActiveCfg = Release|x64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.Release|x64.Build.0 = Release|x64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.Release-Clang|ARM64.ActiveCfg = Release-Clang|ARM64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.Release-Clang|ARM64.Build.0 = Release-Clang|ARM64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.Release-Clang|x64.ActiveCfg = Release-Clang|x64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.Release-Clang|x64.Build.0 = Release-Clang|x64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG|ARM64.ActiveCfg = ReleaseLTCG-Clang|ARM64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG|x64.ActiveCfg = ReleaseLTCG|x64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG|x64.Build.0 = ReleaseLTCG|x64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG-Clang|ARM64.ActiveCfg = ReleaseLTCG-Clang|ARM64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG-Clang|ARM64.Build.0 = ReleaseLTCG-Clang|ARM64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG-Clang|x64.ActiveCfg = ReleaseLTCG-Clang|x64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG-Clang|x64.Build.0 = ReleaseLTCG-Clang|x64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG-Clang-SSE2|ARM64.ActiveCfg = ReleaseLTCG-Clang|ARM64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG-Clang-SSE2|x64.ActiveCfg = ReleaseLTCG-Clang-SSE2|x64 - {28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG-Clang-SSE2|x64.Build.0 = ReleaseLTCG-Clang-SSE2|x64 {72F9423C-91EE-4487-AAC6-555ED6F61AA1}.Debug|ARM64.ActiveCfg = Debug-Clang|ARM64 {72F9423C-91EE-4487-AAC6-555ED6F61AA1}.Debug|x64.ActiveCfg = Debug|x64 {72F9423C-91EE-4487-AAC6-555ED6F61AA1}.Debug|x64.Build.0 = Debug|x64 @@ -300,6 +326,10 @@ Global {72F9423C-91EE-4487-AAC6-555ED6F61AA1}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 {72F9423C-91EE-4487-AAC6-555ED6F61AA1}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 {72F9423C-91EE-4487-AAC6-555ED6F61AA1}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 + {72F9423C-91EE-4487-AAC6-555ED6F61AA1}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {72F9423C-91EE-4487-AAC6-555ED6F61AA1}.Devel-Clang|ARM64.Build.0 = Devel-Clang|ARM64 + {72F9423C-91EE-4487-AAC6-555ED6F61AA1}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 + {72F9423C-91EE-4487-AAC6-555ED6F61AA1}.Devel-Clang|x64.Build.0 = Devel-Clang|x64 {72F9423C-91EE-4487-AAC6-555ED6F61AA1}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {72F9423C-91EE-4487-AAC6-555ED6F61AA1}.Release|x64.ActiveCfg = Release|x64 {72F9423C-91EE-4487-AAC6-555ED6F61AA1}.Release|x64.Build.0 = Release|x64 @@ -334,6 +364,10 @@ Global {8BDA439C-6358-45FB-9994-2FF083BABE06}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 {8BDA439C-6358-45FB-9994-2FF083BABE06}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 {8BDA439C-6358-45FB-9994-2FF083BABE06}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 + {8BDA439C-6358-45FB-9994-2FF083BABE06}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {8BDA439C-6358-45FB-9994-2FF083BABE06}.Devel-Clang|ARM64.Build.0 = Devel-Clang|ARM64 + {8BDA439C-6358-45FB-9994-2FF083BABE06}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 + {8BDA439C-6358-45FB-9994-2FF083BABE06}.Devel-Clang|x64.Build.0 = Devel-Clang|x64 {8BDA439C-6358-45FB-9994-2FF083BABE06}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {8BDA439C-6358-45FB-9994-2FF083BABE06}.Release|x64.ActiveCfg = Release|x64 {8BDA439C-6358-45FB-9994-2FF083BABE06}.Release|x64.Build.0 = Release|x64 @@ -368,6 +402,10 @@ Global {425D6C99-D1C8-43C2-B8AC-4D7B1D941017}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 {425D6C99-D1C8-43C2-B8AC-4D7B1D941017}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 {425D6C99-D1C8-43C2-B8AC-4D7B1D941017}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 + {425D6C99-D1C8-43C2-B8AC-4D7B1D941017}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {425D6C99-D1C8-43C2-B8AC-4D7B1D941017}.Devel-Clang|ARM64.Build.0 = Devel-Clang|ARM64 + {425D6C99-D1C8-43C2-B8AC-4D7B1D941017}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 + {425D6C99-D1C8-43C2-B8AC-4D7B1D941017}.Devel-Clang|x64.Build.0 = Devel-Clang|x64 {425D6C99-D1C8-43C2-B8AC-4D7B1D941017}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {425D6C99-D1C8-43C2-B8AC-4D7B1D941017}.Release|x64.ActiveCfg = Release|x64 {425D6C99-D1C8-43C2-B8AC-4D7B1D941017}.Release|x64.Build.0 = Release|x64 @@ -402,6 +440,10 @@ Global {DD944834-7899-4C1C-A4C1-064B5009D239}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 {DD944834-7899-4C1C-A4C1-064B5009D239}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 {DD944834-7899-4C1C-A4C1-064B5009D239}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 + {DD944834-7899-4C1C-A4C1-064B5009D239}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {DD944834-7899-4C1C-A4C1-064B5009D239}.Devel-Clang|ARM64.Build.0 = Devel-Clang|ARM64 + {DD944834-7899-4C1C-A4C1-064B5009D239}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 + {DD944834-7899-4C1C-A4C1-064B5009D239}.Devel-Clang|x64.Build.0 = Devel-Clang|x64 {DD944834-7899-4C1C-A4C1-064B5009D239}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {DD944834-7899-4C1C-A4C1-064B5009D239}.Release|x64.ActiveCfg = Release|x64 {DD944834-7899-4C1C-A4C1-064B5009D239}.Release|x64.Build.0 = Release|x64 @@ -436,6 +478,10 @@ Global {09553C96-9F39-49BF-8AE6-7ACBD07C410C}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 {09553C96-9F39-49BF-8AE6-7ACBD07C410C}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 {09553C96-9F39-49BF-8AE6-7ACBD07C410C}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 + {09553C96-9F39-49BF-8AE6-7ACBD07C410C}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {09553C96-9F39-49BF-8AE6-7ACBD07C410C}.Devel-Clang|ARM64.Build.0 = Devel-Clang|ARM64 + {09553C96-9F39-49BF-8AE6-7ACBD07C410C}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 + {09553C96-9F39-49BF-8AE6-7ACBD07C410C}.Devel-Clang|x64.Build.0 = Devel-Clang|x64 {09553C96-9F39-49BF-8AE6-7ACBD07C410C}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {09553C96-9F39-49BF-8AE6-7ACBD07C410C}.Release|x64.ActiveCfg = Release|x64 {09553C96-9F39-49BF-8AE6-7ACBD07C410C}.Release|x64.Build.0 = Release|x64 @@ -470,6 +516,10 @@ Global {49953E1B-2EF7-46A4-B88B-1BF9E099093B}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 {49953E1B-2EF7-46A4-B88B-1BF9E099093B}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 {49953E1B-2EF7-46A4-B88B-1BF9E099093B}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 + {49953E1B-2EF7-46A4-B88B-1BF9E099093B}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {49953E1B-2EF7-46A4-B88B-1BF9E099093B}.Devel-Clang|ARM64.Build.0 = Devel-Clang|ARM64 + {49953E1B-2EF7-46A4-B88B-1BF9E099093B}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 + {49953E1B-2EF7-46A4-B88B-1BF9E099093B}.Devel-Clang|x64.Build.0 = Devel-Clang|x64 {49953E1B-2EF7-46A4-B88B-1BF9E099093B}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {49953E1B-2EF7-46A4-B88B-1BF9E099093B}.Release|x64.ActiveCfg = Release|x64 {49953E1B-2EF7-46A4-B88B-1BF9E099093B}.Release|x64.Build.0 = Release|x64 @@ -497,6 +547,8 @@ Global {EA2B9C7A-B8CC-42F9-879B-191A98680C10}.DebugFast|x64.ActiveCfg = DebugFast|x64 {EA2B9C7A-B8CC-42F9-879B-191A98680C10}.DebugFast-Clang|ARM64.ActiveCfg = DebugFast-Clang|ARM64 {EA2B9C7A-B8CC-42F9-879B-191A98680C10}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 + {EA2B9C7A-B8CC-42F9-879B-191A98680C10}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {EA2B9C7A-B8CC-42F9-879B-191A98680C10}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 {EA2B9C7A-B8CC-42F9-879B-191A98680C10}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {EA2B9C7A-B8CC-42F9-879B-191A98680C10}.Release|x64.ActiveCfg = Release|x64 {EA2B9C7A-B8CC-42F9-879B-191A98680C10}.Release-Clang|ARM64.ActiveCfg = Release-Clang|ARM64 @@ -524,6 +576,10 @@ Global {075CED82-6A20-46DF-94C7-9624AC9DDBEB}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 {075CED82-6A20-46DF-94C7-9624AC9DDBEB}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 {075CED82-6A20-46DF-94C7-9624AC9DDBEB}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 + {075CED82-6A20-46DF-94C7-9624AC9DDBEB}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {075CED82-6A20-46DF-94C7-9624AC9DDBEB}.Devel-Clang|ARM64.Build.0 = Devel-Clang|ARM64 + {075CED82-6A20-46DF-94C7-9624AC9DDBEB}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 + {075CED82-6A20-46DF-94C7-9624AC9DDBEB}.Devel-Clang|x64.Build.0 = Devel-Clang|x64 {075CED82-6A20-46DF-94C7-9624AC9DDBEB}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {075CED82-6A20-46DF-94C7-9624AC9DDBEB}.Release|x64.ActiveCfg = Release|x64 {075CED82-6A20-46DF-94C7-9624AC9DDBEB}.Release|x64.Build.0 = Release|x64 @@ -551,6 +607,8 @@ Global {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.DebugFast|x64.ActiveCfg = DebugFast|x64 {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.DebugFast-Clang|ARM64.ActiveCfg = DebugFast-Clang|ARM64 {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 + {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.Release|x64.ActiveCfg = Release|x64 {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.Release-Clang|ARM64.ActiveCfg = Release-Clang|ARM64 @@ -576,6 +634,9 @@ Global {8906836E-F06E-46E8-B11A-74E5E8C7B8FB}.DebugFast-Clang|ARM64.ActiveCfg = DebugFast-Clang|ARM64 {8906836E-F06E-46E8-B11A-74E5E8C7B8FB}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 {8906836E-F06E-46E8-B11A-74E5E8C7B8FB}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 + {8906836E-F06E-46E8-B11A-74E5E8C7B8FB}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {8906836E-F06E-46E8-B11A-74E5E8C7B8FB}.Devel-Clang|ARM64.Build.0 = Devel-Clang|ARM64 + {8906836E-F06E-46E8-B11A-74E5E8C7B8FB}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 {8906836E-F06E-46E8-B11A-74E5E8C7B8FB}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {8906836E-F06E-46E8-B11A-74E5E8C7B8FB}.Release|x64.ActiveCfg = Release|x64 {8906836E-F06E-46E8-B11A-74E5E8C7B8FB}.Release-Clang|ARM64.ActiveCfg = Release-Clang|ARM64 @@ -605,6 +666,10 @@ Global {4BA0A6D4-3AE1-42B2-9347-096FD023FF64}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 {4BA0A6D4-3AE1-42B2-9347-096FD023FF64}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 {4BA0A6D4-3AE1-42B2-9347-096FD023FF64}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 + {4BA0A6D4-3AE1-42B2-9347-096FD023FF64}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {4BA0A6D4-3AE1-42B2-9347-096FD023FF64}.Devel-Clang|ARM64.Build.0 = Devel-Clang|ARM64 + {4BA0A6D4-3AE1-42B2-9347-096FD023FF64}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 + {4BA0A6D4-3AE1-42B2-9347-096FD023FF64}.Devel-Clang|x64.Build.0 = Devel-Clang|x64 {4BA0A6D4-3AE1-42B2-9347-096FD023FF64}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {4BA0A6D4-3AE1-42B2-9347-096FD023FF64}.Release|x64.ActiveCfg = Release|x64 {4BA0A6D4-3AE1-42B2-9347-096FD023FF64}.Release|x64.Build.0 = Release|x64 @@ -633,6 +698,8 @@ Global {3029310E-4211-4C87-801A-72E130A648EF}.DebugFast-Clang|ARM64.ActiveCfg = DebugFast-Clang|ARM64 {3029310E-4211-4C87-801A-72E130A648EF}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 {3029310E-4211-4C87-801A-72E130A648EF}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 + {3029310E-4211-4C87-801A-72E130A648EF}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {3029310E-4211-4C87-801A-72E130A648EF}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 {3029310E-4211-4C87-801A-72E130A648EF}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {3029310E-4211-4C87-801A-72E130A648EF}.Release|x64.ActiveCfg = Release|x64 {3029310E-4211-4C87-801A-72E130A648EF}.Release-Clang|ARM64.ActiveCfg = Release-Clang|ARM64 @@ -661,6 +728,10 @@ Global {E4357877-D459-45C7-B8F6-DCBB587BB528}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 {E4357877-D459-45C7-B8F6-DCBB587BB528}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 {E4357877-D459-45C7-B8F6-DCBB587BB528}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 + {E4357877-D459-45C7-B8F6-DCBB587BB528}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {E4357877-D459-45C7-B8F6-DCBB587BB528}.Devel-Clang|ARM64.Build.0 = Devel-Clang|ARM64 + {E4357877-D459-45C7-B8F6-DCBB587BB528}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 + {E4357877-D459-45C7-B8F6-DCBB587BB528}.Devel-Clang|x64.Build.0 = Devel-Clang|x64 {E4357877-D459-45C7-B8F6-DCBB587BB528}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {E4357877-D459-45C7-B8F6-DCBB587BB528}.Release|x64.ActiveCfg = Release|x64 {E4357877-D459-45C7-B8F6-DCBB587BB528}.Release|x64.Build.0 = Release|x64 @@ -695,6 +766,10 @@ Global {8BE398E6-B882-4248-9065-FECC8728E038}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 {8BE398E6-B882-4248-9065-FECC8728E038}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 {8BE398E6-B882-4248-9065-FECC8728E038}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 + {8BE398E6-B882-4248-9065-FECC8728E038}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {8BE398E6-B882-4248-9065-FECC8728E038}.Devel-Clang|ARM64.Build.0 = Devel-Clang|ARM64 + {8BE398E6-B882-4248-9065-FECC8728E038}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 + {8BE398E6-B882-4248-9065-FECC8728E038}.Devel-Clang|x64.Build.0 = Devel-Clang|x64 {8BE398E6-B882-4248-9065-FECC8728E038}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {8BE398E6-B882-4248-9065-FECC8728E038}.Release|x64.ActiveCfg = Release|x64 {8BE398E6-B882-4248-9065-FECC8728E038}.Release|x64.Build.0 = Release|x64 @@ -729,6 +804,10 @@ Global {57F6206D-F264-4B07-BAF8-11B9BBE1F455}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 {57F6206D-F264-4B07-BAF8-11B9BBE1F455}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 {57F6206D-F264-4B07-BAF8-11B9BBE1F455}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 + {57F6206D-F264-4B07-BAF8-11B9BBE1F455}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {57F6206D-F264-4B07-BAF8-11B9BBE1F455}.Devel-Clang|ARM64.Build.0 = Devel-Clang|ARM64 + {57F6206D-F264-4B07-BAF8-11B9BBE1F455}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 + {57F6206D-F264-4B07-BAF8-11B9BBE1F455}.Devel-Clang|x64.Build.0 = Devel-Clang|x64 {57F6206D-F264-4B07-BAF8-11B9BBE1F455}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {57F6206D-F264-4B07-BAF8-11B9BBE1F455}.Release|x64.ActiveCfg = Release|x64 {57F6206D-F264-4B07-BAF8-11B9BBE1F455}.Release|x64.Build.0 = Release|x64 @@ -761,6 +840,9 @@ Global {C51A346A-86B2-46DF-9BB3-D0AA7E5D8699}.DebugFast-Clang|ARM64.ActiveCfg = DebugFast-Clang|ARM64 {C51A346A-86B2-46DF-9BB3-D0AA7E5D8699}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 {C51A346A-86B2-46DF-9BB3-D0AA7E5D8699}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 + {C51A346A-86B2-46DF-9BB3-D0AA7E5D8699}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {C51A346A-86B2-46DF-9BB3-D0AA7E5D8699}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 + {C51A346A-86B2-46DF-9BB3-D0AA7E5D8699}.Devel-Clang|x64.Build.0 = Devel-Clang|x64 {C51A346A-86B2-46DF-9BB3-D0AA7E5D8699}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {C51A346A-86B2-46DF-9BB3-D0AA7E5D8699}.Release|x64.ActiveCfg = Release|x64 {C51A346A-86B2-46DF-9BB3-D0AA7E5D8699}.Release-Clang|ARM64.ActiveCfg = Release-Clang|ARM64 @@ -788,6 +870,10 @@ Global {F351C4D8-594A-4850-B77B-3C1249812CCE}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 {F351C4D8-594A-4850-B77B-3C1249812CCE}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 {F351C4D8-594A-4850-B77B-3C1249812CCE}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 + {F351C4D8-594A-4850-B77B-3C1249812CCE}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {F351C4D8-594A-4850-B77B-3C1249812CCE}.Devel-Clang|ARM64.Build.0 = Devel-Clang|ARM64 + {F351C4D8-594A-4850-B77B-3C1249812CCE}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 + {F351C4D8-594A-4850-B77B-3C1249812CCE}.Devel-Clang|x64.Build.0 = Devel-Clang|x64 {F351C4D8-594A-4850-B77B-3C1249812CCE}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {F351C4D8-594A-4850-B77B-3C1249812CCE}.Release|x64.ActiveCfg = Release|x64 {F351C4D8-594A-4850-B77B-3C1249812CCE}.Release|x64.Build.0 = Release|x64 @@ -822,6 +908,10 @@ Global {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.Devel-Clang|ARM64.Build.0 = Devel-Clang|ARM64 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.Devel-Clang|x64.Build.0 = Devel-Clang|x64 {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.Release|x64.ActiveCfg = Release|x64 {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.Release|x64.Build.0 = Release|x64 @@ -856,6 +946,10 @@ Global {1AD23A8A-4C20-434C-AE6B-0E07759EEB1E}.DebugFast-Clang|ARM64.Build.0 = DebugFast-Clang|ARM64 {1AD23A8A-4C20-434C-AE6B-0E07759EEB1E}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64 {1AD23A8A-4C20-434C-AE6B-0E07759EEB1E}.DebugFast-Clang|x64.Build.0 = DebugFast-Clang|x64 + {1AD23A8A-4C20-434C-AE6B-0E07759EEB1E}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64 + {1AD23A8A-4C20-434C-AE6B-0E07759EEB1E}.Devel-Clang|ARM64.Build.0 = Devel-Clang|ARM64 + {1AD23A8A-4C20-434C-AE6B-0E07759EEB1E}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64 + {1AD23A8A-4C20-434C-AE6B-0E07759EEB1E}.Devel-Clang|x64.Build.0 = Devel-Clang|x64 {1AD23A8A-4C20-434C-AE6B-0E07759EEB1E}.Release|ARM64.ActiveCfg = Release-Clang|ARM64 {1AD23A8A-4C20-434C-AE6B-0E07759EEB1E}.Release|x64.ActiveCfg = Release|x64 {1AD23A8A-4C20-434C-AE6B-0E07759EEB1E}.Release|x64.Build.0 = Release|x64 diff --git a/src/common/assert.h b/src/common/assert.h index 502f02686..fd7d4d884 100644 --- a/src/common/assert.h +++ b/src/common/assert.h @@ -19,7 +19,7 @@ void Y_OnAssertFailed(const char* szMessage, const char* szFunction, const char* Y_OnAssertFailed("Assertion failed: '" msg "'", __FUNCTION__, __FILE__, __LINE__); \ } -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) #define DebugAssert(expr) \ if (!(expr)) \ { \ @@ -41,7 +41,7 @@ void Y_OnAssertFailed(const char* szMessage, const char* szFunction, const char* // Kills the application, indicating a pure function call that should not have happened. #define PureCall() Y_OnPanicReached("PureCall encountered", __FUNCTION__, __FILE__, __LINE__) -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) // Kills the application, indicating that code that was never supposed to be reached has been executed. #define UnreachableCode() Y_OnPanicReached("Unreachable code reached", __FUNCTION__, __FILE__, __LINE__) #else diff --git a/src/common/log.h b/src/common/log.h index 0f0380644..2723422a8 100644 --- a/src/common/log.h +++ b/src/common/log.h @@ -195,7 +195,7 @@ ALWAYS_INLINE static void FastWrite(Channel channel, const char* functionName, L #define VERBOSE_LOG(...) Log::FastWrite(___LogChannel___, Log::Level::Verbose, __VA_ARGS__) #define DEV_LOG(...) Log::FastWrite(___LogChannel___, Log::Level::Dev, __VA_ARGS__) -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) #define DEBUG_LOG(...) Log::FastWrite(___LogChannel___, Log::Level::Debug, __VA_ARGS__) #define TRACE_LOG(...) Log::FastWrite(___LogChannel___, Log::Level::Trace, __VA_ARGS__) #else @@ -216,7 +216,7 @@ ALWAYS_INLINE static void FastWrite(Channel channel, const char* functionName, L #define VERBOSE_COLOR_LOG(colour, ...) Log::FastWrite(___LogChannel___, Log::Level::Verbose, Log::Color::colour, __VA_ARGS__) #define DEV_COLOR_LOG(colour, ...) Log::FastWrite(___LogChannel___, Log::Level::Dev, Log::Color::colour, __VA_ARGS__) -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) #define DEBUG_COLOR_LOG(colour, ...) Log::FastWrite(___LogChannel___, Log::Level::Debug, Log::Color::colour, __VA_ARGS__) #define TRACE_COLOR_LOG(colour, ...) Log::FastWrite(___LogChannel___, Log::Level::Trace, Log::Color::colour,__VA_ARGS__) #else diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 1b4d025ce..a3245020b 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -147,7 +147,7 @@ if(CPU_ARCH_X64) cpu_recompiler_x64.h ) target_link_libraries(core PRIVATE xbyak) - if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + if(CMAKE_BUILD_TYPE MATCHES "Debug|Devel") target_link_libraries(core PRIVATE zydis) endif() message(STATUS "Building x64 recompiler.") diff --git a/src/core/achievements.cpp b/src/core/achievements.cpp index a214d4f37..7cf9f1fd0 100644 --- a/src/core/achievements.cpp +++ b/src/core/achievements.cpp @@ -624,7 +624,7 @@ bool Achievements::CreateClient(rc_client_t** client, std::unique_ptr {09553c96-9f39-49bf-8ae6-7acbd07c410c} - + {c51a346a-86b2-46df-9bb3-d0aa7e5d8699} @@ -200,8 +200,8 @@ - ZYDIS_DISABLE_ENCODER;ZYDIS_DISABLE_AVX512;ZYDIS_DISABLE_KNC;ZYDIS_STATIC_BUILD;ZYCORE_STATIC_BUILD;%(PreprocessorDefinitions) - %(AdditionalIncludeDirectories);$(SolutionDir)dep\zydis\include;$(SolutionDir)dep\zydis\dependencies\zycore\include + ZYDIS_DISABLE_ENCODER;ZYDIS_DISABLE_AVX512;ZYDIS_DISABLE_KNC;ZYDIS_STATIC_BUILD;ZYCORE_STATIC_BUILD;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories);$(SolutionDir)dep\zydis\include;$(SolutionDir)dep\zydis\dependencies\zycore\include $(IntDir)/%(RelativeDir)/ Use pch.h diff --git a/src/core/cpu_code_cache.cpp b/src/core/cpu_code_cache.cpp index fe968203b..bc8384352 100644 --- a/src/core/cpu_code_cache.cpp +++ b/src/core/cpu_code_cache.cpp @@ -154,7 +154,7 @@ static u8* s_free_far_code_ptr = nullptr; static u32 s_far_code_size = 0; static u32 s_far_code_used = 0; -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) static u32 s_total_instructions_compiled = 0; static u32 s_total_host_instructions_emitted = 0; #endif @@ -1023,7 +1023,7 @@ bool CPU::CodeCache::ReadBlockInstructions(u32 start_pc, BlockInstructionList* i instructions->back().second.is_last_instruction = true; -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) SmallString disasm; DEBUG_LOG("Block at 0x{:08X}", start_pc); DEBUG_LOG(" Uncached fetch ticks: {}", metadata->uncached_fetch_ticks); @@ -1540,7 +1540,7 @@ void CPU::CodeCache::CompileASMFunctions() { MemMap::BeginCodeWrite(); -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) s_total_instructions_compiled = 0; s_total_host_instructions_emitted = 0; #endif diff --git a/src/core/cpu_code_cache_private.h b/src/core/cpu_code_cache_private.h index 5b1db603b..2981ec418 100644 --- a/src/core/cpu_code_cache_private.h +++ b/src/core/cpu_code_cache_private.h @@ -215,7 +215,7 @@ void InterpretUncachedBlock(); void LogCurrentState(); -#if defined(_DEBUG) || false +#if defined(_DEBUG) || defined(_DEVEL) || false // Enable disassembly of host assembly code. #define ENABLE_HOST_DISASSEMBLY 1 #endif diff --git a/src/core/cpu_core.cpp b/src/core/cpu_core.cpp index fbbf39875..6443afaf9 100644 --- a/src/core/cpu_core.cpp +++ b/src/core/cpu_core.cpp @@ -335,7 +335,7 @@ ALWAYS_INLINE_RELEASE void CPU::RaiseException(u32 CAUSE_bits, u32 EPC, u32 vect g_state.cop0_regs.cause.bits = (g_state.cop0_regs.cause.bits & ~Cop0Registers::CAUSE::EXCEPTION_WRITE_MASK) | (CAUSE_bits & Cop0Registers::CAUSE::EXCEPTION_WRITE_MASK); -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (g_state.cop0_regs.cause.Excode != Exception::INT && g_state.cop0_regs.cause.Excode != Exception::Syscall && g_state.cop0_regs.cause.Excode != Exception::BP) { diff --git a/src/core/cpu_recompiler.cpp b/src/core/cpu_recompiler.cpp index 597d7e293..0e11dbab3 100644 --- a/src/core/cpu_recompiler.cpp +++ b/src/core/cpu_recompiler.cpp @@ -354,7 +354,7 @@ bool CPU::Recompiler::Recompiler::TrySwapDelaySlot(Reg rs, Reg rt, Reg rd) const Reg opcode_rt = next_instruction->r.rt; const Reg opcode_rd = next_instruction->r.rd; -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) TinyString disasm; DisassembleInstruction(&disasm, m_current_instruction_pc + 4, next_instruction->bits); #endif @@ -492,7 +492,7 @@ bool CPU::Recompiler::Recompiler::TrySwapDelaySlot(Reg rs, Reg rt, Reg rd) } is_safe: -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) DEBUG_LOG("Swapping delay slot {:08X} {}", m_current_instruction_pc + 4, disasm); #endif @@ -504,7 +504,7 @@ is_safe: return true; is_unsafe: -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) DEBUG_LOG("NOT swapping delay slot {:08X} {}", m_current_instruction_pc + 4, disasm); #endif @@ -1178,7 +1178,7 @@ void CPU::Recompiler::Recompiler::AddLoadStoreInfo(void* code_address, u32 code_ void CPU::Recompiler::Recompiler::CompileInstruction() { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) TinyString str; DisassembleInstruction(&str, m_current_instruction_pc, inst->bits); DEBUG_LOG("Compiling{} {:08X}: {}", m_current_instruction_branch_delay_slot ? " branch delay slot" : "", diff --git a/src/core/dma.cpp b/src/core/dma.cpp index ab0889788..d980df4c9 100644 --- a/src/core/dma.cpp +++ b/src/core/dma.cpp @@ -772,7 +772,7 @@ template TickCount DMA::TransferMemoryToDevice(u32 address, u32 increment, u32 word_count) { const u32 mask = Bus::g_ram_mask; -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if ((address & mask) != address) DEBUG_LOG("DMA TO {} from masked RAM address 0x{:08X} => 0x{:08X}", channel, address, (address & mask)); #endif @@ -862,7 +862,7 @@ template TickCount DMA::TransferDeviceToMemory(u32 address, u32 increment, u32 word_count) { const u32 mask = Bus::g_ram_mask; -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if ((address & mask) != address) DEBUG_LOG("DMA FROM {} to masked RAM address 0x{:08X} => 0x{:08X}", channel, address, (address & mask)); #endif diff --git a/src/core/gpu_dump.cpp b/src/core/gpu_dump.cpp index 216b232f5..5b32be867 100644 --- a/src/core/gpu_dump.cpp +++ b/src/core/gpu_dump.cpp @@ -507,7 +507,7 @@ bool GPUDump::Player::FindFrameStarts(Error* error) return false; } -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) for (size_t i = 0; i < m_frame_offsets.size(); i++) DEBUG_LOG("Frame {} starts at offset {}", i, m_frame_offsets[i]); #endif diff --git a/src/core/gpu_hw.cpp b/src/core/gpu_hw.cpp index 7abdf31f4..2707a197a 100644 --- a/src/core/gpu_hw.cpp +++ b/src/core/gpu_hw.cpp @@ -42,7 +42,7 @@ static constexpr GPUTexture::Format VRAM_DS_FORMAT = GPUTexture::Format::D16; static constexpr GPUTexture::Format VRAM_DS_DEPTH_FORMAT = GPUTexture::Format::D32F; static constexpr GPUTexture::Format VRAM_DS_COLOR_FORMAT = GPUTexture::Format::R32F; -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) static u32 s_draw_number = 0; @@ -189,7 +189,7 @@ private: GPU_HW::GPU_HW() : GPU() { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) s_draw_number = 0; #endif } @@ -3785,7 +3785,7 @@ void GPU_HW::FlushRender() if (index_count == 0) return; -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) GL_SCOPE_FMT("Hardware Draw {}: {}", ++s_draw_number, m_current_draw_rect); #endif diff --git a/src/core/gpu_hw_texture_cache.cpp b/src/core/gpu_hw_texture_cache.cpp index b37cad53b..43ab7d4d1 100644 --- a/src/core/gpu_hw_texture_cache.cpp +++ b/src/core/gpu_hw_texture_cache.cpp @@ -1413,7 +1413,7 @@ const GPUTextureCache::Source* GPUTextureCache::LookupSource(SourceKey key, cons const GPUTextureCache::Source* GPUTextureCache::ReturnSource(Source* source, const GSVector4i uv_rect, PaletteRecordFlags flags) { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) // GL_INS_FMT("Tex hash: {:016X}", source->texture_hash); // GL_INS_FMT("Palette hash: {:016X}", source->palette_hash); if (!uv_rect.eq(INVALID_RECT)) @@ -1471,7 +1471,7 @@ bool GPUTextureCache::IsRectDrawn(const GSVector4i rect) bool GPUTextureCache::AreSourcePagesDrawn(SourceKey key, const GSVector4i rect) { // NOTE: This doesn't handle VRAM wrapping. But neither does the caller. YOLO? -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) { for (u32 offset = 0; offset < TexturePageCountForMode(key.mode); offset++) { @@ -1529,7 +1529,7 @@ void GPUTextureCache::Invalidate() } // should all be null -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) for (u32 i = 0; i < NUM_VRAM_PAGES; i++) DebugAssert(!s_state.pages[i].sources.head && !s_state.pages[i].sources.tail); DebugAssert(!s_state.last_vram_write); diff --git a/src/core/guncon.cpp b/src/core/guncon.cpp index fa88879db..d58ec1054 100644 --- a/src/core/guncon.cpp +++ b/src/core/guncon.cpp @@ -18,7 +18,7 @@ #include -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) #include "common/log.h" LOG_CHANNEL(GunCon); #endif diff --git a/src/core/interrupt_controller.cpp b/src/core/interrupt_controller.cpp index 592497e4f..8f1a58bb9 100644 --- a/src/core/interrupt_controller.cpp +++ b/src/core/interrupt_controller.cpp @@ -51,7 +51,7 @@ void InterruptController::SetLineState(IRQ irq, bool state) if (s_interrupt_line_state == prev_state) return; -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (!(prev_state & bit) && state) DEBUG_LOG("{} IRQ triggered", s_irq_names[static_cast(irq)]); else if ((prev_state & bit) && !state) @@ -84,7 +84,7 @@ void InterruptController::WriteRegister(u32 offset, u32 value) { case 0x00: // I_STATUS { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) const u32 cleared_bits = (s_interrupt_status_register & ~value); for (u32 i = 0; i < static_cast(IRQ::MaxCount); i++) { diff --git a/src/core/memory_card.cpp b/src/core/memory_card.cpp index f7062fde8..db8c52960 100644 --- a/src/core/memory_card.cpp +++ b/src/core/memory_card.cpp @@ -88,7 +88,7 @@ void MemoryCard::ResetTransferState() bool MemoryCard::Transfer(const u8 data_in, u8* data_out) { bool ack = false; -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) const State old_state = m_state; #endif diff --git a/src/core/timing_event.cpp b/src/core/timing_event.cpp index 41101d95d..94aad8581 100644 --- a/src/core/timing_event.cpp +++ b/src/core/timing_event.cpp @@ -387,7 +387,7 @@ void TimingEvents::RunEvents() void TimingEvents::CommitLeftoverTicks() { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (s_state.event_run_tick_counter > s_state.global_tick_counter) DEV_LOG("Late-running {} ticks before execution", s_state.event_run_tick_counter - s_state.global_tick_counter); #endif diff --git a/src/duckstation-qt/CMakeLists.txt b/src/duckstation-qt/CMakeLists.txt index d1dde8b0c..a1e1edfac 100644 --- a/src/duckstation-qt/CMakeLists.txt +++ b/src/duckstation-qt/CMakeLists.txt @@ -219,7 +219,7 @@ if(WIN32) add_custom_command(TARGET duckstation-qt POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DEP_BINS} "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" ) - if(CMAKE_BUILD_TYPE MATCHES "Debug") + if(CMAKE_BUILD_TYPE MATCHES "Debug|Devel") get_property(WINPIXEVENTRUNTIME_DLL TARGET WinPixEventRuntime::WinPixEventRuntime PROPERTY IMPORTED_LOCATION) message(STATUS WP "${WINPIXEVENTRUNTIME_DLL}") add_custom_command(TARGET duckstation-qt POST_BUILD diff --git a/src/duckstation-qt/autoupdaterdialog.cpp b/src/duckstation-qt/autoupdaterdialog.cpp index 140742cd0..dc53d891f 100644 --- a/src/duckstation-qt/autoupdaterdialog.cpp +++ b/src/duckstation-qt/autoupdaterdialog.cpp @@ -135,7 +135,7 @@ bool AutoUpdaterDialog::warnAboutUnofficialBuild() // Thanks, and I hope you understand. // -#if !__has_include("scmversion/tag.h") && !defined(_DEBUG) +#if !__has_include("scmversion/tag.h") constexpr const char* CONFIG_SECTION = "UI"; constexpr const char* CONFIG_KEY = "UnofficialBuildWarningConfirmed"; if ( diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 90156681b..b9c183098 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -197,7 +197,9 @@ QString QtHost::GetAppNameAndVersion() QString QtHost::GetAppConfigSuffix() { -#if defined(_DEBUGFAST) +#if defined(_DEVEL) + return QStringLiteral(" [Devel]"); +#elif defined(_DEBUGFAST) return QStringLiteral(" [DebugFast]"); #elif defined(_DEBUG) return QStringLiteral(" [Debug]"); diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 68fa264fa..e3cefb020 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -233,7 +233,7 @@ if(WIN32) target_link_libraries(util PRIVATE d3d12ma) target_link_libraries(util PRIVATE d3d11.lib d3d12.lib d3dcompiler.lib dxgi.lib winmm.lib Dwmapi.lib winhttp.lib) - if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + if(CMAKE_BUILD_TYPE MATCHES "Debug|Devel") target_link_libraries(util PRIVATE WinPixEventRuntime::WinPixEventRuntime) endif() elseif(APPLE) diff --git a/src/util/cd_image_pbp.cpp b/src/util/cd_image_pbp.cpp index 050393ac0..d011bc52b 100644 --- a/src/util/cd_image_pbp.cpp +++ b/src/util/cd_image_pbp.cpp @@ -152,7 +152,7 @@ private: u16 size; }; -#if _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) static void PrintPBPHeaderInfo(const PBPHeader& pbp_header); static void PrintSFOHeaderInfo(const SFOHeader& sfo_header); static void PrintSFOIndexTableEntry(const SFOIndexTableEntry& sfo_index_table_entry, size_t i); @@ -231,7 +231,7 @@ bool CDImagePBP::LoadPBPHeader() return false; } -#if _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) PrintPBPHeaderInfo(m_pbp_header); #endif @@ -252,7 +252,7 @@ bool CDImagePBP::LoadSFOHeader() return false; } -#if _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) PrintSFOHeaderInfo(m_sfo_header); #endif @@ -273,7 +273,7 @@ bool CDImagePBP::LoadSFOIndexTable() return false; } -#if _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) for (size_t i = 0; i < static_cast(m_sfo_header.num_table_entries); ++i) PrintSFOIndexTableEntry(m_sfo_index_table[i], i); #endif @@ -346,7 +346,7 @@ bool CDImagePBP::LoadSFOTable() } } -#if _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) PrintSFOTable(m_sfo_table); #endif @@ -822,7 +822,7 @@ bool CDImagePBP::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_i return true; } -#if _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) void CDImagePBP::PrintPBPHeaderInfo(const PBPHeader& pbp_header) { printf("PBP header info\n"); diff --git a/src/util/d3d11_device.cpp b/src/util/d3d11_device.cpp index b5ea7c7a3..db619df30 100644 --- a/src/util/d3d11_device.cpp +++ b/src/util/d3d11_device.cpp @@ -32,7 +32,7 @@ static constexpr GPUTexture::Format s_swap_chain_format = GPUTexture::Format::RG void SetD3DDebugObjectName(ID3D11DeviceChild* obj, std::string_view name) { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) // WKPDID_D3DDebugObjectName static constexpr GUID guid = {0x429b8c22, 0x9188, 0x4b0c, {0x87, 0x42, 0xac, 0xb0, 0xbf, 0x85, 0xc2, 0x00}}; @@ -115,7 +115,7 @@ bool D3D11Device::CreateDeviceAndMainSwapChain(std::string_view adapter, Feature } } -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (m_debug_device) m_context.As(&m_annotation); #endif @@ -834,7 +834,7 @@ float D3D11Device::GetAndResetAccumulatedGPUTime() void D3D11Device::PushDebugGroup(const char* name) { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (!m_annotation) return; @@ -844,7 +844,7 @@ void D3D11Device::PushDebugGroup(const char* name) void D3D11Device::PopDebugGroup() { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (!m_annotation) return; @@ -854,7 +854,7 @@ void D3D11Device::PopDebugGroup() void D3D11Device::InsertDebugMessage(const char* msg) { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (!m_annotation) return; diff --git a/src/util/d3d12_builders.cpp b/src/util/d3d12_builders.cpp index 230641c3f..7eaf69bb6 100644 --- a/src/util/d3d12_builders.cpp +++ b/src/util/d3d12_builders.cpp @@ -310,7 +310,7 @@ u32 D3D12::RootSignatureBuilder::AddDescriptorTable(D3D12_DESCRIPTOR_RANGE_TYPE return index; } -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) void D3D12::SetObjectName(ID3D12Object* object, std::string_view name) { diff --git a/src/util/d3d12_builders.h b/src/util/d3d12_builders.h index bf5e3531f..81dc57f7a 100644 --- a/src/util/d3d12_builders.h +++ b/src/util/d3d12_builders.h @@ -122,7 +122,7 @@ private: D3D12_COMPUTE_PIPELINE_STATE_DESC m_desc; }; -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) void SetObjectName(ID3D12Object* object, std::string_view name); #else static inline void SetObjectName(ID3D12Object* object, std::string_view name) diff --git a/src/util/d3d12_descriptor_heap_manager.cpp b/src/util/d3d12_descriptor_heap_manager.cpp index 6ea24ece5..24e860222 100644 --- a/src/util/d3d12_descriptor_heap_manager.cpp +++ b/src/util/d3d12_descriptor_heap_manager.cpp @@ -42,7 +42,7 @@ bool D3D12DescriptorHeapManager::Create(ID3D12Device* device, D3D12_DESCRIPTOR_H void D3D12DescriptorHeapManager::Destroy() { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) for (BitSetType& bs : m_free_slots) { DebugAssert(bs.all()); diff --git a/src/util/d3d12_device.cpp b/src/util/d3d12_device.cpp index 998882eab..8e0386053 100644 --- a/src/util/d3d12_device.cpp +++ b/src/util/d3d12_device.cpp @@ -59,7 +59,7 @@ static constexpr GPUTexture::Format s_swap_chain_format = GPUTexture::Format::RG // We just need to keep this alive, never reference it. static DynamicHeapArray s_pipeline_cache_data; -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) #include "WinPixEventRuntime/pix3.h" static u32 s_debug_scope_depth = 0; #endif @@ -117,7 +117,7 @@ D3D12Device::D3D12Device() { m_render_api = RenderAPI::D3D12; -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) s_debug_scope_depth = 0; #endif } @@ -1273,7 +1273,7 @@ void D3D12Device::SubmitPresent(GPUSwapChain* swap_chain) SC->GetSwapChain()->Present(sync_interval, flags); } -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) static UINT64 Palette(float phase, const std::array& a, const std::array& b, const std::array& c, const std::array& d) { @@ -1290,7 +1290,7 @@ static UINT64 Palette(float phase, const std::array& a, const std::arr void D3D12Device::PushDebugGroup(const char* name) { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (!m_debug_device) return; @@ -1302,7 +1302,7 @@ void D3D12Device::PushDebugGroup(const char* name) void D3D12Device::PopDebugGroup() { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (!m_debug_device) return; @@ -1313,7 +1313,7 @@ void D3D12Device::PopDebugGroup() void D3D12Device::InsertDebugMessage(const char* msg) { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (!m_debug_device) return; diff --git a/src/util/gpu_device.h b/src/util/gpu_device.h index cd66311a8..071fdcd72 100644 --- a/src/util/gpu_device.h +++ b/src/util/gpu_device.h @@ -929,7 +929,7 @@ ALWAYS_INLINE void GPUDevice::PooledTextureDeleter::operator()(GPUTexture* const } // Macros for debug messages. -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) struct GLAutoPop { GLAutoPop(int dummy) {} diff --git a/src/util/imgui_fullscreen.cpp b/src/util/imgui_fullscreen.cpp index 90a24a127..3ff62dbb5 100644 --- a/src/util/imgui_fullscreen.cpp +++ b/src/util/imgui_fullscreen.cpp @@ -2792,7 +2792,7 @@ void ImGuiFullscreen::OpenBackgroundProgressDialog(const char* str_id, std::stri std::unique_lock lock(s_background_progress_lock); -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) for (const BackgroundProgressDialogData& data : s_background_progress_dialogs) { DebugAssert(data.id != id); diff --git a/src/util/media_capture.cpp b/src/util/media_capture.cpp index 01541401e..d356fea0b 100644 --- a/src/util/media_capture.cpp +++ b/src/util/media_capture.cpp @@ -285,8 +285,8 @@ bool MediaCaptureBase::DeliverVideoFrame(GPUTexture* stex) return false; } -#ifdef _DEBUG - GL_OBJECT_NAME_FMT(pf.tex, "GSCapture {}x{} Download Texture", stex->GetWidth(), stex->GetHeight()); +#if defined(_DEBUG) || defined(_DEVEL) + GL_OBJECT_NAME_FMT(pf.tex, "MediaCapture {}x{} Download Texture", stex->GetWidth(), stex->GetHeight()); #endif } diff --git a/src/util/opengl_device.cpp b/src/util/opengl_device.cpp index 79a1474d5..0e0f4e39f 100644 --- a/src/util/opengl_device.cpp +++ b/src/util/opengl_device.cpp @@ -216,7 +216,7 @@ std::unique_ptr OpenGLDevice::CreatePipeline(const GPUPipeline::Com void OpenGLDevice::PushDebugGroup(const char* name) { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (!glPushDebugGroup) return; @@ -226,7 +226,7 @@ void OpenGLDevice::PushDebugGroup(const char* name) void OpenGLDevice::PopDebugGroup() { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (!glPopDebugGroup) return; @@ -236,7 +236,7 @@ void OpenGLDevice::PopDebugGroup() void OpenGLDevice::InsertDebugMessage(const char* msg) { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (!glDebugMessageInsert) return; diff --git a/src/util/opengl_pipeline.cpp b/src/util/opengl_pipeline.cpp index b112c70d6..b0027d452 100644 --- a/src/util/opengl_pipeline.cpp +++ b/src/util/opengl_pipeline.cpp @@ -80,7 +80,7 @@ OpenGLShader::~OpenGLShader() void OpenGLShader::SetDebugName(std::string_view name) { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (glObjectLabel) { if (m_id.has_value()) @@ -155,7 +155,7 @@ bool OpenGLShader::Compile(Error* error) m_id = shader; -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (glObjectLabel && !m_debug_name.empty()) { glObjectLabel(GL_SHADER, shader, static_cast(m_debug_name.length()), @@ -586,7 +586,7 @@ OpenGLPipeline::~OpenGLPipeline() void OpenGLPipeline::SetDebugName(std::string_view name) { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (glObjectLabel) glObjectLabel(GL_PROGRAM, m_program, static_cast(name.length()), name.data()); #endif diff --git a/src/util/opengl_pipeline.h b/src/util/opengl_pipeline.h index 39a53bd98..33a088e69 100644 --- a/src/util/opengl_pipeline.h +++ b/src/util/opengl_pipeline.h @@ -32,7 +32,7 @@ private: std::optional m_id; bool m_compile_tried = false; -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) std::string m_debug_name; #endif }; diff --git a/src/util/opengl_stream_buffer.cpp b/src/util/opengl_stream_buffer.cpp index 212a17187..a4deff5fb 100644 --- a/src/util/opengl_stream_buffer.cpp +++ b/src/util/opengl_stream_buffer.cpp @@ -31,7 +31,7 @@ void OpenGLStreamBuffer::Unbind() void OpenGLStreamBuffer::SetDebugName(std::string_view name) { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (glObjectLabel) { glObjectLabel(GL_BUFFER, GetGLBufferId(), static_cast(name.length()), diff --git a/src/util/opengl_texture.cpp b/src/util/opengl_texture.cpp index 9aa63f020..9f23b9274 100644 --- a/src/util/opengl_texture.cpp +++ b/src/util/opengl_texture.cpp @@ -487,7 +487,7 @@ void OpenGLTexture::GenerateMipmaps() void OpenGLTexture::SetDebugName(std::string_view name) { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (glObjectLabel) glObjectLabel(GL_TEXTURE, m_id, static_cast(name.length()), static_cast(name.data())); #endif @@ -512,7 +512,7 @@ OpenGLSampler::~OpenGLSampler() void OpenGLSampler::SetDebugName(std::string_view name) { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (glObjectLabel) glObjectLabel(GL_SAMPLER, m_id, static_cast(name.length()), static_cast(name.data())); #endif @@ -800,7 +800,7 @@ void OpenGLTextureBuffer::Unmap(u32 used_elements) void OpenGLTextureBuffer::SetDebugName(std::string_view name) { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (glObjectLabel) { glObjectLabel(GL_TEXTURE, m_buffer->GetGLBufferId(), static_cast(name.length()), diff --git a/src/util/postprocessing_shader_fx.cpp b/src/util/postprocessing_shader_fx.cpp index 4f3445a7b..b24e87287 100644 --- a/src/util/postprocessing_shader_fx.cpp +++ b/src/util/postprocessing_shader_fx.cpp @@ -1265,7 +1265,7 @@ bool PostProcessing::ReShadeFXShader::CreatePasses(GPUTexture::Format backbuffer pass.samplers.push_back(std::move(sampler)); } -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) pass.name = std::move(pi.name); #endif m_passes.push_back(std::move(pass)); diff --git a/src/util/postprocessing_shader_fx.h b/src/util/postprocessing_shader_fx.h index ecc2c09d2..329adf2ef 100644 --- a/src/util/postprocessing_shader_fx.h +++ b/src/util/postprocessing_shader_fx.h @@ -132,7 +132,7 @@ private: llvm::SmallVector samplers; u32 num_vertices; -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) std::string name; #endif }; diff --git a/src/util/sdl_input_source.cpp b/src/util/sdl_input_source.cpp index 353441ff5..d02920f78 100644 --- a/src/util/sdl_input_source.cpp +++ b/src/util/sdl_input_source.cpp @@ -304,7 +304,7 @@ bool SDLInputSource::InitializeSubsystem() } SDL_LogSetOutputFunction(SDLLogCallback, nullptr); -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE); #else SDL_LogSetAllPriority(SDL_LOG_PRIORITY_INFO); diff --git a/src/util/sockets.cpp b/src/util/sockets.cpp index f357ca15e..2d7d712d2 100644 --- a/src/util/sockets.cpp +++ b/src/util/sockets.cpp @@ -383,7 +383,7 @@ void SocketMultiplexer::RemoveOpenSocket(BaseSocket* socket) if (epoll_ctl(m_epoll_fd, EPOLL_CTL_DEL, socket->GetDescriptor(), nullptr) != 0) [[unlikely]] ERROR_LOG("epoll_ctl() to remove socket failed: {}", Error::CreateErrno(errno).GetDescription()); #else -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) for (size_t i = 0; i < m_poll_array_active_size; i++) { pollfd& pfd = m_poll_array[i]; diff --git a/src/util/util.props b/src/util/util.props index d6203cd79..d74c36059 100644 --- a/src/util/util.props +++ b/src/util/util.props @@ -17,7 +17,7 @@ - + diff --git a/src/util/vulkan_builders.h b/src/util/vulkan_builders.h index 760caecee..3eb3e1fb3 100644 --- a/src/util/vulkan_builders.h +++ b/src/util/vulkan_builders.h @@ -14,7 +14,7 @@ class Error; -#if defined(_DEBUG) && !defined(CPU_ARCH_ARM32) && !defined(CPU_ARCH_X86) +#if (defined(_DEBUG) || defined(_DEVEL)) && !defined(CPU_ARCH_ARM32) && !defined(CPU_ARCH_X86) #define ENABLE_VULKAN_DEBUG_OBJECTS 1 #endif diff --git a/src/util/vulkan_device.cpp b/src/util/vulkan_device.cpp index 6f99076cd..9839a387e 100644 --- a/src/util/vulkan_device.cpp +++ b/src/util/vulkan_device.cpp @@ -105,7 +105,7 @@ const std::array(GPUTexture::Format::MaxCount)> Vulka // Handles are always 64-bit, even on 32-bit platforms. static const VkRenderPass DYNAMIC_RENDERING_RENDER_PASS = ((VkRenderPass) static_cast(-1LL)); -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) static u32 s_debug_scope_depth = 0; #endif @@ -116,7 +116,7 @@ VulkanDevice::VulkanDevice() { m_render_api = RenderAPI::Vulkan; -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) s_debug_scope_depth = 0; #endif } @@ -2327,7 +2327,7 @@ void VulkanDevice::SubmitPresent(GPUSwapChain* swap_chain) QueuePresent(static_cast(swap_chain)); } -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) static std::array Palette(float phase, const std::array& a, const std::array& b, const std::array& c, const std::array& d) { @@ -2341,7 +2341,7 @@ static std::array Palette(float phase, const std::array& a, void VulkanDevice::PushDebugGroup(const char* name) { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (!vkCmdBeginDebugUtilsLabelEXT || !m_debug_device) return; @@ -2360,7 +2360,7 @@ void VulkanDevice::PushDebugGroup(const char* name) void VulkanDevice::PopDebugGroup() { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (!vkCmdEndDebugUtilsLabelEXT || !m_debug_device) return; @@ -2372,7 +2372,7 @@ void VulkanDevice::PopDebugGroup() void VulkanDevice::InsertDebugMessage(const char* msg) { -#ifdef _DEBUG +#if defined(_DEBUG) || defined(_DEVEL) if (!vkCmdInsertDebugUtilsLabelEXT || !m_debug_device) return; From 9df59713daf3b0373a56013f3f238976cdcd036a Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 1 Dec 2024 23:01:33 +1000 Subject: [PATCH 44/69] GPUDevice: Put debug messages/scopes behind conditions And completely compile them out in Release builds. Gets Devel close to Release in terms of performance. --- src/util/d3d11_device.cpp | 14 ++- src/util/d3d11_device.h | 2 + src/util/d3d11_pipeline.cpp | 8 ++ src/util/d3d11_pipeline.h | 4 + src/util/d3d11_texture.cpp | 19 +++- src/util/d3d11_texture.h | 8 ++ src/util/d3d12_builders.cpp | 2 +- src/util/d3d12_builders.h | 4 +- src/util/d3d12_device.cpp | 16 ++-- src/util/d3d12_device.h | 2 + src/util/d3d12_pipeline.cpp | 8 ++ src/util/d3d12_pipeline.h | 4 + src/util/d3d12_texture.cpp | 16 ++++ src/util/d3d12_texture.h | 8 ++ src/util/gpu_device.h | 125 +++++++++++++++++++++++--- src/util/gpu_texture.h | 17 ++++ src/util/media_capture.cpp | 2 - src/util/metal_device.h | 14 +++ src/util/metal_device.mm | 28 ++++++ src/util/opengl_device.cpp | 10 +-- src/util/opengl_device.h | 2 + src/util/opengl_pipeline.cpp | 14 +-- src/util/opengl_pipeline.h | 6 +- src/util/opengl_stream_buffer.cpp | 6 +- src/util/opengl_stream_buffer.h | 2 + src/util/opengl_texture.cpp | 24 ++--- src/util/opengl_texture.h | 8 ++ src/util/postprocessing_shader_fx.cpp | 2 +- src/util/postprocessing_shader_fx.h | 2 +- src/util/vulkan_device.cpp | 15 ++-- src/util/vulkan_device.h | 2 + src/util/vulkan_pipeline.cpp | 8 ++ src/util/vulkan_pipeline.h | 4 + src/util/vulkan_texture.cpp | 16 ++++ src/util/vulkan_texture.h | 8 ++ 35 files changed, 357 insertions(+), 73 deletions(-) diff --git a/src/util/d3d11_device.cpp b/src/util/d3d11_device.cpp index db619df30..9f50261d7 100644 --- a/src/util/d3d11_device.cpp +++ b/src/util/d3d11_device.cpp @@ -32,7 +32,7 @@ static constexpr GPUTexture::Format s_swap_chain_format = GPUTexture::Format::RG void SetD3DDebugObjectName(ID3D11DeviceChild* obj, std::string_view name) { -#if defined(_DEBUG) || defined(_DEVEL) +#ifdef ENABLE_GPU_OBJECT_NAMES // WKPDID_D3DDebugObjectName static constexpr GUID guid = {0x429b8c22, 0x9188, 0x4b0c, {0x87, 0x42, 0xac, 0xb0, 0xbf, 0x85, 0xc2, 0x00}}; @@ -115,7 +115,7 @@ bool D3D11Device::CreateDeviceAndMainSwapChain(std::string_view adapter, Feature } } -#if defined(_DEBUG) || defined(_DEVEL) +#ifdef ENABLE_GPU_OBJECT_NAMES if (m_debug_device) m_context.As(&m_annotation); #endif @@ -832,36 +832,34 @@ float D3D11Device::GetAndResetAccumulatedGPUTime() return value; } +#ifdef ENABLE_GPU_OBJECT_NAMES + void D3D11Device::PushDebugGroup(const char* name) { -#if defined(_DEBUG) || defined(_DEVEL) if (!m_annotation) return; m_annotation->BeginEvent(StringUtil::UTF8StringToWideString(name).c_str()); -#endif } void D3D11Device::PopDebugGroup() { -#if defined(_DEBUG) || defined(_DEVEL) if (!m_annotation) return; m_annotation->EndEvent(); -#endif } void D3D11Device::InsertDebugMessage(const char* msg) { -#if defined(_DEBUG) || defined(_DEVEL) if (!m_annotation) return; m_annotation->SetMarker(StringUtil::UTF8StringToWideString(msg).c_str()); -#endif } +#endif + void D3D11Device::MapVertexBuffer(u32 vertex_size, u32 vertex_count, void** map_ptr, u32* map_space, u32* map_base_vertex) { diff --git a/src/util/d3d11_device.h b/src/util/d3d11_device.h index d06a75872..c279fdf53 100644 --- a/src/util/d3d11_device.h +++ b/src/util/d3d11_device.h @@ -80,9 +80,11 @@ public: std::unique_ptr CreatePipeline(const GPUPipeline::GraphicsConfig& config, Error* error) override; std::unique_ptr CreatePipeline(const GPUPipeline::ComputeConfig& config, Error* error) override; +#ifdef ENABLE_GPU_OBJECT_NAMES void PushDebugGroup(const char* name) override; void PopDebugGroup() override; void InsertDebugMessage(const char* msg) override; +#endif void MapVertexBuffer(u32 vertex_size, u32 vertex_count, void** map_ptr, u32* map_space, u32* map_base_vertex) override; diff --git a/src/util/d3d11_pipeline.cpp b/src/util/d3d11_pipeline.cpp index b0d4dd681..21bac1563 100644 --- a/src/util/d3d11_pipeline.cpp +++ b/src/util/d3d11_pipeline.cpp @@ -47,11 +47,15 @@ ID3D11ComputeShader* D3D11Shader::GetComputeShader() const return static_cast(m_shader.Get()); } +#ifdef ENABLE_GPU_OBJECT_NAMES + void D3D11Shader::SetDebugName(std::string_view name) { SetD3DDebugObjectName(m_shader.Get(), name); } +#endif + std::unique_ptr D3D11Device::CreateShaderFromBinary(GPUShaderStage stage, std::span data, Error* error) { @@ -135,11 +139,15 @@ D3D11Pipeline::~D3D11Pipeline() D3D11Device::GetInstance().UnbindPipeline(this); } +#ifdef ENABLE_GPU_OBJECT_NAMES + void D3D11Pipeline::SetDebugName(std::string_view name) { // can't label this directly } +#endif + D3D11Device::ComPtr D3D11Device::GetRasterizationState(const GPUPipeline::RasterizationState& rs, Error* error) { diff --git a/src/util/d3d11_pipeline.h b/src/util/d3d11_pipeline.h index c3d58d3b2..97be1399a 100644 --- a/src/util/d3d11_pipeline.h +++ b/src/util/d3d11_pipeline.h @@ -30,7 +30,9 @@ public: ALWAYS_INLINE const std::vector& GetBytecode() const { return m_bytecode; } +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: D3D11Shader(GPUShaderStage stage, Microsoft::WRL::ComPtr shader, std::vector bytecode); @@ -49,7 +51,9 @@ class D3D11Pipeline final : public GPUPipeline public: ~D3D11Pipeline() override; +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif ALWAYS_INLINE bool IsComputePipeline() const { return !m_vs; } ALWAYS_INLINE ID3D11RasterizerState* GetRasterizerState() const { return m_rs.Get(); } diff --git a/src/util/d3d11_texture.cpp b/src/util/d3d11_texture.cpp index cd5fd2931..813d5cc46 100644 --- a/src/util/d3d11_texture.cpp +++ b/src/util/d3d11_texture.cpp @@ -42,11 +42,15 @@ D3D11Sampler::D3D11Sampler(ComPtr ss) : m_ss(std::move(ss)) D3D11Sampler::~D3D11Sampler() = default; +#ifdef ENABLE_GPU_OBJECT_NAMES + void D3D11Sampler::SetDebugName(std::string_view name) { SetD3DDebugObjectName(m_ss.Get(), name); } +#endif + std::unique_ptr D3D11Device::CreateSampler(const GPUSampler::Config& config, Error* error) { static constexpr std::array(GPUSampler::AddressMode::MaxCount)> ta = {{ @@ -200,8 +204,7 @@ bool D3D11Texture::Map(void** map, u32* map_stride, u32 x, u32 y, u32 width, u32 if (IsCompressedFormat(m_format)) { - *map = static_cast(sr.pData) + ((y / GetBlockSize()) * sr.RowPitch) + - ((x / GetBlockSize()) * GetPixelSize()); + *map = static_cast(sr.pData) + ((y / GetBlockSize()) * sr.RowPitch) + ((x / GetBlockSize()) * GetPixelSize()); } else { @@ -225,11 +228,15 @@ void D3D11Texture::GenerateMipmaps() D3D11Device::GetD3DContext()->GenerateMips(m_srv.Get()); } +#ifdef ENABLE_GPU_OBJECT_NAMES + void D3D11Texture::SetDebugName(std::string_view name) { SetD3DDebugObjectName(m_texture.Get(), name); } +#endif + DXGI_FORMAT D3D11Texture::GetDXGIFormat() const { return D3DCommon::GetFormatMapping(m_format).resource_format; @@ -419,11 +426,15 @@ void D3D11TextureBuffer::Unmap(u32 used_elements) m_buffer.Unmap(D3D11Device::GetD3DContext(), size); } +#ifdef ENABLE_GPU_OBJECT_NAMES + void D3D11TextureBuffer::SetDebugName(std::string_view name) { SetD3DDebugObjectName(m_buffer.GetD3DBuffer(), name); } +#endif + std::unique_ptr D3D11Device::CreateTextureBuffer(GPUTextureBuffer::Format format, u32 size_in_elements, Error* error /* = nullptr */) { @@ -543,6 +554,8 @@ void D3D11DownloadTexture::Flush() // Handled when mapped. } +#ifdef ENABLE_GPU_OBJECT_NAMES + void D3D11DownloadTexture::SetDebugName(std::string_view name) { if (name.empty()) @@ -551,6 +564,8 @@ void D3D11DownloadTexture::SetDebugName(std::string_view name) SetD3DDebugObjectName(m_texture.Get(), name); } +#endif + std::unique_ptr D3D11Device::CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format, Error* error /* = nullptr */) { diff --git a/src/util/d3d11_texture.h b/src/util/d3d11_texture.h index 25f636442..0e0617d49 100644 --- a/src/util/d3d11_texture.h +++ b/src/util/d3d11_texture.h @@ -26,7 +26,9 @@ public: ALWAYS_INLINE ID3D11SamplerState* GetSamplerState() const { return m_ss.Get(); } ALWAYS_INLINE ID3D11SamplerState* const* GetSamplerStateArray() const { return m_ss.GetAddressOf(); } +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: D3D11Sampler(ComPtr ss); @@ -88,7 +90,9 @@ public: void Unmap() override; void GenerateMipmaps() override; +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: D3D11Texture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format, Flags flags, @@ -118,7 +122,9 @@ public: void* Map(u32 required_elements) override; void Unmap(u32 used_elements) override; +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: D3D11StreamBuffer m_buffer; @@ -140,7 +146,9 @@ public: void Flush() override; +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: D3D11DownloadTexture(Microsoft::WRL::ComPtr tex, u32 width, u32 height, GPUTexture::Format format); diff --git a/src/util/d3d12_builders.cpp b/src/util/d3d12_builders.cpp index 7eaf69bb6..a423a5fcc 100644 --- a/src/util/d3d12_builders.cpp +++ b/src/util/d3d12_builders.cpp @@ -310,7 +310,7 @@ u32 D3D12::RootSignatureBuilder::AddDescriptorTable(D3D12_DESCRIPTOR_RANGE_TYPE return index; } -#if defined(_DEBUG) || defined(_DEVEL) +#ifdef ENABLE_GPU_OBJECT_NAMES void D3D12::SetObjectName(ID3D12Object* object, std::string_view name) { diff --git a/src/util/d3d12_builders.h b/src/util/d3d12_builders.h index 81dc57f7a..f139b2f0e 100644 --- a/src/util/d3d12_builders.h +++ b/src/util/d3d12_builders.h @@ -6,6 +6,8 @@ #include "common/types.h" #include "common/windows_headers.h" +#include "gpu_device.h" + #include #include #include @@ -122,7 +124,7 @@ private: D3D12_COMPUTE_PIPELINE_STATE_DESC m_desc; }; -#if defined(_DEBUG) || defined(_DEVEL) +#ifdef ENABLE_GPU_OBJECT_NAMES void SetObjectName(ID3D12Object* object, std::string_view name); #else static inline void SetObjectName(ID3D12Object* object, std::string_view name) diff --git a/src/util/d3d12_device.cpp b/src/util/d3d12_device.cpp index 8e0386053..7dd946c3f 100644 --- a/src/util/d3d12_device.cpp +++ b/src/util/d3d12_device.cpp @@ -59,7 +59,7 @@ static constexpr GPUTexture::Format s_swap_chain_format = GPUTexture::Format::RG // We just need to keep this alive, never reference it. static DynamicHeapArray s_pipeline_cache_data; -#if defined(_DEBUG) || defined(_DEVEL) +#ifdef ENABLE_GPU_OBJECT_NAMES #include "WinPixEventRuntime/pix3.h" static u32 s_debug_scope_depth = 0; #endif @@ -117,7 +117,7 @@ D3D12Device::D3D12Device() { m_render_api = RenderAPI::D3D12; -#if defined(_DEBUG) || defined(_DEVEL) +#ifdef ENABLE_GPU_OBJECT_NAMES s_debug_scope_depth = 0; #endif } @@ -1273,7 +1273,8 @@ void D3D12Device::SubmitPresent(GPUSwapChain* swap_chain) SC->GetSwapChain()->Present(sync_interval, flags); } -#if defined(_DEBUG) || defined(_DEVEL) +#ifdef ENABLE_GPU_OBJECT_NAMES + static UINT64 Palette(float phase, const std::array& a, const std::array& b, const std::array& c, const std::array& d) { @@ -1286,41 +1287,36 @@ static UINT64 Palette(float phase, const std::array& a, const std::arr static_cast(std::clamp(result[1] * 255.0f, 0.0f, 255.0f)), static_cast(std::clamp(result[2] * 255.0f, 0.0f, 255.0f))); } -#endif void D3D12Device::PushDebugGroup(const char* name) { -#if defined(_DEBUG) || defined(_DEVEL) if (!m_debug_device) return; const UINT64 color = Palette(static_cast(++s_debug_scope_depth), {0.5f, 0.5f, 0.5f}, {0.5f, 0.5f, 0.5f}, {1.0f, 1.0f, 0.5f}, {0.8f, 0.90f, 0.30f}); PIXBeginEvent(GetCommandList(), color, "%s", name); -#endif } void D3D12Device::PopDebugGroup() { -#if defined(_DEBUG) || defined(_DEVEL) if (!m_debug_device) return; s_debug_scope_depth = (s_debug_scope_depth == 0) ? 0 : (s_debug_scope_depth - 1u); PIXEndEvent(GetCommandList()); -#endif } void D3D12Device::InsertDebugMessage(const char* msg) { -#if defined(_DEBUG) || defined(_DEVEL) if (!m_debug_device) return; PIXSetMarker(GetCommandList(), PIX_COLOR(0, 0, 0), "%s", msg); -#endif } +#endif + void D3D12Device::SetFeatures(D3D_FEATURE_LEVEL feature_level, FeatureMask disabled_features) { m_render_api_version = D3DCommon::GetRenderAPIVersionForFeatureLevel(feature_level); diff --git a/src/util/d3d12_device.h b/src/util/d3d12_device.h index 6306a1376..3b55d65fd 100644 --- a/src/util/d3d12_device.h +++ b/src/util/d3d12_device.h @@ -101,9 +101,11 @@ public: std::unique_ptr CreatePipeline(const GPUPipeline::GraphicsConfig& config, Error* error) override; std::unique_ptr CreatePipeline(const GPUPipeline::ComputeConfig& config, Error* error) override; +#ifdef ENABLE_GPU_OBJECT_NAMES void PushDebugGroup(const char* name) override; void PopDebugGroup() override; void InsertDebugMessage(const char* msg) override; +#endif void MapVertexBuffer(u32 vertex_size, u32 vertex_count, void** map_ptr, u32* map_space, u32* map_base_vertex) override; diff --git a/src/util/d3d12_pipeline.cpp b/src/util/d3d12_pipeline.cpp index 4b78c2aff..84088a3ef 100644 --- a/src/util/d3d12_pipeline.cpp +++ b/src/util/d3d12_pipeline.cpp @@ -22,10 +22,14 @@ D3D12Shader::D3D12Shader(GPUShaderStage stage, Bytecode bytecode) : GPUShader(st D3D12Shader::~D3D12Shader() = default; +#ifdef ENABLE_GPU_OBJECT_NAMES + void D3D12Shader::SetDebugName(std::string_view name) { } +#endif + std::unique_ptr D3D12Device::CreateShaderFromBinary(GPUShaderStage stage, std::span data, Error* error) { @@ -72,11 +76,15 @@ D3D12Pipeline::~D3D12Pipeline() D3D12Device::GetInstance().DeferObjectDestruction(std::move(m_pipeline)); } +#ifdef ENABLE_GPU_OBJECT_NAMES + void D3D12Pipeline::SetDebugName(std::string_view name) { D3D12::SetObjectName(m_pipeline.Get(), name); } +#endif + std::string D3D12Pipeline::GetPipelineName(const GraphicsConfig& config) { SHA1Digest hash; diff --git a/src/util/d3d12_pipeline.h b/src/util/d3d12_pipeline.h index e2f83d14f..e7ce7557a 100644 --- a/src/util/d3d12_pipeline.h +++ b/src/util/d3d12_pipeline.h @@ -25,7 +25,9 @@ public: ALWAYS_INLINE const u8* GetBytecodeData() const { return m_bytecode.data(); } ALWAYS_INLINE u32 GetBytecodeSize() const { return static_cast(m_bytecode.size()); } +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: D3D12Shader(GPUShaderStage stage, Bytecode bytecode); @@ -48,7 +50,9 @@ public: ALWAYS_INLINE const std::array& GetBlendConstantsF() const { return m_blend_constants_f; } ALWAYS_INLINE bool HasVertexStride() const { return (m_vertex_stride > 0); } +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif static std::string GetPipelineName(const GraphicsConfig& config); static std::string GetPipelineName(const ComputeConfig& config); diff --git a/src/util/d3d12_texture.cpp b/src/util/d3d12_texture.cpp index 1d7500dfd..dfcf16046 100644 --- a/src/util/d3d12_texture.cpp +++ b/src/util/d3d12_texture.cpp @@ -616,11 +616,15 @@ void D3D12Texture::ActuallyCommitClear(ID3D12GraphicsCommandList* cmdlist) SetState(State::Dirty); } +#ifdef ENABLE_GPU_OBJECT_NAMES + void D3D12Texture::SetDebugName(std::string_view name) { D3D12::SetObjectName(m_resource.Get(), name); } +#endif + u32 D3D12Texture::CalculateSubresource(u32 layer, u32 level, u32 num_levels) { // D3D11CalcSubresource @@ -698,10 +702,14 @@ D3D12Sampler::~D3D12Sampler() // Cleaned up by main class. } +#ifdef ENABLE_GPU_OBJECT_NAMES + void D3D12Sampler::SetDebugName(std::string_view name) { } +#endif + D3D12DescriptorHandle D3D12Device::GetSampler(const GPUSampler::Config& config, Error* error) { const auto it = m_sampler_map.find(config.key); @@ -843,11 +851,15 @@ void D3D12TextureBuffer::Unmap(u32 used_elements) m_buffer.CommitMemory(size); } +#ifdef ENABLE_GPU_OBJECT_NAMES + void D3D12TextureBuffer::SetDebugName(std::string_view name) { D3D12::SetObjectName(m_buffer.GetBuffer(), name); } +#endif + std::unique_ptr D3D12Device::CreateTextureBuffer(GPUTextureBuffer::Format format, u32 size_in_elements, Error* error /* = nullptr */) { @@ -1026,6 +1038,8 @@ void D3D12DownloadTexture::Flush() } } +#ifdef ENABLE_GPU_OBJECT_NAMES + void D3D12DownloadTexture::SetDebugName(std::string_view name) { if (name.empty()) @@ -1034,6 +1048,8 @@ void D3D12DownloadTexture::SetDebugName(std::string_view name) D3D12::SetObjectName(m_buffer.Get(), name); } +#endif + std::unique_ptr D3D12Device::CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format, Error* error /* = nullptr */) { diff --git a/src/util/d3d12_texture.h b/src/util/d3d12_texture.h index 96ccd0f3d..daa768499 100644 --- a/src/util/d3d12_texture.h +++ b/src/util/d3d12_texture.h @@ -43,7 +43,9 @@ public: void GenerateMipmaps() override; void MakeReadyForSampling() override; +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif void TransitionToState(D3D12_RESOURCE_STATES state); void CommitClear(); @@ -115,7 +117,9 @@ public: ALWAYS_INLINE const D3D12DescriptorHandle& GetDescriptor() const { return m_descriptor; } +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: D3D12Sampler(D3D12DescriptorHandle descriptor); @@ -140,7 +144,9 @@ public: void* Map(u32 required_elements) override; void Unmap(u32 used_elements) override; +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: D3D12StreamBuffer m_buffer; @@ -165,7 +171,9 @@ public: void Flush() override; +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: D3D12DownloadTexture(u32 width, u32 height, GPUTexture::Format format, ComPtr allocation, diff --git a/src/util/gpu_device.h b/src/util/gpu_device.h index 071fdcd72..8a98cb4f5 100644 --- a/src/util/gpu_device.h +++ b/src/util/gpu_device.h @@ -13,6 +13,8 @@ #include "common/small_string.h" #include "common/types.h" +#include "fmt/base.h" + #include #include #include @@ -26,6 +28,11 @@ class Error; class Image; +// Enables debug event generation and object names for graphics debuggers. +#if defined(_DEBUG) || defined(_DEVEL) +#define ENABLE_GPU_OBJECT_NAMES +#endif + enum class RenderAPI : u8 { None, @@ -97,7 +104,14 @@ public: GPUSampler(); virtual ~GPUSampler(); +#ifdef ENABLE_GPU_OBJECT_NAMES virtual void SetDebugName(std::string_view name) = 0; + template + void SetDebugName(fmt::format_string fmt, T&&... args) + { + SetDebugName(TinyString::from_vformat(fmt, fmt::make_format_args(args...))); + } +#endif static Config GetNearestConfig(); static Config GetLinearConfig(); @@ -135,7 +149,14 @@ public: ALWAYS_INLINE GPUShaderStage GetStage() const { return m_stage; } +#ifdef ENABLE_GPU_OBJECT_NAMES virtual void SetDebugName(std::string_view name) = 0; + template + void SetDebugName(fmt::format_string fmt, T&&... args) + { + SetDebugName(TinyString::from_vformat(fmt, fmt::make_format_args(args...))); + } +#endif protected: GPUShaderStage m_stage; @@ -429,7 +450,14 @@ public: GPUPipeline(); virtual ~GPUPipeline(); +#ifdef ENABLE_GPU_OBJECT_NAMES virtual void SetDebugName(std::string_view name) = 0; + template + void SetDebugName(fmt::format_string fmt, T&&... args) + { + SetDebugName(TinyString::from_vformat(fmt, fmt::make_format_args(args...))); + } +#endif }; class GPUTextureBuffer @@ -455,7 +483,14 @@ public: virtual void* Map(u32 required_elements) = 0; virtual void Unmap(u32 used_elements) = 0; +#ifdef ENABLE_GPU_OBJECT_NAMES virtual void SetDebugName(std::string_view name) = 0; + template + void SetDebugName(fmt::format_string fmt, T&&... args) + { + SetDebugName(TinyString::from_vformat(fmt, fmt::make_format_args(args...))); + } +#endif protected: Format m_format; @@ -739,11 +774,25 @@ public: virtual std::unique_ptr CreatePipeline(const GPUPipeline::ComputeConfig& config, Error* error = nullptr) = 0; +#ifdef ENABLE_GPU_OBJECT_NAMES /// Debug messaging. virtual void PushDebugGroup(const char* name) = 0; virtual void PopDebugGroup() = 0; virtual void InsertDebugMessage(const char* msg) = 0; + /// Formatted debug variants. + template + void PushDebugGroup(fmt::format_string fmt, T&&... args) + { + PushDebugGroup(TinyString::from_vformat(fmt, fmt::make_format_args(args...))); + } + template + void InsertDebugMessage(fmt::format_string fmt, T&&... args) + { + InsertDebugMessage(TinyString::from_vformat(fmt, fmt::make_format_args(args...))); + } +#endif + /// Vertex/index buffer abstraction. virtual void MapVertexBuffer(u32 vertex_size, u32 vertex_count, void** map_ptr, u32* map_space, u32* map_base_vertex) = 0; @@ -929,24 +978,74 @@ ALWAYS_INLINE void GPUDevice::PooledTextureDeleter::operator()(GPUTexture* const } // Macros for debug messages. -#if defined(_DEBUG) || defined(_DEVEL) +#ifdef ENABLE_GPU_OBJECT_NAMES struct GLAutoPop { - GLAutoPop(int dummy) {} - ~GLAutoPop() { g_gpu_device->PopDebugGroup(); } + GLAutoPop(const char* name) + { + if (g_gpu_device->IsDebugDevice()) [[unlikely]] + g_gpu_device->PushDebugGroup(name); + } + + template + GLAutoPop(fmt::format_string fmt, T&&... args) + { + if (g_gpu_device->IsDebugDevice()) [[unlikely]] + g_gpu_device->PushDebugGroup(SmallString::from_vformat(fmt, fmt::make_format_args(args...))); + } + + ~GLAutoPop() + { + if (g_gpu_device->IsDebugDevice()) [[unlikely]] + g_gpu_device->PopDebugGroup(); + } }; -#define GL_SCOPE(name) GLAutoPop gl_auto_pop((g_gpu_device->PushDebugGroup(name), 0)) -#define GL_PUSH(name) g_gpu_device->PushDebugGroup(name) -#define GL_POP() g_gpu_device->PopDebugGroup() -#define GL_INS(msg) g_gpu_device->InsertDebugMessage(msg) -#define GL_OBJECT_NAME(obj, name) (obj)->SetDebugName(name) +#define GL_SCOPE(name) GLAutoPop gl_auto_pop(name) +#define GL_PUSH(name) \ + do \ + { \ + if (g_gpu_device->IsDebugDevice()) [[unlikely]] \ + g_gpu_device->PushDebugGroup(name); \ + } while (0) +#define GL_POP() \ + do \ + { \ + if (g_gpu_device->IsDebugDevice()) [[unlikely]] \ + g_gpu_device->PopDebugGroup(); \ + } while (0) +#define GL_INS(msg) \ + do \ + { \ + if (g_gpu_device->IsDebugDevice()) [[unlikely]] \ + g_gpu_device->InsertDebugMessage(msg); \ + } while (0) +#define GL_OBJECT_NAME(obj, name) \ + do \ + { \ + if (g_gpu_device->IsDebugDevice()) [[unlikely]] \ + (obj)->SetDebugName(name); \ + } while (0) -#define GL_SCOPE_FMT(...) \ - GLAutoPop gl_auto_pop((g_gpu_device->PushDebugGroup(SmallString::from_format(__VA_ARGS__)), 0)) -#define GL_PUSH_FMT(...) g_gpu_device->PushDebugGroup(SmallString::from_format(__VA_ARGS__)) -#define GL_INS_FMT(...) g_gpu_device->InsertDebugMessage(SmallString::from_format(__VA_ARGS__)) -#define GL_OBJECT_NAME_FMT(obj, ...) (obj)->SetDebugName(SmallString::from_format(__VA_ARGS__)) +#define GL_SCOPE_FMT(...) GLAutoPop gl_auto_pop(__VA_ARGS__) +#define GL_PUSH_FMT(...) \ + do \ + { \ + if (g_gpu_device->IsDebugDevice()) [[unlikely]] \ + g_gpu_device->PushDebugGroup(__VA_ARGS__); \ + } while (0) +#define GL_INS_FMT(...) \ + do \ + { \ + if (g_gpu_device->IsDebugDevice()) [[unlikely]] \ + g_gpu_device->InsertDebugMessage(__VA_ARGS__); \ + } while (0) +#define GL_OBJECT_NAME_FMT(obj, ...) \ + do \ + { \ + if (g_gpu_device->IsDebugDevice()) [[unlikely]] \ + (obj)->SetDebugName(__VA_ARGS__); \ + } while (0) #else #define GL_SCOPE(name) (void)0 #define GL_PUSH(name) (void)0 diff --git a/src/util/gpu_texture.h b/src/util/gpu_texture.h index dc04d9f59..e243da148 100644 --- a/src/util/gpu_texture.h +++ b/src/util/gpu_texture.h @@ -4,8 +4,11 @@ #pragma once #include "common/gsvector.h" +#include "common/small_string.h" #include "common/types.h" +#include "fmt/format.h" + #include #include #include @@ -184,7 +187,14 @@ public: // Instructs the backend that we're finished rendering to this texture. It may transition it to a new layout. virtual void MakeReadyForSampling(); +#if defined(_DEBUG) || defined(_DEVEL) virtual void SetDebugName(std::string_view name) = 0; + template + void SetDebugName(fmt::format_string fmt, T&&... args) + { + SetDebugName(TinyString::from_vformat(fmt, fmt::make_format_args(args...))); + } +#endif protected: GPUTexture(u16 width, u16 height, u8 layers, u8 levels, u8 samples, Type type, Format format, Flags flags); @@ -252,8 +262,15 @@ public: /// call to CopyFromTexture() and the Flush() call. virtual void Flush() = 0; +#if defined(_DEBUG) || defined(_DEVEL) /// Sets object name that will be displayed in graphics debuggers. virtual void SetDebugName(std::string_view name) = 0; + template + void SetDebugName(fmt::format_string fmt, T&&... args) + { + SetDebugName(TinyString::from_vformat(fmt, fmt::make_format_args(args...))); + } +#endif /// Reads the specified rectangle from the staging texture to out_ptr, with the specified stride /// (length in bytes of each row). CopyFromTexture() must be called first. The contents of any diff --git a/src/util/media_capture.cpp b/src/util/media_capture.cpp index d356fea0b..af251f026 100644 --- a/src/util/media_capture.cpp +++ b/src/util/media_capture.cpp @@ -285,9 +285,7 @@ bool MediaCaptureBase::DeliverVideoFrame(GPUTexture* stex) return false; } -#if defined(_DEBUG) || defined(_DEVEL) GL_OBJECT_NAME_FMT(pf.tex, "MediaCapture {}x{} Download Texture", stex->GetWidth(), stex->GetHeight()); -#endif } pf.tex->CopyFromTexture(0, 0, stex, 0, 0, m_video_width, m_video_height, 0, 0); diff --git a/src/util/metal_device.h b/src/util/metal_device.h index b83880418..1606343db 100644 --- a/src/util/metal_device.h +++ b/src/util/metal_device.h @@ -44,7 +44,9 @@ public: ALWAYS_INLINE id GetSamplerState() const { return m_ss; } +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: MetalSampler(id ss); @@ -62,7 +64,9 @@ public: ALWAYS_INLINE id GetLibrary() const { return m_library; } ALWAYS_INLINE id GetFunction() const { return m_function; } +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: MetalShader(GPUShaderStage stage, id library, id function); @@ -92,7 +96,9 @@ public: ALWAYS_INLINE MTLCullMode GetCullMode() const { return m_cull_mode; } ALWAYS_INLINE MTLPrimitiveType GetPrimitive() const { return m_primitive; } +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: MetalPipeline(id pipeline, id depth, MTLCullMode cull_mode, MTLPrimitiveType primitive); @@ -122,7 +128,9 @@ public: void MakeReadyForSampling() override; void GenerateMipmaps() override; +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif // Call when the texture is bound to the pipeline, or read from in a copy. ALWAYS_INLINE void SetUseFenceCounter(u64 counter) { m_use_fence_counter = counter; } @@ -161,7 +169,9 @@ public: void Flush() override; +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: MetalDownloadTexture(u32 width, u32 height, GPUTexture::Format format, u8* import_buffer, size_t buffer_offset, @@ -187,7 +197,9 @@ public: void* Map(u32 required_elements) override; void Unmap(u32 used_elements) override; +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: MetalStreamBuffer m_buffer; @@ -265,9 +277,11 @@ public: std::unique_ptr CreatePipeline(const GPUPipeline::GraphicsConfig& config, Error* error) override; std::unique_ptr CreatePipeline(const GPUPipeline::ComputeConfig& config, Error* error) override; +#ifdef ENABLE_GPU_OBJECT_NAMES void PushDebugGroup(const char* name) override; void PopDebugGroup() override; void InsertDebugMessage(const char* msg) override; +#endif void MapVertexBuffer(u32 vertex_size, u32 vertex_count, void** map_ptr, u32* map_space, u32* map_base_vertex) override; diff --git a/src/util/metal_device.mm b/src/util/metal_device.mm index 26b504230..e6ee3a91e 100644 --- a/src/util/metal_device.mm +++ b/src/util/metal_device.mm @@ -643,6 +643,8 @@ MetalShader::~MetalShader() MetalDevice::DeferRelease(m_library); } +#ifdef ENABLE_GPU_OBJECT_NAMES + void MetalShader::SetDebugName(std::string_view name) { @autoreleasepool @@ -651,6 +653,8 @@ void MetalShader::SetDebugName(std::string_view name) } } +#endif + std::unique_ptr MetalDevice::CreateShaderFromMSL(GPUShaderStage stage, std::string_view source, std::string_view entry_point, Error* error) { @@ -747,11 +751,15 @@ MetalPipeline::~MetalPipeline() MetalDevice::DeferRelease(m_pipeline); } +#ifdef ENABLE_GPU_OBJECT_NAMES + void MetalPipeline::SetDebugName(std::string_view name) { // readonly property :/ } +#endif + id MetalDevice::GetDepthState(const GPUPipeline::DepthState& ds) { const auto it = m_depth_states.find(ds.key); @@ -1157,6 +1165,8 @@ void MetalTexture::GenerateMipmaps() [encoder generateMipmapsForTexture:m_texture]; } +#ifdef ENABLE_GPU_OBJECT_NAMES + void MetalTexture::SetDebugName(std::string_view name) { @autoreleasepool @@ -1165,6 +1175,8 @@ void MetalTexture::SetDebugName(std::string_view name) } } +#endif + std::unique_ptr MetalDevice::CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GPUTexture::Type type, GPUTexture::Format format, GPUTexture::Flags flags, const void* data, u32 data_stride, @@ -1381,6 +1393,8 @@ void MetalDownloadTexture::Flush() dev.WaitForFenceCounter(m_copy_fence_counter); } +#ifdef ENABLE_GPU_OBJECT_NAMES + void MetalDownloadTexture::SetDebugName(std::string_view name) { @autoreleasepool @@ -1389,6 +1403,8 @@ void MetalDownloadTexture::SetDebugName(std::string_view name) } } +#endif + std::unique_ptr MetalDevice::CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format, Error* error) { @@ -1408,11 +1424,15 @@ MetalSampler::MetalSampler(id ss) : m_ss(ss) MetalSampler::~MetalSampler() = default; +#ifdef ENABLE_GPU_OBJECT_NAMES + void MetalSampler::SetDebugName(std::string_view name) { // lame.. have to put it on the descriptor :/ } +#endif + std::unique_ptr MetalDevice::CreateSampler(const GPUSampler::Config& config, Error* error) { @autoreleasepool @@ -1822,6 +1842,8 @@ void MetalTextureBuffer::Unmap(u32 used_elements) m_buffer.CommitMemory(size); } +#ifdef ENABLE_GPU_OBJECT_NAMES + void MetalTextureBuffer::SetDebugName(std::string_view name) { @autoreleasepool @@ -1830,6 +1852,8 @@ void MetalTextureBuffer::SetDebugName(std::string_view name) } } +#endif + std::unique_ptr MetalDevice::CreateTextureBuffer(GPUTextureBuffer::Format format, u32 size_in_elements, Error* error) { @@ -1840,6 +1864,8 @@ std::unique_ptr MetalDevice::CreateTextureBuffer(GPUTextureBuf return tb; } +#ifdef ENABLE_GPU_OBJECT_NAMES + void MetalDevice::PushDebugGroup(const char* name) { } @@ -1852,6 +1878,8 @@ void MetalDevice::InsertDebugMessage(const char* msg) { } +#endif + void MetalDevice::MapVertexBuffer(u32 vertex_size, u32 vertex_count, void** map_ptr, u32* map_space, u32* map_base_vertex) { diff --git a/src/util/opengl_device.cpp b/src/util/opengl_device.cpp index 0e0f4e39f..cdd2a2d1b 100644 --- a/src/util/opengl_device.cpp +++ b/src/util/opengl_device.cpp @@ -214,29 +214,26 @@ std::unique_ptr OpenGLDevice::CreatePipeline(const GPUPipeline::Com return {}; } +#ifdef ENABLE_GPU_OBJECT_NAMES + void OpenGLDevice::PushDebugGroup(const char* name) { -#if defined(_DEBUG) || defined(_DEVEL) if (!glPushDebugGroup) return; glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, static_cast(std::strlen(name)), name); -#endif } void OpenGLDevice::PopDebugGroup() { -#if defined(_DEBUG) || defined(_DEVEL) if (!glPopDebugGroup) return; glPopDebugGroup(); -#endif } void OpenGLDevice::InsertDebugMessage(const char* msg) { -#if defined(_DEBUG) || defined(_DEVEL) if (!glDebugMessageInsert) return; @@ -245,9 +242,10 @@ void OpenGLDevice::InsertDebugMessage(const char* msg) glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_MARKER, 0, GL_DEBUG_SEVERITY_NOTIFICATION, static_cast(std::strlen(msg)), msg); } -#endif } +#endif + static void GLAD_API_PTR GLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { diff --git a/src/util/opengl_device.h b/src/util/opengl_device.h index c3c1e4ee0..ba4ca9277 100644 --- a/src/util/opengl_device.h +++ b/src/util/opengl_device.h @@ -82,9 +82,11 @@ public: std::unique_ptr CreatePipeline(const GPUPipeline::GraphicsConfig& config, Error* error) override; std::unique_ptr CreatePipeline(const GPUPipeline::ComputeConfig& config, Error* error) override; +#ifdef ENABLE_GPU_OBJECT_NAMES void PushDebugGroup(const char* name) override; void PopDebugGroup() override; void InsertDebugMessage(const char* msg) override; +#endif void MapVertexBuffer(u32 vertex_size, u32 vertex_count, void** map_ptr, u32* map_space, u32* map_base_vertex) override; diff --git a/src/util/opengl_pipeline.cpp b/src/util/opengl_pipeline.cpp index b0027d452..4f36aba73 100644 --- a/src/util/opengl_pipeline.cpp +++ b/src/util/opengl_pipeline.cpp @@ -78,9 +78,10 @@ OpenGLShader::~OpenGLShader() glDeleteShader(m_id.value()); } +#ifdef ENABLE_GPU_OBJECT_NAMES + void OpenGLShader::SetDebugName(std::string_view name) { -#if defined(_DEBUG) || defined(_DEVEL) if (glObjectLabel) { if (m_id.has_value()) @@ -94,9 +95,10 @@ void OpenGLShader::SetDebugName(std::string_view name) m_debug_name = name; } } -#endif } +#endif + bool OpenGLShader::Compile(Error* error) { if (m_compile_tried) @@ -155,7 +157,7 @@ bool OpenGLShader::Compile(Error* error) m_id = shader; -#if defined(_DEBUG) || defined(_DEVEL) +#ifdef ENABLE_GPU_OBJECT_NAMES if (glObjectLabel && !m_debug_name.empty()) { glObjectLabel(GL_SHADER, shader, static_cast(m_debug_name.length()), @@ -584,14 +586,16 @@ OpenGLPipeline::~OpenGLPipeline() dev.UnrefVAO(m_key.va_key); } +#ifdef ENABLE_GPU_OBJECT_NAMES + void OpenGLPipeline::SetDebugName(std::string_view name) { -#if defined(_DEBUG) || defined(_DEVEL) if (glObjectLabel) glObjectLabel(GL_PROGRAM, m_program, static_cast(name.length()), name.data()); -#endif } +#endif + std::unique_ptr OpenGLDevice::CreatePipeline(const GPUPipeline::GraphicsConfig& config, Error* error) { const OpenGLPipeline::ProgramCacheKey pkey = OpenGLPipeline::GetProgramCacheKey(config); diff --git a/src/util/opengl_pipeline.h b/src/util/opengl_pipeline.h index 33a088e69..a5f3e575f 100644 --- a/src/util/opengl_pipeline.h +++ b/src/util/opengl_pipeline.h @@ -16,7 +16,9 @@ class OpenGLShader final : public GPUShader public: ~OpenGLShader() override; +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif bool Compile(Error* error); @@ -32,7 +34,7 @@ private: std::optional m_id; bool m_compile_tried = false; -#if defined(_DEBUG) || defined(_DEVEL) +#ifdef ENABLE_GPU_OBJECT_NAMES std::string m_debug_name; #endif }; @@ -104,7 +106,9 @@ public: ALWAYS_INLINE const BlendState& GetBlendState() const { return m_blend_state; } ALWAYS_INLINE GLenum GetTopology() const { return m_topology; } +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: OpenGLPipeline(const ProgramCacheKey& key, GLuint program, VertexArrayCache::const_iterator vao, diff --git a/src/util/opengl_stream_buffer.cpp b/src/util/opengl_stream_buffer.cpp index a4deff5fb..d0f29a4f0 100644 --- a/src/util/opengl_stream_buffer.cpp +++ b/src/util/opengl_stream_buffer.cpp @@ -29,17 +29,19 @@ void OpenGLStreamBuffer::Unbind() glBindBuffer(m_target, 0); } +#if defined(_DEBUG) || defined(_DEVEL) + void OpenGLStreamBuffer::SetDebugName(std::string_view name) { -#if defined(_DEBUG) || defined(_DEVEL) if (glObjectLabel) { glObjectLabel(GL_BUFFER, GetGLBufferId(), static_cast(name.length()), static_cast(name.data())); } -#endif } +#endif + namespace { // Uses glBufferSubData() to update. Preferred for drivers which don't support {ARB,EXT}_buffer_storage. diff --git a/src/util/opengl_stream_buffer.h b/src/util/opengl_stream_buffer.h index fa0bca26d..78d9e732c 100644 --- a/src/util/opengl_stream_buffer.h +++ b/src/util/opengl_stream_buffer.h @@ -26,7 +26,9 @@ public: void Bind(); void Unbind(); +#if defined(_DEBUG) || defined(_DEVEL) void SetDebugName(std::string_view name); +#endif struct MappingResult { diff --git a/src/util/opengl_texture.cpp b/src/util/opengl_texture.cpp index 9f23b9274..1b04fb5f7 100644 --- a/src/util/opengl_texture.cpp +++ b/src/util/opengl_texture.cpp @@ -485,18 +485,14 @@ void OpenGLTexture::GenerateMipmaps() glBindTexture(target, 0); } +#ifdef ENABLE_GPU_OBJECT_NAMES + void OpenGLTexture::SetDebugName(std::string_view name) { -#if defined(_DEBUG) || defined(_DEVEL) if (glObjectLabel) glObjectLabel(GL_TEXTURE, m_id, static_cast(name.length()), static_cast(name.data())); -#endif } -#if 0 -// If we don't have border clamp.. too bad, just hope for the best. -if (!m_gl_context->IsGLES() || GLAD_GL_ES_VERSION_3_2 || GLAD_GL_NV_texture_border_clamp || - GLAD_GL_EXT_texture_border_clamp || GLAD_GL_OES_texture_border_clamp) #endif ////////////////////////////////////////////////////////////////////////// @@ -510,14 +506,16 @@ OpenGLSampler::~OpenGLSampler() OpenGLDevice::GetInstance().UnbindSampler(m_id); } +#ifdef ENABLE_GPU_OBJECT_NAMES + void OpenGLSampler::SetDebugName(std::string_view name) { -#if defined(_DEBUG) || defined(_DEVEL) if (glObjectLabel) glObjectLabel(GL_SAMPLER, m_id, static_cast(name.length()), static_cast(name.data())); -#endif } +#endif + std::unique_ptr OpenGLDevice::CreateSampler(const GPUSampler::Config& config, Error* error /* = nullptr */) { static constexpr std::array(GPUSampler::AddressMode::MaxCount)> ta = {{ @@ -798,17 +796,19 @@ void OpenGLTextureBuffer::Unmap(u32 used_elements) m_buffer->Unmap(size); } +#ifdef ENABLE_GPU_OBJECT_NAMES + void OpenGLTextureBuffer::SetDebugName(std::string_view name) { -#if defined(_DEBUG) || defined(_DEVEL) if (glObjectLabel) { glObjectLabel(GL_TEXTURE, m_buffer->GetGLBufferId(), static_cast(name.length()), static_cast(name.data())); } -#endif } +#endif + std::unique_ptr OpenGLDevice::CreateTextureBuffer(GPUTextureBuffer::Format format, u32 size_in_elements, Error* error) { @@ -1037,6 +1037,8 @@ void OpenGLDownloadTexture::Flush() m_sync = {}; } +#ifdef ENABLE_GPU_OBJECT_NAMES + void OpenGLDownloadTexture::SetDebugName(std::string_view name) { if (name.empty()) @@ -1046,6 +1048,8 @@ void OpenGLDownloadTexture::SetDebugName(std::string_view name) glObjectLabel(GL_BUFFER, m_buffer_id, static_cast(name.length()), name.data()); } +#endif + std::unique_ptr OpenGLDevice::CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format, Error* error /* = nullptr */) { diff --git a/src/util/opengl_texture.h b/src/util/opengl_texture.h index 5bc290c2b..7b213b89d 100644 --- a/src/util/opengl_texture.h +++ b/src/util/opengl_texture.h @@ -30,7 +30,9 @@ public: void Unmap() override; void GenerateMipmaps() override; +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif static std::unique_ptr Create(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format, Flags flags, const void* data, u32 data_pitch, @@ -78,7 +80,9 @@ public: void* Map(u32 required_elements) override; void Unmap(u32 used_elements) override; +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: OpenGLTextureBuffer(Format format, u32 size_in_elements, std::unique_ptr buffer, @@ -97,7 +101,9 @@ public: ALWAYS_INLINE GLuint GetID() const { return m_id; } +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: OpenGLSampler(GLuint id); @@ -121,7 +127,9 @@ public: void Flush() override; +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: OpenGLDownloadTexture(u32 width, u32 height, GPUTexture::Format format, bool imported, GLuint buffer_id, diff --git a/src/util/postprocessing_shader_fx.cpp b/src/util/postprocessing_shader_fx.cpp index b24e87287..797085327 100644 --- a/src/util/postprocessing_shader_fx.cpp +++ b/src/util/postprocessing_shader_fx.cpp @@ -1265,7 +1265,7 @@ bool PostProcessing::ReShadeFXShader::CreatePasses(GPUTexture::Format backbuffer pass.samplers.push_back(std::move(sampler)); } -#if defined(_DEBUG) || defined(_DEVEL) +#ifdef ENABLE_GPU_OBJECT_NAMES pass.name = std::move(pi.name); #endif m_passes.push_back(std::move(pass)); diff --git a/src/util/postprocessing_shader_fx.h b/src/util/postprocessing_shader_fx.h index 329adf2ef..79089541b 100644 --- a/src/util/postprocessing_shader_fx.h +++ b/src/util/postprocessing_shader_fx.h @@ -132,7 +132,7 @@ private: llvm::SmallVector samplers; u32 num_vertices; -#if defined(_DEBUG) || defined(_DEVEL) +#ifdef ENABLE_GPU_OBJECT_NAMES std::string name; #endif }; diff --git a/src/util/vulkan_device.cpp b/src/util/vulkan_device.cpp index 9839a387e..f61fb69a7 100644 --- a/src/util/vulkan_device.cpp +++ b/src/util/vulkan_device.cpp @@ -105,7 +105,7 @@ const std::array(GPUTexture::Format::MaxCount)> Vulka // Handles are always 64-bit, even on 32-bit platforms. static const VkRenderPass DYNAMIC_RENDERING_RENDER_PASS = ((VkRenderPass) static_cast(-1LL)); -#if defined(_DEBUG) || defined(_DEVEL) +#ifdef ENABLE_GPU_OBJECT_NAMES static u32 s_debug_scope_depth = 0; #endif @@ -116,7 +116,7 @@ VulkanDevice::VulkanDevice() { m_render_api = RenderAPI::Vulkan; -#if defined(_DEBUG) || defined(_DEVEL) +#ifdef ENABLE_GPU_OBJECT_NAMES s_debug_scope_depth = 0; #endif } @@ -2327,7 +2327,7 @@ void VulkanDevice::SubmitPresent(GPUSwapChain* swap_chain) QueuePresent(static_cast(swap_chain)); } -#if defined(_DEBUG) || defined(_DEVEL) +#ifdef ENABLE_GPU_OBJECT_NAMES static std::array Palette(float phase, const std::array& a, const std::array& b, const std::array& c, const std::array& d) { @@ -2337,11 +2337,9 @@ static std::array Palette(float phase, const std::array& a, result[2] = a[2] + b[2] * std::cos(6.28318f * (c[2] * phase + d[2])); return result; } -#endif void VulkanDevice::PushDebugGroup(const char* name) { -#if defined(_DEBUG) || defined(_DEVEL) if (!vkCmdBeginDebugUtilsLabelEXT || !m_debug_device) return; @@ -2355,32 +2353,29 @@ void VulkanDevice::PushDebugGroup(const char* name) {color[0], color[1], color[2], 1.0f}, }; vkCmdBeginDebugUtilsLabelEXT(GetCurrentCommandBuffer(), &label); -#endif } void VulkanDevice::PopDebugGroup() { -#if defined(_DEBUG) || defined(_DEVEL) if (!vkCmdEndDebugUtilsLabelEXT || !m_debug_device) return; s_debug_scope_depth = (s_debug_scope_depth == 0) ? 0 : (s_debug_scope_depth - 1u); vkCmdEndDebugUtilsLabelEXT(GetCurrentCommandBuffer()); -#endif } void VulkanDevice::InsertDebugMessage(const char* msg) { -#if defined(_DEBUG) || defined(_DEVEL) if (!vkCmdInsertDebugUtilsLabelEXT || !m_debug_device) return; const VkDebugUtilsLabelEXT label = {VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT, nullptr, msg, {0.0f, 0.0f, 0.0f, 1.0f}}; vkCmdInsertDebugUtilsLabelEXT(GetCurrentCommandBuffer(), &label); -#endif } +#endif + u32 VulkanDevice::GetMaxMultisamples(VkPhysicalDevice physical_device, const VkPhysicalDeviceProperties& properties) { VkImageFormatProperties color_properties = {}; diff --git a/src/util/vulkan_device.h b/src/util/vulkan_device.h index 8ad11229a..a7a9f78ba 100644 --- a/src/util/vulkan_device.h +++ b/src/util/vulkan_device.h @@ -118,9 +118,11 @@ public: std::unique_ptr CreatePipeline(const GPUPipeline::GraphicsConfig& config, Error* error) override; std::unique_ptr CreatePipeline(const GPUPipeline::ComputeConfig& config, Error* error) override; +#ifdef ENABLE_GPU_OBJECT_NAMES void PushDebugGroup(const char* name) override; void PopDebugGroup() override; void InsertDebugMessage(const char* msg) override; +#endif void MapVertexBuffer(u32 vertex_size, u32 vertex_count, void** map_ptr, u32* map_space, u32* map_base_vertex) override; diff --git a/src/util/vulkan_pipeline.cpp b/src/util/vulkan_pipeline.cpp index a6d801c77..7a6c76b87 100644 --- a/src/util/vulkan_pipeline.cpp +++ b/src/util/vulkan_pipeline.cpp @@ -22,11 +22,15 @@ VulkanShader::~VulkanShader() vkDestroyShaderModule(VulkanDevice::GetInstance().GetVulkanDevice(), m_module, nullptr); } +#ifdef ENABLE_GPU_OBJECT_NAMES + void VulkanShader::SetDebugName(std::string_view name) { Vulkan::SetObjectName(VulkanDevice::GetInstance().GetVulkanDevice(), m_module, name); } +#endif + std::unique_ptr VulkanDevice::CreateShaderFromBinary(GPUShaderStage stage, std::span data, Error* error) { @@ -107,11 +111,15 @@ VulkanPipeline::~VulkanPipeline() VulkanDevice::GetInstance().DeferPipelineDestruction(m_pipeline); } +#ifdef ENABLE_GPU_OBJECT_NAMES + void VulkanPipeline::SetDebugName(std::string_view name) { Vulkan::SetObjectName(VulkanDevice::GetInstance().GetVulkanDevice(), m_pipeline, name); } +#endif + std::unique_ptr VulkanDevice::CreatePipeline(const GPUPipeline::GraphicsConfig& config, Error* error) { static constexpr std::array, static_cast(GPUPipeline::Primitive::MaxCount)> diff --git a/src/util/vulkan_pipeline.h b/src/util/vulkan_pipeline.h index 13324f3be..2dcc72d91 100644 --- a/src/util/vulkan_pipeline.h +++ b/src/util/vulkan_pipeline.h @@ -15,7 +15,9 @@ public: ALWAYS_INLINE VkShaderModule GetModule() const { return m_module; } +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: VulkanShader(GPUShaderStage stage, VkShaderModule mod); @@ -34,7 +36,9 @@ public: ALWAYS_INLINE Layout GetLayout() const { return m_layout; } ALWAYS_INLINE u8 GetVerticesPerPrimitive() const { return m_vertices_per_primitive; } +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: VulkanPipeline(VkPipeline pipeline, Layout layout, u8 vertices_per_primitive, RenderPassFlag render_pass_flags); diff --git a/src/util/vulkan_texture.cpp b/src/util/vulkan_texture.cpp index 8d7061806..4e3accaab 100644 --- a/src/util/vulkan_texture.cpp +++ b/src/util/vulkan_texture.cpp @@ -459,6 +459,8 @@ void VulkanTexture::OverrideImageLayout(Layout new_layout) m_layout = new_layout; } +#ifdef ENABLE_GPU_OBJECT_NAMES + void VulkanTexture::SetDebugName(std::string_view name) { VulkanDevice& dev = VulkanDevice::GetInstance(); @@ -466,6 +468,8 @@ void VulkanTexture::SetDebugName(std::string_view name) Vulkan::SetObjectName(dev.GetVulkanDevice(), m_view, name); } +#endif + void VulkanTexture::TransitionToLayout(Layout layout) { TransitionToLayout(VulkanDevice::GetInstance().GetCurrentCommandBuffer(), layout); @@ -775,11 +779,15 @@ VulkanSampler::~VulkanSampler() // Cleaned up by main class. } +#ifdef ENABLE_GPU_OBJECT_NAMES + void VulkanSampler::SetDebugName(std::string_view name) { Vulkan::SetObjectName(VulkanDevice::GetInstance().GetVulkanDevice(), m_sampler, name); } +#endif + VkSampler VulkanDevice::GetSampler(const GPUSampler::Config& config, Error* error) { const auto it = m_sampler_map.find(config.key); @@ -942,6 +950,8 @@ void VulkanTextureBuffer::Unmap(u32 used_elements) m_buffer.CommitMemory(size); } +#ifdef ENABLE_GPU_OBJECT_NAMES + void VulkanTextureBuffer::SetDebugName(std::string_view name) { VulkanDevice& dev = VulkanDevice::GetInstance(); @@ -950,6 +960,8 @@ void VulkanTextureBuffer::SetDebugName(std::string_view name) Vulkan::SetObjectName(dev.GetVulkanDevice(), m_buffer_view, name); } +#endif + std::unique_ptr VulkanDevice::CreateTextureBuffer(GPUTextureBuffer::Format format, u32 size_in_elements, Error* error) { @@ -1195,6 +1207,8 @@ void VulkanDownloadTexture::Flush() } } +#ifdef ENABLE_GPU_OBJECT_NAMES + void VulkanDownloadTexture::SetDebugName(std::string_view name) { if (name.empty()) @@ -1203,6 +1217,8 @@ void VulkanDownloadTexture::SetDebugName(std::string_view name) Vulkan::SetObjectName(VulkanDevice::GetInstance().GetVulkanDevice(), m_buffer, name); } +#endif + std::unique_ptr VulkanDevice::CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format, Error* error /* = nullptr */) { diff --git a/src/util/vulkan_texture.h b/src/util/vulkan_texture.h index b30ba7dfb..f1c83ccec 100644 --- a/src/util/vulkan_texture.h +++ b/src/util/vulkan_texture.h @@ -56,7 +56,9 @@ public: void MakeReadyForSampling() override; void GenerateMipmaps() override; +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif void TransitionToLayout(Layout layout); void CommitClear(); @@ -120,7 +122,9 @@ public: ALWAYS_INLINE VkSampler GetSampler() const { return m_sampler; } +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: VulkanSampler(VkSampler sampler); @@ -146,7 +150,9 @@ public: void* Map(u32 required_elements) override; void Unmap(u32 used_elements) override; +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: VulkanStreamBuffer m_buffer; @@ -170,7 +176,9 @@ public: void Flush() override; +#ifdef ENABLE_GPU_OBJECT_NAMES void SetDebugName(std::string_view name) override; +#endif private: VulkanDownloadTexture(u32 width, u32 height, GPUTexture::Format format, VmaAllocation allocation, From e9848a618278d4869e68df522857f63c4d3380f5 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 1 Dec 2024 23:14:24 +1000 Subject: [PATCH 45/69] Misc: Collapse more niche log channels --- src/common/log_channels.h | 14 ++------------ src/core/analog_controller.cpp | 2 +- src/core/analog_joystick.cpp | 2 +- src/core/gpu_backend.cpp | 2 +- src/core/gpu_hw_texture_cache.cpp | 2 +- src/core/gpu_sw.cpp | 2 +- src/core/gpu_sw_rasterizer.cpp | 2 +- src/core/guncon.cpp | 14 ++++---------- src/core/jogcon.cpp | 2 +- src/core/justifier.cpp | 2 +- src/core/negcon_rumble.cpp | 2 +- src/core/playstation_mouse.cpp | 2 +- src/duckstation-qt/controllerbindingwidgets.cpp | 2 +- src/util/gpu_shader_cache.cpp | 2 +- 14 files changed, 18 insertions(+), 34 deletions(-) diff --git a/src/common/log_channels.h b/src/common/log_channels.h index b075a8764..5ccab2409 100644 --- a/src/common/log_channels.h +++ b/src/common/log_channels.h @@ -3,8 +3,6 @@ #define ENUMERATE_LOG_CHANNELS(X) \ X(Achievements) \ - X(AnalogController) \ - X(AnalogJoystick) \ X(AudioStream) \ X(AutoUpdaterDialog) \ X(BIOS) \ @@ -16,7 +14,7 @@ X(Cheats) \ X(CodeCache) \ X(CompressHelpers) \ - X(ControllerBindingWidget) \ + X(Controller) \ X(CubebAudioStream) \ X(CueParser) \ X(DInputSource) \ @@ -27,17 +25,12 @@ X(FullscreenUI) \ X(GDBProtocol) \ X(GPU) \ - X(GPUBackend) \ X(GPUDevice) \ X(GPUDump) \ - X(GPUShaderCache) \ - X(GPUTextureCache) \ - X(GPU_HW) \ X(GPU_SW) \ - X(GPU_SW_Rasterizer) \ + X(GPU_HW) \ X(GameDatabase) \ X(GameList) \ - X(GunCon) \ X(HTTPDownloader) \ X(Host) \ X(ImGuiFullscreen) \ @@ -45,19 +38,16 @@ X(Image) \ X(InputManager) \ X(InterruptController) \ - X(Justifier) \ X(Log) \ X(MDEC) \ X(MediaCapture) \ X(MemMap) \ X(MemoryCard) \ X(Multitap) \ - X(NeGconRumble) \ X(PCDrv) \ X(Pad) \ X(PerfMon) \ X(PlatformMisc) \ - X(PlayStationMouse) \ X(PostProcessing) \ X(ProgressCallback) \ X(ReShadeFXShader) \ diff --git a/src/core/analog_controller.cpp b/src/core/analog_controller.cpp index c064325ab..ffd777ba6 100644 --- a/src/core/analog_controller.cpp +++ b/src/core/analog_controller.cpp @@ -20,7 +20,7 @@ #include -LOG_CHANNEL(AnalogController); +LOG_CHANNEL(Controller); AnalogController::AnalogController(u32 index) : Controller(index) { diff --git a/src/core/analog_joystick.cpp b/src/core/analog_joystick.cpp index 4b545180b..a4ea53dce 100644 --- a/src/core/analog_joystick.cpp +++ b/src/core/analog_joystick.cpp @@ -16,7 +16,7 @@ #include "IconsPromptFont.h" #include "fmt/format.h" -LOG_CHANNEL(AnalogJoystick); +LOG_CHANNEL(Controller); AnalogJoystick::AnalogJoystick(u32 index) : Controller(index) { diff --git a/src/core/gpu_backend.cpp b/src/core/gpu_backend.cpp index b24c3f6b0..59d883dc5 100644 --- a/src/core/gpu_backend.cpp +++ b/src/core/gpu_backend.cpp @@ -9,7 +9,7 @@ #include "common/log.h" #include "common/timer.h" -LOG_CHANNEL(GPUBackend); +LOG_CHANNEL(GPU); std::unique_ptr g_gpu_backend; diff --git a/src/core/gpu_hw_texture_cache.cpp b/src/core/gpu_hw_texture_cache.cpp index 43ab7d4d1..347246152 100644 --- a/src/core/gpu_hw_texture_cache.cpp +++ b/src/core/gpu_hw_texture_cache.cpp @@ -37,7 +37,7 @@ #include #include -LOG_CHANNEL(GPUTextureCache); +LOG_CHANNEL(GPU_HW); #include "common/ryml_helpers.h" diff --git a/src/core/gpu_sw.cpp b/src/core/gpu_sw.cpp index fd781af63..9159dbddc 100644 --- a/src/core/gpu_sw.cpp +++ b/src/core/gpu_sw.cpp @@ -16,7 +16,7 @@ #include -LOG_CHANNEL(GPU_SW); +LOG_CHANNEL(GPU); GPU_SW::GPU_SW() = default; diff --git a/src/core/gpu_sw_rasterizer.cpp b/src/core/gpu_sw_rasterizer.cpp index 76cfc99db..3f2246e65 100644 --- a/src/core/gpu_sw_rasterizer.cpp +++ b/src/core/gpu_sw_rasterizer.cpp @@ -10,7 +10,7 @@ #include "common/log.h" #include "common/string_util.h" -LOG_CHANNEL(GPU_SW_Rasterizer); +LOG_CHANNEL(GPU_SW); namespace GPU_SW_Rasterizer { constinit const DitherLUT g_dither_lut = []() constexpr { diff --git a/src/core/guncon.cpp b/src/core/guncon.cpp index d58ec1054..c2a75c452 100644 --- a/src/core/guncon.cpp +++ b/src/core/guncon.cpp @@ -11,6 +11,7 @@ #include "util/state_wrapper.h" #include "common/assert.h" +#include "common/log.h" #include "common/path.h" #include "common/string_util.h" @@ -18,10 +19,7 @@ #include -#if defined(_DEBUG) || defined(_DEVEL) -#include "common/log.h" -LOG_CHANNEL(GunCon); -#endif +LOG_CHANNEL(Controller); static constexpr std::array(GunCon::Binding::ButtonCount)> s_button_indices = {{13, 3, 14}}; @@ -264,13 +262,9 @@ std::unique_ptr GunCon::Create(u32 index) static const Controller::ControllerBindingInfo s_binding_info[] = { #define BUTTON(name, display_name, icon_name, binding, genb) \ - { \ - name, display_name, icon_name, static_cast(binding), InputBindingInfo::Type::Button, genb \ - } + {name, display_name, icon_name, static_cast(binding), InputBindingInfo::Type::Button, genb} #define HALFAXIS(name, display_name, icon_name, binding, genb) \ - { \ - name, display_name, icon_name, static_cast(binding), InputBindingInfo::Type::HalfAxis, genb \ - } + {name, display_name, icon_name, static_cast(binding), InputBindingInfo::Type::HalfAxis, genb} // clang-format off {"Pointer", TRANSLATE_NOOP("GunCon", "Pointer/Aiming"), ICON_PF_MOUSE, static_cast(GunCon::Binding::ButtonCount), InputBindingInfo::Type::Pointer, GenericInputBinding::Unknown}, diff --git a/src/core/jogcon.cpp b/src/core/jogcon.cpp index 0f1e29719..ca5663018 100644 --- a/src/core/jogcon.cpp +++ b/src/core/jogcon.cpp @@ -18,7 +18,7 @@ #include "IconsPromptFont.h" #include "fmt/format.h" -LOG_CHANNEL(AnalogController); +LOG_CHANNEL(Controller); JogCon::JogCon(u32 index) : Controller(index) { diff --git a/src/core/justifier.cpp b/src/core/justifier.cpp index cb6b0c693..89fcacd2e 100644 --- a/src/core/justifier.cpp +++ b/src/core/justifier.cpp @@ -19,7 +19,7 @@ #include "IconsPromptFont.h" #include -LOG_CHANNEL(Justifier); +LOG_CHANNEL(Controller); // #define CHECK_TIMING 1 #ifdef CHECK_TIMING diff --git a/src/core/negcon_rumble.cpp b/src/core/negcon_rumble.cpp index 01c732c7a..ac3728b32 100644 --- a/src/core/negcon_rumble.cpp +++ b/src/core/negcon_rumble.cpp @@ -23,7 +23,7 @@ #include -LOG_CHANNEL(NeGconRumble); +LOG_CHANNEL(Controller); // Mapping of Button to index of corresponding bit in m_button_state static constexpr std::array(NeGconRumble::Button::Count)> s_button_indices = {3, 4, 5, 6, diff --git a/src/core/playstation_mouse.cpp b/src/core/playstation_mouse.cpp index 73b33e76d..f24e1874b 100644 --- a/src/core/playstation_mouse.cpp +++ b/src/core/playstation_mouse.cpp @@ -16,7 +16,7 @@ #include #include -LOG_CHANNEL(PlayStationMouse); +LOG_CHANNEL(Controller); static constexpr std::array(PlayStationMouse::Binding::ButtonCount)> s_button_indices = { {11, 10}}; diff --git a/src/duckstation-qt/controllerbindingwidgets.cpp b/src/duckstation-qt/controllerbindingwidgets.cpp index 226d2c578..66dfc5684 100644 --- a/src/duckstation-qt/controllerbindingwidgets.cpp +++ b/src/duckstation-qt/controllerbindingwidgets.cpp @@ -39,7 +39,7 @@ #include #include -LOG_CHANNEL(ControllerBindingWidget); +LOG_CHANNEL(Host); ControllerBindingWidget::ControllerBindingWidget(QWidget* parent, ControllerSettingsWindow* dialog, u32 port) : QWidget(parent), m_dialog(dialog), m_config_section(Controller::GetSettingsSection(port)), m_port_number(port) diff --git a/src/util/gpu_shader_cache.cpp b/src/util/gpu_shader_cache.cpp index 9ab35fc3d..4c4f2dc19 100644 --- a/src/util/gpu_shader_cache.cpp +++ b/src/util/gpu_shader_cache.cpp @@ -15,7 +15,7 @@ #include "compress_helpers.h" -LOG_CHANNEL(GPUShaderCache); +LOG_CHANNEL(GPUDevice); #pragma pack(push, 1) struct CacheFileHeader From 2f70d1bd9c01b342316c7b1bb9a3a6cdb0369e4b Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 1 Dec 2024 23:18:27 +1000 Subject: [PATCH 46/69] CPU: Write trace log to data directory --- src/core/cpu_core.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/cpu_core.cpp b/src/core/cpu_core.cpp index 6443afaf9..34720981f 100644 --- a/src/core/cpu_core.cpp +++ b/src/core/cpu_core.cpp @@ -20,6 +20,7 @@ #include "common/fastjmp.h" #include "common/file_system.h" #include "common/log.h" +#include "common/path.h" #include "fmt/format.h" @@ -144,9 +145,9 @@ void CPU::StopTrace() void CPU::WriteToExecutionLog(const char* format, ...) { - if (!s_log_file_opened) + if (!s_log_file_opened) [[unlikely]] { - s_log_file = FileSystem::OpenCFile("cpu_log.txt", "wb"); + s_log_file = FileSystem::OpenCFile(Path::Combine(EmuFolders::DataRoot, "cpu_log.txt").c_str(), "wb"); s_log_file_opened = true; } From 71e1032605bb9a7eb1f8164b8feb4c5de297cdf3 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Mon, 2 Dec 2024 01:20:32 +1000 Subject: [PATCH 47/69] GameDB: Army Men: Sarge's Heroes --- data/resources/gamedb.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/data/resources/gamedb.yaml b/data/resources/gamedb.yaml index 4388c4cd5..b170ea4a5 100644 --- a/data/resources/gamedb.yaml +++ b/data/resources/gamedb.yaml @@ -9735,6 +9735,9 @@ SLES-02626: controllers: - AnalogController - DigitalController + settings: + dmaMaxSliceTicks: 100 # Stops large VRAM transfer DMA from blazing + dmaHaltTicks: 200 # past the deferred CDROM async interrupt. metadata: publisher: "3DO" developer: "3DO" @@ -9754,6 +9757,9 @@ SLES-02627: controllers: - AnalogController - DigitalController + settings: + dmaMaxSliceTicks: 100 # Stops large VRAM transfer DMA from blazing + dmaHaltTicks: 200 # past the deferred CDROM async interrupt. metadata: publisher: "3DO" developer: "3DO" @@ -9773,6 +9779,9 @@ SLES-02628: controllers: - AnalogController - DigitalController + settings: + dmaMaxSliceTicks: 100 # Stops large VRAM transfer DMA from blazing + dmaHaltTicks: 200 # past the deferred CDROM async interrupt. metadata: publisher: "3DO" developer: "3DO" @@ -9792,6 +9801,9 @@ SLES-02630: controllers: - AnalogController - DigitalController + settings: + dmaMaxSliceTicks: 100 # Stops large VRAM transfer DMA from blazing + dmaHaltTicks: 200 # past the deferred CDROM async interrupt. metadata: publisher: "3DO" developer: "3DO" @@ -9811,6 +9823,9 @@ SLES-02629: controllers: - AnalogController - DigitalController + settings: + dmaMaxSliceTicks: 100 # Stops large VRAM transfer DMA from blazing + dmaHaltTicks: 200 # past the deferred CDROM async interrupt. metadata: publisher: "3DO" developer: "3DO" @@ -9833,6 +9848,9 @@ SLUS-00914: controllers: - AnalogController - DigitalController + settings: + dmaMaxSliceTicks: 100 # Stops large VRAM transfer DMA from blazing + dmaHaltTicks: 200 # past the deferred CDROM async interrupt. metadata: publisher: "3DO" developer: "3DO" From 6756c96fa22c923a4c002b5b328421069c10ab52 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Mon, 2 Dec 2024 17:23:46 +1000 Subject: [PATCH 48/69] CDROM: Improve SeekL -> ReadN timing See comments - Mech stops at target Data - 2, or SubQ target. --- src/core/cdrom.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp index 5c7c01904..b4c70b6c4 100644 --- a/src/core/cdrom.cpp +++ b/src/core/cdrom.cpp @@ -46,6 +46,7 @@ enum : u32 SECTOR_HEADER_SIZE = CDImage::SECTOR_HEADER_SIZE, MODE1_HEADER_SIZE = CDImage::MODE1_HEADER_SIZE, MODE2_HEADER_SIZE = CDImage::MODE2_HEADER_SIZE, + SUBQ_SECTOR_SKEW = 2, XA_ADPCM_SAMPLES_PER_SECTOR_4BIT = 4032, // 28 words * 8 nibbles per word * 18 chunks XA_ADPCM_SAMPLES_PER_SECTOR_8BIT = 2016, // 28 words * 4 bytes per word * 18 chunks XA_RESAMPLE_RING_BUFFER_SIZE = 32, @@ -2967,10 +2968,13 @@ bool CDROM::CompleteSeek() if (seek_okay) { const CDImage::SubChannelQ& subq = GetSectorSubQ(s_reader.GetLastReadSector(), s_reader.GetSectorSubQ()); + s_state.current_lba = s_reader.GetLastReadSector(); + if (subq.IsCRCValid()) { // seek and update sub-q for ReadP command s_state.last_subq = subq; + s_state.last_subq_needs_update = false; const auto [seek_mm, seek_ss, seek_ff] = CDImage::Position::FromLBA(s_reader.GetLastReadSector()).ToBCD(); seek_okay = (subq.absolute_minute_bcd == seek_mm && subq.absolute_second_bcd == seek_ss && subq.absolute_frame_bcd == seek_ff); @@ -2984,12 +2988,16 @@ bool CDROM::CompleteSeek() seek_okay = (s_state.last_sector_header.minute == seek_mm && s_state.last_sector_header.second == seek_ss && s_state.last_sector_header.frame == seek_ff); - if (seek_okay) + if (seek_okay && !s_state.play_after_seek && !s_state.read_after_seek) { - // after reading the target, the mech immediately does a 1T reverse - const u32 spt = GetSectorsPerTrack(s_state.current_subq_lba); - SetHoldPosition(s_state.current_subq_lba, - (spt <= s_state.current_subq_lba) ? (s_state.current_subq_lba - spt) : 0); + // This is pretty janky. The mech completes the seek when it "sees" a data header + // 2 sectors before the seek target, so that a subsequent ReadN can complete nearly + // immediately. Therefore when the seek completes, SubQ = Target, Data = Target - 2. + // Hack the SubQ back by 2 frames so that following seeks will read forward. If we + // ever properly handle SubQ versus data positions, this can be removed. + s_state.current_subq_lba = + (s_state.current_lba >= SUBQ_SECTOR_SKEW) ? (s_state.current_lba - SUBQ_SECTOR_SKEW) : 0; + s_state.last_subq_needs_update = true; } } } @@ -3016,8 +3024,6 @@ bool CDROM::CompleteSeek() } } } - - s_state.current_lba = s_reader.GetLastReadSector(); } return seek_okay; @@ -3234,7 +3240,7 @@ void CDROM::DoSectorRead() // so that multiple sectors could be read in one back, in which case we could just "look ahead" to grab the // subq, but I haven't got around to it. It'll break libcrypt, but CC doesn't use it. One day I'll get around to // doing the refactor.... but given this is the only game that relies on it, priorities. - s_reader.GetMedia()->GenerateSubChannelQ(&s_state.last_subq, s_state.current_lba + 2); + s_reader.GetMedia()->GenerateSubChannelQ(&s_state.last_subq, s_state.current_lba + SUBQ_SECTOR_SKEW); } } else From 22edf232693cf3540ee902af96a10e3b02f932a1 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Mon, 2 Dec 2024 17:24:18 +1000 Subject: [PATCH 49/69] GPU: Fix parameter logging of some commands --- src/core/gpu.cpp | 19 +++++++++---------- src/core/gpu_commands.cpp | 4 ++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index 1108d9599..6b0e03c14 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -1585,32 +1585,31 @@ void GPU::HandleGetGPUInfoCommand(u32 value) case 0x02: // Get Texture Window { - DEBUG_LOG("Get texture window"); m_GPUREAD_latch = m_draw_mode.texture_window_value; + DEBUG_LOG("Get texture window => 0x{:08X}", m_GPUREAD_latch); } break; case 0x03: // Get Draw Area Top Left { - DEBUG_LOG("Get drawing area top left"); - m_GPUREAD_latch = - ((m_drawing_area.left & UINT32_C(0b1111111111)) | ((m_drawing_area.top & UINT32_C(0b1111111111)) << 10)); + m_GPUREAD_latch = (m_drawing_area.left | (m_drawing_area.top << 10)); + DEBUG_LOG("Get drawing area top left: ({}, {}) => 0x{:08X}", m_drawing_area.left, m_drawing_area.top, + m_GPUREAD_latch); } break; case 0x04: // Get Draw Area Bottom Right { - DEBUG_LOG("Get drawing area bottom right"); - m_GPUREAD_latch = - ((m_drawing_area.right & UINT32_C(0b1111111111)) | ((m_drawing_area.bottom & UINT32_C(0b1111111111)) << 10)); + m_GPUREAD_latch = (m_drawing_area.right | (m_drawing_area.bottom << 10)); + DEBUG_LOG("Get drawing area bottom right: ({}, {}) => 0x{:08X}", m_drawing_area.bottom, m_drawing_area.right, + m_GPUREAD_latch); } break; case 0x05: // Get Drawing Offset { - DEBUG_LOG("Get drawing offset"); - m_GPUREAD_latch = - ((m_drawing_offset.x & INT32_C(0b11111111111)) | ((m_drawing_offset.y & INT32_C(0b11111111111)) << 11)); + m_GPUREAD_latch = (m_drawing_offset.x & 0x7FF) | ((m_drawing_offset.y & 0x7FF) << 11); + DEBUG_LOG("Get drawing offset: ({}, {}) => 0x{:08X}", m_drawing_offset.x, m_drawing_offset.y, m_GPUREAD_latch); } break; diff --git a/src/core/gpu_commands.cpp b/src/core/gpu_commands.cpp index 73c4a9d21..890cfa632 100644 --- a/src/core/gpu_commands.cpp +++ b/src/core/gpu_commands.cpp @@ -267,7 +267,7 @@ bool GPU::HandleSetDrawingAreaBottomRightCommand() const u32 right = param & DRAWING_AREA_COORD_MASK; const u32 bottom = (param >> 10) & DRAWING_AREA_COORD_MASK; - DEBUG_LOG("Set drawing area bottom-right: ({}, {})", m_drawing_area.right, m_drawing_area.bottom); + DEBUG_LOG("Set drawing area bottom-right: ({}, {})", right, bottom); if (m_drawing_area.right != right || m_drawing_area.bottom != bottom) { FlushRender(); @@ -288,7 +288,7 @@ bool GPU::HandleSetDrawingOffsetCommand() const u32 param = FifoPop() & 0x00FFFFFFu; const s32 x = SignExtendN<11, s32>(param & 0x7FFu); const s32 y = SignExtendN<11, s32>((param >> 11) & 0x7FFu); - DEBUG_LOG("Set drawing offset ({}, {})", m_drawing_offset.x, m_drawing_offset.y); + DEBUG_LOG("Set drawing offset ({}, {})", x, y); if (m_drawing_offset.x != x || m_drawing_offset.y != y) { FlushRender(); From 3959c83bd4e7c0db9680782efc6b76a7722f2879 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Mon, 2 Dec 2024 20:12:40 +1000 Subject: [PATCH 50/69] GPU/TextureCache: Fix split writes not dumping --- src/core/gpu_hw_texture_cache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/gpu_hw_texture_cache.cpp b/src/core/gpu_hw_texture_cache.cpp index 347246152..44de7df8e 100644 --- a/src/core/gpu_hw_texture_cache.cpp +++ b/src/core/gpu_hw_texture_cache.cpp @@ -1958,7 +1958,7 @@ void GPUTextureCache::RemoveVRAMWrite(VRAMWrite* entry) LoopRectPagesWithEarlyExit(entry->write_rect, [&entry, &other_write](u32 pn) { PageEntry& pg = s_state.pages[pn]; ListIterateWithEarlyExit(pg.writes, [&entry, &other_write](VRAMWrite* cur) { - if (cur->hash != entry->hash) + if (cur == entry || cur->hash != entry->hash) return true; other_write = cur; From ac79e43cc060787185cc6467fc7a408223e25269 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Mon, 2 Dec 2024 20:35:56 +1000 Subject: [PATCH 51/69] Deps: Bump versions - Qt to 6.8.1. - Harfbuzz to 10.1.0. - libzip to 11.11.2. --- scripts/deps/build-dependencies-linux.sh | 22 +++++++++---------- scripts/deps/build-dependencies-mac.sh | 20 ++++++++--------- .../deps/build-dependencies-windows-arm64.bat | 22 +++++++++---------- .../deps/build-dependencies-windows-x64.bat | 22 +++++++++---------- .../packaging/flatpak/modules/11-libzip.yaml | 2 +- 5 files changed, 44 insertions(+), 44 deletions(-) diff --git a/scripts/deps/build-dependencies-linux.sh b/scripts/deps/build-dependencies-linux.sh index 4a64e511f..bcebeeb7b 100755 --- a/scripts/deps/build-dependencies-linux.sh +++ b/scripts/deps/build-dependencies-linux.sh @@ -62,14 +62,14 @@ if [ "${INSTALLDIR:0:1}" != "/" ]; then fi FREETYPE=2.13.3 -HARFBUZZ=10.0.1 +HARFBUZZ=10.1.0 LIBBACKTRACE=86885d14049fab06ef8a33aac51664230ca09200 LIBJPEGTURBO=3.0.4 LIBPNG=1.6.44 LIBWEBP=1.4.0 -LIBZIP=1.11.1 +LIBZIP=1.11.2 SDL2=2.30.9 -QT=6.8.0 +QT=6.8.1 ZSTD=1.5.6 CPUINFO=7524ad504fdcfcf75a18a133da6abd75c5d48053 @@ -116,7 +116,7 @@ if [ "$SKIP_HARFBUZZ" != true ]; then curl -C - -L -o "harfbuzz-$HARFBUZZ.tar.gz" "https://github.com/harfbuzz/harfbuzz/archive/refs/tags/$HARFBUZZ.tar.gz" fi cat >> SHASUMS <> SHASUMS <> SHASUMS < SHASUMS < Date: Mon, 2 Dec 2024 20:36:22 +1000 Subject: [PATCH 52/69] GPU/HW: Fix TC + SW-For-Readbacks combo --- src/core/gpu_backend.h | 1 + src/core/gpu_hw.cpp | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/core/gpu_backend.h b/src/core/gpu_backend.h index ea25a36a1..be9701f95 100644 --- a/src/core/gpu_backend.h +++ b/src/core/gpu_backend.h @@ -25,6 +25,7 @@ public: virtual ~GPUBackend(); ALWAYS_INLINE const Threading::Thread* GetThread() const { return m_use_gpu_thread ? &m_gpu_thread : nullptr; } + ALWAYS_INLINE bool IsUsingThread() const { return m_use_gpu_thread; } virtual bool Initialize(bool use_thread); virtual void Reset(); diff --git a/src/core/gpu_hw.cpp b/src/core/gpu_hw.cpp index 2707a197a..7b561383e 100644 --- a/src/core/gpu_hw.cpp +++ b/src/core/gpu_hw.cpp @@ -3199,7 +3199,8 @@ void GPU_HW::FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color) GL_SCOPE_FMT("FillVRAM({},{} => {},{} ({}x{}) with 0x{:08X}", x, y, x + width, y + height, width, height, color); DeactivateROV(); - if (m_sw_renderer) + const bool handle_with_tc = (m_use_texture_cache && !IsInterlacedRenderingEnabled()); + if (m_sw_renderer && !handle_with_tc) { GPUBackendFillVRAMCommand* cmd = m_sw_renderer->NewFillVRAMCommand(); FillBackendCommandParameters(cmd); @@ -3216,7 +3217,7 @@ void GPU_HW::FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color) const GSVector4i bounds = GetVRAMTransferBounds(x, y, width, height); // If TC is enabled, we have to update local memory. - if (m_use_texture_cache && !IsInterlacedRenderingEnabled()) + if (handle_with_tc) { AddWrittenRectangle(bounds); GPU_SW_Rasterizer::FillVRAM(x, y, width, height, color, false, 0); @@ -3328,7 +3329,7 @@ void GPU_HW::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, b DebugAssert(bounds.right <= static_cast(VRAM_WIDTH) && bounds.bottom <= static_cast(VRAM_HEIGHT)); AddWrittenRectangle(bounds); - if (m_sw_renderer) + if (m_sw_renderer && m_sw_renderer->IsUsingThread()) { const u32 num_words = width * height; GPUBackendUpdateVRAMCommand* cmd = m_sw_renderer->NewUpdateVRAMCommand(num_words); From 297165d1eec6880c4947c612dfde36c74fddd368 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Tue, 3 Dec 2024 12:36:53 +1000 Subject: [PATCH 53/69] Deps: Fix MacOS build --- .github/workflows/macos-build.yml | 4 ++-- scripts/deps/build-dependencies-mac.sh | 20 +++++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.github/workflows/macos-build.yml b/.github/workflows/macos-build.yml index af08b3dbc..652438d69 100644 --- a/.github/workflows/macos-build.yml +++ b/.github/workflows/macos-build.yml @@ -14,8 +14,8 @@ jobs: with: fetch-depth: 0 - - name: Use Xcode 15.4 - run: sudo xcode-select -s /Applications/Xcode_15.4.app + - name: Use Xcode 16.1 + run: sudo xcode-select -s /Applications/Xcode_16.1.app - name: Install packages shell: bash diff --git a/scripts/deps/build-dependencies-mac.sh b/scripts/deps/build-dependencies-mac.sh index 235af3248..246fdbcbc 100755 --- a/scripts/deps/build-dependencies-mac.sh +++ b/scripts/deps/build-dependencies-mac.sh @@ -44,7 +44,7 @@ LIBPNG=1.6.44 LIBJPEGTURBO=3.0.4 LIBWEBP=1.4.0 LIBZIP=1.11.2 -FFMPEG=7.0.2 +FFMPEG=7.1 MOLTENVK=1.2.9 QT=6.8.1 @@ -73,6 +73,12 @@ CMAKE_ARCH_X64=-DCMAKE_OSX_ARCHITECTURES="x86_64" CMAKE_ARCH_ARM64=-DCMAKE_OSX_ARCHITECTURES="arm64" CMAKE_ARCH_UNIVERSAL=-DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" +# SBOM generation appears to be broken on MacOS, and I can't be arsed to debug it. +CMAKE_COMMON_QT=( + -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" + -DQT_GENERATE_SBOM=OFF +) + cat > SHASUMS < Date: Tue, 3 Dec 2024 13:28:19 +1000 Subject: [PATCH 54/69] MetalDevice: Fix incorrect pixel format for RGB5A1 --- src/util/metal_device.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/metal_device.mm b/src/util/metal_device.mm index e6ee3a91e..745606ba6 100644 --- a/src/util/metal_device.mm +++ b/src/util/metal_device.mm @@ -50,7 +50,7 @@ static constexpr std::array(GPUTexture::Format: MTLPixelFormatRGBA8Unorm, // RGBA8 MTLPixelFormatBGRA8Unorm, // BGRA8 MTLPixelFormatB5G6R5Unorm, // RGB565 - MTLPixelFormatA1BGR5Unorm, // RGB5A1 + MTLPixelFormatBGR5A1Unorm, // RGB5A1 MTLPixelFormatR8Unorm, // R8 MTLPixelFormatDepth16Unorm, // D16 MTLPixelFormatDepth24Unorm_Stencil8, // D24S8 From 25063d40187dcc2f171713c86ede76be472ff3f4 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Tue, 3 Dec 2024 14:13:04 +1000 Subject: [PATCH 55/69] Achievements: Fix overlay stacking --- src/core/achievements.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/achievements.cpp b/src/core/achievements.cpp index 7cf9f1fd0..9df8d7c82 100644 --- a/src/core/achievements.cpp +++ b/src/core/achievements.cpp @@ -2238,7 +2238,7 @@ void Achievements::DrawGameOverlays() s_state.active_progress_indicator.reset(); } - position.y -= image_size.y - padding * 3.0f; + position.y -= image_size.y + padding * 3.0f; } if (!s_state.active_leaderboard_trackers.empty()) From 03eb4a6bf946b5db848f9ad9f629a1cb87f30fc7 Mon Sep 17 00:00:00 2001 From: Anderson Cardoso <43047877+andercard0@users.noreply.github.com> Date: Tue, 3 Dec 2024 01:45:36 -0300 Subject: [PATCH 56/69] =?UTF-8?q?Atualiza=C3=A7=C3=A3o=20Portugu=C3=AAs=20?= =?UTF-8?q?do=20Brasil=20(#3339)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../translations/duckstation-qt_pt-BR.ts | 2904 +++++++++-------- 1 file changed, 1541 insertions(+), 1363 deletions(-) diff --git a/src/duckstation-qt/translations/duckstation-qt_pt-BR.ts b/src/duckstation-qt/translations/duckstation-qt_pt-BR.ts index 75b982f30..33c56bbe0 100644 --- a/src/duckstation-qt/translations/duckstation-qt_pt-BR.ts +++ b/src/duckstation-qt/translations/duckstation-qt_pt-BR.ts @@ -376,41 +376,41 @@ Token gerado %2. Achievements - - + + Overriding executable Substituindo o executável - + Loading state Carregando estado - - + + Resuming state Retomando estado - + Hardcore mode will be enabled on system reset. Modo dificílimo será ligado assim que o sistema for reiniciado. - + {} (Unofficial) {} (Não oficial) - + Mastered {} Conquistado {} - - + + %n points Achievement points @@ -419,66 +419,66 @@ Token gerado %2. - + Leaderboard attempt started. Placar de líderes inciado. - + Leaderboard attempt failed. Placar de líderes falhou. - + Your Time: {} (Best: {}) Seu tempo: {} (melhor: {}) - + Your Score: {} (Best: {}) Sua pontuação: {} (melhor: {}) - + Your Value: {} (Best: {}) Sua pontuação: {} (melhor: {}) - + {} Leaderboard Position: {} of {} {} Posição na tabela de classificação: {} de {} - + Server error in {}: {} Erro no servidor {}: {} - + Achievements Disconnected Conquistas desconectadas - + An unlock request could not be completed. We will keep retrying to submit this request. Não foi possível concluir sua solicitação de desbloqueio. Continuaremos tentando enviar esta solicitação. - + Achievements Reconnected Conquistas reconectadas - + All pending unlock requests have completed. Todas as solicitações de desbloqueio pendentes foram concluídas. - + Score: {} ({} softcore) Unread messages: {} Summary for login notification. @@ -486,45 +486,45 @@ Unread messages: {} Mensagens não lidas: {} - - + + Confirm Hardcore Mode Confirmar modo dificílimo - - + + {0} cannot be performed while hardcore mode is active. Do you want to disable hardcore mode? {0} will be cancelled if you select No. {0} não é possível no momento enquanto o modo dificílimo estiver ligado. Deseja desativar o dificílimo? {0} será cancelado se você escolher Não. - - + + Cannot {} while hardcode mode is active. Não é possível registrar {} enquanto o modo hardcore estiver ativo. - + Yes Sim - + No Não - + Active Challenge Achievements Conquistas ativadas - + (Hardcore Mode) (Modo hardcore) - + You have unlocked all achievements and earned %n points! Point count @@ -533,81 +533,81 @@ Mensagens não lidas: {} - + You have unlocked {0} of {1} achievements, earning {2} of {3} possible points. Você desbloqueou {0} de {1} conquistas, ganhando {2} de {3} pontos possíveis. - + Unknown Desconhecido - + Locked Bloqueado - + Unlocked Desbloqueado - + Unsupported Sem suporte - + Unofficial Não oficial - + Recently Unlocked Desbloqueado recentemente - + Active Challenges Desafios ativos - + Almost There Quase lá - - - + + + Change Selection Alterar seleção - + View Details Ver detalhes - - - + + + Back Voltar - + XXX points XXX pontos - + Unlocked: {} Desbloqueado: {} - + This game has %n leaderboards. Leaderboard count @@ -616,80 +616,80 @@ Mensagens não lidas: {} - + Open Leaderboard Abrir lista de classificação - - + + Loading... Carregando... - + Change Page Mudar de página - + View Profile Ver perfil - - + + Leaderboard download failed Falha ao baixar os placares de líderes - + Hardcore mode is now enabled. O modo Hardcore está ativado. - + Your Time: {}{} Seu tempo: {}{} - + Your Score: {}{} Sua pontuação: {}{} - + Your Value: {}{} Sua pontuação: {}{} - + (Submitting) (enviando) - + Hardcore mode is now disabled. O modo Hardcore está desligado. - + {} (Hardcore Mode) {} (Modo dificílimo) - - + + Failed to read executable from disc. Achievements disabled. Falha ao ler o executável do disco. Conquistas desativadas. - + {0}, {1}. - + You have unlocked {} of %n achievements Achievement popup @@ -698,7 +698,7 @@ Mensagens não lidas: {} - + and earned {} of %n points Achievement popup @@ -707,18 +707,18 @@ Mensagens não lidas: {} - - + + This game has no achievements. Este jogo não possui conquistas. - + {0}, {1} - + %n achievements Mastery popup @@ -727,52 +727,52 @@ Mensagens não lidas: {} - + Submitting scores is disabled because hardcore mode is off. Leaderboards are read-only. O envio de pontuações está desligado porque o modo dificílimo está desativado. As tabelas de classificação são somente leitura. - + Show Best Mostrar melhor - + Show Nearby Mostrar nas redondezas - + Rank Classificação - + Name Nome - + Time Tempo - + Score Placar - + Value Valor - + Date Submitted Data do envio - + Downloading leaderboard data, please wait... Baixando informações de placares, por favor aguarde... @@ -1052,261 +1052,261 @@ Mensagens não lidas: {} AnalogController - + Controller {} switched to analog mode. Controle {} alterado para o modo analógico. - + Controller {} switched to digital mode. Controle {} alterado para o modo digital. - + Controller {} is locked to analog mode by the game. Controle {} está travado no modo analógico pelo jogo. - + Controller {} is locked to digital mode by the game. Controle {} está travado no modo digital pelo jogo. - + D-Pad Up D-Pad para cima - + D-Pad Right D-Pad para a direita - + D-Pad Down D-Pad para baixo - + D-Pad Left D-Pad para a esquerda - + Analog Toggle Alternância do analógico - + Left Stick Left Analógico esquerdo (esquerda) - + Left Stick Right Analógico esquerdo (direita) - + Left Stick Down Analógico esquerdo(baixo) - + Left Stick Up Analógico para cima - + Right Stick Left Analógico direito (direita) - + Right Stick Right Analógico esquerdo (esquerda) - + Right Stick Down Analógico para baixo - + Right Stick Up Analógico direito(cima) - + Not Inverted Não invertido - + Invert Left/Right Inverter Esquerda/Direita - + Invert Up/Down Inverter para cima/para baixo - + Invert Left/Right + Up/Down Inverter Esquerda/Direita + Cima/Baixo - + Forces the controller to analog mode when the console is reset/powered on. Força o controle a entrar no modo analógico quando o console é reiniciado/ligado. - + Analog Deadzone Zona morta do analógico - + Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored. Define a zona morta do analógico, por exemplo:. a fração do movimento do analógico que será ignorada. - + Analog Sensitivity Sensibilidade do analógico - + Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent controllers, e.g. DualShock 4, Xbox One Controller. Define o fator de escala do eixo do analógico. Um valor entre 130% e 140% é recomendável quando estiver usando controles mais recentes, por exemplo: Dualshock 4 e controles de Xbox One. - + Button/Trigger Deadzone Botão/Gatilho zona morta - + Sets the deadzone for activating buttons/triggers, i.e. the fraction of the trigger which will be ignored. Define a zona morta para acionamento de botões e gatilhos, ou seja, a fração do gatilho que será ignorada. - + Large Motor Vibration Bias Direção de vibração do motor grande - + Sets the bias value for the large vibration motor. If vibration in some games is too weak or not functioning, try increasing this value. Negative values will decrease the intensity of vibration. Define o valor de direcionamento para o motor grande. Se a vibração em alguns jogos estiver muito fraca ou não estiver funcionando, tente aumentar esse valor. Valores negativos diminuirão a intensidade da vibração. - + Small Motor Vibration Bias Direção de vibração do motor pequeno - + Sets the bias value for the small vibration motor. If vibration in some games is too weak or not functioning, try increasing this value. Negative values will decrease the intensity of vibration. Define o valor de direcionamento para o motor pequeno. Se a vibração em alguns jogos estiver muito fraca ou não estiver funcionando, tente aumentar esse valor. Valores negativos diminuirão a intensidade da vibração. - + Invert Left Stick Inverter analógico esquerdo - + Inverts the direction of the left analog stick. Inverte a direção do controle analógico esquerdo. - + Invert Right Stick Inverter analógico direito - + Inverts the direction of the right analog stick. Inverte a direção do controle analógico direito. - + Select Select - + Start Start - + Triangle Triângulo - + Cross Cruz - + Circle Círculo - + Square Quadrado - + L1 L1 - + L2 L2 - + R1 R1 - + R2 R2 - + L3 L3 - + R3 R3 - + Force Analog Mode on Reset Forçar modo analógico ao reiniciar - + Use Analog Sticks for D-Pad in Digital Mode Usar analógicos como D-Pad no modo digital - + Allows you to use the analog sticks to control the d-pad in digital mode, as well as the buttons. Te permite usar os analógicos como um direcional (D-Pad) no modo digital, assim como os botões. @@ -1969,8 +1969,8 @@ Mensagens não lidas: {} AutoUpdaterDialog - - + + Automatic Updater Atualizador automático @@ -2005,68 +2005,68 @@ Mensagens não lidas: {} Lembrar-me mais tarde - + Do not show again Não mostrar novamente - - + + Updater Error Erro na atualização - + No updates are currently available. Please try again later. Não há novas atualizações no momento. - + Current Version: %1 (%2) Versão atual: %1 (%2) - + New Version: %1 (%2) Nova versão: %1 (%2) - + Download... Baixando... - + Loading... Carregando... - + <h2>Changes:</h2> <h2>Mudanças:</h2> - + <h2>Save State Warning</h2><p>Installing this update will make your save states <b>incompatible</b>. Please ensure you have saved your games to memory card before installing this update or you will lose progress.</p> <h2>Alerta para ESTADOS SALVOS</h2><p>Ao instalar esta atualização, seus ESTADOS SALVOS se tornarão<b> incompativeis</b>. Tenha absoluta certeza de ter salvo todo o seu progresso no seu CARTÃO DE MEMÓRIA antes de instalar esta atualização ou você perderá todo o seu progresso.</p> - + <h2>Settings Warning</h2><p>Installing this update will reset your program configuration. Please note that you will have to reconfigure your settings after this update.</p> <h2>Alerta sobre suas configurações</h2><p>Ao instalar esta atualização todas as suas configurações serão REDEFINIDAS para o padrão sendo assim, você terá de refazê-las novamente.</p> - + <h4>Installing this update will download %1 MB through your internet connection.</h4> <h4>Esta atualização irá baixar %1 MB.</h4> - + Downloading %1... Baixando %1... - + Failed to remove updater exe after update: %1 Falha ao remover o executável do atualizador após esta atualização: @@ -2465,22 +2465,22 @@ Mensagens não lidas: {} Cheats - + Gameshark Gameshark - + Manual Manual - + Automatic (Frame End) Automático (quadro final) - + %n game patches are active. OSD Message @@ -2489,7 +2489,7 @@ Mensagens não lidas: {} - + %n cheats are enabled. This may crash games. OSD Message @@ -2498,12 +2498,12 @@ Mensagens não lidas: {} - + No cheats/patches are found or enabled. Não há trapaças ou modificações para serem ativadas. - + Cheat '{}' applied. Trapaça '{}' aplicada. @@ -2908,7 +2908,7 @@ This warning will only be shown once. Controller - + @@ -2916,13 +2916,23 @@ This warning will only be shown once. Controle {} alterado para o modo analógico. - + Controller {} switched to digital mode. Controle {} alterado para o modo digital. + + + Controller {} switched to JogCon mode. + O controle {} mudou para o modo JogCon. + + + + Controller {} switched to Digital mode. + O controle {} mudou para o modo digital. + ControllerBindingWidget @@ -2974,12 +2984,28 @@ This warning will only be shown once. Nenhuma atribuição genérica foi gerada para o dispositivo '%1'. O controle pode não suportar o mapeamento automático gerado pelo emulador. - + + Axes Eixo - + + Large Motor + Motor maior + + + + Vibration + Vibração + + + + Small Motor + Motor menor + + + Buttons Botões @@ -3776,17 +3802,17 @@ This warning will only be shown once. ControllerCustomSettingsWidget - + Restore Default Settings Restaurar a configuração original - + Browse... Buscar... - + Select File Escolha o arquivo @@ -4047,33 +4073,33 @@ This warning will only be shown once. Definir... - + Not Configured Não configurado - - + + %1% %1% - + Set Frequency Definir frequência - + Frequency: Frequência: - + Macro will not repeat. O macro não se repetirá. - + Macro will toggle buttons every %1 frames. O macro alternará os botões a cada %1 quadros. @@ -4081,12 +4107,12 @@ This warning will only be shown once. ControllerMacroWidget - + Controller Port %1 Macros Porta do controle %1 Macros - + Macro %1 %2 Macro %1 @@ -4295,17 +4321,17 @@ Esta ação não poderá ser desfeita. NeGcon - + Analog Controller Controle analógico - + GunCon GunCon - + Not Connected Não conectado @@ -4328,6 +4354,11 @@ Esta ação não poderá ser desfeita. NeGcon (Rumble) NeGcon (vibração) + + + JogCon + + CoverDownloadDialog @@ -5190,37 +5221,37 @@ This file can be several gigabytes, so be aware of SSD wear. EmuThread - - - - - - + + + + + + Error Erro - + Failed to boot system: %1 Falha ao iniciar o sistema: %1 - + Failed to load state: %1 Falha ao carregar o estado: %1 - + No resume save state found. Salvamento rápido não encontrado. - + Memory Card Busy Cartão de memória em uso - + WARNING: Your game is still saving to the memory card. Continuing to %1 may IRREVERSIBLY DESTROY YOUR MEMORY CARD. We recommend resuming your game and waiting 5 seconds for it to finish saving. Do you want to %1 anyway? @@ -5229,60 +5260,60 @@ Do you want to %1 anyway? Deseja continuar para %1 mesmo assim? - + shut down encerrar - + reset reiniciar - + change disc mudar disco - + Failed to switch to subimage %1 Falha ao alternar para a subimagem %1 - - + + Failed to save state: %1 Falha ao salvar o estado: %1 - + Game: %1 (%2) Jogo: %1 (%2) - + Rich presence inactive or unsupported. Presença rica do Discord inativa ou não suportada. - + Game not loaded or no RetroAchievements available. Jogo não carregado ou sem conquistas disponíveis. - + %1x%2 %1x%2 - + Game: %1 FPS Jogo: %1 FPS - + Video: %1 FPS (%2%) Vídeo: %1 FPS (%2%) @@ -5703,328 +5734,328 @@ Deseja continuar para %1 mesmo assim? FullscreenUI - + - - + 1 Frame 1 quadro - + 10 Frames 10 quadros - + 100% [60 FPS (NTSC) / 50 FPS (PAL)] 100% [60 FPS (NTSC) / 50 FPS (PAL)] - + 1000% [600 FPS (NTSC) / 500 FPS (PAL)] 1000% [600 FPS (NTSC) / 500 FPS (PAL)] - + 10x 10x - + 10x (20x Speed) (x velocidade) 10x (20x velocidade) - + 11x 11x - + 125% [75 FPS (NTSC) / 62 FPS (PAL)] 125% [75 FPS (NTSC) / 62 FPS (PAL)] - + 12x 12x - + 13x 13x - + 14x 14x - + 150% [90 FPS (NTSC) / 75 FPS (PAL)] 150% [90 FPS (NTSC) / 75 FPS (PAL)] - + 15x 15x - + 16x 16x - + 175% [105 FPS (NTSC) / 87 FPS (PAL)] 175% [105 FPS (NTSC) / 87 FPS (PAL)] - + 1x 1x - + 2 Frames 2 Quadros - + 20% [12 FPS (NTSC) / 10 FPS (PAL)] - + 200% [120 FPS (NTSC) / 100 FPS (PAL)] - + 250% [150 FPS (NTSC) / 125 FPS (PAL)] - + 2x 2x - + 2x (Quad Speed) 2x (4x velocidade) - + 3 Frames 3 quadros - + 30% [18 FPS (NTSC) / 15 FPS (PAL)] - + 300% [180 FPS (NTSC) / 150 FPS (PAL)] - + 350% [210 FPS (NTSC) / 175 FPS (PAL)] - + 3x 3x - + 3x (6x Speed) 3x (6X velocidade) - + 3x (for 720p) 3x (720p) - + 4 Frames 4 Quadros - + 40% [24 FPS (NTSC) / 20 FPS (PAL)] - + 400% [240 FPS (NTSC) / 200 FPS (PAL)] - + 450% [270 FPS (NTSC) / 225 FPS (PAL)] - + 4x 4x - + 4x (8x Speed) 4x (8X velocidade) - + 5 Frames 5 Quadros - + 50% [30 FPS (NTSC) / 25 FPS (PAL)] - + 500% [300 FPS (NTSC) / 250 FPS (PAL)] - + 5x 5x - + 5x (10x Speed) 5x (10x velocidade) - + 5x (for 1080p) 5x (1080p) - + 6 Frames 6 Quadros - + 60% [36 FPS (NTSC) / 30 FPS (PAL)] - + 600% [360 FPS (NTSC) / 300 FPS (PAL)] - + 6x 6x - + 6x (12x Speed) 6x (12x velocidade) - + 6x (for 1440p) 6x (1440p) - + 7 Frames 7 Quadros - + 70% [42 FPS (NTSC) / 35 FPS (PAL)] - + 700% [420 FPS (NTSC) / 350 FPS (PAL)] - + 7x 7x - + 7x (14x Speed) 7x (14x velocidade) - + 8 Frames 8 Quadros - + 80% [48 FPS (NTSC) / 40 FPS (PAL)] - + 800% [480 FPS (NTSC) / 400 FPS (PAL)] - + 8x 8x - + 8x (16x Speed) 8x (16x velocidade) - + 9 Frames 9 Quadros - + 90% [54 FPS (NTSC) / 45 FPS (PAL)] - + 900% [540 FPS (NTSC) / 450 FPS (PAL)] - + 9x 9x - + 9x (18x Speed) 9x (18x velocidade) - + 9x (for 4K) 9x (4K) - + A resume save state created at %s was found. Do you want to load this save and continue? @@ -6033,92 +6064,92 @@ Do you want to load this save and continue? Deseja carregar e continuar a partir desde dado ? - + About DuckStation Sobre o emulador - + Account Conta - + Accurate Blending Combinação precisa - + Achievement Notifications Notificações de conquistas - + Achievements Conquistas - + Achievements Settings Configurações de conquistas - + Add Search Directory Adicionar diretório de busca - + Add Shader Adicionar shader - + Adds a new directory to the game search list. Adiciona um novo diretório à lista de pesquisa de jogos. - + Adds a new shader to the chain. Adiciona um novo shader à lista. - + Adds additional precision to PGXP data post-projection. May improve visuals in some games. Precisão adicional ao PGXP dados pós-projeção. Pode melhorar visualmente alguns jogos. - + Achievements are not enabled. As conquistas não estão ativadas. - + About Sobre - + Advanced Avançado - + Advanced Settings Configurações avançadas - + All Time: {} Todos os tempos: {} - + Allow Booting Without SBI File Inicializar jogos sem arquivo SBI - + Allows loading protected games without subchannel information. Permite carregar jogos protegidos sem informações de subcanal. @@ -6127,197 +6158,197 @@ Deseja carregar e continuar a partir desde dado ? Aplica técnicas modernas de dithering para suavizar ainda mais os gradientes quando a opção cor verdadeira está ativada. - + Apply Image Patches Aplicar modificações (PPF) - + Are you sure you want to clear the current post-processing chain? All configuration will be lost. Tem certeza de que deseja limpar a cadeia de pós-processamento atual? Toda a configuração será perdida. - + Aspect Ratio Proporção - + Attempts to detect one pixel high/wide lines that rely on non-upscaled rasterization behavior, filling in gaps introduced by upscaling. Tenta detectar linhas de um pixel de altura/largura que dependem do comportamento de rasterização sem aumento de escala, preenchendo as lacunas introduzidas pelo aumento de escala. - + Attempts to map the selected port to a chosen controller. Tenta mapear a porta selecionada para o controle escolhido. - + Audio Backend Opção de áudio - + Audio Control Controle de áudio - + Audio Settings Configurações de áudio - + Auto-Detect Detectar automaticamente - + Automatic Mapping Mapeamento automático - + Automatic based on window size Automático, baseado no tamanho da janela - + Automatic mapping completed for {}. Mapeamento automático concluído para {}. - + Automatic mapping failed for {}. Falha ao realizar o mapeamento automático para {}. - + Automatic mapping failed, no devices are available. Falha ao realizar o mapeamento automático, nenhum dispositivo está disponível. - + Automatically Resize Window Redimensionar janela automaticamente - + Automatically applies patches to disc images when they are present, currently only PPF is supported. Aplica automaticamente correções a imagens de disco quando existirem; atualmente, somente arquivos PPF são compatíveis. - + Automatically resizes the window to match the internal resolution. Redimensiona automaticamente a janela para corresponder à resolução interna. - + Automatically saves the emulator state when powering down or exiting. You can then resume directly from where you left off next time. Salva automaticamente o estado do emulador ao desligar ou sair do jogo. Você pode então continuar diretamente de onde parou na próxima vez. - + Automatically switches to fullscreen mode when the program is started. Muda para o modo tela cheia assim que um jogo é iniciado. - + Avoids calls to C++ code, significantly speeding up the recompiler. Evita invocações de código C++, acelerando significativamente o recompilador. - + BIOS Directory Diretório do BIOS - + BIOS Selection Seleção de BIOS - + BIOS Settings Configurações de BIOS - + BIOS for {} BIOS para {} - + BIOS to use when emulating {} consoles. BIOS usada na emulação de consoles {}. - + Back Voltar - + Back To Pause Menu Voltar ao menu Pausa - + Backend Settings Configurações de backend - + Behavior Comportamento - + Borderless Fullscreen Tela cheia sem bordas - + Buffer Size Tamanho do buffer - + Buttons Botões - + CD-ROM Emulation Emulação do CD-ROM - + CPU Emulation Emulação de CPU - + CPU Mode Modo CPU - + Cancel Cancelar - + Capture Captura - + Change Disc Mudar disco - + Changes the aspect ratio used to display the console's output to the screen. Altera a proporção de aspecto usada para exibir a saída do console na tela. @@ -6326,37 +6357,37 @@ Deseja carregar e continuar a partir desde dado ? Lista de trapaças - + Chooses the backend to use for rendering the console/game visuals. Escolhe o backend a ser usado para renderizar os visuais do console/jogo. - + Chooses the language used for UI elements. Escolhe o idioma usado para os elementos da interface do usuário. - + Clean Boot Inicialização padrão - + Clear Settings Limpar configurações - + Clear Shaders Limpar shaders - + Clears a shader from the chain. Limpa um shader da lista. - + Clears all settings set for this game. Limpa todas as configurações definidas para esse jogo. @@ -6365,57 +6396,57 @@ Deseja carregar e continuar a partir desde dado ? Limpa o bit de máscara/transparência nos despejos de gravação de VRAM. - + Close Fechar - + Close Game Fechar o jogo - + Close Menu Fechar menu - + Compatibility Rating Classificação de compatibilidade - + Compatibility: Compatibilidade: - + Configuration Configuração - + Confirm Power Off Confirmar ao fechar - + Console Settings Configurações do console - + Controller Port {} Porta de controle {} - + Controller Port {} Macros Porta de controle {} macros - + Controller Port {} Settings Portal de controle {} configurações @@ -6432,237 +6463,237 @@ Deseja carregar e continuar a partir desde dado ? Portal de controle {}{} configurações - + Controller Settings Configurações de controle - + Controller Type Tipo de controle - + Controller settings reset to default. As configurações do controle foram redefinidas para o padrão. - + Controls Controles - + Controls the volume of the audio played on the host when fast forwarding. Controla o volume do áudio quando o avanço rápido é usado. Caso não queira escutar o som do jogo muito rápido ou distorcido diminua o volume. Valores medidos em porcentagem. - + Controls the volume of the audio played on the host. Controla o volume do áudio do emulador. - + Copies the current global settings to this game. Copia as configurações globais atuais para este jogo. - + Copies the global controller configuration to this game. Copia a configuração do controle global para esse jogo. - + Copy Global Settings Copiar configurações globais - + Copy Settings Copiar configurações - + Cover Settings Configurações da capa - + Covers Directory Diretório de capas - + Create Criar - + Create Save State Backups Criar cópia de segurança de save states - + Crop Mode Modo de corte - + Culling Correction Correção de curvas - + Current Game Jogo atual - + Deadzone Zona morta - + Debugging Settings Configurações de depuração - + Default Padrão - + Default Boot Inicialização padrão - + Default View Exibição padrão - + Default: Disabled Padrão: desativado - + Default: Enabled Padrão: ativado - + Delete Save Apagar salvar - + Delete State Apagar estado salvo - + Desktop Mode Modo desktop - + Details Detalhes - + Details unavailable for game not scanned in game list. Detalhes indisponíveis para o jogo não verificado na lista de jogos. - + Determines how large the on-screen messages and monitor are. Determina o tamanho das mensagens na tela e do monitor. - + Determines how much button pressure is ignored before activating the macro. Determina o quanto a pressão do botão é ignorada antes de ativar a macro. - + Determines how much latency there is between the audio being picked up by the host API, and played through speakers. Determina a latência entre a captação do áudio pela API do host e sua reprodução pelos alto-falantes. - + Determines how much of the area typically not visible on a consumer TV set to crop/hide. Determina quanto da área normalmente não visível em um aparelho de TV deve ser cortada/oculta. - + Determines how much pressure is simulated when macro is active. Determina a quantidade de pressão será emulada quando a macro está ativa. - + Determines how the emulated CPU executes instructions. Determina como o CPU emulado executa suas instruções. - + Determines how the emulated console's output is upscaled or downscaled to your monitor's resolution. Determina como a imagem do console emulado é aumentada ou reduzida de acordo com a resolução do seu monitor. - + Determines quality of audio when not running at 100% speed. Determina a qualidade do áudio quando não está sendo executado a 100% da velocidade. - + Determines that field that the game list will be sorted by. Determina como a lista de jogos será classificada. - + Determines the amount of audio buffered before being pulled by the host API. Determina a quantidade de áudio armazenada antes de ser extraída pela API do host. - + Determines the emulated hardware type. Determina o tipo de hardware emulado. - + Determines the format that screenshots will be saved/compressed with. Determina o formato em que as capturas de tela serão salvas/comprimidas. - + Determines the frequency at which the macro will toggle the buttons on and off (aka auto fire). Determina a frequência com que a macro ativará e desativará os botões (também conhecida como disparo automático). - + Determines the margin between the edge of the screen and on-screen messages. Determina a margem entre a borda da tela e as mensagens na tela. - + Determines the position on the screen when black borders must be added. Determina a posição na tela quando as bordas pretas devem ser adicionadas. - + Determines the size of screenshots created by DuckStation. Determina o tamanho das capturas de tela criadas pelo DuckStation. - + Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed. Mostra uma janela de confirmação ao tentar fechar o emulador ou jogo quando atalho é pressionado. - + Device Settings Configurações do dispositivo @@ -6675,27 +6706,27 @@ Deseja carregar e continuar a partir desde dado ? Desativar entrelaçamento - + Disable Mailbox Presentation Desativar apresentação de entrada - + Disable Subdirectory Scanning Desativar a verificação de subdiretórios - + Disable on 2D Polygons Desativa PGXP em jogos com polígonos 2D - + Disabled Desativado - + Disables dithering and uses the full 8 bits per channel of color information. Desativa o dithering e usa 8 bits completos por canal de informações de cor. @@ -6704,57 +6735,57 @@ Deseja carregar e continuar a partir desde dado ? Desativa a renderização entrelaçada e a exibição na GPU. Alguns jogos podem ser renderizados em 480p, mas outros não funcionarão. - + Discord Server Servidor no Discord - + Display Settings Opções de vídeo - + Displays popup messages on events such as achievement unlocks and leaderboard submissions. Exibe mensagens de alerta em eventos como desbloqueios de conquistas e envios de placar. - + Displays popup messages when starting, submitting, or failing a leaderboard challenge. Exibe mensagens de alerta ao iniciar, enviar ou na falha em um desafio da tabela de classificação de líderes. - + Double-Click Toggles Fullscreen Duplo clique para alternar para tela cheia - + Download Covers Baixar capas - + Downloads covers from a user-specified URL template. Baixa capas de um modelo de URL especificado pelo usuário. - + Downsamples the rendered image prior to displaying it. Can improve overall image quality in mixed 2D/3D games. Reduz a amostragem da imagem renderizada antes de exibi-la. Pode melhorar a qualidade geral da imagem em jogos 2D/3D mistos. - + Downsampling Suavização - + Downsampling Display Scale Escala de suavização da imagem - + Duck icon by icons8 (https://icons8.com/icon/74847/platforms.undefined.short-title) Ícone do pato por : icons8 (https://icons8.com/icon/74847/platforms.undefined.short-title) @@ -6767,637 +6798,637 @@ Deseja carregar e continuar a partir desde dado ? Despejar texturas - + Emulation Settings Configurações de emulação - + Emulation Speed Velocidade da emulação - + Enable 8MB RAM Ativar 8 MB de RAM - + Enable Achievements Ativar conquistas - + Enable Discord Presence Ativar presença rica do Discord - + Enable Fast Boot Ativar inicialização rápida - + Enable In-Game Overlays Ativar sobreposições no jogo - + Enable Overclocking Ativar overclocking - + Enable Post Processing Ativar pós-processamento - + Enable Recompiler Block Linking Ativar conexão de bloco recompilador - + Enable Recompiler ICache Ativar recompilador ICache - + Enable Recompiler Memory Exceptions Habilitar exceções de memória - + Enable Region Check Ativar verificaçao de região - + Enable Rewinding Ativar retrocesso - + Enable SDL Input Source Ativar fonte de entrada SDL - + Enable Subdirectory Scanning Ativar a verificação de subdiretórios - + Enable TTY Logging Habilitar registro TTY - + Enable Texture Cache Ativar armazenamento de textura - + Enable Texture Dumping Ativar o despejo de textura - + Enable Texture Replacements Ativar substituições de textura - + Enable VRAM Write Dumping Ativar o despejo de gravação da VRAM - + Enable/Disable the Player LED on DualSense controllers. Ativar/desativar o LED indicador de jogador nos controladores DualSense. - + Enables caching of guest textures, required for texture replacement. Permite o armazenamento em lote de texturas, necessário para a substituição de texturas. - + Enables dumping of textures to image files, which can be replaced. Not compatible with all games. Permite o despejo de texturas em arquivos de imagem, que podem ser substituídos. Não é compatível com todos os jogos. - + Enables loading of cheats for this game from DuckStation's database. Ativa o carregamento de trapaças para este jogo a partir do banco de dados do DuckStation. - + Enables loading of replacement textures. Not compatible with all games. Permite o carregamento de texturas de personalizadas. Não é compatível com todos os jogos. - + Enables smooth scrolling of menus in Big Picture UI. Ativa rolagem suave dos menus no modo tela cheia. - + Enables the cheats that are selected below. Ativa as trapaças selecionadas abaixo. - + Enables the older, less accurate MDEC decoding routines. May be required for old replacement backgrounds to match/load. Ativa as rotinas de decodificação MDEC mais antigas e menos precisas. Pode ser necessário para que os fundos de substituição antigos sejam compatíveis/carregados. - + Encore Mode Modo de repetição - + Ensures every frame generated is displayed for optimal pacing. Enable for variable refresh displays, such as GSync/FreeSync. Disable if you are having speed or sound issues. Garante que todos os quadros gerados sejam exibidos para otimizar o ritmo da emulação. Ative esta opção caso tenha monitores com função de atualização variável, como GSync/FreeSync. Desative se estiver tendo problemas de velocidade ou de som. - + Enter Value Insira um valor - + Exit DuckStation Sair do Duckstation - + Exits Big Picture mode, returning to the desktop interface. Sai do modo Tela cheia, retornando à interface de área de trabalho. - + FMV Chroma Smoothing Suavização de croma do FMV - + Failed to load shader {}. It may be invalid. Error was: Falha ao carregar o shader {}. Erro: - + Fast Forward Boot Inicialização rápida - + Fast forwards through the early loading process when fast booting, saving time. Results may vary between games. Avança rapidamente durante o processo de carregamento inicial, economizando tempo. Os resultados podem variar entre os jogos. - + File Size Tamanho do arquivo - + File Size: %.2f MB Tamanho do arquivo: %.2f MB - + Force 4:3 For FMVs Forçar 4:4 para FMVs - + Force Video Timing Forçar modo NTSC - + Frequency Frequência - + Game Patches Modificações de jogos - + Genre: %.*s Genêro: %.*s - + Graphics Settings Configurações gráficas - + Internal Resolution Resolução interna - + Language: Idioma: - + Launch Options Opções de inicialização - + Launch a game from a file, disc, or starts the console without any disc inserted. Inicie um jogo a partir de um arquivo, disco ou inicie o console sem nenhum disco inserido. - + Line Detection Detecção de linha - + Load Database Cheats Carregar banco de dados de trapaças - + Load Global State Carregar estado global - + Navigate Navegar - + No cheats are available for this game. Não há trapaças disponíveis para este jogo. - + No patches are available for this game. Não há modificações disponíveis para este jogo. - + PGXP Depth Buffer PGXP Buffer de profundidade - + Parent Directory Diretório raiz - + Press To Toggle Pressione para alternar - + Pressure Pressão - + Resume Last Session Retomar a última sessão - + Return To Game Retornar ao jogo - + Return to desktop mode, or exit the application. Retornar ao modo de área de trabalho ou sair do aplicativo. - + Return to the previous menu. Retornar ao menu anterior. - + Rewind for {0} frames, lasting {1:.2f} seconds will require up to {2} MB of RAM and {3} MB of VRAM. Rebobinar para {0} quadros, com duração de {1:.2f} segundos, exigirá até {2} MB de RAM e {3} MB de VRAM. - + Rewind is disabled because runahead is enabled. Runahead will significantly increase system requirements. O retrocesso não está ativado porque o pulo de quadros está ativado; Tenha em mente que ativando esta função poderá aumentar muito os requisitos do sistema. (Uso de RAM e V-RAM). - + Rewind is not enabled. Please note that enabling rewind may significantly increase system requirements. Retrocesso não está ativado. Tenha em mente que ativando esta função poderá aumentar muito os requisitos do sistema. - + Rounds texture coordinates instead of flooring when upscaling. Can fix misaligned textures in some games, but break others, and is incompatible with texture filtering. Arredonda as coordenadas da textura em vez de arredondar a parte inferior ao subir a resolução. Pode corrigir texturas desalinhadas em alguns jogos, mas quebrar outras, é incompatível com a filtragem de texturas. - + Screen Margins Margens da tela - + Screen Position Posição da tela - + Screenshot Format Formato de captura de tela - + Screenshot Quality Qualidade da captura de tela - + Screenshot Size Tamanho da captura de tela - + Select Select - + Select Disc Drive Selecionar unidade de disco - + Select Game Selecionar jogo - + Select State Selecionar dado salvo - + Selects the quality at which screenshots will be compressed. Seleciona a qualidade na qual as capturas de tela serão compactadas. - + Shows the host's CPU usage of each system thread in the top-right corner of the display. Mostra o uso do CPU sequencialmente por sistema no topo superior direito da tela. - + Smooths out blockyness between colour transitions in 24-bit content, usually FMVs. Suaviza a imagem entre as transições de cores em conteúdo de 24 bits, geralmente FMVs. - + Smooths out the blockiness of magnified textures on 2D objects. Suaviza artefatos de texturas ampliadas em objetos 2D. - + Sprite Texture Filtering Filtro de textura de sprites - + Start Disc Iniciar disco - + Start Game Iniciar jogo - + Start a game from a disc in your PC's DVD drive. Inicie um jogo a partir de um disco na unidade de DVD do seu PC. - + Synchronizes presentation of the console's frames to the host. GSync/FreeSync users should enable Optimal Frame Pacing instead. Sincroniza a apresentação dos quadros do console para o monitor. Quem faz o uso de GSync/FreeSync deve ativar o Otimização de quadros. - + Textures Directory Diretório de texturas - + The texture cache is currently experimental, and may cause rendering errors in some games. O armazenamento de textura é atualmente experimental e pode causar erros de renderização em alguns jogos. - + This game has no achievements. Este jogo não possui conquistas. - + This game has no leaderboards. Este jogo não possui placares de líderes. - + Timing out in {:.0f} seconds... Tempo limite em {:.0f} segundos... - + Toggle Fullscreen Alternar para o modo tela cheia - + Toggles the macro when the button is pressed, instead of held. Alterna a macro quando o botão é pressionado, em vez de pressionado. - + Trigger Gatilho - + UI Language Idioma da interface - + Uncompressed Size Tamanho sem compressão - + Uncompressed Size: %.2f MB Tamanho sem compressão: %.2f MB - + Ungrouped Não agrupado - + Unknown File Size Tamanho de arquivo desconhecido - + Use Old MDEC Routines Usar rotinas MDEC antigas - + Use Single Card For Multi-Disc Games Use cartão único para jogos com vários discos - + Vertex Cache Cache de vértices - + Vertical Sync (VSync) Sincronização vertical (Vsync) - + WARNING: Activating cheats can cause unpredictable behavior, crashing, soft-locks, or broken saved games. AVISO: a ativação de modificações de jogos pode causar comportamentos imprevisíveis, travamentos, corrupção de dados de jogos já salvos. - + WARNING: Activating game patches can cause unpredictable behavior, crashing, soft-locks, or broken saved games. AVISO: a ativação de modificações de jogos pode causar comportamentos imprevisíveis, travamentos, corrupção de dados de jogos já salvos. - + Wireframe Rendering Modo de renderização Wireframe - + Enable VRAM Write Texture Replacement Ativar texturas personalizadas - + %.1f ms - + %.2f Seconds %.2f Segundos - + %d Frames %d Quadros - + %d sectors %d setores - + Adjusts the emulation speed so the console's refresh rate matches the host when VSync is enabled. Ajusta a velocidade de emulação para que a taxa de atualização do console corresponda à do monitor do usuário quando o VSync estiver ativado. - + An error occurred while deleting empty game settings: {} Ocorreu um erro ao excluir configurações por jogo vazias: {} - + An error occurred while saving game settings: {} Ocorreu um erro ao salvar as configurações por jogo: {} - + Automatically loads and applies cheats on game start. Cheats can break games and saves. Carrega e aplica automaticamente as trapaças no início do jogo. Trapaças podem quebrar jogos e salvamentos. - + Change Page Mudar de página - + Change Selection Alterar seleção - + Change View Alterar visualização - + Changes settings for the application. Altera as configurações do aplicativo. - + Cheats Trapaças - + Completely exits the application, returning you to your desktop. Sai completamente do aplicativo, retornando-o à área de trabalho. - + Contributor List Lista de colaboradores - + Could not find any CD/DVD-ROM devices. Please ensure you have a drive connected and sufficient permissions to access it. Não foi possível encontrar nenhum dispositivo de CD/DVD-ROM. Verifique se há uma unidade conectada e se há permissões suficientes para acessá-la. - + Create New... Criar novo... - + Deinterlacing Mode Modo de desentrelaçamento - + Depth Clear Threshold Limite de compensação de profundidade @@ -7406,62 +7437,62 @@ Error was: Define como o áudio é expandido de estéreo para surround nos jogos compatíveis. - + Determines the rotation of the simulated TV screen. Determina a rotação da tela de TV emulada. - + Determines which algorithm is used to convert interlaced frames to progressive for display on your system. Determina qual algoritmo é usado para converter quadros entrelaçados em progressivos para exibição em seu sistema. - + Disc {} | {} Disco {} | {} - + DuckStation is a free simulator/emulator of the Sony PlayStation(TM) console, focusing on playability, speed, and long-term maintainability. O Duckstation é um emulador de código aberto do console Playstation 1 focado na velocidade, jogabilidade e suporte a longo prazo. - + Dump Replaced Textures Despejar texturas personalizadas - + Dumps textures that have replacements already loaded. Despeja texturas que já foram carregadas. - + Enable Cheats Ativar trapaças - + Enable XInput Input Source Ativar fonte de entrada XInput - + Enable debugging when supported by the host's renderer API. Only for developer use. Habilita a depuração quando suportada pela API do renderizador do host. somente para uso do desenvolvedor. - + Enables alignment and bus exceptions. Not needed for any known games. Ativa o alinhamento e as exceções de barramento. Não é necessário para nenhum jogo conhecido. - + Enables an additional 6MB of RAM to obtain a total of 2+6 = 8MB, usually present on dev consoles. Permite um adicional de 6 MB de RAM para obter um total de 2+6 = 8 MB, geralmente presente em consoles de desenvolvimento. - + Enables an additional three controller slots on each port. Not supported in all games. Permite três compartimentos de controle adicionais em cada porta. Não é compatível com todos os jogos. @@ -7470,37 +7501,37 @@ Error was: Permite um ritmo de quadros mais preciso ao custo da duração da bateria. - + Enables the replacement of background textures in supported games. Permite a substituição de texturas de fundo em jogos compatíveis. - + Enter the name of the input profile you wish to create. Digite o nome do perfil de entrada que você deseja criar. - + Error Erro - + Execution Mode Modo de execução - + Exit Sair - + Exit And Save State Sair e salvar estado - + Exit Without Saving Sair sem salvar @@ -7509,47 +7540,47 @@ Error was: Modo de expansão - + Failed to copy text to clipboard. Falha ao copiar o texto para a área de transferência. - + Failed to delete save state. Falha ao excluir o estado salvo. - + Failed to delete {}. Falha ao excluir {}. - + Failed to load '{}'. Falha ao carregar '{}'. - + Failed to save input profile '{}'. Falha ao salvar o perfil de entrada '{}'. - + Fast Boot Início rápido - + Fast Forward Speed Velocidade do avanço rápido - + Fast Forward Volume Volume do avanço rápido - + File Title Título do jogo (na pasta) @@ -7562,132 +7593,132 @@ Error was: Força os jogos Europeus a serem executados como se fossem NTSC, ou seja, 60hz. Alguns jogos Europeus serão executados em suas velocidades "normais", enquanto outros serão interrompidos. - + Forces a full rescan of all games previously identified. Força uma nova varredura completa de todos os jogos identificados anteriormente. - + Forces blending to be done in the shader at 16-bit precision, when not using true color. Non-trivial performance impact, and unnecessary for most games. Força a mistura no sombreador com precisão de 16 bits, quando não estiver usando cores reais. Impacto não comum no desempenho e desnecessário para a maioria dos jogos. - + Forces the use of FIFO over Mailbox presentation, i.e. double buffering instead of triple buffering. Usually results in worse frame pacing. Força o uso do FIFO em vez de apresentação de entrada, ou seja, buffer duplo em vez de buffer triplo. Geralmente resulta em um ritmo de quadros pior. - + Forcibly mutes both CD-DA and XA audio from the CD-ROM. Can be used to disable background music in some games. Opção útil para silenciar tanto CDs de áudio quanto sons de fundo em alguns jogos. - + Frame Time Buffer Armazenamento temporal de quadro - + From File... Do arquivo... - + Fullscreen Resolution Resolução de tela cheia - + GPU Adapter Adaptador de GPU - + GPU Renderer Renderizador de GPU - + GPU adapter will be applied after restarting. O adaptador de GPU será usado após a reinicialização. - + Game Grid Grade de jogos - + Game List Lista de jogos - + Game List Settings Configurar lista de jogos - + Game Properties Propriedades do jogo - + Game Quick Save Salvamento rápido do jogo - + Game Slot {0}##game_slot_{0} Compartimento de jogo {0}##game_slot_{0} - + Game compatibility rating copied to clipboard. Classificação de compatibilidade do jogo copiada para a área de transferência. - + Game not loaded or no RetroAchievements available. Jogo não carregado ou sem conquistas disponíveis. - + Game path copied to clipboard. Caminho do jogo copiado para a área de transferência. - + Game region copied to clipboard. Região do jogo copiada para a área de transferência. - + Game serial copied to clipboard. Número de série do jogo copiada para a área de transferência. - + Game settings have been cleared for '{}'. As configurações do jogo foram apagadas para '{}'. - + Game settings initialized with global settings for '{}'. Configurações do jogo inicializadas com configurações globais para '{}'. - + Game title copied to clipboard. Título do jogo copiado para a área de transferência. - + Game type copied to clipboard. Tipo de jogo copiado para a área de transferência. - + Game: {} ({}) Jogo: {} ({}) @@ -7696,67 +7727,67 @@ Error was: Genêro: %s - + Geometry Tolerance Tolerância de geometria - + GitHub Repository Repositório no Github - + Global Slot {0} - {1}##global_slot_{0} Compartimento de jogo {0} - {1}##global_slot_{0} - + Global Slot {0}##global_slot_{0} Compartimento de jogo {0}##global_slot_{0} - + Hardcore Mode Modo hardcore - + Hardcore mode will be enabled on next game restart. O modo hardcore será ativado na próxima reinicialização do jogo. - + Hide Cursor In Fullscreen Esconder cursor em tela cheia - + Hides the mouse pointer/cursor when the emulator is in fullscreen mode. Oculta o cursor do mouse quando o emulador está no modo de tela cheia. - + Hotkey Settings Configurações de atalhos - + How many saves will be kept for rewinding. Higher values have greater memory requirements. Quantas gravações serão mantidas para retrocesso. Valores mais altos exigem mais memória. - + How often a rewind state will be created. Higher frequencies have greater system requirements. Com que frequência um estado de retrocesso será criado. Frequências mais altas têm requisitos de sistema maiores. - + Identifies any new files added to the game directories. Identifica todos os novos arquivos adicionados aos diretórios do jogo. - + If not enabled, the current post processing chain will be ignored. Se não estiver ativado, a cadeia de pós-processamento atual será ignorada. @@ -7765,202 +7796,202 @@ Error was: Aumentar resolução em tempo real - + Increases the field of view from 4:3 to the chosen display aspect ratio in 3D games. Aumenta o campo de visão de 4:3 para a proporção de tela escolhida em jogos 3D. - + Increases the precision of polygon culling, reducing the number of holes in geometry. Aumenta a precisão das curvas nos polígonos, reduzindo o número de buracos na geometria do mesmo. Requer a Correção geometrica ativada. - + Infinite/Instantaneous Infinita/Instantânea - + Inhibit Screensaver Inibir proteção de tela - + Input Sources Fontes de entrada - + Input profile '{}' loaded. Perfil de entrada '{}' carregado. - + Input profile '{}' saved. Perfil de entrada '{}' salvo. - + Integration Integração - + Interface Settings Configurações de interface - + Last Played Jogou pela última vez - + Last Played: %s Jogou pela última vez - + Latency Control Controle de latência - + Launch a game by selecting a file/disc image. Inicie um jogo selecionando uma imagem de arquivo/disco. - + Launch a game from images scanned from your game directories. Inicie um jogo a partir de arquivos de imagens (.chd .iso) armazenados nos diretórios de jogos. - + Leaderboard Notifications Notificações do placar de líderes - + Leaderboards Classificações - + Leaderboards are not enabled. Os placares não estão ativadas. - + List Settings Configurações de lista - + Load Devices From Save States Carregar a partir do estado salvo - + Load Profile Carregar perfil - + Load Resume State Carregar estado salvo - + Load State Carregar estado - + Loads all replacement texture to RAM, reducing stuttering at runtime. Carrega todas as texturas de substituição na RAM, reduzindo engasgos no tempo de execução. - + Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay. Carrega o jogo na memória RAM. Útil para evitar certas instabilidades durante o jogo. - + Log Level Nivel do registro - + Log To Debug Console Carregar para console de depuração - + Log To File Carregar para arquivo - + Log To System Console Carregar para console - + Logging Registro - + Logging Settings Configurações de registro - + Login Entrar - + Login token generated on {} Token de conexão gerado em {} - + Logout Sair - + Logs BIOS calls to printf(). Not all games contain debugging messages. Registra chamadas do BIOS no console. Nem todos os jogos contêm mensagens de depuração. - + Logs in to RetroAchievements. Conecta-se ao RetroAchievements. - + Logs messages to duckstation.log in the user directory. Cria um arquivo de registro (duckstation.log) no diretório do usuário. - + Logs messages to the console window. Mostra mensagens na janela de console. - + Logs messages to the debug console where supported. Mostra mensagens no console de depuração quando suportado. - + Logs out of RetroAchievements. Desconecta-se do RetroAchievements. - + Macro Button {} Botões de macro {} @@ -7981,97 +8012,97 @@ Error was: Gatilho do macro {} - + Makes games run closer to their console framerate, at a small cost to performance. Faz com que os jogos sejam executados mais próximos da taxa de quadros do console, com um pequeno custo para o desempenho. - + Memory Card Busy Cartão de memória em uso - + Memory Card Directory Diretório de cartão de memória - + Memory Card Port {} Porta de cartão de memória {} - + Memory Card Settings Cartões de memória - + Memory Card {} Type Tipo de cartão de memória {} - + Merge Multi-Disc Games Mesclar jogos com múltiplos discos - + Merges multi-disc games into one item in the game list. Mescla jogos com vários discos em um único item na lista de jogos. - + Minimal Output Latency Latência de saída mínima - + Move Down Para baixo - + Move Up Para cima - + Moves this shader higher in the chain, applying it earlier. Move shader para um nível mais alto na lista, aplicando-o antes dos demais. - + Moves this shader lower in the chain, applying it later. Move shader para um nível mais abaixo na lista, aplicando-o depois dos demais. - + Multitap Multitap - + Multitap Mode Modo multitap - + Mute All Sound Silenciar tudo - + Mute CD Audio Silenciar áudio CDs - + No Binding Sem atribuição - + No Game Selected Nenhum jogo selecionado @@ -8080,227 +8111,227 @@ Error was: Não foram encontradas trapaças para {}. - + No input profiles available. Não há perfis de entrada disponíveis. - + No resume save state found. Salvamento rápido não encontrado. - + No save present in this slot. Não há salvamento presente nesse compartimento. - + No save states found. Não foram encontrados estados salvos. - + No, resume the game. Não, retomar ao jogo. - + None (Double Speed) Nenhuma - + None (Normal Speed) Nenhuma (velocidade normal) - + Not Logged In Não conectado - + Not Scanning Subdirectories Não está verificando subdiretórios - + OK - + OSD Scale Tamanho das mensagens em tela - + On-Screen Display Mensagens em tela - + Open Containing Directory Abrir diretório de conteúdo - + Open in File Browser Abrir no navegador de arquivos - + Operations Operações - + Optimal Frame Pacing Otimização de quadros - + Options Opções - + Output Latency Latência de saída - + Output Volume Volume de saída - + Overclocking Percentage Porcentagem de sobrecarga do CPU - + Overlays or replaces normal triangle drawing with a wireframe/line view. Sobrepõe os gráficos do jogo por linhas triangulares para visualização dos gráficos em forma de esboço. - + PGXP (Precision Geometry Transform Pipeline) PGXP (correção geométrica) - + PGXP Geometry Correction PGXP correção geométrica - + Patches Modificações - + Patches the BIOS to skip the boot animation. Safe to enable. Modifica o BIOS para ignorar a animação de inicialização. É seguro ativar. - + Path Caminho - + Pause On Controller Disconnection Pausar na desconexão do controle - + Pause On Focus Loss Pausar quando inativo - + Pause On Start Pausar ao iniciar - + Pauses the emulator when a controller with bindings is disconnected. Pausa o emulador quando um controle já configurado é desconectado. - + Pauses the emulator when a game is started. Pausa a emulação quando um jogo é iniciado. - + Pauses the emulator when you minimize the window or switch to another application, and unpauses when you switch back. Pausa o emulador quando a janela é minimizada ou na mudança de foco para outro aplicativo aberto, e retoma quando volta ao foco. - + Per-Game Configuration Configuração por jogo - + Per-game controller configuration initialized with global settings. Configuração do controle por jogo carregada com as configurações globais. - + Performance enhancement - jumps directly between blocks instead of returning to the dispatcher. Aprimoramento do desempenho - salta diretamente entre blocos antes de renderiza-los na tela. - + Perspective Correct Colors Perspectiva de correção de cores - + Perspective Correct Textures Perspectiva de correção de texturas - + Plays sound effects for events such as achievement unlocks and leaderboard submissions. Reproduz efeitos sonoros para eventos como desbloqueios de conquistas e envios de placar. - + Port {} Controller Type Porta do controle do tipo{} - + Post-Processing Settings Ajustes de pós-processamento - + Post-processing chain cleared. Lista de pós-processamento limpa. - + Post-processing shaders reloaded. Texturas de pós-processamento recarregadas. - + Preload Images to RAM Carregar jogo para RAM - + Preload Replacement Textures Pré-carregar texturas personalizadas - + Writes backgrounds that can be replaced to the dump directory. Grava os planos de fundo que podem ser substituídos no diretório de despejo. @@ -8309,387 +8340,387 @@ Error was: Apresenta quadros em segundo plano quando o avanço rápido ou vsync está desativado. - + Preserve Projection Precision Preservar precisão e projeção - + Prevents the emulator from producing any audible sound. Silencia totalmente o emulador. - + Prevents the screen saver from activating and the host from sleeping while emulation is running. Evita que a proteção de tela seja ativada enquanto a emulação está em execução. - + Provides vibration and LED control support over Bluetooth. Fornece vibração e suporte de controle de LED por Bluetooth. - + Push a controller button or axis now. Pressione um botão ou eixo do controle agora. - + Quick Save Salvamento rápido - + RAIntegration is being used instead of the built-in achievements implementation. A integração com o Retroachievements está sendo usada em vez da implementação de conquistas incorporadas. - + Read Speedup Velocidade de leitura - + Readahead Sectors Setores de readaptação - + Recompiler Fast Memory Access Acesso rápido à memória do recompilador - + Reduce Input Latency Redução da latência de entrada - + Reduces "wobbly" polygons by attempting to preserve the fractional component through memory transfers. Reduz polígonos "trepidantes", tentando preservar o componente por meio de transferências de memória. - + Reduces hitches in emulation by reading/decompressing CD data asynchronously on a worker thread. Reduz engasgos na emulação lendo / descomprimindo os arquivos da mídia de forma assincrona. - + Reduces input latency by delaying the start of frame until closer to the presentation time. Reduz a latência de entrada atrasando o início do quadro o mais próximo ao tempo de apresentação. - + Reduces polygon Z-fighting through depth testing. Low compatibility with games. Reduz o conflito de polígonos Z por meio de testes de profundidade. Tem baixa compatibilidade com jogos. - + Reduces the size of save states by compressing the data before saving. Reduz o tamanho dos estados salvos comprimindo os dados antes de salvá-los. - + Region Região - + Region: Região: - + Release Date: %s Data de lançamento: %s - + Reload Shaders Recarregar shaders - + Reloads the shaders from disk, applying any changes. Recarrega os shaders do disco, aplicando todas as alterações. - + Remove From Chain Remover da lista - + Remove From List Remover da lista - + Removed stage {} ({}). Removido do estágio {} ({}). - + Removes this shader from the chain. Remove esse shader da lista. - + Renames existing save states when saving to a backup file. Renomeia os estados salvos existentes ao salvar em um arquivo de backup. - + Rendering Renderizador - + Replaces these settings with a previously saved input profile. Substitui essas configurações por um perfil de entrada salvo anteriormente. - + Rescan All Games Examinar tudo - + Reset Memory Card Directory Redefinir diretório do cartão de memória - + Reset Play Time Redefinir tempo de jogo - + Reset Settings Redefinir configurações - + Reset System Reiniciar o sistema - + Resets all configuration to defaults (including bindings). Redefine todas as configurações para os padrões (inclusive as atribuições). - + Resets memory card directory to default (user directory). Redefine o diretório do cartão de memória para o padrão (diretório do usuário). - + Resolution change will be applied after restarting. A alteração da resolução será aplicada após a reinicialização. - + Restores the state of the system prior to the last state loaded. Restaura o estado do sistema antes do último estado carregado. - + Resume Game Continuar jogo - + Reverses the game list sort order from the default (usually ascending to descending). Inverte a ordem de classificação da lista de jogos do padrão (geralmente crescente para decrescente). - + Rewind Save Frequency Frequência do retrocesso - + Rewind Save Slots Retrocede espaços de salvamento - + Rich presence inactive or unsupported. Presença rica do Discord inativa ou não suportada. - + Round Upscaled Texture Coordinates Coordenadas de textura ampliada - + Runahead Avançar - + Runahead/Rewind Retroceder/Avançar - + Runs the software renderer in parallel for VRAM readbacks. On some systems, this may result in greater performance. Executa o renderizador de software em paralelo para leituras de VRAM. Em alguns sistemas, isso pode resultar em melhor desempenho. - + SDL DualSense Player LED LED indicador de jogador Dualsense - + SDL DualShock 4 / DualSense Enhanced Mode SDL DualShock 4 / Modo aprimorado DualSense - + Safe Mode Modo seguro - + Save Profile Salvar perfil - + Save Screenshot Salvar captura de tela - + Save State Salvar estado - + Save State Compression Compressão de dados de estado salvo - + Save State On Exit Salvar ao fechar - + Saved {:%c} Salvo {:%c} - + Saves state periodically so you can rewind any mistakes while playing. Salva o estado periodicamente para que você possa retroceder qualquer erro durante a reprodução. - + Scaled Dithering Escala do pontilhado (dithering) - + Scales internal VRAM resolution by the specified multiplier. Some games require 1x VRAM resolution. Dimensiona a resolução interna da VRAM de acordo com o multiplicador especificado. Alguns jogos exigem resolução de 1x na VRAM. - + Scales the dithering pattern with the internal rendering resolution, making it less noticeable. Usually safe to enable. Dimensiona o padrão de pontilhamento (dithering) com a resolução de renderização interna, tornando-o menos perceptível. Normalmente, é seguro ativá-lo. - + Scaling Dimensionamento - + Scan For New Games Examinar por novos jogos - + Scanning Subdirectories Verificação de subdiretórios - + Screen Rotation Rotação da tela - + Search Directories Pesquisar diretórios - + Seek Speedup Velocidade de busca - + Select Device Selecionar dispositivo - + Select Disc Escolher disco - + Select Disc Image Escolha uma imagem de disco - + Select Macro {} Binds Selecionar macro {} atribuições - + Selects the GPU to use for rendering. Seleciona a GPU a ser usada na renderização. - + Selects the percentage of the normal clock speed the emulated hardware will run at. Escolhe a porcentagem de velocidade na qual o console emulado será executado. - + Selects the resolution scale that will be applied to the final image. 1x will downsample to the original console resolution. Escolha a escala de resolução que será aplicada à imagem final. 1x reduzirá a resolução original do console. - + Selects the resolution to use in fullscreen modes. Seleciona a resolução a ser usada nos modos de tela cheia. - + Selects the view that the game list will open to. Seleciona o modo de exibição na qual a lista de jogos será aberta. - + Serial Serial - + Session: {} Sessão: {} - + Set Input Binding Definir atribuição de entrada @@ -8698,177 +8729,177 @@ Error was: Definir canal alfa de despejo de gravação VRAM - + Sets a threshold for discarding precise values when exceeded. May help with glitches in some games. Define um limite para descartar valores quando excedido. Pode ajudar com falhas em alguns jogos. - + Sets a threshold for discarding the emulated depth buffer. May help in some games. Define um limite para descartar o buffer de profundidade emulado. Pode ajudar em alguns jogos. - + Sets the fast forward speed. It is not guaranteed that this speed will be reached on all systems. Define a velocidade de avanço rápido. Não é garantido que essa velocidade seja alcançada em todos os sistemas. - + Sets the target emulation speed. It is not guaranteed that this speed will be reached on all systems. Define a velocidade de emulação. Não é garantido que essa velocidade seja atingida em todos os sistemas. - + Sets the turbo speed. It is not guaranteed that this speed will be reached on all systems. Define a velocidade do turbo. Não é garantido que essa velocidade seja atingida em todos os sistemas. - + Sets the verbosity of messages logged. Higher levels will log more messages. Configura o nível detalhado de mensagens que serão armazenadas. Níveis mais altos terão mensagens maiores. - + Sets which sort of memory card image will be used for slot {}. Define o tipo de imagem do cartão de memória a ser usado no compartimento {}. - + Setting {} binding {}. Configurando {} atribuição {}. - + Settings Configurações - + Settings and Operations Configurações e operações - + Shader {} added as stage {}. Shader {} adicionado {}. - + Shared Card Name Nome do cartão compartilhado - + Show CPU Usage Mostrar uso do CPU - + Show Controller Input Mostrar controles na tela - + Show Enhancement Settings Mostrar configurações de aprimoramento - + Show FPS Mostrar FPS - + Show Frame Times Mostrar tempos por quadro - + Show GPU Statistics Mostrar estatísticas da GPU - + Show GPU Usage Mostrar uso da GPU - + Show Latency Statistics Exibir estatísticas de latência - + Show OSD Messages Mostrar mensagens na tela - + Show Resolution Mostrar resolução - + Show Speed Mostrar Velocidade - + Show Status Indicators Mostrar indicadores de situação - + Shows a visual history of frame times in the upper-left corner of the display. Mostra o histórico dos tempos de quadro no canto superior esquerdo da tela. - + Shows enhancement settings in the bottom-right corner of the screen. Mostra as configurações de aprimoramento no canto inferior direito da tela. - + Shows icons in the lower-right corner of the screen when a challenge/primed achievement is active. Mostra ícones no canto inferior direito da tela quando um desafio/conquista está ativa. - + Shows information about input and audio latency in the top-right corner of the display. Mostra informações sobre a latência de entrada e de áudio no canto superior direito da tela. - + Shows information about the emulated GPU in the top-right corner of the display. Mostra informações sobre a GPU no canto superior direito da tela. - + Shows on-screen-display messages when events occur. Mostra mensagens na tela quando ocorrem eventos. - + Shows persistent icons when turbo is active or when paused. Mostra ícones quando o turbo está ativo ou em pausa. - + Shows the current controller state of the system in the bottom-left corner of the display. Mostra quais botões estão sendo pressionados no canto inferior esquerdo da tela. - + Shows the current emulation speed of the system in the top-right corner of the display as a percentage. Mostra a velocidade de emulação atual do sistema no canto superior direito da tela registrado em porcentagem. - + Shows the current rendering resolution of the system in the top-right corner of the display. Mostra a resolução de renderização atual do sistema no canto superior direito da tela. - + Shows the game you are currently playing as part of your profile in Discord. Mostra o jogo que estiver jogando no seu perfil no Discord quando conectado. @@ -8877,197 +8908,197 @@ Error was: Mostra o uso da CPU do host com base em threads no canto superior direito da tela. - + Shows the host's GPU usage in the top-right corner of the display. Mostra o uso da GPU do host no canto superior direito da tela. - + Shows the number of frames (or v-syncs) displayed per second by the system in the top-right corner of the display. Mostra o número de quadros (ou v-syncs) por segundo pelo sistema no canto superior direito da tela. - + Simulates the CPU's instruction cache in the recompiler. Can help with games running too fast. Simula o cache de instruções da CPU no recompilador. Pode ajudar com jogos executados muito rapidamente. - + Simulates the region check present in original, unmodified consoles. Simula a verificação de região conforme no console sem nenhum tipo de modificação. - + Simulates the system ahead of time and rolls back/replays to reduce input lag. Very high system requirements. Simula o pulo de quadros para tentar reduzir atrasos. Requisitos de sistema muito altos. - + Skip Duplicate Frame Display Ignorar exibição de quadro duplicado - + Skips the presentation/display of frames that are not unique. Can result in worse frame pacing. Pula a exibição de quadros que não são exclusivos. Pode resultar em um ritmo de quadros pior. - + Slow Boot Inicialização lenta - + Smooth Scrolling Rolagem suave - + Smooths out the blockiness of magnified textures on 3D objects. Suaviza as texturas em objetos 3D. - + Sort By Classificar por - + Sort Reversed Classificação invertida - + Sound Effects Efeitos sonoros - + Specifies the amount of buffer time added, which reduces the additional sleep time introduced. Especifica a quantidade de tempo de buffer adicionado, o que reduz o tempo adicional de suspensão. - + Spectator Mode Modo espectador - + Speed Control Controle de Velocidade - + Speeds up CD-ROM reads by the specified factor. May improve loading speeds in some games, and break others. Acelera a leitura de CD-ROM pelo valor configurado. Pode melhorar a velocidade de carregamento em alguns jogos e prejudicar outros. - + Speeds up CD-ROM seeks by the specified factor. May improve loading speeds in some games, and break others. Acelera a leitura de CD-ROM pelo valor configurado. Pode melhorar a velocidade de carregamento em alguns jogos e prejudicar outros. - + Stage {}: {} - + Start BIOS Iniciar o BIOS - + Start File Iniciar arquivo - + Start Fullscreen Iniciar em tela cheia - + Start the console without any disc inserted. Ligue o console sem nenhum disco inserido. - + Stores the current settings to an input profile. Armazena as configurações atuais em um perfil de entrada. - + Stretch Display Vertically Esticar imagem verticalmente - + Stretch Mode Modo de alongamento - + Stretches the display to match the aspect ratio by multiplying vertically instead of horizontally. Estica a tela para corresponder à proporção de aspecto, multiplicando verticalmente em vez de horizontalmente. - + Summary Índice - + Switches back to 4:3 display aspect ratio when displaying 24-bit content, usually FMVs. Muda para o formato 4:3 quando FMV's são iniciados. - + Switches between full screen and windowed when the window is double-clicked. Alterna entre tela cheia e janela quando a janela é clicada duas vezes. - + Sync To Host Refresh Rate Sincronizar taxa de atualização - + Temporarily disables all enhancements, useful when testing. Desativa temporariamente todos os aprimoramentos, útil para testes. - + Test Unofficial Achievements Testar as conquistas não oficiais - + Texture Filtering Filtro de textura - + Texture Replacements Texturas personalizadas - + The SDL input source supports most controllers. A fonte de entrada SDL é compatível com a maioria dos controles. - + The XInput source provides support for XBox 360/XBox One/XBox Series controllers. A fonte XInput oferece suporte para os controles XBox 360/XBox One/XBox Series. - + The audio backend determines how frames produced by the emulator are submitted to the host. O backend de áudio determina como os quadros produzidos pelo emulador são enviados ao host. - + The selected memory card image will be used in shared mode for this slot. A imagem do cartão de memória escolhida será usada no modo compartilhado para esse compartimento. @@ -9076,37 +9107,37 @@ Error was: Apresentação sequencial - + Threaded Rendering Renderização sequencial - + Time Played Tempo de jogo - + Time Played: %s Jogou pela última vez: %s - + Title Título - + Toggle Analog Alternância do analógico - + Toggle Fast Forward Alternar pulo de quadros - + Toggle every %d frames Alternar a cada %d quadros @@ -9115,122 +9146,122 @@ Error was: Suavização de gradientes - + True Color Rendering Renderização com cores reais - + Turbo Speed Velocidade do Turbo - + Type Tipo - + Undo Load State Desfazer carregar estado - + Unknown Desconhecido - + Unlimited ilimitado - + Use Blit Swap Chain Usar cadeia de troca - + Use Debug GPU Device Usar dispositivo de depuração - + Use Global Setting Usar configuração global - + Use Light Theme Usar tema claro - + Use Software Renderer For Readbacks Usar modo software para releituras - + Username: {} Nome de usuário: {} - + Uses PGXP for all instructions, not just memory operations. Usa PGXP para todas as instruções, não apenas para operações de memória. - + Uses a blit presentation model instead of flipping. This may be needed on some systems. Usa um modelo de apresentação blit em vez de flipping. - + Uses a light coloured theme instead of the default dark theme. Usa um tema de cor clara em vez do tema escuro padrão. - + Uses a second thread for drawing graphics. Speed boost, and safe to use. Usa um segundo thread para desenhar gráficos. Aumento de velocidade e uso seguro. - + Uses game-specific settings for controllers for this game. Usa configurações específicas do jogo para os controles deste jogo. - + Uses native resolution coordinates for 2D polygons, instead of precise coordinates. Can fix misaligned UI in some games, but otherwise should be left disabled. Usa coordenadas de resolução nativa para jogos com polígonos 2D, em vez de coordenadas precisas. Pode corrigir interfaces de usuário desalinhadas em alguns jogos, caso contrário, deve ser desativada. - + Uses perspective-correct interpolation for colors, which can improve visuals in some games. Usa interpolação com correção de perspectiva para cores, o que pode melhorar o visual em alguns jogos. - + Uses perspective-correct interpolation for texture coordinates, straightening out warped textures. Usa interpolação com correção de perspectiva para coordenadas de textura, endireitando texturas distorcidas. - + Uses screen positions to resolve PGXP data. May improve visuals in some games. Usa as posições da tela para resolver os dados do PGXP. Pode melhorar o visual em alguns jogos. - + Utilizes the chosen video timing regardless of the game's setting. Utiliza o tempo de vídeo escolhido, independentemente da configuração do jogo. - + Value: {} | Default: {} | Minimum: {} | Maximum: {} Valor: {} | Padrão: {} | Mínimo: {} | Máximo: {} - + WARNING: Your game is still saving to the memory card. Continuing to {0} may IRREVERSIBLY DESTROY YOUR MEMORY CARD. We recommend resuming your game and waiting 5 seconds for it to finish saving. Do you want to {0} anyway? @@ -9239,47 +9270,47 @@ Do you want to {0} anyway? Deseja fazer {0} mesmo assim? - + When enabled and logged in, DuckStation will scan for achievements on startup. Quando ativado e conectado o DuckStation irá buscar por conquistas assim que o jogo for iniciado. - + When enabled, DuckStation will assume all achievements are locked and not send any unlock notifications to the server. Quando ativado, o DuckStation entenderá que todas as conquistas deverão ficar travadas e não enviará nenhuma notificação de desbloqueio ao servidor. - + When enabled, DuckStation will list achievements from unofficial sets. These achievements are not tracked by RetroAchievements. Quando ativado, o DuckStation listará as conquistas não oficiais. Essas conquistas não são monitoradas pelo RetroAchievements. - + When enabled, each session will behave as if no achievements have been unlocked. Quando ativada, cada sessão se comportará como se nenhuma conquista tivesse sido desbloqueada. - + When enabled, memory cards and controllers will be overwritten when save states are loaded. Quando ativado, os cartões de memória e os controles serão sobrescritos quando os estados de salvamento forem carregados. - + When enabled, the minimum supported output latency will be used for the host API. Quando ativada, a latência mínima de saída suportada será usada para a API do host. - + When playing a multi-disc game and using per-game (title) memory cards, use a single memory card for all discs. Ao jogar um jogo com vários discos usando cartões de memória independente por jogo, use um único cartão de memória para todos os discos. - + When this option is chosen, the clock speed set below will be used. Quando ativado, a velocidade escolhida será usada. - + Widescreen Rendering Renderização em tela panorâmica @@ -9288,72 +9319,72 @@ Deseja fazer {0} mesmo assim? Grava texturas que podem ser substituídas no diretório de despejo. - + Yes, {} now and risk memory card corruption. Sim, {} aceitar que o cartão de memória será corrompido. - + "Challenge" mode for achievements, including leaderboard tracking. Disables save state, cheats, and slowdown functions. Desativa salvamentos rápidos, carregamento de trapaças e funções de velocidade. - + "PlayStation" and "PSX" are registered trademarks of Sony Interactive Entertainment Europe Limited. This software is not affiliated in any way with Sony Interactive Entertainment. "PlayStation" e "PSX" são marcas registradas da Sony Interactive Entertainment Europe Limited. Este software não é afiliado de forma alguma à Sony Interactive Entertainment. - + change disc mudar disco - + reset reiniciar - + shut down desligar - + {:%H:%M} - + {:%Y-%m-%d %H:%M:%S} - + {} Frames {} Quadros - + {} deleted. {} Apagado. - + {} does not exist. {} não existe. - + {} is not a valid disc image. {} não é uma imagem de disco válida. - + Version: %s Versão: %s - + %d ms @@ -9361,53 +9392,53 @@ Deseja fazer {0} mesmo assim? GPU - + Saving screenshot to '{}'. Salvando a captura de tela em '{}'. - + Saved screenshot to '{}'. Captura de tela salva em '{}'. - + Failed to save screenshot to '{}'. Falha ao salvar a captura de tela '{}'. - + Failed to start GPU trace: Falha ao salvar o rastreio da GPU: - + Saving {0} frame GPU trace to '{1}'. Salvando o rastreamento da GPU do quadro {0} em '{1}'. - + Saving multi-frame frame GPU trace to '{1}'. Salvando o rastreamento da GPU de quadros múltiplos em '{1}'. - + Failed to close GPU trace: Falha ao fechar o rastreio da GPU: - - + + Saved GPU trace to '{}'. Rastreamento da GPU salvo em '{}'. - + Compressing GPU trace '{}'... Comprimindo o rastreamento da GPU '{}'... - + Failed to save GPU trace to '{}': Falha ao salvar o rastreio da GPU em '{}': @@ -9415,12 +9446,12 @@ Deseja fazer {0} mesmo assim? GPUDevice - + Error Erro - + OpenGL renderer unavailable, your driver or hardware is not recent enough. OpenGL 3.1 or OpenGL ES 3.1 is required. Renderizador openGL não disponível, driver ou hardware não compatíveis. OpenGL 3.1 ou OpenGLES 3.0 são obrigatórios. @@ -9466,7 +9497,7 @@ Deseja fazer {0} mesmo assim? Automático - + Software Software @@ -9588,7 +9619,7 @@ As funções de fetch, loops e exibição de rasterização são obrigatórias.< A escala de resolução {0}x não é compatível com a redução de amostra adaptável, usando {1}x. - + %n replacement textures found. Replacement texture count @@ -9597,7 +9628,7 @@ As funções de fetch, loops e exibição de rasterização são obrigatórias.< - + No replacement textures found. Não foram encontradas texturas personalizadas. @@ -10520,93 +10551,93 @@ Configure um controle compatível da lista acima. Lista de reprodução - + Disc EntryType Disco - + Disc Set EntryType Conjunto de discos - + PS-EXE EntryType - + Playlist EntryType Lista de reprodução - + PSF EntryType - + Scanning directory '{}'... Varrendo diretório '{}'... - + Scanning '{}'... Varredura '{}'... - + Unknown Desconhecido - + Never Nunca - + Today Hoje - + Yesterday Ontem - + {}h {}m {}h {}m - + {}h {}m {}s {}h {}m {}s - + {}m {}s {}m {}s - + {}s {}s - + None Nenhum - + %n hours %n horas @@ -10615,7 +10646,7 @@ Configure um controle compatível da lista acima. - + %n minutes %n minutos @@ -11294,7 +11325,6 @@ Escanear o diretório desta forma demora mais tempo porém, identificará arquiv O armazenamento de textura é atualmente experimental e pode causar erros de renderização em alguns jogos. - Enable Texture Replacements Ativar substituições de textura @@ -11390,6 +11420,11 @@ Escanear o diretório desta forma demora mais tempo porém, identificará arquiv Line Detection: Detecção de linha: + + + Enable Texture Replacement + Ativar substituição de textura + Wireframe Mode: @@ -12678,87 +12713,87 @@ Escanear o diretório desta forma demora mais tempo porém, identificará arquiv GunCon - + Pointer/Aiming Ponteiro/Mira - + Trigger Gatilho - + Shoot Offscreen Atirar para fora da tela - + A - + B - + Relative Left Relativo para esquerda - + Relative Right Relativo para direita - + Relative Up Relativo para cima - + Relative Down Relativo para baixo - + Crosshair Image Path Caminho da imagem da mira - + Path to an image to use as a crosshair/cursor. Caminho para imagem a ser usada como cursor. - + Crosshair Image Scale Escala de mira - + Scale of crosshair image on screen. Escala da mira em tela. - + Cursor Color Cor do cursor - + Applies a color to the chosen crosshair images, can be used for multiple players. Specify in HTML/CSS format (e.g. #aabbcc) Aplica uma cor às imagens de mira escolhidas, pode ser usado por vários jogadores. Especifique no formato HTML/CSS (por exemplo, #aabbcc) - + X Scale Dimensão X (verticalmente) - + Scales X coordinates relative to the center of the screen. Dimensionar coordenadas X em relação ao centro da tela . @@ -13624,6 +13659,146 @@ Escanear o diretório desta forma demora mais tempo porém, identificará arquiv Indisponível + + JogCon + + + Failed to create force feedback device for Port {}: +{} + Falha ao criar o dispositivo de feedback de força para a porta {}: +{} + + + + D-Pad Up + D-Pad para cima + + + + D-Pad Right + D-Pad para a direita + + + + D-Pad Down + D-Pad para baixo + + + + D-Pad Left + D-Pad para a esquerda + + + + Triangle + Triângulo + + + + Circle + Círculo + + + + Cross + Cruz + + + + Square + Quadrado + + + + Select + + + + + Start + + + + + L1 + L1 + + + + R1 + R1 + + + + L2 + L2 + + + + R2 + R2 + + + + Mode + Modo + + + + Steering Left + Direção esquerda + + + + Steering Right + Direção direita + + + + Force Feedback Device + Dispositivo de realimentação de força + + + + Analog Deadzone + Zona morta do analógico + + + + Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored. + Define a zona morta do analógico, por exemplo:. a fração do movimento do analógico que será ignorada. + + + + Analog Sensitivity + Sensibilidade do analógico + + + + Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent controllers, e.g. DualShock 4, Xbox One Controller. + Define o fator de escala do eixo do analógico. Um valor entre 130% e 140% é recomendável quando estiver usando controles mais recentes, por exemplo: Dualshock 4 e controles de Xbox One. + + + + Button/Trigger Deadzone + Botão/Gatilho zona morta + + + + Sets the deadzone for activating buttons/triggers, i.e. the fraction of the trigger which will be ignored. + Define a zona morta para acionamento de botões e gatilhos, ou seja, a fração do gatilho que será ignorada. + + + + Steering Hold Deadzone + Zona morta de retenção da direção + + + + Sets the deadzone for holding the wheel at the set position, i.e. when it will not trigger an effect. + Define a zona morta para manter o volante na posição definida, ou seja, quando ele não acionará um efeito. + + Justifier @@ -13816,57 +13991,57 @@ Escanear o diretório desta forma demora mais tempo porém, identificará arquiv Janela de registro - + &Clear Limpar - + &Save... Salvar... - + Cl&ose Fechar - + &Settings Configurações - + Log To &System Console Registro no console do sistema - + Log To &Debug Console Registrar no console de depuração - + Log To &File Registro em arquivo - + Attach To &Main Window Anexar à janela principal - + Show &Timestamps Mostrar registros de data e hora - + &Log Level Nível do registro - + &Channels Canais @@ -13875,27 +14050,27 @@ Escanear o diretório desta forma demora mais tempo porém, identificará arquiv Filtros - + Select Log File Selecionar arquivo de registro - + Log Files (*.txt) Arquivos de registro (*.txt) - + Error Erro - + Failed to open file for writing. Falha ao abrir o arquivo para escrita. - + Log was written to %1. O registro foi gravado em %1. @@ -13949,8 +14124,8 @@ Escanear o diretório desta forma demora mais tempo porém, identificará arquiv - - + + Change Disc Mudar disco @@ -13962,8 +14137,8 @@ Escanear o diretório desta forma demora mais tempo porém, identificará arquiv - - + + Load State Carregar estado @@ -14138,48 +14313,43 @@ Escanear o diretório desta forma demora mais tempo porém, identificará arquiv &Settings Co&nfigurações + + + Enable Safe Mode + Ativar modo seguro + &Memory Cards Cartões de memória - - + + Start Big Picture Mode Iniciar em modo tela grande - - + + Big Picture Modo tela grande - + Cover Downloader Baixar capas - + Media Ca&pture Mídia e captura - + Capture GPU Frame Captura de quadros da GPU - - - asdf - - - - - aaa - - Fullscreen @@ -14309,9 +14479,8 @@ Escanear o diretório desta forma demora mais tempo porém, identificará arquiv Despejar cópias da VRAM para o CPU - Disable All Enhancements - Desativar todas as melhorias + Desativar todas as melhorias @@ -14365,7 +14534,7 @@ Escanear o diretório desta forma demora mais tempo porém, identificará arquiv - + Resume Continuar @@ -14429,57 +14598,55 @@ Escanear o diretório desta forma demora mais tempo porém, identificará arquiv Mais zoom (modo grade) - Ctrl++ - Ctrl++ + Ctrl++ - + Zoom &Out (Grid View) Menos zoom (modo grade) - Ctrl+- - Ctrl+- + Ctrl+- - + Refresh &Covers (Grid View) Atualizar capas (modo grade) - + Open Memory Card Directory... Abrir diretório do cartão de memória... - + Open Data Directory... Abrir diretório de arquivos... - + Power Off &Without Saving Desligar sem salvar - + Memory &Scanner Leitor de memória - + Show Game Icons (List View) Exibir ícones de jogos (exibição em modo lista) - + Open Texture Directory... Abrir o diretório de texturas... - + Reload Texture Replacements Recarregar texturas personalizadas @@ -14494,23 +14661,23 @@ Escanear o diretório desta forma demora mais tempo porém, identificará arquiv Editor de cartão de memória - + Failed to get window info from widget Falha ao tentar obter informação da janela - - + + Select Disc Image Escolha uma imagem de disco - + Start Disc Iniciar disco - + Could not find any CD-ROM devices. Please ensure you have a CD-ROM drive connected and sufficient permissions to access it. Não foi possível encontrar nenhum dispositivo de CD-ROM. Certifique-se de ter uma unidade de CD-ROM conectada e permissões suficientes para acessá-la. @@ -14519,108 +14686,108 @@ Escanear o diretório desta forma demora mais tempo porém, identificará arquiv Todos os tipos de arquivo (*.bin *.img *.iso *.cue *.chd *.ecm *.mds *.pbp *.exe *.psexe *.psf *.minipsf *.m3u);;Imagens de faixa única (*.bin *.img *.iso);;Cue Sheets (*.cue);;MAME CHD Images (*.chd);;Error Code Modeler Images (*.ecm);;Media Descriptor Sidecar Images (*.mds);;PlayStation EBOOTs (*.pbp);;PlayStation Executables (*.exe *.psexe);;Arquivos de formato de som portátil (*.psf *.minipsf);;Playlists (*.m3u) - + All File Types (*.bin *.img *.iso *.cue *.chd *.cpe *.ecm *.mds *.pbp *.elf *.exe *.psexe *.ps-exe *.psx *.psf *.minipsf *.m3u *.psxgpu);;Single-Track Raw Images (*.bin *.img *.iso);;Cue Sheets (*.cue);;MAME CHD Images (*.chd);;Error Code Modeler Images (*.ecm);;Media Descriptor Sidecar Images (*.mds);;PlayStation EBOOTs (*.pbp *.PBP);;PlayStation Executables (*.cpe *.elf *.exe *.psexe *.ps-exe, *.psx);;Portable Sound Format Files (*.psf *.minipsf);;Playlists (*.m3u);;PSX GPU Dumps (*.psxgpu) Todos os tipos (*.bin *.img *.iso *.cue *.chd *.cpe *.ecm *.mds *.pbp *.elf *.exe *.psexe *.ps-exe *.psx *.psf *.minipsf *.m3u *.psxgpu);;Single-Track Raw Images (*.bin *.img *.iso);;Cue Sheets (*.cue);;MAME CHD Images (*.chd);;Error Code Modeler Images (*.ecm);;Media Descriptor Sidecar Images (*.mds);;PlayStation EBOOTs (*.pbp *.PBP);;PlayStation Executables (*.cpe *.elf *.exe *.psexe *.ps-exe, *.psx);;Portable Sound Format Files (*.psf *.minipsf);;Playlists (*.m3u);;PSX GPU Dumps (*.psxgpu) - - - + + + Error Erro - + Paused Pausado - + %1 (%2) %1 (%2) - + Select disc drive: Escolha a unidade de disco: - + Resume (%1) Continuar (%1) - - - + + + Game Save %1 (%2) Jogo salvo %1 (%2) - + Edit Memory Cards... Editar cartões de memória... - + Delete Save States... Apagar estados salvos... - + Confirm Save State Deletion Confirmar deleção de estado salvo - + Are you sure you want to delete all save states for %1? The saves will not be recoverable. Tem certeza de que deseja apagar os estados salvos %1? Não será possivel reverter esta ação. - + Load From File... Carregar do arquivo... - - + + Select Save State File Escolher arquivo de salvamento rápido - - + + Save States (*.sav) Salvamento rápido (*.sav) - + Undo Load State Desfazer estado carregado - - + + Game Save %1 (Empty) Jogo salvo %1 (Vazio) - - + + Global Save %1 (%2) Compartimento global %1 (%2) - - + + Global Save %1 (Empty) Compartimento global %1 (Vazio) - + Save To File... Salvar para arquivo... @@ -14649,12 +14816,12 @@ The saves will not be recoverable. &Aplicar trapaças - + Load Resume State Carregar estado salvo - + A resume save state was found for this game, saved at: %1. @@ -14667,52 +14834,52 @@ Do you want to load this state, or start from a fresh boot? Você deseja que este arquivo seja carregado ou que seja reiniciado novamente? - + Fresh Boot Inicialização limpa - + Delete And Boot Excluir e iniciar - + Failed to delete save state file '%1'. Falha ao apagar o arquivo de estado salvo '%1'. - + Confirm Disc Change Confirmar troca de disco - + Do you want to swap discs or boot the new image (via system reset)? Deseja trocar discos ou inicializar outro (via reinicialização do sistema)? - + Swap Disc Trocar disco - + Reset Redefinir - + Media Capture Mídia e captura - + <p>Sorry, you are trying to update a DuckStation version which is not an official GitHub release. To prevent incompatibilities, the auto-updater is only enabled on official builds.</p><p>Please download an official release from from <a href="https://www.duckstation.org/">duckstation.org</a>.</p> <p>Desculpe, você está tentando atualizar uma versão do DuckStation que não é uma versão oficial do GitHub. Para evitar incompatibilidades, o atualizador automático só é ativado em versões oficiais</p><p>Por favor baixe a versão oficial em <a href="https://www.duckstation.org/">duckstation.org</a>.</p> - + Cancel Cancelar @@ -14721,42 +14888,42 @@ Você deseja que este arquivo seja carregado ou que seja reiniciado novamente?Gerenciador de trapaças - + Stop Big Picture Mode Parar modo tela cheia - + Exit Big Picture Sair do modo tela cheia - + You must select a disc to change discs. Você deve escolher um disco para confirmar a troca - + All Cover Image Types (*.jpg *.jpeg *.png *.webp) Todos os tipos de imagem de capa (*.jpg *.jpeg *.png *.webp) - + You must select a different file to the current cover image. Você deve selecionar um arquivo diferente da imagem de capa atual. - + Failed to remove '%1' Falha ao remover '%1' - + Confirm Reset Confirmar redefinição - + Are you sure you want to reset the play time for '%1'? This action cannot be undone. @@ -14765,8 +14932,8 @@ This action cannot be undone. Esta ação não poderá ser desfeita. - - + + Properties... Propriedades... @@ -14775,92 +14942,92 @@ Esta ação não poderá ser desfeita. Todos os tipos de arquivo (*.bin *.img *.iso *.cue *.chd *.ecm *.mds *.pbp *.exe *.psexe *.psf *.minipsf *.m3u);;Imagens de faixa única (*.bin *.img *.iso);;Cue Sheets (*.cue);;MAME CHD Images (*.chd);;Error Code Modeler Images (*.ecm);;Media Descriptor Sidecar Images (*.mds);;PlayStation EBOOTs (*.pbp);;PlayStation Executables (*.exe *.psexe);;Arquivos de formato de som portátil (*.psf *.minipsf);;Playlists (*.m3u) - + Select Cheats... Escolher trapaças... - + Cheats are not enabled. As trapaças não estão ativadas. - + &Apply Cheat Aplicar trapaça - + Open Containing Directory... Abrir diretório... - - + + Set Cover Image... Definir imagem de capa... - + Default Boot Inicialização padrão - + Fast Boot Inicialização rápida - + Full Boot Inicialização completa - + Boot and Debug Iniciar jogo com depurador - - + + Exclude From List Excluir da lista - + Reset Play Time Redefinir tempo de jogo - + Select Disc Escolher disco - + Add Search Directory... Adicionar um novo diretório... - + Select Cover Image Escolher imagem de capa - + Cover Already Exists Capa já existe - + A cover image for this game already exists, do you wish to replace it? A capa para este jogo já existe, deseja substituí-lá? - - - - + + + + Copy Error Erro ao copiar @@ -14869,35 +15036,35 @@ Esta ação não poderá ser desfeita. Todos os tipos de arquivo (*.bin *.img *.iso *.cue *.chd *.ecm *.mds *.pbp *.exe *.psexe *.psf *.minipsf *.m3u);;Imagens de faixa única (*.bin *.img *.iso);;Cue Sheets (*.cue);;MAME CHD Images (*.chd);;Error Code Modeler Images (*.ecm);;Media Descriptor Sidecar Images (*.mds);;PlayStation EBOOTs (*.pbp);;PlayStation Executables (*.exe *.psexe);;Arquivos de formato de som portátil (*.psf *.minipsf);;Playlists (*.m3u) - + Failed to remove existing cover '%1' Falha ao remover capa existente '%1' - + Failed to copy '%1' to '%2' Falha ao copiar '%1' para '%2' - + %1x Scale Expandir para %1x - - - + + + Destination File Destino do arquivo - - + + Binary Files (*.bin) Arquivos binários (*.bin) - + Binary Files (*.bin);;PNG Images (*.png) Arquivos (*.bin);;Imagens (*.png) @@ -14952,36 +15119,36 @@ Esta ação não poderá ser desfeita. - + Confirm Shutdown Confirmar desligamento - + Are you sure you want to shut down the virtual machine? Tem certeza de que deseja desligar a máquina virtual? - + Save State For Resume Salvar estado e continuar - - - - + + + + Memory Card Not Found Cartão de memória não encontrado - + Memory card '%1' does not exist. Do you want to create an empty memory card? Cartão de memória '%1' não existe, você deseja criar um cartão de memória vazio? - - + + Memory card '%1' could not be found. Try starting the game and saving to create it. Cartão de memória '%1' não encontrado. Experimente iniciar o jogo e salvá-lo para que ele seja criado. @@ -14998,22 +15165,22 @@ As trapaças ficam guardadas no estado de salvamento rápido mesmo após serem d Tem certeza de que deseja continuar? - + Failed to create memory card '%1': %2 Erro ao criar cartão de memória '%1': %2 - + %1 Files (*.%2) %1 Arquivos (*.%2) - + Updater Error Erro na atualização - + Automatic updating is not supported on the current platform. Atualizações automáticas não são compatíveis com a plataforma atual. @@ -15021,12 +15188,12 @@ Tem certeza de que deseja continuar? MediaCapture - + Failed to load Media Foundation libraries: Falha ao carregar bibliotecas de mídia: - + You may be missing one or more files, or are using the incorrect version. This build of DuckStation requires: libavcodec: {} libavformat: {} @@ -16274,17 +16441,17 @@ Você deve excluir o cartão de memória manualmente se quiser salvar. OSDMessage - + System reset. Sistema reiniciado. - + Disabling PCDrv because no root directory is specified. PCDrv desativado porque nenhum diretório raiz foi especificado. - + PGXP is incompatible with the software renderer, disabling PGXP. PGXP é incompatível com o rederizador por software, desligando PGXP. @@ -16297,22 +16464,22 @@ Você deve excluir o cartão de memória manualmente se quiser salvar.Pulo de quadros não é compatível com a versão de 32Bits. - + Rewind is disabled because runahead is enabled. Função de retrocesso desligada porque o avanço rápido está ligado. - + Recompiler options changed, flushing all blocks. As opções do recompilador foram alteradas, limpando todos os blocos. - + Widescreen hack is now enabled, and aspect ratio is set to {}. Ajuste de tela panorâmica ligado, a proporção da imagem está definida para {}. - + Widescreen hack is now disabled, and aspect ratio is set to {}. Ajuste de tela panorâmica desligado, a proporção da imagem foi definida para {}. @@ -16341,7 +16508,7 @@ Você deve excluir o cartão de memória manualmente se quiser salvar.Texturas personalizadas recarregadas. - + Rewinding is not enabled. O retrocesso não está habilitado. @@ -16426,27 +16593,27 @@ Você deve excluir o cartão de memória manualmente se quiser salvar.Áudio do CD religado. - + Swapped memory card ports. Both ports have a memory card. Portas de cartão de memória trocadas. ambas já contém cartão. - + Swapped memory card ports. Port 2 has a memory card, Port 1 is empty. Portas de cartão de memória trocadas. porta 2 tem um cartão de memória, porta 1 vazia. - + Swapped memory card ports. Port 1 has a memory card, Port 2 is empty. Portas de cartão de memória trocadas. porta 1 tem um cartão de memória, porta 2 vazia. - + Swapped memory card ports. Neither port has a memory card. Portas de cartão de memória trocadas. nenhuma das portas possui cartão de memória. - + Failed to open CD image from save state '{}': {}. Using existing image '{}', this may result in instability. Falha ao abrir a imagem do CD a partir do estado de salvamento '{}': {}. @@ -16523,59 +16690,59 @@ Deixando conectado {2}. O cartão de memória {} existe, mas não no compartimento atual. Reconectando o cartão de memória. - + CD image preloading not available for multi-disc image '{}' O pré-carregamento da imagem do CD não está disponível para jogos multi-discos '{}' - + Precaching CD image failed, it may be unreliable. Pré-alocação de disco falhou, pode ser que a imagem esteja danificada. - + Loading state from '{}'... Estado carregado de '{}'... - + State saved to '{}'. Estado salvo em '{}'. - + Failed to open disc image '{}': {}. Falha ao abrir a imagem do disco '{}': {}. - + Inserted disc '{}' ({}). Disco inserido '{}' ({}). - + Failed to save undo load state: {} Falha ao desfazer o carregamento: {} - + Switching to {} renderer... Alterando para renderizador {}... - + Switching to {}{} GPU renderer. Alternando para o renderizador de GPU {}{}. - + Switching to {} audio backend. Alternando para o backend de áudio {}. - + Switching to {} CPU execution mode. Alternando para o modo de execução pelo CPU {}. @@ -16833,17 +17000,17 @@ The URL was: %1 QtAsyncProgressThread - + Error Erro - + Question Pergunta - + Information Informação @@ -16851,124 +17018,135 @@ The URL was: %1 QtHost - - - - - - - - - - - - - - + + + + + + + + + + + + + + Error Erro - + An error occurred while deleting empty game settings: {} Ocorreu um erro ao apagar as configurações vazias: {} - + An error occurred while saving game settings: {} Ocorreu um erro ao salvar as configurações por jogo: {} - Failed to create HTTPDownloader. - Falha ao criar protocolo HTTP. + Falha ao criar protocolo HTTP. - + Downloading %1... Baixando %1... - Download failed with HTTP status code %1. - O download falhou com o código HTTP %1. + O download falhou com o código HTTP %1. - + + Failed to create HTTPDownloader: +%1 + + + + + Download failed with HTTP status code %1: +%2 + O download falhou com o código HTTP %1: +%2 + + + Download failed: Data is empty. O download falhou: Os dados estão vazios. - + Failed to write '%1'. Falha ao gravar '%1'. - + Failed to open downloaded zip file. Falha ao abrir o arquivo compactado baixado. - + Failed to locate '%1' in zip. Falha ao localizar '%1' no zip. - + Failed to open '%1': %2. Falha ao abrir '%1': %2. - + Failed to read '%1' from zip. Falha ao ler '%1' a partir do zip. - + Failed to write to '%1'. Falha ao gravar em '%1'. - + RA: Logged in as %1 (%2, %3 softcore). %4 unread messages. RA: conectado como %1 (%2, %3 normal). %4 mensagens não lidas. - + Controller {} connected. Controle {} conectado. - + System paused because controller {} was disconnected. O sistema foi pausado porque o controle {} foi desconectado. - + Controller {} disconnected. Controle {} desconectado. - + File '%1' does not exist. O arquivo '%1' não existe. - + The specified save state does not exist. O dado de salvamento não existe. - + Cannot use no-gui mode, because no boot filename was specified. Não é possível usar o modo no-gui, porque nenhum parâmetro de inicialização foi configurado. - + Cannot use batch mode, because no boot filename was specified. Não é possível usar este modo porque nenhum parâmetro de inicialização foi configurado. @@ -16996,22 +17174,22 @@ The URL was: %1 DuckStation - + Cancel Cancelar - + Error Erro - + Question Pergunta - + Information Informação @@ -17258,121 +17436,121 @@ Deseja criar esse diretório? Zstandard (Alta) - + None LogLevel Nenhum - + Error LogLevel Erro - + Warning LogLevel Alerta - + Information LogLevel Informação - + Verbose LogLevel Detalhado - + Developer LogLevel Desenvolvedor - + Debug LogLevel Depurar - + Trace LogLevel Rastreio - + Auto-Detect ConsoleRegion Detectar automaticamente - + NTSC-J (Japan) ConsoleRegion NTSC-J (Japão) - + NTSC-U/C (US, Canada) ConsoleRegion NTSC-U/C (US, Canadá) - + PAL (Europe, Australia) ConsoleRegion PAL (Europeu, Austrália) - + NTSC-J (Japan) DiscRegion NTSC-J (Japão) - + NTSC-U/C (US, Canada) DiscRegion NTSC-U/C (US, Canadá) - + PAL (Europe, Australia) DiscRegion PAL (Europeu, Austrália) - + Other DiscRegion Outros - + Non-PS1 DiscRegion Não é de PS1 - + Interpreter (Slowest) CPUExecutionMode Interpretador (mais lento) - + Cached Interpreter (Faster) CPUExecutionMode Interpretador armazenado (rápido) - + Recompiler (Fastest) CPUExecutionMode Recompilador (mais rápido) @@ -17383,529 +17561,529 @@ Deseja criar esse diretório? Novo recompilador (experimental) - + Disabled (Slowest) CPUFastmemMode Desativado (Lento) - + MMap (Hardware, Fastest, 64-Bit Only) CPUFastmemMode MMap (hardware, mais rápido) - + LUT (Faster) CPUFastmemMode LUT (rápido) - + Automatic GPURenderer Automático - + Direct3D 11 GPURenderer - + Direct3D 12 GPURenderer - + Metal GPURenderer - + Vulkan GPURenderer - + OpenGL GPURenderer - + Software GPURenderer Software - + Nearest-Neighbor GPUTextureFilter Nearest-Neighbor - + Bilinear GPUTextureFilter Bi-linear - + Bilinear (No Edge Blending) GPUTextureFilter Bi-linear (sem AA) - + JINC2 (Slow) GPUTextureFilter JINC2 (lento) - + JINC2 (Slow, No Edge Blending) GPUTextureFilter JINC2 (lento, sem AA) - + xBR (Very Slow) GPUTextureFilter xBR (muito lento) - + xBR (Very Slow, No Edge Blending) GPUTextureFilter xBR (muito lento sem AA) - + Disabled GPULineDetectMode Desligado - + Quads GPULineDetectMode Quadrantes - + Triangles (Basic) GPULineDetectMode Triângulos (básico) - + Triangles (Aggressive) GPULineDetectMode Triângulos (agressivo) - + Disabled GPUDownsampleMode Desligado - + Box (Downsample 3D/Smooth All) GPUDownsampleMode Misto (Reduz 3D / Suaviza tudo) - + Adaptive (Preserve 3D/Smooth 2D) GPUDownsampleMode Adaptativo (Preserva o 3D / Suaviza 2D) - + Disabled GPUWireframeMode Desligado - + Overlay Wireframe GPUWireframeMode Sobreposição - + Only Wireframe GPUWireframeMode Esboço - + Disabled GPUDumpCompressionMode Desativado - + Zstandard (Low) GPUDumpCompressionMode Zstandard (baixo) - + Zstandard (Default) GPUDumpCompressionMode Zstandard (Padrão) - + Zstandard (High) GPUDumpCompressionMode Zstandard (Alta) - + XZ (Low) GPUDumpCompressionMode XZ (baixa) - + XZ (Default) GPUDumpCompressionMode XZ (padrão) - + XZ (High) GPUDumpCompressionMode XZ (alta) - + Disabled (Flickering) DisplayDeinterlacingMode Oscilação (desativada) - + Weave (Combing) DisplayDeinterlacingMode Ondulação - + Blend (Blur) DisplayDeinterlacingMode Desfoque - + Adaptive (FastMAD) DisplayDeinterlacingMode Adaptativo (rápido) - + Progressive (Optimal) DisplayDeinterlacingMode Progressivo (ideal) - + None DisplayCropMode Nenhum - + Only Overscan Area DisplayCropMode Somente área renderizada - + Only Overscan Area (Aspect Uncorrected) DisplayCropMode Somente área renderizada (Aspecto não corrigido) - + All Borders DisplayCropMode Todas as bordas - + All Borders (Aspect Uncorrected) DisplayCropMode Todas as bordas (Aspecto não corrigido) - + Auto (Game Native) DisplayAspectRatio Auto (resolução nativa) - + Stretch To Fill DisplayAspectRatio Esticar para preencher - + Custom DisplayAspectRatio Personalizado - + Left / Top DisplayAlignment Topo superior - + Center DisplayAlignment Centro - + Right / Bottom DisplayAlignment Esquerda inferior - + No Rotation DisplayRotation Sem rotação - + Rotate 90° (Clockwise) DisplayRotation Girar 90° (sentido horário) - + Rotate 180° (Vertical Flip) DisplayRotation Girar 180° (inversão vertical) - + Rotate 270° (Clockwise) DisplayRotation Girar 270° (sentido horário) - + Disabled ForceVideoTiming Desligado - + NTSC (60hz) ForceVideoTiming - + PAL (50hz) ForceVideoTiming - + Nearest-Neighbor DisplayScalingMode Nearest-Neighbor - + Nearest-Neighbor (Integer) DisplayScalingMode Nearest-Neighbor (integro) - + Bilinear (Smooth) DisplayScalingMode Bilinear (suave) - + Bilinear (Sharp) DisplayScalingMode Bilinear (forte) - + Bilinear (Integer) DisplayScalingMode Bilinear (inteiro) - + Automatic DisplayExclusiveFullscreenControl Automático - + Disallowed DisplayExclusiveFullscreenControl Não permitido - + Allowed DisplayExclusiveFullscreenControl Permitido - + Screen Resolution DisplayScreenshotMode Resolução da tela - + Internal Resolution DisplayScreenshotMode Resolução interna - + Internal Resolution (Aspect Uncorrected) DisplayScreenshotMode Resolução interna (aspecto não corrigido) - + PNG DisplayScreenshotFormat - + JPEG DisplayScreenshotFormat - + WebP DisplayScreenshotFormat - + No Memory Card MemoryCardType Sem cartão de memória - + Shared Between All Games MemoryCardType Compartilhada entre jogos - + Separate Card Per Game (Serial) MemoryCardType Cartão separado por jogo (Serial) - + Separate Card Per Game (Title) MemoryCardType Cartão separado por jogo (título) - + Separate Card Per Game (File Title) MemoryCardType Separar cartão por jogo (título do arquivo) - + Non-Persistent Card (Do Not Save) MemoryCardType Cartão não persistente (não salvar) - + Disabled MultitapMode Desligado - + Enable on Port 1 Only MultitapMode Ativar somente na porta 1 - + Enable on Port 2 Only MultitapMode Ativar somente na porta 2 - + Enable on Ports 1 and 2 MultitapMode Ativar nas portas 1 e 2 - + Uncompressed SaveStateCompressionMode Não comprimido - + Deflate (Low) SaveStateCompressionMode Compressão (baixa) - + Deflate (Default) SaveStateCompressionMode Diminuto (Padrão) - + Deflate (High) SaveStateCompressionMode Compressão (alta) - + Zstandard (Low) SaveStateCompressionMode Zstandard (baixo) - + Zstandard (Default) SaveStateCompressionMode Zstandard (Padrão) - + Zstandard (High) SaveStateCompressionMode Zstandard (Alta) @@ -17967,13 +18145,13 @@ Deseja criar esse diretório? Extensão do tempo (mudança de tempo, melhor qualidade) - + Media Foundation MediaCaptureBackend Mídia - + FFmpeg MediaCaptureBackend @@ -18542,7 +18720,7 @@ Escanear o diretório desta forma demora mais tempo porém, identificará arquiv - + Error Erro @@ -18553,42 +18731,42 @@ Escanear o diretório desta forma demora mais tempo porém, identificará arquiv Falha ao iniciar o sistema: {} - + Failed to undo load state. Falha ao desfazer o estado de carregamento armazenado. - + Failed to load state: {} Falha ao carregar o estado: {} - + Failed to save state: {} Falha ao salvar o estado: {} - + Compatibility settings are not enabled. Some games may not function correctly. As configurações de compatibilidade não estão ativadas. Alguns jogos podem não funcionar corretamente. - + Cannot save state while memory card is being saved. Não é possível salvar o estado enquanto o cartão de memória está sendo lido. - + Failed to initialize {} renderer, falling back to software renderer. Falha ao inicializar o renderizador {}, retornando ao renderizador por software. - + This save state was created with a different BIOS. This may cause stability issues. Esse estado salvo foi criado com um BIOS diferente. Isso pode causar problemas de estabilidade. - + WARNING: CPU overclock ({}%) was different in save state ({}%). AVISO: O overclock do CPU ({}%) era diferente no estado de salvamento ({}%). @@ -18607,7 +18785,7 @@ Escanear o diretório desta forma demora mais tempo porém, identificará arquiv - + You are attempting to run a libcrypt protected game without an SBI file: {0}: {1} @@ -18628,7 +18806,7 @@ Consulte o LEIAME para obter instruções sobre como adicionar um arquivo SBI. Deseja continuar? - + You are attempting to run a libcrypt protected game without an SBI file: {0}: {1} @@ -18645,32 +18823,32 @@ Seu arquivo de jogo está incompleto, você deve adicionar o arquivo SBI para ex O nome do arquivo SBI deve corresponder ao nome da imagem do disco. - + Failed to switch to subimage {} in '{}': {}. Falha ao alternar para a subimagem {} em '{}': {}. - + Switched to sub-image {} ({}) in '{}'. Alternado para a subimagem {} ({}) em '{}'. - + CPU clock speed is set to {}% ({} / {}). This may crash games. Velocidade do CPU configurada para {}% ({} / {}. pode causar falhas nos jogos. - + CD-ROM read speedup set to {}x (effective speed {}x). This may crash games. Velocidade de leitura do CD-ROM definida como {}x (velocidade efetiva {}x). Jogos podem travar. - + CD-ROM seek speedup set to {}. This may crash games. Velocidade de leitura do CD-ROM definida para {}. Jogos podem travar. - + Instant Instantâneo @@ -18679,17 +18857,17 @@ O nome do arquivo SBI deve corresponder ao nome da imagem do disco.A opção forçar temporizador no modo NTSC está ativada. Os jogos serão executados em uma velocidade incorreta. - + Multisample anti-aliasing is enabled, some games may not render correctly. O anti-serrilhamento múltiplo está ativado, alguns jogos podem não ser renderizados corretamente. - + Round upscaled texture coordinates is enabled. This may cause rendering errors. A opção de arredondamento das coordenadas de textura com escalas maiores está ativada. Isso pode causar erros de renderização. - + 8MB RAM is enabled, this may be incompatible with some games. Modo de 8 MB de RAM está ativado, o que pode ser incompatível com alguns jogos. @@ -18705,69 +18883,69 @@ O nome do arquivo SBI deve corresponder ao nome da imagem do disco. - + Host GPU device encountered an error and has recovered. This may cause broken rendering. O dispositivo encontrou um erro e se recuperou. Falhas na renderização poderão ocorrer. - + Startup was cancelled. A inicialização foi cancelada. - + This save state was created with the following tainted options, and may be unstable. You will need to reset the system to clear any effects. Esse estado salvo foi criado com opções adulteradas e pode ser instável. Será necessário reiniciar o sistema para eliminar quaisquer efeitos. - + Force frame timings is enabled. Games may run at incorrect speeds. Forçar temporização de quadros está ativada. Jogos podem ser executados em velocidades incorretas. - + Texture cache is enabled. This feature is experimental, some games may not render correctly. O armazenamento de textura é atualmente experimental e pode causar erros de renderização em alguns jogos. - + Safe mode is enabled. Modo seguro está ativado. - + Overclock disabled. Overclock desligado. - + 8MB RAM disabled. 8MB RAM desligado. - + Cheats disabled. Trapaças desativada. - + Patches disabled. Modificações desativadas. - + Resolution scale set to 1x. Escala de resolução definida para 1x. - + Multisample anti-aliasing disabled. Suavização de serrilhado desligado. - + True color disabled. Cor real desativada. @@ -18776,107 +18954,107 @@ O nome do arquivo SBI deve corresponder ao nome da imagem do disco.Suavização de gradientes desligada. - + Texture filtering disabled. Filtro de textura desativado. - + Interlaced rendering enabled. Renderização entrelaçada ativada. - + Video timings set to default. Temporizadores de vídeo definidos para o padrão. - + Widescreen rendering disabled. Renderização em tela panorâmica desativada. - + FMV chroma smoothing disabled. Suavização de croma do FMV desligado. - + CD-ROM read speedup disabled. Velocidade de leitura CD-ROM desligado. - + CD-ROM seek speedup disabled. Velocidade de busca CD-ROM desligado. - + Mute CD-ROM audio disabled. Silenciar o áudio do CD-ROM desativado. - + VRAM write texture replacements disabled. Substituições de textura na VRAM desativadas. - + Use old MDEC routines disabled. Usar rotinas MDEC antigas desativado. - + PCDrv disabled. PCDrv desativado. - + Fast boot disabled. Inicialização rápida desativada. - + CD-ROM SubQ Skew is enabled. This will break games. A derivação do CD-ROM no módulo SubQ está ativada. Erros poderão ocorrer. - + Failed to save resume state: {} Falha ao resumir o estado: {} - + capturing audio and video Capturando áudio e vídeo - + capturing video Capturando vídeo - + capturing audio Capturando áudio - + Failed to create media capture: {0} Falha ao criar a captura de mídia: {0} - + Starting {0} to '{1}'. Iniciando {0} a '{1}'. - + Stopped {0} to '{1}'. Terminado {0} a '{1}'. - + Stopped {0}: {1}. Terminado {0}: {1}. @@ -18909,72 +19087,72 @@ O nome do arquivo SBI deve corresponder ao nome da imagem do disco.A trapaça '{}' já está ativada. - + Per-game memory card cannot be used for slot {} as the running game has no code. Using shared card instead. O cartão de memória individual não pode ser usado no compartimento {} porque o jogo em execução não possui código. Usando cartão compartilhado. - + Save state is incompatible: minimum version is {0} but state is version {1}. Dado de estado salvo incompatível: a versão mínima é {0}, mas o estado é referente a versão {1}. - + Save state is incompatible: maximum version is {0} but state is version {1}. Dado de estado salvo incompatível: a versão máxima é {0}, mas o estado é referente a versão {1}. - + Failed to open CD image '{}' used by save state: Falha ao abrir a imagem de CD '{}' usada pelo estado de salvamento: - + CPU Overclock Taint Overclock de CPU - + CD-ROM Read Speedup Taint Velocidade de leitura CD-ROM - + CD-ROM Seek Speedup Taint Velocidade de busca CD-ROM - + Force Frame Timings Taint Forçar temporizadores de quadro - + 8MB RAM Taint - + Cheats Taint Trapaças - + Game Patches Taint Modificações de jogos - - + + System is not in correct state. O sistema não está em seu estado correto. @@ -18985,27 +19163,27 @@ O nome do arquivo SBI deve corresponder ao nome da imagem do disco. - + Per-game memory card cannot be used for slot {} as the running game has no title. Using shared card instead. O cartão de memória individual não pode ser usado no compartimento {} porque o jogo em execução não tem título. Usando cartão compartilhado. - + Using disc-specific memory card '{}' instead of per-game card. Usando cartão de memória específico do disco '{}' em vez do cartão por jogo. - + Per-game memory card cannot be used for slot {} as the running game has no path. Using shared card instead. O cartão de memória individual não pode ser usado no compartimento {} porque o jogo em execução não tem caminho configurado. Usando cartão compartilhado. - + Game changed, reloading memory cards. Jogo trocado, recarregando cartões de memória. - + No BIOS image found for {} region. DuckStation requires a PS1 or PS2 BIOS in order to run. @@ -19022,7 +19200,7 @@ Por motivos legais, você DEVE obter uma imagem do BIOS de uma unidade PS1 que s Após o despejo, essa imagem de BIOS deve ser colocada na pasta bios dentro do diretório de dados (Menu ferramentas > Abrir diretório de dados). - + No BIOS image found for {} region. Nenhuma imagem de BIOS encontrada para região {}. From 9a5ee3aae63a5f78e8ca5083d3e05dbef89b4d6c Mon Sep 17 00:00:00 2001 From: Stenzek Date: Tue, 3 Dec 2024 16:16:35 +1000 Subject: [PATCH 57/69] Qt: Fix horizontal scrollbar showing in summary track list --- src/duckstation-qt/gamesummarywidget.cpp | 34 +++++++++++++++++++----- src/duckstation-qt/gamesummarywidget.h | 5 ++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/duckstation-qt/gamesummarywidget.cpp b/src/duckstation-qt/gamesummarywidget.cpp index 31f005b42..8fcf6f5f7 100644 --- a/src/duckstation-qt/gamesummarywidget.cpp +++ b/src/duckstation-qt/gamesummarywidget.cpp @@ -106,6 +106,25 @@ void GameSummaryWidget::reloadGameSettings() m_ui.editInputProfile->setEnabled(m_ui.inputProfile->currentIndex() >= 1); } +void GameSummaryWidget::resizeEvent(QResizeEvent* event) +{ + QWidget::resizeEvent(event); + updateTracksInfoColumnSizes(); +} + +void GameSummaryWidget::showEvent(QShowEvent* event) +{ + QWidget::showEvent(event); + + // Need to put this on show as well, otherwise it lags behind the vertical scrollbar being enabled. + updateTracksInfoColumnSizes(); +} + +void GameSummaryWidget::updateTracksInfoColumnSizes() +{ + QtUtils::ResizeColumnsForTableView(m_ui.tracks, {70, 75, 70, 70, -1, 40}); +} + void GameSummaryWidget::populateUi(const std::string& path, const std::string& serial, DiscRegion region, const GameDatabase::Entry* entry) { @@ -287,13 +306,12 @@ void GameSummaryWidget::setRevisionText(const QString& text) m_ui.revision->setVisible(true); } -static QString MSFTotString(const CDImage::Position& position) +static QString MSFToString(const CDImage::Position& position) { - return QStringLiteral("%1:%2:%3 (LBA %4)") + return QStringLiteral("%1:%2:%3") .arg(static_cast(position.minute), 2, 10, static_cast('0')) .arg(static_cast(position.second), 2, 10, static_cast('0')) - .arg(static_cast(position.frame), 2, 10, static_cast('0')) - .arg(static_cast(position.ToLBA())); + .arg(static_cast(position.frame), 2, 10, static_cast('0')); } void GameSummaryWidget::populateTracksInfo() @@ -302,7 +320,6 @@ void GameSummaryWidget::populateTracksInfo() {"Audio", "Mode 1", "Mode 1/Raw", "Mode 2", "Mode 2/Form 1", "Mode 2/Form 2", "Mode 2/Mix", "Mode 2/Raw"}}; m_ui.tracks->clearContents(); - QtUtils::ResizeColumnsForTableView(m_ui.tracks, {70, 75, 95, 95, 215, 40}); std::unique_ptr image = CDImage::Open(m_path.c_str(), false, nullptr); if (!image) @@ -327,10 +344,13 @@ void GameSummaryWidget::populateTracksInfo() m_ui.tracks->insertRow(row); m_ui.tracks->setItem(row, 0, num); m_ui.tracks->setItem(row, 1, new QTableWidgetItem(track_mode_strings[static_cast(mode)])); - m_ui.tracks->setItem(row, 2, new QTableWidgetItem(MSFTotString(position))); - m_ui.tracks->setItem(row, 3, new QTableWidgetItem(MSFTotString(length))); + m_ui.tracks->setItem(row, 2, new QTableWidgetItem(MSFToString(position))); + m_ui.tracks->setItem(row, 3, new QTableWidgetItem(MSFToString(length))); m_ui.tracks->setItem(row, 4, new QTableWidgetItem(tr(""))); + for (int i = 1; i <= 4; i++) + m_ui.tracks->item(row, i)->setTextAlignment(Qt::AlignCenter); + QTableWidgetItem* status = new QTableWidgetItem(QString()); status->setTextAlignment(Qt::AlignCenter); m_ui.tracks->setItem(row, 5, status); diff --git a/src/duckstation-qt/gamesummarywidget.h b/src/duckstation-qt/gamesummarywidget.h index a96fa4404..15718f4ca 100644 --- a/src/duckstation-qt/gamesummarywidget.h +++ b/src/duckstation-qt/gamesummarywidget.h @@ -26,6 +26,10 @@ public: void reloadGameSettings(); +protected: + void resizeEvent(QResizeEvent* event) override; + void showEvent(QShowEvent* event) override; + private Q_SLOTS: void onCustomLanguageChanged(int language); void onCompatibilityCommentsClicked(); @@ -43,6 +47,7 @@ private: void setRevisionText(const QString& text); void populateTracksInfo(); + void updateTracksInfoColumnSizes(); Ui::GameSummaryWidget m_ui; SettingsWindow* m_dialog; From d3ceda0c5bfd71f96b97ed9650c5a6f3a506874b Mon Sep 17 00:00:00 2001 From: Stenzek Date: Tue, 3 Dec 2024 16:33:18 +1000 Subject: [PATCH 58/69] CPU/CodeCache: Improve block host size heuristics Codegen is much better these days, especially with NewRec. --- src/core/cpu_code_cache.cpp | 34 +++++++++++++++++++++------------- src/core/cpu_recompiler.h | 21 ++++++++++++--------- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/core/cpu_code_cache.cpp b/src/core/cpu_code_cache.cpp index bc8384352..31c1ac411 100644 --- a/src/core/cpu_code_cache.cpp +++ b/src/core/cpu_code_cache.cpp @@ -154,9 +154,10 @@ static u8* s_free_far_code_ptr = nullptr; static u32 s_far_code_size = 0; static u32 s_far_code_used = 0; -#if defined(_DEBUG) || defined(_DEVEL) +#ifdef DUMP_CODE_SIZE_STATS static u32 s_total_instructions_compiled = 0; static u32 s_total_host_instructions_emitted = 0; +static u32 s_total_host_code_used_by_instructions = 0; #endif } // namespace CPU::CodeCache @@ -691,7 +692,6 @@ void CPU::CodeCache::InvalidateAllRAMBlocks() void CPU::CodeCache::ClearBlocks() { - for (u32 i = 0; i < Bus::RAM_8MB_CODE_PAGE_COUNT; i++) { PageProtectionInfo& ppi = s_page_protection[i]; @@ -1345,10 +1345,13 @@ void CPU::CodeCache::CompileOrRevalidateBlock(u32 start_pc) } // Ensure we're not going to run out of space while compiling this block. - // We could definitely do better here... TODO: far code is no longer needed for newrec + // We could definitely do better here... const u32 block_size = static_cast(s_block_instructions.size()); - if (GetFreeCodeSpace() < (block_size * Recompiler::MAX_NEAR_HOST_BYTES_PER_INSTRUCTION) || - GetFreeFarCodeSpace() < (block_size * Recompiler::MAX_FAR_HOST_BYTES_PER_INSTRUCTION)) + const u32 free_code_space = GetFreeCodeSpace(); + const u32 free_far_code_space = GetFreeFarCodeSpace(); + if (free_code_space < (block_size * Recompiler::MAX_NEAR_HOST_BYTES_PER_INSTRUCTION) || + free_code_space < Recompiler::MIN_CODE_RESERVE_FOR_BLOCK || + free_far_code_space < Recompiler::MIN_CODE_RESERVE_FOR_BLOCK) { ERROR_LOG("Out of code space while compiling {:08X}. Resetting code cache.", start_pc); CodeCache::Reset(); @@ -1540,9 +1543,10 @@ void CPU::CodeCache::CompileASMFunctions() { MemMap::BeginCodeWrite(); -#if defined(_DEBUG) || defined(_DEVEL) +#ifdef DUMP_CODE_SIZE_STATS s_total_instructions_compiled = 0; s_total_host_instructions_emitted = 0; + s_total_host_code_used_by_instructions = 0; #endif const u32 asm_size = EmitASMFunctions(GetFreeCodePointer(), GetFreeCodeSpace()); @@ -1580,14 +1584,18 @@ bool CPU::CodeCache::CompileBlock(Block* block) const u32 host_instructions = GetHostInstructionCount(host_code, host_code_size); s_total_instructions_compiled += block->size; s_total_host_instructions_emitted += host_instructions; + s_total_host_code_used_by_instructions += host_code_size; - DEV_LOG("0x{:08X}: {}/{}b for {}b ({}i), blowup: {:.2f}x, cache: {:.2f}%/{:.2f}%, ipi: {:.2f}/{:.2f}", block->pc, - host_code_size, host_far_code_size, block->size * 4, block->size, - static_cast(host_code_size) / static_cast(block->size * 4), - (static_cast(s_code_used) / static_cast(s_code_size)) * 100.0f, - (static_cast(s_far_code_used) / static_cast(s_far_code_size)) * 100.0f, - static_cast(host_instructions) / static_cast(block->size), - static_cast(s_total_host_instructions_emitted) / static_cast(s_total_instructions_compiled)); + DEV_LOG( + "0x{:08X}: {}/{}b for {}b ({}i), blowup: {:.2f}x, cache: {:.2f}%/{:.2f}%, ipi: {:.2f}/{:.2f}, bpi: {:.2f}/{:.2f}", + block->pc, host_code_size, host_far_code_size, block->size * 4, block->size, + static_cast(host_code_size) / static_cast(block->size * 4), + (static_cast(s_code_used) / static_cast(s_code_size)) * 100.0f, + (static_cast(s_far_code_used) / static_cast(s_far_code_size)) * 100.0f, + static_cast(host_instructions) / static_cast(block->size), + static_cast(s_total_host_instructions_emitted) / static_cast(s_total_instructions_compiled), + static_cast(block->host_code_size) / static_cast(block->size), + static_cast(s_total_host_code_used_by_instructions) / static_cast(s_total_instructions_compiled)); #endif #if 0 diff --git a/src/core/cpu_recompiler.h b/src/core/cpu_recompiler.h index 625420767..9ae94962a 100644 --- a/src/core/cpu_recompiler.h +++ b/src/core/cpu_recompiler.h @@ -14,7 +14,6 @@ namespace CPU { -// TODO: Get rid of the virtuals... somehow. class Recompiler { public: @@ -26,8 +25,10 @@ public: #if defined(CPU_ARCH_X64) // A reasonable "maximum" number of bytes per instruction. - static constexpr u32 MAX_NEAR_HOST_BYTES_PER_INSTRUCTION = 64; - static constexpr u32 MAX_FAR_HOST_BYTES_PER_INSTRUCTION = 128; + // Seems to hover around ~21 bytes without PGXP, and ~26 bytes with. + // Use an upper bound of 32 bytes to be safe. + static constexpr u32 MAX_NEAR_HOST_BYTES_PER_INSTRUCTION = 32; + static constexpr u32 MIN_CODE_RESERVE_FOR_BLOCK = 512; // Number of host registers. static constexpr u32 NUM_HOST_REGS = 16; @@ -37,7 +38,7 @@ public: // A reasonable "maximum" number of bytes per instruction. static constexpr u32 MAX_NEAR_HOST_BYTES_PER_INSTRUCTION = 64; - static constexpr u32 MAX_FAR_HOST_BYTES_PER_INSTRUCTION = 128; + static constexpr u32 MIN_CODE_RESERVE_FOR_BLOCK = 512; // Number of host registers. static constexpr u32 NUM_HOST_REGS = 16; @@ -45,14 +46,16 @@ public: #elif defined(CPU_ARCH_ARM64) + // A reasonable "maximum" number of bytes per instruction. + // Seems to hover around ~24 bytes without PGXP, and ~40 bytes with. + // Use an upper bound of 48 bytes to be safe. + static constexpr u32 MAX_NEAR_HOST_BYTES_PER_INSTRUCTION = 48; + static constexpr u32 MIN_CODE_RESERVE_FOR_BLOCK = 512; + // Number of host registers. static constexpr u32 NUM_HOST_REGS = 32; static constexpr bool HAS_MEMORY_OPERANDS = false; - // A reasonable "maximum" number of bytes per instruction. - static constexpr u32 MAX_NEAR_HOST_BYTES_PER_INSTRUCTION = 64; - static constexpr u32 MAX_FAR_HOST_BYTES_PER_INSTRUCTION = 128; - #elif defined(CPU_ARCH_RISCV64) // Number of host registers. @@ -61,7 +64,7 @@ public: // A reasonable "maximum" number of bytes per instruction. static constexpr u32 MAX_NEAR_HOST_BYTES_PER_INSTRUCTION = 64; - static constexpr u32 MAX_FAR_HOST_BYTES_PER_INSTRUCTION = 128; + static constexpr u32 MIN_CODE_RESERVE_FOR_BLOCK = 512; #endif From 0a2facfaeb1f0cf5411005fbfaa12f4c41e99051 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Tue, 3 Dec 2024 16:54:40 +1000 Subject: [PATCH 59/69] Settings: Don't enable fastmem without recompiler Don't need to bother allocating memory otherwise. --- src/core/settings.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/settings.cpp b/src/core/settings.cpp index ef2cf778e..8b46de696 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -1012,6 +1012,10 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages) } #endif + // fastmem should be off if we're not using the intepreter, save the allocation + if (g_settings.cpu_execution_mode != CPUExecutionMode::Recompiler) + g_settings.cpu_fastmem_mode = CPUFastmemMode::Disabled; + if (g_settings.IsRunaheadEnabled() && g_settings.rewind_enable) { if (display_osd_messages) From 5b6e3a952cf6e15f46d8919772050d52a564183e Mon Sep 17 00:00:00 2001 From: Stenzek Date: Tue, 3 Dec 2024 16:55:00 +1000 Subject: [PATCH 60/69] System: Reset code cache on fastmem mode change Fixes excess backpatching and potential crashes when changing mode. --- src/core/settings.cpp | 2 +- src/core/system.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 8b46de696..feb4225fe 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -1012,7 +1012,7 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages) } #endif - // fastmem should be off if we're not using the intepreter, save the allocation + // fastmem should be off if we're not using the recompiler, save the allocation if (g_settings.cpu_execution_mode != CPUExecutionMode::Recompiler) g_settings.cpu_fastmem_mode = CPUFastmemMode::Disabled; diff --git a/src/core/system.cpp b/src/core/system.cpp index e508c2c24..14e484e83 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -4338,6 +4338,8 @@ void System::CheckForSettingsChanges(const Settings& old_settings) { // Reallocate fastmem area, even if it's not being used. Bus::RemapFastmemViews(); + CPU::CodeCache::Reset(); + InterruptExecution(); } SPU::GetOutputStream()->SetOutputVolume(GetAudioOutputVolume()); From d93c713fb76cd964ee5feb3910344b6dea0a400f Mon Sep 17 00:00:00 2001 From: Stenzek Date: Tue, 3 Dec 2024 17:03:52 +1000 Subject: [PATCH 61/69] FileSystem: Make POSIXLock moveable --- src/common/file_system.cpp | 16 ++++++++++++++++ src/common/file_system.h | 6 ++++++ 2 files changed, 22 insertions(+) diff --git a/src/common/file_system.cpp b/src/common/file_system.cpp index 364199b26..6184a231a 100644 --- a/src/common/file_system.cpp +++ b/src/common/file_system.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #ifdef __APPLE__ #include @@ -2823,6 +2824,10 @@ static bool SetLock(int fd, bool lock) return res; } +FileSystem::POSIXLock::POSIXLock() : m_fd(-1) +{ +} + FileSystem::POSIXLock::POSIXLock(int fd) : m_fd(fd) { if (!SetLock(m_fd, true)) @@ -2835,10 +2840,21 @@ FileSystem::POSIXLock::POSIXLock(std::FILE* fp) : m_fd(fileno(fp)) m_fd = -1; } +FileSystem::POSIXLock::POSIXLock(POSIXLock&& move) +{ + m_fd = std::exchange(move.m_fd, -1); +} + FileSystem::POSIXLock::~POSIXLock() { if (m_fd >= 0) SetLock(m_fd, false); } +FileSystem::POSIXLock& FileSystem::POSIXLock::operator=(POSIXLock&& move) +{ + m_fd = std::exchange(move.m_fd, -1); + return *this; +} + #endif diff --git a/src/common/file_system.h b/src/common/file_system.h index 7003001ea..5b816ce1d 100644 --- a/src/common/file_system.h +++ b/src/common/file_system.h @@ -159,10 +159,16 @@ void DiscardAtomicRenamedFile(AtomicRenamedFile& file); class POSIXLock { public: + POSIXLock(); POSIXLock(int fd); POSIXLock(std::FILE* fp); + POSIXLock(POSIXLock&& move); + POSIXLock(const POSIXLock&) = delete; ~POSIXLock(); + POSIXLock& operator=(POSIXLock&& move); + POSIXLock& operator=(const POSIXLock&) = delete; + private: int m_fd; }; From 04e472d0881df576eda3e1782babc70305971cef Mon Sep 17 00:00:00 2001 From: Stenzek Date: Tue, 3 Dec 2024 17:10:54 +1000 Subject: [PATCH 62/69] FileSystem: Add non-blocking option to POSIXLock --- src/common/file_system.cpp | 43 +++++++++++++++++++++++++++----------- src/common/file_system.h | 7 +++++-- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/common/file_system.cpp b/src/common/file_system.cpp index 6184a231a..20e757cb1 100644 --- a/src/common/file_system.cpp +++ b/src/common/file_system.cpp @@ -2788,19 +2788,25 @@ bool FileSystem::SetPathCompression(const char* path, bool enable) return false; } -static bool SetLock(int fd, bool lock) +static bool SetLock(int fd, bool lock, bool block, Error* error) { // We want to lock the whole file. const off_t offs = lseek(fd, 0, SEEK_CUR); if (offs < 0) { - ERROR_LOG("lseek({}) failed: {}", fd, errno); + if (error) + error->SetErrno("lseek() failed: ", errno); + else + ERROR_LOG("lseek({}) failed: {}", fd, errno); return false; } if (offs != 0 && lseek(fd, 0, SEEK_SET) < 0) { - ERROR_LOG("lseek({}, 0) failed: {}", fd, errno); + if (error) + error->SetErrno("lseek(0) failed: ", errno); + else + ERROR_LOG("lseek({}, 0) failed: {}", fd, errno); return false; } @@ -2808,19 +2814,24 @@ static bool SetLock(int fd, bool lock) bool res; for (;;) { - res = (lockf(fd, lock ? F_LOCK : F_ULOCK, 0) == 0); + res = (lockf(fd, lock ? (block ? F_TLOCK : F_LOCK) : F_ULOCK, 0) == 0); if (!res && errno == EINTR) continue; else break; } + if (!res) + { + if (error) + error->SetErrno("lockf() failed: ", errno); + else + ERROR_LOG("lockf() for {} failed: {}", lock ? "lock" : "unlock", errno); + } + if (lseek(fd, offs, SEEK_SET) < 0) Panic("Repositioning file descriptor after lock failed."); - if (!res) - ERROR_LOG("lockf() for {} failed: {}", lock ? "lock" : "unlock", errno); - return res; } @@ -2828,15 +2839,15 @@ FileSystem::POSIXLock::POSIXLock() : m_fd(-1) { } -FileSystem::POSIXLock::POSIXLock(int fd) : m_fd(fd) +FileSystem::POSIXLock::POSIXLock(int fd, bool block, Error* error) : m_fd(fd) { - if (!SetLock(m_fd, true)) + if (!SetLock(m_fd, true, block, error)) m_fd = -1; } -FileSystem::POSIXLock::POSIXLock(std::FILE* fp) : m_fd(fileno(fp)) +FileSystem::POSIXLock::POSIXLock(std::FILE* fp, bool block, Error* error) : m_fd(fileno(fp)) { - if (!SetLock(m_fd, true)) + if (!SetLock(m_fd, true, block, error)) m_fd = -1; } @@ -2846,9 +2857,17 @@ FileSystem::POSIXLock::POSIXLock(POSIXLock&& move) } FileSystem::POSIXLock::~POSIXLock() +{ + Unlock(); +} + +void FileSystem::POSIXLock::Unlock() { if (m_fd >= 0) - SetLock(m_fd, false); + { + SetLock(m_fd, false, true, nullptr); + m_fd = -1; + } } FileSystem::POSIXLock& FileSystem::POSIXLock::operator=(POSIXLock&& move) diff --git a/src/common/file_system.h b/src/common/file_system.h index 5b816ce1d..c87c1d658 100644 --- a/src/common/file_system.h +++ b/src/common/file_system.h @@ -160,8 +160,8 @@ class POSIXLock { public: POSIXLock(); - POSIXLock(int fd); - POSIXLock(std::FILE* fp); + POSIXLock(int fd, bool block = true, Error* error = nullptr); + POSIXLock(std::FILE* fp, bool block = true, Error* error = nullptr); POSIXLock(POSIXLock&& move); POSIXLock(const POSIXLock&) = delete; ~POSIXLock(); @@ -169,6 +169,9 @@ public: POSIXLock& operator=(POSIXLock&& move); POSIXLock& operator=(const POSIXLock&) = delete; + ALWAYS_INLINE bool IsLocked() const { return (m_fd >= 0); } + void Unlock(); + private: int m_fd; }; From 84a1e209eadf96e213a82bcb2a592978ce36ffa8 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Tue, 3 Dec 2024 17:17:00 +1000 Subject: [PATCH 63/69] OpenGLDevice: Lock pipeline cache on Linux Prevents multiple processes from trampling on one another. --- src/util/opengl_device.h | 12 ++++- src/util/opengl_pipeline.cpp | 88 ++++++++++++++++++++++++++---------- 2 files changed, 75 insertions(+), 25 deletions(-) diff --git a/src/util/opengl_device.h b/src/util/opengl_device.h index ba4ca9277..fa1cae9c9 100644 --- a/src/util/opengl_device.h +++ b/src/util/opengl_device.h @@ -11,11 +11,18 @@ #include "opengl_pipeline.h" #include "opengl_texture.h" -#include +#include "common/file_system.h" + #include #include #include +// Unix doesn't prevent concurrent write access, need to explicitly lock the pipeline cache. +// Don't worry about Android, it's not like you can run one more than one instance of the app there... +#if !defined(_WIN32) && !defined(__ANDROID__) +#define OPENGL_PIPELINE_CACHE_NEEDS_LOCK 1 +#endif + class OpenGLPipeline; class OpenGLStreamBuffer; class OpenGLTexture; @@ -232,6 +239,9 @@ private: bool m_timestamp_query_started = false; std::FILE* m_pipeline_disk_cache_file = nullptr; +#ifdef OPENGL_PIPELINE_CACHE_NEEDS_LOCK + FileSystem::POSIXLock m_pipeline_disk_cache_file_lock; +#endif u32 m_pipeline_disk_cache_data_end = 0; bool m_pipeline_disk_cache_changed = false; diff --git a/src/util/opengl_pipeline.cpp b/src/util/opengl_pipeline.cpp index 4f36aba73..ec31f4147 100644 --- a/src/util/opengl_pipeline.cpp +++ b/src/util/opengl_pipeline.cpp @@ -761,12 +761,22 @@ bool OpenGLDevice::OpenPipelineCache(const std::string& path, Error* error) { DebugAssert(!m_pipeline_disk_cache_file); - std::FILE* fp = FileSystem::OpenCFile(path.c_str(), "r+b", error); + auto fp = FileSystem::OpenManagedCFile(path.c_str(), "r+b", error); if (!fp) return false; +#ifdef OPENGL_PIPELINE_CACHE_NEEDS_LOCK + // Unix doesn't prevent concurrent write access, need to explicitly lock it. + FileSystem::POSIXLock fp_lock(fp.get(), true, error); + if (!fp_lock.IsLocked()) + { + Error::AddPrefix(error, "Failed to lock cache file: "); + return false; + } +#endif + // Read footer. - const s64 size = FileSystem::FSize64(fp); + const s64 size = FileSystem::FSize64(fp.get()); if (size < static_cast(sizeof(PipelineDiskCacheFooter)) || size >= static_cast(std::numeric_limits::max())) { @@ -775,11 +785,10 @@ bool OpenGLDevice::OpenPipelineCache(const std::string& path, Error* error) } PipelineDiskCacheFooter file_footer; - if (FileSystem::FSeek64(fp, size - sizeof(PipelineDiskCacheFooter), SEEK_SET) != 0 || - std::fread(&file_footer, sizeof(file_footer), 1, fp) != 1) + if (FileSystem::FSeek64(fp.get(), size - sizeof(PipelineDiskCacheFooter), SEEK_SET) != 0 || + std::fread(&file_footer, sizeof(file_footer), 1, fp.get()) != 1) { Error::SetStringView(error, "Invalid cache file footer."); - std::fclose(fp); return false; } @@ -795,16 +804,15 @@ bool OpenGLDevice::OpenPipelineCache(const std::string& path, Error* error) 0) { Error::SetStringView(error, "Cache does not match expected driver/version."); - std::fclose(fp); return false; } m_pipeline_disk_cache_data_end = static_cast(size) - sizeof(PipelineDiskCacheFooter) - (sizeof(PipelineDiskCacheIndexEntry) * file_footer.num_programs); - if (m_pipeline_disk_cache_data_end < 0 || FileSystem::FSeek64(fp, m_pipeline_disk_cache_data_end, SEEK_SET) != 0) + if (m_pipeline_disk_cache_data_end < 0 || + FileSystem::FSeek64(fp.get(), m_pipeline_disk_cache_data_end, SEEK_SET) != 0) { Error::SetStringView(error, "Failed to seek to start of index entries."); - std::fclose(fp); return false; } @@ -812,12 +820,11 @@ bool OpenGLDevice::OpenPipelineCache(const std::string& path, Error* error) for (u32 i = 0; i < file_footer.num_programs; i++) { PipelineDiskCacheIndexEntry entry; - if (std::fread(&entry, sizeof(entry), 1, fp) != 1 || + if (std::fread(&entry, sizeof(entry), 1, fp.get()) != 1 || (static_cast(entry.offset) + static_cast(entry.compressed_size)) >= size) { Error::SetStringView(error, "Failed to read disk cache entry."); m_program_cache.clear(); - std::fclose(fp); return false; } @@ -825,7 +832,6 @@ bool OpenGLDevice::OpenPipelineCache(const std::string& path, Error* error) { Error::SetStringView(error, "Duplicate program in disk cache."); m_program_cache.clear(); - std::fclose(fp); return false; } @@ -840,15 +846,42 @@ bool OpenGLDevice::OpenPipelineCache(const std::string& path, Error* error) } VERBOSE_LOG("Read {} programs from disk cache.", m_program_cache.size()); - m_pipeline_disk_cache_file = fp; + m_pipeline_disk_cache_file = fp.release(); +#ifdef OPENGL_PIPELINE_CACHE_NEEDS_LOCK + m_pipeline_disk_cache_file_lock = std::move(fp_lock); +#endif return true; } bool OpenGLDevice::CreatePipelineCache(const std::string& path, Error* error) { +#ifndef OPENGL_PIPELINE_CACHE_NEEDS_LOCK m_pipeline_disk_cache_file = FileSystem::OpenCFile(path.c_str(), "w+b", error); if (!m_pipeline_disk_cache_file) return false; +#else + // Manually truncate it, that way we don't blow away another process's file on Linux. + m_pipeline_disk_cache_file = FileSystem::OpenCFile(path.c_str(), "a+b", error); + if (!m_pipeline_disk_cache_file || !FileSystem::FSeek64(m_pipeline_disk_cache_file, 0, SEEK_SET, error)) + return false; + + m_pipeline_disk_cache_file_lock = FileSystem::POSIXLock(m_pipeline_disk_cache_file, true, error); + if (!m_pipeline_disk_cache_file_lock.IsLocked()) + { + Error::AddPrefix(error, "Failed to lock cache file: "); + std::fclose(m_pipeline_disk_cache_file); + m_pipeline_disk_cache_file = nullptr; + return false; + } + + if (!FileSystem::FTruncate64(m_pipeline_disk_cache_file, 0, error)) + { + Error::AddPrefix(error, "Failed to truncate cache file: "); + m_pipeline_disk_cache_file_lock = {}; + std::fclose(m_pipeline_disk_cache_file); + m_pipeline_disk_cache_file = nullptr; + } +#endif m_pipeline_disk_cache_data_end = 0; m_pipeline_disk_cache_changed = true; @@ -982,6 +1015,9 @@ bool OpenGLDevice::DiscardPipelineCache() if (!FileSystem::FTruncate64(m_pipeline_disk_cache_file, 0, &error)) { ERROR_LOG("Failed to truncate pipeline cache: {}", error.GetDescription()); +#ifdef OPENGL_PIPELINE_CACHE_NEEDS_LOCK + m_pipeline_disk_cache_file_lock.Unlock(); +#endif std::fclose(m_pipeline_disk_cache_file); m_pipeline_disk_cache_file = nullptr; return false; @@ -994,19 +1030,25 @@ bool OpenGLDevice::DiscardPipelineCache() bool OpenGLDevice::ClosePipelineCache(const std::string& filename, Error* error) { + const auto close_cache = [this]() { +#ifdef OPENGL_PIPELINE_CACHE_NEEDS_LOCK + m_pipeline_disk_cache_file_lock.Unlock(); +#endif + std::fclose(m_pipeline_disk_cache_file); + m_pipeline_disk_cache_file = nullptr; + }; + if (!m_pipeline_disk_cache_changed) { VERBOSE_LOG("Not updating pipeline cache because it has not changed."); - std::fclose(m_pipeline_disk_cache_file); - m_pipeline_disk_cache_file = nullptr; + close_cache(); return true; } // Rewrite footer/index entries. if (!FileSystem::FSeek64(m_pipeline_disk_cache_file, m_pipeline_disk_cache_data_end, SEEK_SET, error) != 0) { - std::fclose(m_pipeline_disk_cache_file); - m_pipeline_disk_cache_file = nullptr; + close_cache(); return false; } @@ -1027,8 +1069,7 @@ bool OpenGLDevice::ClosePipelineCache(const std::string& filename, Error* error) if (std::fwrite(&entry, sizeof(entry), 1, m_pipeline_disk_cache_file) != 1) [[unlikely]] { Error::SetErrno(error, "fwrite() for entry failed: ", errno); - std::fclose(m_pipeline_disk_cache_file); - m_pipeline_disk_cache_file = nullptr; + close_cache(); return false; } @@ -1039,15 +1080,14 @@ bool OpenGLDevice::ClosePipelineCache(const std::string& filename, Error* error) FillFooter(&footer, m_shader_cache.GetVersion()); footer.num_programs = count; - if (std::fwrite(&footer, sizeof(footer), 1, m_pipeline_disk_cache_file) != 1) [[unlikely]] + if (std::fwrite(&footer, sizeof(footer), 1, m_pipeline_disk_cache_file) != 1 || + std::fflush(m_pipeline_disk_cache_file) != 0) [[unlikely]] { Error::SetErrno(error, "fwrite() for footer failed: ", errno); - std::fclose(m_pipeline_disk_cache_file); - m_pipeline_disk_cache_file = nullptr; + close_cache(); + return false; } - if (std::fclose(m_pipeline_disk_cache_file) != 0) - Error::SetErrno(error, "fclose() failed: ", errno); - m_pipeline_disk_cache_file = nullptr; + close_cache(); return true; } From 3ca2579882f35e3a6a63e1ab711e13eb09e039e8 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Tue, 3 Dec 2024 19:03:16 +1000 Subject: [PATCH 64/69] Qt: Add additional early SSE4.1 check on Windows reshadefx uses roundss in std::unordered_map initializers, no other way to stop this. If it's not reshade, it'll probably be something else. --- src/duckstation-qt/vcruntimecheck.cpp | 39 +++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/duckstation-qt/vcruntimecheck.cpp b/src/duckstation-qt/vcruntimecheck.cpp index fc3debea9..2320a1040 100644 --- a/src/duckstation-qt/vcruntimecheck.cpp +++ b/src/duckstation-qt/vcruntimecheck.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 +#include "common/intrin.h" #include "common/windows_headers.h" #include @@ -15,10 +16,48 @@ static constexpr DWORD64 MIN_VERSION = MAKE_VERSION64(14, 38, 33135, 0); static constexpr const char* DOWNLOAD_URL = "https://aka.ms/vs/17/release/vc_redist.x64.exe"; +#ifdef CPU_ARCH_SSE41 + +// Can't rely on IsProcessorFeaturePresent(PF_SSE4_1_INSTRUCTIONS_AVAILABLE) because that was only added in Win10 2004, +// and you can bet that people with such ancient CPUs probably aren't running the latest OS versions either. +ALWAYS_INLINE static bool CheckCPUIDForSSE4() +{ + int result[4] = {}; + + __cpuid(result, 0); + const int max_function_id = result[0]; + if (max_function_id >= 1) + { + __cpuid(result, 1); + + // The presence of SSE4.1 is indicated by bit 19 of ECX. + return (result[2] & (1 << 19)) != 0; + } + + // Function 1 is not supported, so SSE4.1 cannot be present. + return false; +} + +#endif + struct VCRuntimeCheckObject { VCRuntimeCheckObject() { +#ifdef CPU_ARCH_SSE41 + // We could end up using SSE4 instructions in fmt etc too. Gotta check for it first. + if (!CheckCPUIDForSSE4()) + { + MessageBoxW(nullptr, + L"Your CPU does not support the SSE4.1 instruction set. SSE4.1 is required for this version of " + L"DuckStation. Please download and switch to the legacy SSE2 version. You can download this from " + L"www.duckstation.org under \"Other Platforms\".", + L"Hardware Check Failed", MB_OK); + TerminateProcess(GetCurrentProcess(), 0xFFFFFFFF); + return; + } +#endif + const HMODULE crt_handle = GetModuleHandleW(L"msvcp140.dll"); if (!crt_handle) return; From fe3b4154b7279598db2a2f9d61eff0bef5f80eac Mon Sep 17 00:00:00 2001 From: Stenzek Date: Tue, 3 Dec 2024 22:35:49 +1000 Subject: [PATCH 65/69] PostProcessing: Fix crash on UI open with OpenGL --- src/util/postprocessing_shader_fx.cpp | 56 ++++++++++++++------------- src/util/postprocessing_shader_fx.h | 3 +- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/util/postprocessing_shader_fx.cpp b/src/util/postprocessing_shader_fx.cpp index 797085327..301170e69 100644 --- a/src/util/postprocessing_shader_fx.cpp +++ b/src/util/postprocessing_shader_fx.cpp @@ -37,16 +37,6 @@ LOG_CHANNEL(ReShadeFXShader); static constexpr s32 DEFAULT_BUFFER_WIDTH = 3840; static constexpr s32 DEFAULT_BUFFER_HEIGHT = 2160; -static RenderAPI GetRenderAPI() -{ -#ifdef _WIN32 - static constexpr RenderAPI DEFAULT_RENDER_API = RenderAPI::D3D11; -#else - static constexpr RenderAPI DEFAULT_RENDER_API = RenderAPI::D3D12; -#endif - return g_gpu_device ? g_gpu_device->GetRenderAPI() : DEFAULT_RENDER_API; -} - static bool PreprocessorFileExistsCallback(const std::string& path) { if (Path::IsAbsolute(path)) @@ -69,12 +59,24 @@ static bool PreprocessorReadFileCallback(const std::string& path, std::string& d return true; } -static std::tuple, GPUShaderLanguage> CreateRFXCodegen() +static std::tuple, GPUShaderLanguage> CreateRFXCodegen(bool only_config) { - const bool debug_info = g_gpu_device ? g_gpu_device->IsDebugDevice() : false; - const bool uniforms_to_spec_constants = false; - const RenderAPI rapi = GetRenderAPI(); - [[maybe_unused]] const u32 rapi_version = g_gpu_device ? g_gpu_device->GetRenderAPIVersion() : 0; + constexpr bool uniforms_to_spec_constants = false; + + if (only_config) + { + // Use SPIR-V for obtaining config, it's the fastest to generate. + return std::make_tuple(std::unique_ptr( + reshadefx::create_codegen_spirv(true, false, uniforms_to_spec_constants, false, false)), + GPUShaderLanguage::SPV); + } + + // Should have a GPU device and be on the GPU thread. + Assert(g_gpu_device); + + const bool debug_info = g_gpu_device->IsDebugDevice(); + const RenderAPI rapi = g_gpu_device->GetRenderAPI(); + [[maybe_unused]] const u32 rapi_version = g_gpu_device->GetRenderAPIVersion(); switch (rapi) { @@ -331,11 +333,11 @@ bool PostProcessing::ReShadeFXShader::LoadFromString(std::string name, std::stri code.push_back('\n'); // TODO: This could use spv, it's probably fastest. - const auto& [cg, cg_language] = CreateRFXCodegen(); + const auto& [cg, cg_language] = CreateRFXCodegen(only_config); if (!CreateModule(only_config ? DEFAULT_BUFFER_WIDTH : g_gpu_device->GetMainSwapChain()->GetWidth(), only_config ? DEFAULT_BUFFER_HEIGHT : g_gpu_device->GetMainSwapChain()->GetHeight(), cg.get(), - std::move(code), error)) + cg_language, std::move(code), error)) { return false; } @@ -401,7 +403,7 @@ bool PostProcessing::ReShadeFXShader::WantsDepthBuffer() const } bool PostProcessing::ReShadeFXShader::CreateModule(s32 buffer_width, s32 buffer_height, reshadefx::codegen* cg, - std::string code, Error* error) + GPUShaderLanguage cg_language, std::string code, Error* error) { reshadefx::preprocessor pp; pp.set_include_callbacks(PreprocessorFileExistsCallback, PreprocessorReadFileCallback); @@ -433,17 +435,17 @@ bool PostProcessing::ReShadeFXShader::CreateModule(s32 buffer_width, s32 buffer_ pp.add_macro_definition("RESHADE_DEPTH_LINEARIZATION_FAR_PLANE", "1000.0"); pp.add_macro_definition("RESHADE_DEPTH_INPUT_IS_REVERSED", "0"); - switch (GetRenderAPI()) + switch (cg_language) { - case RenderAPI::D3D11: - case RenderAPI::D3D12: + case GPUShaderLanguage::HLSL: pp.add_macro_definition("__RENDERER__", "0x0B000"); break; - case RenderAPI::OpenGL: - case RenderAPI::OpenGLES: - case RenderAPI::Vulkan: - case RenderAPI::Metal: + case GPUShaderLanguage::GLSL: + case GPUShaderLanguage::GLSLES: + case GPUShaderLanguage::GLSLVK: + case GPUShaderLanguage::MSL: + case GPUShaderLanguage::SPV: pp.add_macro_definition("__RENDERER__", "0x14300"); break; @@ -1338,10 +1340,10 @@ bool PostProcessing::ReShadeFXShader::CompilePipeline(GPUTexture::Format format, if (fxcode.empty() || fxcode.back() != '\n') fxcode.push_back('\n'); - const auto& [cg, cg_language] = CreateRFXCodegen(); + const auto& [cg, cg_language] = CreateRFXCodegen(false); Error error; - if (!CreateModule(width, height, cg.get(), std::move(fxcode), &error)) + if (!CreateModule(width, height, cg.get(), cg_language, std::move(fxcode), &error)) { ERROR_LOG("Failed to create module for '{}': {}", m_name, error.GetDescription()); return false; diff --git a/src/util/postprocessing_shader_fx.h b/src/util/postprocessing_shader_fx.h index 79089541b..286026d14 100644 --- a/src/util/postprocessing_shader_fx.h +++ b/src/util/postprocessing_shader_fx.h @@ -98,7 +98,8 @@ private: ShaderOption::ValueVector value; }; - bool CreateModule(s32 buffer_width, s32 buffer_height, reshadefx::codegen* cg, std::string code, Error* error); + bool CreateModule(s32 buffer_width, s32 buffer_height, reshadefx::codegen* cg, GPUShaderLanguage cg_language, + std::string code, Error* error); bool CreateOptions(const reshadefx::effect_module& mod, Error* error); bool GetSourceOption(const reshadefx::uniform& ui, SourceOptionType* si, Error* error); bool CreatePasses(GPUTexture::Format backbuffer_format, const reshadefx::effect_module& mod, Error* error); From 20df4ec14ebb67314325bb4bdf3899b6e1efe269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20=22IlDucci?= Date: Thu, 5 Dec 2024 06:30:09 +0000 Subject: [PATCH 66/69] Spanish (Spain) update 2024/12/03 (#3340) Translation of latest changes. --- .../translations/duckstation-qt_es-ES.ts | 3708 +++++++++-------- 1 file changed, 1966 insertions(+), 1742 deletions(-) diff --git a/src/duckstation-qt/translations/duckstation-qt_es-ES.ts b/src/duckstation-qt/translations/duckstation-qt_es-ES.ts index 8feda92c5..8693bc386 100644 --- a/src/duckstation-qt/translations/duckstation-qt_es-ES.ts +++ b/src/duckstation-qt/translations/duckstation-qt_es-ES.ts @@ -170,47 +170,45 @@ No obstante, el modo «hardcore» también impide el uso de los guardados rápid AchievementSettingsWidget - + Enable Achievements Logros - - Use First Disc From Playlist - Usar el primer disco de la lista + Usar el primer disco de la lista - + Enable Hardcore Mode Modo «hardcore» - + Test Unofficial Achievements Probar logros no oficiales - - + + Enable Sound Effects Efectos de sonido - + Account Cuenta - - + + Login... Iniciar sesión... - + View Profile... Ver perfil... @@ -221,138 +219,136 @@ No obstante, el modo «hardcore» también impide el uso de los guardados rápid - + Enable Spectator Mode Habilitar modo espectador - + Enable Encore Mode Habilitar modo «encore» (nueva partida) - + Notifications Notificaciones - - + + 5 seconds 5 segundos - - + + Show Achievement Notifications Mostrar notificaciones de logros - - + + Show Leaderboard Notifications Mostrar notificaciones de tablas - - + + Enable In-Game Overlays Superposiciones dentro del juego - + Username: Login token generated at: Nombre de usuario: Fecha de creación del token de acceso: - + Game Info Información del juego + - + - - - + Unchecked Deshabilitado - + When enabled and logged in, DuckStation will scan for achievements on startup. Al activar esta opción e iniciar una sesión, DuckStation buscará logros al arrancar. - + Displays popup messages on events such as achievement unlocks and game completion. Muestra mensajes emergentes en ciertas situaciones, como el desbloqueo de logros o al pasarte un juego. - + Displays popup messages when starting, submitting, or failing a leaderboard challenge. Muestra mensajes emergentes al activar, enviar o fracasar un desafío de una tabla de clasificación. - + When enabled, each session will behave as if no achievements have been unlocked. Al activar esta opción, cada sesión de juego se comportará como si no se hubiesen desbloqueado logros. - + When enabled, DuckStation will assume all achievements are locked and not send any unlock notifications to the server. Al activar esta opción, DuckStation asumirá que todos los logros están bloqueados y no enviará notificaciones de desbloqueo al servidor. - + When enabled, DuckStation will list achievements from unofficial sets. Please note that these achievements are not tracked by RetroAchievements, so they unlock every time. Al activar esta opción, DuckStation mostrará los logros de colecciones no oficiales. Ten en cuenta que RetroAchievements no hace un seguimiento de estos logros, así que se desbloquearán constantemente. - When enabled, the first disc in a playlist will be used for achievements, regardless of which disc is active. - Si esta opción está habilitada, se utilizará el primer disco de una lista para buscar logros, independientemente del disco que esté activo. + Si esta opción está habilitada, se utilizará el primer disco de una lista para buscar logros, independientemente del disco que esté activo. - + "Challenge" mode for achievements, including leaderboard tracking. Disables save state, cheats, and slowdown functions. El modo más desafiante, que incluye un seguimiento de las tablas de clasificación. Desactiva las características de guardados rápidos, trucos y ralentización. - - - - + + + + Checked Habilitado - + Plays sound effects for events such as achievement unlocks and leaderboard submissions. Reproduce efectos de sonido en ciertas situaciones, como el desbloqueo de logros y el envío de puntuaciones a las tablas. - + Shows icons in the lower-right corner of the screen when a challenge/primed achievement is active. Muestra iconos en la esquina inferior derecha de la pantalla cuando haya un logro activo o un desafío. - + Reset System Reiniciar sistema - + Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now? El modo «hardcore» no se activará hasta que se reinicie el sistema. ¿Deseas reiniciar ahora? - - + + %n seconds % segundo @@ -360,19 +356,19 @@ Fecha de creación del token de acceso: - + Username: %1 Login token generated on %2. Nombre de usuario: %1 Fecha de creación del token de acceso: %2. - + Logout Cerrar sesión - + Not Logged In. No se ha iniciado sesión. @@ -380,40 +376,40 @@ Fecha de creación del token de acceso: %2. Achievements - - + + Overriding executable Omitiendo ejecutable - + Loading state carga de guardados rápidos - - + + Resuming state guardados rápidos de continuación - + Hardcore mode will be enabled on system reset. El modo «hardcore» se activará al reiniciar el sistema. - + {} (Unofficial) {} (no oficial) - + Mastered {} Has dominado {} - - + + %n points Achievement points @@ -422,86 +418,86 @@ Fecha de creación del token de acceso: %2. - + Leaderboard attempt started. Intento de entrar en tabla de clasificación iniciado. - + Leaderboard attempt failed. Intento de entrar en tabla de clasificación fallido. - + Your Time: {}{} Tu tiempo: {}{} - + Your Score: {}{} Tu puntuación: {}{} - + Your Value: {}{} Tu valor: {}{} - + (Submitting) (enviando) - + Your Time: {} (Best: {}) Tu tiempo: {} (Mejor: {}) - + Your Score: {} (Best: {}) Tu puntuación: {} (Mejor: {}) - + Your Value: {} (Best: {}) Tu valor: {} (Mejor: {}) - + {} Leaderboard Position: {} of {} {} Posición en tabla: {} de {} - + Server error in {}: {} Error del servidor en {}: {} - + Achievements Disconnected Logros desconectados - + An unlock request could not be completed. We will keep retrying to submit this request. No se ha podido completar una petición de desbloqueo. La petición seguirá reenviándose. - + Achievements Reconnected Logros reconectados - + All pending unlock requests have completed. Se han completado todas las peticiones de desbloqueo pendientes. - + Score: {} ({} softcore) Unread messages: {} Summary for login notification. @@ -509,114 +505,114 @@ Unread messages: {} Mensajes sin leer: {} - - + + Confirm Hardcore Mode Confirmar modo «hardcore» - - + + {0} cannot be performed while hardcore mode is active. Do you want to disable hardcore mode? {0} will be cancelled if you select No. No se puede ejecutar la característica de {0} mientras el modo «hardcore» esté activo. ¿Deseas desactivar el modo «hardcore»? Si seleccionas No, se cancelará la acción. - - + + Cannot {} while hardcode mode is active. No se puede ejecutar la característica de {0} mientras el modo «hardcore» esté activo. - + Yes - + No No - + Active Challenge Achievements Logros de desafío activos - + (Hardcore Mode) (modo «hardcore») - + Unknown Desconocidos - + Locked Bloqueados - + Unlocked Desbloqueados - + Unsupported No admitidoNo admitidos - + Unofficial No oficiales - + Recently Unlocked Desbloqueados recientemente - + Active Challenges Desafíos activos - + Almost There A punto de desbloquearse - - - + + + Change Selection Cambiar la selección - + View Details Más información - - - + + + Back Volver - + XXX points XXX puntos - + {0}, {1}. {0} {1}. - + You have unlocked {} of %n achievements Achievement popup @@ -625,7 +621,7 @@ Mensajes sin leer: {} - + and earned {} of %n points Achievement popup @@ -634,7 +630,7 @@ Mensajes sin leer: {} - + You have unlocked all achievements and earned %n points! Point count @@ -643,12 +639,12 @@ Mensajes sin leer: {} - + Unlocked: {} Desbloqueado: {} - + This game has %n leaderboards. Leaderboard count @@ -657,61 +653,61 @@ Mensajes sin leer: {} - - + + Loading... Cargando... - + Change Page Cambiar página - + View Profile Ver perfil - - + + Leaderboard download failed Error al descargar la tabla de clasificación - + Hardcore mode is now enabled. El modo «hardcore» está activado. - + Hardcore mode is now disabled. El modo «hardcore» está desactivado. - + {} (Hardcore Mode) {} (modo «hardcore») - - + + Failed to read executable from disc. Achievements disabled. Error al leer el ejecutable del disco. Logros desactivados. - - + + This game has no achievements. No hay logros para este juego. - + {0}, {1} {0}, {1} - + %n achievements Mastery popup @@ -720,62 +716,62 @@ Mensajes sin leer: {} - + You have unlocked {0} of {1} achievements, earning {2} of {3} possible points. Has obtenido {0} de {1} logros y {2} de {3} puntos. - + Submitting scores is disabled because hardcore mode is off. Leaderboards are read-only. No se enviarán puntuaciones porque el modo «hardcore» está desactivado. Las tablas de puntuaciones están en modo de solo lectura. - + Show Best Mostrar primeros - + Show Nearby Mostrar cercanos - + Rank Rango - + Name Nombre - + Time Tiempo - + Score Puntuación - + Value Valor - + Date Submitted Fecha de envío - + Open Leaderboard Abrir tabla de clasificación - + Downloading leaderboard data, please wait... Descargando datos de tablas de puntuaciones, espera... @@ -1055,241 +1051,241 @@ Mensajes sin leer: {} AnalogController - + Controller {} switched to analog mode. Mando {} cambiado a modo analógico. - + Controller {} switched to digital mode. Mando {} cambiado a modo digital. - + Controller {} is locked to analog mode by the game. Mando {} bloqueado en modo analógico por el juego. - + Controller {} is locked to digital mode by the game. Mando {} bloqueado en modo digital por el juego. - + D-Pad Up Botón de dirección hacia arriba - + D-Pad Right Botón de dirección hacia la derecha - + D-Pad Down Botón de dirección hacia abajo - + D-Pad Left Botón de dirección hacia la izquierda - + Triangle Triángulo - + Circle Círculo - + Cross Cruz/X - + Square Cuadrado - + Select SELECT - + Start START - + Analog Toggle Alternar función analógica - + L1 L1 - + R1 R1 - + L2 L2 - + R2 R2 - + L3 L3 - + R3 R3 - + Left Stick Left Joystick izquierdo hacia la izquierda - + Left Stick Right Joystick izquierdo hacia la derecha - + Left Stick Down Joystick izquierdo hacia abajo - + Left Stick Up Joystick izquierdo hacia arriba - + Right Stick Left Joystick derecho hacia la izquierda - + Right Stick Right Joystick derecho hacia la derecha - + Right Stick Down Joystick derecho hacia abajo - + Right Stick Up Joystick derecho hacia arriba - + Not Inverted No invertir - + Invert Left/Right Invertir izquierda/derecha - + Invert Up/Down Invertir arriba/abajo - + Invert Left/Right + Up/Down Invertir izquierda/derecha + arriba/abajo - + Force Analog Mode on Reset Forzar el modo analógico al reiniciar - + Forces the controller to analog mode when the console is reset/powered on. Fuerza el modo analógico en el mando cuando la consola se reinicie/encienda. - + Use Analog Sticks for D-Pad in Digital Mode Usar los joysticks analógicos como cruceta en el modo digital - + Allows you to use the analog sticks to control the d-pad in digital mode, as well as the buttons. Permite usar los joysticks analógicos para controlar la cruceta y los botones en el modo digital. - + Analog Deadzone Zona muerta del joystick analógico - + Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored. - Establece la zona muerta del joystick analógico, es decir, cuánto movimiento del joystick se ignorará. + Establece la zona de inactividad del joystick analógico, es decir, cuánto movimiento del joystick se ignorará. - + Analog Sensitivity Sensibilidad analógica - + Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent controllers, e.g. DualShock 4, Xbox One Controller. Establece el factor de escalado para los ejes de los joysticks analógicos. Se recomienda entre 130 % y 140 % para mandos modernos, como el DualShock 4 o el mando de Xbox One. - + Button/Trigger Deadzone Zona muerta de botón/gatillo - + Sets the deadzone for activating buttons/triggers, i.e. the fraction of the trigger which will be ignored. Establece la zona de inactividad para activar botones o gatillos, es decir, la distancia de la pulsación que será ignorada. - + Large Motor Vibration Bias Margen de error de la vibración del motor grande - + Sets the bias value for the large vibration motor. If vibration in some games is too weak or not functioning, try increasing this value. Negative values will decrease the intensity of vibration. Establece el margen de error para la vibración del motor grande. Si en algunos juegos la vibración es muy débil o no funciona, prueba a aumentar este valor. Un valor negativo reducirá la intensidad de la vibración. - + Small Motor Vibration Bias Margen de error de la vibración del motor pequeño - + Sets the bias value for the small vibration motor. If vibration in some games is too weak or not functioning, try increasing this value. Negative values will decrease the intensity of vibration. Establece el margen de error para la vibración del motor pequeño. Si en algunos juegos la vibración es muy débil o no funciona, prueba a aumentar este valor. Un valor negativo reducirá la intensidad de la vibración. @@ -1302,22 +1298,22 @@ Mensajes sin leer: {} Indica la medida de la vibración. Si la vibración en algunos juegos es débil o no funciona, intenta incrementar este valor. - + Invert Left Stick Invertir joystick izquierdo - + Inverts the direction of the left analog stick. Invierte la dirección del joystick analógico izquierdo. - + Invert Right Stick Invertir joystick derecho - + Inverts the direction of the right analog stick. Invierte la dirección del joystick analógico derecho. @@ -1477,7 +1473,7 @@ Mensajes sin leer: {} Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored. - Establece la zona muerta del joystick analógico, es decir, cuánto movimiento del joystick será ignorado. + Establece la zona de inactividad del joystick analógico, es decir, cuánto movimiento del joystick se ignorará. @@ -1984,8 +1980,8 @@ Mensajes sin leer: {} AutoUpdaterDialog - - + + Automatic Updater Actualizador automático @@ -2020,70 +2016,76 @@ Mensajes sin leer: {} Recordar más tarde - + Do not show again No volver a mostrar - - + + Updater Error Error de actualización - + No updates are currently available. Please try again later. No hay actualizaciones disponibles. Vuelve a intentarlo más tarde. - + Current Version: %1 (%2) Versión actual: %1 (%2) - + New Version: %1 (%2) Versión nueva: %1 (%2) - + Download... Descargando... - + Loading... Cargando... - + <h2>Changes:</h2> <h2>Cambios:</h2> - + <h2>Save State Warning</h2><p>Installing this update will make your save states <b>incompatible</b>. Please ensure you have saved your games to memory card before installing this update or you will lose progress.</p> <h2>Advertencia sobre los guardados rápidos</h2><p>La instalación de esta actualización hará que tus guardados rápidos <b>dejen de ser compatibles</b>. Asegúrate de haber guardado tus avances en una Memory Card antes de instalar esta actualización, o perderás dichos avances.</p> - + <h2>Settings Warning</h2><p>Installing this update will reset your program configuration. Please note that you will have to reconfigure your settings after this update.</p> <h2>Alerta sobre configuración</h2><p>Esta actualización reiniciará la configuración del programa. Ten en cuenta que deberás reajustar toda la configuración de nuevo cuando haya terminado la actualización.</p> - + <h4>Installing this update will download %1 MB through your internet connection.</h4> <h4>La instalación de esta actualización necesita descargar %1 MB a través de tu conexión de internet.</h4> - + Downloading %1... Descargando %1... - + + Failed to remove updater exe after update: +%1 + Error al eliminar el ejecutable del actualizador tras la actualización: +%1 + + Failed to remove updater exe after update. - Error al eliminar el ejecutable del actualizador tras la actualización. + Error al eliminar el ejecutable del actualizador tras la actualización. @@ -2474,22 +2476,22 @@ Mensajes sin leer: {} Cheats - + Gameshark GameShark - + Manual Manual - + Automatic (Frame End) Automática (al acabar el fotograma) - + %n game patches are active. OSD Message @@ -2498,7 +2500,7 @@ Mensajes sin leer: {} - + %n cheats are enabled. This may crash games. OSD Message @@ -2507,12 +2509,12 @@ Mensajes sin leer: {} - + No cheats/patches are found or enabled. No se han encontrado o activado trucos/parches. - + Cheat '{}' applied. Truco «{}» aplicado. @@ -2921,7 +2923,7 @@ Esta advertencia se mostrará solo una vez. Controller - + @@ -2929,13 +2931,23 @@ Esta advertencia se mostrará solo una vez. Mando {} cambiado a modo analógico. - + Controller {} switched to digital mode. Mando {} cambiado a modo digital. + + + Controller {} switched to JogCon mode. + Mando {} cambiado a modo JogCon. + + + + Controller {} switched to Digital mode. + Mando {} cambiado a modo digital. + ControllerBindingWidget @@ -2987,12 +2999,28 @@ Esta advertencia se mostrará solo una vez. No se han podido generar asociaciones genéricas para el dispositivo «%1». El mando/dispositivo de origen podría no ser compatible con las asociaciones automáticas. - + + Axes Ejes - + + Large Motor + Motor grande + + + + Vibration + Vibración + + + + Small Motor + Motor pequeño + + + Buttons Botones @@ -3789,17 +3817,17 @@ Esta advertencia se mostrará solo una vez. ControllerCustomSettingsWidget - + Restore Default Settings Restablecer valores predeterminados - + Browse... Buscar... - + Select File Seleccionar archivo @@ -4060,33 +4088,33 @@ Esta advertencia se mostrará solo una vez. Establecer... - + Not Configured Sin configurar - - + + %1% %1 % - + Set Frequency Establecer frecuencia - + Frequency: Frecuencia: - + Macro will not repeat. La macro no se repetirá. - + Macro will toggle buttons every %1 frames. La macro alternará pulsaciones cada %1 fotogramas. @@ -4094,12 +4122,12 @@ Esta advertencia se mostrará solo una vez. ControllerMacroWidget - + Controller Port %1 Macros Macros del puerto de mando %1 - + Macro %1 %2 Macro %1 @@ -4309,17 +4337,17 @@ Esta acción no puede deshacerse. NeGcon - + Analog Controller Mando analógico - + GunCon GunCon - + Not Connected Sin conectar @@ -4342,6 +4370,11 @@ Esta acción no puede deshacerse. NeGcon (Rumble) NeGcon con vibración + + + JogCon + JogCon + CoverDownloadDialog @@ -5213,37 +5246,37 @@ Este archivo puede llegar a ocupar varios gigabytes, así que ten cuidado con el EmuThread - - - - - - + + + + + + Error Error - + Failed to boot system: %1 Error al arrancar el sistema: %1 - + Failed to load state: %1 Error al cargar el guardado rápido: %1 - + No resume save state found. No se han encontrado guardados de continuación. - + Memory Card Busy Memory Card ocupada - + WARNING: Your game is still saving to the memory card. Continuing to %1 may IRREVERSIBLY DESTROY YOUR MEMORY CARD. We recommend resuming your game and waiting 5 seconds for it to finish saving. Do you want to %1 anyway? @@ -5252,60 +5285,60 @@ Do you want to %1 anyway? ¿Deseas %1? - + shut down apagar - + reset reiniciar - + change disc cambiar de disco - + Failed to switch to subimage %1 Error al cambiar a la subimagen %1. - - + + Failed to save state: %1 Error al crear el guardado rápido: %1 - + Game: %1 (%2) Juego: %1 (%2) - + Rich presence inactive or unsupported. «Rich Presence» inactiva o no compatible. - + Game not loaded or no RetroAchievements available. No se ha cargado un juego o RetroAchievements no está disponible. - + %1x%2 %1 × %2 - + Game: %1 FPS Juego: %1 FPS - + Video: %1 FPS (%2%) Vídeo: %1 FPS (%2 %) @@ -5727,322 +5760,322 @@ Do you want to %1 anyway? FullscreenUI - + 1 Frame 1 fotograma - + 10 Frames 10 fotogramas - + 100% [60 FPS (NTSC) / 50 FPS (PAL)] 100 % [60 FPS (NTSC) / 50 FPS (PAL)] - + 1000% [600 FPS (NTSC) / 500 FPS (PAL)] 1000 % [600 FPS (NTSC) / 500 FPS (PAL)] - + 10x 10x - + 10x (20x Speed) 10x (velocidad 20x) - + 11x 11x - + 125% [75 FPS (NTSC) / 62 FPS (PAL)] 125 % [75 FPS (NTSC) / 62 FPS (PAL)] - + 12x 12x - + 13x 13x - + 14x 14x - + 150% [90 FPS (NTSC) / 75 FPS (PAL)] 150 % [90 FPS (NTSC) / 75 FPS (PAL)] - + 15x 15x - + 16x 16x - + 175% [105 FPS (NTSC) / 87 FPS (PAL)] 175 % [105 FPS (NTSC) / 87 FPS (PAL)] - + 1x 1x - + 2 Frames 2 fotogramas - + 20% [12 FPS (NTSC) / 10 FPS (PAL)] 20 % [12 FPS (NTSC) / 10 FPS (PAL)] - + 200% [120 FPS (NTSC) / 100 FPS (PAL)] 200 % [120 FPS (NTSC) / 100 FPS (PAL)] - + 250% [150 FPS (NTSC) / 125 FPS (PAL)] 250 % [150 FPS (NTSC) / 125 FPS (PAL)] - + 2x 2x - + 2x (Quad Speed) 2x (velocidad x4) - + 3 Frames 3 fotogramas - + 30% [18 FPS (NTSC) / 15 FPS (PAL)] 30 % [18 FPS (NTSC) / 15 FPS (PAL)] - + 300% [180 FPS (NTSC) / 150 FPS (PAL)] 300 % [180 FPS (NTSC) / 150 FPS (PAL)] - + 350% [210 FPS (NTSC) / 175 FPS (PAL)] 350 % [210 FPS (NTSC) / 175 FPS (PAL)] - + 3x 3x - + 3x (6x Speed) 3x (velocidad 6x) - + 3x (for 720p) 3x (para 720p) - + 4 Frames 4 fotogramas - + 40% [24 FPS (NTSC) / 20 FPS (PAL)] 40 % [24 FPS (NTSC) / 20 FPS (PAL)] - + 400% [240 FPS (NTSC) / 200 FPS (PAL)] 400 % [240 FPS (NTSC) / 200 FPS (PAL)] - + 450% [270 FPS (NTSC) / 225 FPS (PAL)] 450 % [270 FPS (NTSC) / 225 FPS (PAL)] - + 4x 4x - + 4x (8x Speed) 4x (velocidad 8x) - + 5 Frames 5 fotogramas - + 50% [30 FPS (NTSC) / 25 FPS (PAL)] 50 % [30 FPS (NTSC) / 25 FPS (PAL)] - + 500% [300 FPS (NTSC) / 250 FPS (PAL)] 500 % [300 FPS (NTSC) / 250 FPS (PAL)] - + 5x 5x - + 5x (10x Speed) 5x (velocidad 10x) - + 5x (for 1080p) 5x (para 1080p) - + 6 Frames 6 fotogramas - + 60% [36 FPS (NTSC) / 30 FPS (PAL)] 60 % [36 FPS (NTSC) / 30 FPS (PAL)] - + 600% [360 FPS (NTSC) / 300 FPS (PAL)] 600 % [360 FPS (NTSC) / 300 FPS (PAL)] - + 6x 6x - + 6x (12x Speed) 6x (velocidad 12x) - + 6x (for 1440p) 6x (para 1440p) - + 7 Frames 7 fotogramas - + 70% [42 FPS (NTSC) / 35 FPS (PAL)] 70 % [42 FPS (NTSC) / 35 FPS (PAL)] - + 700% [420 FPS (NTSC) / 350 FPS (PAL)] 700 % [420 FPS (NTSC) / 350 FPS (PAL)] - + 7x 7x - + 7x (14x Speed) 7x (velocidad 14x) - + 8 Frames 8 fotogramas - + 80% [48 FPS (NTSC) / 40 FPS (PAL)] 80 % [48 FPS (NTSC) / 40 FPS (PAL)] - + 800% [480 FPS (NTSC) / 400 FPS (PAL)] 800 % [480 FPS (NTSC) / 400 FPS (PAL)] - + 8x 8x - + 8x (16x Speed) 8x (velocidad 16x) - + 9 Frames 9 fotogramas - + 90% [54 FPS (NTSC) / 45 FPS (PAL)] 90 % [54 FPS (NTSC) / 45 FPS (PAL)] - + 900% [540 FPS (NTSC) / 450 FPS (PAL)] 900 % [540 FPS (NTSC) / 450 FPS (PAL)] - + 9x 9x - + 9x (18x Speed) 9x (velocidad 18x) - + 9x (for 4K) 9x (para 4K) - + A resume save state created at %s was found. Do you want to load this save and continue? @@ -6051,129 +6084,129 @@ Do you want to load this save and continue? ¿Deseas cargar este guardado rápido? - + About Acerca de - + About DuckStation Acerca de DuckStation - + Account Cuenta - + Accurate Blending Mezcla precisa - + Achievement Notifications Notificaciones sobre logros - + Achievements Logros - + Achievements Settings Configuración de logros - + Add Search Directory Añadir directorio de búsqueda - + Add Shader Añadir sombreador - + Adds a new directory to the game search list. Añade un directorio nuevo a la lista de búsqueda de juegos. - + Adds a new shader to the chain. Añade un sombreador nuevo a la cadena. - + Adds additional precision to PGXP data post-projection. May improve visuals in some games. Añade una precisión adicional a la posproyección de datos de la PGXP. Puede mejorar los gráficos de algunos juegos. - + Achievements are not enabled. Los logros están desactivados. - + %.2f Seconds %.2f segundos - + %d Frames %d fotogramas - + %d ms %d ms - + %d sectors %d sectores - + - - - + Advanced Avanzado - + Advanced Settings Configuración avanzada - + All Time: {} Absoluto: {} - + Allow Booting Without SBI File Arrancar sin un archivo SBI - + Allows loading protected games without subchannel information. Permite cargar juegos protegidos sin la información del subcanal. - + An error occurred while deleting empty game settings: {} Se ha producido un error al eliminar una configuración de juego en blanco: {} - + An error occurred while saving game settings: {} Se ha producido un error al guardar la configuración de juego: @@ -6184,197 +6217,197 @@ Do you want to load this save and continue? Aplica técnicas modernas de tramado para suavizar más todavía los degradados al activar el modo a color verdadero. - + Apply Image Patches Aplicar parches de imagen - + Are you sure you want to clear the current post-processing chain? All configuration will be lost. ¿Seguro que quieres borrar la cadena de posprocesado actual? Se perderá toda la configuración. - + Aspect Ratio Relación de aspecto - + Attempts to detect one pixel high/wide lines that rely on non-upscaled rasterization behavior, filling in gaps introduced by upscaling. Intenta detectar líneas de un píxel de alto/ancho que utilicen métodos de rasterización no escalados para rellenar los huecos provocados por el escalado. - + Attempts to map the selected port to a chosen controller. Intenta asignar el puerto seleccionado a un mando concreto. - + Audio Backend Motor de audio - + Audio Control Control de audio - + Audio Settings Configuración de audio - + Auto-Detect Detección automática - + Automatic Mapping Asignación automática - + Automatic based on window size Automática según el tamaño de la ventana - + Automatic mapping completed for {}. Asignación automática de {} completada. - + Automatic mapping failed for {}. Error en la asignación automática de {}. - + Automatic mapping failed, no devices are available. Error en la asignación automática: no hay dispositivos disponibles. - + Automatically Resize Window Cambiar automáticamente el tamaño de ventana - + Automatically applies patches to disc images when they are present, currently only PPF is supported. Aplica parches automáticamente a las imágenes del disco cuando estén presentes, actualmente solo los parches PPF son compatibles. - + Automatically resizes the window to match the internal resolution. Cambia automáticamente el tamaño de la ventana al de la resolución interna. - + Automatically saves the emulator state when powering down or exiting. You can then resume directly from where you left off next time. Guarda automáticamente el estado del emulador al apagar o salir. Después podrás continuar la partida donde la dejaste. - + Automatically switches to fullscreen mode when the program is started. Cambia automáticamente al modo a pantalla completa cuando se inicie el programa. - + Avoids calls to C++ code, significantly speeding up the recompiler. Evita las llamadas a código en C++, lo que mejorará significativamente la velocidad del recompilador. - + BIOS Directory Directorio de BIOS - + BIOS Selection Selección de BIOS - + BIOS Settings Configuración de BIOS - + BIOS for {} BIOS para {} - + BIOS to use when emulating {} consoles. La BIOS que se utilizará al emular consolas {}. - + Back Volver - + Back To Pause Menu Volver al menú de pausa - + Backend Settings Configuración del motor - + Behavior Comportamiento - + Borderless Fullscreen Pantalla completa sin bordes - + Buffer Size Tamaño de búfer - + Buttons Botones - + CD-ROM Emulation Emulación de CD-ROM - + CPU Emulation Emulación de CPU - + CPU Mode Modo de CPU - + Cancel Cancelar - + Capture Captura - + Change Disc Cambiar disco - + Changes the aspect ratio used to display the console's output to the screen. Cambia la relación de aspecto con la que se mostrará la imagen de la consola en pantalla. @@ -6383,37 +6416,37 @@ Do you want to load this save and continue? Lista de trucos - + Chooses the backend to use for rendering the console/game visuals. Selecciona el motor que se utilizará para renderizar la imagen de la consola/juego. - + Chooses the language used for UI elements. Selecciona el idioma de los elementos de la interfaz. - + Clean Boot Arranque limpio - + Clear Settings Borrar configuración - + Clear Shaders Borrar sombreadores - + Clears a shader from the chain. Quita un sombreador de la cadena. - + Clears all settings set for this game. Elimina toda la configuración específica para este juego. @@ -6422,62 +6455,62 @@ Do you want to load this save and continue? Borra el bit de enmascaramiento/transparencia en los volcados de escritura de la VRAM. - + Close Cerrar - + Close Game Cerrar juego - + Close Menu Cerrar menú - + Compatibility Rating Valoración de compatibilidad - + Compatibility: Compatibilidad: - + Completely exits the application, returning you to your desktop. Cierra por completo la aplicación y te devuelve al escritorio. - + Configuration Configuración - + Confirm Power Off Confirmar apagado - + Console Settings Configuración de la consola - + Controller Port {} Puerto de mando {} - + Controller Port {} Macros Macros del puerto de mando {} - + Controller Port {} Settings Configuración del puerto de mando {} @@ -6494,157 +6527,157 @@ Do you want to load this save and continue? Configuración del puerto de mando {}{} - + Controller Settings Configuración de mandos - + Controller Type Tipo de mando - + Controller settings reset to default. Configuración de mandos reiniciada a sus valores predeterminados. - + Controls Controles - + Controls the volume of the audio played on the host when fast forwarding. Controla el volumen del audio que se reproduzca en el equipo durante un avance rápido. - + Controls the volume of the audio played on the host. Controla el volumen del audio que se reproduzca en el equipo. - + Copies the current global settings to this game. Copia la configuración global actual a este juego. - + Copies the global controller configuration to this game. Copia la configuración global de mandos actual a este juego. - + Copy Global Settings Copiar configuración global - + Copy Settings Copiar configuración - + Could not find any CD/DVD-ROM devices. Please ensure you have a drive connected and sufficient permissions to access it. No se han podido encontrar dispositivos de CD/DVD-ROM. Asegúrate de tener una unidad conectada y los permisos necesarios para poder acceder a la misma. - + Cover Settings Configuración de carátula - + Covers Directory Directorio de carátulas - + Create Crear - + Create Save State Backups Crear copias de seguridad de los guardados rápidos - + Crop Mode Modo de recorte - + Culling Correction Corrección de «culling» - + Current Game Juego actual - + Deadzone Zona muerta - + Debugging Settings Configuración de depuración - + Default Valor predeterminado - + Default Boot Arranque predeterminado - + Default View Vista predeterminada - + Default: Disabled Valor predeterminado: desactivado - + Default: Enabled Valor predeterminado: activado - + Deinterlacing Mode Modo de desentrelazado - + Delete Save Eliminar archivo guardado - + Delete State Eliminar guardado rápido - + Desktop Mode Modo escritorio - + Details Detalles - + Details unavailable for game not scanned in game list. No hay detalles para un juego que no se encuentre en la lista de juegos. @@ -6653,82 +6686,82 @@ Do you want to load this save and continue? Determina la forma de expandir la señal de audio de estéreo a envolvente en los juegos que sean compatibles. - + Determines how large the on-screen messages and monitor are. Determina el tamaño de los mensajes en pantalla en relación con el monitor. - + Determines how much latency there is between the audio being picked up by the host API, and played through speakers. Determina la cantidad de latencia que habrá entre la recepción del audio por parte de la API del equipo y su reproducción por los altavoces. - + Determines how much of the area typically not visible on a consumer TV set to crop/hide. Determina la zona a recortar/ocultar que no suele mostrarse en un televisor comercial. - + Determines how the emulated CPU executes instructions. Determina cómo ejecutará las instrucciones la CPU emulada. - + Determines how the emulated console's output is upscaled or downscaled to your monitor's resolution. Determina cómo se escalará la salida de imagen de la consola emulada a la resolución de tu monitor. - + Determines quality of audio when not running at 100% speed. Determina la calidad del audio cuando la emulación no se ejecute al 100 % de velocidad. - + Determines that field that the game list will be sorted by. Determina la categoría que se utilizará para ordenar la lista de juegos. - + Determines the amount of audio buffered before being pulled by the host API. Determina la cantidad de audio que se almacenará en el búfer antes de que la API del equipo la reclame. - + Determines the emulated hardware type. Determina el tipo de hardware emulado. - + Determines the format that screenshots will be saved/compressed with. Determina el formato con el que se guardarán/comprimirán las capturas de pantalla. - + Determines the position on the screen when black borders must be added. Determina la posición de la imagen cuando se tengan que añadir bordes negros. - + Determines the rotation of the simulated TV screen. Determina la rotación de la pantalla simulada. - + Determines the size of screenshots created by DuckStation. Determina el tamaño de las capturas de pantalla creadas por DuckStation. - + Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed. Determina si se mostrará un mensaje de confirmación para cerrar el emulador/juego cuando se pulse el atajo. - + Determines which algorithm is used to convert interlaced frames to progressive for display on your system. Determina el algoritmo que se utilizará para convertir los fotogramas entrelazados a progresivos para mostrarlos en tu equipo. - + Device Settings Configuración del dispositivo @@ -6741,27 +6774,27 @@ Do you want to load this save and continue? Deshabilitar entrelazado - + Disable Mailbox Presentation Deshabilitar presentación «mailbox» - + Disable Subdirectory Scanning Deshabilitar búsqueda en subdirectorios - + Disable on 2D Polygons Deshabilitar en polígonos en 2D - + Disabled Opción desactivada - + Disables dithering and uses the full 8 bits per channel of color information. Desactiva el tramado y utiliza la totalidad de los 8 bits por canal de información de color. @@ -6770,62 +6803,62 @@ Do you want to load this save and continue? Desactiva el renderizado entrelazado en la GPU. Algunos juegos podrían renderizarse a 480p, pero otros fallarán. - + Disc {} | {} Disco {} | {} - + Discord Server Servidor de Discord... - + Display Settings Configuración de imagen - + Displays popup messages on events such as achievement unlocks and leaderboard submissions. Muestra mensajes emergentes en casos tales como el desbloqueo de un logro o la entrada en una tabla de clasificación. - + Displays popup messages when starting, submitting, or failing a leaderboard challenge. Muestra mensajes emergentes al activar, enviar o fracasar un desafío de una tabla de clasificación. - + Double-Click Toggles Fullscreen Hacer doble clic para pantalla completa - + Download Covers Descargar carátulas - + Downloads covers from a user-specified URL template. Descarga las carátulas de una URL de plantilla especificada por el usuario. - + Downsamples the rendered image prior to displaying it. Can improve overall image quality in mixed 2D/3D games. Submuestrea la imagen renderizada antes de mostrarla. Puede mejorar la calidad de imagen en juegos de 2D/3D mixto. - + Downsampling Submuestreo - + Downsampling Display Scale Escala de submuestreo - + Duck icon by icons8 (https://icons8.com/icon/74847/platforms.undefined.short-title) Icono del pato por icons8 (https://icons8.com/icon/74847/platforms.undefined.short-title) @@ -6838,177 +6871,177 @@ Do you want to load this save and continue? Volcar escrituras de VRAM reemplazables - + Emulation Settings Configuración de emulación - + Emulation Speed Velocidad de emulación - + Enable 8MB RAM Habilitar RAM de 8 MB - + Enable Achievements Logros - + Enable Cheats Habilitar trucos - + Enable Discord Presence Habilitar presencia en Discord - + Enable Fast Boot Habilitar arranque rápido - + Enable In-Game Overlays Superposiciones dentro del juego - + Enable Overclocking «Overclocking» - + Enable Post Processing Habilitar posprocesado - + Enable Recompiler Block Linking Vinculación de bloques del recompilador - + Enable Recompiler ICache ICache del recompilador - + Enable Recompiler Memory Exceptions Excepciones de memoria del recompilador - + Enable Region Check Habilitar comprobación de región - + Enable Rewinding Habilitar rebobinado - + Enable SDL Input Source Habilitar origen de entrada SDL - + Enable TTY Logging Habilitar registro por terminal - + Enable Texture Cache Habilitar caché de texturas - + Enable Texture Dumping Habilitar volcado de texturas - + Enable Texture Replacements Habilitar texturas de reemplazo - + Enable VRAM Write Dumping Habilitar volcado de escrituras a VRAM - + Enable/Disable the Player LED on DualSense controllers. Habilita/Deshabilita los indicadores de jugador en los mandos DualSense. - + Enables caching of guest textures, required for texture replacement. Habilita la caché de texturas externas. Necesaria para el reemplazo de texturas. - + Enables dumping of textures to image files, which can be replaced. Not compatible with all games. Habilita el volcado de texturas a archivos de imagen que pueden ser reemplazados. No todos los juegos son compatibles con esta opción. - + Enables loading of cheats for this game from DuckStation's database. Permite cargar de la base de datos de DuckStation trucos para este juego. - + Enables loading of replacement textures. Not compatible with all games. Habilita la carga de texturas de reemplazo. No todos los juegos son compatibles con esta opción. - + Enables smooth scrolling of menus in Big Picture UI. Habilita el desplazamiento fluido de los menús en la interfaz Big Picture. - + Enables the cheats that are selected below. Habilita los trucos que estén seleccionados. - + Enables the older, less accurate MDEC decoding routines. May be required for old replacement backgrounds to match/load. Habilita las rutinas de decodificación MDEC antiguas y menos precisas. Podrían ser necesarias para que los fondos de reemplazo antiguos sean detectados y carguen. - + Encore Mode Modo «encore» (nueva partida) - + Ensures every frame generated is displayed for optimal pacing. Enable for variable refresh displays, such as GSync/FreeSync. Disable if you are having speed or sound issues. Muestra todos los fotogramas generados para mejorar la duración de fotogramas. Habilitar para pantallas con GSync/FreeSync. Deshabilitar si falla la velocidad o el sonido. - + Enter Value Introducir valor - + Error Error - + Exit DuckStation Salir - + Exits Big Picture mode, returning to the desktop interface. Cierra el modo Big Picture y vuelve a la interfaz para escritorios. @@ -7017,74 +7050,74 @@ Do you want to load this save and continue? Modo de expansión - + FMV Chroma Smoothing Suavizar croma en vídeos FMV - + Failed to load shader {}. It may be invalid. Error was: Error al cargar el sombreador {}. Podría no ser válido. El error ha sido el siguiente: - + Fast Forward Boot Acelerar arranque - + Fast forwards through the early loading process when fast booting, saving time. Results may vary between games. Acelera las primeras etapas de carga en el arranque rápido para ganar tiempo. Los resultados pueden cambiar según el juego. - + Force 4:3 For FMVs Forzar 4:3 para vídeos FMV - + Force Video Timing Forzar velocidad de fotogramas - + Genre: %.*s Género: %.*s - + Internal Resolution Resolución interna - + Latency Control Control de latencia - + Launch Options Opciones de ejecución - + Launch a game from a file, disc, or starts the console without any disc inserted. Ejecuta un juego de un archivo, un disco o arranca la consola sin introducir un disco. - + Line Detection Detección de líneas - + List Settings Configuración de la lista - + Load Global State Cargar guardado global @@ -7093,322 +7126,322 @@ El error ha sido el siguiente: Pulsar para alternar macro {} - + Memory Card Busy Memory Card ocupada - + Merge Multi-Disc Games Combinar juegos multidisco - + Merges multi-disc games into one item in the game list. Combina los juegos con varios discos en un solo elemento de la lista de juegos. - + Multitap Multitap - + No cheats are available for this game. No hay trucos disponibles para este juego. - + No patches are available for this game. No hay parches disponibles para este juego. - + PGXP Depth Buffer Búfer de profundidad de la PGXP - + Resume Last Session Reanudar última sesión - + Return To Game Volver a la partida - + Return to desktop mode, or exit the application. Vuelve al modo para escritorios o cierra la aplicación. - + Return to the previous menu. Vuelve al menú anterior. - + Reverses the game list sort order from the default (usually ascending to descending). Invierte el orden predeterminado de la lista de juegos (normalmente de ascendente a descendente). - + Rewind for {0} frames, lasting {1:.2f} seconds will require up to {2} MB of RAM and {3} MB of VRAM. Rebobinar {0} fotogramas, durante {1:.2f} segundos requerirá hasta {2} MB de RAM y {3} MB de VRAM. - + Rewind is disabled because runahead is enabled. Runahead will significantly increase system requirements. Rebobinado desactivado porque la predicción de latencia está activada. Esta aumentará los requisitos del sistema. - + Rewind is not enabled. Please note that enabling rewind may significantly increase system requirements. Rebobinado desactivado. Activarlo aumentará significativamente los requisitos del sistema. - + Round Upscaled Texture Coordinates Redondear coordenadas de texturas escaladas - + SDL DualSense Player LED Indicadores de jugador del DualSense - + Screen Position Posición de imagen - + Screen Rotation Rotación de imagen - + Screenshot Format Formato de capturas de pantalla - + Screenshot Quality Calidad de capturas de pantalla - + Screenshot Size Tamaño de capturas de pantalla - + Select Seleccionar - + Select Disc Seleccionar disco - + Select Disc Drive Seleccionar unidad de disco - + Select Game Seleccionar juego - + Select State Seleccionar guardado rápido - + Selects the quality at which screenshots will be compressed. Selecciona la calidad con la que se comprimirán las capturas de pantalla. - + Selects the view that the game list will open to. Selecciona la vista con la que se abrirá la lista de juegos. - + Show Latency Statistics Mostrar estadísticas de latencia - + Shows information about input and audio latency in the top-right corner of the display. Muestra información sobre la latencia de entrada y audio en la esquina superior derecha de la imagen. - + Shows the game you are currently playing as part of your profile in Discord. Muestra el juego al que estés jugando actualmente en tu perfil de Discord. - + Shows the host's CPU usage of each system thread in the top-right corner of the display. Muestra el uso de la CPU del equipo desgranado por subprocesos en la esquina superior derecha de la imagen. - + Skip Duplicate Frame Display Omitir fotogramas duplicados - + Skips the presentation/display of frames that are not unique. Can result in worse frame pacing. Omite los fotogramas que no sean únicos. Podría empeorar la duración de fotogramas. - + Smooth Scrolling Desplazamiento fluido - + Smooths out blockyness between colour transitions in 24-bit content, usually FMVs. Suaviza el efecto de cuadrados entre las transiciones de colores en los contenidos a 24 bits, como vídeos FMV. - + Specifies the amount of buffer time added, which reduces the additional sleep time introduced. Especifica la cantidad de tiempo que se añadirá al búfer, reduciendo el tiempo de espera adicional que se introducirá. - + Start Disc Iniciar disco - + Start Game Iniciar juego - + Start a game from a disc in your PC's DVD drive. Inicia un juego desde la unidad de DVD de tu equipo. - + Synchronizes presentation of the console's frames to the host. GSync/FreeSync users should enable Optimal Frame Pacing instead. Sincroniza la presentación de los fotogramas de la consola con el equipo. Los usuarios de GSync/FreeSync deberían usar en su lugar Optimizar duración de fotogramas. - + Textures Directory Directorio de texturas - + The texture cache is currently experimental, and may cause rendering errors in some games. La caché de texturas se encuentra en una fase experimental y podría provocar errores de renderizado en algunos juegos. - + This game has no achievements. No hay logros para este juego. - + This game has no leaderboards. No hay tablas de clasificación para este juego. - + Timing out in {:.0f} seconds... Esperando {:.0f} segundos... - + Toggle Fullscreen Alternar pantalla completa - + Toggles the macro when the button is pressed, instead of held. Alterna la macro al pulsar el botón, en vez de al mantenerlo. - + Trigger Desencadenador - + UI Language Idioma de la interfaz - + Uncompressed Size Tamaño sin comprimir - + Uncompressed Size: %.2f MB Tamaño sin comprimir: %.2f MB - + Ungrouped Sin agrupar - + Unknown File Size Tamaño de archivo desconocido - + Use Old MDEC Routines Utilizar rutinas MDEC antiguas - + Use Single Card For Multi-Disc Games Usar una Memory Card para cada juego multidisco - + Uses native resolution coordinates for 2D polygons, instead of precise coordinates. Can fix misaligned UI in some games, but otherwise should be left disabled. Utiliza las coordenadas de la resolución nativa para los polígonos en 2D. Puede corregir interfaces desalineadas, pero debe deshabilitarse si no es necesario. - + Utilizes the chosen video timing regardless of the game's setting. Utiliza la velocidad de fotogramas seleccionada sin importar la que dicte el juego. - + Vertex Cache Caché de vértices - + Vertical Sync (VSync) Sincronización vertical («VSync») - + WARNING: Activating cheats can cause unpredictable behavior, crashing, soft-locks, or broken saved games. ADVERTENCIA: los trucos pueden provocar comportamientos impredecibles, cuelgues, bloqueos por software o corromper las partidas guardadas. - + WARNING: Activating game patches can cause unpredictable behavior, crashing, soft-locks, or broken saved games. ADVERTENCIA: los parches pueden provocar comportamientos impredecibles, cuelgues, bloqueos por software o corromper las partidas guardadas. - + WARNING: Your game is still saving to the memory card. Continuing to {0} may IRREVERSIBLY DESTROY YOUR MEMORY CARD. We recommend resuming your game and waiting 5 seconds for it to finish saving. Do you want to {0} anyway? @@ -7417,127 +7450,132 @@ Do you want to {0} anyway? ¿Deseas {0}? - + Wireframe Rendering Renderizar mallas de polígonos - + Enable Subdirectory Scanning Habilitar búsqueda en subdirectorios - + + %.1f ms + %.1f ms + + + Adjusts the emulation speed so the console's refresh rate matches the host when VSync is enabled. Ajusta la velocidad de la emulación para que la frecuencia de actualización de la consola sea igual a la del equipo cuando se habilite la sincronización vertical. - + Automatically loads and applies cheats on game start. Cheats can break games and saves. Carga y aplica trucos automáticamente al iniciar el juego. Los trucos pueden dañar tanto juegos como partidas guardadas. - + Change Page Cambiar página - + Change Selection Cambiar la selección - + Change View Cambiar vista - + Changes settings for the application. Cambia la configuración de la aplicación. - + Cheats Trucos - + Contributor List Lista de colaboradores - + Create New... Crear nuevo... - + Depth Clear Threshold Umbral de limpieza de profundidad - + Determines how much button pressure is ignored before activating the macro. Determina la cantidad de presión en el botón que se ignorará antes de activar la macro. - + Determines how much pressure is simulated when macro is active. Determina la cantidad de presión que se simulará al activar la macro. - + Determines the frequency at which the macro will toggle the buttons on and off (aka auto fire). Determina la frecuencia con la que la macro activará y desactivará los botones (es decir, autodisparo/«autofire»). - + Determines the margin between the edge of the screen and on-screen messages. Determina el margen que habrá entre el borde de la imagen y los mensajes en pantalla. - + DuckStation is a free simulator/emulator of the Sony PlayStation(TM) console, focusing on playability, speed, and long-term maintainability. DuckStation es un emulador/simulador gratuito de la consola Sony PlayStation(TM) especializado en jugabilidad, velocidad y mantenimiento a largo plazo. - + Dump Replaced Textures Volcar texturas reemplazadas - + Dumps textures that have replacements already loaded. Vuelta las texturas que ya tengan reemplazos cargados. - + Enable VRAM Write Texture Replacement Escritura de la VRAM a las texturas de reemplazo - + Enable XInput Input Source Habilitar origen de entrada XInput - + Enable debugging when supported by the host's renderer API. Only for developer use. Activa el sistema de depuración cuando lo permita la API de renderizado del equipo. Solo para desarrolladores. - + Enables alignment and bus exceptions. Not needed for any known games. Habilita las excepciones por alineación y buses. No es necesario para ningún juego conocido. - + Enables an additional 6MB of RAM to obtain a total of 2+6 = 8MB, usually present on dev consoles. Habilita 6 MB adicionales de RAM para dar 2+6=8 MB, habituales en consolas de desarrollo. - + Enables an additional three controller slots on each port. Not supported in all games. Habilita tres puertos de mando adicionales en cada puerto. No es compatible con todos los juegos. @@ -7546,87 +7584,87 @@ Do you want to {0} anyway? Habilita un ritmo de fotogramas más preciso a costa de consumir más batería. - + Enables the replacement of background textures in supported games. Habilita el reemplazo de texturas de fondo en los juegos que sean compatibles. - + Enter the name of the input profile you wish to create. Introduce el nombre del perfil de entrada nuevo. - + Execution Mode Modo de ejecución - + Exit Salir - + Exit And Save State Salir y hacer un guardado rápido - + Exit Without Saving Salir sin guardar - + Failed to copy text to clipboard. Error al copiar el texto al portapapeles. - + Failed to delete save state. Error al eliminar el guardado rápido. - + Failed to delete {}. Error al eliminar «{}». - + Failed to load '{}'. Error al cargar «{}». - + Failed to save input profile '{}'. Error al guardar el perfil de entrada «{}». - + Fast Boot Arranque rápido - + Fast Forward Speed Velocidad de avance rápido - + Fast Forward Volume Volumen de avance rápido - + File Size Tamaño de archivo - + File Size: %.2f MB Tamaño de archivo: %.2f MB - + File Title Título de archivo @@ -7639,142 +7677,142 @@ Do you want to {0} anyway? Fuerza a los juegos PAL a ejecutarse a la velocidad NTSC (60 Hz). Algunos juegos PAL se ejecutarán a su velocidad «normal», pero otros fallarán. - + Forces a full rescan of all games previously identified. Fuerza una búsqueda completa de todos los juegos ya identificados. - + Forces blending to be done in the shader at 16-bit precision, when not using true color. Non-trivial performance impact, and unnecessary for most games. Fuerza a ejecutar las mezclas en el sombreador a 16 bits si no se utiliza el modo a color verdadero. Afecta al rendimiento y es innecesario por lo general. - + Forces the use of FIFO over Mailbox presentation, i.e. double buffering instead of triple buffering. Usually results in worse frame pacing. Fuerza la presentación FIFO en vez de la tipo «mailbox», es decir: búfer doble en vez de triple. Suele producir un ritmo de fotogramas peor. - + Forcibly mutes both CD-DA and XA audio from the CD-ROM. Can be used to disable background music in some games. Silencia a la fuerza el audio CD-DA y XA del CD-ROM. Puede usarse para deshabilitar la música de fondo en algunos juegos. - + Frame Time Buffer Búfer de duración de fotogramas - + Frequency Frecuencia - + From File... Desde archivo... - + Fullscreen Resolution Resolución a pantalla completa - + GPU Adapter Adaptador de GPU - + GPU Renderer Renderizador de GPU - + GPU adapter will be applied after restarting. El adaptador de GPU se aplicará al reiniciar. - + Game Grid Cuadrícula de juegos - + Game List Lista de juegos - + Game List Settings Configuración de la lista de juegos - + Game Patches Parches de juegos - + Game Properties Propiedades del juego - + Game Quick Save Guardado rápido de juego - + Game Slot {0}##game_slot_{0} Espacio de juego {0}##game_slot_{0} - + Game compatibility rating copied to clipboard. Valoración de compatibilidad del juego copiada al portapapeles. - + Game not loaded or no RetroAchievements available. No se ha cargado un juego o RetroAchievements no está disponible. - + Game path copied to clipboard. Ruta del juego copiada al portapapeles. - + Game region copied to clipboard. Región del juego copiada al portapapeles. - + Game serial copied to clipboard. Número de serie del juego copiado al portapapeles. - + Game settings have been cleared for '{}'. Configuración del juego «{}» eliminada. - + Game settings initialized with global settings for '{}'. Configuración del juego «{}» creada con la configuración global. - + Game title copied to clipboard. Título del juego copiado al portapapeles. - + Game type copied to clipboard. Tipo del juego copiado al portapapeles. - + Game: {} ({}) Juego: {} ({}) @@ -7783,72 +7821,72 @@ Do you want to {0} anyway? Género: %s - + Geometry Tolerance Tolerancia geométrica - + GitHub Repository Repositorio en GitHub - + Global Slot {0} - {1}##global_slot_{0} Espacio global {0} - {1}##global_slot_{0} - + Global Slot {0}##global_slot_{0} Espacio global {0}##global_slot_{0} - + Graphics Settings Configuración de gráficos - + Hardcore Mode Modo «hardcore» - + Hardcore mode will be enabled on next game restart. El modo «hardcore» se activará al reiniciar el juego. - + Hide Cursor In Fullscreen Ocultar cursor en pantalla completa - + Hides the mouse pointer/cursor when the emulator is in fullscreen mode. Oculta el puntero/cursor del ratón cuando el emulador esté en el modo a pantalla completa. - + Hotkey Settings Configuración de atajos - + How many saves will be kept for rewinding. Higher values have greater memory requirements. Indica la cantidad de datos que se conservarán para el rebobinado. Un valor alto aumentará los requisitos del sistema. - + How often a rewind state will be created. Higher frequencies have greater system requirements. Indica la cantidad de guardados rápidos que se crearán para el rebobinado. Un valor alto aumentará los requisitos del sistema. - + Identifies any new files added to the game directories. Identifica cualquier archivo nuevo que se haya añadido a los directorios de juegos. - + If not enabled, the current post processing chain will be ignored. Si esta opción está deshabilitada, se ignorará la cadena de posprocesado actual. @@ -7857,202 +7895,202 @@ Do you want to {0} anyway? Incrementar la resolución del temporizador - + Increases the field of view from 4:3 to the chosen display aspect ratio in 3D games. Aumenta el campo de visión de 4:3 a la relación de aspecto de imagen seleccionada en los juegos en 3D. - + Increases the precision of polygon culling, reducing the number of holes in geometry. Incrementa la precisión de la selección (o «culling») de polígonos para reducir el número de huecos en la geometría. - + Infinite/Instantaneous Infinita/Instantánea - + Inhibit Screensaver Inhibir salvapantallas - + Input Sources Orígenes de entrada - + Input profile '{}' loaded. Perfil de entrada «{}» cargado. - + Input profile '{}' saved. Perfil de entrada «{}» guardado. - + Integration Integración - + Interface Settings Configuración de la interfaz - + Language: Idioma: - + Last Played Última partida - + Last Played: %s Última partida: %s - + Launch a game by selecting a file/disc image. Ejecuta un juego seleccionando un archivo o imagen de disco. - + Launch a game from images scanned from your game directories. Ejecuta un juego de las imágenes encontradas en tus directorios de juegos. - + Leaderboard Notifications Notificaciones sobre tablas de clasificación - + Leaderboards Tablas de clasificación - + Leaderboards are not enabled. Las tablas de clasificación están desactivadas. - + Load Database Cheats Cargar trucos de base de datos - + Load Devices From Save States Cargar dispositivos de guardados rápidos - + Load Profile Cargar perfil - + Load Resume State Cargar guardado de continuación - + Load State Cargar guardado rápido - + Loads all replacement texture to RAM, reducing stuttering at runtime. Carga todas las texturas de reemplazo en la RAM, reduciendo los tirones durante la emulación. - + Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay. Carga la imagen del juego en la RAM. Ideal para rutas de red que puedan volverse inestables durante una partida. - + Log Level Nivel de registro - + Log To Debug Console Registrar en consola de depuración - + Log To File Registrar en archivo - + Log To System Console Registrar en consola del sistema - + Logging Registro - + Logging Settings Configuración de registros - + Login Iniciar sesión - + Login token generated on {} Token de acceso generado el {} - + Logout Cerrar sesión - + Logs BIOS calls to printf(). Not all games contain debugging messages. Registra las llamadas de la BIOS a la acción printf(). No todos los juegos tienen mensajes de depuración. - + Logs in to RetroAchievements. Inicia sesión en RetroAchievements. - + Logs messages to duckstation.log in the user directory. Copia los mensajes al archivo duckstation.log, en el directorio del usuario. - + Logs messages to the console window. Muestra los mensajes en la ventana de la consola. - + Logs messages to the debug console where supported. Muestra los mensajes en la consola de depuración cuando sea posible. - + Logs out of RetroAchievements. Cierra la sesión de RetroAchievements. - + Macro Button {} Botón de macro {} @@ -8069,82 +8107,82 @@ Do you want to {0} anyway? Desencadenador de la macro {} - + Makes games run closer to their console framerate, at a small cost to performance. Fuerza a los juegos a ejecutarse a una velocidad más cercana a la que tendrían en consola a cambio de perder un poco de rendimiento. - + Memory Card Directory Directorio de Memory Cards - + Memory Card Port {} Ranura de Memory Card {} - + Memory Card Settings Configuración de Memory Cards - + Memory Card {} Type Tipo de Memory Card {} - + Minimal Output Latency Latencia mínima de salida - + Move Down Bajar - + Move Up Subir - + Moves this shader higher in the chain, applying it earlier. Sube el sombreador en la cadena, haciendo que se aplique más pronto. - + Moves this shader lower in the chain, applying it later. Baja el sombreador en la cadena, haciendo que se aplique más tarde. - + Multitap Mode Modo de multitap - + Mute All Sound Silenciar todo - + Mute CD Audio Silenciar audio de CD - + Navigate Desplazar - + No Binding Sin asignar - + No Game Selected No se ha seleccionado un juego @@ -8153,232 +8191,232 @@ Do you want to {0} anyway? No se han encontrado trucos para {}. - + No input profiles available. No hay perfiles de entrada disponibles. - + No resume save state found. No se han encontrado guardados de continuación. - + No save present in this slot. No hay un guardado en este espacio. - + No save states found. No se han encontrado guardados rápidos. - + No, resume the game. No, continuar la partida. - + None (Double Speed) Nula (velocidad doble) - + None (Normal Speed) Nula (velocidad normal) - + Not Logged In Sesión no iniciada - + Not Scanning Subdirectories Búsqueda en subdirectorios deshabilitada - + OK Aceptar - + OSD Scale Escala de mensajes en pantalla - + On-Screen Display Mensajes en pantalla - + Open Containing Directory Abrir carpeta contenedora - + Open in File Browser Abrir en explorador de archivos - + Operations Operaciones - + Optimal Frame Pacing Optimizar duración de fotogramas - + Options Opciones - + Output Latency Latencia de salida - + Output Volume Volumen de salida - + Overclocking Percentage Porcentaje de «overclocking» - + Overlays or replaces normal triangle drawing with a wireframe/line view. Superpone o subtituye el renderizado normal de los triángulos con una representación por mallas o líneas. - + PGXP (Precision Geometry Transform Pipeline) PGXP (transformación precisa de geometría) - + PGXP Geometry Correction Corrección de geometría de la PGXP - + Parent Directory Directorio superior - + Patches Parches - + Patches the BIOS to skip the boot animation. Safe to enable. Parchea la BIOS para que omita la animación de arranque. Es seguro. - + Path Ruta - + Pause On Controller Disconnection Pausar al desconectarse un mando - + Pause On Focus Loss Pausar al pasar a segundo plano - + Pause On Start Pausar nada más iniciar - + Pauses the emulator when a controller with bindings is disconnected. Pausa el emulador cuando se desconecte un mando que tenga asignaciones. - + Pauses the emulator when a game is started. Pausa el emulador cuando se inicie un juego. - + Pauses the emulator when you minimize the window or switch to another application, and unpauses when you switch back. Pausa el emulador cuando se minimice la ventana o se cambie a otra aplicación y lo reanuda cuando se vuelva al emulador. - + Per-Game Configuration Configuraciones por juego - + Per-game controller configuration initialized with global settings. Configuración de mandos según el juego iniciada con la configuración global. - + Performance enhancement - jumps directly between blocks instead of returning to the dispatcher. Mejora el rendimiento: salta directamente de un bloque a otro en vez de volver al distribuidor. - + Perspective Correct Colors Corregir perspectiva de colores - + Perspective Correct Textures Corregir perspectiva de texturas - + Plays sound effects for events such as achievement unlocks and leaderboard submissions. Reproduce efectos de sonido en casos tales como el desbloqueo de un logro o la entrada en una tabla de clasificación. - + Port {} Controller Type Tipo de mando del puerto {} - + Post-Processing Settings Configuración de posprocesado - + Post-processing chain cleared. Cadena de posprocesado borrada. - + Post-processing shaders reloaded. Sombreadores de posprocesado recargados. - + Preload Images to RAM Precargar imágenes a RAM - + Preload Replacement Textures Precargar texturas de reemplazo - + Writes backgrounds that can be replaced to the dump directory. Escribe fondos que pueden ser reemplazados al directorio de volcados. @@ -8387,377 +8425,377 @@ Do you want to {0} anyway? Presenta los fotogramas utilizando un subproceso en segundo plano al utilizar el avance rápido o al desactivar la sincronización vertical. - + Preserve Projection Precision Conservar precisión de proyección - + Press To Toggle Pulsar para alternar - + Pressure Presión - + Prevents the emulator from producing any audible sound. Impide que el emulador produzca cualquier sonido. - + Prevents the screen saver from activating and the host from sleeping while emulation is running. Evita que el protector de pantalla se active y que el equipo entre en suspensión mientras el emulador esté en funcionamiento. - + Provides vibration and LED control support over Bluetooth. Ofrece soporte para vibración y control de luces led a través de Bluetooth. - + Push a controller button or axis now. Pulsa un botón o eje del mando. - + Quick Save Guardado rápido - + RAIntegration is being used instead of the built-in achievements implementation. Se está utilizando RAIntegration en vez de la implementación nativa de logros. - + Read Speedup Aceleración de lectura - + Readahead Sectors Lectura asíncrona de sectores - + Recompiler Fast Memory Access Memoria de acceso rápido del recompilador - + Reduce Input Latency Reducir latencia de entrada - + Reduces "wobbly" polygons by attempting to preserve the fractional component through memory transfers. Evita el «tembleque» de los polígonos intentando preservar los componentes fraccionarios durante las transferencias de memoria. - + Reduces hitches in emulation by reading/decompressing CD data asynchronously on a worker thread. Reduce los tirones en la emulación leyendo/descomprimiendo los datos del CD de manera asíncrona en un subproceso de trabajo. - + Reduces input latency by delaying the start of frame until closer to the presentation time. Reduce la latencia de entrada retrasando el inicio del fotograma hasta un momento más cercano a su presentación. - + Reduces polygon Z-fighting through depth testing. Low compatibility with games. Reduce las interferencias entre polígonos (o «Z-fighting») mediante pruebas de profundidad. Tiene baja compatibilidad. - + Reduces the size of save states by compressing the data before saving. Reduce el tamaño de los guardados rápidos comprimiendo sus datos. - + Region Región - + Region: Región: - + Release Date: %s Fecha de lanzamiento: %s - + Reload Shaders Recargar sombreadores - + Reloads the shaders from disk, applying any changes. Recarga los sombreadores del disco y aplica cualquier cambio. - + Remove From Chain Quitar de la cadena - + Remove From List Quitar de la lista - + Removed stage {} ({}). Etapa {} ({}) quitada. - + Removes this shader from the chain. Quita este sombreador de la cadena. - + Renames existing save states when saving to a backup file. Renombra los guardados rápidos al hacer un archivo de respaldo. - + Rendering Renderizado - + Replaces these settings with a previously saved input profile. Reemplaza esta configuración con la de un perfil de entrada guardado. - + Rescan All Games Volver a buscar todos los juegos - + Reset Memory Card Directory Reiniciar directorio de Memory Cards - + Reset Play Time Reiniciar tiempo jugado - + Reset Settings Reiniciar configuración - + Reset System Reiniciar sistema - + Resets all configuration to defaults (including bindings). Restablece toda la configuración a sus valores predeterminados (incluyendo las asignaciones). - + Resets memory card directory to default (user directory). Restablece el directorio de Memory Cards a su valor predeterminado (el directorio del usuario). - + Resolution change will be applied after restarting. Los cambios en la resolución se aplicarán al reiniciar. - + Restores the state of the system prior to the last state loaded. Restablece el estado del sistema a cuando se cargó el último guardado rápido. - + Resume Game Reanudar juego - + Rewind Save Frequency Frecuencia de guardado del rebobinado - + Rewind Save Slots Espacios de guardado para el rebobinado - + Rich presence inactive or unsupported. «Rich Presence» inactiva o no compatible. - + Rounds texture coordinates instead of flooring when upscaling. Can fix misaligned textures in some games, but break others, and is incompatible with texture filtering. Redondea las coordenadas de texturas al escalar la imagen. Puede realinear las texturas de algunos juegos, pero dañar otros y es incompatible con el filtrado de texturas. - + Runahead Predicción de latencia - + Runahead/Rewind Predicción de latencia/Rebobinado - + Runs the software renderer in parallel for VRAM readbacks. On some systems, this may result in greater performance. Ejecuta el renderizador por software en paralelo para cotejar las lecturas de VRAM. En algunos sistemas puede aumentar el rendimiento. - + SDL DualShock 4 / DualSense Enhanced Mode Modo SDL mejorado para DualShock 4/DualSense - + Safe Mode Modo seguro - + Save Profile Guardar perfil - + Save Screenshot Capturar pantalla - + Save State Crear guardado rápido - + Save State Compression Comprimir guardados rápidos - + Save State On Exit Crear guardado rápido al salir - + Saved {:%c} Archivo guardado: {:%c} - + Saves state periodically so you can rewind any mistakes while playing. Crea periódicamente guardados rápidos para que puedas deshacer cualquier error que cometas al jugar. - + Scaled Dithering Escalado de tramado - + Scales internal VRAM resolution by the specified multiplier. Some games require 1x VRAM resolution. Escala la resolución interna de la VRAM utilizando el multiplicador especificado. Algunos juegos necesitan una resolución de VRAM de x1. - + Scales the dithering pattern with the internal rendering resolution, making it less noticeable. Usually safe to enable. Escala el patrón del tramado con la resolución interna de renderizado, lo que lo disimula más. Una opción por lo general segura. - + Scaling Escalado - + Scan For New Games Buscar juegos nuevos - + Scanning Subdirectories Buscando en subdirectorios - + Screen Margins Márgenes de pantalla - + Search Directories Buscar en directorios - + Seek Speedup Aceleración de búsqueda - + Select Device Seleccionar dispositivo - + Select Disc Image Seleccionar imagen de disco - + Select Macro {} Binds Seleccionar asignaciones de la macro {} - + Selects the GPU to use for rendering. Selecciona la GPU que se utilizará para renderizar. - + Selects the percentage of the normal clock speed the emulated hardware will run at. Selecciona el porcentaje de velocidad del reloj normal con el que se ejecutará el hardware emulado. - + Selects the resolution scale that will be applied to the final image. 1x will downsample to the original console resolution. Selecciona la escala de resolución que se aplicará a la imagen final. «1x» reducirá la resolución a la original de la consola. - + Selects the resolution to use in fullscreen modes. Selecciona la resolución que se utilizará en los modos a pantalla completa. - + Serial Número de serie - + Session: {} Sesión: {} - + Set Input Binding Establecer asignación de entrada @@ -8766,162 +8804,162 @@ Do you want to {0} anyway? Establecer canal alfa de los volcados de escritura VRAM - + Sets a threshold for discarding precise values when exceeded. May help with glitches in some games. Establece un umbral para descartar valores que se excedan de un punto concreto. Podría ayudar a corregir defectos en algunos juegos. - + Sets a threshold for discarding the emulated depth buffer. May help in some games. Establece un umbral para descartar el búfer de profundidad emulado. Podría ayudar con algunos juegos. - + Sets the fast forward speed. It is not guaranteed that this speed will be reached on all systems. Establece la velocidad del avance rápido. No se asegura que esa velocidad se vaya a alcanzar en todos los sistemas. - + Sets the target emulation speed. It is not guaranteed that this speed will be reached on all systems. Establece la velocidad de emulación de destino. No se asegura que esa velocidad se vaya a alcanzar en todos los sistemas. - + Sets the turbo speed. It is not guaranteed that this speed will be reached on all systems. Establece la velocidad del turbo. No se asegura que esa velocidad se vaya a alcanzar en todos los sistemas. - + Sets the verbosity of messages logged. Higher levels will log more messages. Establece el grado de detalle de los mensajes del registro. Los niveles más altos mostrarán más mensajes. - + Sets which sort of memory card image will be used for slot {}. Establece el tipo de imagen de Memory Card que se utilizará para la ranura {}. - + Setting {} binding {}. Configurando la asociación para el {0} «{1}». - + Settings Configuración - + Settings and Operations Ajustes y operaciones - + Shader {} added as stage {}. Sombreador {} añadido en la etapa {}. - + Shared Card Name Nombre de Memory Card compartida - + Show CPU Usage Mostrar uso de CPU - + Show Controller Input Mostrar entradas del mando - + Show Enhancement Settings Mostrar configuración de mejoras - + Show FPS Mostrar FPS - + Show Frame Times Mostrar duración de fotogramas - + Show GPU Statistics Mostrar estadísticas de la GPU - + Show GPU Usage Mostrar uso de GPU - + Show OSD Messages Mostrar mensajes en pantalla - + Show Resolution Mostrar resolución - + Show Speed Mostrar velocidad - + Show Status Indicators Mostrar indicadores de estado - + Shows a visual history of frame times in the upper-left corner of the display. Muestra un historial visual de duraciones de fotogramas en la esquina superior izquierda de la imagen. - + Shows enhancement settings in the bottom-right corner of the screen. Muestra la configuración de mejoras en la esquina inferior derecha de la imagen. - + Shows icons in the lower-right corner of the screen when a challenge/primed achievement is active. Muestra iconos en la esquina inferior derecha de la imagen cuando haya activo un logro cercano o un desafío. - + Shows information about the emulated GPU in the top-right corner of the display. Muestra información sobre la GPU emulada en la esquina superior derecha de la imagen. - + Shows on-screen-display messages when events occur. Muestra mensajes en pantalla cuando ocurran eventos. - + Shows persistent icons when turbo is active or when paused. Muestra iconos persistentes al activar el turbo o al pausar la emulación. - + Shows the current controller state of the system in the bottom-left corner of the display. Muestra el estado actual del mando del sistema en la esquina inferior izquierda de la imagen. - + Shows the current emulation speed of the system in the top-right corner of the display as a percentage. Muestra el porcentaje de velocidad de emulación actual del sistema en la esquina superior derecha de la imagen. - + Shows the current rendering resolution of the system in the top-right corner of the display. Muestra la resolución actual de renderizado del sistema en la esquina superior derecha de la imagen. @@ -8930,187 +8968,187 @@ Do you want to {0} anyway? Muestra el uso de la CPU del equipo desgranado por subprocesos en la esquina superior derecha de la imagen. - + Shows the host's GPU usage in the top-right corner of the display. Muestra el uso de la CPU del equipo en la esquina superior derecha de la imagen. - + Shows the number of frames (or v-syncs) displayed per second by the system in the top-right corner of the display. Muestra el número de fotogramas de vídeo (o v-syncs) mostrados por segundo por el sistema en la esquina superior derecha de la imagen. - + Simulates the CPU's instruction cache in the recompiler. Can help with games running too fast. Simula la caché de instrucciones de la CPU en el recompilador. Podría ayudar a aquellos juegos que se ejecuten demasiado rápido. - + Simulates the region check present in original, unmodified consoles. Simula la comprobación de región presente en las consolas originales sin modificar. - + Simulates the system ahead of time and rolls back/replays to reduce input lag. Very high system requirements. Simula el sistema por adelantado y retrocede o repite las acciones para reducir el retraso de entrada. Tiene unos requisitos de sistema muy altos. - + Slow Boot Arranque lento - + Smooths out the blockiness of magnified textures on 2D objects. Suaviza el efecto cuadriculado de las texturas ampliadas en objetos 2D. - + Smooths out the blockiness of magnified textures on 3D objects. Suaviza el efecto cuadriculado de las texturas ampliadas en objetos 3D. - + Sort By Ordenar por... - + Sort Reversed Invertir orden - + Sound Effects Efectos de sonido - + Spectator Mode Modo espectador - + Speed Control Control de velocidad - + Speeds up CD-ROM reads by the specified factor. May improve loading speeds in some games, and break others. Acelera las lecturas del CD-ROM por el factor especificado. Podría ayudar en los tiempos de carga de algunos juegos y hacer que otros fallen. - + Speeds up CD-ROM seeks by the specified factor. May improve loading speeds in some games, and break others. Acelera las búsquedas en el CD-ROM por el factor especificado. Podría ayudar en los tiempos de carga de algunos juegos y hacer que otros fallen. - + Sprite Texture Filtering Filtrado de «sprites» - + Stage {}: {} Etapa {}: {} - + Start BIOS Iniciar BIOS - + Start File Iniciar archivo - + Start Fullscreen Iniciar a pantalla completa - + Start the console without any disc inserted. Arranca la consola sin introducir un disco. - + Stores the current settings to an input profile. Guarda la configuración actual en un perfil de entrada. - + Stretch Display Vertically Estirar imagen verticalmente - + Stretch Mode Modo de estiramiento - + Stretches the display to match the aspect ratio by multiplying vertically instead of horizontally. Estira la imagen para que coincida con la relación de aspecto multiplicándola verticalmente, no horizontalmente. - + Summary Resumen - + Switches back to 4:3 display aspect ratio when displaying 24-bit content, usually FMVs. Cambia a la relación de aspecto 4:3 cuando se muestren contenidos a 24 bits, que suelen ser las FMV. - + Switches between full screen and windowed when the window is double-clicked. Alterna entre los modos a pantalla completa y en ventana al hacer doble clic en la misma. - + Sync To Host Refresh Rate Sincronizar con la frecuencia de actualización del equipo - + Temporarily disables all enhancements, useful when testing. Desactiva todas las mejoras temporalmente. Ideal para hacer pruebas. - + Test Unofficial Achievements Probar logros no oficiales - + Texture Filtering Filtrado de texturas - + Texture Replacements Texturas de reemplazo - + The SDL input source supports most controllers. El origen de entrada SDL es compatible con la mayoría de mandos. - + The XInput source provides support for XBox 360/XBox One/XBox Series controllers. El origen XInput ofrece compatibilidad para los mandos de Xbox 360/Xbox One/Xbox Series. - + The audio backend determines how frames produced by the emulator are submitted to the host. El motor de audio determina la cantidad de fotogramas producidos por el emulador que son enviados al equipo. - + The selected memory card image will be used in shared mode for this slot. Se utilizará la imagen de Memory Card seleccionada en el modo compartido para esta ranura. @@ -9119,37 +9157,37 @@ Do you want to {0} anyway? Presentación multihilo - + Threaded Rendering Renderizado multihilo - + Time Played Tiempo jugado - + Time Played: %s Tiempo jugado: %s - + Title Título - + Toggle Analog Alternar función analógica - + Toggle Fast Forward Alternar avance rápido - + Toggle every %d frames Alternar cada %d fotogramas @@ -9158,152 +9196,152 @@ Do you want to {0} anyway? Desbandear a color verdadero - + True Color Rendering Renderizado a color verdadero - + Turbo Speed Velocidad del turbo - + Type Tipo - + Undo Load State Deshacer carga de guardado rápido - + Unknown Desconocido - + Unlimited Sin límite - + Use Blit Swap Chain Usar cadena de intercambio de blits - + Use Debug GPU Device Usar dispositivo gráfico de depuración - + Use Global Setting Usar configuración global - + Use Light Theme Usar tema claro - + Use Software Renderer For Readbacks Usar el renderizador por software para cotejar - + Username: {} Nombre de usuario: {} - + Uses PGXP for all instructions, not just memory operations. Utiliza la PGXP para todas las instrucciones y no solo para operaciones de memoria. - + Uses a blit presentation model instead of flipping. This may be needed on some systems. Utiliza un modelo de presentación por BLIT en vez de voltear la imagen. Algunos sistemas podrían necesitarlo. - + Uses a light coloured theme instead of the default dark theme. Utiliza un tema de colores claros en lugar del tema oscuro predeterminado. - + Uses a second thread for drawing graphics. Speed boost, and safe to use. Utiliza un segundo subproceso para el renderizado de gráficos. Mejora la velocidad y es seguro. - + Uses game-specific settings for controllers for this game. Utiliza una configuración de mandos específica para este juego. - + Uses perspective-correct interpolation for colors, which can improve visuals in some games. Aplica una interpolación correctora de perspectiva a los colores de los vértices, lo que puede mejorar los gráficos de algunos juegos. - + Uses perspective-correct interpolation for texture coordinates, straightening out warped textures. Aplica una interpolación correctora de perspectiva a las coordenadas de texturas para enmendar las texturas distorsionadas. - + Uses screen positions to resolve PGXP data. May improve visuals in some games. Utiliza las posiciones en pantalla para resolver los datos de la PGXP. Podría mejorar los gráficos de algunos juegos. - + Value: {} | Default: {} | Minimum: {} | Maximum: {} Valor: {} | Predeterminado: {} | Mínimo: {} | Máximo: {} - + When enabled and logged in, DuckStation will scan for achievements on startup. Si esta opción está habilitada y has iniciado sesión, DuckStation buscará logros al iniciarse. - + When enabled, DuckStation will assume all achievements are locked and not send any unlock notifications to the server. Si esta opción está habilitada, DuckStation asumirá que todos los logros están bloqueados y no enviará notificaciones de desbloqueo al servidor. - + When enabled, DuckStation will list achievements from unofficial sets. These achievements are not tracked by RetroAchievements. Si esta opción está habilitada, DuckStation mostrará los logros de colecciones no oficiales. RetroAchievements no hará un seguimiento de estos logros. - + When enabled, each session will behave as if no achievements have been unlocked. Si esta opción está habilitada, cada sesión de juego considerará que no se han desbloqueado logros anteriormente. - + When enabled, memory cards and controllers will be overwritten when save states are loaded. Si esta opción está habilitada, se sobrescribirán las Memory Cards y los mandos al cargar guardados rápidos. - + When enabled, the minimum supported output latency will be used for the host API. Si esta opción está habilitada, se utilizará la latencia de salida mínima compatible para la API del equipo. - + When playing a multi-disc game and using per-game (title) memory cards, use a single memory card for all discs. Cuando utilices un formato multidisco y Memory Cards para cada juego, se usará una sola Memory Card para todos los discos. - + When this option is chosen, the clock speed set below will be used. Al habilitar esta opción se utilizará la velocidad de reloj seleccionada. - + Widescreen Rendering Renderizar imagen panorámica @@ -9312,67 +9350,67 @@ Do you want to {0} anyway? Escribe las texturas que puedan ser reemplazadas en el directorio de volcados. - + Yes, {} now and risk memory card corruption. Sí, correr el riesgo de corromper la Memory Card. - + "Challenge" mode for achievements, including leaderboard tracking. Disables save state, cheats, and slowdown functions. Activa el modo «desafío» para los logros y el seguimiento de las tablas de puntuación. Desactiva los guardados rápidos, los trucos y las funciones de ralentización. - + "PlayStation" and "PSX" are registered trademarks of Sony Interactive Entertainment Europe Limited. This software is not affiliated in any way with Sony Interactive Entertainment. «PlayStation» y «PSX» son marcas registradas de Sony Interactive Entertainment. Esta aplicación no está afiliada de ninguna forma con Sony Interactive Entertainment. - + change disc cambiar de disco - + reset reiniciar - + shut down apagar - + {:%H:%M} {:%H:%M} - + {:%Y-%m-%d %H:%M:%S} {:%d/%m/%Y %H:%M:%S} - + {} Frames {} fotogramas - + {} deleted. {} ha sido eliminado. - + {} does not exist. {} no existe. - + {} is not a valid disc image. {} no es una imagen de disco válida. - + Version: %s Versión: %s @@ -9380,53 +9418,53 @@ Do you want to {0} anyway? GPU - + Saving screenshot to '{}'. Guardando captura de pantalla en «{}». - + Saved screenshot to '{}'. Captura de pantalla guardada en «{}». - + Failed to save screenshot to '{}'. Error al guardar captura de pantalla en «{}». - + Failed to start GPU trace: Error al iniciar seguimiento de la GPU: - + Saving {0} frame GPU trace to '{1}'. Guardando seguimiento de la GPU de {0} fotograma a «{1}». - + Saving multi-frame frame GPU trace to '{1}'. Guardando seguimiento de la GPU multifotograma a «{1}». - + Failed to close GPU trace: Error al cerrar el seguimiento de la GPU: - - + + Saved GPU trace to '{}'. Seguimiento de la GPU guardado en «{}». - + Compressing GPU trace '{}'... Comprimiendo archivo de seguimiento de la GPU «{}»... - + Failed to save GPU trace to '{}': Error al guardar el seguimiento de la GPU en «{}»: @@ -9434,12 +9472,12 @@ Do you want to {0} anyway? GPUDevice - + Error Error - + OpenGL renderer unavailable, your driver or hardware is not recent enough. OpenGL 3.1 or OpenGL ES 3.1 is required. Renderizador de OpenGL no disponible: tu hardware o tus controladores no son lo suficientemente modernos. Se requiere de OpenGL 3.1 o de OpenGL ES 3.1. @@ -9505,7 +9543,7 @@ Do you want to {0} anyway? OpenGL - + Software Software @@ -9563,37 +9601,37 @@ Do you want to {0} anyway? Escala de resolución establecida en ×{0} (pantalla {1} × {2}, VRAM {3} × {4}) - + Internal resolution set to {0}x ({1}x{2}). Resolución interna establecida en ×{0} ({1} × {2}). - + Multisample anti-aliasing set to {}x (SSAA). Suavizado de bordes de muestreo múltiple configurado a ×{} (SSAA). - + Multisample anti-aliasing set to {}x. Suavizado de bordes de muestreo múltiple configurado a ×{}. - + {}x MSAA is not supported, using {}x instead. El nivel de MSAA ×{0} no es compatible, se va a usar ×{1} en su lugar. - + SSAA is not supported, using MSAA instead. SSAA no es compatible, se va a usar MSAA en su lugar. - + Texture filter '{}/{}' is not supported with the current renderer. El filtro de texturas «{}/{}» no es compatible con el renderizador actual. - + Accurate blending is not supported by your current GPU. It requires framebuffer fetch, feedback loops, or rasterizer order views. Tu GPU actual no es compatible con la mezcla precisa. @@ -9601,13 +9639,13 @@ Son necesarios el acceso al búfer de fotogramas, los bucles de retroalimentación o las vistas ordenadas del rasterizador. - + Multisample anti-aliasing is not supported when using ROV blending. El suavizado de bordes de muestreo múltiple no es compatible con las mezclas con ROV. - + PGXP depth buffer is not supported by your current GPU or renderer. It requires framebuffer fetch, feedback loops, or rasterizer order views. Tu GPU o renderizador actuales no son compatibles con @@ -9616,22 +9654,22 @@ Son necesarios el acceso al búfer de fotogramas, los bucles de retroalimentación o las vistas ordenadas del rasterizador. - + Geometry shaders are not supported by your GPU, and are required for wireframe rendering. Tu GPU no es compatible con sombreadores de geometría, que son necesarios para renderizar las mallas de polígonos. - + Resolution scale {0}x is not divisible by downsample scale {1}x, using {2}x instead. La escala de resolución ×{0} no es divisible por la escala de submuestreo ×{1}, utilizando ×{2} en su lugar. - + Resolution scale {0}x not supported for adaptive downsampling, using {1}x. La escala de resolución ×{0} no es compatible con el suavizado adaptativo, se va a usar ×{1}. - + %n replacement textures found. Replacement texture count @@ -9640,7 +9678,7 @@ de retroalimentación o las vistas ordenadas del rasterizador. - + No replacement textures found. No se han encontrado texturas de reemplazo. @@ -9946,7 +9984,7 @@ Cualquier truco de la base de datos seguirá cargado y presente, a menos que des GameDatabase - + Unknown Desconocido @@ -10079,242 +10117,242 @@ Cualquier truco de la base de datos seguirá cargado y presente, a menos que des Contiene protección LibCrypt - + Display cropping set to {}. Recorte de imagen cambiado a {}. - - + + Deinterlacing set to {}. Desentrelazado cambiado a {}. - + CPU recompiler disabled. Recompilador de CPU deshabilitado. - + Unknown CompatibilityRating Desconocida - + Doesn't Boot CompatibilityRating No arranca - + Crashes In Intro CompatibilityRating Se cuelga al inicio - + Crashes In-Game CompatibilityRating Se cuelga en juego - + Graphical/Audio Issues CompatibilityRating Problemas audiovisuales - + No Issues CompatibilityRating Sin problemas - + Force Interpreter GameDatabase::Trait Forzar intérprete - + Force Software Renderer GameDatabase::Trait Forzar renderizador por software - + Force Software Renderer For Readbacks GameDatabase::Trait Forzar renderizador por software para cotejar - + Force Round Texture Coordinates GameDatabase::Trait Forzar redondeo de coordenadas de texturas - + Force Accurate Blending GameDatabase::Trait Forzar mezcla precisa - + Force Deinterlacing GameDatabase::Trait Forzar desentrelazado - + Force Full Boot GameDatabase::Trait Forzar arranque completo - + Disable Automatic Analog Mode GameDatabase::Trait Deshabilitar modo analógico automático - + Disable True Color GameDatabase::Trait Deshabilitar color verdadero - + Disable Upscaling GameDatabase::Trait Deshabilitar escalado - + Disable Texture Filtering GameDatabase::Trait Deshabilitar filtrado de texturas - + Disable Sprite Texture Filtering GameDatabase::Trait Deshabilitar filtrado de «sprites» - + Disable Scaled Dithering GameDatabase::Trait Deshabilitar escalado de tramado - + Disable Widescreen GameDatabase::Trait Deshabilitar pantalla panorámica - + Disable PGXP GameDatabase::Trait Deshabilitar PGXP - + Disable PGXP Culling GameDatabase::Trait Deshabilitar «culling» de la PGXP - + Disable PGXP Texture Correction GameDatabase::Trait Deshabilitar corrección de texturas de la PGXP - + Disable PGXP Color Correction GameDatabase::Trait Deshabilitar corrección de color de la PGXP - + Disable PGXP Depth Buffer GameDatabase::Trait Deshabilitar búfer de profundidad de la PGXP - + Disable PGXP Preserve Projection Floating Point GameDatabase::Trait Deshabilitar conservado de precisión de proyección de la PGXP - + Disable PGXP on 2D Polygons GameDatabase::Trait Deshabilitar PGXP en polígonos en 2D - + Force PGXP Vertex Cache GameDatabase::Trait Forzar caché de vértices de la PGXP - + Force PGXP CPU Mode GameDatabase::Trait Forzar modo CPU de la PGXP - + Force Recompiler Memory Exceptions GameDatabase::Trait Forzar excepciones de memoria del recompilador - + Force Recompiler ICache GameDatabase::Trait Forzar ICache del recompilador - + Force Recompiler LUT Fastmem GameDatabase::Trait Forzar memoria de acceso rápido LUT del recompilador - + Force CD-ROM SubQ Skew GameDatabase::Trait Forzar distorsión del SubQ del CD-ROM - + Is LibCrypt Protected GameDatabase::Trait Contiene protección LibCrypt - + Hardware rendering disabled. Renderizado por hardware deshabilitado. - + Software renderer readbacks enabled. Cotejados del renderizador de software habilitados. - + Accurate blending enabled. Mezcla precisa habilitada. - + Fast boot disabled. Arranque rápido deshabilitado. @@ -10323,32 +10361,32 @@ Cualquier truco de la base de datos seguirá cargado y presente, a menos que des Renderizado entrelazado habilitado. - + True color disabled. Color verdadero deshabilitado. - + Upscaling disabled. Escalado deshabilitado. - + Texture filtering disabled. Filtrado de texturas deshabilitado. - + Sprite texture filtering disabled. Filtrado de «sprites» deshabilitado. - + Scaled dithering. Tramado escalado. - + Widescreen rendering disabled. Renderizado de imagen panorámica deshabilitado. @@ -10357,67 +10395,67 @@ Cualquier truco de la base de datos seguirá cargado y presente, a menos que des Forzado de velocidad NTSC deshabilitado. - + PGXP geometry correction disabled. Corrección de geometría de la PGXP deshabilitada. - + PGXP culling correction disabled. Corrección de «culling» de la PGXP deshabilitada. - + PGXP perspective correct textures disabled. Corrección de perspectiva de texturas de la PGXP deshabilitada. - + PGXP perspective correct colors disabled. Corrección de perspectiva de colores de la PGXP deshabilitada. - + PGXP preserve projection precision disabled. Conservado de precisión de proyección de la PGXP deshabilitada. - + PGXP vertex cache enabled. Caché de vértices de la PGXP habilitada. - + PGXP Vertex Cache is enabled, but it is not required for this game. This may cause rendering errors. La caché de vértices de la PGXP está habilitada, pero este juego no la necesita. Podrían producirse errores de renderizado. - + PGXP CPU mode enabled. Modo CPU de la PGXP habilitado. - + PGXP CPU mode is enabled, but it is not required for this game. This may cause rendering errors. El modo CPU de la PGXP está habilitado, pero este juego no lo necesita. Podrían producirse errores de renderizado. - + PGXP depth buffer disabled. Búfer de profundidad de la PGXP deshabilitado. - + PGXP disabled on 2D polygons. PGXP deshabilitada en polígonos en 2D. - + Compatibility settings for this game have been applied. Se ha aplicado la configuración de compatibilidad para este juego. - + Controller in port {0} ({1}) is not supported for {2}. Supported controllers: {3} Please configure a supported controller from the list above. @@ -10426,119 +10464,119 @@ Mandos soportados: {3} Selecciona un mando de la lista superior. - - - + + + Settings Configuración - + Title Título - + Serial Número de serie - + Languages Idiomas - + Rating Valoración - + Version Tested Versión probada - + Comments Comentarios - + Supported Controllers Mandos compatibles - + Traits Características - + Display Active Start Offset Compensación del inicio de imagen activa - + Display Active End Offset Compensación del final de imagen activa - + Display Line Start Offset Compensación de la primera línea de imagen - + Display Line End Offset Compensación de la última línea de imagen - + Display Crop Mode Recorte de imagen - + Display Deinterlacing Mode Modo de desentrelazado de la imagen - + DMA Max Slice Ticks Duración máxima de los cortes de la DMA - + DMA Halt Ticks Duración de las paradas de la DMA - + GPU FIFO Size Tamaño del FIFO de la GPU - + GPU Max Runahead Predicción máxima de la GPU - + GPU PGXP Tolerance Tolerancia de GPU de la PGXP - + GPU PGXP Depth Threshold Umbral de limpieza de profundidad de GPU de la PGXP - + GPU Line Detect Mode Modo de detección de líneas de la GPU - + Disc Set Conjunto de discos @@ -10566,93 +10604,93 @@ Selecciona un mando de la lista superior. PSF - + Disc EntryType Disco - + Disc Set EntryType Conjunto de discos - + PS-EXE EntryType PS-EXE - + Playlist EntryType Lista de reproducción - + PSF EntryType PSF - + Scanning directory '{}'... Buscando en directorio {}... - + Scanning '{}'... Buscando en {}... - + Unknown Desconocido - + Never Nunca - + Today Hoy - + Yesterday Ayer - + {}h {}m {} h {} min - + {}h {}m {}s {} h {} min {} s - + {}m {}s {} min {} s - + {}s {} s - + None Ninguno - + %n hours %n hora @@ -10661,7 +10699,7 @@ Selecciona un mando de la lista superior. - + %n minutes %n minuto @@ -10938,176 +10976,181 @@ La búsqueda recursiva llevará más tiempo, pero identificará todo archivo que Ventana - + Clear the line to restore the original title... Deja la línea en blanco para restaurar el título original... - - + + Restore Restablecer - + Image Path: Ruta de imagen: - + Serial: Número de serie: - + # N.º - + Mode Modo - + Start Inicio - + Length Longitud - + Hash «Hash» - + Status Estado - + Region: Región: - + + Languages: + Idiomas: + + + Developer: Desarrolladora: - + Controllers: Mandos: - + Tracks: Pistas: - + Release Info: Datos de lanzamiento: - + Input Profile: Perfil de entrada: - + Genre: Género: - + Verify Verificar - + Comments Comentarios - + Edit... Editar... - + Type: Tipo: - + Title: Título: - + Compatibility: Compatibilidad: - - - - - - - - - + + + + + + + + + Unknown Desconocido - + %1 (Published by %2) %1 (distribuido por %2) - + Published by %1 Distribuido por %1 - + Released %1 Publicado el %1 - + %1-%2 players %1-%2 jugadores - + %1 players %1 jugadores - + %1-%2 memory card blocks %1-%2 bloques de Memory Card - + %1 memory card blocks %1 bloques de Memory Card - + Use Global Settings Usar configuración global - + Game Specific Configuration Configuración del juego @@ -11116,62 +11159,67 @@ La búsqueda recursiva llevará más tiempo, pero identificará todo archivo que %1 [%2] - + + Show Default Flag + Mostrar bandera predeterminada + + + %1 tracks covering %2 MB (%3 MB on disk) %1 pistas que contienen %2 MB (%3 MB en disco) - + Track %1 Pista %1 - + <not computed> <sin calcular> - + Compatibility Report Valoración de compatibilidad - + Per-game controller configuration initialized with global settings. Configuración de mandos según el juego iniciada con la configuración global. - + Error Error - + Failed to open CD image for hashing. Error al abrir la imagen del CD para calcular su «hash». - + Verifying hashes... Comprobando «hashes»... - + Revision: %1 Revisión: %1 - + N/A No disponible - + Serial Mismatch: %1 vs %2 Los números de serie no coinciden: %1 y %2 - + Search on Redump.org Buscar en Redump.org @@ -11393,7 +11441,6 @@ La búsqueda recursiva llevará más tiempo, pero identificará todo archivo que La caché de texturas se encuentra en una fase experimental y podría provocar errores de renderizado en algunos juegos. - Enable Texture Replacements Habilitar texturas de reemplazo @@ -11735,6 +11782,11 @@ La búsqueda recursiva llevará más tiempo, pero identificará todo archivo que General Settings Configuración general + + + Enable Texture Replacement + Habilitar texturas de reemplazo + @@ -12719,87 +12771,87 @@ La búsqueda recursiva llevará más tiempo, pero identificará todo archivo que GunCon - + Pointer/Aiming Puntero/Apuntado - + Trigger Gatillo - + Shoot Offscreen Disparar fuera de la pantalla - + A A - + B B - + Relative Left Hacia la izquierda (relativa) - + Relative Right Hacia la derecha (relativa) - + Relative Up Hacia arriba (relativo) - + Relative Down Hacia abajo (relativo) - + Crosshair Image Path Ruta para la imagen de mira - + Path to an image to use as a crosshair/cursor. La ruta de una imagen que servirá como punto de mira/cursor. - + Crosshair Image Scale Escala de la imagen de mira - + Scale of crosshair image on screen. La escala en pantalla de la imagen de mira. - + Cursor Color Color del cursor - + Applies a color to the chosen crosshair images, can be used for multiple players. Specify in HTML/CSS format (e.g. #aabbcc) Aplica un color a las imágenes de mira seleccionadas, ideal para varios jugadores. Utiliza el formato HTML/CSS (p. ej.: #aabbcc) - + X Scale Escala X - + Scales X coordinates relative to the center of the screen. Escala las coordenadas X relativas al centro de la pantalla. @@ -12807,49 +12859,49 @@ La búsqueda recursiva llevará más tiempo, pero identificará todo archivo que Hotkeys - - - - - - - - - - + + + + + + + + + + + - - + General General - + Fast Forward Avance rápido - + Toggle Fast Forward Alternar avance rápido - + Turbo Turbo - + Toggle Turbo Alternar turbo - + Toggle Fullscreen Alternar pantalla completa - + Toggle Pause Alternar pausa @@ -12858,7 +12910,7 @@ La búsqueda recursiva llevará más tiempo, pero identificará todo archivo que Alternar trucos - + Power Off System Apagar sistema @@ -12867,460 +12919,460 @@ La búsqueda recursiva llevará más tiempo, pero identificará todo archivo que Alternar códigos de parche - + Reset System Reiniciar sistema - + Save Screenshot Capturar pantalla - + Change Disc Cambiar disco - + Frame Step Avanzar fotograma - + Rewind Rebobinar - + Toggle Clock Speed Control (Overclocking) Alternar control de velocidad de reloj («overclocking») - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + Graphics Gráficos - + Toggle Software Rendering Alternar renderizado por software - + Toggle PGXP Alternar PGXP - + Toggle PGXP Depth Buffer Alternar búfer de profundidad de la PGXP - + Increase Resolution Scale Incrementar escala de resolución - + Open Pause Menu Abrir menú de pausa - + Open Cheat Settings Abrir configuración de trucos - + Record Single Frame GPU Trace Grabar seguimiento de la GPU monofotograma - + Record Multi-Frame GPU Trace Grabar seguimiento de la GPU multifotograma - + Toggle Media Capture Alternar captura de medios - + Open Achievement List Abrir lista de logros - + Open Leaderboard List Abrir tabla de clasificación - + + - - - - - - - + + + + + + System Sistema - + Swap Memory Card Slots Cambiar ranuras de Memory Card - + Increase Emulation Speed Incrementar velocidad de emulación - + Decrease Emulation Speed Disminuir velocidad de emulación - + Reset Emulation Speed Reiniciar velocidad de emulación - + Decrease Resolution Scale Disminuir escala de resolución - + Toggle Post-Processing Alternar posprocesado - + Toggle Internal Post-Processing Alternar posprocesado interno - + Reload Post Processing Shaders Recargar sombreadores de posprocesado - + Reload Texture Replacements Recargar texturas de reemplazo - + Toggle Widescreen Alternar imagen panorámica - + Toggle PGXP CPU Mode Alternar modo CPU de la PGXP - + Toggle On-Screen Display Alternar mensajes en pantalla - + Rotate Display Clockwise Rotar imagen en sentido horario - + Rotate Display Counterclockwise Rotar imagen en sentido antihorario - - - - - - - - + + + + + + + + Save States Guardados rápidos - + Load From Selected Slot Cargar guardado del espacio seleccionado - + Save To Selected Slot Guardar en espacio seleccionado - + Select Previous Save Slot Seleccionar espacio de guardado anterior - + Select Next Save Slot Seleccionar espacio de guardado siguiente - + Save State and Select Next Slot Crear guardado rápido y seleccionar el espacio siguiente - + Undo Load State Deshacer carga de guardado rápido - + Load Game State 1 Cargar guardado rápido del espacio 1 - + Load Game State 2 Cargar guardado rápido del espacio 2 - + Load Game State 3 Cargar guardado rápido del espacio 3 - + Load Game State 4 Cargar guardado rápido del espacio 4 - + Load Game State 5 Cargar guardado rápido del espacio 5 - + Load Game State 6 Cargar guardado rápido del espacio 6 - + Load Game State 7 Cargar guardado rápido del espacio 7 - + Load Game State 8 Cargar guardado rápido del espacio 8 - + Load Game State 9 Cargar guardado rápido del espacio 9 - + Load Game State 10 Cargar guardado rápido del espacio 10 - + Save Game State 1 Crear guardado rápido en el espacio 1 - + Save Game State 2 Crear guardado rápido en el espacio 2 - + Save Game State 3 Crear guardado rápido en el espacio 3 - + Save Game State 4 Crear guardado rápido en el espacio 4 - + Save Game State 5 Crear guardado rápido en el espacio 5 - + Save Game State 6 Crear guardado rápido en el espacio 6 - + Save Game State 7 Crear guardado rápido en el espacio 7 - + Save Game State 8 Crear guardado rápido en el espacio 8 - + Save Game State 9 Crear guardado rápido en el espacio 9 - + Save Game State 10 Crear guardado rápido en el espacio 10 - + Load Global State 1 Cargar guardado global 1 - + Load Global State 2 Cargar guardado global 2 - + Load Global State 3 Cargar guardado global 3 - + Load Global State 4 Cargar guardado global 4 - + Load Global State 5 Cargar guardado global 5 - + Load Global State 6 Cargar guardado global 6 - + Load Global State 7 Cargar guardado global 7 - + Load Global State 8 Cargar guardado global 8 - + Load Global State 9 Cargar guardado global 9 - + Load Global State 10 Cargar guardado global 10 - + Save Global State 1 Guardar guardado global 1 - + Save Global State 2 Guardar guardado global 2 - + Save Global State 3 Guardar guardado global 3 - + Save Global State 4 Guardar guardado global 4 - + Save Global State 5 Guardar guardado global 5 - + Save Global State 6 Guardar guardado global 6 - + Save Global State 7 Guardar guardado global 7 - + Save Global State 8 Guardar guardado global 8 - + Save Global State 9 Guardar guardado global 9 - + Save Global State 10 Guardar guardado global 10 - - - - + + + + Audio Audio - + Toggle Mute Alternar silencio de audio - + Toggle CD Audio Mute Alternar silencio de audio CD - + Volume Up Subir volumen - + Volume Down Bajar volumen @@ -13665,6 +13717,146 @@ La búsqueda recursiva llevará más tiempo, pero identificará todo archivo que No disponible + + JogCon + + + Failed to create force feedback device for Port {}: +{} + Error al crear dispositivo de Force Feedback en el puerto {}: +{} + + + + D-Pad Up + Botón de dirección hacia arriba + + + + D-Pad Right + Botón de dirección hacia la derecha + + + + D-Pad Down + Botón de dirección hacia abajo + + + + D-Pad Left + Botón de dirección hacia la izquierda + + + + Triangle + Triángulo + + + + Circle + Círculo + + + + Cross + Cruz/X + + + + Square + Cuadrado + + + + Select + SELECT + + + + Start + START + + + + L1 + L1 + + + + R1 + R1 + + + + L2 + L2 + + + + R2 + R2 + + + + Mode + Modo + + + + Steering Left + Girar hacia la izquierda + + + + Steering Right + Girar hacia la derecha + + + + Force Feedback Device + Dispositivo de Force Feedback + + + + Analog Deadzone + Zona muerta del joystick analógico + + + + Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored. + Establece la zona de inactividad del joystick analógico, es decir, cuánto movimiento del joystick se ignorará. + + + + Analog Sensitivity + Sensibilidad analógica + + + + Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent controllers, e.g. DualShock 4, Xbox One Controller. + Establece el factor de escalado para los ejes de los joysticks analógicos. Se recomienda entre 130 % y 140 % para mandos modernos, como el DualShock 4 o el mando de Xbox One. + + + + Button/Trigger Deadzone + Zona muerta de botón/gatillo + + + + Sets the deadzone for activating buttons/triggers, i.e. the fraction of the trigger which will be ignored. + Establece la zona de inactividad para activar botones o gatillos, es decir, la distancia de la pulsación que será ignorada. + + + + Steering Hold Deadzone + Zona muerta del centro del volante + + + + Sets the deadzone for holding the wheel at the set position, i.e. when it will not trigger an effect. + Establece la zona de inactividad de la posición central del volante, es decir, cuándo dejará de hacer efecto. + + Justifier @@ -13857,57 +14049,57 @@ La búsqueda recursiva llevará más tiempo, pero identificará todo archivo que Ventana de registro - + &Clear &Vaciar - + &Save... &Guardar... - + Cl&ose &Cerrar - + &Settings &Configuración - + Log To &System Console Enviar registros a consola del &sistema - + Log To &Debug Console Enviar registros a consola de &depuración - + Log To &File Enviar registros a &archivo - + Attach To &Main Window Acoplar a ventana &principal - + Show &Timestamps Mostrar &marcas de tiempo - + &Log Level &Nivel de registro - + &Channels &Canales @@ -13916,27 +14108,27 @@ La búsqueda recursiva llevará más tiempo, pero identificará todo archivo que &Filtros - + Select Log File Seleccionar archivo de registro - + Log Files (*.txt) Archivos de registro (*.txt) - + Error Error - + Failed to open file for writing. Error al abrir el archivo para su escritura. - + Log was written to %1. Registro escrito en %1. @@ -13989,15 +14181,15 @@ La búsqueda recursiva llevará más tiempo, pero identificará todo archivo que - - + + Change Disc Cambiar disco - - + + Load State Cargar guardado rápido @@ -14117,29 +14309,32 @@ La búsqueda recursiva llevará más tiempo, pero identificará todo archivo que Repositorio en &GitHub... - + + Enable Safe Mode + Habilitar modo seguro + + + Open Texture Directory... Abrir directorio de texturas... - + Reload Texture Replacements Recargar texturas de reemplazo - + Capture GPU Frame Capturar fotograma de la GPU - asdf - asdf + asdf - aaa - aaa + aaa &Issue Tracker... @@ -14321,12 +14516,11 @@ La búsqueda recursiva llevará más tiempo, pero identificará todo archivo que Volcar copias de VRAM a CPU - Disable All Enhancements - Desactivar todas las mejoras + Desactivar todas las mejoras - + Media Ca&pture &Capturar medios @@ -14391,35 +14585,35 @@ La búsqueda recursiva llevará más tiempo, pero identificará todo archivo que Combinar juegos multidisco - - + + Start Big Picture Mode Iniciar Big Picture - - + + Big Picture Big Picture - + Cover Downloader Descargador de carátulas - + Memory &Scanner &Buscador de memoria - + Show Game Icons (List View) Mostrar iconos de juegos (vista de lista) - + Resume Continuar @@ -14499,63 +14693,61 @@ La búsqueda recursiva llevará más tiempo, pero identificará todo archivo que &Aumentar tamaño (vista en cuadrícula) - Ctrl++ - Ctrl++ + Ctrl++ - + Zoom &Out (Grid View) &Disminuir tamaño (vista en cuadrícula) - Ctrl+- - Ctrl+- + Ctrl+- - + Refresh &Covers (Grid View) Actuali&zar carátulas (vista en cuadrícula) - + Open Memory Card Directory... Abrir directorio de Memory Cards... - + Open Data Directory... Abrir directorio de datos... - + Power Off &Without Saving Apagar &sin guardar - - + + Select Disc Image Seleccionar imagen de disco - + Could not find any CD-ROM devices. Please ensure you have a CD-ROM drive connected and sufficient permissions to access it. No se ha encontrado un dispositivo de CD-ROM. Asegúrate de tener una unidad de CD-ROM conectada y los permisos necesarios de acceso. - + %1 (%2) %1 (%2) - + Select disc drive: Selecciona la unidad de disco: - + Start Disc Iniciar disco @@ -14564,88 +14756,88 @@ La búsqueda recursiva llevará más tiempo, pero identificará todo archivo que Administrador de trucos - - + + Properties... Propiedades... - + Open Containing Directory... Abrir directorio contenedor... - - + + Set Cover Image... Establecer imagen de carátula... - + Default Boot Arranque predeterminado - + Fast Boot Arranque rápido - + Full Boot Arranque completo - + Boot and Debug Arrancar y depurar - - + + Exclude From List Excluir de la lista - + Add Search Directory... Añadir directorio de búsqueda... - + Select Cover Image Seleccionar imagen de carátula - + Cover Already Exists La carátula ya existe - + A cover image for this game already exists, do you wish to replace it? Ya existe una carátula para este juego, ¿quieres reemplazarla? - - - - + + + + Copy Error Error de copia - + All File Types (*.bin *.img *.iso *.cue *.chd *.cpe *.ecm *.mds *.pbp *.elf *.exe *.psexe *.ps-exe *.psx *.psf *.minipsf *.m3u *.psxgpu);;Single-Track Raw Images (*.bin *.img *.iso);;Cue Sheets (*.cue);;MAME CHD Images (*.chd);;Error Code Modeler Images (*.ecm);;Media Descriptor Sidecar Images (*.mds);;PlayStation EBOOTs (*.pbp *.PBP);;PlayStation Executables (*.cpe *.elf *.exe *.psexe *.ps-exe, *.psx);;Portable Sound Format Files (*.psf *.minipsf);;Playlists (*.m3u);;PSX GPU Dumps (*.psxgpu) Todos los tipos de archivo (*.bin *.img *.iso *.cue *.chd *.cpe *.ecm *.mds *.pbp *.elf *.exe *.psexe *.ps-exe *.psx *.psf *.minipsf *.m3u *.psxgpu);;Imágenes RAW de una pista (*.bin *.img *.iso);;Archivos CUE (*.cue);;Imágenes CHD para MAME (*.chd);;Imágenes Error Code Modeler (*.ecm);;Imágenes de Media Descriptor (*.mds);;EBOOTs de PlayStation (*.pbp *.PBP);;Ejecutables de PlayStation (*.exe *.psexe *.ps-exe, *.psx);;Archivos Portable Sound Format (*.psf *.minipsf);;Listas de reproducción (*.m3u);;Volcados de la GPU de PSX (*.psxgpu) - + Failed to remove existing cover '%1' Error al eliminar la carátula existente «%1» - + Failed to copy '%1' to '%2' Error al copiar «%1» a «%2» @@ -14654,51 +14846,51 @@ La búsqueda recursiva llevará más tiempo, pero identificará todo archivo que Todos los tipos de archivo (*.bin *.img *.iso *.cue *.chd *.ecm *.mds *.pbp *.exe *.psexe *.ps-exe *.psf *.minipsf *.m3u);;Imágenes RAW de una pista (*.bin *.img *.iso);;Archivo CUE (*.cue);;Imágenes CHD para MAME (*.chd);;Imágenes Error Code Modeler (*.ecm);;Imágenes de Media Descriptor (*.mds);;EBOOTs de PlayStation (*.pbp *.PBP);;Ejecutables de PlayStation (*.exe *.psexe *.ps-exe);;Archivos Portable Sound Format (*.psf *.minipsf);;Listas de reproducción (*.m3u) - - - + + + Error Error - + Failed to get window info from widget Error al obtener la información de la ventana a partir del widget - + Paused En pausa - + Resume (%1) Continuar (%1) - - - + + + Game Save %1 (%2) Estado de juego %1 (%2) - + Edit Memory Cards... Editar Memory Cards... - + Delete Save States... Borrar guardados rápidos... - + Confirm Save State Deletion Confirmar borrado de guardados rápidos - + Are you sure you want to delete all save states for %1? The saves will not be recoverable. @@ -14707,47 +14899,47 @@ The saves will not be recoverable. Una vez sean borrados, no se podrán recuperar. - + Load From File... Cargar archivo... - - + + Select Save State File Seleccionar archivo de guardado rápido - - + + Save States (*.sav) Guardados rápidos (*.sav) - + Undo Load State Deshacer carga de guardado rápido - - + + Game Save %1 (Empty) Estado de juego %1 (vacío) - - + + Global Save %1 (%2) Estado global %1 (%2) - - + + Global Save %1 (Empty) Estado global %1 (vacío) - + Save To File... Guardar en archivo... @@ -14776,12 +14968,12 @@ Una vez sean borrados, no se podrán recuperar. &Aplicar trucos - + Load Resume State Cargar guardado de continuación - + A resume save state was found for this game, saved at: %1. @@ -14794,47 +14986,47 @@ Do you want to load this state, or start from a fresh boot? ¿Deseas cargar este guardado rápido o empezar desde el principio? - + Fresh Boot Empezar de cero - + Delete And Boot Eliminar y empezar - + Failed to delete save state file '%1'. Error al eliminar el guardado rápido «%1». - + Confirm Disc Change Confirmación de cambio de disco - + Do you want to swap discs or boot the new image (via system reset)? ¿Deseas cambiar de disco o ejecutar la imagen nueva (reiniciando el sistema)? - + Swap Disc Cambiar disco - + Reset Reiniciar - + <p>Sorry, you are trying to update a DuckStation version which is not an official GitHub release. To prevent incompatibilities, the auto-updater is only enabled on official builds.</p><p>Please download an official release from from <a href="https://www.duckstation.org/">duckstation.org</a>.</p> <p>Lamentablemente, estás intentando actualizar una versión de PCSX2 que no es una versión oficial de GitHub. El actualizador automático solo está activado en las compilaciones oficiales para evitar incompatibilidades.</p><p>Te rogamos que descargues una compilación oficial de la página <a href="https://www.duckstation.org/">duckstation.org</a>.</p> - + Cancel Cancelar @@ -14843,67 +15035,67 @@ Do you want to load this state, or start from a fresh boot? Todos los tipos de archivo (*.bin *.img *.iso *.cue *.chd *.ecm *.mds *.pbp *.exe *.psexe *.ps-exe *.psx *.psf *.minipsf *.m3u);;Imágenes RAW de una pista (*.bin *.img *.iso);;Archivos CUE (*.cue);;Imágenes CHD para MAME (*.chd);;Imágenes Error Code Modeler (*.ecm);;Imágenes de Media Descriptor (*.mds);;EBOOTs de PlayStation (*.pbp *.PBP);;Ejecutables de PlayStation (*.exe *.psexe *.ps-exe, *.psx);;Archivos Portable Sound Format (*.psf *.minipsf);;Listas de reproducción (*.m3u) - + Select Cheats... Seleccionar trucos... - + Cheats are not enabled. Los trucos están desactivados. - + &Apply Cheat &Aplicar truco - + Stop Big Picture Mode Detener Big Picture - + Exit Big Picture Salir de Big Picture - + You must select a disc to change discs. Para cambiar de disco, debes seleccionar uno. - + Reset Play Time Reiniciar tiempo jugado - + Select Disc Seleccionar disco - + All Cover Image Types (*.jpg *.jpeg *.png *.webp) Todos los archivos de imágenes de carátulas (*.jpg *.jpeg *.png *.webp) - + You must select a different file to the current cover image. Tienes que elegir un archivo que no sea la imagen de caráctula actual. - + Failed to remove '%1' Error al eliminar «%1» - + Confirm Reset Confirmar reinicio - + Are you sure you want to reset the play time for '%1'? This action cannot be undone. @@ -14912,25 +15104,25 @@ This action cannot be undone. Esta acción no se puede deshacer. - + %1x Scale Escala %1x - - - + + + Destination File Archivo de destino - - + + Binary Files (*.bin) Archivos BIN (*.bin) - + Binary Files (*.bin);;PNG Images (*.png) Archivos BIN (*.bin);;Imágenes PNG (*.png) @@ -14985,36 +15177,36 @@ Esta acción no se puede deshacer. QDarkStyle - + Confirm Shutdown Confirmar apagado - + Are you sure you want to shut down the virtual machine? ¿Seguro que quieres apagar la máquina virtual? - + Save State For Resume Guardado de continuación - - - - + + + + Memory Card Not Found Memory Card no encontrada - + Memory card '%1' does not exist. Do you want to create an empty memory card? La Memory Card «%1» no existe. ¿Quieres crear una Memory Card vacía? - - + + Memory card '%1' could not be found. Try starting the game and saving to create it. No se ha encontrado la Memory Card «%1». Intenta iniciar el juego y guardar una partida para crearla. @@ -15031,27 +15223,27 @@ Los trucos persistirán a través de los guardados rápidos, incluso después de ¿Seguro que quieres continuar? - + Failed to create memory card '%1': %2 Error al crear la Memory Card «%1»: %2 - + %1 Files (*.%2) Archivos %1 (*.%2) - + Media Capture Captura de medios - + Updater Error Error de actualización - + Automatic updating is not supported on the current platform. Las actualizaciones automáticas no son compatibles con la plataforma actual. @@ -15059,12 +15251,12 @@ Los trucos persistirán a través de los guardados rápidos, incluso después de MediaCapture - + Failed to load Media Foundation libraries: Error al cargar las bibliotecas de Media Foundation: - + You may be missing one or more files, or are using the incorrect version. This build of DuckStation requires: libavcodec: {} libavformat: {} @@ -15890,8 +16082,17 @@ Si quieres guardar datos, debes eliminar la Memory Card de forma manual. + Freeze Selected Entries + Bloquear resultados seleccionados + + + + Remove Selected Entries + Quitar resultados seleccionados + + Remove Selected Entries from Watch List - Eliminar resultados seleccionados del análisis + Eliminar resultados seleccionados del análisis @@ -15929,17 +16130,17 @@ Si quieres guardar datos, debes eliminar la Memory Card de forma manual.Dirección de RAM para HxD: 0x%1 - + Enter manual address: Introduce la dirección manual: - + Select data size: Selecciona el tamaño de los datos: - + %1 (only showing first %2) %1 (solo se muestran los %2 primeros) @@ -16307,17 +16508,17 @@ Si quieres guardar datos, debes eliminar la Memory Card de forma manual. OSDMessage - + System reset. Reiniciando sistema. - + Disabling PCDrv because no root directory is specified. Desactivando PCDrv porque no se ha especificado un directorio raíz. - + PGXP is incompatible with the software renderer, disabling PGXP. PGXP es incompatible con el renderizador por software, por lo tanto se deshabilitará. @@ -16330,22 +16531,22 @@ Si quieres guardar datos, debes eliminar la Memory Card de forma manual.La predicción de latencia no es compatible con las versiones ARM de 32 bits para Android. - + Rewind is disabled because runahead is enabled. Se ha desactivado la función de rebobinado porque la predicción de latencia está activada. - + Recompiler options changed, flushing all blocks. Las opciones del recompilador han cambiado, limpiando los bloques. - + Widescreen hack is now enabled, and aspect ratio is set to {}. Hack de imagen panorámica habilitado, relación de aspecto establecida en {}. - + Widescreen hack is now disabled, and aspect ratio is set to {}. Hack de imagen panorámica deshabilitado, relación de aspecto establecida en {}. @@ -16366,17 +16567,17 @@ Si quieres guardar datos, debes eliminar la Memory Card de forma manual.Memory Card guardada en «{}». - + CD image preloading not available for multi-disc image '{}' Precarga de imagen de CD no disponible para la imagen multidisco «{}» - + Precaching CD image failed, it may be unreliable. Error al precachear la imagen de CD, el sistema podría ser inestable. - + Switching to {} CPU execution mode. Cambiando al modo de ejecución de CPU {}. @@ -16396,7 +16597,7 @@ Si quieres guardar datos, debes eliminar la Memory Card de forma manual. - + Failed to load state from slot {0}: {1} Error al cargar el guardado rápido del espacio {0}: @@ -16409,53 +16610,53 @@ Si quieres guardar datos, debes eliminar la Memory Card de forma manual. - + Failed to save state to slot {0}: {1} Error al crear un guardado rápido en el espacio {0}: {1} - + CPU clock speed control enabled ({:.3f} MHz). Control de velocidad de reloj de la CPU activado ({:.3f} MHz). - + CPU clock speed control disabled ({:.3f} MHz). Control de velocidad de reloj de la CPU desactivado ({:.3f} MHz). - - - + + + Emulation speed set to {}%. Velocidad de emulación cambiada a {} %. - + PGXP is now enabled. PGXP habilitada. - + PGXP is now disabled. PGXP deshabilitada. - + PGXP Depth Buffer is now enabled. Búfer de profundidad de la PGXP habilitado. - + PGXP Depth Buffer is now disabled. Búfer de profundidad de la PGXP deshabilitado. - - - + + + Volume: {}% Volumen: {} % @@ -16464,102 +16665,102 @@ Si quieres guardar datos, debes eliminar la Memory Card de forma manual.Texturas de reemplazo recargadas. - + Rewinding is not enabled. El rebobinado no está habilitado. - + PGXP CPU mode is now enabled. Modo CPU de la PGXP habilitado. - + PGXP CPU mode is now disabled. Modo CPU de la PGXP deshabilitado. - + Volume: Muted Volumen: silenciado - + CD Audio Muted. Audio de CD silenciado. - + CD Audio Unmuted. Según la RAE es válido el uso del prefijo 'de-'. Audio de CD activado. - + Swapped memory card ports. Both ports have a memory card. Intercambiados los puertos de Memory Card. Ambos tienen una Memory Card. - + Swapped memory card ports. Port 2 has a memory card, Port 1 is empty. Intercambiados los puertos de Memory Card. El puerto 2 contiene una Memory Card y el puerto 1 está vacío. - + Swapped memory card ports. Port 1 has a memory card, Port 2 is empty. Intercambiados los puertos de Memory Card. El puerto 1 contiene una Memory Card y el puerto 2 está vacío. - + Swapped memory card ports. Neither port has a memory card. Intercambiados los puertos de Memory Card. Ninguno tiene una Memory Card. - + Failed to save undo load state: {} Error al crear el guardado rápido para deshacer: {1} - + Loading state from '{}'... Cargando guardado rápido de «{}»... - + State saved to '{}'. Guardado rápido creado en «{}». - + Failed to open CD image from save state '{}': {}. Using existing image '{}', this may result in instability. Error al abrir la imagen de CD del guardado rápido «{}»: {}. Usando la imagen existente «{}», podría haber inestabilidades. - + Failed to open disc image '{}': {}. Error al abrir la imagen de disco «{}»: {}. - + Inserted disc '{}' ({}). Disco «{}» introducido ({}). - + Switching to {}{} GPU renderer. Cambiando al renderizador de GPU {}{}. - + Switching to {} audio backend. Cambiando al motor de audio {}. - + Switching to {} renderer... Cambiando al renderizador por {}... @@ -16570,7 +16771,7 @@ Usando la imagen existente «{}», podría haber inestabilidades. - + No post-processing shaders are selected. No se han seleccionado sombreadores de posprocesado. @@ -16585,7 +16786,7 @@ Usando la imagen existente «{}», podría haber inestabilidades. Posprocesado deshabilitado. - + Post-processing shaders reloaded. Sombreadores de posprocesado recargados. @@ -16868,17 +17069,17 @@ URL introducida: %1 QtAsyncProgressThread - + Error Error - + Question Pregunta - + Information Información @@ -16886,124 +17087,136 @@ URL introducida: %1 QtHost - - - - - - - - - - - - - - + + + + + + + + + + + + + + Error Error - + An error occurred while deleting empty game settings: {} Se ha producido un error al eliminar una configuración de juego en blanco: {} - + An error occurred while saving game settings: {} Se ha producido un error al guardar la configuración de juego: {} - Failed to create HTTPDownloader. - Error al crear HTTPDownloader. + Error al crear HTTPDownloader. - + Downloading %1... Descargando %1... - Download failed with HTTP status code %1. - Error al descargar, código de estado HTTP %1. + Error al descargar, código de estado HTTP %1. - + + Failed to create HTTPDownloader: +%1 + Error al crear HTTPDownloader: +%1 + + + + Download failed with HTTP status code %1: +%2 + Error al descargar, código de estado HTTP %1: +%2 + + + Download failed: Data is empty. Error al descargar: los datos están vacíos. - + Failed to write '%1'. Error al escribir en «%1». - + Failed to open downloaded zip file. Error al abrir el archivo zip descargado. - + Failed to locate '%1' in zip. Error al buscar «%1» en el archivo zip. - + Failed to open '%1': %2. Error al abrir «%1»: «%2». - + Failed to read '%1' from zip. Error al leer «%1» del archivo zip. - + Failed to write to '%1'. Error al escribir a «%1». - + RA: Logged in as %1 (%2, %3 softcore). %4 unread messages. RA: sesión iniciada como %1 (%2, %3 en modo normal). %4 mensajes sin leer. - + Controller {} connected. Mando {} conectado. - + System paused because controller {} was disconnected. Se ha pausado el sistema porque el mando {} se ha desconectado. - + Controller {} disconnected. Mando {} desconectado. - + File '%1' does not exist. El archivo «%1» no existe. - + The specified save state does not exist. El guardado rápido indicado no existe. - + Cannot use no-gui mode, because no boot filename was specified. No se puede utilizar el modo «no-gui» porque no se ha especificado un nombre del archivo de arranque. - + Cannot use batch mode, because no boot filename was specified. No se puede utilizar el modo por lotes porque no se ha especificado un nombre del archivo de arranque. @@ -17031,22 +17244,22 @@ URL introducida: %1 DuckStation - + Cancel Cancelar - + Error Error - + Question Pregunta - + Information Información @@ -17054,72 +17267,72 @@ URL introducida: %1 SaveStateSelectorUI - + Saved at {0:%H:%M} on {0:%a} {0:%Y/%m/%d}. Guardado a las {0:%H:%M} del {0:%d/%m/%Y}. - + Load Cargar - + Save Guardar - + Select Previous Seleccionar anterior - + Select Next Seleccionar siguiente - + {} ({}) {} ({}) - + No save present in this slot. No hay un guardado en este espacio. - + Global Slot {} Espacio global {} - + Game Slot {} Espacio de juego {} - + No save state found in Global Slot {}. No hay guardados rápidos en el espacio global {}. - + No save state found in Slot {}. No hay guardados rápidos en el espacio {}. - + no save yet no hay datos guardados - + Global Save Slot {0} selected ({1}). Espacio global {0} seleccionado ({1}). - + Save Slot {0} selected ({1}). Espacio de guardado {0} seleccionado ({1}). @@ -17305,643 +17518,654 @@ Do you want to create this directory? Zstandard (alta) - + None LogLevel Nada - + Error LogLevel Error - + Warning LogLevel Alerta - + Information LogLevel Información - + Verbose LogLevel Detalles - + Developer LogLevel Desarrollo - + Debug LogLevel Depuración - + Trace LogLevel Seguimiento - + Auto-Detect ConsoleRegion Detección automática - + NTSC-J (Japan) ConsoleRegion NTSC-J (Japón) - + NTSC-U/C (US, Canada) ConsoleRegion NTSC-U/C (EE. UU., Canadá) - + PAL (Europe, Australia) ConsoleRegion PAL (Europa, Australia) - + NTSC-J (Japan) DiscRegion NTSC-J (Japón) - + NTSC-U/C (US, Canada) DiscRegion NTSC-U/C (EE. UU., Canadá) - + PAL (Europe, Australia) DiscRegion PAL (Europa, Australia) - + Other DiscRegion Otra - + Non-PS1 DiscRegion Ajena a PS1 - + Interpreter (Slowest) CPUExecutionMode Intérprete (el más lento) - + Cached Interpreter (Faster) CPUExecutionMode Intérprete en caché (más rápido) - + Recompiler (Fastest) CPUExecutionMode Recompilador (lo más rápido) - New Recompiler (Experimental) CPUExecutionMode - Recompilador nuevo (experimental) + Recompilador nuevo (experimental) - + Disabled (Slowest) CPUFastmemMode Deshabilitado (lo más lento) - + MMap (Hardware, Fastest, 64-Bit Only) CPUFastmemMode MMap (por hardware, el más rápido, solo para 64 bits) - + LUT (Faster) CPUFastmemMode LUT (más rápido) - + Automatic GPURenderer Automático - + Direct3D 11 GPURenderer Direct3D 11 - + Direct3D 12 GPURenderer Direct3D 12 - + Metal GPURenderer Metal - + Vulkan GPURenderer Vulkan - + OpenGL GPURenderer OpenGL - + Software GPURenderer Software - + Nearest-Neighbor GPUTextureFilter Vecino más cercano - + Bilinear GPUTextureFilter Bilineal - + Bilinear (No Edge Blending) GPUTextureFilter Bilineal (sin unión de bordes) - + JINC2 (Slow) GPUTextureFilter JINC2 (lento) - + JINC2 (Slow, No Edge Blending) GPUTextureFilter JINC2 (lento, sin unión de bordes) - + xBR (Very Slow) GPUTextureFilter xBR (muy lento) - + xBR (Very Slow, No Edge Blending) GPUTextureFilter xBR (muy lento, sin unión de bordes) - + Disabled GPULineDetectMode Deshabilitada - + Quads GPULineDetectMode Cuadrados - + Triangles (Basic) GPULineDetectMode Triángulos (básica) - + Triangles (Aggressive) GPULineDetectMode Triángulos (agresiva) - + Disabled GPUDownsampleMode Deshabilitado - + Box (Downsample 3D/Smooth All) GPUDownsampleMode Cuadro (reducir resolución 3D/suavizar todo) - + Adaptive (Preserve 3D/Smooth 2D) GPUDownsampleMode Adaptable (preservar 3D/suavizar 2D) - + Disabled GPUWireframeMode Deshabilitado - + Overlay Wireframe GPUWireframeMode Superponer mallas - + Only Wireframe GPUWireframeMode Mostrar solo mallas - + Disabled GPUDumpCompressionMode Desactivada - + Zstandard (Low) GPUDumpCompressionMode Zstandard (baja) - + Zstandard (Default) GPUDumpCompressionMode Zstandard (predeterminada) - + Zstandard (High) GPUDumpCompressionMode Zstandard (alta) - + XZ (Low) GPUDumpCompressionMode XZ (baja) - + XZ (Default) GPUDumpCompressionMode XZ (predeterminada) - + XZ (High) GPUDumpCompressionMode XZ (alta) - + Disabled (Flickering) DisplayDeinterlacingMode Desactivado (parpadeos) - + Weave (Combing) DisplayDeinterlacingMode «Weave» (efecto de peine) - + Blend (Blur) DisplayDeinterlacingMode Fusión (más borroso) - + Adaptive (FastMAD) DisplayDeinterlacingMode Adaptativo (FastMAD) - + Progressive (Optimal) DisplayDeinterlacingMode Progresivo (óptimo) - + None DisplayCropMode No recortar - + Only Overscan Area DisplayCropMode Solo el área de sobrebarrido - + + Only Overscan Area (Aspect Uncorrected) + DisplayCropMode + Solo el área de sobrebarrido (sin corregir aspecto) + + + All Borders DisplayCropMode Todos los bordes - + + All Borders (Aspect Uncorrected) + DisplayCropMode + Todos los bordes (sin corregir aspecto) + + + Auto (Game Native) DisplayAspectRatio Automática (nativa del juego) - + Stretch To Fill DisplayAspectRatio Estirar para rellenar - + Custom DisplayAspectRatio Personalizada - + Left / Top DisplayAlignment Superior izquierda - + Center DisplayAlignment Centrada - + Right / Bottom DisplayAlignment Inferior derecha - + No Rotation DisplayRotation No rotar - + Rotate 90° (Clockwise) DisplayRotation Rotar 90° (en sentido horario) - + Rotate 180° (Vertical Flip) DisplayRotation Rotar 180° (invertir verticalmente) - + Rotate 270° (Clockwise) DisplayRotation Rotar 270° (en sentido horario) - + Disabled ForceVideoTiming Deshabilitar - + NTSC (60hz) ForceVideoTiming NTSC (60 Hz) - + PAL (50hz) ForceVideoTiming PAL (50 Hz) - + Nearest-Neighbor DisplayScalingMode Vecino más cercano - + Nearest-Neighbor (Integer) DisplayScalingMode Vecino más cercano (por números enteros) - + Bilinear (Smooth) DisplayScalingMode Bilineal (suavizado) - + Bilinear (Sharp) DisplayScalingMode Bilineal (definido) - + Bilinear (Integer) DisplayScalingMode Bilineal (por números enteros) - + Automatic DisplayExclusiveFullscreenControl Automático - + Disallowed DisplayExclusiveFullscreenControl Desactivar - + Allowed DisplayExclusiveFullscreenControl Activar - + Screen Resolution DisplayScreenshotMode Resolución de pantalla - + Internal Resolution DisplayScreenshotMode Resolución interna - + Internal Resolution (Aspect Uncorrected) DisplayScreenshotMode Resolución interna (sin corregir aspecto) - + PNG DisplayScreenshotFormat PNG - + JPEG DisplayScreenshotFormat JPEG - + WebP DisplayScreenshotFormat WebP - + No Memory Card MemoryCardType No utilizar una Memory Card - + Shared Between All Games MemoryCardType Compartida entre todos los juegos - + Separate Card Per Game (Serial) MemoryCardType Memory Card individual para cada juego (por número de serie) - + Separate Card Per Game (Title) MemoryCardType Memory Card individual para cada juego (por título) - + Separate Card Per Game (File Title) MemoryCardType Memory Card individual para cada juego (por nombre de archivo) - + Non-Persistent Card (Do Not Save) MemoryCardType Memory Card no persistente (no guardar) - + Disabled MultitapMode Deshabilitado - + Enable on Port 1 Only MultitapMode Habilitar solamente en el puerto 1 - + Enable on Port 2 Only MultitapMode Habilitar solamente en el puerto 2 - + Enable on Ports 1 and 2 MultitapMode Habilitar en los puertos 1 y 2 - + Uncompressed SaveStateCompressionMode Sin comprimir - + Deflate (Low) SaveStateCompressionMode Deflate (baja) - + Deflate (Default) SaveStateCompressionMode Deflate (predeterminada) - + Deflate (High) SaveStateCompressionMode Deflate (alta) - + Zstandard (Low) SaveStateCompressionMode Zstandard (baja) - + Zstandard (Default) SaveStateCompressionMode Zstandard (predeterminada) - + Zstandard (High) SaveStateCompressionMode Zstandard (alta) @@ -17983,13 +18207,13 @@ Do you want to create this directory? Ampliación de tiempo (alterará el tempo, mejor calidad) - + Media Foundation MediaCaptureBackend Media Foundation - + FFmpeg MediaCaptureBackend FFmpeg @@ -18557,123 +18781,123 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es System - - - + + + Error Error - - + + Failed to boot system: {} Error al arrancar el sistema: {} - + Failed to undo load state. Error al deshacer el guardado rápido. - + Failed to load state: {} Error al cargar el guardado rápido: {} - + Failed to save state: {} Error al crear el guardado rápido: {} - + Cannot save state while memory card is being saved. No se puede crear un guardado rápido mientras se esté guardando en una Memory Card. - + Failed to initialize {} renderer, falling back to software renderer. Error al iniciar el renderizador {}, cambiando al renderizador por software. - + CPU Overclock Taint «Overclocking» de CPU - + CD-ROM Read Speedup Taint Aceleración de lectura de CD-ROM - + CD-ROM Seek Speedup Taint Aceleración de búsqueda de CD-ROM - + Force Frame Timings Taint Forzado de velocidad de fotogramas - + 8MB RAM Taint RAM de 8 MB - + Cheats Taint Trucos - + Game Patches Taint Parches de juegos - + Host GPU device encountered an error and has recovered. This may cause broken rendering. La GPU del equipo ha sufrido un error y se ha recuperado. El renderizado podría mostrar defectos. - + Startup was cancelled. Arranque cancelado. - + This save state was created with a different BIOS. This may cause stability issues. Este guardado rápido se creó con una BIOS distinta. Podría haber problemas de estabilidad. - + WARNING: CPU overclock ({}%) was different in save state ({}%). AVISO: el «overclocking» de la CPU ({} %) era distinto en el guardado rápido ({} %). - - + + System is not in correct state. El estado del sistema no es correcto. - + Save state is incompatible: minimum version is {0} but state is version {1}. Guardado rápido incompatible: la versión mínima es la {0}, pero el guardado es de la versión {1}. - + Save state is incompatible: maximum version is {0} but state is version {1}. Guardado rápido incompatible: la versión máxima es la {0}, pero el guardado es de la versión {1}. - + This save state was created with the following tainted options, and may be unstable. You will need to reset the system to clear any effects. Este guardado rápido ha sido creado con una de las siguientes opciones @@ -18681,117 +18905,117 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es para eliminar cualquier efecto secundario. - + Force frame timings is enabled. Games may run at incorrect speeds. El forzado de velocidad de fotogramas. Los juegos podrían ejecutarse a velocidades incorrectas. - + Texture cache is enabled. This feature is experimental, some games may not render correctly. La caché de texturas está habilitada. Esta característica es experimental y algunos juegos podrían renderizarse incorrectamente. - + Safe mode is enabled. Modo seguro habilitado. - + Overclock disabled. «Overclocking» deshabilitado. - + 8MB RAM disabled. RAM de 8 MB habilitada. - + Cheats disabled. Trucos deshabilitados. - + Patches disabled. Parches deshabilitados. - + Resolution scale set to 1x. Escala de resolución configurada a ×1. - + Multisample anti-aliasing disabled. Suavizado de bordes de muestreo múltiple deshabilitado. - + True color disabled. Color verdadero deshabilitado. - + Texture filtering disabled. Filtrado de texturas deshabilitado. - + Interlaced rendering enabled. Renderizado entrelazado habilitado. - + Video timings set to default. Velocidad de fotogramas configurada al valor predeterminado. - + Widescreen rendering disabled. Renderizado de imagen panorámica deshabilitado. - + FMV chroma smoothing disabled. Suavizado de croma en vídeos FMV deshabilitado. - + CD-ROM read speedup disabled. Aceleración de lectura de CD-ROM deshabilitada. - + CD-ROM seek speedup disabled. Aceleración de búsqueda de CD-ROM deshabilitada. - + Mute CD-ROM audio disabled. Silenciado de audio del CD-ROM deshabilitado. - + VRAM write texture replacements disabled. Escritura de la VRAM a las texturas de reemplazo deshabilitada. - + Use old MDEC routines disabled. Uso de rutinas MDEC antiguas deshabilitado. - + PCDrv disabled. PCDrv deshabilitado. - + Fast boot disabled. Arranque rápido deshabilitado. - + Failed to open CD image '{}' used by save state: Error al abrir la imagen de CD «{}» usada por el guardado rápido: @@ -18818,32 +19042,32 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es - + Per-game memory card cannot be used for slot {} as the running game has no code. Using shared card instead. No puede usarse una Memory Card individual para el juego en la ranura {} ya que el juego no tiene un código. Se usará una Memory Card compartida. - + Per-game memory card cannot be used for slot {} as the running game has no title. Using shared card instead. No puede usarse una Memory Card individual para el juego en la ranura {} ya que el juego no tiene un título. Se usará una Memory Card compartida. - + Using disc-specific memory card '{}' instead of per-game card. Utilizando Memory Card {} individual por disco en vez de por juego. - + Per-game memory card cannot be used for slot {} as the running game has no path. Using shared card instead. No puede usarse una Memory Card individual para el juego en la ranura {} ya que el juego no tiene una ruta. Se usará una Memory Card compartida. - + Game changed, reloading memory cards. Juego cambiado, volviendo a cargar las Memory Cards. - + You are attempting to run a libcrypt protected game without an SBI file: {0}: {1} @@ -18864,7 +19088,7 @@ Revisa las instrucciones del archivo README sobre como agregar un archivo SBI. ¿Quieres continuar? - + You are attempting to run a libcrypt protected game without an SBI file: {0}: {1} @@ -18881,12 +19105,12 @@ Este volcado está incompleto. Debes añadir el archivo SBI para poder ejecutar El nombre del archivo SBI debe ser idéntico al nombre de la imagen de disco. - + Failed to switch to subimage {} in '{}': {}. Error al cambiar a la subimagen {} de «{}»: {}. - + Switched to sub-image {} ({}) in '{}'. Cambiando a subimagen {} ({}) de «{}». @@ -18898,22 +19122,22 @@ El nombre del archivo SBI debe ser idéntico al nombre de la imagen de disco. - + CPU clock speed is set to {}% ({} / {}). This may crash games. Velocidad de CPU establecida en {0} % ({1}/{2}). Los juegos podrían quedarse colgados. - + CD-ROM read speedup set to {}x (effective speed {}x). This may crash games. Aceleración de lectura del CD-ROM establecida en ×{0} (velocidad real ×{1}). Los juegos podrían quedarse colgados. - + CD-ROM seek speedup set to {}. This may crash games. Aceleración de búsqueda del CD-ROM establecida en {}. Los juegos podrían quedarse colgados. - + Instant Instantánea @@ -18922,17 +19146,17 @@ El nombre del archivo SBI debe ser idéntico al nombre de la imagen de disco.El forzado de la velocidad NTSC está activado. Los juegos podrían ejecutarse a velocidades incorrectas. - + Multisample anti-aliasing is enabled, some games may not render correctly. El suavizado de bordes de muestreo múltiple está activado. Los juegos podrían renderizarse incorrectamente. - + Round upscaled texture coordinates is enabled. This may cause rendering errors. Redondeado de coordenadas de texturas escaladas activado. Podrían producirse errores de renderizado. - + 8MB RAM is enabled, this may be incompatible with some games. Los 8 MB de RAM están activados, este modo podría no ser compatible con algunos juegos. @@ -18941,52 +19165,52 @@ El nombre del archivo SBI debe ser idéntico al nombre de la imagen de disco.Se han desactivado todas las mejoras. - + Compatibility settings are not enabled. Some games may not function correctly. Configuración de compatibilidad desactivada. Algunos juegos podrían no funcionar correctamente. - + CD-ROM SubQ Skew is enabled. This will break games. Distorsión del SubQ del CD-ROM habilitada. Los juegos podrían fallar. - + Failed to save resume state: {} Error al crear el guardado rápido de continuación: {} - + capturing audio and video captura de audio y vídeo - + capturing video captura de vídeo - + capturing audio captura de audio - + Failed to create media capture: {0} Error al crear la captura de medios: {0} - + Starting {0} to '{1}'. Iniciando {0} en «{1}». - + Stopped {0} to '{1}'. Deteniendo {0} de «{1}». - + Stopped {0}: {1}. Deteniendo {0}: {1}. @@ -19019,7 +19243,7 @@ El nombre del archivo SBI debe ser idéntico al nombre de la imagen de disco.El truco «{}» ya está aplicado. - + No BIOS image found for {} region. DuckStation requires a PS1 or PS2 BIOS in order to run. @@ -19036,7 +19260,7 @@ Por motivos de legalidad, *es imperativo* que obtengas una BIOS a partir de un s Una vez hayas volcado una imagen de la BIOS, deberás guardarla en la carpeta «bios» del directorio de datos (menú Herramientas -> Abrir directorio de datos). - + No BIOS image found for {} region. No se ha encontrado una imagen de la BIOS de la región {}. From e9644c7eeb157bd1635e6185367de12cf56beda5 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Thu, 5 Dec 2024 16:22:44 +1000 Subject: [PATCH 67/69] ISOReader: Add file extraction helpers --- src/util/iso_reader.cpp | 96 +++++++++++++++++++++++++++++++++++++++++ src/util/iso_reader.h | 9 ++++ 2 files changed, 105 insertions(+) diff --git a/src/util/iso_reader.cpp b/src/util/iso_reader.cpp index e3f676680..ef9d8870b 100644 --- a/src/util/iso_reader.cpp +++ b/src/util/iso_reader.cpp @@ -5,7 +5,9 @@ #include "cd_image.h" #include "common/error.h" +#include "common/file_system.h" #include "common/log.h" +#include "common/progress_callback.h" #include "common/string_util.h" #include "fmt/format.h" @@ -385,3 +387,97 @@ bool IsoReader::ReadFile(const ISODirectoryEntry& de, std::vector* data, Err data->resize(de.length_le); return true; } + +bool IsoReader::WriteFileToStream(std::string_view path, std::FILE* fp, Error* error /* = nullptr */, + ProgressCallback* progress /* = nullptr */) +{ + auto de = LocateFile(path, error); + if (!de) + return false; + + return WriteFileToStream(de.value(), fp, error, progress); +} + +bool IsoReader::WriteFileToStream(const ISODirectoryEntry& de, std::FILE* fp, Error* error /* = nullptr */, + ProgressCallback* progress /* = nullptr */) +{ + if (de.flags & ISODirectoryEntryFlag_Directory) + { + Error::SetString(error, "File is a directory"); + return false; + } + + if (!FileSystem::FSeek64(fp, 0, SEEK_SET, error)) + return false; + + if (de.length_le == 0) + return FileSystem::FTruncate64(fp, 0, error); + + const u32 num_sectors = (de.length_le + (SECTOR_SIZE - 1)) / SECTOR_SIZE; + u32 file_pos = 0; + u8 sector_buffer[SECTOR_SIZE]; + + if (progress) + { + progress->SetProgressRange(de.length_le); + progress->SetProgressValue(0); + } + + for (u32 i = 0, lsn = de.location_le; i < num_sectors; i++, lsn++) + { + if (!ReadSector(sector_buffer, lsn, error)) + return false; + + const u32 write_size = std::min(de.length_le - file_pos, SECTOR_SIZE); + if (std::fwrite(sector_buffer, write_size, 1, fp) != 1) + { + Error::SetErrno(error, "fwrite() failed: ", errno); + return false; + } + + file_pos += write_size; + if (progress) + { + progress->SetProgressValue(file_pos); + if (progress->IsCancelled()) + { + Error::SetStringView(error, "Operation was cancelled."); + return false; + } + } + } + + if (std::fflush(fp) != 0) + { + Error::SetErrno(error, "fflush() failed: ", errno); + return false; + } + + return true; +} + +std::string IsoReader::ISODirectoryEntryDateTime::GetFormattedTime() const +{ + // need to apply the UTC offset, so first convert to unix time + struct tm utime; + utime.tm_year = years_since_1900; + utime.tm_mon = (month > 0) ? (month - 1) : 0; + utime.tm_mday = (day > 0) ? (day - 1) : 0; + utime.tm_hour = hour; + utime.tm_min = minute; + utime.tm_sec = second; + + const s32 uts_offset = static_cast(gmt_offset) * 3600; + const time_t uts = std::mktime(&utime) + uts_offset; + + struct tm ltime; +#ifdef _MSC_VER + localtime_s(<ime, &uts); +#else + localtime_r(&uts, <ime); +#endif + + char buf[128]; + const size_t len = std::strftime(buf, std::size(buf), "%c", <ime); + return std::string(buf, len); +} diff --git a/src/util/iso_reader.h b/src/util/iso_reader.h index 0eb8049f6..54fab6d15 100644 --- a/src/util/iso_reader.h +++ b/src/util/iso_reader.h @@ -5,6 +5,7 @@ #include "common/types.h" +#include #include #include #include @@ -13,6 +14,7 @@ class CDImage; class Error; +class ProgressCallback; class IsoReader { @@ -104,6 +106,8 @@ public: u8 minute; u8 second; s8 gmt_offset; + + std::string GetFormattedTime() const; }; enum ISODirectoryEntryFlags : u8 @@ -161,6 +165,11 @@ public: bool ReadFile(std::string_view path, std::vector* data, Error* error = nullptr); bool ReadFile(const ISODirectoryEntry& de, std::vector* data, Error* error = nullptr); + bool WriteFileToStream(std::string_view path, std::FILE* fp, Error* error = nullptr, + ProgressCallback* progress = nullptr); + bool WriteFileToStream(const ISODirectoryEntry& de, std::FILE* fp, Error* error = nullptr, + ProgressCallback* progress = nullptr); + private: static std::string_view GetDirectoryEntryFileName(const u8* sector, u32 de_sector_offset); From 58f5d7e1ba2f79b79373b990e49bdc8bba505248 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Thu, 5 Dec 2024 16:22:55 +1000 Subject: [PATCH 68/69] Qt: Fix missing status message on delayed progress show --- src/duckstation-qt/qtprogresscallback.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/duckstation-qt/qtprogresscallback.cpp b/src/duckstation-qt/qtprogresscallback.cpp index e23a5e951..9ebd565b8 100644 --- a/src/duckstation-qt/qtprogresscallback.cpp +++ b/src/duckstation-qt/qtprogresscallback.cpp @@ -47,9 +47,7 @@ void QtModalProgressCallback::SetStatusText(const std::string_view text) { ProgressCallback::SetStatusText(text); checkForDelayedShow(); - - if (m_dialog.isVisible()) - m_dialog.setLabelText(QtUtils::StringViewToQString(text)); + m_dialog.setLabelText(QtUtils::StringViewToQString(text)); } void QtModalProgressCallback::SetProgressRange(u32 range) From 541985fb7029f91a3c6931d6cdba57748e7514e0 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Thu, 5 Dec 2024 16:23:10 +1000 Subject: [PATCH 69/69] Qt: Add ISO Browser --- src/duckstation-qt/CMakeLists.txt | 3 + src/duckstation-qt/duckstation-qt.vcxproj | 6 + .../duckstation-qt.vcxproj.filters | 6 + src/duckstation-qt/isobrowserwindow.cpp | 340 ++++++++++++++++++ src/duckstation-qt/isobrowserwindow.h | 45 +++ src/duckstation-qt/isobrowserwindow.ui | 132 +++++++ src/duckstation-qt/mainwindow.cpp | 13 + 7 files changed, 545 insertions(+) create mode 100644 src/duckstation-qt/isobrowserwindow.cpp create mode 100644 src/duckstation-qt/isobrowserwindow.h create mode 100644 src/duckstation-qt/isobrowserwindow.ui diff --git a/src/duckstation-qt/CMakeLists.txt b/src/duckstation-qt/CMakeLists.txt index a1e1edfac..9e2fd8645 100644 --- a/src/duckstation-qt/CMakeLists.txt +++ b/src/duckstation-qt/CMakeLists.txt @@ -106,6 +106,9 @@ set(SRCS interfacesettingswidget.cpp interfacesettingswidget.h interfacesettingswidget.ui + isobrowserwindow.cpp + isobrowserwindow.h + isobrowserwindow.ui logwindow.cpp logwindow.h mainwindow.cpp diff --git a/src/duckstation-qt/duckstation-qt.vcxproj b/src/duckstation-qt/duckstation-qt.vcxproj index d29bacc47..f11917df0 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj +++ b/src/duckstation-qt/duckstation-qt.vcxproj @@ -27,6 +27,7 @@ + @@ -85,6 +86,7 @@ + @@ -242,6 +244,7 @@ + @@ -354,6 +357,9 @@ Document + + Document + diff --git a/src/duckstation-qt/duckstation-qt.vcxproj.filters b/src/duckstation-qt/duckstation-qt.vcxproj.filters index 220b25d9c..2e0b9024a 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj.filters +++ b/src/duckstation-qt/duckstation-qt.vcxproj.filters @@ -175,6 +175,10 @@ moc + + + moc + @@ -237,6 +241,7 @@ + @@ -289,6 +294,7 @@ + diff --git a/src/duckstation-qt/isobrowserwindow.cpp b/src/duckstation-qt/isobrowserwindow.cpp new file mode 100644 index 000000000..cb15c820c --- /dev/null +++ b/src/duckstation-qt/isobrowserwindow.cpp @@ -0,0 +1,340 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#include "isobrowserwindow.h" +#include "qtprogresscallback.h" +#include "qtutils.h" + +#include "util/cd_image.h" + +#include "common/align.h" +#include "common/error.h" +#include "common/file_system.h" +#include "common/log.h" +#include "common/path.h" + +#include +#include +#include +#include +#include + +LOG_CHANNEL(Host); + +ISOBrowserWindow::ISOBrowserWindow(QWidget* parent) : QWidget(parent) +{ + m_ui.setupUi(this); + m_ui.splitter->setSizes({200, 600}); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + connect(m_ui.openFile, &QAbstractButton::clicked, this, &ISOBrowserWindow::onOpenFileClicked); + connect(m_ui.directoryView, &QTreeWidget::itemClicked, this, &ISOBrowserWindow::onDirectoryItemClicked); + connect(m_ui.fileView, &QTreeWidget::itemActivated, this, &ISOBrowserWindow::onFileItemActivated); + connect(m_ui.fileView, &QTreeWidget::itemSelectionChanged, this, &ISOBrowserWindow::onFileItemSelectionChanged); + connect(m_ui.fileView, &QTreeWidget::customContextMenuRequested, this, &ISOBrowserWindow::onFileContextMenuRequested); + connect(m_ui.close, &QAbstractButton::clicked, this, &ISOBrowserWindow::close); +} + +ISOBrowserWindow::~ISOBrowserWindow() = default; + +ISOBrowserWindow* ISOBrowserWindow::createAndOpenFile(QWidget* parent, const QString& path) +{ + ISOBrowserWindow* ib = new ISOBrowserWindow(nullptr); + + Error error; + if (!ib->tryOpenFile(path, &error)) + { + QMessageBox::critical(parent, tr("Error"), + tr("Failed to open %1:\n%2").arg(path).arg(QString::fromStdString(error.GetDescription()))); + delete ib; + return nullptr; + } + + return ib; +} + +bool ISOBrowserWindow::tryOpenFile(const QString& path, Error* error /*= nullptr*/) +{ + const std::string native_path = QDir::toNativeSeparators(path).toStdString(); + std::unique_ptr image = CDImage::Open(native_path.c_str(), false, error); + if (!image) + return false; + + IsoReader new_reader; + if (!new_reader.Open(image.get(), 1, error)) + return false; + + m_image = std::move(image); + m_iso = std::move(new_reader); + m_ui.openPath->setText(QString::fromStdString(native_path)); + setWindowTitle(tr("ISO Browser - %1").arg(QtUtils::StringViewToQString(Path::GetFileName(native_path)))); + populateDirectories(); + populateFiles(QString()); + return true; +} + +void ISOBrowserWindow::resizeEvent(QResizeEvent* ev) +{ + QWidget::resizeEvent(ev); + resizeFileListColumns(); +} + +void ISOBrowserWindow::showEvent(QShowEvent* ev) +{ + QWidget::showEvent(ev); + resizeFileListColumns(); +} + +void ISOBrowserWindow::onOpenFileClicked() +{ + const QString path = QFileDialog::getOpenFileName( + this, tr("Select File"), + m_image ? QtUtils::StringViewToQString(Path::GetDirectory(m_image->GetPath())) : QString()); + if (path.isEmpty()) + return; + + Error error; + if (!tryOpenFile(path, &error)) + { + QMessageBox::critical(this, tr("Error"), + tr("Failed to open %1:\n%2").arg(path).arg(QString::fromStdString(error.GetDescription()))); + return; + } +} + +void ISOBrowserWindow::onDirectoryItemClicked(QTreeWidgetItem* item, int column) +{ + populateFiles(item->data(0, Qt::UserRole).toString()); +} + +void ISOBrowserWindow::onFileItemActivated(QTreeWidgetItem* item, int column) +{ + if (item->data(0, Qt::UserRole + 1).toBool()) + { + // directory + const QString dir = item->data(0, Qt::UserRole).toString(); + populateFiles(dir); + + // select it in the directory list + QTreeWidgetItem* dir_item = findDirectoryItemForPath(dir, nullptr); + if (dir_item) + { + QSignalBlocker sb(m_ui.directoryView); + m_ui.directoryView->clearSelection(); + dir_item->setSelected(true); + } + + return; + } + + // file, go to extract + extractFile(item->data(0, Qt::UserRole).toString()); +} + +void ISOBrowserWindow::onFileItemSelectionChanged() +{ + const QList items = m_ui.fileView->selectedItems(); + if (items.isEmpty()) + { + m_ui.extract->setEnabled(false); + return; + } + + // directory? + const bool is_directory = items.front()->data(0, Qt::UserRole + 1).toBool(); + m_ui.extract->setEnabled(!is_directory); +} + +void ISOBrowserWindow::onFileContextMenuRequested(const QPoint& pos) +{ + const QList items = m_ui.fileView->selectedItems(); + if (items.isEmpty()) + return; + + QMenu menu; + + const bool is_directory = items.front()->data(0, Qt::UserRole + 1).toBool(); + const QString path = items.front()->data(0, Qt::UserRole).toString(); + if (is_directory) + { + connect(menu.addAction(QIcon::fromTheme(QIcon::ThemeIcon::FolderOpen), tr("&Open")), &QAction::triggered, this, + [this, &path]() { populateFiles(path); }); + } + else + { + connect(menu.addAction(QIcon::fromTheme(QIcon::ThemeIcon::DocumentSaveAs), tr("&Extract")), &QAction::triggered, + this, [this, &path]() { extractFile(path); }); + } + + menu.exec(m_ui.fileView->mapToGlobal(pos)); +} + +void ISOBrowserWindow::resizeFileListColumns() +{ + QtUtils::ResizeColumnsForTreeView(m_ui.fileView, {-1, 200, 100}); +} + +void ISOBrowserWindow::extractFile(const QString& path) +{ + const std::string spath = path.toStdString(); + const QString filename = QtUtils::StringViewToQString(Path::GetFileName(spath)); + std::string save_path = + QDir::toNativeSeparators(QFileDialog::getSaveFileName(this, tr("Extract File"), filename)).toStdString(); + if (save_path.empty()) + return; + + Error error; + std::optional de = m_iso.LocateFile(path.toStdString(), &error); + if (de.has_value()) + { + auto fp = FileSystem::CreateAtomicRenamedFile(std::move(save_path), &error); + if (fp) + { + QtModalProgressCallback cb(this, 0.15f); + cb.SetCancellable(true); + cb.SetTitle("ISO Browser"); + cb.SetStatusText(tr("Extracting %1...").arg(filename).toStdString()); + if (m_iso.WriteFileToStream(de.value(), fp.get(), &error, &cb)) + { + if (FileSystem::CommitAtomicRenamedFile(fp, &error)) + return; + } + else + { + // don't display error if cancelled + FileSystem::DiscardAtomicRenamedFile(fp); + if (cb.IsCancellable()) + return; + } + } + } + + QMessageBox::critical(this, tr("Error"), + tr("Failed to save %1:\n%2").arg(path).arg(QString::fromStdString(error.GetDescription()))); +} + +QTreeWidgetItem* ISOBrowserWindow::findDirectoryItemForPath(const QString& path, QTreeWidgetItem* parent) const +{ + if (!parent) + { + parent = m_ui.directoryView->topLevelItem(0); + if (path.isEmpty()) + return parent; + } + + const int count = parent->childCount(); + for (int i = 0; i < count; i++) + { + QTreeWidgetItem* item = parent->child(i); + if (item->data(0, Qt::UserRole) == path) + return item; + + QTreeWidgetItem* child_item = findDirectoryItemForPath(path, item); + if (child_item) + return child_item; + } + + return nullptr; +} + +void ISOBrowserWindow::populateDirectories() +{ + m_ui.directoryView->clear(); + m_ui.extract->setEnabled(false); + + QTreeWidgetItem* root = new QTreeWidgetItem; + root->setIcon(0, QIcon::fromTheme("disc-line")); + root->setText(0, QtUtils::StringViewToQString(Path::GetFileTitle(m_image->GetPath()))); + root->setData(0, Qt::UserRole, QString()); + m_ui.directoryView->addTopLevelItem(root); + + populateSubdirectories(std::string_view(), root); + + root->setExpanded(true); + + QSignalBlocker sb(m_ui.directoryView); + root->setSelected(true); +} + +void ISOBrowserWindow::populateSubdirectories(std::string_view dir, QTreeWidgetItem* parent) +{ + Error error; + std::vector> entries = m_iso.GetEntriesInDirectory(dir, &error); + if (entries.empty() && error.IsValid()) + { + ERROR_LOG("Failed to populate directory '{}': {}", dir, error.GetDescription()); + return; + } + + for (const auto& [full_path, entry] : entries) + { + if (!entry.IsDirectory()) + continue; + + QTreeWidgetItem* item = new QTreeWidgetItem(parent); + item->setIcon(0, QIcon::fromTheme(QStringLiteral("folder-open-line"))); + item->setText(0, QtUtils::StringViewToQString(Path::GetFileName(full_path))); + item->setData(0, Qt::UserRole, QString::fromStdString(full_path)); + populateSubdirectories(full_path, item); + } +} + +void ISOBrowserWindow::populateFiles(const QString& path) +{ + const std::string spath = path.toStdString(); + + m_ui.fileView->clear(); + + Error error; + std::vector> entries = + m_iso.GetEntriesInDirectory(spath, &error); + if (entries.empty() && error.IsValid()) + { + ERROR_LOG("Failed to populate files '{}': {}", spath, error.GetDescription()); + return; + } + + const auto add_entry = [this](const std::string& full_path, const IsoReader::ISODirectoryEntry& entry) { + const std::string_view filename = Path::GetFileName(full_path); + + QTreeWidgetItem* item = new QTreeWidgetItem; + item->setIcon( + 0, QIcon::fromTheme(entry.IsDirectory() ? QStringLiteral("folder-open-line") : QStringLiteral("file-line"))); + item->setText(0, QtUtils::StringViewToQString(Path::GetFileName(full_path))); + item->setData(0, Qt::UserRole, QString::fromStdString(full_path)); + item->setData(0, Qt::UserRole + 1, entry.IsDirectory()); + item->setText(1, QString::fromStdString(entry.recoding_time.GetFormattedTime())); + item->setText(2, tr("%1 KB").arg(Common::AlignUpPow2(entry.length_le, 1024) / 1024)); + m_ui.fileView->addTopLevelItem(item); + }; + + if (!path.isEmpty()) + { + QTreeWidgetItem* item = new QTreeWidgetItem; + item->setIcon(0, QIcon::fromTheme(QStringLiteral("folder-open-line"))); + item->setText(0, tr("")); + item->setData(0, Qt::UserRole, QtUtils::StringViewToQString(Path::GetDirectory(spath))); + item->setData(0, Qt::UserRole + 1, true); + m_ui.fileView->addTopLevelItem(item); + } + + // list directories first + for (const auto& [full_path, entry] : entries) + { + if (!entry.IsDirectory()) + continue; + + add_entry(full_path, entry); + } + + for (const auto& [full_path, entry] : entries) + { + if (entry.IsDirectory()) + continue; + + add_entry(full_path, entry); + } + + // this is utter shit, the scrollbar visibility doesn't update in time, so we have to queue it. + QTimer::singleShot(20, Qt::TimerType::CoarseTimer, this, SLOT(resizeFileListColumns())); +} diff --git a/src/duckstation-qt/isobrowserwindow.h b/src/duckstation-qt/isobrowserwindow.h new file mode 100644 index 000000000..6e67c8c03 --- /dev/null +++ b/src/duckstation-qt/isobrowserwindow.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#pragma once + +#include "ui_isobrowserwindow.h" + +#include "util/iso_reader.h" + +class ISOBrowserWindow : public QWidget +{ + Q_OBJECT + +public: + ISOBrowserWindow(QWidget* parent = nullptr); + ~ISOBrowserWindow(); + + static ISOBrowserWindow* createAndOpenFile(QWidget* parent, const QString& path); + + bool tryOpenFile(const QString& path, Error* error = nullptr); + +protected: + void resizeEvent(QResizeEvent* ev); + void showEvent(QShowEvent* ev); + +private Q_SLOTS: + void onOpenFileClicked(); + void onDirectoryItemClicked(QTreeWidgetItem* item, int column); + void onFileItemActivated(QTreeWidgetItem* item, int column); + void onFileItemSelectionChanged(); + void onFileContextMenuRequested(const QPoint& pos); + void resizeFileListColumns(); + +private: + void populateDirectories(); + void populateSubdirectories(std::string_view dir, QTreeWidgetItem* parent); + void populateFiles(const QString& path); + void extractFile(const QString& path); + + QTreeWidgetItem* findDirectoryItemForPath(const QString& path, QTreeWidgetItem* parent = nullptr) const; + + Ui::ISOBrowserWindow m_ui; + std::unique_ptr m_image; + IsoReader m_iso; +}; diff --git a/src/duckstation-qt/isobrowserwindow.ui b/src/duckstation-qt/isobrowserwindow.ui new file mode 100644 index 000000000..5dd42e5fc --- /dev/null +++ b/src/duckstation-qt/isobrowserwindow.ui @@ -0,0 +1,132 @@ + + + ISOBrowserWindow + + + + 0 + 0 + 828 + 511 + + + + Form + + + + :/icons/media-optical.png:/icons/media-optical.png + + + + + + + + File: + + + + + + + true + + + + + + + ... + + + + + + + + + + + + Qt::Orientation::Horizontal + + + + QAbstractItemView::EditTrigger::NoEditTriggers + + + true + + + + Name + + + + + + Qt::ContextMenuPolicy::CustomContextMenu + + + QAbstractItemView::EditTrigger::NoEditTriggers + + + false + + + + Name + + + + + Date + + + + + Size + + + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + Extract + + + + + + + Close + + + + + + + + + + + + diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 44e349107..02aab3b49 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -12,6 +12,7 @@ #include "gamelistsettingswidget.h" #include "gamelistwidget.h" #include "interfacesettingswidget.h" +#include "isobrowserwindow.h" #include "logwindow.h" #include "memorycardeditorwindow.h" #include "memoryscannerwindow.h" @@ -1422,6 +1423,18 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point) QtUtils::OpenURL(this, QUrl::fromLocalFile(fi.absolutePath())); }); + if (entry->IsDisc()) + { + connect(menu.addAction(tr("Browse ISO...")), &QAction::triggered, [this, entry]() { + ISOBrowserWindow* ib = ISOBrowserWindow::createAndOpenFile(this, QString::fromStdString(entry->path)); + if (ib) + { + ib->setAttribute(Qt::WA_DeleteOnClose); + ib->show(); + } + }); + } + connect(menu.addAction(tr("Set Cover Image...")), &QAction::triggered, [this, entry]() { setGameListEntryCoverImage(entry); });