[Kernel] Allow loading info from "Account" file (gamertag etc)

This should let Xenia load in Account files from an actual 360, one step closer to fully supporting 360 profiles!

Only decrypts/reads atm, but the code is there for re-encrypting, maybe once new UI is ready we can make use of that for creating new 360-compatible profiles?
This commit is contained in:
emoose 2018-12-04 04:10:33 +00:00
parent 981eff5902
commit ad47cd7022
No known key found for this signature in database
GPG Key ID: 3735C67912F5FF97
3 changed files with 219 additions and 31 deletions

View File

@ -14,11 +14,12 @@
#include <gflags/gflags.h>
#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<wchar_t>(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<wchar_t>(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<BinarySetting>(0x63E83FFD));
// Try loading profile GPD files...
LoadGpdFiles();
LoadProfile();
}
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();
}
void UserProfile::LoadGpdFiles() {
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_) {

View File

@ -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<uint32_t> reserved_flags;
xe::be<uint32_t> live_flags;
wchar_t gamertag[0x10];
xe::be<uint64_t> xuid_online; // 09....
xe::be<uint32_t> cached_user_flags;
xe::be<uint32_t> 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> 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<std::unique_ptr<Setting>> setting_list_;
std::unordered_map<uint32_t, Setting*> settings_;

View File

@ -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<uint32_t> reserved;
xe::be<uint32_t> live_flags;
wchar_t gamertag[0x10];
xe::be<uint64_t> xuid_online; // 09....
xe::be<uint32_t> user_flags;
xe::be<uint32_t> 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<uint64_t> xuid_offline; // E0.....
X_XAMACCOUNTINFO account;