From 71085aae771a6ea0b383cf79c2f3dc3026d6cc3e Mon Sep 17 00:00:00 2001 From: Cancerous Date: Thu, 5 Sep 2019 05:24:42 -0400 Subject: [PATCH] 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 --- src/xenia/emulator.cc | 18 +- src/xenia/kernel/util/crypto_utils.cc | 130 ++++++ src/xenia/kernel/util/crypto_utils.h | 28 ++ src/xenia/kernel/util/xdbf_utils.cc | 107 ----- src/xenia/kernel/util/xdbf_utils.h | 146 ------ src/xenia/kernel/xam/apps/xgi_app.cc | 48 +- src/xenia/kernel/xam/apps/xlivebase_app.cc | 20 +- src/xenia/kernel/xam/user_profile.cc | 432 ++++++++++++++++- src/xenia/kernel/xam/user_profile.h | 137 +++++- src/xenia/kernel/xam/xam_info.cc | 4 +- src/xenia/kernel/xam/xam_input.cc | 2 +- src/xenia/kernel/xam/xam_locale.cc | 83 ++-- src/xenia/kernel/xam/xam_msg.cc | 5 + src/xenia/kernel/xam/xam_net.cc | 2 +- src/xenia/kernel/xam/xam_notify.cc | 17 +- src/xenia/kernel/xam/xam_user.cc | 171 ++++++- src/xenia/kernel/xam/xdbf/xdbf.cc | 435 ++++++++++++++++++ src/xenia/kernel/xam/xdbf/xdbf.h | 269 +++++++++++ src/xenia/kernel/xam/xdbf/xdbf_xbox.h | 137 ++++++ src/xenia/kernel/xboxkrnl/xboxkrnl_crypt.cc | 1 + src/xenia/kernel/xboxkrnl/xboxkrnl_debug.cc | 3 +- .../kernel/xboxkrnl/xboxkrnl_threading.cc | 3 + src/xenia/kernel/xobject.cc | 2 +- src/xenia/kernel/xobject.h | 2 +- 24 files changed, 1846 insertions(+), 356 deletions(-) create mode 100644 src/xenia/kernel/util/crypto_utils.cc create mode 100644 src/xenia/kernel/util/crypto_utils.h delete mode 100644 src/xenia/kernel/util/xdbf_utils.cc delete mode 100644 src/xenia/kernel/util/xdbf_utils.h create mode 100644 src/xenia/kernel/xam/xdbf/xdbf.cc create mode 100644 src/xenia/kernel/xam/xdbf/xdbf.h create mode 100644 src/xenia/kernel/xam/xdbf/xdbf_xbox.h diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 3f7e06555..b3edd18c5 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -33,8 +33,8 @@ #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/user_module.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/xdbf/xdbf.h" #include "xenia/kernel/xbdm/xbdm_module.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_module.h" #include "xenia/memory.h" @@ -670,6 +670,7 @@ X_STATUS Emulator::CompleteLaunch(const std::wstring& path, module->GetOptHeader(XEX_HEADER_EXECUTION_INFO, &info); if (info) { title_id_ = info->title_id; + xe::LogLineFormat(xe::LogLevel::Error, 'i', "Title ID : %.8X\n", title_id_); } // Try and load the resource database (xex only). @@ -681,13 +682,16 @@ X_STATUS Emulator::CompleteLaunch(const std::wstring& path, uint32_t resource_size = 0; if (XSUCCEEDED( module->GetSection(title_id, &resource_data, &resource_size))) { - kernel::util::XdbfGameData db( - module->memory()->TranslateVirtual(resource_data), resource_size); - if (db.is_valid()) { - game_title_ = xe::to_wstring(db.title()); - auto icon_block = db.icon(); + kernel::xam::xdbf::SpaFile spa; + if (spa.Read(module->memory()->TranslateVirtual(resource_data), + resource_size)) { + // Set title SPA and get title name/icon + kernel_state_->user_profile()->SetTitleSpaData(spa); + game_title_ = xe::to_wstring(spa.GetTitleName()); + auto icon_block = spa.GetIcon(); if (icon_block) { - display_window_->SetIcon(icon_block.buffer, icon_block.size); + display_window_->SetIcon(icon_block->data.data(), + icon_block->data.size()); } } } diff --git a/src/xenia/kernel/util/crypto_utils.cc b/src/xenia/kernel/util/crypto_utils.cc new file mode 100644 index 000000000..9c08441cf --- /dev/null +++ b/src/xenia/kernel/util/crypto_utils.cc @@ -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 + +#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 diff --git a/src/xenia/kernel/util/crypto_utils.h b/src/xenia/kernel/util/crypto_utils.h new file mode 100644 index 000000000..456b7cff7 --- /dev/null +++ b/src/xenia/kernel/util/crypto_utils.h @@ -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 diff --git a/src/xenia/kernel/util/xdbf_utils.cc b/src/xenia/kernel/util/xdbf_utils.cc deleted file mode 100644 index e79b15449..000000000 --- a/src/xenia/kernel/util/xdbf_utils.cc +++ /dev/null @@ -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(ptr); - ptr += sizeof(XbdfHeader); - if (header_->magic != kXdbfMagicXdbf) { - data_ = nullptr; - return; - } - - entries_ = reinterpret_cast(ptr); - ptr += sizeof(XbdfEntry) * header_->entry_count; - - files_ = reinterpret_cast(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(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(locale)); - if (!language_block) { - return ""; - } - - auto xstr_head = - reinterpret_cast(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(ptr); - ptr += sizeof(XdbfStringTableEntry); - if (entry->id == string_id) { - return std::string(reinterpret_cast(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(block.buffer); - assert_true(xstc->magic == kXdbfMagicXstc); - return static_cast(static_cast(xstc->default_language)); -} - -std::string XdbfGameData::title() const { - return GetStringTableEntry(default_language(), kXdbfIdTitle); -} - -} // namespace util -} // namespace kernel -} // namespace xe diff --git a/src/xenia/kernel/util/xdbf_utils.h b/src/xenia/kernel/util/xdbf_utils.h deleted file mode 100644 index 83ba12e27..000000000 --- a/src/xenia/kernel/util/xdbf_utils.h +++ /dev/null @@ -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 -#include - -#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 magic; - xe::be version; - xe::be entry_count; - xe::be entry_used; - xe::be free_count; - xe::be free_used; - }; - static_assert_size(XbdfHeader, 24); - - struct XbdfEntry { - xe::be section; - xe::be id; - xe::be offset; - xe::be size; - }; - static_assert_size(XbdfEntry, 18); - - struct XbdfFileLoc { - xe::be offset; - xe::be size; - }; - static_assert_size(XbdfFileLoc, 8); - - struct XdbfXstc { - xe::be magic; - xe::be version; - xe::be size; - xe::be default_language; - }; - static_assert_size(XdbfXstc, 16); - - struct XdbfXstrHeader { - xe::be magic; - xe::be version; - xe::be size; - xe::be string_count; - }; - static_assert_size(XdbfXstrHeader, 14); - - struct XdbfStringTableEntry { - xe::be id; - xe::be 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_ diff --git a/src/xenia/kernel/xam/apps/xgi_app.cc b/src/xenia/kernel/xam/apps/xgi_app.cc index c232dacd0..88fcd573c 100644 --- a/src/xenia/kernel/xam/apps/xgi_app.cc +++ b/src/xenia/kernel/xam/apps/xgi_app.cc @@ -17,6 +17,11 @@ namespace kernel { namespace xam { namespace apps { +struct X_XUSER_ACHIEVEMENT { + xe::be user_idx; + xe::be achievement_id; +}; + XgiApp::XgiApp(KernelState* kernel_state) : App(kernel_state, 0xFB) {} // 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(buffer + 4); XELOGD("XGIUserWriteAchievements(%.8X, %.8X)", achievement_count, 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; } case 0x000B0010: { @@ -78,8 +109,11 @@ X_RESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr, return X_STATUS_SUCCESS; } case 0x000B0011: { - // TODO(DrChat): Figure out what this is again - } break; + // TODO(PermaNull): reverse buffer contents. + // TEST + XELOGD("XGISessionDelete"); + return X_STATUS_SUCCESS; + } case 0x000B0012: { assert_true(buffer_length == 0x14); uint32_t session_ptr = xe::load_and_swap(buffer + 0x0); @@ -93,6 +127,16 @@ X_RESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr, user_count, unk_0, user_index_array, private_slots_array); return X_STATUS_SUCCESS; } + case 0x000B0014: { + // TEST Gets Jetpac XBLA in game + XELOGD("XGI_unknown"); + return X_STATUS_SUCCESS; + } + case 0x000B0015: { + // TEST Gets Jetpac XBLA in game + XELOGD("XGI_unknown"); + return X_STATUS_SUCCESS; + } case 0x000B0041: { assert_true(!buffer_length || buffer_length == 32); // 00000000 2789fecc 00000000 00000000 200491e0 00000000 200491f0 20049340 diff --git a/src/xenia/kernel/xam/apps/xlivebase_app.cc b/src/xenia/kernel/xam/apps/xlivebase_app.cc index 76149d906..75700a445 100644 --- a/src/xenia/kernel/xam/apps/xlivebase_app.cc +++ b/src/xenia/kernel/xam/apps/xlivebase_app.cc @@ -41,18 +41,30 @@ X_RESULT XLiveBaseApp::DispatchMessageSync(uint32_t message, xe::store_and_swap(buffer + 0, 1); // XONLINE_NAT_OPEN return X_ERROR_SUCCESS; } + case 0x00058007: { + /* + Occurs if title calls XOnlineGetServiceInfo, expects dwServiceId + and pServiceInfo. pServiceInfo should contain pointer to + XONLINE_SERVICE_INFO structure. + */ + XELOGD("CXLiveLogon::GetServiceInfo(%.8X, %.8X)", buffer_ptr, + buffer_length); + return 1229; // ERROR_CONNECTION_INVALID + } case 0x00058020: { // 0x00058004 is called right before this. // We should create a XamEnumerate-able empty list here, but I'm not // sure of the format. // buffer_length seems to be the same ptr sent to 0x00058004. - XELOGD("XLiveBaseFriendsCreateEnumerator(%.8X, %.8X) unimplemented", - buffer_ptr, buffer_length); + XELOGD("CXLiveFriends::Enumerate(%.8X, %.8X) unimplemented", buffer_ptr, + buffer_length); return X_STATUS_UNSUCCESSFUL; } case 0x00058023: { - XELOGD("XliveBaseUnk58023(%.8X, %.8X) unimplemented", buffer_ptr, - buffer_length); + XELOGD( + "CXLiveMessaging::XMessageGameInviteGetAcceptedInfo(%.8X, %.8X) " + "unimplemented", + buffer_ptr, buffer_length); return X_STATUS_UNSUCCESSFUL; } case 0x00058046: { diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc index 6471b3d1e..1eaf5947b 100644 --- a/src/xenia/kernel/xam/user_profile.cc +++ b/src/xenia/kernel/xam/user_profile.cc @@ -11,15 +11,100 @@ #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/util/shim_utils.h" +#include "xenia/base/cvar.h" +#include "xenia/base/clock.h" +#include "xenia/base/filesystem.h" +#include "xenia/base/logging.h" +#include "xenia/base/mapped_memory.h" +#include "xenia/kernel/util/crypto_utils.h" #include "xenia/kernel/xam/user_profile.h" namespace xe { namespace kernel { namespace xam { -UserProfile::UserProfile() { - xuid_ = 0xBABEBABEBABEBABE; - name_ = "User"; +DEFINE_string(profile_directory, "Content\\Profile\\", + "The directory to store profile data inside", "Kernel"); + +constexpr uint32_t kDashboardID = 0xFFFE07D1; + +std::string X_XAMACCOUNTINFO::GetGamertagString() const { + return xe::to_string(std::wstring(gamertag)); +} + +bool UserProfile::DecryptAccountFile(const uint8_t* data, + X_XAMACCOUNTINFO* output, bool devkit) { + const uint8_t* key = util::GetXeKey(0x19, devkit); + if (!key) { + return false; // this shouldn't happen... + } + + // Generate RC4 key from data hash + uint8_t rc4_key[0x14]; + util::HmacSha(key, 0x10, data, 0x10, 0, 0, 0, 0, rc4_key, 0x14); + + uint8_t dec_data[sizeof(X_XAMACCOUNTINFO) + 8]; + + // Decrypt data + util::RC4(rc4_key, 0x10, data + 0x10, sizeof(dec_data), dec_data, + sizeof(dec_data)); + + // Verify decrypted data against hash + uint8_t data_hash[0x14]; + util::HmacSha(key, 0x10, dec_data, sizeof(dec_data), 0, 0, 0, 0, data_hash, + 0x14); + + if (std::memcmp(data, data_hash, 0x10) == 0) { + // Copy account data to output + std::memcpy(output, dec_data + 8, sizeof(X_XAMACCOUNTINFO)); + + // Swap gamertag endian + xe::copy_and_swap(output->gamertag, output->gamertag, 0x10); + return true; + } + + return false; +} + +void UserProfile::EncryptAccountFile(const X_XAMACCOUNTINFO* input, + uint8_t* output, bool devkit) { + const uint8_t* key = util::GetXeKey(0x19, devkit); + if (!key) { + return; // this shouldn't happen... + } + + X_XAMACCOUNTINFO* output_acct = (X_XAMACCOUNTINFO*)(output + 0x18); + std::memcpy(output_acct, input, sizeof(X_XAMACCOUNTINFO)); + + // Swap gamertag endian + xe::copy_and_swap(output_acct->gamertag, output_acct->gamertag, + 0x10); + + // Set confounder, should be random but meh + std::memset(output + 0x10, 0xFD, 8); + + // Encrypted data = xam account info + 8 byte confounder + uint32_t enc_data_size = sizeof(X_XAMACCOUNTINFO) + 8; + + // Set data hash + uint8_t data_hash[0x14]; + util::HmacSha(key, 0x10, output + 0x10, enc_data_size, 0, 0, 0, 0, data_hash, + 0x14); + + std::memcpy(output, data_hash, 0x10); + + // Generate RC4 key from data hash + uint8_t rc4_key[0x14]; + util::HmacSha(key, 0x10, data_hash, 0x10, 0, 0, 0, 0, rc4_key, 0x14); + + // Encrypt data + util::RC4(rc4_key, 0x10, output + 0x10, enc_data_size, output + 0x10, + enc_data_size); +} + +UserProfile::UserProfile() : dash_gpd_(kDashboardID) { + account_.xuid_online = 0xE000BABEBABEBABE; + wcscpy_s(account_.gamertag, L"XeniaUser"); // https://cs.rin.ru/forum/viewtopic.php?f=38&t=60668&hilit=gfwl+live&start=195 // https://github.com/arkem/py360/blob/master/py360/constants.py @@ -85,6 +170,347 @@ UserProfile::UserProfile() { AddSetting(std::make_unique(0x63E83FFE)); // XPROFILE_TITLE_SPECIFIC3 AddSetting(std::make_unique(0x63E83FFD)); + + // Try loading profile GPD files... + LoadProfile(); +} + +void UserProfile::LoadProfile() { + auto mmap_ = + MappedMemory::Open(xe::to_wstring(cvars::profile_directory) + L"Account", + MappedMemory::Mode::kRead); + if (mmap_) { + XELOGI("Loading Account file from path %SAccount", + xe::to_wstring(cvars::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", xe::to_wstring(cvars::profile_directory).c_str()); + + mmap_ = MappedMemory::Open( + xe::to_wstring(cvars::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 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(cvars::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 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(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(xdbf::SpaSection::kImage), + static_cast(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(xdbf::GpdSection::kString); + title_name_ent.info.id = static_cast(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 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& 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(cvars::profile_directory))) { + filesystem::CreateFolder(xe::to_wstring(cvars::profile_directory)); + } + + wchar_t fname[256]; + _swprintf(fname, L"%X.gpd", title_id); + + filesystem::CreateFile(xe::to_wstring(cvars::profile_directory) + fname); + auto mmap_ = + MappedMemory::Open(xe::to_wstring(cvars::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 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) { diff --git a/src/xenia/kernel/xam/user_profile.h b/src/xenia/kernel/xam/user_profile.h index f8f2dacd8..c30c838b3 100644 --- a/src/xenia/kernel/xam/user_profile.h +++ b/src/xenia/kernel/xam/user_profile.h @@ -15,12 +15,118 @@ #include #include +#include "xenia/kernel/xam/xdbf/xdbf.h" #include "xenia/xbox.h" namespace xe { namespace kernel { namespace xam { +// from https://github.com/xemio/testdev/blob/master/xkelib/xam/_xamext.h +#pragma pack(push, 4) +struct X_XAMACCOUNTINFO { + enum AccountReservedFlags { + kPasswordProtected = 0x10000000, + kLiveEnabled = 0x20000000, + kRecovering = 0x40000000, + kVersionMask = 0x000000FF + }; + + enum AccountUserFlags { + kPaymentInstrumentCreditCard = 1, + + kCountryMask = 0xFF00, + kSubscriptionTierMask = 0xF00000, + kLanguageMask = 0x3E000000, + + kParentalControlEnabled = 0x1000000, + }; + + enum AccountSubscriptionTier { + kSubscriptionTierSilver = 3, + kSubscriptionTierGold = 6, + kSubscriptionTierFamilyGold = 9 + }; + + // already exists inside xdbf.h?? + enum AccountLanguage { + kNoLanguage, + kEnglish, + kJapanese, + kGerman, + kFrench, + kSpanish, + kItalian, + kKorean, + kTChinese, + kPortuguese, + kSChinese, + kPolish, + kRussian, + kNorwegian = 15 + }; + + enum AccountLiveFlags { kAcctRequiresManagement = 1 }; + + xe::be reserved_flags; + xe::be live_flags; + wchar_t gamertag[0x10]; + xe::be xuid_online; // 09.... + xe::be cached_user_flags; + xe::be network_id; + char passcode[4]; + char online_domain[0x14]; + char online_kerberos_realm[0x18]; + char online_key[0x10]; + char passport_membername[0x72]; + char passport_password[0x20]; + char owner_passport_membername[0x72]; + + bool IsPasscodeEnabled() { + return (bool)(reserved_flags & AccountReservedFlags::kPasswordProtected); + } + + bool IsLiveEnabled() { + return (bool)(reserved_flags & AccountReservedFlags::kLiveEnabled); + } + + bool IsRecovering() { + return (bool)(reserved_flags & AccountReservedFlags::kRecovering); + } + + bool IsPaymentInstrumentCreditCard() { + return (bool)(cached_user_flags & + AccountUserFlags::kPaymentInstrumentCreditCard); + } + + bool IsParentalControlled() { + return (bool)(cached_user_flags & + AccountUserFlags::kParentalControlEnabled); + } + + bool IsXUIDOffline() { return ((xuid_online >> 60) & 0xF) == 0xE; } + bool IsXUIDOnline() { return ((xuid_online >> 48) & 0xFFFF) == 0x9; } + bool IsXUIDValid() { return IsXUIDOffline() != IsXUIDOnline(); } + bool IsTeamXUID() { + return (xuid_online & 0xFF00000000000140) == 0xFE00000000000100; + } + + uint32_t GetCountry() { return (cached_user_flags & kCountryMask) >> 8; } + + AccountSubscriptionTier GetSubscriptionTier() { + return (AccountSubscriptionTier)( + (cached_user_flags & kSubscriptionTierMask) >> 20); + } + + AccountLanguage GetLanguage() { + return (AccountLanguage)((cached_user_flags & kLanguageMask) >> 25); + } + + std::string GetGamertagString() const; +}; +static_assert_size(X_XAMACCOUNTINFO, 0x17C); +#pragma pack(pop) + class UserProfile { public: struct Setting { @@ -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(); - uint64_t xuid() const { return xuid_; } - std::string name() const { return name_; } - uint32_t signin_state() const { return 1; } + uint64_t xuid() const { return account_.xuid_online; } + std::string name() const { return account_.GetGamertagString(); } + // uint32_t signin_state() const { return 1; } void AddSetting(std::unique_ptr setting); Setting* 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& titles); + + bool UpdateTitleGpd(uint32_t title_id = -1); + bool UpdateAllGpds(); + private: - uint64_t xuid_; - std::string name_; + void LoadProfile(); + bool UpdateGpd(uint32_t title_id, xdbf::GpdFile& gpd_data); + + X_XAMACCOUNTINFO account_; std::vector> setting_list_; std::unordered_map settings_; void LoadSetting(UserProfile::Setting*); void SaveSetting(UserProfile::Setting*); + + std::unordered_map title_gpds_; + xdbf::GpdFile dash_gpd_; + xdbf::GpdFile* curr_gpd_ = nullptr; + uint32_t curr_title_id_ = -1; }; } // namespace xam diff --git a/src/xenia/kernel/xam/xam_info.cc b/src/xenia/kernel/xam/xam_info.cc index dfb78654c..db882bacb 100644 --- a/src/xenia/kernel/xam/xam_info.cc +++ b/src/xenia/kernel/xam/xam_info.cc @@ -290,7 +290,9 @@ void XamLoaderLaunchTitle(lpstring_t raw_name, dword_t flags) { auto& loader_data = xam->loader_data(); loader_data.launch_flags = flags; - + XELOGI( + "XamLoaderLaunchTitle launching: (%S) with flags (%d)", + std::string(raw_name), flags); // Translate the launch path to a full path. if (raw_name && raw_name.value() == "") { loader_data.launch_path = "game:\\default.xex"; diff --git a/src/xenia/kernel/xam/xam_input.cc b/src/xenia/kernel/xam/xam_input.cc index 7aead257d..eb83d27a9 100644 --- a/src/xenia/kernel/xam/xam_input.cc +++ b/src/xenia/kernel/xam/xam_input.cc @@ -182,7 +182,7 @@ X_HRESULT_result_t XamUserGetDeviceContext(dword_t user_index, dword_t unk, // Games check the result - usually with some masking. // If this function fails they assume zero, so let's fail AND // set zero just to be safe. - *out_ptr = 0; + //*out_ptr = 0; if (!user_index || (user_index & 0xFF) == 0xFF) { return X_E_SUCCESS; } else { diff --git a/src/xenia/kernel/xam/xam_locale.cc b/src/xenia/kernel/xam/xam_locale.cc index 9cd5125df..1c6c465ce 100644 --- a/src/xenia/kernel/xam/xam_locale.cc +++ b/src/xenia/kernel/xam/xam_locale.cc @@ -17,12 +17,15 @@ #include "xenia/kernel/xthread.h" #include "xenia/xbox.h" -DECLARE_int32(user_country); - // TODO(gibbed): put these forward decls in a header somewhere. namespace xe { namespace kernel { +namespace xboxkrnl { +X_STATUS xeExGetXConfigSetting(uint16_t category, uint16_t setting, + void* buffer, uint16_t buffer_size, + uint16_t* required_size); +} // namespace xboxkrnl namespace xam { uint32_t xeXGetGameRegion(); } // namespace xam @@ -72,8 +75,7 @@ const wchar_t* xeXamGetOnlineCountryString(uint8_t id) { L"PG", L"PN", L"RE", L"RW", L"WS", L"SM", L"ST", L"SN", L"RS", L"SC", L"SL", L"SB", L"SO", L"LK", L"SH", L"KN", L"LC", L"PM", L"VC", L"SR", L"SZ", L"TJ", L"TZ", L"TL", L"TG", L"TK", L"TO", - L"TM", L"TC", L"TV", L"UG", L"VU", L"VA", nullptr, L"VG", L"WF", - L"EH", L"ZM", L"ZZ", + L"TM", L"TC", L"TV", L"UG", }; #pragma warning(suppress : 6385) return id < xe::countof(table) ? table[id] : nullptr; @@ -92,7 +94,6 @@ const wchar_t* xeXamGetCountryString(uint8_t id) { L"PH", L"PK", L"PL", L"PR", L"PT", L"PY", L"QA", L"RO", L"RU", L"SA", L"SE", L"SG", L"SI", L"SK", nullptr, L"SV", L"SY", L"TH", L"TN", L"TR", L"TT", L"TW", L"UA", L"US", L"UY", L"UZ", L"VE", L"VN", L"YE", L"ZA", - L"ZW", L"ZZ", }; #pragma warning(suppress : 6385) return id < xe::countof(table) ? table[id] : nullptr; @@ -101,7 +102,7 @@ const wchar_t* xeXamGetCountryString(uint8_t id) { const wchar_t* xeXamGetLanguageString(uint8_t id) { static const wchar_t* const table[] = { L"zz", L"en", L"ja", L"de", L"fr", L"es", L"it", L"ko", L"zh", - L"pt", nullptr, L"pl", L"ru", L"sv", L"tr", L"nb", L"nl", L"zh", + L"pt", nullptr, L"pl", L"ru", L"sv", L"tr", L"nb", L"nl", }; #pragma warning(suppress : 6385) return id < xe::countof(table) ? table[id] : nullptr; @@ -121,19 +122,9 @@ const wchar_t* xeXamGetLocaleString(uint8_t id) { uint8_t xeXamGetLocaleFromOnlineCountry(uint8_t id) { static uint8_t const table[] = { - 0, 43, 0, 0, 40, 2, 1, 0, 3, 0, 0, 0, 0, 4, 0, 0, 5, 0, 33, - 6, 7, 8, 0, 9, 13, 10, 0, 0, 0, 0, 0, 31, 11, 0, 12, 35, 0, 14, - 0, 15, 0, 0, 16, 0, 18, 42, 17, 0, 0, 0, 19, 0, 0, 20, 0, 0, 21, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 23, 25, - 24, 0, 0, 0, 0, 0, 26, 0, 27, 0, 0, 0, 37, 41, 32, 28, 0, 29, 0, - 0, 0, 0, 0, 39, 0, 34, 0, 36, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 38, + 0, 43, 0, 0, 40, 2, 1, 0, 3, 0, 0, 0, 0, 4, 0, + 0, 5, 0, 33, 6, 7, 8, 0, 9, 13, 10, 0, 0, 0, 0, + 0, 31, 11, 0, 12, 35, 0, 14, 0, 15, 0, 0, 16, }; #pragma warning(suppress : 6385) return id < xe::countof(table) ? table[id] : 0; @@ -141,9 +132,9 @@ uint8_t xeXamGetLocaleFromOnlineCountry(uint8_t id) { uint8_t xeXamGetLanguageFromOnlineLanguage(uint8_t id) { static uint8_t const table[] = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 17, 11, 12, 1, 1, 15, 16, 13, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 14, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 17, 11, 12, 1, 1, 15, 16, 13, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 14, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }; #pragma warning(suppress : 6385) return id < xe::countof(table) ? table[id] : 0; @@ -165,21 +156,13 @@ const wchar_t* xeXamGetOnlineLanguageString(uint8_t id) { uint8_t xeXamGetCountryFromOnlineCountry(uint8_t id) { static uint8_t const table[] = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 0, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 0, 95, - 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 111, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 0, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 0, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, }; #pragma warning(suppress : 6385) return id < xe::countof(table) ? table[id] : 0; @@ -187,12 +170,9 @@ uint8_t xeXamGetCountryFromOnlineCountry(uint8_t id) { uint8_t xeXamGetLocaleFromCountry(uint8_t id) { static uint8_t const table[] = { - 0, 43, 0, 0, 40, 2, 1, 0, 3, 0, 0, 0, 0, 4, 0, 0, 5, 0, 33, - 6, 7, 8, 0, 9, 13, 10, 0, 0, 0, 0, 0, 31, 11, 0, 12, 35, 0, 14, - 0, 15, 0, 0, 16, 0, 18, 42, 17, 0, 0, 0, 19, 0, 0, 20, 0, 0, 21, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 23, 25, - 24, 0, 0, 0, 0, 0, 26, 0, 27, 0, 0, 0, 37, 41, 32, 28, 0, 29, 0, - 0, 0, 0, 0, 39, 0, 34, 0, 36, 0, 0, 0, 0, 0, 30, 0, 38, + 0, 43, 0, 0, 40, 2, 1, 0, 3, 0, 0, 0, 0, 4, 0, + 0, 5, 0, 33, 6, 7, 8, 0, 9, 13, 10, 0, 0, 0, 0, + 0, 31, 11, 0, 12, 35, 0, 14, 0, 15, 0, 0, 16, }; #pragma warning(suppress : 6385) return id < xe::countof(table) ? table[id] : 0; @@ -201,17 +181,16 @@ uint8_t xeXamGetLocaleFromCountry(uint8_t id) { // Helpers. uint8_t xeXamGetLocaleEx(uint8_t max_country_id, uint8_t max_locale_id) { - // TODO(gibbed): rework when XConfig is cleanly implemented. - uint8_t country_id = static_cast(cvars::user_country); - /*if (XSUCCEEDED(xboxkrnl::xeExGetXConfigSetting( - 3, 14, &country_id, sizeof(country_id), nullptr))) {*/ - if (country_id <= max_country_id) { - uint8_t locale_id = xeXamGetLocaleFromCountry(country_id); - if (locale_id <= max_locale_id) { - return locale_id; + uint8_t country_id; + if (XSUCCEEDED(xboxkrnl::xeExGetXConfigSetting( + 3, 14, &country_id, sizeof(country_id), nullptr))) { + if (country_id <= max_country_id) { + uint8_t locale_id = xeXamGetLocaleFromCountry(country_id); + if (locale_id <= max_locale_id) { + return locale_id; + } } } - /*}*/ // couldn't find locale, fallback from game region. auto game_region = xeXGetGameRegion(); diff --git a/src/xenia/kernel/xam/xam_msg.cc b/src/xenia/kernel/xam/xam_msg.cc index 07d0be6c7..020868c87 100644 --- a/src/xenia/kernel/xam/xam_msg.cc +++ b/src/xenia/kernel/xam/xam_msg.cc @@ -12,6 +12,7 @@ #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xam/xam_private.h" #include "xenia/kernel/xevent.h" +#include "xenia/kernel/xthread.h" #include "xenia/xbox.h" namespace xe { @@ -47,11 +48,15 @@ dword_result_t XMsgStartIORequest(dword_t app, dword_t message, app, message, buffer, buffer_length); if (result == X_ERROR_NOT_FOUND) { XELOGE("XMsgStartIORequest: app %.8X undefined", (uint32_t)app); + XThread::SetLastError(X_ERROR_NOT_FOUND); } if (overlapped_ptr) { kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result); result = X_ERROR_IO_PENDING; } + if (result == X_ERROR_SUCCESS || X_ERROR_IO_PENDING) { + XThread::SetLastError(0); + } return result; } DECLARE_XAM_EXPORT1(XMsgStartIORequest, kNone, kImplemented); diff --git a/src/xenia/kernel/xam/xam_net.cc b/src/xenia/kernel/xam/xam_net.cc index d0a3d70c3..5a9034e36 100644 --- a/src/xenia/kernel/xam/xam_net.cc +++ b/src/xenia/kernel/xam/xam_net.cc @@ -461,7 +461,7 @@ DECLARE_XAM_EXPORT1(NetDll_XNetXnAddrToMachineId, kNetworking, kStub); void NetDll_XNetInAddrToString(dword_t caller, dword_t in_addr, lpstring_t string_out, dword_t string_size) { - strncpy(string_out, "666.666.666.666", string_size); + strncpy(string_out, "127.0.0.1", string_size); } DECLARE_XAM_EXPORT1(NetDll_XNetInAddrToString, kNetworking, kStub); diff --git a/src/xenia/kernel/xam/xam_notify.cc b/src/xenia/kernel/xam/xam_notify.cc index 6f6b229bf..56ff5dc2d 100644 --- a/src/xenia/kernel/xam/xam_notify.cc +++ b/src/xenia/kernel/xam/xam_notify.cc @@ -18,7 +18,10 @@ namespace xe { namespace kernel { namespace xam { -dword_result_t XamNotifyCreateListenerInternal(qword_t mask) { +dword_result_t XamNotifyCreateListenerInternal(qword_t mask, dword_t unk, + dword_t one) { + // r4=1 may indicate user process? + auto listener = object_ref(new XNotifyListener(kernel_state())); listener->Initialize(mask); @@ -28,10 +31,11 @@ dword_result_t XamNotifyCreateListenerInternal(qword_t mask) { return handle; } -DECLARE_XAM_EXPORT1(XamNotifyCreateListenerInternal, kNone, kImplemented); +DECLARE_XAM_EXPORT2(XamNotifyCreateListenerInternal, kNone, kImplemented, + kSketchy); -dword_result_t XamNotifyCreateListener(qword_t mask) { - return XamNotifyCreateListenerInternal(mask); +dword_result_t XamNotifyCreateListener(qword_t mask, dword_t one) { + return XamNotifyCreateListenerInternal(mask, 0, one); } DECLARE_XAM_EXPORT1(XamNotifyCreateListener, kNone, kImplemented); @@ -56,6 +60,9 @@ dword_result_t XNotifyGetNext(dword_t handle, dword_t match_id, // Asking for a specific notification id = match_id; dequeued = listener->DequeueNotification(match_id, ¶m); + // TODO(Gliniak): Requires research. There is no such match_id! + if (!dequeued && !param) + dequeued = listener->DequeueNotification(&id, ¶m); } else { // Just get next. dequeued = listener->DequeueNotification(&id, ¶m); @@ -71,7 +78,7 @@ dword_result_t XNotifyGetNext(dword_t handle, dword_t match_id, return dequeued ? 1 : 0; } -DECLARE_XAM_EXPORT1(XNotifyGetNext, kNone, kImplemented); +DECLARE_XAM_EXPORT2(XNotifyGetNext, kNone, kImplemented, kHighFrequency); dword_result_t XNotifyDelayUI(dword_t delay_ms) { // Ignored. diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index 738188891..f32e98738 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -16,11 +16,93 @@ #include "xenia/kernel/xenumerator.h" #include "xenia/kernel/xthread.h" #include "xenia/xbox.h" +#include "xenia/base/cvar.h" + + +DEFINE_bool(signin_state, true, + "User signed in", "Kernel"); namespace xe { namespace kernel { namespace xam { +struct X_PROFILEENUMRESULT { + xe::be xuid_offline; // E0..... + X_XAMACCOUNTINFO account; + xe::be 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(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 overlapped) { + assert_true(flags == 0); + + auto e = kernel_state()->object_table()->LookupObject(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(); + 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, lpqword_t xuid_ptr) { if (user_index) { @@ -44,7 +126,7 @@ dword_result_t XamUserGetSigninState(dword_t user_index) { if (user_index == 0 || (user_index & 0xFF) == 0xFF) { const auto& user_profile = kernel_state()->user_profile(); - return user_profile->signin_state(); + return ((cvars::signin_state) ? 1 : 0); } else { return 0; } @@ -75,7 +157,7 @@ X_HRESULT_result_t XamUserGetSigninInfo(dword_t user_index, dword_t flags, const auto& user_profile = kernel_state()->user_profile(); info->xuid = user_profile->xuid(); - info->signin_state = user_profile->signin_state(); + info->signin_state = ((cvars::signin_state) ? 1 : 0); std::strncpy(info->name, user_profile->name().data(), 15); return X_E_SUCCESS; } @@ -86,19 +168,8 @@ dword_result_t XamUserGetName(dword_t user_index, lpstring_t buffer, if (user_index) { return X_ERROR_NO_SUCH_USER; } - - if (!buffer_len) { - return X_ERROR_SUCCESS; - } - const auto& user_profile = kernel_state()->user_profile(); - const auto& user_name = user_profile->name(); - - // Real XAM will only copy a maximum of 15 characters out. - size_t copy_length = std::min( - {size_t(15), user_name.size(), static_cast(buffer_len) - 1}); - std::memcpy(buffer, user_name.data(), copy_length); - buffer[copy_length] = '\0'; + std::strncpy(buffer, user_profile->name().data(), buffer_len); return X_ERROR_SUCCESS; } DECLARE_XAM_EXPORT1(XamUserGetName, kUserProfiles, kImplemented); @@ -436,7 +507,7 @@ dword_result_t XamUserAreUsersFriends(dword_t user_index, dword_t unk1, X_RESULT result = X_ERROR_SUCCESS; const auto& user_profile = kernel_state()->user_profile(); - if (user_profile->signin_state() == 0) { + if (((cvars::signin_state) ? 1 : 0) == 0) { result = X_ERROR_NOT_LOGGED_ON; } else { // No friends! @@ -459,6 +530,20 @@ dword_result_t XamShowSigninUI(dword_t unk, dword_t unk_mask) { } DECLARE_XAM_EXPORT1(XamShowSigninUI, kUserProfiles, kStub); +#pragma pack(push, 1) +struct X_XACHIEVEMENT_DETAILS { + xe::be id; + xe::be label_ptr; + xe::be description_ptr; + xe::be unachieved_ptr; + xe::be image_id; + xe::be gamerscore; + xe::be unlock_time; + xe::be flags; +}; +static_assert_size(X_XACHIEVEMENT_DETAILS, 36); +#pragma pack(pop) + dword_result_t XamUserCreateAchievementEnumerator(dword_t title_id, dword_t user_index, dword_t xuid, dword_t flags, @@ -466,14 +551,60 @@ dword_result_t XamUserCreateAchievementEnumerator(dword_t title_id, lpdword_t buffer_size_ptr, lpdword_t handle_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(); *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 = kernel_memory()->SystemHeapAlloc( + ((uint32_t)wcslen(placeholder_val) + 1) * 2); + auto* place_addr = kernel_memory()->TranslateVirtual(placeholder); + + memset(place_addr, 0, (wcslen(placeholder_val) + 1) * 2); + xe::copy_and_swap(place_addr, placeholder_val, wcslen(placeholder_val)); + } + + std::vector 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; } DECLARE_XAM_EXPORT1(XamUserCreateAchievementEnumerator, kUserProfiles, @@ -526,8 +657,10 @@ DECLARE_XAM_EXPORT1(XamSessionCreateHandle, kUserProfiles, kStub); dword_result_t XamSessionRefObjByHandle(dword_t handle, lpdword_t obj_ptr) { assert_true(handle == 0xCAFEDEAD); - *obj_ptr = 0; - return X_ERROR_FUNCTION_FAILED; + // TODO(PermaNull): Implement this properly, + // For the time being returning 0xDEADF00D will prevent crashing. + *obj_ptr = 0xDEADF00D; + return X_ERROR_SUCCESS; } DECLARE_XAM_EXPORT1(XamSessionRefObjByHandle, kUserProfiles, kStub); diff --git a/src/xenia/kernel/xam/xdbf/xdbf.cc b/src/xenia/kernel/xam/xdbf/xdbf.cc new file mode 100644 index 000000000..f04a18656 --- /dev/null +++ b/src/xenia/kernel/xam/xdbf/xdbf.cc @@ -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(ptr); + ptr += sizeof(XdbfStringTableEntry); + if (entry->id == string_id) { + return std::string(reinterpret_cast(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(SpaSection::kStringTable), + static_cast(locale)); + if (!xstr_table) { + return ""; + } + + auto xstr_head = + reinterpret_cast(xstr_table->data.data()); + assert_true(xstr_head->magic == static_cast(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* achievements) const { + auto xach_table = GetEntry(static_cast(SpaSection::kMetadata), + static_cast(SpaID::Xach)); + if (!xach_table) { + return 0; + } + + auto xach_head = + reinterpret_cast(xach_table->data.data()); + assert_true(xach_head->magic == static_cast(SpaID::Xach)); + assert_true(xach_head->version == 1); + + auto xstr_table = GetEntry(static_cast(SpaSection::kStringTable), + static_cast(locale)); + if (!xstr_table) { + return 0; + } + + auto xstr_head = + reinterpret_cast(xstr_table->data.data()); + assert_true(xstr_head->magic == static_cast(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(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(SpaSection::kImage), + static_cast(SpaID::Title)); +} + +Locale SpaFile::GetDefaultLocale() const { + auto block = GetEntry(static_cast(SpaSection::kMetadata), + static_cast(SpaID::Xstc)); + if (!block) { + return Locale::kEnglish; + } + + auto xstc = reinterpret_cast(block->data.data()); + assert_true(xstc->magic == static_cast(SpaID::Xstc)); + + return static_cast(static_cast(xstc->default_language)); +} + +std::string SpaFile::GetTitleName() const { + return GetStringTableEntry(GetDefaultLocale(), + static_cast(SpaID::Title)); +} + +uint32_t SpaFile::GetTitleId() const { + auto block = GetEntry(static_cast(SpaSection::kMetadata), + static_cast(SpaID::Xthd)); + if (!block) { + return -1; + } + + auto xthd = reinterpret_cast(block->data.data()); + assert_true(xthd->magic == static_cast(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(GpdSection::kAchievement) || + entry->info.id != id) { + continue; + } + + auto* ach_data = + reinterpret_cast(entry->data.data()); + + if (dest) { + dest->ReadGPD(ach_data); + } + return true; + } + + return false; +} + +uint32_t GpdFile::GetAchievements( + std::vector* 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(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(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(GpdSection::kTitle) || + entry->info.id != title_id) { + continue; + } + + auto* title_data = + reinterpret_cast(entry->data.data()); + + dest->ReadGPD(title_data); + + return true; + } + + return false; +} + +uint32_t GpdFile::GetTitles(std::vector* 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(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(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(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(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(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*)label_ptr, ach.label.c_str(), + ach.label.size()); + xe::copy_and_swap((wchar_t*)desc_ptr, ach.description.c_str(), + ach.description.size()); + xe::copy_and_swap((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(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(ent.data.data()); + title.WriteGPD(title_data); + + return UpdateEntry(ent); +} + +} // namespace xdbf +} // namespace xam +} // namespace kernel +} // namespace xe diff --git a/src/xenia/kernel/xam/xdbf/xdbf.h b/src/xenia/kernel/xam/xdbf/xdbf.h new file mode 100644 index 000000000..de12617af --- /dev/null +++ b/src/xenia/kernel/xam/xdbf/xdbf.h @@ -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 +#include + +#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(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(dest + 1); + xe::copy_and_swap((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( + flags & static_cast(AchievementFlags::kTypeMask)); + } + + bool IsUnlocked() { + return flags & static_cast(AchievementFlags::kAchieved); + } + + bool IsUnlockedOnline() { + return flags & static_cast(AchievementFlags::kAchievedOnline); + } + + void Unlock(bool online = false) { + flags |= static_cast(AchievementFlags::kAchieved); + if (online) { + flags |= static_cast(AchievementFlags::kAchievedOnline); + } + + unlock_time = Clock::QueryHostSystemTime(); + } + + void Lock() { + flags = flags & ~(static_cast(AchievementFlags::kAchieved)); + flags = flags & ~(static_cast(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(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 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 entries_; + std::vector free_entries_; +}; + +class SpaFile : public XdbfFile { + public: + std::string GetStringTableEntry(Locale locale, uint16_t string_id) const; + + uint32_t GetAchievements(Locale locale, + std::vector* 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* achievements) const; + + bool GetTitle(uint32_t title_id, TitlePlayed* title); + uint32_t GetTitles(std::vector* 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_ diff --git a/src/xenia/kernel/xam/xdbf/xdbf_xbox.h b/src/xenia/kernel/xam/xdbf/xdbf_xbox.h new file mode 100644 index 000000000..cff02d68d --- /dev/null +++ b/src/xenia/kernel/xam/xdbf/xdbf_xbox.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 id; + xe::be string_length; +}; +static_assert_size(XdbfStringTableEntry, 4); + +#pragma pack(push, 1) +struct X_XDBF_HEADER { + xe::be magic; + xe::be version; + xe::be entry_count; + xe::be entry_used; + xe::be free_count; + xe::be free_used; +}; +static_assert_size(X_XDBF_HEADER, 24); + +struct X_XDBF_ENTRY { + xe::be section; + xe::be id; + xe::be offset; + xe::be size; +}; +static_assert_size(X_XDBF_ENTRY, 18); + +struct X_XDBF_FILELOC { + xe::be offset; + xe::be size; +}; +static_assert_size(X_XDBF_FILELOC, 8); + +struct X_XDBF_XSTC_DATA { + xe::be magic; + xe::be version; + xe::be size; + xe::be default_language; +}; +static_assert_size(X_XDBF_XSTC_DATA, 16); + +struct X_XDBF_XTHD_DATA { + xe::be magic; + xe::be version; + xe::be unk8; + xe::be title_id; + xe::be unk10; // always 1? + xe::be title_version_major; + xe::be title_version_minor; + xe::be title_version_build; + xe::be title_version_revision; + xe::be unk1C; + xe::be unk20; + xe::be unk24; + xe::be unk28; +}; +static_assert_size(X_XDBF_XTHD_DATA, 0x2C); + +struct X_XDBF_TABLE_HEADER { + xe::be magic; + xe::be version; + xe::be size; + xe::be count; +}; +static_assert_size(X_XDBF_TABLE_HEADER, 14); + +struct X_XDBF_SPA_ACHIEVEMENT { + xe::be id; + xe::be label_id; + xe::be description_id; + xe::be unachieved_id; + xe::be image_id; + xe::be gamerscore; + xe::be unkE; + xe::be flags; + xe::be unk14; + xe::be unk18; + xe::be unk1C; + xe::be unk20; +}; +static_assert_size(X_XDBF_SPA_ACHIEVEMENT, 0x24); + +struct X_XDBF_GPD_ACHIEVEMENT { + xe::be magic; + xe::be id; + xe::be image_id; + xe::be gamerscore; + xe::be flags; + xe::be 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 title_id; + xe::be achievements_possible; + xe::be achievements_earned; + xe::be gamerscore_total; + xe::be gamerscore_earned; + xe::be reserved_achievement_count; + + // the following are meant to be split into possible/earned, 1 byte each + // but who cares + xe::be all_avatar_awards; + xe::be male_avatar_awards; + xe::be female_avatar_awards; + xe::be reserved_flags; + xe::be 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_ \ No newline at end of file diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_crypt.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_crypt.cc index 9a8e4a001..48cd004d1 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_crypt.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_crypt.cc @@ -9,6 +9,7 @@ #include "xenia/base/logging.h" #include "xenia/kernel/kernel_state.h" +#include "xenia/kernel/util/crypto_utils.h" #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_private.h" #include "xenia/xbox.h" diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_debug.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_debug.cc index 6a490677e..575496726 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_debug.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_debug.cc @@ -136,8 +136,9 @@ DECLARE_XBOXKRNL_EXPORT2(RtlRaiseException, kDebug, kStub, kImportant); void KeBugCheckEx(dword_t code, dword_t param1, dword_t param2, dword_t param3, dword_t param4) { - XELOGD("*** STOP: 0x%.8X (0x%.8X, 0x%.8X, 0x%.8X, 0x%.8X)", code, param1, + XELOGE("*** STOP: 0x%.8X (0x%.8X, 0x%.8X, 0x%.8X, 0x%.8X)", code, param1, param2, param3, param4); + XELOGE(" ### GUEST RAISE EXCEPTION - should have crashed here ###"); fflush(stdout); //xe::debugging::Break(); assert_always(); diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc index c601b51da..220a2d4a0 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc @@ -137,6 +137,9 @@ dword_result_t ExCreateThread(lpdword_t handle_ptr, dword_t stack_size, if (handle_ptr) { if (creation_flags & 0x80) { *handle_ptr = thread->guest_object(); + } else if (!*handle_ptr && (creation_flags == X_CREATE_SUSPENDED)) { + // TODO(Gliniak): Temporary solution, requires more research // && !stack_size + *handle_ptr = thread->handle(); } else { thread->RetainHandle(); *handle_ptr = thread->handle(); diff --git a/src/xenia/kernel/xobject.cc b/src/xenia/kernel/xobject.cc index 7f615ef4b..01e333a63 100644 --- a/src/xenia/kernel/xobject.cc +++ b/src/xenia/kernel/xobject.cc @@ -68,7 +68,7 @@ Emulator* XObject::emulator() const { return kernel_state_->emulator_; } KernelState* XObject::kernel_state() const { return kernel_state_; } Memory* XObject::memory() const { return kernel_state_->memory(); } -XObject::Type XObject::type() const { return type_; } +XObject::Type XObject::type() { return type_; } void XObject::RetainHandle() { kernel_state_->object_table()->RetainHandle(handles_[0]); diff --git a/src/xenia/kernel/xobject.h b/src/xenia/kernel/xobject.h index 23ab38d6e..280250ca5 100644 --- a/src/xenia/kernel/xobject.h +++ b/src/xenia/kernel/xobject.h @@ -134,7 +134,7 @@ class XObject { KernelState* kernel_state() const; Memory* memory() const; - Type type() const; + Type type(); // Returns the primary handle of this object. X_HANDLE handle() const { return handles_[0]; }