[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:
parent
981eff5902
commit
ad47cd7022
|
@ -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_) {
|
||||
|
|
|
@ -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_;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue