Compare commits

..
Author SHA1 Message Date
David 953d49810a Merge pull request #2797 from FearlessTobi/port-4877
Port citra-emu/citra#4877: "citra_qt: on osx chdir to bundle dir to allow detection of user folder"
2019-09-05 12:29:50 +10:00
David d34fa7c4fa Merge pull request #2802 from ReinUsesLisp/hsetp2-pred
half_set_predicate: Fix HSETP2 predicate assignments
2019-09-05 12:26:39 +10:00
David 14d8c1b594 Merge pull request #2418 from DarkLordZach/srv-es
es: Implement various ticket accessor commands from IEticketService
2019-09-05 12:13:14 +10:00
FearlessTobi 1aec2ff4d2 Address review comments 2019-09-05 03:40:49 +02:00
David aa8daaf22a Merge pull request #2808 from FearlessTobi/port-4866
Port citra-emu/citra#4866: "configure_dialog: reverse tab map to avoid logic based on user-facing/translatable text"
2019-09-05 11:02:53 +10:00
David 8795645d97 Merge pull request #2707 from DarkLordZach/oss-miimodel
system_archive: Add open-source reimplementation of MiiModel data
2019-09-05 10:56:32 +10:00
David b3e1ec25fc Merge pull request #2807 from FearlessTobi/port-4864
Port citra-emu/citra#4864: "yuzu/configure: move speed limiter to general"
2019-09-05 10:54:28 +10:00
FearlessTobiandWeiyi Wang ea8244301d yuzu/configure: move speed limiter to general
The speed limiter being a frame limiter is an implmentation detail and can be changed in the future. What user care about is that it limit the emulation speed in genenral (not just graphics but also audio+input)

Co-Authored-By: Weiyi Wang <[email protected]>
2019-09-05 02:11:37 +02:00
David a956d0b0eb Merge pull request #2830 from FearlessTobi/port-4911
Port citra-emu/citra#4911: "Add cancel option to analog stick configuration"
2019-09-05 09:53:44 +10:00
David d8e59a28ea Merge pull request #2834 from Morph1984/audrenu_QueryAudioDeviceInputEvent
audren_u: Stub IAudioDevice::QueryAudioDeviceInputEvent
2019-09-05 09:51:50 +10:00
fearlessTobiandWeiyi Wang 9ca4718aed configure_dialog: reverse tab map to avoid logic based on user-facing/translatable text
Co-Authored-By: Weiyi Wang <[email protected]>
2019-09-04 17:09:08 +02:00
ReinUsesLisp dfae2d141a half_set_predicate: Fix predicate assignments 2019-09-04 01:54:23 -03:00
Morph1984 84815fa879 Add Kernel::EventPair audio_input_device_switch_event; 2019-09-03 23:50:56 -04:00
Morph1984 1449ed9dbf audren_u: Stub IAudioDevice::QueryAudioDeviceInputEvent 2019-09-03 23:13:32 -04:00
fearlessTobiandVitor K 22fd208e8d Add cancel option to analog stick configuration
Co-Authored-By: Vitor K <[email protected]>
2019-09-03 22:53:59 +02:00
Weiyi Wang 0580112940 Guard unistd.h with MacOS only macro
Fix compile error on Windows caused by #4877
Weird, I thought I saw this guard during the code review...
2019-08-22 15:58:39 +02:00
B3n30 246b515a86 citra_qt: on osx chdir to bundle dir to allow detection of user folder 2019-08-22 14:41:11 +02:00
Zach Hilman adab188c2b system_archive: Add open-source reimplementation of MiiModel data 2019-07-10 07:21:36 -04:00
Zach Hilman 50d5414075 key_manager: Convert Ticket union to std::variant 2019-07-07 21:38:33 -04:00
Zach Hilman d9ef20e5a5 es: Populate/synthesize tickets on construction 2019-07-07 21:38:33 -04:00
Zach Hilman f8718ae779 key_manager: Add structure for Ticket parsing 2019-07-07 21:38:33 -04:00
Zach Hilman b294b13584 es: Implement ETicket GetPersonalizedTicketData (17)
Copies the raw personal ticket data into the buffer provided.
2019-07-07 21:38:33 -04:00
Zach Hilman c6a32dc077 es: Implement ETicket GetCommonTicketData (16)
Copies the raw common ticket data for the specified rights ID into the buffer provided.
2019-07-07 21:38:33 -04:00
Zach Hilman 44b0c19f6a es: Implement ETicket GetPersonalizedTicketSize (15)
Returns the size of the buffer needed to hold the personal ticket associated with the rights ID.
2019-07-07 21:38:33 -04:00
Zach Hilman 35b617b57f es: Implement ETicket GetCommonTicketSize (14)
Returns the size of the buffer needed to hold the common ticket associated with the rights ID.
2019-07-07 21:38:33 -04:00
Zach Hilman 669a21babb es: Implement ETicket ListPersonalizedTicket (12)
Returns an application-specific number of entries of personal tickets, starting at offset 0.
2019-07-07 21:38:33 -04:00
Zach Hilman 5d6bf75296 es: Implement ETicket ListCommonTicket (11)
Returns an application specified count of entries of common tickets, starting at offset 0.
2019-07-07 21:38:33 -04:00
Zach Hilman 11f45e6015 es: Implement ETicket CountPersonalizedTicket (10)
Returns the number of personalized (console/user-unique) tickets in the KeyManager.
2019-07-07 21:38:33 -04:00
Zach Hilman 71bc2182c2 es: Implement ETicket CountCommonTicket (9)
Returns the number of common (non-console-unique) tickets in the KeyManager.
2019-07-07 21:38:33 -04:00
Zach Hilman 475a7a4446 es: Implement ETicket GetTitleKey (8)
Takes a rights ID as input and returns the associated title key, if it exists.
2019-07-07 21:38:33 -04:00
Zach Hilman f15f73a555 es: Implement ETicket ImportTicket (1)
Takes a ticket and certificate and installs it to the KeyManager.
2019-07-07 21:38:33 -04:00
Zach Hilman e35fac2054 key_manager: Add accessors/helpers for ticket management 2019-07-07 21:38:33 -04:00
Zach Hilman 5275fd2789 key_manager: Add equality operator for RSAKeyPair 2019-07-07 21:38:33 -04:00
16 changed files with 704 additions and 123 deletions

No files matched your search

+2
View File
@@ -70,6 +70,8 @@ add_library(core STATIC
file_sys/sdmc_factory.h
file_sys/submission_package.cpp
file_sys/submission_package.h
file_sys/system_archive/mii_model.cpp
file_sys/system_archive/mii_model.h
file_sys/system_archive/ng_word.cpp
file_sys/system_archive/ng_word.h
file_sys/system_archive/system_archive.cpp
+207 -34
View File
@@ -37,6 +37,7 @@
namespace Core::Crypto {
constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
constexpr u64 FULL_TICKET_SIZE = 0x400;
using namespace Common;
@@ -55,6 +56,99 @@ const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{
{{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"},
};
namespace {
template <std::size_t Size>
bool IsAllZeroArray(const std::array<u8, Size>& array) {
return std::all_of(array.begin(), array.end(), [](const auto& elem) { return elem == 0; });
}
} // namespace
u64 GetSignatureTypeDataSize(SignatureType type) {
switch (type) {
case SignatureType::RSA_4096_SHA1:
case SignatureType::RSA_4096_SHA256:
return 0x200;
case SignatureType::RSA_2048_SHA1:
case SignatureType::RSA_2048_SHA256:
return 0x100;
case SignatureType::ECDSA_SHA1:
case SignatureType::ECDSA_SHA256:
return 0x3C;
}
UNREACHABLE();
}
u64 GetSignatureTypePaddingSize(SignatureType type) {
switch (type) {
case SignatureType::RSA_4096_SHA1:
case SignatureType::RSA_4096_SHA256:
case SignatureType::RSA_2048_SHA1:
case SignatureType::RSA_2048_SHA256:
return 0x3C;
case SignatureType::ECDSA_SHA1:
case SignatureType::ECDSA_SHA256:
return 0x40;
}
UNREACHABLE();
}
SignatureType Ticket::GetSignatureType() const {
if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
return ticket->sig_type;
}
if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
return ticket->sig_type;
}
if (auto ticket = std::get_if<ECDSATicket>(&data)) {
return ticket->sig_type;
}
UNREACHABLE();
}
TicketData& Ticket::GetData() {
if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
return ticket->data;
}
if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
return ticket->data;
}
if (auto ticket = std::get_if<ECDSATicket>(&data)) {
return ticket->data;
}
UNREACHABLE();
}
const TicketData& Ticket::GetData() const {
if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
return ticket->data;
}
if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
return ticket->data;
}
if (auto ticket = std::get_if<ECDSATicket>(&data)) {
return ticket->data;
}
UNREACHABLE();
}
u64 Ticket::GetSize() const {
const auto sig_type = GetSignatureType();
return sizeof(SignatureType) + GetSignatureTypeDataSize(sig_type) +
GetSignatureTypePaddingSize(sig_type) + sizeof(TicketData);
}
Ticket Ticket::SynthesizeCommon(Key128 title_key, const std::array<u8, 16>& rights_id) {
RSA2048Ticket out{};
out.sig_type = SignatureType::RSA_2048_SHA256;
out.data.rights_id = rights_id;
out.data.title_key_common = title_key;
return Ticket{out};
}
Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) {
Key128 out{};
@@ -135,6 +229,27 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) {
}
}
RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const {
if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek))
return {};
const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek);
std::vector<u8> extended_iv(eticket_extended_kek.begin(), eticket_extended_kek.begin() + 0x10);
std::array<u8, 0x230> extended_dec{};
AESCipher<Key128> rsa_1(eticket_final, Mode::CTR);
rsa_1.SetIV(extended_iv);
rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10,
extended_dec.data(), Op::Decrypt);
RSAKeyPair<2048> rsa_key{};
std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size());
std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size());
std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size());
return rsa_key;
}
Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) {
AESCipher<Key128> mac_cipher(keyblob_key, Mode::ECB);
Key128 mac_key{};
@@ -237,7 +352,7 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
return Loader::ResultStatus::Success;
}
std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) {
std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save) {
if (!ticket_save.IsOpen())
return {};
@@ -246,14 +361,14 @@ std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) {
return {};
}
std::vector<TicketRaw> out;
std::vector<Ticket> out;
for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) {
if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 &&
buffer[offset + 3] == 0x0) {
out.emplace_back();
auto& next = out.back();
std::memcpy(&next, buffer.data() + offset, sizeof(TicketRaw));
offset += next.size();
std::memcpy(&next, buffer.data() + offset, sizeof(Ticket));
offset += FULL_TICKET_SIZE;
}
}
@@ -305,29 +420,23 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
return offset;
}
std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
const RSAKeyPair<2048>& key) {
u32 cert_authority;
std::memcpy(&cert_authority, ticket.data() + 0x140, sizeof(cert_authority));
if (cert_authority == 0)
const auto issuer = ticket.GetData().issuer;
if (issuer == std::array<u8, 0x40>{})
return {};
if (cert_authority != Common::MakeMagic('R', 'o', 'o', 't')) {
LOG_INFO(Crypto,
"Attempting to parse ticket with non-standard certificate authority {:08X}.",
cert_authority);
if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') {
LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority.");
}
Key128 rights_id;
std::memcpy(rights_id.data(), ticket.data() + 0x2A0, sizeof(Key128));
Key128 rights_id = ticket.GetData().rights_id;
if (rights_id == Key128{})
return {};
Key128 key_temp{};
if (!std::any_of(ticket.begin() + 0x190, ticket.begin() + 0x280, [](u8 b) { return b != 0; })) {
std::memcpy(key_temp.data(), ticket.data() + 0x180, key_temp.size());
return std::make_pair(rights_id, key_temp);
if (!std::any_of(ticket.GetData().title_key_common_pad.begin(),
ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) {
return std::make_pair(rights_id, ticket.GetData().title_key_common);
}
mbedtls_mpi D; // RSA Private Exponent
@@ -342,7 +451,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size());
mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size());
mbedtls_mpi_read_binary(&S, ticket.data() + 0x180, 0x100);
mbedtls_mpi_read_binary(&S, ticket.GetData().title_key_block.data(), 0x100);
mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr);
@@ -366,6 +475,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
return {};
ASSERT(*offset > 0);
Key128 key_temp{};
std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size());
return std::make_pair(rights_id, key_temp);
@@ -450,6 +560,8 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16);
encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
} else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) {
eticket_extended_kek = Common::HexStringToArray<576>(out[1]);
} else {
for (const auto& kv : KEYS_VARIABLE_LENGTH) {
if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2))
@@ -862,20 +974,19 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
// Titlekeys
data.DecryptProdInfo(GetBISKey(0));
const auto eticket_extended_kek = data.GetETicketExtendedKek();
eticket_extended_kek = data.GetETicketExtendedKek();
WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek);
PopulateTickets();
}
std::vector<u8> extended_iv(0x10);
std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size());
std::array<u8, 0x230> extended_dec{};
AESCipher<Key128> rsa_1(eticket_final, Mode::CTR);
rsa_1.SetIV(extended_iv);
rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10,
extended_dec.data(), Op::Decrypt);
void KeyManager::PopulateTickets() {
const auto rsa_key = GetETicketRSAKey();
RSAKeyPair<2048> rsa_key{};
std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size());
std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size());
std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size());
if (rsa_key == RSAKeyPair<2048>{})
return;
if (!common_tickets.empty() && !personal_tickets.empty())
return;
const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"/system/save/80000000000000e1",
@@ -886,19 +997,41 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
const auto blob2 = GetTicketblob(save2);
auto res = GetTicketblob(save1);
const auto idx = res.size();
res.insert(res.end(), blob2.begin(), blob2.end());
for (const auto& raw : res) {
const auto pair = ParseTicket(raw, rsa_key);
for (std::size_t i = 0; i < res.size(); ++i) {
const auto common = i < idx;
const auto pair = ParseTicket(res[i], rsa_key);
if (!pair)
continue;
const auto& [rid, key] = *pair;
u128 rights_id;
std::memcpy(rights_id.data(), rid.data(), rid.size());
if (common) {
common_tickets[rights_id] = res[i];
} else {
personal_tickets[rights_id] = res[i];
}
SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
}
}
void KeyManager::SynthesizeTickets() {
for (const auto& key : s128_keys) {
if (key.first.type != S128KeyType::Titlekey) {
continue;
}
u128 rights_id{key.first.field1, key.first.field2};
Key128 rights_id_2;
std::memcpy(rights_id_2.data(), rights_id.data(), rights_id_2.size());
const auto ticket = Ticket::SynthesizeCommon(key.second, rights_id_2);
common_tickets.insert_or_assign(rights_id, ticket);
}
}
void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) {
if (key == Key128{})
return;
@@ -997,6 +1130,46 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
DeriveBase();
}
const std::map<u128, Ticket>& KeyManager::GetCommonTickets() const {
return common_tickets;
}
const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const {
return personal_tickets;
}
bool KeyManager::AddTicketCommon(Ticket raw) {
const auto rsa_key = GetETicketRSAKey();
if (rsa_key == RSAKeyPair<2048>{})
return false;
const auto pair = ParseTicket(raw, rsa_key);
if (!pair)
return false;
const auto& [rid, key] = *pair;
u128 rights_id;
std::memcpy(rights_id.data(), rid.data(), rid.size());
common_tickets[rights_id] = raw;
SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
return true;
}
bool KeyManager::AddTicketPersonalized(Ticket raw) {
const auto rsa_key = GetETicketRSAKey();
if (rsa_key == RSAKeyPair<2048>{})
return false;
const auto pair = ParseTicket(raw, rsa_key);
if (!pair)
return false;
const auto& [rid, key] = *pair;
u128 rights_id;
std::memcpy(rights_id.data(), rid.data(), rid.size());
common_tickets[rights_id] = raw;
SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
return true;
}
const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
{"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}},
{"eticket_rsa_kek_source",
+109 -7
View File
@@ -9,8 +9,10 @@
#include <optional>
#include <string>
#include <variant>
#include <boost/container/flat_map.hpp>
#include <fmt/format.h>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/crypto/partition_data_manager.h"
#include "core/file_sys/vfs_types.h"
@@ -30,7 +32,79 @@ constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180;
using Key128 = std::array<u8, 0x10>;
using Key256 = std::array<u8, 0x20>;
using SHA256Hash = std::array<u8, 0x20>;
using TicketRaw = std::array<u8, 0x400>;
enum class SignatureType {
RSA_4096_SHA1 = 0x10000,
RSA_2048_SHA1 = 0x10001,
ECDSA_SHA1 = 0x10002,
RSA_4096_SHA256 = 0x10003,
RSA_2048_SHA256 = 0x10004,
ECDSA_SHA256 = 0x10005,
};
u64 GetSignatureTypeDataSize(SignatureType type);
u64 GetSignatureTypePaddingSize(SignatureType type);
enum class TitleKeyType : u8 {
Common = 0,
Personalized = 1,
};
struct TicketData {
std::array<u8, 0x40> issuer;
union {
std::array<u8, 0x100> title_key_block;
struct {
Key128 title_key_common;
std::array<u8, 0xF0> title_key_common_pad;
};
};
INSERT_PADDING_BYTES(0x1);
TitleKeyType type;
INSERT_PADDING_BYTES(0x3);
u8 revision;
INSERT_PADDING_BYTES(0xA);
u64 ticket_id;
u64 device_id;
std::array<u8, 0x10> rights_id;
u32 account_id;
INSERT_PADDING_BYTES(0x14C);
};
static_assert(sizeof(TicketData) == 0x2C0, "TicketData has incorrect size.");
struct RSA4096Ticket {
SignatureType sig_type;
std::array<u8, 0x200> sig_data;
INSERT_PADDING_BYTES(0x3C);
TicketData data;
};
struct RSA2048Ticket {
SignatureType sig_type;
std::array<u8, 0x100> sig_data;
INSERT_PADDING_BYTES(0x3C);
TicketData data;
};
struct ECDSATicket {
SignatureType sig_type;
std::array<u8, 0x3C> sig_data;
INSERT_PADDING_BYTES(0x40);
TicketData data;
};
struct Ticket {
std::variant<RSA4096Ticket, RSA2048Ticket, ECDSATicket> data;
SignatureType GetSignatureType() const;
TicketData& GetData();
const TicketData& GetData() const;
u64 GetSize() const;
static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id);
};
static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big.");
@@ -43,6 +117,19 @@ struct RSAKeyPair {
std::array<u8, 4> exponent;
};
template <size_t bit_size, size_t byte_size>
bool operator==(const RSAKeyPair<bit_size, byte_size>& lhs,
const RSAKeyPair<bit_size, byte_size>& rhs) {
return std::tie(lhs.encryption_key, lhs.decryption_key, lhs.modulus, lhs.exponent) ==
std::tie(rhs.encryption_key, rhs.decryption_key, rhs.modulus, rhs.exponent);
}
template <size_t bit_size, size_t byte_size>
bool operator!=(const RSAKeyPair<bit_size, byte_size>& lhs,
const RSAKeyPair<bit_size, byte_size>& rhs) {
return !(lhs == rhs);
}
enum class KeyCategory : u8 {
Standard,
Title,
@@ -151,22 +238,35 @@ public:
static bool KeyFileExists(bool title);
// Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system save
// 8*43 and the private file to exist.
// Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system
// save 8*43 and the private file to exist.
void DeriveSDSeedLazy();
bool BaseDeriveNecessary() const;
void DeriveBase();
void DeriveETicket(PartitionDataManager& data);
void PopulateTickets();
void SynthesizeTickets();
void PopulateFromPartitionData(PartitionDataManager& data);
const std::map<u128, Ticket>& GetCommonTickets() const;
const std::map<u128, Ticket>& GetPersonalizedTickets() const;
bool AddTicketCommon(Ticket raw);
bool AddTicketPersonalized(Ticket raw);
private:
std::map<KeyIndex<S128KeyType>, Key128> s128_keys;
std::map<KeyIndex<S256KeyType>, Key256> s256_keys;
// Map from rights ID to ticket
std::map<u128, Ticket> common_tickets;
std::map<u128, Ticket> personal_tickets;
std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{};
std::array<std::array<u8, 0x90>, 0x20> keyblobs{};
std::array<u8, 576> eticket_extended_kek{};
bool dev_mode;
void LoadFromFile(const std::string& filename, bool is_title_keys);
@@ -178,6 +278,8 @@ private:
void DeriveGeneralPurposeKeys(std::size_t crypto_revision);
RSAKeyPair<2048> GetETicketRSAKey() const;
void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
@@ -195,11 +297,11 @@ std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblo
std::optional<Key128> DeriveSDSeed();
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys);
std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save);
std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save);
// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority (offset
// 0x140-0x144 is zero)
std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority
// (offset 0x140-0x144 is zero)
std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
const RSAKeyPair<2048>& eticket_extended_key);
} // namespace Core::Crypto
@@ -0,0 +1,46 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/file_sys/system_archive/mii_model.h"
#include "core/file_sys/vfs_vector.h"
namespace FileSys::SystemArchive {
namespace MiiModelData {
constexpr std::array<u8, 0x10> NFTR_STANDARD{'N', 'F', 'T', 'R', 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
constexpr std::array<u8, 0x10> NFSR_STANDARD{'N', 'F', 'S', 'R', 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
constexpr auto TEXTURE_LOW_LINEAR = NFTR_STANDARD;
constexpr auto TEXTURE_LOW_SRGB = NFTR_STANDARD;
constexpr auto TEXTURE_MID_LINEAR = NFTR_STANDARD;
constexpr auto TEXTURE_MID_SRGB = NFTR_STANDARD;
constexpr auto SHAPE_HIGH = NFSR_STANDARD;
constexpr auto SHAPE_MID = NFSR_STANDARD;
} // namespace MiiModelData
VirtualDir MiiModel() {
auto out = std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{},
std::vector<VirtualDir>{}, "data");
out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_LINEAR.size()>>(
MiiModelData::TEXTURE_LOW_LINEAR, "NXTextureLowLinear.dat"));
out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_SRGB.size()>>(
MiiModelData::TEXTURE_LOW_SRGB, "NXTextureLowSRGB.dat"));
out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_LINEAR.size()>>(
MiiModelData::TEXTURE_MID_LINEAR, "NXTextureMidLinear.dat"));
out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_SRGB.size()>>(
MiiModelData::TEXTURE_MID_SRGB, "NXTextureMidSRGB.dat"));
out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_HIGH.size()>>(
MiiModelData::SHAPE_HIGH, "ShapeHigh.dat"));
out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_MID.size()>>(
MiiModelData::SHAPE_MID, "ShapeMid.dat"));
return std::move(out);
}
} // namespace FileSys::SystemArchive
@@ -0,0 +1,13 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/file_sys/vfs_types.h"
namespace FileSys::SystemArchive {
VirtualDir MiiModel();
} // namespace FileSys::SystemArchive
@@ -4,6 +4,7 @@
#include "common/logging/log.h"
#include "core/file_sys/romfs.h"
#include "core/file_sys/system_archive/mii_model.h"
#include "core/file_sys/system_archive/ng_word.h"
#include "core/file_sys/system_archive/system_archive.h"
#include "core/file_sys/system_archive/system_version.h"
@@ -24,7 +25,7 @@ struct SystemArchiveDescriptor {
constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHIVES{{
{0x0100000000000800, "CertStore", nullptr},
{0x0100000000000801, "ErrorMessage", nullptr},
{0x0100000000000802, "MiiModel", nullptr},
{0x0100000000000802, "MiiModel", &MiiModel},
{0x0100000000000803, "BrowserDll", nullptr},
{0x0100000000000804, "Help", nullptr},
{0x0100000000000805, "SharedFont", nullptr},
+15 -1
View File
@@ -173,7 +173,7 @@ public:
{7, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolumeAuto"},
{8, &IAudioDevice::GetAudioDeviceOutputVolume, "GetAudioDeviceOutputVolumeAuto"},
{10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"},
{11, nullptr, "QueryAudioDeviceInputEvent"},
{11, &IAudioDevice::QueryAudioDeviceInputEvent, "QueryAudioDeviceInputEvent"},
{12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"},
{13, nullptr, "GetAudioSystemMasterVolumeSetting"},
};
@@ -183,6 +183,10 @@ public:
buffer_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
"IAudioOutBufferReleasedEvent");
// Should be similar to audio_output_device_switch_event
audio_input_device_switch_event = Kernel::WritableEvent::CreateEventPair(
kernel, Kernel::ResetType::Automatic, "IAudioDevice:AudioInputDeviceSwitchedEvent");
// Should only be signalled when an audio output device has been changed, example: speaker
// to headset
audio_output_device_switch_event = Kernel::WritableEvent::CreateEventPair(
@@ -292,6 +296,15 @@ private:
rb.Push<u32>(1);
}
// Should be similar to QueryAudioDeviceOutputEvent
void QueryAudioDeviceInputEvent(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(audio_input_device_switch_event.readable);
}
void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
@@ -302,6 +315,7 @@ private:
u32_le revision = 0;
Kernel::EventPair buffer_event;
Kernel::EventPair audio_input_device_switch_event;
Kernel::EventPair audio_output_device_switch_event;
}; // namespace Audio
+220 -10
View File
@@ -2,32 +2,37 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/crypto/key_manager.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/service.h"
namespace Service::ES {
constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::ETicket, 2};
constexpr ResultCode ERROR_INVALID_RIGHTS_ID{ErrorModule::ETicket, 3};
class ETicket final : public ServiceFramework<ETicket> {
public:
explicit ETicket() : ServiceFramework{"es"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, nullptr, "ImportTicket"},
{1, &ETicket::ImportTicket, "ImportTicket"},
{2, nullptr, "ImportTicketCertificateSet"},
{3, nullptr, "DeleteTicket"},
{4, nullptr, "DeletePersonalizedTicket"},
{5, nullptr, "DeleteAllCommonTicket"},
{6, nullptr, "DeleteAllPersonalizedTicket"},
{7, nullptr, "DeleteAllPersonalizedTicketEx"},
{8, nullptr, "GetTitleKey"},
{9, nullptr, "CountCommonTicket"},
{10, nullptr, "CountPersonalizedTicket"},
{11, nullptr, "ListCommonTicket"},
{12, nullptr, "ListPersonalizedTicket"},
{8, &ETicket::GetTitleKey, "GetTitleKey"},
{9, &ETicket::CountCommonTicket, "CountCommonTicket"},
{10, &ETicket::CountPersonalizedTicket, "CountPersonalizedTicket"},
{11, &ETicket::ListCommonTicket, "ListCommonTicket"},
{12, &ETicket::ListPersonalizedTicket, "ListPersonalizedTicket"},
{13, nullptr, "ListMissingPersonalizedTicket"},
{14, nullptr, "GetCommonTicketSize"},
{15, nullptr, "GetPersonalizedTicketSize"},
{16, nullptr, "GetCommonTicketData"},
{17, nullptr, "GetPersonalizedTicketData"},
{14, &ETicket::GetCommonTicketSize, "GetCommonTicketSize"},
{15, &ETicket::GetPersonalizedTicketSize, "GetPersonalizedTicketSize"},
{16, &ETicket::GetCommonTicketData, "GetCommonTicketData"},
{17, &ETicket::GetPersonalizedTicketData, "GetPersonalizedTicketData"},
{18, nullptr, "OwnTicket"},
{19, nullptr, "GetTicketInfo"},
{20, nullptr, "ListLightTicketInfo"},
@@ -51,7 +56,212 @@ public:
};
// clang-format on
RegisterHandlers(functions);
keys.PopulateTickets();
keys.SynthesizeTickets();
}
private:
bool CheckRightsId(Kernel::HLERequestContext& ctx, const u128& rights_id) {
if (rights_id == u128{}) {
LOG_ERROR(Service_ETicket, "The rights ID was invalid!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_RIGHTS_ID);
return false;
}
return true;
}
void ImportTicket(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto ticket = ctx.ReadBuffer();
const auto cert = ctx.ReadBuffer(1);
if (ticket.size() < sizeof(Core::Crypto::Ticket)) {
LOG_ERROR(Service_ETicket, "The input buffer is not large enough!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_ARGUMENT);
return;
}
Core::Crypto::Ticket raw{};
std::memcpy(&raw, ticket.data(), sizeof(Core::Crypto::Ticket));
if (!keys.AddTicketPersonalized(raw)) {
LOG_ERROR(Service_ETicket, "The ticket could not be imported!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_ARGUMENT);
return;
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void GetTitleKey(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto rights_id = rp.PopRaw<u128>();
LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
if (!CheckRightsId(ctx, rights_id))
return;
const auto key =
keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]);
if (key == Core::Crypto::Key128{}) {
LOG_ERROR(Service_ETicket,
"The titlekey doesn't exist in the KeyManager or the rights ID was invalid!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_RIGHTS_ID);
return;
}
ctx.WriteBuffer(key.data(), key.size());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void CountCommonTicket(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ETicket, "called");
const auto count = keys.GetCommonTickets().size();
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(count);
}
void CountPersonalizedTicket(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ETicket, "called");
const auto count = keys.GetPersonalizedTickets().size();
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(count);
}
void ListCommonTicket(Kernel::HLERequestContext& ctx) {
u32 out_entries;
if (keys.GetCommonTickets().empty())
out_entries = 0;
else
out_entries = ctx.GetWriteBufferSize() / sizeof(u128);
LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries);
keys.PopulateTickets();
const auto tickets = keys.GetCommonTickets();
std::vector<u128> ids;
std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids),
[](const auto& pair) { return pair.first; });
out_entries = std::min<u32>(ids.size(), out_entries);
ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128));
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(out_entries);
}
void ListPersonalizedTicket(Kernel::HLERequestContext& ctx) {
u32 out_entries;
if (keys.GetPersonalizedTickets().empty())
out_entries = 0;
else
out_entries = ctx.GetWriteBufferSize() / sizeof(u128);
LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries);
keys.PopulateTickets();
const auto tickets = keys.GetPersonalizedTickets();
std::vector<u128> ids;
std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids),
[](const auto& pair) { return pair.first; });
out_entries = std::min<u32>(ids.size(), out_entries);
ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128));
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(out_entries);
}
void GetCommonTicketSize(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto rights_id = rp.PopRaw<u128>();
LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
if (!CheckRightsId(ctx, rights_id))
return;
const auto ticket = keys.GetCommonTickets().at(rights_id);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push<u64>(ticket.GetSize());
}
void GetPersonalizedTicketSize(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto rights_id = rp.PopRaw<u128>();
LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
if (!CheckRightsId(ctx, rights_id))
return;
const auto ticket = keys.GetPersonalizedTickets().at(rights_id);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push<u64>(ticket.GetSize());
}
void GetCommonTicketData(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto rights_id = rp.PopRaw<u128>();
LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
if (!CheckRightsId(ctx, rights_id))
return;
const auto ticket = keys.GetCommonTickets().at(rights_id);
const auto write_size = std::min<u64>(ticket.GetSize(), ctx.GetWriteBufferSize());
ctx.WriteBuffer(&ticket, write_size);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push<u64>(write_size);
}
void GetPersonalizedTicketData(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto rights_id = rp.PopRaw<u128>();
LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
if (!CheckRightsId(ctx, rights_id))
return;
const auto ticket = keys.GetPersonalizedTickets().at(rights_id);
const auto write_size = std::min<u64>(ticket.GetSize(), ctx.GetWriteBufferSize());
ctx.WriteBuffer(&ticket, write_size);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push<u64>(write_size);
}
Core::Crypto::KeyManager keys;
};
void InstallInterfaces(SM::ServiceManager& service_manager) {
@@ -42,9 +42,8 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) {
cond = instr.hsetp2.reg.cond;
h_and = instr.hsetp2.reg.h_and;
op_b =
UnpackHalfFloat(GetOperandAbsNegHalf(GetRegister(instr.gpr20), instr.hsetp2.reg.abs_b,
instr.hsetp2.reg.negate_b),
instr.hsetp2.reg.type_b);
GetOperandAbsNegHalf(UnpackHalfFloat(GetRegister(instr.gpr20), instr.hsetp2.reg.type_b),
instr.hsetp2.reg.abs_b, instr.hsetp2.reg.negate_b);
break;
default:
UNREACHABLE();
@@ -52,22 +51,22 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) {
}
const OperationCode combiner = GetPredicateCombiner(instr.hsetp2.op);
const Node combined_pred = GetPredicate(instr.hsetp2.pred3, instr.hsetp2.neg_pred);
const Node combined_pred = GetPredicate(instr.hsetp2.pred39, instr.hsetp2.neg_pred);
const auto Write = [&](u64 dest, Node src) {
SetPredicate(bb, dest, Operation(combiner, std::move(src), combined_pred));
};
const Node comparison = GetPredicateComparisonHalf(cond, op_a, op_b);
const u64 first = instr.hsetp2.pred0;
const u64 second = instr.hsetp2.pred39;
const u64 first = instr.hsetp2.pred3;
const u64 second = instr.hsetp2.pred0;
if (h_and) {
const Node joined = Operation(OperationCode::LogicalAnd2, comparison);
Node joined = Operation(OperationCode::LogicalAnd2, comparison);
Write(first, joined);
Write(second, Operation(OperationCode::LogicalNegate, joined));
Write(second, Operation(OperationCode::LogicalNegate, std::move(joined)));
} else {
Write(first, Operation(OperationCode::LogicalPick2, comparison, Immediate(0u)));
Write(second, Operation(OperationCode::LogicalPick2, comparison, Immediate(1u)));
Write(first, Operation(OperationCode::LogicalPick2, comparison, Immediate(0U)));
Write(second, Operation(OperationCode::LogicalPick2, comparison, Immediate(1U)));
}
return pc;
+24 -20
View File
@@ -68,12 +68,14 @@ void ConfigureDialog::RetranslateUI() {
ui->tabWidget->setCurrentIndex(old_index);
}
Q_DECLARE_METATYPE(QList<QWidget*>);
void ConfigureDialog::PopulateSelectionList() {
const std::array<std::pair<QString, QStringList>, 4> items{
{{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}},
{tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}},
{tr("Graphics"), {tr("Graphics")}},
{tr("Controls"), {tr("Input"), tr("Hotkeys")}}},
const std::array<std::pair<QString, QList<QWidget*>>, 4> items{
{{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->gameListTab}},
{tr("System"), {ui->systemTab, ui->profileManagerTab, ui->audioTab}},
{tr("Graphics"), {ui->graphicsTab}},
{tr("Controls"), {ui->inputTab, ui->hotkeysTab}}},
};
[[maybe_unused]] const QSignalBlocker blocker(ui->selectorList);
@@ -81,7 +83,7 @@ void ConfigureDialog::PopulateSelectionList() {
ui->selectorList->clear();
for (const auto& entry : items) {
auto* const item = new QListWidgetItem(entry.first);
item->setData(Qt::UserRole, entry.second);
item->setData(Qt::UserRole, QVariant::fromValue(entry.second));
ui->selectorList->addItem(item);
}
@@ -93,24 +95,26 @@ void ConfigureDialog::UpdateVisibleTabs() {
return;
}
const std::map<QString, QWidget*> widgets = {
{tr("General"), ui->generalTab},
{tr("System"), ui->systemTab},
{tr("Profiles"), ui->profileManagerTab},
{tr("Input"), ui->inputTab},
{tr("Hotkeys"), ui->hotkeysTab},
{tr("Graphics"), ui->graphicsTab},
{tr("Audio"), ui->audioTab},
{tr("Debug"), ui->debugTab},
{tr("Web"), ui->webTab},
{tr("Game List"), ui->gameListTab},
const std::map<QWidget*, QString> widgets = {
{ui->generalTab, tr("General")},
{ui->systemTab, tr("System")},
{ui->profileManagerTab, tr("Profiles")},
{ui->inputTab, tr("Input")},
{ui->hotkeysTab, tr("Hotkeys")},
{ui->graphicsTab, tr("Graphics")},
{ui->audioTab, tr("Audio")},
{ui->debugTab, tr("Debug")},
{ui->webTab, tr("Web")},
{ui->gameListTab, tr("Game List")},
};
[[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget);
ui->tabWidget->clear();
const QStringList tabs = items[0]->data(Qt::UserRole).toStringList();
for (const auto& tab : tabs) {
ui->tabWidget->addTab(widgets.find(tab)->second, tab);
const QList<QWidget*> tabs = qvariant_cast<QList<QWidget*>>(items[0]->data(Qt::UserRole));
for (const auto tab : tabs) {
ui->tabWidget->addTab(tab, widgets.at(tab));
}
}
@@ -19,6 +19,8 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
}
SetConfiguration();
connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled);
}
ConfigureGeneral::~ConfigureGeneral() = default;
@@ -27,6 +29,10 @@ void ConfigureGeneral::SetConfiguration() {
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot);
ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked());
ui->frame_limit->setValue(Settings::values.frame_limit);
}
void ConfigureGeneral::ApplyConfiguration() {
@@ -34,6 +40,9 @@ void ConfigureGeneral::ApplyConfiguration() {
UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();
UISettings::values.theme =
ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked();
Settings::values.frame_limit = ui->frame_limit->value();
}
void ConfigureGeneral::changeEvent(QEvent* event) {
@@ -24,6 +24,33 @@
<layout class="QHBoxLayout" name="GeneralHorizontalLayout">
<item>
<layout class="QVBoxLayout" name="GeneralVerticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QCheckBox" name="toggle_frame_limit">
<property name="text">
<string>Limit Speed Percent</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="frame_limit">
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="toggle_check_exit">
<property name="text">
@@ -55,7 +55,6 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
SetConfiguration();
connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled);
connect(ui->bg_button, &QPushButton::clicked, this, [this] {
const QColor new_bg_color = QColorDialog::getColor(bg_color);
if (!new_bg_color.isValid()) {
@@ -72,9 +71,6 @@ void ConfigureGraphics::SetConfiguration() {
ui->resolution_factor_combobox->setCurrentIndex(
static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor)));
ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked());
ui->frame_limit->setValue(Settings::values.frame_limit);
ui->use_disk_shader_cache->setEnabled(runtime_lock);
ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache);
ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation);
@@ -89,8 +85,6 @@ void ConfigureGraphics::SetConfiguration() {
void ConfigureGraphics::ApplyConfiguration() {
Settings::values.resolution_factor =
ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex()));
Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked();
Settings::values.frame_limit = ui->frame_limit->value();
Settings::values.use_disk_shader_cache = ui->use_disk_shader_cache->isChecked();
Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked();
Settings::values.use_asynchronous_gpu_emulation =
@@ -22,33 +22,6 @@
<string>Graphics</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QCheckBox" name="toggle_frame_limit">
<property name="text">
<string>Limit Speed Percent</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="frame_limit">
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="use_disk_shader_cache">
<property name="text">
@@ -301,13 +301,16 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
});
}
connect(analog_map_stick[analog_id], &QPushButton::clicked, [=] {
QMessageBox::information(this, tr("Information"),
tr("After pressing OK, first move your joystick horizontally, "
"and then vertically."));
HandleClick(
analog_map_stick[analog_id],
[=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; },
InputCommon::Polling::DeviceType::Analog);
if (QMessageBox::information(
this, tr("Information"),
tr("After pressing OK, first move your joystick horizontally, "
"and then vertically."),
QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
HandleClick(
analog_map_stick[analog_id],
[=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; },
InputCommon::Polling::DeviceType::Analog);
}
});
}
+11
View File
@@ -6,6 +6,9 @@
#include <clocale>
#include <memory>
#include <thread>
#ifdef __APPLE__
#include <unistd.h> // for chdir
#endif
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
#include "applets/error.h"
@@ -2226,6 +2229,14 @@ int main(int argc, char* argv[]) {
QCoreApplication::setOrganizationName(QStringLiteral("yuzu team"));
QCoreApplication::setApplicationName(QStringLiteral("yuzu"));
#ifdef __APPLE__
// If you start a bundle (binary) on OSX without the Terminal, the working directory is "/".
// But since we require the working directory to be the executable path for the location of the
// user folder in the Qt Frontend, we need to cd into that working directory
const std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
chdir(bin_path.c_str());
#endif
// Enables the core to make the qt created contexts current on std::threads
QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
QApplication app(argc, argv);