[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 <gflags/gflags.h>
|
||||||
|
|
||||||
#include "xenia/kernel/xam/user_profile.h"
|
|
||||||
#include "xenia/base/clock.h"
|
#include "xenia/base/clock.h"
|
||||||
#include "xenia/base/filesystem.h"
|
#include "xenia/base/filesystem.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/mapped_memory.h"
|
#include "xenia/base/mapped_memory.h"
|
||||||
|
#include "xenia/kernel/util/crypto_utils.h"
|
||||||
|
#include "xenia/kernel/xam/user_profile.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace kernel {
|
namespace kernel {
|
||||||
|
@ -29,9 +30,83 @@ DEFINE_string(profile_directory, "Content\\Profile\\",
|
||||||
|
|
||||||
constexpr uint32_t kDashboardID = 0xFFFE07D1;
|
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) {
|
UserProfile::UserProfile() : dash_gpd_(kDashboardID) {
|
||||||
xuid_ = 0xBABEBABEBABEBABE;
|
account_.xuid_online = 0xBABEBABEBABEBABE;
|
||||||
name_ = "User";
|
wcscpy_s(account_.gamertag, L"XeniaUser");
|
||||||
|
|
||||||
// https://cs.rin.ru/forum/viewtopic.php?f=38&t=60668&hilit=gfwl+live&start=195
|
// 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
|
// 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));
|
AddSetting(std::make_unique<BinarySetting>(0x63E83FFD));
|
||||||
|
|
||||||
// Try loading profile GPD files...
|
// 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());
|
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",
|
xe::to_wstring(FLAGS_profile_directory) + L"FFFE07D1.gpd",
|
||||||
MappedMemory::Mode::kRead);
|
MappedMemory::Mode::kRead);
|
||||||
if (!mmap_) {
|
if (!mmap_) {
|
||||||
|
|
|
@ -22,6 +22,111 @@ namespace xe {
|
||||||
namespace kernel {
|
namespace kernel {
|
||||||
namespace xam {
|
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 {
|
class UserProfile {
|
||||||
public:
|
public:
|
||||||
struct Setting {
|
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();
|
UserProfile();
|
||||||
|
|
||||||
uint64_t xuid() const { return xuid_; }
|
uint64_t xuid() const { return account_.xuid_online; }
|
||||||
std::string name() const { return name_; }
|
std::string name() const { return account_.GetGamertagString(); }
|
||||||
uint32_t signin_state() const { return 1; }
|
uint32_t signin_state() const { return 1; }
|
||||||
|
|
||||||
void AddSetting(std::unique_ptr<Setting> setting);
|
void AddSetting(std::unique_ptr<Setting> setting);
|
||||||
|
@ -216,11 +327,10 @@ class UserProfile {
|
||||||
bool UpdateAllGpds();
|
bool UpdateAllGpds();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void LoadGpdFiles();
|
void LoadProfile();
|
||||||
bool UpdateGpd(uint32_t title_id, xdbf::GpdFile& gpd_data);
|
bool UpdateGpd(uint32_t title_id, xdbf::GpdFile& gpd_data);
|
||||||
|
|
||||||
uint64_t xuid_;
|
X_XAMACCOUNTINFO account_;
|
||||||
std::string name_;
|
|
||||||
std::vector<std::unique_ptr<Setting>> setting_list_;
|
std::vector<std::unique_ptr<Setting>> setting_list_;
|
||||||
std::unordered_map<uint32_t, Setting*> settings_;
|
std::unordered_map<uint32_t, Setting*> settings_;
|
||||||
|
|
||||||
|
|
|
@ -21,26 +21,6 @@ namespace xe {
|
||||||
namespace kernel {
|
namespace kernel {
|
||||||
namespace xam {
|
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 {
|
struct X_PROFILEENUMRESULT {
|
||||||
xe::be<uint64_t> xuid_offline; // E0.....
|
xe::be<uint64_t> xuid_offline; // E0.....
|
||||||
X_XAMACCOUNTINFO account;
|
X_XAMACCOUNTINFO account;
|
||||||
|
|
Loading…
Reference in New Issue