diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc index 591762861..bd8156e59 100644 --- a/src/xenia/kernel/xam/user_profile.cc +++ b/src/xenia/kernel/xam/user_profile.cc @@ -14,11 +14,12 @@ #include -#include "xenia/kernel/xam/user_profile.h" #include "xenia/base/clock.h" #include "xenia/base/filesystem.h" #include "xenia/base/logging.h" #include "xenia/base/mapped_memory.h" +#include "xenia/kernel/util/crypto_utils.h" +#include "xenia/kernel/xam/user_profile.h" namespace xe { namespace kernel { @@ -29,9 +30,83 @@ DEFINE_string(profile_directory, "Content\\Profile\\", constexpr uint32_t kDashboardID = 0xFFFE07D1; +std::string X_XAMACCOUNTINFO::GetGamertagString() const { + return xe::to_string(std::wstring(gamertag)); +} + +bool UserProfile::DecryptAccountFile(const uint8_t* data, + X_XAMACCOUNTINFO* output, bool devkit) { + const uint8_t* key = util::GetXeKey(0x19, devkit); + if (!key) { + return false; // this shouldn't happen... + } + + // Generate RC4 key from data hash + uint8_t rc4_key[0x14]; + util::HmacSha(key, 0x10, data, 0x10, 0, 0, 0, 0, rc4_key, 0x14); + + uint8_t dec_data[sizeof(X_XAMACCOUNTINFO) + 8]; + + // Decrypt data + util::RC4(rc4_key, 0x10, data + 0x10, sizeof(dec_data), dec_data, + sizeof(dec_data)); + + // Verify decrypted data against hash + uint8_t data_hash[0x14]; + util::HmacSha(key, 0x10, dec_data, sizeof(dec_data), 0, 0, 0, 0, data_hash, + 0x14); + + if (std::memcmp(data, data_hash, 0x10) == 0) { + // Copy account data to output + std::memcpy(output, dec_data + 8, sizeof(X_XAMACCOUNTINFO)); + + // Swap gamertag endian + xe::copy_and_swap(output->gamertag, output->gamertag, 0x10); + return true; + } + + return false; +} + +void UserProfile::EncryptAccountFile(const X_XAMACCOUNTINFO* input, + uint8_t* output, bool devkit) { + const uint8_t* key = util::GetXeKey(0x19, devkit); + if (!key) { + return; // this shouldn't happen... + } + + X_XAMACCOUNTINFO* output_acct = (X_XAMACCOUNTINFO*)(output + 0x18); + std::memcpy(output_acct, input, sizeof(X_XAMACCOUNTINFO)); + + // Swap gamertag endian + xe::copy_and_swap(output_acct->gamertag, output_acct->gamertag, + 0x10); + + // Set confounder, should be random but meh + std::memset(output + 0x10, 0xFD, 8); + + // Encrypted data = xam account info + 8 byte confounder + uint32_t enc_data_size = sizeof(X_XAMACCOUNTINFO) + 8; + + // Set data hash + uint8_t data_hash[0x14]; + util::HmacSha(key, 0x10, output + 0x10, enc_data_size, 0, 0, 0, 0, data_hash, + 0x14); + + std::memcpy(output, data_hash, 0x10); + + // Generate RC4 key from data hash + uint8_t rc4_key[0x14]; + util::HmacSha(key, 0x10, data_hash, 0x10, 0, 0, 0, 0, rc4_key, 0x14); + + // Encrypt data + util::RC4(rc4_key, 0x10, output + 0x10, enc_data_size, output + 0x10, + enc_data_size); +} + UserProfile::UserProfile() : dash_gpd_(kDashboardID) { - xuid_ = 0xBABEBABEBABEBABE; - name_ = "User"; + account_.xuid_online = 0xBABEBABEBABEBABE; + wcscpy_s(account_.gamertag, L"XeniaUser"); // https://cs.rin.ru/forum/viewtopic.php?f=38&t=60668&hilit=gfwl+live&start=195 // https://github.com/arkem/py360/blob/master/py360/constants.py @@ -99,13 +174,36 @@ UserProfile::UserProfile() : dash_gpd_(kDashboardID) { AddSetting(std::make_unique(0x63E83FFD)); // Try loading profile GPD files... - LoadGpdFiles(); + LoadProfile(); } -void UserProfile::LoadGpdFiles() { +void UserProfile::LoadProfile() { + auto mmap_ = + MappedMemory::Open(xe::to_wstring(FLAGS_profile_directory) + L"Account", + MappedMemory::Mode::kRead); + if (mmap_) { + XELOGI("Loading Account file from path %sAccount", + FLAGS_profile_directory.c_str()); + + X_XAMACCOUNTINFO tmp_acct; + bool success = DecryptAccountFile(mmap_->data(), &tmp_acct); + if (!success) { + success = DecryptAccountFile(mmap_->data(), &tmp_acct, true); + } + + if (!success) { + XELOGW("Failed to decrypt Account file data"); + } else { + std::memcpy(&account_, &tmp_acct, sizeof(X_XAMACCOUNTINFO)); + XELOGI("Loaded Account \"%s\" successfully!", name().c_str()); + } + + mmap_->Close(); + } + XELOGI("Loading profile GPDs from path %s", FLAGS_profile_directory.c_str()); - auto mmap_ = MappedMemory::Open( + mmap_ = MappedMemory::Open( xe::to_wstring(FLAGS_profile_directory) + L"FFFE07D1.gpd", MappedMemory::Mode::kRead); if (!mmap_) { diff --git a/src/xenia/kernel/xam/user_profile.h b/src/xenia/kernel/xam/user_profile.h index 2b0ce4c18..76ad5f696 100644 --- a/src/xenia/kernel/xam/user_profile.h +++ b/src/xenia/kernel/xam/user_profile.h @@ -22,6 +22,111 @@ namespace xe { namespace kernel { namespace xam { +// from https://github.com/xemio/testdev/blob/master/xkelib/xam/_xamext.h +#pragma pack(push, 4) +struct X_XAMACCOUNTINFO { + enum AccountReservedFlags { + kPasswordProtected = 0x10000000, + kLiveEnabled = 0x20000000, + kRecovering = 0x40000000, + kVersionMask = 0x000000FF + }; + + enum AccountUserFlags { + kPaymentInstrumentCreditCard = 1, + + kCountryMask = 0xFF00, + kSubscriptionTierMask = 0xF00000, + kLanguageMask = 0x3E000000, + + kParentalControlEnabled = 0x1000000, + }; + + enum AccountSubscriptionTier { + kSubscriptionTierSilver = 3, + kSubscriptionTierGold = 6, + kSubscriptionTierFamilyGold = 9 + }; + + // already exists inside xdbf.h?? + enum AccountLanguage { + kNoLanguage, + kEnglish, + kJapanese, + kGerman, + kFrench, + kSpanish, + kItalian, + kKorean, + kTChinese, + kPortuguese, + kSChinese, + kPolish, + kRussian, + kNorwegian = 15 + }; + + enum AccountLiveFlags { kAcctRequiresManagement = 1 }; + + xe::be reserved_flags; + xe::be live_flags; + wchar_t gamertag[0x10]; + xe::be xuid_online; // 09.... + xe::be cached_user_flags; + xe::be network_id; + char passcode[4]; + char online_domain[0x14]; + char online_kerberos_realm[0x18]; + char online_key[0x10]; + char passport_membername[0x72]; + char passport_password[0x20]; + char owner_passport_membername[0x72]; + + bool IsPasscodeEnabled() { + return (bool)(reserved_flags & AccountReservedFlags::kPasswordProtected); + } + + bool IsLiveEnabled() { + return (bool)(reserved_flags & AccountReservedFlags::kLiveEnabled); + } + + bool IsRecovering() { + return (bool)(reserved_flags & AccountReservedFlags::kRecovering); + } + + bool IsPaymentInstrumentCreditCard() { + return (bool)(cached_user_flags & + AccountUserFlags::kPaymentInstrumentCreditCard); + } + + bool IsParentalControlled() { + return (bool)(cached_user_flags & + AccountUserFlags::kParentalControlEnabled); + } + + bool IsXUIDOffline() { return ((xuid_online >> 60) & 0xF) == 0xE; } + bool IsXUIDOnline() { return ((xuid_online >> 48) & 0xFFFF) == 0x9; } + bool IsXUIDValid() { return IsXUIDOffline() != IsXUIDOnline(); } + bool IsTeamXUID() { + return (xuid_online & 0xFF00000000000140) == 0xFE00000000000100; + } + + uint32_t GetCountry() { return (cached_user_flags & kCountryMask) >> 8; } + + AccountSubscriptionTier GetSubscriptionTier() { + return (AccountSubscriptionTier)( + (cached_user_flags & kSubscriptionTierMask) >> 20); + } + + AccountLanguage GetLanguage() { + return (AccountLanguage)((cached_user_flags & kLanguageMask) >> 25); + } + + std::string GetGamertagString() const; +}; +static_assert_size(X_XAMACCOUNTINFO, 0x17C); +#pragma pack(pop) + class UserProfile { public: struct Setting { @@ -198,10 +303,16 @@ class UserProfile { } }; + static bool DecryptAccountFile(const uint8_t* data, X_XAMACCOUNTINFO* output, + bool devkit = false); + + static void EncryptAccountFile(const X_XAMACCOUNTINFO* input, uint8_t* output, + bool devkit = false); + UserProfile(); - uint64_t xuid() const { return xuid_; } - std::string name() const { return name_; } + uint64_t xuid() const { return account_.xuid_online; } + std::string name() const { return account_.GetGamertagString(); } uint32_t signin_state() const { return 1; } void AddSetting(std::unique_ptr setting); @@ -216,11 +327,10 @@ class UserProfile { bool UpdateAllGpds(); private: - void LoadGpdFiles(); + void LoadProfile(); bool UpdateGpd(uint32_t title_id, xdbf::GpdFile& gpd_data); - uint64_t xuid_; - std::string name_; + X_XAMACCOUNTINFO account_; std::vector> setting_list_; std::unordered_map settings_; diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index 83d0171a3..34e64db8d 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -21,26 +21,6 @@ namespace xe { namespace kernel { namespace xam { -// from https://github.com/xemio/testdev/blob/master/xkelib/xam/_xamext.h -#pragma pack(push, 4) -struct X_XAMACCOUNTINFO { - xe::be reserved; - xe::be live_flags; - wchar_t gamertag[0x10]; - xe::be xuid_online; // 09.... - xe::be user_flags; - xe::be network_id; - char passcode[4]; - char online_domain[0x14]; - char online_kerberos_realm[0x18]; - char online_key[0x10]; - char passport_membername[0x72]; - char passport_password[0x20]; - char owner_passport_membername[0x72]; -}; -static_assert_size(X_XAMACCOUNTINFO, 0x17C); -#pragma pack(pop) - struct X_PROFILEENUMRESULT { xe::be xuid_offline; // E0..... X_XAMACCOUNTINFO account;