Merge branch 'profile-gpds-rebase' of https://github.com/emoose/xenia into canary
Merge Emoose profiles-gpds https://github.com/emoose/xenia/tree/profile-gpds-rebase
This commit is contained in:
commit
127b465ccc
|
@ -33,8 +33,8 @@
|
||||||
#include "xenia/kernel/kernel_state.h"
|
#include "xenia/kernel/kernel_state.h"
|
||||||
#include "xenia/kernel/user_module.h"
|
#include "xenia/kernel/user_module.h"
|
||||||
#include "xenia/kernel/util/gameinfo_utils.h"
|
#include "xenia/kernel/util/gameinfo_utils.h"
|
||||||
#include "xenia/kernel/util/xdbf_utils.h"
|
|
||||||
#include "xenia/kernel/xam/xam_module.h"
|
#include "xenia/kernel/xam/xam_module.h"
|
||||||
|
#include "xenia/kernel/xam/xdbf/xdbf.h"
|
||||||
#include "xenia/kernel/xbdm/xbdm_module.h"
|
#include "xenia/kernel/xbdm/xbdm_module.h"
|
||||||
#include "xenia/kernel/xboxkrnl/xboxkrnl_module.h"
|
#include "xenia/kernel/xboxkrnl/xboxkrnl_module.h"
|
||||||
#include "xenia/memory.h"
|
#include "xenia/memory.h"
|
||||||
|
@ -670,13 +670,16 @@ X_STATUS Emulator::CompleteLaunch(const std::wstring& path,
|
||||||
uint32_t resource_size = 0;
|
uint32_t resource_size = 0;
|
||||||
if (XSUCCEEDED(
|
if (XSUCCEEDED(
|
||||||
module->GetSection(title_id, &resource_data, &resource_size))) {
|
module->GetSection(title_id, &resource_data, &resource_size))) {
|
||||||
kernel::util::XdbfGameData db(
|
kernel::xam::xdbf::SpaFile spa;
|
||||||
module->memory()->TranslateVirtual(resource_data), resource_size);
|
if (spa.Read(module->memory()->TranslateVirtual(resource_data),
|
||||||
if (db.is_valid()) {
|
resource_size)) {
|
||||||
game_title_ = xe::to_wstring(db.title());
|
// Set title SPA and get title name/icon
|
||||||
auto icon_block = db.icon();
|
kernel_state_->user_profile()->SetTitleSpaData(spa);
|
||||||
|
game_title_ = xe::to_wstring(spa.GetTitleName());
|
||||||
|
auto icon_block = spa.GetIcon();
|
||||||
if (icon_block) {
|
if (icon_block) {
|
||||||
display_window_->SetIcon(icon_block.buffer, icon_block.size);
|
display_window_->SetIcon(icon_block->data.data(),
|
||||||
|
icon_block->data.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "xenia/kernel/util/crypto_utils.h"
|
||||||
|
#include "xenia/xbox.h"
|
||||||
|
|
||||||
|
#include "third_party/crypto/TinySHA1.hpp"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace kernel {
|
||||||
|
namespace util {
|
||||||
|
|
||||||
|
uint8_t xekey_0x19[] = {0xE1, 0xBC, 0x15, 0x9C, 0x73, 0xB1, 0xEA, 0xE9,
|
||||||
|
0xAB, 0x31, 0x70, 0xF3, 0xAD, 0x47, 0xEB, 0xF3};
|
||||||
|
|
||||||
|
uint8_t xekey_0x19_devkit[] = {0xDA, 0xB6, 0x9A, 0xD9, 0x8E, 0x28, 0x76, 0x4F,
|
||||||
|
0x97, 0x7E, 0xE2, 0x48, 0x7E, 0x4F, 0x3F, 0x68};
|
||||||
|
|
||||||
|
const uint8_t* GetXeKey(uint32_t idx, bool devkit) {
|
||||||
|
if (idx != 0x19) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return devkit ? xekey_0x19_devkit : xekey_0x19;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HmacSha(const uint8_t* key, uint32_t key_size_in, const uint8_t* inp_1,
|
||||||
|
uint32_t inp_1_size, const uint8_t* inp_2, uint32_t inp_2_size,
|
||||||
|
const uint8_t* inp_3, uint32_t inp_3_size, uint8_t* out,
|
||||||
|
uint32_t out_size) {
|
||||||
|
uint32_t key_size = key_size_in;
|
||||||
|
sha1::SHA1 sha;
|
||||||
|
uint8_t kpad_i[0x40];
|
||||||
|
uint8_t kpad_o[0x40];
|
||||||
|
uint8_t tmp_key[0x40];
|
||||||
|
std::memset(kpad_i, 0x36, 0x40);
|
||||||
|
std::memset(kpad_o, 0x5C, 0x40);
|
||||||
|
|
||||||
|
// Setup HMAC key
|
||||||
|
// If > block size, use its hash
|
||||||
|
if (key_size > 0x40) {
|
||||||
|
sha1::SHA1 sha_key;
|
||||||
|
sha_key.processBytes(key, key_size);
|
||||||
|
sha_key.finalize((uint8_t*)tmp_key);
|
||||||
|
|
||||||
|
key_size = 0x14u;
|
||||||
|
} else {
|
||||||
|
std::memcpy(tmp_key, key, key_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < key_size; i++) {
|
||||||
|
kpad_i[i] = tmp_key[i] ^ 0x36;
|
||||||
|
kpad_o[i] = tmp_key[i] ^ 0x5C;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inner
|
||||||
|
sha.processBytes(kpad_i, 0x40);
|
||||||
|
|
||||||
|
if (inp_1_size) {
|
||||||
|
sha.processBytes(inp_1, inp_1_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inp_2_size) {
|
||||||
|
sha.processBytes(inp_2, inp_2_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inp_3_size) {
|
||||||
|
sha.processBytes(inp_3, inp_3_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t digest[0x14];
|
||||||
|
sha.finalize(digest);
|
||||||
|
sha.reset();
|
||||||
|
|
||||||
|
// Outer
|
||||||
|
sha.processBytes(kpad_o, 0x40);
|
||||||
|
sha.processBytes(digest, 0x14);
|
||||||
|
sha.finalize(digest);
|
||||||
|
|
||||||
|
std::memcpy(out, digest, std::min((uint32_t)out_size, 0x14u));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RC4(const uint8_t* key, uint32_t key_size_in, const uint8_t* data,
|
||||||
|
uint32_t data_size, uint8_t* out, uint32_t out_size) {
|
||||||
|
uint8_t tmp_key[0x10];
|
||||||
|
uint32_t sbox_size;
|
||||||
|
uint8_t sbox[0x100];
|
||||||
|
uint32_t i;
|
||||||
|
uint32_t j;
|
||||||
|
|
||||||
|
// Setup RC4 session...
|
||||||
|
std::memcpy(tmp_key, key, 0x10);
|
||||||
|
i = j = 0;
|
||||||
|
sbox_size = 0x100;
|
||||||
|
for (uint32_t x = 0; x < sbox_size; x++) {
|
||||||
|
sbox[x] = (uint8_t)x;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t idx = 0;
|
||||||
|
for (uint32_t x = 0; x < sbox_size; x++) {
|
||||||
|
idx = (idx + sbox[x] + key[x % 0x10]) % sbox_size;
|
||||||
|
uint8_t temp = sbox[idx];
|
||||||
|
sbox[idx] = sbox[x];
|
||||||
|
sbox[x] = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crypt data
|
||||||
|
for (uint32_t idx = 0; idx < data_size; idx++) {
|
||||||
|
i = (i + 1) % sbox_size;
|
||||||
|
j = (j + sbox[i]) % sbox_size;
|
||||||
|
uint8_t temp = sbox[i];
|
||||||
|
sbox[i] = sbox[j];
|
||||||
|
sbox[j] = temp;
|
||||||
|
|
||||||
|
uint8_t a = data[idx];
|
||||||
|
uint8_t b = sbox[(sbox[i] + sbox[j]) % sbox_size];
|
||||||
|
out[idx] = (uint8_t)(a ^ b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace util
|
||||||
|
} // namespace kernel
|
||||||
|
} // namespace xe
|
|
@ -0,0 +1,28 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "xenia/xbox.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace kernel {
|
||||||
|
namespace util {
|
||||||
|
|
||||||
|
const uint8_t* GetXeKey(uint32_t idx, bool devkit = false);
|
||||||
|
|
||||||
|
void HmacSha(const uint8_t* key, uint32_t key_size_in, const uint8_t* inp_1,
|
||||||
|
uint32_t inp_1_size, const uint8_t* inp_2, uint32_t inp_2_size,
|
||||||
|
const uint8_t* inp_3, uint32_t inp_3_size, uint8_t* out,
|
||||||
|
uint32_t out_size);
|
||||||
|
|
||||||
|
void RC4(const uint8_t* key, uint32_t key_size_in, const uint8_t* data,
|
||||||
|
uint32_t data_size, uint8_t* out, uint32_t out_size);
|
||||||
|
|
||||||
|
} // namespace util
|
||||||
|
} // namespace kernel
|
||||||
|
} // namespace xe
|
|
@ -1,107 +0,0 @@
|
||||||
/**
|
|
||||||
******************************************************************************
|
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
|
||||||
******************************************************************************
|
|
||||||
* Copyright 2016 Ben Vanik. All rights reserved. *
|
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
||||||
******************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "xenia/kernel/util/xdbf_utils.h"
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
namespace kernel {
|
|
||||||
namespace util {
|
|
||||||
|
|
||||||
constexpr uint32_t kXdbfMagicXdbf = 'XDBF';
|
|
||||||
constexpr uint32_t kXdbfMagicXstc = 'XSTC';
|
|
||||||
constexpr uint32_t kXdbfMagicXstr = 'XSTR';
|
|
||||||
|
|
||||||
XdbfWrapper::XdbfWrapper(const uint8_t* data, size_t data_size)
|
|
||||||
: data_(data), data_size_(data_size) {
|
|
||||||
if (!data || data_size <= sizeof(XbdfHeader)) {
|
|
||||||
data_ = nullptr;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint8_t* ptr = data_;
|
|
||||||
|
|
||||||
header_ = reinterpret_cast<const XbdfHeader*>(ptr);
|
|
||||||
ptr += sizeof(XbdfHeader);
|
|
||||||
if (header_->magic != kXdbfMagicXdbf) {
|
|
||||||
data_ = nullptr;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
entries_ = reinterpret_cast<const XbdfEntry*>(ptr);
|
|
||||||
ptr += sizeof(XbdfEntry) * header_->entry_count;
|
|
||||||
|
|
||||||
files_ = reinterpret_cast<const XbdfFileLoc*>(ptr);
|
|
||||||
ptr += sizeof(XbdfFileLoc) * header_->free_count;
|
|
||||||
|
|
||||||
content_offset_ = ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
XdbfBlock XdbfWrapper::GetEntry(XdbfSection section, uint64_t id) const {
|
|
||||||
for (uint32_t i = 0; i < header_->entry_used; ++i) {
|
|
||||||
auto& entry = entries_[i];
|
|
||||||
if (entry.section == static_cast<uint16_t>(section) && entry.id == id) {
|
|
||||||
XdbfBlock block;
|
|
||||||
block.buffer = content_offset_ + entry.offset;
|
|
||||||
block.size = entry.size;
|
|
||||||
return block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {0};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string XdbfWrapper::GetStringTableEntry(XdbfLocale locale,
|
|
||||||
uint16_t string_id) const {
|
|
||||||
auto language_block =
|
|
||||||
GetEntry(XdbfSection::kStringTable, static_cast<uint64_t>(locale));
|
|
||||||
if (!language_block) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
auto xstr_head =
|
|
||||||
reinterpret_cast<const XdbfXstrHeader*>(language_block.buffer);
|
|
||||||
assert_true(xstr_head->magic == kXdbfMagicXstr);
|
|
||||||
assert_true(xstr_head->version == 1);
|
|
||||||
|
|
||||||
const uint8_t* ptr = language_block.buffer + sizeof(XdbfXstrHeader);
|
|
||||||
for (uint16_t i = 0; i < xstr_head->string_count; ++i) {
|
|
||||||
auto entry = reinterpret_cast<const XdbfStringTableEntry*>(ptr);
|
|
||||||
ptr += sizeof(XdbfStringTableEntry);
|
|
||||||
if (entry->id == string_id) {
|
|
||||||
return std::string(reinterpret_cast<const char*>(ptr),
|
|
||||||
entry->string_length);
|
|
||||||
}
|
|
||||||
ptr += entry->string_length;
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr uint64_t kXdbfIdTitle = 0x8000;
|
|
||||||
constexpr uint64_t kXdbfIdXstc = 0x58535443;
|
|
||||||
|
|
||||||
XdbfBlock XdbfGameData::icon() const {
|
|
||||||
return GetEntry(XdbfSection::kImage, kXdbfIdTitle);
|
|
||||||
}
|
|
||||||
|
|
||||||
XdbfLocale XdbfGameData::default_language() const {
|
|
||||||
auto block = GetEntry(XdbfSection::kMetadata, kXdbfIdXstc);
|
|
||||||
if (!block.buffer) {
|
|
||||||
return XdbfLocale::kEnglish;
|
|
||||||
}
|
|
||||||
auto xstc = reinterpret_cast<const XdbfXstc*>(block.buffer);
|
|
||||||
assert_true(xstc->magic == kXdbfMagicXstc);
|
|
||||||
return static_cast<XdbfLocale>(static_cast<uint32_t>(xstc->default_language));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string XdbfGameData::title() const {
|
|
||||||
return GetStringTableEntry(default_language(), kXdbfIdTitle);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace util
|
|
||||||
} // namespace kernel
|
|
||||||
} // namespace xe
|
|
|
@ -1,146 +0,0 @@
|
||||||
/**
|
|
||||||
******************************************************************************
|
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
|
||||||
******************************************************************************
|
|
||||||
* Copyright 2016 Ben Vanik. All rights reserved. *
|
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
||||||
******************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef XENIA_KERNEL_UTIL_XDBF_UTILS_H_
|
|
||||||
#define XENIA_KERNEL_UTIL_XDBF_UTILS_H_
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "xenia/base/memory.h"
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
namespace kernel {
|
|
||||||
namespace util {
|
|
||||||
|
|
||||||
// https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/XEX/SPA.h
|
|
||||||
// https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/XEX/SPA.cpp
|
|
||||||
|
|
||||||
enum class XdbfSection : uint16_t {
|
|
||||||
kMetadata = 0x0001,
|
|
||||||
kImage = 0x0002,
|
|
||||||
kStringTable = 0x0003,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Found by dumping the kSectionStringTable sections of various games:
|
|
||||||
enum class XdbfLocale : uint32_t {
|
|
||||||
kUnknown = 0,
|
|
||||||
kEnglish = 1,
|
|
||||||
kJapanese = 2,
|
|
||||||
kGerman = 3,
|
|
||||||
kFrench = 4,
|
|
||||||
kSpanish = 5,
|
|
||||||
kItalian = 6,
|
|
||||||
kKorean = 7,
|
|
||||||
kChinese = 8,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct XdbfBlock {
|
|
||||||
const uint8_t* buffer;
|
|
||||||
size_t size;
|
|
||||||
|
|
||||||
operator bool() const { return buffer != nullptr; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Wraps an XBDF (XboxDataBaseFormat) in-memory database.
|
|
||||||
// https://free60project.github.io/wiki/XDBF.html
|
|
||||||
class XdbfWrapper {
|
|
||||||
public:
|
|
||||||
XdbfWrapper(const uint8_t* data, size_t data_size);
|
|
||||||
|
|
||||||
// True if the target memory contains a valid XDBF instance.
|
|
||||||
bool is_valid() const { return data_ != nullptr; }
|
|
||||||
|
|
||||||
// Gets an entry in the given section.
|
|
||||||
// If the entry is not found the returned block will be nullptr.
|
|
||||||
XdbfBlock GetEntry(XdbfSection section, uint64_t id) const;
|
|
||||||
|
|
||||||
// Gets a string from the string table in the given language.
|
|
||||||
// Returns the empty string if the entry is not found.
|
|
||||||
std::string GetStringTableEntry(XdbfLocale locale, uint16_t string_id) const;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
#pragma pack(push, 1)
|
|
||||||
struct XbdfHeader {
|
|
||||||
xe::be<uint32_t> magic;
|
|
||||||
xe::be<uint32_t> version;
|
|
||||||
xe::be<uint32_t> entry_count;
|
|
||||||
xe::be<uint32_t> entry_used;
|
|
||||||
xe::be<uint32_t> free_count;
|
|
||||||
xe::be<uint32_t> free_used;
|
|
||||||
};
|
|
||||||
static_assert_size(XbdfHeader, 24);
|
|
||||||
|
|
||||||
struct XbdfEntry {
|
|
||||||
xe::be<uint16_t> section;
|
|
||||||
xe::be<uint64_t> id;
|
|
||||||
xe::be<uint32_t> offset;
|
|
||||||
xe::be<uint32_t> size;
|
|
||||||
};
|
|
||||||
static_assert_size(XbdfEntry, 18);
|
|
||||||
|
|
||||||
struct XbdfFileLoc {
|
|
||||||
xe::be<uint32_t> offset;
|
|
||||||
xe::be<uint32_t> size;
|
|
||||||
};
|
|
||||||
static_assert_size(XbdfFileLoc, 8);
|
|
||||||
|
|
||||||
struct XdbfXstc {
|
|
||||||
xe::be<uint32_t> magic;
|
|
||||||
xe::be<uint32_t> version;
|
|
||||||
xe::be<uint32_t> size;
|
|
||||||
xe::be<uint32_t> default_language;
|
|
||||||
};
|
|
||||||
static_assert_size(XdbfXstc, 16);
|
|
||||||
|
|
||||||
struct XdbfXstrHeader {
|
|
||||||
xe::be<uint32_t> magic;
|
|
||||||
xe::be<uint32_t> version;
|
|
||||||
xe::be<uint32_t> size;
|
|
||||||
xe::be<uint16_t> string_count;
|
|
||||||
};
|
|
||||||
static_assert_size(XdbfXstrHeader, 14);
|
|
||||||
|
|
||||||
struct XdbfStringTableEntry {
|
|
||||||
xe::be<uint16_t> id;
|
|
||||||
xe::be<uint16_t> string_length;
|
|
||||||
};
|
|
||||||
static_assert_size(XdbfStringTableEntry, 4);
|
|
||||||
#pragma pack(pop)
|
|
||||||
|
|
||||||
private:
|
|
||||||
const uint8_t* data_ = nullptr;
|
|
||||||
size_t data_size_ = 0;
|
|
||||||
const uint8_t* content_offset_ = nullptr;
|
|
||||||
|
|
||||||
const XbdfHeader* header_ = nullptr;
|
|
||||||
const XbdfEntry* entries_ = nullptr;
|
|
||||||
const XbdfFileLoc* files_ = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
class XdbfGameData : public XdbfWrapper {
|
|
||||||
public:
|
|
||||||
XdbfGameData(const uint8_t* data, size_t data_size)
|
|
||||||
: XdbfWrapper(data, data_size) {}
|
|
||||||
|
|
||||||
// The game icon image, if found.
|
|
||||||
XdbfBlock icon() const;
|
|
||||||
|
|
||||||
// The game's default language.
|
|
||||||
XdbfLocale default_language() const;
|
|
||||||
|
|
||||||
// The game's title in its default language.
|
|
||||||
std::string title() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace util
|
|
||||||
} // namespace kernel
|
|
||||||
} // namespace xe
|
|
||||||
|
|
||||||
#endif // XENIA_KERNEL_UTIL_XDBF_UTILS_H_
|
|
|
@ -17,6 +17,11 @@ namespace kernel {
|
||||||
namespace xam {
|
namespace xam {
|
||||||
namespace apps {
|
namespace apps {
|
||||||
|
|
||||||
|
struct X_XUSER_ACHIEVEMENT {
|
||||||
|
xe::be<uint32_t> user_idx;
|
||||||
|
xe::be<uint32_t> achievement_id;
|
||||||
|
};
|
||||||
|
|
||||||
XgiApp::XgiApp(KernelState* kernel_state) : App(kernel_state, 0xFB) {}
|
XgiApp::XgiApp(KernelState* kernel_state) : App(kernel_state, 0xFB) {}
|
||||||
|
|
||||||
// http://mb.mirage.org/bugzilla/xliveless/main.c
|
// http://mb.mirage.org/bugzilla/xliveless/main.c
|
||||||
|
@ -55,6 +60,32 @@ X_RESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
||||||
uint32_t achievements_ptr = xe::load_and_swap<uint32_t>(buffer + 4);
|
uint32_t achievements_ptr = xe::load_and_swap<uint32_t>(buffer + 4);
|
||||||
XELOGD("XGIUserWriteAchievements(%.8X, %.8X)", achievement_count,
|
XELOGD("XGIUserWriteAchievements(%.8X, %.8X)", achievement_count,
|
||||||
achievements_ptr);
|
achievements_ptr);
|
||||||
|
|
||||||
|
auto* game_gpd = kernel_state_->user_profile()->GetTitleGpd();
|
||||||
|
if (!game_gpd) {
|
||||||
|
XELOGE("XGIUserWriteAchievements failed, no game GPD set?");
|
||||||
|
return X_ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool modified = false;
|
||||||
|
auto* achievement =
|
||||||
|
(X_XUSER_ACHIEVEMENT*)memory_->TranslateVirtual(achievements_ptr);
|
||||||
|
xdbf::Achievement ach;
|
||||||
|
for (uint32_t i = 0; i < achievement_count; i++, achievement++) {
|
||||||
|
if (game_gpd->GetAchievement(achievement->achievement_id, &ach)) {
|
||||||
|
if (!ach.IsUnlocked()) {
|
||||||
|
XELOGI("Achievement Unlocked! %ws (%d gamerscore) - %ws",
|
||||||
|
ach.label.c_str(), ach.gamerscore, ach.description.c_str());
|
||||||
|
ach.Unlock(false);
|
||||||
|
game_gpd->UpdateAchievement(ach);
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (modified) {
|
||||||
|
kernel_state_->user_profile()->UpdateTitleGpd();
|
||||||
|
}
|
||||||
|
|
||||||
return X_ERROR_SUCCESS;
|
return X_ERROR_SUCCESS;
|
||||||
}
|
}
|
||||||
case 0x000B0010: {
|
case 0x000B0010: {
|
||||||
|
|
|
@ -11,6 +11,14 @@
|
||||||
|
|
||||||
#include "xenia/kernel/kernel_state.h"
|
#include "xenia/kernel/kernel_state.h"
|
||||||
#include "xenia/kernel/util/shim_utils.h"
|
#include "xenia/kernel/util/shim_utils.h"
|
||||||
|
|
||||||
|
#include <gflags/gflags.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"
|
#include "xenia/kernel/xam/user_profile.h"
|
||||||
|
|
||||||
DEFINE_string(profile_name, "XeniaUser", "Gamertag", "General");
|
DEFINE_string(profile_name, "XeniaUser", "Gamertag", "General");
|
||||||
|
@ -19,9 +27,88 @@ namespace xe {
|
||||||
namespace kernel {
|
namespace kernel {
|
||||||
namespace xam {
|
namespace xam {
|
||||||
|
|
||||||
UserProfile::UserProfile() {
|
DEFINE_string(profile_directory, "Content\\Profile\\",
|
||||||
xuid_ = 0xE000BABEBABEBABE;
|
"The directory to store profile data inside");
|
||||||
name_ = (cvars::profile_name);
|
|
||||||
|
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) {
|
||||||
|
account_.xuid_online = 0xE000BABEBABEBABE;
|
||||||
|
wcscpy_s(account_.gamertag, cvars::profile_name);
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -87,6 +174,347 @@ UserProfile::UserProfile() {
|
||||||
AddSetting(std::make_unique<BinarySetting>(0x63E83FFE));
|
AddSetting(std::make_unique<BinarySetting>(0x63E83FFE));
|
||||||
// XPROFILE_TITLE_SPECIFIC3
|
// XPROFILE_TITLE_SPECIFIC3
|
||||||
AddSetting(std::make_unique<BinarySetting>(0x63E83FFD));
|
AddSetting(std::make_unique<BinarySetting>(0x63E83FFD));
|
||||||
|
|
||||||
|
// Try loading profile GPD files...
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
XELOGI("Loading profile GPDs from path %s", FLAGS_profile_directory.c_str());
|
||||||
|
|
||||||
|
mmap_ = MappedMemory::Open(
|
||||||
|
xe::to_wstring(FLAGS_profile_directory) + L"FFFE07D1.gpd",
|
||||||
|
MappedMemory::Mode::kRead);
|
||||||
|
if (!mmap_) {
|
||||||
|
XELOGW(
|
||||||
|
"Failed to open dash GPD (FFFE07D1.gpd) for reading, using blank one");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dash_gpd_.Read(mmap_->data(), mmap_->size());
|
||||||
|
mmap_->Close();
|
||||||
|
|
||||||
|
std::vector<xdbf::TitlePlayed> titles;
|
||||||
|
dash_gpd_.GetTitles(&titles);
|
||||||
|
|
||||||
|
for (auto title : titles) {
|
||||||
|
wchar_t fname[256];
|
||||||
|
_swprintf(fname, L"%X.gpd", title.title_id);
|
||||||
|
mmap_ = MappedMemory::Open(xe::to_wstring(FLAGS_profile_directory) + fname,
|
||||||
|
MappedMemory::Mode::kRead);
|
||||||
|
if (!mmap_) {
|
||||||
|
XELOGE("Failed to open GPD for title %X (%s)!", title.title_id,
|
||||||
|
xe::to_string(title.title_name).c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
xdbf::GpdFile title_gpd(title.title_id);
|
||||||
|
bool result = title_gpd.Read(mmap_->data(), mmap_->size());
|
||||||
|
mmap_->Close();
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
XELOGE("Failed to read GPD for title %X (%s)!", title.title_id,
|
||||||
|
xe::to_string(title.title_name).c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
title_gpds_[title.title_id] = title_gpd;
|
||||||
|
}
|
||||||
|
|
||||||
|
XELOGI("Loaded %d profile GPDs", title_gpds_.size() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
xdbf::GpdFile* UserProfile::SetTitleSpaData(const xdbf::SpaFile& spa_data) {
|
||||||
|
uint32_t spa_title = spa_data.GetTitleId();
|
||||||
|
|
||||||
|
std::vector<xdbf::Achievement> spa_achievements;
|
||||||
|
// TODO: let user choose locale?
|
||||||
|
spa_data.GetAchievements(spa_data.GetDefaultLocale(), &spa_achievements);
|
||||||
|
|
||||||
|
xdbf::TitlePlayed title_info;
|
||||||
|
|
||||||
|
auto gpd = title_gpds_.find(spa_title);
|
||||||
|
if (gpd != title_gpds_.end()) {
|
||||||
|
auto& title_gpd = (*gpd).second;
|
||||||
|
|
||||||
|
XELOGI("Loaded existing GPD for title %X", spa_title);
|
||||||
|
|
||||||
|
bool always_update_title = false;
|
||||||
|
if (!dash_gpd_.GetTitle(spa_title, &title_info)) {
|
||||||
|
assert_always();
|
||||||
|
XELOGE(
|
||||||
|
"GPD exists but is missing XbdfTitlePlayed entry? (this shouldn't be "
|
||||||
|
"happening!)");
|
||||||
|
// Try to work around it...
|
||||||
|
title_info.title_name = xe::to_wstring(spa_data.GetTitleName());
|
||||||
|
title_info.title_id = spa_title;
|
||||||
|
title_info.achievements_possible = 0;
|
||||||
|
title_info.achievements_earned = 0;
|
||||||
|
title_info.gamerscore_total = 0;
|
||||||
|
title_info.gamerscore_earned = 0;
|
||||||
|
always_update_title = true;
|
||||||
|
}
|
||||||
|
title_info.last_played = Clock::QueryHostSystemTime();
|
||||||
|
|
||||||
|
// Check SPA for any achievements current GPD might be missing
|
||||||
|
// (maybe added in TUs etc?)
|
||||||
|
bool ach_updated = false;
|
||||||
|
for (auto ach : spa_achievements) {
|
||||||
|
bool ach_exists = title_gpd.GetAchievement(ach.id, nullptr);
|
||||||
|
if (ach_exists && !always_update_title) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Achievement doesn't exist in current title info, lets add it
|
||||||
|
title_info.achievements_possible++;
|
||||||
|
title_info.gamerscore_total += ach.gamerscore;
|
||||||
|
|
||||||
|
// If it doesn't exist in GPD, add it to that too
|
||||||
|
if (!ach_exists) {
|
||||||
|
XELOGD(
|
||||||
|
"Adding new achievement %d (%s) from SPA (wasn't inside existing "
|
||||||
|
"GPD)",
|
||||||
|
ach.id, xe::to_string(ach.label).c_str());
|
||||||
|
|
||||||
|
ach_updated = true;
|
||||||
|
title_gpd.UpdateAchievement(ach);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update dash with new title_info
|
||||||
|
dash_gpd_.UpdateTitle(title_info);
|
||||||
|
|
||||||
|
// Only write game GPD if achievements were updated
|
||||||
|
if (ach_updated) {
|
||||||
|
UpdateGpd(spa_title, title_gpd);
|
||||||
|
}
|
||||||
|
UpdateGpd(kDashboardID, dash_gpd_);
|
||||||
|
} else {
|
||||||
|
// GPD not found... have to create it!
|
||||||
|
XELOGI("Creating new GPD for title %X", spa_title);
|
||||||
|
|
||||||
|
title_info.title_name = xe::to_wstring(spa_data.GetTitleName());
|
||||||
|
title_info.title_id = spa_title;
|
||||||
|
title_info.last_played = Clock::QueryHostSystemTime();
|
||||||
|
|
||||||
|
// Copy cheevos from SPA -> GPD
|
||||||
|
xdbf::GpdFile title_gpd(spa_title);
|
||||||
|
for (auto ach : spa_achievements) {
|
||||||
|
title_gpd.UpdateAchievement(ach);
|
||||||
|
|
||||||
|
title_info.achievements_possible++;
|
||||||
|
title_info.gamerscore_total += ach.gamerscore;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try copying achievement images if we can...
|
||||||
|
for (auto ach : spa_achievements) {
|
||||||
|
auto* image_entry = spa_data.GetEntry(
|
||||||
|
static_cast<uint16_t>(xdbf::SpaSection::kImage), ach.image_id);
|
||||||
|
if (image_entry) {
|
||||||
|
title_gpd.UpdateEntry(*image_entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try adding title image & name
|
||||||
|
auto* title_image =
|
||||||
|
spa_data.GetEntry(static_cast<uint16_t>(xdbf::SpaSection::kImage),
|
||||||
|
static_cast<uint64_t>(xdbf::SpaID::Title));
|
||||||
|
if (title_image) {
|
||||||
|
title_gpd.UpdateEntry(*title_image);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto title_name = xe::to_wstring(spa_data.GetTitleName());
|
||||||
|
if (title_name.length()) {
|
||||||
|
xdbf::Entry title_name_ent;
|
||||||
|
title_name_ent.info.section =
|
||||||
|
static_cast<uint16_t>(xdbf::GpdSection::kString);
|
||||||
|
title_name_ent.info.id = static_cast<uint64_t>(xdbf::SpaID::Title);
|
||||||
|
title_name_ent.data.resize((title_name.length() + 1) * 2);
|
||||||
|
xe::copy_and_swap((wchar_t*)title_name_ent.data.data(),
|
||||||
|
title_name.c_str(), title_name.length());
|
||||||
|
title_gpd.UpdateEntry(title_name_ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
title_gpds_[spa_title] = title_gpd;
|
||||||
|
|
||||||
|
// Update dash GPD with title and write updated GPDs
|
||||||
|
dash_gpd_.UpdateTitle(title_info);
|
||||||
|
|
||||||
|
UpdateGpd(spa_title, title_gpd);
|
||||||
|
UpdateGpd(kDashboardID, dash_gpd_);
|
||||||
|
}
|
||||||
|
|
||||||
|
curr_gpd_ = &title_gpds_[spa_title];
|
||||||
|
curr_title_id_ = spa_title;
|
||||||
|
|
||||||
|
// Print achievement list to log, ATM there's no other way for users to see
|
||||||
|
// achievement status...
|
||||||
|
std::vector<xdbf::Achievement> achievements;
|
||||||
|
if (curr_gpd_->GetAchievements(&achievements)) {
|
||||||
|
XELOGI("Achievement list:");
|
||||||
|
|
||||||
|
for (auto ach : achievements) {
|
||||||
|
// TODO: use ach.unachieved_desc for locked achievements?
|
||||||
|
// depends on XdbfAchievementFlags::kShowUnachieved afaik
|
||||||
|
XELOGI("%d - %s - %s - %d GS - %s", ach.id,
|
||||||
|
xe::to_string(ach.label).c_str(),
|
||||||
|
xe::to_string(ach.description).c_str(), ach.gamerscore,
|
||||||
|
ach.IsUnlocked() ? "unlocked" : "locked");
|
||||||
|
}
|
||||||
|
|
||||||
|
XELOGI("Unlocked achievements: %d/%d, gamerscore: %d/%d\r\n",
|
||||||
|
title_info.achievements_earned, title_info.achievements_possible,
|
||||||
|
title_info.gamerscore_earned, title_info.gamerscore_total);
|
||||||
|
}
|
||||||
|
|
||||||
|
return curr_gpd_;
|
||||||
|
}
|
||||||
|
|
||||||
|
xdbf::GpdFile* UserProfile::GetTitleGpd(uint32_t title_id) {
|
||||||
|
if (title_id == -1) {
|
||||||
|
return curr_gpd_;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto gpd = title_gpds_.find(title_id);
|
||||||
|
if (gpd == title_gpds_.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &(*gpd).second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UserProfile::GetTitles(std::vector<xdbf::GpdFile*>& titles) {
|
||||||
|
for (auto title : title_gpds_) {
|
||||||
|
titles.push_back(&title.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UserProfile::UpdateTitleGpd(uint32_t title_id) {
|
||||||
|
if (title_id == -1) {
|
||||||
|
if (!curr_gpd_ || curr_title_id_ == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
title_id = curr_title_id_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool result = UpdateGpd(title_id, *curr_gpd_);
|
||||||
|
if (!result) {
|
||||||
|
XELOGE("UpdateTitleGpd failed on title %X!", title_id);
|
||||||
|
} else {
|
||||||
|
XELOGD("Updated title %X GPD successfully!", title_id);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UserProfile::UpdateAllGpds() {
|
||||||
|
for (const auto& pair : title_gpds_) {
|
||||||
|
auto gpd = pair.second;
|
||||||
|
bool result = UpdateGpd(pair.first, gpd);
|
||||||
|
if (!result) {
|
||||||
|
XELOGE("UpdateGpdFiles failed on title %X!", pair.first);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No need to update dash GPD here, the UpdateGpd func should take care of it
|
||||||
|
// when needed
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UserProfile::UpdateGpd(uint32_t title_id, xdbf::GpdFile& gpd_data) {
|
||||||
|
size_t gpd_length = 0;
|
||||||
|
if (!gpd_data.Write(nullptr, &gpd_length)) {
|
||||||
|
XELOGE("Failed to get GPD size for title %X!", title_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filesystem::PathExists(xe::to_wstring(FLAGS_profile_directory))) {
|
||||||
|
filesystem::CreateFolder(xe::to_wstring(FLAGS_profile_directory));
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t fname[256];
|
||||||
|
_swprintf(fname, L"%X.gpd", title_id);
|
||||||
|
|
||||||
|
filesystem::CreateFile(xe::to_wstring(FLAGS_profile_directory) + fname);
|
||||||
|
auto mmap_ =
|
||||||
|
MappedMemory::Open(xe::to_wstring(FLAGS_profile_directory) + fname,
|
||||||
|
MappedMemory::Mode::kReadWrite, 0, gpd_length);
|
||||||
|
if (!mmap_) {
|
||||||
|
XELOGE("Failed to open %X.gpd for writing!", title_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ret_val = true;
|
||||||
|
|
||||||
|
if (!gpd_data.Write(mmap_->data(), &gpd_length)) {
|
||||||
|
XELOGE("Failed to write GPD data for %X!", title_id);
|
||||||
|
ret_val = false;
|
||||||
|
} else {
|
||||||
|
// Check if we need to update dashboard data...
|
||||||
|
if (title_id != kDashboardID) {
|
||||||
|
xdbf::TitlePlayed title_info;
|
||||||
|
if (dash_gpd_.GetTitle(title_id, &title_info)) {
|
||||||
|
std::vector<xdbf::Achievement> gpd_achievements;
|
||||||
|
gpd_data.GetAchievements(&gpd_achievements);
|
||||||
|
|
||||||
|
uint32_t num_ach_total = 0;
|
||||||
|
uint32_t num_ach_earned = 0;
|
||||||
|
uint32_t gamerscore_total = 0;
|
||||||
|
uint32_t gamerscore_earned = 0;
|
||||||
|
for (auto ach : gpd_achievements) {
|
||||||
|
num_ach_total++;
|
||||||
|
gamerscore_total += ach.gamerscore;
|
||||||
|
if (ach.IsUnlocked()) {
|
||||||
|
num_ach_earned++;
|
||||||
|
gamerscore_earned += ach.gamerscore;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only update dash GPD if something has changed
|
||||||
|
if (num_ach_total != title_info.achievements_possible ||
|
||||||
|
num_ach_earned != title_info.achievements_earned ||
|
||||||
|
gamerscore_total != title_info.gamerscore_total ||
|
||||||
|
gamerscore_earned != title_info.gamerscore_earned) {
|
||||||
|
title_info.achievements_possible = num_ach_total;
|
||||||
|
title_info.achievements_earned = num_ach_earned;
|
||||||
|
title_info.gamerscore_total = gamerscore_total;
|
||||||
|
title_info.gamerscore_earned = gamerscore_earned;
|
||||||
|
|
||||||
|
dash_gpd_.UpdateTitle(title_info);
|
||||||
|
UpdateGpd(kDashboardID, dash_gpd_);
|
||||||
|
|
||||||
|
// TODO: update gamerscore/achievements earned/titles played settings
|
||||||
|
// in dashboard GPD
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mmap_->Close(gpd_length);
|
||||||
|
return ret_val;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UserProfile::AddSetting(std::unique_ptr<Setting> setting) {
|
void UserProfile::AddSetting(std::unique_ptr<Setting> setting) {
|
||||||
|
|
|
@ -15,12 +15,118 @@
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/kernel/xam/xdbf/xdbf.h"
|
||||||
#include "xenia/xbox.h"
|
#include "xenia/xbox.h"
|
||||||
|
|
||||||
namespace xe {
|
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 {
|
||||||
|
@ -197,23 +303,44 @@ 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);
|
||||||
Setting* GetSetting(uint32_t setting_id);
|
Setting* GetSetting(uint32_t setting_id);
|
||||||
|
|
||||||
|
xdbf::GpdFile* SetTitleSpaData(const xdbf::SpaFile& spa_data);
|
||||||
|
xdbf::GpdFile* GetTitleGpd(uint32_t title_id = -1);
|
||||||
|
|
||||||
|
void GetTitles(std::vector<xdbf::GpdFile*>& titles);
|
||||||
|
|
||||||
|
bool UpdateTitleGpd(uint32_t title_id = -1);
|
||||||
|
bool UpdateAllGpds();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint64_t xuid_;
|
void LoadProfile();
|
||||||
std::string name_;
|
bool UpdateGpd(uint32_t title_id, xdbf::GpdFile& gpd_data);
|
||||||
|
|
||||||
|
X_XAMACCOUNTINFO account_;
|
||||||
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_;
|
||||||
|
|
||||||
void LoadSetting(UserProfile::Setting*);
|
void LoadSetting(UserProfile::Setting*);
|
||||||
void SaveSetting(UserProfile::Setting*);
|
void SaveSetting(UserProfile::Setting*);
|
||||||
|
|
||||||
|
std::unordered_map<uint32_t, xdbf::GpdFile> title_gpds_;
|
||||||
|
xdbf::GpdFile dash_gpd_;
|
||||||
|
xdbf::GpdFile* curr_gpd_ = nullptr;
|
||||||
|
uint32_t curr_title_id_ = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace xam
|
} // namespace xam
|
||||||
|
|
|
@ -21,6 +21,83 @@ namespace xe {
|
||||||
namespace kernel {
|
namespace kernel {
|
||||||
namespace xam {
|
namespace xam {
|
||||||
|
|
||||||
|
struct X_PROFILEENUMRESULT {
|
||||||
|
xe::be<uint64_t> xuid_offline; // E0.....
|
||||||
|
X_XAMACCOUNTINFO account;
|
||||||
|
xe::be<uint32_t> device_id;
|
||||||
|
};
|
||||||
|
static_assert_size(X_PROFILEENUMRESULT, 0x188);
|
||||||
|
|
||||||
|
dword_result_t XamProfileCreateEnumerator(dword_t device_id,
|
||||||
|
lpdword_t handle_out) {
|
||||||
|
assert_not_null(handle_out);
|
||||||
|
|
||||||
|
auto e =
|
||||||
|
new XStaticEnumerator(kernel_state(), 1, sizeof(X_PROFILEENUMRESULT));
|
||||||
|
|
||||||
|
e->Initialize();
|
||||||
|
|
||||||
|
const auto& user_profile = kernel_state()->user_profile();
|
||||||
|
|
||||||
|
X_PROFILEENUMRESULT* profile = (X_PROFILEENUMRESULT*)e->AppendItem();
|
||||||
|
memset(profile, 0, sizeof(X_PROFILEENUMRESULT));
|
||||||
|
profile->xuid_offline = user_profile->xuid();
|
||||||
|
profile->device_id = 0xF00D0000;
|
||||||
|
|
||||||
|
auto tag = xe::to_wstring(user_profile->name());
|
||||||
|
xe::copy_and_swap<wchar_t>(profile->account.gamertag, tag.c_str(),
|
||||||
|
tag.length());
|
||||||
|
profile->account.xuid_online = user_profile->xuid();
|
||||||
|
|
||||||
|
*handle_out = e->handle();
|
||||||
|
return X_ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
DECLARE_XAM_EXPORT1(XamProfileCreateEnumerator, kUserProfiles, kImplemented);
|
||||||
|
|
||||||
|
dword_result_t XamProfileEnumerate(dword_t handle, dword_t flags,
|
||||||
|
lpvoid_t buffer,
|
||||||
|
pointer_t<XAM_OVERLAPPED> overlapped) {
|
||||||
|
assert_true(flags == 0);
|
||||||
|
|
||||||
|
auto e = kernel_state()->object_table()->LookupObject<XEnumerator>(handle);
|
||||||
|
if (!e) {
|
||||||
|
if (overlapped) {
|
||||||
|
kernel_state()->CompleteOverlappedImmediateEx(
|
||||||
|
overlapped, X_ERROR_INVALID_HANDLE, X_ERROR_INVALID_HANDLE, 0);
|
||||||
|
return X_ERROR_IO_PENDING;
|
||||||
|
} else {
|
||||||
|
return X_ERROR_INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.Zero(sizeof(X_PROFILEENUMRESULT));
|
||||||
|
|
||||||
|
X_RESULT result;
|
||||||
|
|
||||||
|
if (e->current_item() >= e->item_count()) {
|
||||||
|
result = X_ERROR_NO_MORE_FILES;
|
||||||
|
} else {
|
||||||
|
auto item_buffer = buffer.as<uint8_t*>();
|
||||||
|
if (!e->WriteItem(item_buffer)) {
|
||||||
|
result = X_ERROR_NO_MORE_FILES;
|
||||||
|
} else {
|
||||||
|
result = X_ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return X_ERROR_NO_MORE_FILES in HRESULT form.
|
||||||
|
X_HRESULT extended_result = result != 0 ? X_HRESULT_FROM_WIN32(result) : 0;
|
||||||
|
if (overlapped) {
|
||||||
|
kernel_state()->CompleteOverlappedImmediateEx(
|
||||||
|
overlapped, result, extended_result, result == X_ERROR_SUCCESS ? 1 : 0);
|
||||||
|
return X_ERROR_IO_PENDING;
|
||||||
|
} else {
|
||||||
|
assert_always();
|
||||||
|
return X_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DECLARE_XAM_EXPORT1(XamProfileEnumerate, kUserProfiles, kImplemented);
|
||||||
|
|
||||||
X_HRESULT_result_t XamUserGetXUID(dword_t user_index, dword_t unk,
|
X_HRESULT_result_t XamUserGetXUID(dword_t user_index, dword_t unk,
|
||||||
lpqword_t xuid_ptr) {
|
lpqword_t xuid_ptr) {
|
||||||
if (user_index) {
|
if (user_index) {
|
||||||
|
@ -448,6 +525,20 @@ dword_result_t XamShowSigninUI(dword_t unk, dword_t unk_mask) {
|
||||||
}
|
}
|
||||||
DECLARE_XAM_EXPORT1(XamShowSigninUI, kUserProfiles, kStub);
|
DECLARE_XAM_EXPORT1(XamShowSigninUI, kUserProfiles, kStub);
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct X_XACHIEVEMENT_DETAILS {
|
||||||
|
xe::be<uint32_t> id;
|
||||||
|
xe::be<uint32_t> label_ptr;
|
||||||
|
xe::be<uint32_t> description_ptr;
|
||||||
|
xe::be<uint32_t> unachieved_ptr;
|
||||||
|
xe::be<uint32_t> image_id;
|
||||||
|
xe::be<uint32_t> gamerscore;
|
||||||
|
xe::be<uint64_t> unlock_time;
|
||||||
|
xe::be<uint32_t> flags;
|
||||||
|
};
|
||||||
|
static_assert_size(X_XACHIEVEMENT_DETAILS, 36);
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
dword_result_t XamUserCreateAchievementEnumerator(dword_t title_id,
|
dword_result_t XamUserCreateAchievementEnumerator(dword_t title_id,
|
||||||
dword_t user_index,
|
dword_t user_index,
|
||||||
dword_t xuid, dword_t flags,
|
dword_t xuid, dword_t flags,
|
||||||
|
@ -455,14 +546,60 @@ dword_result_t XamUserCreateAchievementEnumerator(dword_t title_id,
|
||||||
lpdword_t buffer_size_ptr,
|
lpdword_t buffer_size_ptr,
|
||||||
lpdword_t handle_ptr) {
|
lpdword_t handle_ptr) {
|
||||||
if (buffer_size_ptr) {
|
if (buffer_size_ptr) {
|
||||||
*buffer_size_ptr = 500 * count;
|
*buffer_size_ptr = sizeof(X_XACHIEVEMENT_DETAILS) * count;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto e = new XStaticEnumerator(kernel_state(), count, 500);
|
auto e = new XStaticEnumerator(kernel_state(), count,
|
||||||
|
sizeof(X_XACHIEVEMENT_DETAILS));
|
||||||
e->Initialize();
|
e->Initialize();
|
||||||
|
|
||||||
*handle_ptr = e->handle();
|
*handle_ptr = e->handle();
|
||||||
|
|
||||||
|
// Copy achievements into the enumerator if game GPD is loaded
|
||||||
|
auto* game_gpd = kernel_state()->user_profile()->GetTitleGpd(title_id);
|
||||||
|
if (!game_gpd) {
|
||||||
|
XELOGE(
|
||||||
|
"XamUserCreateAchievementEnumerator failed to find GPD for title %X!",
|
||||||
|
title_id);
|
||||||
|
return X_ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t placeholder = 0;
|
||||||
|
|
||||||
|
if (!placeholder) {
|
||||||
|
wchar_t* placeholder_val = L"<placeholder>";
|
||||||
|
|
||||||
|
placeholder = kernel_memory()->SystemHeapAlloc(
|
||||||
|
((uint32_t)wcslen(placeholder_val) + 1) * 2);
|
||||||
|
auto* place_addr = kernel_memory()->TranslateVirtual<wchar_t*>(placeholder);
|
||||||
|
|
||||||
|
memset(place_addr, 0, (wcslen(placeholder_val) + 1) * 2);
|
||||||
|
xe::copy_and_swap(place_addr, placeholder_val, wcslen(placeholder_val));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<xdbf::Achievement> achievements;
|
||||||
|
game_gpd->GetAchievements(&achievements);
|
||||||
|
|
||||||
|
for (auto ach : achievements) {
|
||||||
|
auto* details = (X_XACHIEVEMENT_DETAILS*)e->AppendItem();
|
||||||
|
details->id = ach.id;
|
||||||
|
details->image_id = ach.image_id;
|
||||||
|
details->gamerscore = ach.gamerscore;
|
||||||
|
details->unlock_time = ach.unlock_time;
|
||||||
|
details->flags = ach.flags;
|
||||||
|
|
||||||
|
// TODO: these, allocating guest mem for them every CreateEnum call would be
|
||||||
|
// very bad...
|
||||||
|
|
||||||
|
// maybe we could alloc these in guest when the title GPD is first loaded?
|
||||||
|
details->label_ptr = placeholder;
|
||||||
|
details->description_ptr = placeholder;
|
||||||
|
details->unachieved_ptr = placeholder;
|
||||||
|
}
|
||||||
|
|
||||||
|
XELOGD("XamUserCreateAchievementEnumerator: added %d items to enumerator",
|
||||||
|
e->item_count());
|
||||||
|
|
||||||
return X_ERROR_SUCCESS;
|
return X_ERROR_SUCCESS;
|
||||||
}
|
}
|
||||||
DECLARE_XAM_EXPORT1(XamUserCreateAchievementEnumerator, kUserProfiles,
|
DECLARE_XAM_EXPORT1(XamUserCreateAchievementEnumerator, kUserProfiles,
|
||||||
|
|
|
@ -0,0 +1,435 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2016 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "xenia/kernel/xam/xdbf/xdbf.h"
|
||||||
|
#include "xenia/base/string.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace kernel {
|
||||||
|
namespace xam {
|
||||||
|
namespace xdbf {
|
||||||
|
|
||||||
|
constexpr uint32_t kXdbfMagicXdbf = 'XDBF';
|
||||||
|
|
||||||
|
bool XdbfFile::Read(const uint8_t* data, size_t data_size) {
|
||||||
|
if (!data || data_size <= sizeof(X_XDBF_HEADER)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* ptr = data;
|
||||||
|
memcpy(&header_, ptr, sizeof(X_XDBF_HEADER));
|
||||||
|
if (header_.magic != kXdbfMagicXdbf) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr += sizeof(X_XDBF_HEADER);
|
||||||
|
|
||||||
|
auto* free_ptr = (const X_XDBF_FILELOC*)(ptr + (sizeof(X_XDBF_ENTRY) *
|
||||||
|
header_.entry_count));
|
||||||
|
auto* data_ptr =
|
||||||
|
(uint8_t*)free_ptr + (sizeof(X_XDBF_FILELOC) * header_.free_count);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < header_.entry_used; i++) {
|
||||||
|
Entry entry;
|
||||||
|
memcpy(&entry.info, ptr, sizeof(X_XDBF_ENTRY));
|
||||||
|
entry.data.resize(entry.info.size);
|
||||||
|
memcpy(entry.data.data(), data_ptr + entry.info.offset, entry.info.size);
|
||||||
|
entries_.push_back(entry);
|
||||||
|
|
||||||
|
ptr += sizeof(X_XDBF_ENTRY);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < header_.free_used; i++) {
|
||||||
|
free_entries_.push_back(*free_ptr);
|
||||||
|
free_ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool XdbfFile::Write(uint8_t* data, size_t* data_size) {
|
||||||
|
*data_size = 0;
|
||||||
|
|
||||||
|
*data_size += sizeof(X_XDBF_HEADER);
|
||||||
|
*data_size += entries_.size() * sizeof(X_XDBF_ENTRY);
|
||||||
|
*data_size += 1 * sizeof(X_XDBF_FILELOC);
|
||||||
|
|
||||||
|
size_t entries_size = 0;
|
||||||
|
for (auto ent : entries_) {
|
||||||
|
entries_size += ent.data.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
*data_size += entries_size;
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
header_.entry_count = header_.entry_used = (uint32_t)entries_.size();
|
||||||
|
header_.free_count = header_.free_used = 1;
|
||||||
|
|
||||||
|
auto* ptr = data;
|
||||||
|
memcpy(ptr, &header_, sizeof(X_XDBF_HEADER));
|
||||||
|
ptr += sizeof(X_XDBF_HEADER);
|
||||||
|
|
||||||
|
auto* free_ptr =
|
||||||
|
(X_XDBF_FILELOC*)(ptr + (sizeof(X_XDBF_ENTRY) * header_.entry_count));
|
||||||
|
auto* data_start =
|
||||||
|
(uint8_t*)free_ptr + (sizeof(X_XDBF_FILELOC) * header_.free_count);
|
||||||
|
|
||||||
|
auto* data_ptr = data_start;
|
||||||
|
for (auto ent : entries_) {
|
||||||
|
ent.info.offset = (uint32_t)(data_ptr - data_start);
|
||||||
|
ent.info.size = (uint32_t)ent.data.size();
|
||||||
|
memcpy(ptr, &ent.info, sizeof(X_XDBF_ENTRY));
|
||||||
|
|
||||||
|
memcpy(data_ptr, ent.data.data(), ent.data.size());
|
||||||
|
data_ptr += ent.data.size();
|
||||||
|
ptr += sizeof(X_XDBF_ENTRY);
|
||||||
|
}
|
||||||
|
|
||||||
|
free_entries_.clear();
|
||||||
|
X_XDBF_FILELOC free_ent;
|
||||||
|
free_ent.offset = (uint32_t)*data_size - sizeof(X_XDBF_HEADER) -
|
||||||
|
(sizeof(X_XDBF_ENTRY) * header_.entry_count) -
|
||||||
|
(sizeof(X_XDBF_FILELOC) * header_.free_count);
|
||||||
|
|
||||||
|
free_ent.size = 0 - free_ent.offset;
|
||||||
|
free_entries_.push_back(free_ent);
|
||||||
|
|
||||||
|
for (auto ent : free_entries_) {
|
||||||
|
memcpy(free_ptr, &ent, sizeof(X_XDBF_FILELOC));
|
||||||
|
free_ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry* XdbfFile::GetEntry(uint16_t section, uint64_t id) const {
|
||||||
|
for (size_t i = 0; i < entries_.size(); i++) {
|
||||||
|
auto* entry = (Entry*)&entries_[i];
|
||||||
|
if (entry->info.section != section || entry->info.id != id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool XdbfFile::UpdateEntry(Entry entry) {
|
||||||
|
for (size_t i = 0; i < entries_.size(); i++) {
|
||||||
|
auto* ent = (Entry*)&entries_[i];
|
||||||
|
if (ent->info.section != entry.info.section ||
|
||||||
|
ent->info.id != entry.info.id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ent->data = entry.data;
|
||||||
|
ent->info.size = (uint32_t)entry.data.size();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry new_entry;
|
||||||
|
new_entry.info.section = entry.info.section;
|
||||||
|
new_entry.info.id = entry.info.id;
|
||||||
|
new_entry.info.size = (uint32_t)entry.data.size();
|
||||||
|
new_entry.data = entry.data;
|
||||||
|
|
||||||
|
entries_.push_back(new_entry);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetStringTableEntry_(const uint8_t* table_start, uint16_t string_id,
|
||||||
|
uint16_t count) {
|
||||||
|
auto* ptr = table_start;
|
||||||
|
for (uint16_t i = 0; i < count; ++i) {
|
||||||
|
auto entry = reinterpret_cast<const XdbfStringTableEntry*>(ptr);
|
||||||
|
ptr += sizeof(XdbfStringTableEntry);
|
||||||
|
if (entry->id == string_id) {
|
||||||
|
return std::string(reinterpret_cast<const char*>(ptr),
|
||||||
|
entry->string_length);
|
||||||
|
}
|
||||||
|
ptr += entry->string_length;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SpaFile::GetStringTableEntry(Locale locale,
|
||||||
|
uint16_t string_id) const {
|
||||||
|
auto xstr_table = GetEntry(static_cast<uint16_t>(SpaSection::kStringTable),
|
||||||
|
static_cast<uint64_t>(locale));
|
||||||
|
if (!xstr_table) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto xstr_head =
|
||||||
|
reinterpret_cast<const X_XDBF_TABLE_HEADER*>(xstr_table->data.data());
|
||||||
|
assert_true(xstr_head->magic == static_cast<uint32_t>(SpaID::Xstr));
|
||||||
|
assert_true(xstr_head->version == 1);
|
||||||
|
|
||||||
|
const uint8_t* ptr = xstr_table->data.data() + sizeof(X_XDBF_TABLE_HEADER);
|
||||||
|
|
||||||
|
return GetStringTableEntry_(ptr, string_id, xstr_head->count);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t SpaFile::GetAchievements(
|
||||||
|
Locale locale, std::vector<Achievement>* achievements) const {
|
||||||
|
auto xach_table = GetEntry(static_cast<uint16_t>(SpaSection::kMetadata),
|
||||||
|
static_cast<uint64_t>(SpaID::Xach));
|
||||||
|
if (!xach_table) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto xach_head =
|
||||||
|
reinterpret_cast<const X_XDBF_TABLE_HEADER*>(xach_table->data.data());
|
||||||
|
assert_true(xach_head->magic == static_cast<uint32_t>(SpaID::Xach));
|
||||||
|
assert_true(xach_head->version == 1);
|
||||||
|
|
||||||
|
auto xstr_table = GetEntry(static_cast<uint16_t>(SpaSection::kStringTable),
|
||||||
|
static_cast<uint64_t>(locale));
|
||||||
|
if (!xstr_table) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto xstr_head =
|
||||||
|
reinterpret_cast<const X_XDBF_TABLE_HEADER*>(xstr_table->data.data());
|
||||||
|
assert_true(xstr_head->magic == static_cast<uint32_t>(SpaID::Xstr));
|
||||||
|
assert_true(xstr_head->version == 1);
|
||||||
|
|
||||||
|
const uint8_t* xstr_ptr =
|
||||||
|
xstr_table->data.data() + sizeof(X_XDBF_TABLE_HEADER);
|
||||||
|
|
||||||
|
if (achievements) {
|
||||||
|
auto* ach_data =
|
||||||
|
reinterpret_cast<const X_XDBF_SPA_ACHIEVEMENT*>(xach_head + 1);
|
||||||
|
for (uint32_t i = 0; i < xach_head->count; i++) {
|
||||||
|
Achievement ach;
|
||||||
|
ach.id = ach_data->id;
|
||||||
|
ach.image_id = ach_data->image_id;
|
||||||
|
ach.gamerscore = ach_data->gamerscore;
|
||||||
|
ach.flags = ach_data->flags;
|
||||||
|
|
||||||
|
ach.label = xe::to_wstring(
|
||||||
|
GetStringTableEntry_(xstr_ptr, ach_data->label_id, xstr_head->count));
|
||||||
|
|
||||||
|
ach.description = xe::to_wstring(GetStringTableEntry_(
|
||||||
|
xstr_ptr, ach_data->description_id, xstr_head->count));
|
||||||
|
|
||||||
|
ach.unachieved_desc = xe::to_wstring(GetStringTableEntry_(
|
||||||
|
xstr_ptr, ach_data->unachieved_id, xstr_head->count));
|
||||||
|
|
||||||
|
achievements->push_back(ach);
|
||||||
|
ach_data++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return xach_head->count;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry* SpaFile::GetIcon() const {
|
||||||
|
return GetEntry(static_cast<uint16_t>(SpaSection::kImage),
|
||||||
|
static_cast<uint64_t>(SpaID::Title));
|
||||||
|
}
|
||||||
|
|
||||||
|
Locale SpaFile::GetDefaultLocale() const {
|
||||||
|
auto block = GetEntry(static_cast<uint16_t>(SpaSection::kMetadata),
|
||||||
|
static_cast<uint64_t>(SpaID::Xstc));
|
||||||
|
if (!block) {
|
||||||
|
return Locale::kEnglish;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto xstc = reinterpret_cast<const X_XDBF_XSTC_DATA*>(block->data.data());
|
||||||
|
assert_true(xstc->magic == static_cast<uint32_t>(SpaID::Xstc));
|
||||||
|
|
||||||
|
return static_cast<Locale>(static_cast<uint32_t>(xstc->default_language));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SpaFile::GetTitleName() const {
|
||||||
|
return GetStringTableEntry(GetDefaultLocale(),
|
||||||
|
static_cast<uint16_t>(SpaID::Title));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t SpaFile::GetTitleId() const {
|
||||||
|
auto block = GetEntry(static_cast<uint16_t>(SpaSection::kMetadata),
|
||||||
|
static_cast<uint64_t>(SpaID::Xthd));
|
||||||
|
if (!block) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto xthd = reinterpret_cast<const X_XDBF_XTHD_DATA*>(block->data.data());
|
||||||
|
assert_true(xthd->magic == static_cast<uint32_t>(SpaID::Xthd));
|
||||||
|
|
||||||
|
return xthd->title_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GpdFile::GetAchievement(uint16_t id, Achievement* dest) {
|
||||||
|
for (size_t i = 0; i < entries_.size(); i++) {
|
||||||
|
auto* entry = (Entry*)&entries_[i];
|
||||||
|
if (entry->info.section !=
|
||||||
|
static_cast<uint16_t>(GpdSection::kAchievement) ||
|
||||||
|
entry->info.id != id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* ach_data =
|
||||||
|
reinterpret_cast<const X_XDBF_GPD_ACHIEVEMENT*>(entry->data.data());
|
||||||
|
|
||||||
|
if (dest) {
|
||||||
|
dest->ReadGPD(ach_data);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t GpdFile::GetAchievements(
|
||||||
|
std::vector<Achievement>* achievements) const {
|
||||||
|
uint32_t ach_count = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < entries_.size(); i++) {
|
||||||
|
auto* entry = (Entry*)&entries_[i];
|
||||||
|
if (entry->info.section !=
|
||||||
|
static_cast<uint16_t>(GpdSection::kAchievement)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (entry->info.id == 0x100000000 || entry->info.id == 0x200000000) {
|
||||||
|
continue; // achievement sync data, ignore it
|
||||||
|
}
|
||||||
|
|
||||||
|
ach_count++;
|
||||||
|
|
||||||
|
if (achievements) {
|
||||||
|
auto* ach_data =
|
||||||
|
reinterpret_cast<const X_XDBF_GPD_ACHIEVEMENT*>(entry->data.data());
|
||||||
|
|
||||||
|
Achievement ach;
|
||||||
|
ach.ReadGPD(ach_data);
|
||||||
|
|
||||||
|
achievements->push_back(ach);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ach_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GpdFile::GetTitle(uint32_t title_id, TitlePlayed* dest) {
|
||||||
|
for (size_t i = 0; i < entries_.size(); i++) {
|
||||||
|
auto* entry = (Entry*)&entries_[i];
|
||||||
|
if (entry->info.section != static_cast<uint16_t>(GpdSection::kTitle) ||
|
||||||
|
entry->info.id != title_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* title_data =
|
||||||
|
reinterpret_cast<const X_XDBF_GPD_TITLEPLAYED*>(entry->data.data());
|
||||||
|
|
||||||
|
dest->ReadGPD(title_data);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t GpdFile::GetTitles(std::vector<TitlePlayed>* titles) const {
|
||||||
|
uint32_t title_count = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < entries_.size(); i++) {
|
||||||
|
auto* entry = (Entry*)&entries_[i];
|
||||||
|
if (entry->info.section != static_cast<uint16_t>(GpdSection::kTitle)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (entry->info.id == 0x100000000 || entry->info.id == 0x200000000) {
|
||||||
|
continue; // achievement sync data, ignore it
|
||||||
|
}
|
||||||
|
|
||||||
|
title_count++;
|
||||||
|
|
||||||
|
if (titles) {
|
||||||
|
auto* title_data =
|
||||||
|
reinterpret_cast<const X_XDBF_GPD_TITLEPLAYED*>(entry->data.data());
|
||||||
|
|
||||||
|
TitlePlayed title;
|
||||||
|
title.ReadGPD(title_data);
|
||||||
|
titles->push_back(title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return title_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GpdFile::UpdateAchievement(Achievement ach) {
|
||||||
|
Entry ent;
|
||||||
|
ent.info.section = static_cast<uint16_t>(GpdSection::kAchievement);
|
||||||
|
ent.info.id = ach.id;
|
||||||
|
|
||||||
|
// calculate entry size...
|
||||||
|
size_t label_len = (ach.label.length() * 2) + 2;
|
||||||
|
size_t desc_len = (ach.description.length() * 2) + 2;
|
||||||
|
size_t unach_len = (ach.unachieved_desc.length() * 2) + 2;
|
||||||
|
|
||||||
|
size_t est_size = sizeof(X_XDBF_GPD_ACHIEVEMENT);
|
||||||
|
est_size += label_len;
|
||||||
|
est_size += desc_len;
|
||||||
|
est_size += unach_len;
|
||||||
|
|
||||||
|
ent.data.resize(est_size);
|
||||||
|
memset(ent.data.data(), 0, est_size);
|
||||||
|
|
||||||
|
// convert Achievement to GPD achievement
|
||||||
|
auto* ach_data = reinterpret_cast<X_XDBF_GPD_ACHIEVEMENT*>(ent.data.data());
|
||||||
|
ach_data->id = ach.id;
|
||||||
|
ach_data->image_id = ach.image_id;
|
||||||
|
ach_data->gamerscore = ach.gamerscore;
|
||||||
|
ach_data->flags = ach.flags;
|
||||||
|
ach_data->unlock_time = ach.unlock_time;
|
||||||
|
|
||||||
|
auto* label_ptr = reinterpret_cast<uint8_t*>(ent.data.data() +
|
||||||
|
sizeof(X_XDBF_GPD_ACHIEVEMENT));
|
||||||
|
auto* desc_ptr = label_ptr + label_len;
|
||||||
|
auto* unach_ptr = desc_ptr + desc_len;
|
||||||
|
|
||||||
|
xe::copy_and_swap<wchar_t>((wchar_t*)label_ptr, ach.label.c_str(),
|
||||||
|
ach.label.size());
|
||||||
|
xe::copy_and_swap<wchar_t>((wchar_t*)desc_ptr, ach.description.c_str(),
|
||||||
|
ach.description.size());
|
||||||
|
xe::copy_and_swap<wchar_t>((wchar_t*)unach_ptr, ach.unachieved_desc.c_str(),
|
||||||
|
ach.unachieved_desc.size());
|
||||||
|
|
||||||
|
return UpdateEntry(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GpdFile::UpdateTitle(TitlePlayed title) {
|
||||||
|
Entry ent;
|
||||||
|
ent.info.section = static_cast<uint16_t>(GpdSection::kTitle);
|
||||||
|
ent.info.id = title.title_id;
|
||||||
|
|
||||||
|
// calculate entry size...
|
||||||
|
size_t name_len = (title.title_name.length() * 2) + 2;
|
||||||
|
|
||||||
|
size_t est_size = sizeof(X_XDBF_GPD_TITLEPLAYED);
|
||||||
|
est_size += name_len;
|
||||||
|
|
||||||
|
ent.data.resize(est_size);
|
||||||
|
memset(ent.data.data(), 0, est_size);
|
||||||
|
|
||||||
|
// convert XdbfTitlePlayed to GPD title
|
||||||
|
auto* title_data = reinterpret_cast<X_XDBF_GPD_TITLEPLAYED*>(ent.data.data());
|
||||||
|
title.WriteGPD(title_data);
|
||||||
|
|
||||||
|
return UpdateEntry(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace xdbf
|
||||||
|
} // namespace xam
|
||||||
|
} // namespace kernel
|
||||||
|
} // namespace xe
|
|
@ -0,0 +1,269 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2016 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_KERNEL_XAM_XDBF_XDBF_H_
|
||||||
|
#define XENIA_KERNEL_XAM_XDBF_XDBF_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/clock.h"
|
||||||
|
#include "xenia/base/memory.h"
|
||||||
|
|
||||||
|
#include "xenia/kernel/xam/xdbf/xdbf_xbox.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace kernel {
|
||||||
|
namespace xam {
|
||||||
|
namespace xdbf {
|
||||||
|
|
||||||
|
// https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/XEX/SPA.h
|
||||||
|
// https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/XEX/SPA.cpp
|
||||||
|
|
||||||
|
enum class SpaID : uint64_t {
|
||||||
|
Xach = 'XACH',
|
||||||
|
Xstr = 'XSTR',
|
||||||
|
Xstc = 'XSTC',
|
||||||
|
Xthd = 'XTHD',
|
||||||
|
Title = 0x8000,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SpaSection : uint16_t {
|
||||||
|
kMetadata = 0x1,
|
||||||
|
kImage = 0x2,
|
||||||
|
kStringTable = 0x3,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class GpdSection : uint16_t {
|
||||||
|
kAchievement = 0x1,
|
||||||
|
kImage = 0x2,
|
||||||
|
kSetting = 0x3,
|
||||||
|
kTitle = 0x4,
|
||||||
|
kString = 0x5,
|
||||||
|
kSecurity = 0x6
|
||||||
|
};
|
||||||
|
|
||||||
|
// Found by dumping the kSectionStringTable sections of various games:
|
||||||
|
enum class Locale : uint32_t {
|
||||||
|
kUnknown = 0,
|
||||||
|
kEnglish = 1,
|
||||||
|
kJapanese = 2,
|
||||||
|
kGerman = 3,
|
||||||
|
kFrench = 4,
|
||||||
|
kSpanish = 5,
|
||||||
|
kItalian = 6,
|
||||||
|
kKorean = 7,
|
||||||
|
kChinese = 8,
|
||||||
|
};
|
||||||
|
|
||||||
|
inline std::wstring ReadNullTermString(const wchar_t* ptr) {
|
||||||
|
std::wstring retval;
|
||||||
|
wchar_t data = xe::byte_swap(*ptr);
|
||||||
|
while (data != 0) {
|
||||||
|
retval += data;
|
||||||
|
ptr++;
|
||||||
|
data = xe::byte_swap(*ptr);
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TitlePlayed {
|
||||||
|
uint32_t title_id = 0;
|
||||||
|
std::wstring title_name;
|
||||||
|
uint32_t achievements_possible = 0;
|
||||||
|
uint32_t achievements_earned = 0;
|
||||||
|
uint32_t gamerscore_total = 0;
|
||||||
|
uint32_t gamerscore_earned = 0;
|
||||||
|
uint16_t reserved_achievement_count = 0;
|
||||||
|
uint16_t all_avatar_awards = 0;
|
||||||
|
uint16_t male_avatar_awards = 0;
|
||||||
|
uint16_t female_avatar_awards = 0;
|
||||||
|
uint32_t reserved_flags = 0;
|
||||||
|
uint64_t last_played = 0;
|
||||||
|
|
||||||
|
void ReadGPD(const X_XDBF_GPD_TITLEPLAYED* src) {
|
||||||
|
title_id = src->title_id;
|
||||||
|
achievements_possible = src->achievements_possible;
|
||||||
|
achievements_earned = src->achievements_earned;
|
||||||
|
gamerscore_total = src->gamerscore_total;
|
||||||
|
gamerscore_earned = src->gamerscore_earned;
|
||||||
|
reserved_achievement_count = src->reserved_achievement_count;
|
||||||
|
all_avatar_awards = src->all_avatar_awards;
|
||||||
|
male_avatar_awards = src->male_avatar_awards;
|
||||||
|
female_avatar_awards = src->female_avatar_awards;
|
||||||
|
reserved_flags = src->reserved_flags;
|
||||||
|
last_played = src->last_played;
|
||||||
|
|
||||||
|
auto* txt_ptr = reinterpret_cast<const uint8_t*>(src + 1);
|
||||||
|
title_name = ReadNullTermString((const wchar_t*)txt_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteGPD(X_XDBF_GPD_TITLEPLAYED* dest) {
|
||||||
|
dest->title_id = title_id;
|
||||||
|
dest->achievements_possible = achievements_possible;
|
||||||
|
dest->achievements_earned = achievements_earned;
|
||||||
|
dest->gamerscore_total = gamerscore_total;
|
||||||
|
dest->gamerscore_earned = gamerscore_earned;
|
||||||
|
dest->reserved_achievement_count = reserved_achievement_count;
|
||||||
|
dest->all_avatar_awards = all_avatar_awards;
|
||||||
|
dest->male_avatar_awards = male_avatar_awards;
|
||||||
|
dest->female_avatar_awards = female_avatar_awards;
|
||||||
|
dest->reserved_flags = reserved_flags;
|
||||||
|
dest->last_played = last_played;
|
||||||
|
|
||||||
|
auto* txt_ptr = reinterpret_cast<const uint8_t*>(dest + 1);
|
||||||
|
xe::copy_and_swap<wchar_t>((wchar_t*)txt_ptr, title_name.c_str(),
|
||||||
|
title_name.size());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class AchievementType : uint32_t {
|
||||||
|
kCompletion = 1,
|
||||||
|
kLeveling = 2,
|
||||||
|
kUnlock = 3,
|
||||||
|
kEvent = 4,
|
||||||
|
kTournament = 5,
|
||||||
|
kCheckpoint = 6,
|
||||||
|
kOther = 7,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class AchievementFlags : uint32_t {
|
||||||
|
kTypeMask = 0x7,
|
||||||
|
kShowUnachieved = 0x8,
|
||||||
|
kAchievedOnline = 0x10000,
|
||||||
|
kAchieved = 0x20000
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Achievement {
|
||||||
|
uint16_t id = 0;
|
||||||
|
std::wstring label;
|
||||||
|
std::wstring description;
|
||||||
|
std::wstring unachieved_desc;
|
||||||
|
uint32_t image_id = 0;
|
||||||
|
uint32_t gamerscore = 0;
|
||||||
|
uint32_t flags = 0;
|
||||||
|
uint64_t unlock_time = 0;
|
||||||
|
|
||||||
|
AchievementType GetType() {
|
||||||
|
return static_cast<AchievementType>(
|
||||||
|
flags & static_cast<uint32_t>(AchievementFlags::kTypeMask));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsUnlocked() {
|
||||||
|
return flags & static_cast<uint32_t>(AchievementFlags::kAchieved);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsUnlockedOnline() {
|
||||||
|
return flags & static_cast<uint32_t>(AchievementFlags::kAchievedOnline);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Unlock(bool online = false) {
|
||||||
|
flags |= static_cast<uint32_t>(AchievementFlags::kAchieved);
|
||||||
|
if (online) {
|
||||||
|
flags |= static_cast<uint32_t>(AchievementFlags::kAchievedOnline);
|
||||||
|
}
|
||||||
|
|
||||||
|
unlock_time = Clock::QueryHostSystemTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lock() {
|
||||||
|
flags = flags & ~(static_cast<uint32_t>(AchievementFlags::kAchieved));
|
||||||
|
flags = flags & ~(static_cast<uint32_t>(AchievementFlags::kAchievedOnline));
|
||||||
|
unlock_time = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReadGPD(const X_XDBF_GPD_ACHIEVEMENT* src) {
|
||||||
|
id = src->id;
|
||||||
|
image_id = src->image_id;
|
||||||
|
gamerscore = src->gamerscore;
|
||||||
|
flags = src->flags;
|
||||||
|
unlock_time = src->unlock_time;
|
||||||
|
|
||||||
|
auto* txt_ptr = reinterpret_cast<const uint8_t*>(src + 1);
|
||||||
|
|
||||||
|
label = ReadNullTermString((const wchar_t*)txt_ptr);
|
||||||
|
|
||||||
|
txt_ptr += (label.length() * 2) + 2;
|
||||||
|
description = ReadNullTermString((const wchar_t*)txt_ptr);
|
||||||
|
|
||||||
|
txt_ptr += (description.length() * 2) + 2;
|
||||||
|
unachieved_desc = ReadNullTermString((const wchar_t*)txt_ptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Entry {
|
||||||
|
X_XDBF_ENTRY info;
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parses/creates an XDBF (XboxDataBaseFormat) file
|
||||||
|
// http://www.free60.org/wiki/XDBF
|
||||||
|
class XdbfFile {
|
||||||
|
public:
|
||||||
|
XdbfFile() {
|
||||||
|
header_.magic = 'XDBF';
|
||||||
|
header_.version = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Read(const uint8_t* data, size_t data_size);
|
||||||
|
bool Write(uint8_t* data, size_t* data_size);
|
||||||
|
|
||||||
|
Entry* GetEntry(uint16_t section, uint64_t id) const;
|
||||||
|
|
||||||
|
// Updates (or adds) an entry
|
||||||
|
bool UpdateEntry(Entry entry);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
X_XDBF_HEADER header_;
|
||||||
|
std::vector<Entry> entries_;
|
||||||
|
std::vector<X_XDBF_FILELOC> free_entries_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SpaFile : public XdbfFile {
|
||||||
|
public:
|
||||||
|
std::string GetStringTableEntry(Locale locale, uint16_t string_id) const;
|
||||||
|
|
||||||
|
uint32_t GetAchievements(Locale locale,
|
||||||
|
std::vector<Achievement>* achievements) const;
|
||||||
|
|
||||||
|
Entry* GetIcon() const;
|
||||||
|
Locale GetDefaultLocale() const;
|
||||||
|
std::string GetTitleName() const;
|
||||||
|
uint32_t GetTitleId() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GpdFile : public XdbfFile {
|
||||||
|
public:
|
||||||
|
GpdFile() : title_id_(-1) {}
|
||||||
|
GpdFile(uint32_t title_id) : title_id_(title_id) {}
|
||||||
|
|
||||||
|
bool GetAchievement(uint16_t id, Achievement* dest);
|
||||||
|
uint32_t GetAchievements(std::vector<Achievement>* achievements) const;
|
||||||
|
|
||||||
|
bool GetTitle(uint32_t title_id, TitlePlayed* title);
|
||||||
|
uint32_t GetTitles(std::vector<TitlePlayed>* titles) const;
|
||||||
|
|
||||||
|
// Updates (or adds) an achievement
|
||||||
|
bool UpdateAchievement(Achievement ach);
|
||||||
|
|
||||||
|
// Updates (or adds) a title
|
||||||
|
bool UpdateTitle(TitlePlayed title);
|
||||||
|
|
||||||
|
uint32_t GetTitleId() { return title_id_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t title_id_ = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace xdbf
|
||||||
|
} // namespace xam
|
||||||
|
} // namespace kernel
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_KERNEL_XAM_XDBF_XDBF_H_
|
|
@ -0,0 +1,137 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2016 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_KERNEL_XAM_XDBF_XDBF_XBOX_H_
|
||||||
|
#define XENIA_KERNEL_XAM_XDBF_XDBF_XBOX_H_
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace kernel {
|
||||||
|
namespace xam {
|
||||||
|
namespace xdbf {
|
||||||
|
|
||||||
|
/* Native XDBF structs used by 360 are in this file */
|
||||||
|
|
||||||
|
struct XdbfStringTableEntry {
|
||||||
|
xe::be<uint16_t> id;
|
||||||
|
xe::be<uint16_t> string_length;
|
||||||
|
};
|
||||||
|
static_assert_size(XdbfStringTableEntry, 4);
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct X_XDBF_HEADER {
|
||||||
|
xe::be<uint32_t> magic;
|
||||||
|
xe::be<uint32_t> version;
|
||||||
|
xe::be<uint32_t> entry_count;
|
||||||
|
xe::be<uint32_t> entry_used;
|
||||||
|
xe::be<uint32_t> free_count;
|
||||||
|
xe::be<uint32_t> free_used;
|
||||||
|
};
|
||||||
|
static_assert_size(X_XDBF_HEADER, 24);
|
||||||
|
|
||||||
|
struct X_XDBF_ENTRY {
|
||||||
|
xe::be<uint16_t> section;
|
||||||
|
xe::be<uint64_t> id;
|
||||||
|
xe::be<uint32_t> offset;
|
||||||
|
xe::be<uint32_t> size;
|
||||||
|
};
|
||||||
|
static_assert_size(X_XDBF_ENTRY, 18);
|
||||||
|
|
||||||
|
struct X_XDBF_FILELOC {
|
||||||
|
xe::be<uint32_t> offset;
|
||||||
|
xe::be<uint32_t> size;
|
||||||
|
};
|
||||||
|
static_assert_size(X_XDBF_FILELOC, 8);
|
||||||
|
|
||||||
|
struct X_XDBF_XSTC_DATA {
|
||||||
|
xe::be<uint32_t> magic;
|
||||||
|
xe::be<uint32_t> version;
|
||||||
|
xe::be<uint32_t> size;
|
||||||
|
xe::be<uint32_t> default_language;
|
||||||
|
};
|
||||||
|
static_assert_size(X_XDBF_XSTC_DATA, 16);
|
||||||
|
|
||||||
|
struct X_XDBF_XTHD_DATA {
|
||||||
|
xe::be<uint32_t> magic;
|
||||||
|
xe::be<uint32_t> version;
|
||||||
|
xe::be<uint32_t> unk8;
|
||||||
|
xe::be<uint32_t> title_id;
|
||||||
|
xe::be<uint32_t> unk10; // always 1?
|
||||||
|
xe::be<uint16_t> title_version_major;
|
||||||
|
xe::be<uint16_t> title_version_minor;
|
||||||
|
xe::be<uint16_t> title_version_build;
|
||||||
|
xe::be<uint16_t> title_version_revision;
|
||||||
|
xe::be<uint32_t> unk1C;
|
||||||
|
xe::be<uint32_t> unk20;
|
||||||
|
xe::be<uint32_t> unk24;
|
||||||
|
xe::be<uint32_t> unk28;
|
||||||
|
};
|
||||||
|
static_assert_size(X_XDBF_XTHD_DATA, 0x2C);
|
||||||
|
|
||||||
|
struct X_XDBF_TABLE_HEADER {
|
||||||
|
xe::be<uint32_t> magic;
|
||||||
|
xe::be<uint32_t> version;
|
||||||
|
xe::be<uint32_t> size;
|
||||||
|
xe::be<uint16_t> count;
|
||||||
|
};
|
||||||
|
static_assert_size(X_XDBF_TABLE_HEADER, 14);
|
||||||
|
|
||||||
|
struct X_XDBF_SPA_ACHIEVEMENT {
|
||||||
|
xe::be<uint16_t> id;
|
||||||
|
xe::be<uint16_t> label_id;
|
||||||
|
xe::be<uint16_t> description_id;
|
||||||
|
xe::be<uint16_t> unachieved_id;
|
||||||
|
xe::be<uint32_t> image_id;
|
||||||
|
xe::be<uint16_t> gamerscore;
|
||||||
|
xe::be<uint16_t> unkE;
|
||||||
|
xe::be<uint32_t> flags;
|
||||||
|
xe::be<uint32_t> unk14;
|
||||||
|
xe::be<uint32_t> unk18;
|
||||||
|
xe::be<uint32_t> unk1C;
|
||||||
|
xe::be<uint32_t> unk20;
|
||||||
|
};
|
||||||
|
static_assert_size(X_XDBF_SPA_ACHIEVEMENT, 0x24);
|
||||||
|
|
||||||
|
struct X_XDBF_GPD_ACHIEVEMENT {
|
||||||
|
xe::be<uint32_t> magic;
|
||||||
|
xe::be<uint32_t> id;
|
||||||
|
xe::be<uint32_t> image_id;
|
||||||
|
xe::be<uint32_t> gamerscore;
|
||||||
|
xe::be<uint32_t> flags;
|
||||||
|
xe::be<uint64_t> unlock_time;
|
||||||
|
// wchar_t* title;
|
||||||
|
// wchar_t* description;
|
||||||
|
// wchar_t* unlocked_description;
|
||||||
|
};
|
||||||
|
|
||||||
|
// from https://github.com/xemio/testdev/blob/master/xkelib/xam/_xamext.h
|
||||||
|
struct X_XDBF_GPD_TITLEPLAYED {
|
||||||
|
xe::be<uint32_t> title_id;
|
||||||
|
xe::be<uint32_t> achievements_possible;
|
||||||
|
xe::be<uint32_t> achievements_earned;
|
||||||
|
xe::be<uint32_t> gamerscore_total;
|
||||||
|
xe::be<uint32_t> gamerscore_earned;
|
||||||
|
xe::be<uint16_t> reserved_achievement_count;
|
||||||
|
|
||||||
|
// the following are meant to be split into possible/earned, 1 byte each
|
||||||
|
// but who cares
|
||||||
|
xe::be<uint16_t> all_avatar_awards;
|
||||||
|
xe::be<uint16_t> male_avatar_awards;
|
||||||
|
xe::be<uint16_t> female_avatar_awards;
|
||||||
|
xe::be<uint32_t> reserved_flags;
|
||||||
|
xe::be<uint64_t> last_played;
|
||||||
|
// wchar_t* title_name;
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
} // namespace xdbf
|
||||||
|
} // namespace xam
|
||||||
|
} // namespace kernel
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_KERNEL_XAM_XDBF_XDBF_XBOX_H_
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/kernel/kernel_state.h"
|
#include "xenia/kernel/kernel_state.h"
|
||||||
|
#include "xenia/kernel/util/crypto_utils.h"
|
||||||
#include "xenia/kernel/util/shim_utils.h"
|
#include "xenia/kernel/util/shim_utils.h"
|
||||||
#include "xenia/kernel/xboxkrnl/xboxkrnl_private.h"
|
#include "xenia/kernel/xboxkrnl/xboxkrnl_private.h"
|
||||||
#include "xenia/xbox.h"
|
#include "xenia/xbox.h"
|
||||||
|
@ -433,75 +434,17 @@ void XeCryptHmacSha(lpvoid_t key, dword_t key_size_in, lpvoid_t inp_1,
|
||||||
dword_t inp_1_size, lpvoid_t inp_2, dword_t inp_2_size,
|
dword_t inp_1_size, lpvoid_t inp_2, dword_t inp_2_size,
|
||||||
lpvoid_t inp_3, dword_t inp_3_size, lpvoid_t out,
|
lpvoid_t inp_3, dword_t inp_3_size, lpvoid_t out,
|
||||||
dword_t out_size) {
|
dword_t out_size) {
|
||||||
uint32_t key_size = key_size_in;
|
util::HmacSha(key, key_size_in, inp_1, inp_1_size, inp_2, inp_2_size, inp_3,
|
||||||
sha1::SHA1 sha;
|
inp_3_size, out, out_size);
|
||||||
uint8_t kpad_i[0x40];
|
|
||||||
uint8_t kpad_o[0x40];
|
|
||||||
uint8_t tmp_key[0x40];
|
|
||||||
std::memset(kpad_i, 0x36, 0x40);
|
|
||||||
std::memset(kpad_o, 0x5C, 0x40);
|
|
||||||
|
|
||||||
// Setup HMAC key
|
|
||||||
// If > block size, use its hash
|
|
||||||
if (key_size > 0x40) {
|
|
||||||
sha1::SHA1 sha_key;
|
|
||||||
sha_key.processBytes(key, key_size);
|
|
||||||
sha_key.finalize((uint8_t*)tmp_key);
|
|
||||||
|
|
||||||
key_size = 0x14u;
|
|
||||||
} else {
|
|
||||||
std::memcpy(tmp_key, key, key_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < key_size; i++) {
|
|
||||||
kpad_i[i] = tmp_key[i] ^ 0x36;
|
|
||||||
kpad_o[i] = tmp_key[i] ^ 0x5C;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inner
|
|
||||||
sha.processBytes(kpad_i, 0x40);
|
|
||||||
|
|
||||||
if (inp_1_size) {
|
|
||||||
sha.processBytes(inp_1, inp_1_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inp_2_size) {
|
|
||||||
sha.processBytes(inp_2, inp_2_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inp_3_size) {
|
|
||||||
sha.processBytes(inp_3, inp_3_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t digest[0x14];
|
|
||||||
sha.finalize(digest);
|
|
||||||
sha.reset();
|
|
||||||
|
|
||||||
// Outer
|
|
||||||
sha.processBytes(kpad_o, 0x40);
|
|
||||||
sha.processBytes(digest, 0x14);
|
|
||||||
sha.finalize(digest);
|
|
||||||
|
|
||||||
std::memcpy(out, digest, std::min((uint32_t)out_size, 0x14u));
|
|
||||||
}
|
}
|
||||||
DECLARE_XBOXKRNL_EXPORT1(XeCryptHmacSha, kNone, kImplemented);
|
DECLARE_XBOXKRNL_EXPORT1(XeCryptHmacSha, kNone, kImplemented);
|
||||||
|
|
||||||
// Keys
|
|
||||||
// TODO: Array of keys we need
|
|
||||||
|
|
||||||
// Retail key 0x19
|
|
||||||
static const uint8_t key19[] = {0xE1, 0xBC, 0x15, 0x9C, 0x73, 0xB1, 0xEA, 0xE9,
|
|
||||||
0xAB, 0x31, 0x70, 0xF3, 0xAD, 0x47, 0xEB, 0xF3};
|
|
||||||
|
|
||||||
dword_result_t XeKeysHmacSha(dword_t key_num, lpvoid_t inp_1,
|
dword_result_t XeKeysHmacSha(dword_t key_num, lpvoid_t inp_1,
|
||||||
dword_t inp_1_size, lpvoid_t inp_2,
|
dword_t inp_1_size, lpvoid_t inp_2,
|
||||||
dword_t inp_2_size, lpvoid_t inp_3,
|
dword_t inp_2_size, lpvoid_t inp_3,
|
||||||
dword_t inp_3_size, lpvoid_t out,
|
dword_t inp_3_size, lpvoid_t out,
|
||||||
dword_t out_size) {
|
dword_t out_size) {
|
||||||
const uint8_t* key = nullptr;
|
const uint8_t* key = util::GetXeKey(key_num);
|
||||||
if (key_num == 0x19) {
|
|
||||||
key = key19;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key) {
|
if (key) {
|
||||||
XeCryptHmacSha((void*)key, 0x10, inp_1, inp_1_size, inp_2, inp_2_size,
|
XeCryptHmacSha((void*)key, 0x10, inp_1, inp_1_size, inp_2, inp_2_size,
|
||||||
|
|
Loading…
Reference in New Issue