diff --git a/docs/building.md b/docs/building.md index 0a70fb206..a02cd989d 100644 --- a/docs/building.md +++ b/docs/building.md @@ -99,7 +99,7 @@ Clang-9 or newer should be available from system repositories on all up to date You will also need some development libraries. To get them on an Ubuntu system: ```bash -sudo apt-get install libgtk-3-dev libpthread-stubs0-dev liblz4-dev libx11-dev libvulkan-dev libsdl2-dev libiberty-dev libunwind-dev libc++-dev libc++abi-dev +sudo apt-get install libgtk-3-dev libpthread-stubs0-dev liblz4-dev libx11-dev libx11-xcb-dev libvulkan-dev libsdl2-dev libiberty-dev libunwind-dev libc++-dev libc++abi-dev ``` In addition, you will need up to date Vulkan libraries and drivers for your hardware, which most distributions have in their standard repositories nowadays. diff --git a/src/xenia/base/testing/utf8_test.cc b/src/xenia/base/testing/utf8_test.cc index 9a33ff4fb..356f8ad0f 100644 --- a/src/xenia/base/testing/utf8_test.cc +++ b/src/xenia/base/testing/utf8_test.cc @@ -388,20 +388,45 @@ TEST_CASE("UTF-8 Fix Path Separators", "[utf8]") { TEST_CASE("UTF-8 Find Name From Path", "[utf8]") { TEST_PATH(utf8::find_name_from_path, "/", ""); + TEST_PATH(utf8::find_name_from_path, "//", ""); + TEST_PATH(utf8::find_name_from_path, "///", ""); + TEST_PATH(utf8::find_name_from_path, "C/", "C"); + TEST_PATH(utf8::find_name_from_path, "/C/", "C"); + TEST_PATH(utf8::find_name_from_path, "C/D/", "D"); + TEST_PATH(utf8::find_name_from_path, "/C/D/E/", "E"); + TEST_PATH(utf8::find_name_from_path, "foo/bar/D/", "D"); + TEST_PATH(utf8::find_name_from_path, "/foo/bar/E/qux/", "qux"); TEST_PATH(utf8::find_name_from_path, "foo/bar/baz/qux/", "qux"); + TEST_PATH(utf8::find_name_from_path, "foo/bar/baz/qux//", "qux"); + TEST_PATH(utf8::find_name_from_path, "foo/bar/baz/qux///", "qux"); TEST_PATH(utf8::find_name_from_path, "foo/bar/baz/qux.txt", "qux.txt"); TEST_PATH(utf8::find_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ/", "ほげほげ"); + TEST_PATH(utf8::find_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ//", + "ほげほげ"); + TEST_PATH(utf8::find_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ///", + "ほげほげ"); TEST_PATH(utf8::find_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", "ほげほげ.txt"); + TEST_PATH(utf8::find_name_from_path, "/foo", "foo"); + TEST_PATH(utf8::find_name_from_path, "//foo", "foo"); + TEST_PATH(utf8::find_name_from_path, "///foo", "foo"); TEST_PATH(utf8::find_name_from_path, "/foo/bar/baz/qux.txt", "qux.txt"); TEST_PATH(utf8::find_name_from_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ/", "ほげほげ"); + TEST_PATH(utf8::find_name_from_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ//", + "ほげほげ"); + TEST_PATH(utf8::find_name_from_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ///", + "ほげほげ"); TEST_PATH(utf8::find_name_from_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", "ほげほげ.txt"); TEST_PATH(utf8::find_name_from_path, "X:/foo/bar/baz/qux.txt", "qux.txt"); TEST_PATH(utf8::find_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ/", "ほげほげ"); + TEST_PATH(utf8::find_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ//", + "ほげほげ"); + TEST_PATH(utf8::find_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ///", + "ほげほげ"); TEST_PATH(utf8::find_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", "ほげほげ.txt"); TEST_PATH(utf8::find_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら.ほげほげ", @@ -413,26 +438,49 @@ TEST_CASE("UTF-8 Find Name From Path", "[utf8]") { TEST_CASE("UTF-8 Find Base Name From Path", "[utf8]") { TEST_PATH(utf8::find_base_name_from_path, "foo/bar/baz/qux.txt", "qux"); TEST_PATH(utf8::find_base_name_from_path, "foo/bar/baz/qux/", "qux"); + TEST_PATH(utf8::find_base_name_from_path, "foo/bar/baz/qux//", "qux"); + TEST_PATH(utf8::find_base_name_from_path, "foo/bar/baz/qux///", "qux"); + TEST_PATH(utf8::find_base_name_from_path, "C/", "C"); + TEST_PATH(utf8::find_base_name_from_path, "/C/", "C"); + TEST_PATH(utf8::find_base_name_from_path, "C/D/", "D"); + TEST_PATH(utf8::find_base_name_from_path, "/C/D/E/", "E"); + TEST_PATH(utf8::find_base_name_from_path, "foo/bar/D/", "D"); TEST_PATH(utf8::find_base_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", "ほげほげ"); TEST_PATH(utf8::find_base_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ/", "ほげほげ"); + TEST_PATH(utf8::find_base_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ//", + "ほげほげ"); + TEST_PATH(utf8::find_base_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ///", + "ほげほげ"); TEST_PATH(utf8::find_base_name_from_path, "ほげ/ぴよ/ふが/ほげら.ほげほげ", "ほげら"); TEST_PATH(utf8::find_base_name_from_path, "/foo/bar/baz/qux.txt", "qux"); TEST_PATH(utf8::find_base_name_from_path, "/foo/bar/baz/qux/", "qux"); + TEST_PATH(utf8::find_base_name_from_path, "/foo/bar/baz/qux//", "qux"); + TEST_PATH(utf8::find_base_name_from_path, "/foo/bar/baz/qux///", "qux"); TEST_PATH(utf8::find_base_name_from_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", "ほげほげ"); TEST_PATH(utf8::find_base_name_from_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ/", "ほげほげ"); + TEST_PATH(utf8::find_base_name_from_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ//", + "ほげほげ"); + TEST_PATH(utf8::find_base_name_from_path, + "/ほげ/ぴよ/ふが/ほげら/ほげほげ///", "ほげほげ"); TEST_PATH(utf8::find_base_name_from_path, "/ほげ/ぴよ/ふが/ほげら.ほげほげ", "ほげら"); TEST_PATH(utf8::find_base_name_from_path, "X:/foo/bar/baz/qux.txt", "qux"); TEST_PATH(utf8::find_base_name_from_path, "X:/foo/bar/baz/qux/", "qux"); + TEST_PATH(utf8::find_base_name_from_path, "X:/foo/bar/baz/qux//", "qux"); + TEST_PATH(utf8::find_base_name_from_path, "X:/foo/bar/baz/qux///", "qux"); TEST_PATH(utf8::find_base_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", "ほげほげ"); TEST_PATH(utf8::find_base_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ/", "ほげほげ"); + TEST_PATH(utf8::find_base_name_from_path, + "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ//", "ほげほげ"); + TEST_PATH(utf8::find_base_name_from_path, + "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ///", "ほげほげ"); TEST_PATH(utf8::find_base_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら.ほげほげ", "ほげら"); } @@ -443,38 +491,79 @@ TEST_CASE("UTF-8 Find Base Path", "[utf8]") { TEST_PATH(utf8::find_base_path, "", ""); TEST_PATH(utf8::find_base_path, "/", ""); TEST_PATH(utf8::find_base_path, "//", ""); + TEST_PATH(utf8::find_base_path, "///", ""); TEST_PATH(utf8::find_base_path, "/foo", ""); + TEST_PATH(utf8::find_base_path, "//foo", ""); + TEST_PATH(utf8::find_base_path, "///foo", ""); TEST_PATH(utf8::find_base_path, "/foo/", ""); + TEST_PATH(utf8::find_base_path, "/foo//", ""); + TEST_PATH(utf8::find_base_path, "/foo///", ""); + TEST_PATH(utf8::find_base_path, "//foo/", ""); + TEST_PATH(utf8::find_base_path, "//foo//", ""); + TEST_PATH(utf8::find_base_path, "//foo///", ""); + TEST_PATH(utf8::find_base_path, "///foo/", ""); + TEST_PATH(utf8::find_base_path, "///foo//", ""); + TEST_PATH(utf8::find_base_path, "///foo///", ""); TEST_PATH(utf8::find_base_path, "/foo/bar", "/foo"); TEST_PATH(utf8::find_base_path, "/foo/bar/", "/foo"); + TEST_PATH(utf8::find_base_path, "/foo/bar//", "/foo"); + TEST_PATH(utf8::find_base_path, "/foo/bar///", "/foo"); TEST_PATH(utf8::find_base_path, "/foo/bar/baz/qux", "/foo/bar/baz"); TEST_PATH(utf8::find_base_path, "/foo/bar/baz/qux/", "/foo/bar/baz"); + TEST_PATH(utf8::find_base_path, "/foo/bar/baz/qux//", "/foo/bar/baz"); + TEST_PATH(utf8::find_base_path, "/foo/bar/baz/qux///", "/foo/bar/baz"); TEST_PATH(utf8::find_base_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ", "/ほげ/ぴよ/ふが/ほげら"); TEST_PATH(utf8::find_base_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ/", "/ほげ/ぴよ/ふが/ほげら"); + TEST_PATH(utf8::find_base_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ//", + "/ほげ/ぴよ/ふが/ほげら"); + TEST_PATH(utf8::find_base_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ///", + "/ほげ/ぴよ/ふが/ほげら"); TEST_PATH(utf8::find_base_path, "foo", ""); TEST_PATH(utf8::find_base_path, "foo/", ""); + TEST_PATH(utf8::find_base_path, "foo//", ""); + TEST_PATH(utf8::find_base_path, "foo///", ""); TEST_PATH(utf8::find_base_path, "foo/bar", "foo"); TEST_PATH(utf8::find_base_path, "foo/bar/", "foo"); + TEST_PATH(utf8::find_base_path, "foo/bar//", "foo"); + TEST_PATH(utf8::find_base_path, "foo/bar///", "foo"); TEST_PATH(utf8::find_base_path, "foo/bar/baz/qux", "foo/bar/baz"); TEST_PATH(utf8::find_base_path, "foo/bar/baz/qux/", "foo/bar/baz"); + TEST_PATH(utf8::find_base_path, "foo/bar/baz/qux//", "foo/bar/baz"); + TEST_PATH(utf8::find_base_path, "foo/bar/baz/qux///", "foo/bar/baz"); TEST_PATH(utf8::find_base_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ", "ほげ/ぴよ/ふが/ほげら"); TEST_PATH(utf8::find_base_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ/", "ほげ/ぴよ/ふが/ほげら"); + TEST_PATH(utf8::find_base_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ//", + "ほげ/ぴよ/ふが/ほげら"); + TEST_PATH(utf8::find_base_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ///", + "ほげ/ぴよ/ふが/ほげら"); TEST_PATH(utf8::find_base_path, "X:", ""); TEST_PATH(utf8::find_base_path, "X:/", ""); + TEST_PATH(utf8::find_base_path, "X://", ""); + TEST_PATH(utf8::find_base_path, "X:///", ""); TEST_PATH(utf8::find_base_path, "X:/foo", "X:"); TEST_PATH(utf8::find_base_path, "X:/foo/", "X:"); + TEST_PATH(utf8::find_base_path, "X:/foo//", "X:"); + TEST_PATH(utf8::find_base_path, "X:/foo///", "X:"); TEST_PATH(utf8::find_base_path, "X:/foo/bar", "X:/foo"); TEST_PATH(utf8::find_base_path, "X:/foo/bar/", "X:/foo"); + TEST_PATH(utf8::find_base_path, "X:/foo/bar//", "X:/foo"); + TEST_PATH(utf8::find_base_path, "X:/foo/bar///", "X:/foo"); TEST_PATH(utf8::find_base_path, "X:/foo/bar/baz/qux", "X:/foo/bar/baz"); TEST_PATH(utf8::find_base_path, "X:/foo/bar/baz/qux/", "X:/foo/bar/baz"); + TEST_PATH(utf8::find_base_path, "X:/foo/bar/baz/qux//", "X:/foo/bar/baz"); + TEST_PATH(utf8::find_base_path, "X:/foo/bar/baz/qux///", "X:/foo/bar/baz"); TEST_PATH(utf8::find_base_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ", "X:/ほげ/ぴよ/ふが/ほげら"); TEST_PATH(utf8::find_base_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ/", "X:/ほげ/ぴよ/ふが/ほげら"); + TEST_PATH(utf8::find_base_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ//", + "X:/ほげ/ぴよ/ふが/ほげら"); + TEST_PATH(utf8::find_base_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ///", + "X:/ほげ/ぴよ/ふが/ほげら"); } // TODO(gibbed): find_base_guest_path @@ -482,6 +571,8 @@ TEST_CASE("UTF-8 Find Base Path", "[utf8]") { TEST_CASE("UTF-8 Canonicalize Path", "[utf8]") { TEST_PATH(utf8::canonicalize_path, "foo/bar/baz/qux", "foo/bar/baz/qux"); TEST_PATH(utf8::canonicalize_path, "foo/bar/baz/qux/", "foo/bar/baz/qux"); + TEST_PATH(utf8::canonicalize_path, "foo/bar/baz/qux//", "foo/bar/baz/qux"); + TEST_PATH(utf8::canonicalize_path, "foo/bar/baz/qux///", "foo/bar/baz/qux"); TEST_PATH(utf8::canonicalize_path, "foo/./baz/qux", "foo/baz/qux"); TEST_PATH(utf8::canonicalize_path, "foo/./baz/qux/", "foo/baz/qux"); TEST_PATH(utf8::canonicalize_path, "foo/../baz/qux", "baz/qux"); diff --git a/src/xenia/base/utf8.cc b/src/xenia/base/utf8.cc index 308117477..3f4775b7a 100644 --- a/src/xenia/base/utf8.cc +++ b/src/xenia/base/utf8.cc @@ -582,43 +582,40 @@ std::string find_name_from_path(const std::string_view path, auto it = end; --it; - // path is padded with separator - size_t padding = 0; - if (*it == uint32_t(separator)) { + // skip trailing separators at the end of the path + while (*it == uint32_t(separator)) { if (it == begin) { + // path is all separators, name is empty return std::string(); } --it; - padding = 1; } - // path is just separator - if (it == begin) { - return std::string(); - } + // update end so it is before any trailing separators + end = std::next(it); - // search for separator - while (it != begin) { - if (*it == uint32_t(separator)) { + // skip non-separators + while (*it != uint32_t(separator)) { + if (it == begin) { break; } --it; } - // no separator -- copy entire string (except trailing separator) - if (it == begin) { - return std::string(path.substr(0, path.size() - padding)); + // if the iterator is on a separator, advance + if (*it == uint32_t(separator)) { + ++it; } - auto length = byte_length(std::next(it), end); - auto offset = path.length() - length; - return std::string(path.substr(offset, length - padding)); + auto offset = byte_length(begin, it); + auto length = byte_length(it, end); + return std::string(path.substr(offset, length)); } std::string find_base_name_from_path(const std::string_view path, char32_t separator) { auto name = find_name_from_path(path, separator); - if (!name.size()) { + if (!name.length()) { return std::string(); } @@ -653,28 +650,34 @@ std::string find_base_path(const std::string_view path, char32_t separator) { auto it = end; --it; - // skip trailing separator - if (*it == uint32_t(separator)) { + // skip trailing separators at the end of the path + while (*it == uint32_t(separator)) { if (it == begin) { return std::string(); } --it; } - while (it != begin) { - if (*it == uint32_t(separator)) { - break; + // skip non-separators + while (*it != uint32_t(separator)) { + if (it == begin) { + // there are no separators, base path is empty + return std::string(); } --it; } - if (it == begin) { - return std::string(); + // skip trailing separators at the end of the base path + while (*it == uint32_t(separator)) { + if (it == begin) { + // base path is all separators, base path is empty + return std::string(); + } + --it; } - auto length = byte_length(it, end); - auto offset = path.length() - length; - return std::string(path.substr(0, offset)); + auto length = byte_length(begin, std::next(it)); + return std::string(path.substr(0, length)); } std::string canonicalize_path(const std::string_view path, char32_t separator) { diff --git a/src/xenia/cpu/xex_module.cc b/src/xenia/cpu/xex_module.cc index 70861aa7e..83fbc2139 100644 --- a/src/xenia/cpu/xex_module.cc +++ b/src/xenia/cpu/xex_module.cc @@ -966,6 +966,16 @@ bool XexModule::LoadContinue() { return false; } + // Parse any "unsafe" headers into safer variants + xex2_opt_generic_u32* alternate_titleids; + if (GetOptHeader(xex2_header_keys::XEX_HEADER_ALTERNATE_TITLE_IDS, + &alternate_titleids)) { + auto count = alternate_titleids->count(); + for (uint32_t i = 0; i < count; i++) { + opt_alternate_title_ids_.push_back(alternate_titleids->values[i]); + } + } + // Scan and find the low/high addresses. // All code sections are continuous, so this should be easy. auto heap = memory()->LookupHeap(base_address_); diff --git a/src/xenia/cpu/xex_module.h b/src/xenia/cpu/xex_module.h index 9834a675f..cd8fc49c5 100644 --- a/src/xenia/cpu/xex_module.h +++ b/src/xenia/cpu/xex_module.h @@ -107,6 +107,10 @@ class XexModule : public xe::cpu::Module { return retval; } + std::vector opt_alternate_title_ids() const { + return opt_alternate_title_ids_; + } + const uint32_t base_address() const { return base_address_; } const bool is_dev_kit() const { return is_dev_kit_; } @@ -198,6 +202,9 @@ class XexModule : public xe::cpu::Module { import_libs_; // pre-loaded import libraries for ease of use std::vector pe_sections_; + // XEX_HEADER_ALTERNATE_TITLE_IDS loaded into a safe std::vector + std::vector opt_alternate_title_ids_; + uint8_t session_key_[0x10]; bool is_dev_kit_ = false; diff --git a/src/xenia/kernel/util/object_table.cc b/src/xenia/kernel/util/object_table.cc index df881e3a1..d1250791c 100644 --- a/src/xenia/kernel/util/object_table.cc +++ b/src/xenia/kernel/util/object_table.cc @@ -205,6 +205,10 @@ X_STATUS ObjectTable::RemoveHandle(X_HANDLE handle) { XELOGI("Removed handle:{:08X} for {}", handle, typeid(*object).name()); + // Remove object name from mapping to prevent naming collision. + if (!object->name().empty()) { + RemoveNameMapping(object->name()); + } // Release now that the object has been removed from the table. object->Release(); } diff --git a/src/xenia/kernel/util/xex2_info.h b/src/xenia/kernel/util/xex2_info.h index a5b5e5a33..6b77476d8 100644 --- a/src/xenia/kernel/util/xex2_info.h +++ b/src/xenia/kernel/util/xex2_info.h @@ -529,6 +529,13 @@ struct xex2_import_library { } }; +struct xex2_opt_generic_u32 { + xe::be size; + xe::be values[1]; + + uint32_t count() const { return (size - 4) / 4; } +}; + struct xex2_opt_header { xe::be key; // 0x0 diff --git a/src/xenia/kernel/xam/apps/xam_app.cc b/src/xenia/kernel/xam/apps/xam_app.cc index a565a49ac..664e1e60f 100644 --- a/src/xenia/kernel/xam/apps/xam_app.cc +++ b/src/xenia/kernel/xam/apps/xam_app.cc @@ -59,13 +59,8 @@ X_HRESULT XamApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr, uint32_t item_count = 0; auto result = e->WriteItems(data->buffer_ptr, buffer, data->buffer_size, &item_count); - assert_true(XSUCCEEDED(result)); - assert_true(item_count <= 1); - if (XSUCCEEDED(result) && item_count == 1) { - auto content_data = reinterpret_cast(buffer); - // TODO(gibbed): WTF? - *reinterpret_cast*>(&buffer[0x140]) = - content_data->title_id; + + if (result == X_ERROR_SUCCESS && item_count >= 1) { if (data->length_ptr) { auto length_ptr = memory_->TranslateVirtual*>(data->length_ptr); diff --git a/src/xenia/kernel/xam/content_manager.cc b/src/xenia/kernel/xam/content_manager.cc index bdc09af8b..a4367a48b 100644 --- a/src/xenia/kernel/xam/content_manager.cc +++ b/src/xenia/kernel/xam/content_manager.cc @@ -31,7 +31,7 @@ static int content_device_id_ = 0; ContentPackage::ContentPackage(KernelState* kernel_state, const std::string_view root_name, - const ContentData& data, + const XCONTENT_AGGREGATE_DATA& data, const std::filesystem::path& package_path) : kernel_state_(kernel_state), root_name_(root_name) { device_path_ = fmt::format("\\Device\\Content\\{0}\\", ++content_device_id_); @@ -58,63 +58,49 @@ ContentManager::ContentManager(KernelState* kernel_state, ContentManager::~ContentManager() = default; std::filesystem::path ContentManager::ResolvePackageRoot( - uint32_t content_type) { - auto title_id = fmt::format("{:8X}", kernel_state_->title_id()); - - std::string type_name; - switch (content_type) { - case 1: - // Save games. - type_name = "00000001"; - break; - case 2: - // DLC from the marketplace. - type_name = "00000002"; - break; - case 3: - // Publisher content? - type_name = "00000003"; - break; - case 0x000D0000: - // ??? - type_name = "000D0000"; - break; - default: - assert_unhandled_case(data.content_type); - return std::filesystem::path(); + XContentType content_type, uint32_t title_id) { + if (title_id == kCurrentlyRunningTitleId) { + title_id = kernel_state_->title_id(); } + auto title_id_str = fmt::format("{:08X}", title_id); + auto content_type_str = fmt::format("{:08X}", uint32_t(content_type)); // Package root path: - // content_root/title_id/type_name/ - return root_path_ / title_id / type_name; + // content_root/title_id/content_type/ + return root_path_ / title_id_str / content_type_str; } std::filesystem::path ContentManager::ResolvePackagePath( - const ContentData& data) { + const XCONTENT_AGGREGATE_DATA& data) { // Content path: - // content_root/title_id/type_name/data_file_name/ - auto package_root = ResolvePackageRoot(data.content_type); - return package_root / xe::to_path(data.file_name); + // content_root/title_id/content_type/data_file_name/ + auto package_root = ResolvePackageRoot(data.content_type, data.title_id); + return package_root / xe::to_path(data.file_name()); } -std::vector ContentManager::ListContent(uint32_t device_id, - uint32_t content_type) { - std::vector result; +std::vector ContentManager::ListContent( + uint32_t device_id, XContentType content_type, uint32_t title_id) { + std::vector result; + + if (title_id == kCurrentlyRunningTitleId) { + title_id = kernel_state_->title_id(); + } // Search path: // content_root/title_id/type_name/* - auto package_root = ResolvePackageRoot(content_type); + auto package_root = ResolvePackageRoot(content_type, title_id); auto file_infos = xe::filesystem::ListFiles(package_root); for (const auto& file_info : file_infos) { if (file_info.type != xe::filesystem::FileInfo::Type::kDirectory) { // Directories only. continue; } - ContentData content_data; + XCONTENT_AGGREGATE_DATA content_data; content_data.device_id = device_id; content_data.content_type = content_type; - content_data.display_name = xe::path_to_utf16(file_info.name); - content_data.file_name = xe::path_to_utf8(file_info.name); + content_data.set_display_name(xe::path_to_utf16(file_info.name)); + content_data.set_file_name(xe::path_to_utf8(file_info.name)); + content_data.title_id = title_id; result.emplace_back(std::move(content_data)); } @@ -122,7 +108,7 @@ std::vector ContentManager::ListContent(uint32_t device_id, } std::unique_ptr ContentManager::ResolvePackage( - const std::string_view root_name, const ContentData& data) { + const std::string_view root_name, const XCONTENT_AGGREGATE_DATA& data) { auto package_path = ResolvePackagePath(data); if (!std::filesystem::exists(package_path)) { return nullptr; @@ -135,13 +121,13 @@ std::unique_ptr ContentManager::ResolvePackage( return package; } -bool ContentManager::ContentExists(const ContentData& data) { +bool ContentManager::ContentExists(const XCONTENT_AGGREGATE_DATA& data) { auto path = ResolvePackagePath(data); return std::filesystem::exists(path); } X_RESULT ContentManager::CreateContent(const std::string_view root_name, - const ContentData& data) { + const XCONTENT_AGGREGATE_DATA& data) { auto global_lock = global_critical_region_.Acquire(); if (open_packages_.count(string_key(root_name))) { @@ -168,7 +154,7 @@ X_RESULT ContentManager::CreateContent(const std::string_view root_name, } X_RESULT ContentManager::OpenContent(const std::string_view root_name, - const ContentData& data) { + const XCONTENT_AGGREGATE_DATA& data) { auto global_lock = global_critical_region_.Acquire(); if (open_packages_.count(string_key(root_name))) { @@ -207,8 +193,8 @@ X_RESULT ContentManager::CloseContent(const std::string_view root_name) { return X_ERROR_SUCCESS; } -X_RESULT ContentManager::GetContentThumbnail(const ContentData& data, - std::vector* buffer) { +X_RESULT ContentManager::GetContentThumbnail( + const XCONTENT_AGGREGATE_DATA& data, std::vector* buffer) { auto global_lock = global_critical_region_.Acquire(); auto package_path = ResolvePackagePath(data); auto thumb_path = package_path / kThumbnailFileName; @@ -226,8 +212,8 @@ X_RESULT ContentManager::GetContentThumbnail(const ContentData& data, } } -X_RESULT ContentManager::SetContentThumbnail(const ContentData& data, - std::vector buffer) { +X_RESULT ContentManager::SetContentThumbnail( + const XCONTENT_AGGREGATE_DATA& data, std::vector buffer) { auto global_lock = global_critical_region_.Acquire(); auto package_path = ResolvePackagePath(data); std::filesystem::create_directories(package_path); @@ -242,7 +228,7 @@ X_RESULT ContentManager::SetContentThumbnail(const ContentData& data, } } -X_RESULT ContentManager::DeleteContent(const ContentData& data) { +X_RESULT ContentManager::DeleteContent(const XCONTENT_AGGREGATE_DATA& data) { auto global_lock = global_critical_region_.Acquire(); if (IsContentOpen(data)) { @@ -267,7 +253,7 @@ std::filesystem::path ContentManager::ResolveGameUserContentPath() { return root_path_ / title_id / kGameUserContentDirName / user_name; } -bool ContentManager::IsContentOpen(const ContentData& data) const { +bool ContentManager::IsContentOpen(const XCONTENT_AGGREGATE_DATA& data) const { return std::any_of(open_packages_.cbegin(), open_packages_.cend(), [data](std::pair content) { return data == content.second->GetPackageContentData(); diff --git a/src/xenia/kernel/xam/content_manager.h b/src/xenia/kernel/xam/content_manager.h index 5eef60b4f..4876bfd1c 100644 --- a/src/xenia/kernel/xam/content_manager.h +++ b/src/xenia/kernel/xam/content_manager.h @@ -31,107 +31,108 @@ namespace xe { namespace kernel { namespace xam { +// If set in XCONTENT_AGGREGATE_DATA, will be substituted with the running +// titles ID +// TODO: check if actual x360 kernel/xam has a value similar to this +constexpr uint32_t kCurrentlyRunningTitleId = 0xFFFFFFFF; + struct XCONTENT_DATA { be device_id; - be content_type; + be content_type; union { - be display_name[128]; - char16_t display_name_chars[128]; - }; - char file_name[42]; - uint8_t padding[2]; -}; -static_assert_size(XCONTENT_DATA, 308); + // this should be be, but that stops copy constructor being + // generated... + uint16_t uint[128]; + char16_t chars[128]; + } display_name_raw; -struct XCONTENT_AGGREGATE_DATA { - be device_id; - be content_type; - union { - be display_name[128]; - char16_t display_name_chars[128]; - }; - char file_name[42]; + char file_name_raw[42]; + + // Some games use this padding field as a null-terminator, as eg. + // DLC packages usually fill the entire file_name_raw array + // Not every game sets it to 0 though, so make sure any file_name_raw reads + // only go up to 42 chars! uint8_t padding[2]; + + bool operator==(const XCONTENT_DATA& other) const { + // Package is located via device_id/content_type/file_name, so only need to + // compare those + return device_id == other.device_id && content_type == other.content_type && + file_name() == other.file_name(); + } + + std::u16string display_name() const { + return load_and_swap(display_name_raw.uint); + } + + std::string file_name() const { + std::string value; + value.assign(file_name_raw, + std::min(strlen(file_name_raw), countof(file_name_raw))); + return value; + } + + void set_display_name(const std::u16string_view value) { + // Some games (eg Goldeneye XBLA) require multiple null-terminators for it + // to read the string properly, blanking the array should take care of that + + std::fill_n(display_name_raw.chars, countof(display_name_raw.chars), 0); + string_util::copy_and_swap_truncating(display_name_raw.chars, value, + countof(display_name_raw.chars)); + } + + void set_file_name(const std::string_view value) { + std::fill_n(file_name_raw, countof(file_name_raw), 0); + string_util::copy_maybe_truncating( + file_name_raw, value, xe::countof(file_name_raw)); + + // Some games rely on padding field acting as a null-terminator... + padding[0] = padding[1] = 0; + } +}; +static_assert_size(XCONTENT_DATA, 0x134); + +struct XCONTENT_AGGREGATE_DATA : XCONTENT_DATA { + be unk134; // some titles store XUID here? be title_id; -}; -static_assert_size(XCONTENT_AGGREGATE_DATA, 312); -struct ContentData { - uint32_t device_id; - uint32_t content_type; - std::u16string display_name; - std::string file_name; - - ContentData() = default; - - bool operator==(const ContentData& rhs) const { - return device_id == rhs.device_id && content_type == rhs.content_type && - file_name == rhs.file_name; + XCONTENT_AGGREGATE_DATA() = default; + XCONTENT_AGGREGATE_DATA(const XCONTENT_DATA& other) { + device_id = other.device_id; + content_type = other.content_type; + set_display_name(other.display_name()); + set_file_name(other.file_name()); + padding[0] = padding[1] = 0; + unk134 = 0; + title_id = kCurrentlyRunningTitleId; } - explicit ContentData(const XCONTENT_DATA& data) { - device_id = data.device_id; - content_type = data.content_type; - display_name = xe::load_and_swap(data.display_name); - file_name = xe::load_and_swap(data.file_name); - } - - void Write(XCONTENT_DATA* data) const { - data->device_id = device_id; - data->content_type = content_type; - xe::string_util::copy_and_swap_truncating( - data->display_name_chars, display_name, - xe::countof(data->display_name_chars)); - xe::string_util::copy_maybe_truncating< - string_util::Safety::IKnowWhatIAmDoing>(data->file_name, file_name, - xe::countof(data->file_name)); - } -}; - -struct ContentAggregateData { - uint32_t device_id; - uint32_t content_type; - std::u16string display_name; - std::string file_name; - uint32_t title_id; - - ContentAggregateData() = default; - - explicit ContentAggregateData(const XCONTENT_AGGREGATE_DATA& data) { - device_id = data.device_id; - content_type = data.content_type; - display_name = xe::load_and_swap(data.display_name); - file_name = xe::load_and_swap(data.file_name); - title_id = data.title_id; - } - - void Write(XCONTENT_AGGREGATE_DATA* data) const { - data->device_id = device_id; - data->content_type = content_type; - xe::string_util::copy_and_swap_truncating( - data->display_name_chars, display_name, - xe::countof(data->display_name_chars)); - xe::string_util::copy_maybe_truncating< - string_util::Safety::IKnowWhatIAmDoing>(data->file_name, file_name, - xe::countof(data->file_name)); - data->title_id = title_id; + bool operator==(const XCONTENT_AGGREGATE_DATA& other) const { + // Package is located via device_id/title_id/content_type/file_name, so only + // need to compare those + return device_id == other.device_id && title_id == other.title_id && + content_type == other.content_type && + file_name() == other.file_name(); } }; +static_assert_size(XCONTENT_AGGREGATE_DATA, 0x148); class ContentPackage { public: ContentPackage(KernelState* kernel_state, const std::string_view root_name, - const ContentData& data, + const XCONTENT_AGGREGATE_DATA& data, const std::filesystem::path& package_path); ~ContentPackage(); - const ContentData& GetPackageContentData() const { return content_data_; } + const XCONTENT_AGGREGATE_DATA& GetPackageContentData() const { + return content_data_; + } private: KernelState* kernel_state_; std::string root_name_; std::string device_path_; - ContentData content_data_; + XCONTENT_AGGREGATE_DATA content_data_; }; class ContentManager { @@ -140,30 +141,32 @@ class ContentManager { const std::filesystem::path& root_path); ~ContentManager(); - std::vector ListContent(uint32_t device_id, - uint32_t content_type); + std::vector ListContent(uint32_t device_id, + XContentType content_type, + uint32_t title_id = -1); std::unique_ptr ResolvePackage( - const std::string_view root_name, const ContentData& data); + const std::string_view root_name, const XCONTENT_AGGREGATE_DATA& data); - bool ContentExists(const ContentData& data); + bool ContentExists(const XCONTENT_AGGREGATE_DATA& data); X_RESULT CreateContent(const std::string_view root_name, - const ContentData& data); + const XCONTENT_AGGREGATE_DATA& data); X_RESULT OpenContent(const std::string_view root_name, - const ContentData& data); + const XCONTENT_AGGREGATE_DATA& data); X_RESULT CloseContent(const std::string_view root_name); - X_RESULT GetContentThumbnail(const ContentData& data, + X_RESULT GetContentThumbnail(const XCONTENT_AGGREGATE_DATA& data, std::vector* buffer); - X_RESULT SetContentThumbnail(const ContentData& data, + X_RESULT SetContentThumbnail(const XCONTENT_AGGREGATE_DATA& data, std::vector buffer); - X_RESULT DeleteContent(const ContentData& data); + X_RESULT DeleteContent(const XCONTENT_AGGREGATE_DATA& data); std::filesystem::path ResolveGameUserContentPath(); - bool IsContentOpen(const ContentData& data) const; + bool IsContentOpen(const XCONTENT_AGGREGATE_DATA& data) const; void CloseOpenedFilesFromContent(const std::string_view root_name); private: - std::filesystem::path ResolvePackageRoot(uint32_t content_type); - std::filesystem::path ResolvePackagePath(const ContentData& data); + std::filesystem::path ResolvePackageRoot(XContentType content_type, + uint32_t title_id = -1); + std::filesystem::path ResolvePackagePath(const XCONTENT_AGGREGATE_DATA& data); KernelState* kernel_state_; std::filesystem::path root_path_; diff --git a/src/xenia/kernel/xam/xam_content.cc b/src/xenia/kernel/xam/xam_content.cc index 0f8a365e8..b708b163b 100644 --- a/src/xenia/kernel/xam/xam_content.cc +++ b/src/xenia/kernel/xam/xam_content.cc @@ -96,11 +96,12 @@ dword_result_t XamContentCreateEnumerator(dword_t user_index, dword_t device_id, if (!device_info || device_info->device_id == DummyDeviceId::HDD) { // Get all content data. auto content_datas = kernel_state()->content_manager()->ListContent( - static_cast(DummyDeviceId::HDD), content_type); + static_cast(DummyDeviceId::HDD), + XContentType(uint32_t(content_type))); for (const auto& content_data : content_datas) { auto item = reinterpret_cast(e->AppendItem()); assert_not_null(item); - content_data.Write(item); + *item = content_data; } } @@ -116,15 +117,23 @@ dword_result_t XamContentCreateEnumerator(dword_t user_index, dword_t device_id, } DECLARE_XAM_EXPORT1(XamContentCreateEnumerator, kContent, kImplemented); -dword_result_t XamContentCreateEx(dword_t user_index, lpstring_t root_name, - lpvoid_t content_data_ptr, dword_t flags, +dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name, + lpvoid_t content_data_ptr, + dword_t content_data_size, dword_t flags, lpdword_t disposition_ptr, lpdword_t license_mask_ptr, dword_t cache_size, qword_t content_size, lpvoid_t overlapped_ptr) { X_RESULT result = X_ERROR_INVALID_PARAMETER; - auto content_data = - static_cast(*content_data_ptr.as()); + XCONTENT_AGGREGATE_DATA content_data; + if (content_data_size == sizeof(XCONTENT_DATA)) { + content_data = *content_data_ptr.as(); + } else if (content_data_size == sizeof(XCONTENT_AGGREGATE_DATA)) { + content_data = *content_data_ptr.as(); + } else { + assert_always(); + return result; + } auto content_manager = kernel_state()->content_manager(); bool create = false; @@ -210,6 +219,18 @@ dword_result_t XamContentCreateEx(dword_t user_index, lpstring_t root_name, return result; } } + +dword_result_t XamContentCreateEx(dword_t user_index, lpstring_t root_name, + lpvoid_t content_data_ptr, dword_t flags, + lpdword_t disposition_ptr, + lpdword_t license_mask_ptr, + dword_t cache_size, qword_t content_size, + lpvoid_t overlapped_ptr) { + return xeXamContentCreate(user_index, root_name, content_data_ptr, + sizeof(XCONTENT_DATA), flags, disposition_ptr, + license_mask_ptr, cache_size, content_size, + overlapped_ptr); +} DECLARE_XAM_EXPORT1(XamContentCreateEx, kContent, kImplemented); dword_result_t XamContentCreate(dword_t user_index, lpstring_t root_name, @@ -217,9 +238,9 @@ dword_result_t XamContentCreate(dword_t user_index, lpstring_t root_name, lpdword_t disposition_ptr, lpdword_t license_mask_ptr, lpvoid_t overlapped_ptr) { - return XamContentCreateEx(user_index, root_name, content_data_ptr, flags, - disposition_ptr, license_mask_ptr, 0, 0, - overlapped_ptr); + return xeXamContentCreate(user_index, root_name, content_data_ptr, + sizeof(XCONTENT_DATA), flags, disposition_ptr, + license_mask_ptr, 0, 0, overlapped_ptr); } DECLARE_XAM_EXPORT1(XamContentCreate, kContent, kImplemented); @@ -227,7 +248,8 @@ dword_result_t XamContentCreateInternal( lpstring_t root_name, lpvoid_t content_data_ptr, dword_t flags, lpdword_t disposition_ptr, lpdword_t license_mask_ptr, dword_t cache_size, qword_t content_size, lpvoid_t overlapped_ptr) { - return XamContentCreateEx(0xFE, root_name, content_data_ptr, flags, + return xeXamContentCreate(0xFE, root_name, content_data_ptr, + sizeof(XCONTENT_AGGREGATE_DATA), flags, disposition_ptr, license_mask_ptr, cache_size, content_size, overlapped_ptr); } @@ -277,14 +299,13 @@ dword_result_t XamContentGetCreator(dword_t user_index, lpunknown_t overlapped_ptr) { auto result = X_ERROR_SUCCESS; - auto content_data = - static_cast(*content_data_ptr.as()); + XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as(); bool content_exists = kernel_state()->content_manager()->ContentExists(content_data); if (content_exists) { - if (content_data.content_type == 1) { + if (content_data.content_type == XContentType::kSavedGame) { // User always creates saves. *is_creator_ptr = 1; if (creator_xuid_ptr) { @@ -316,8 +337,7 @@ dword_result_t XamContentGetThumbnail(dword_t user_index, lpunknown_t overlapped_ptr) { assert_not_null(buffer_size_ptr); uint32_t buffer_size = *buffer_size_ptr; - auto content_data = - static_cast(*content_data_ptr.as()); + XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as(); // Get thumbnail (if it exists). std::vector buffer; @@ -353,8 +373,7 @@ dword_result_t XamContentSetThumbnail(dword_t user_index, lpvoid_t content_data_ptr, lpvoid_t buffer_ptr, dword_t buffer_size, lpunknown_t overlapped_ptr) { - auto content_data = - static_cast(*content_data_ptr.as()); + XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as(); // Buffer is PNG data. auto buffer = std::vector((uint8_t*)buffer_ptr, @@ -373,8 +392,7 @@ DECLARE_XAM_EXPORT1(XamContentSetThumbnail, kContent, kImplemented); dword_result_t XamContentDelete(dword_t user_index, lpvoid_t content_data_ptr, lpunknown_t overlapped_ptr) { - auto content_data = - static_cast(*content_data_ptr.as()); + XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as(); auto result = kernel_state()->content_manager()->DeleteContent(content_data); diff --git a/src/xenia/kernel/xam/xam_content_aggregate.cc b/src/xenia/kernel/xam/xam_content_aggregate.cc index 39f4bb2b5..3de7b1566 100644 --- a/src/xenia/kernel/xam/xam_content_aggregate.cc +++ b/src/xenia/kernel/xam/xam_content_aggregate.cc @@ -11,6 +11,7 @@ #include "xenia/base/math.h" #include "xenia/base/string_util.h" #include "xenia/kernel/kernel_state.h" +#include "xenia/kernel/user_module.h" #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xam/xam_content_device.h" #include "xenia/kernel/xam/xam_private.h" @@ -22,14 +23,15 @@ namespace xe { namespace kernel { namespace xam { -void AddODDContentTest(object_ref e, uint32_t content_type) { +void AddODDContentTest(object_ref e, + XContentType content_type) { auto root_entry = kernel_state()->file_system()->ResolvePath( "game:\\Content\\0000000000000000"); if (!root_entry) { return; } - auto content_type_path = fmt::format("{:08X}", content_type); + auto content_type_path = fmt::format("{:08X}", uint32_t(content_type)); xe::filesystem::WildcardEngine title_find_engine; title_find_engine.SetRule("????????"); @@ -62,14 +64,11 @@ void AddODDContentTest(object_ref e, uint32_t content_type) { auto item = reinterpret_cast(e->AppendItem()); assert_not_null(item); - ContentAggregateData content_aggregate_data = {}; - content_aggregate_data.device_id = - static_cast(DummyDeviceId::ODD); - content_aggregate_data.content_type = content_type; - content_aggregate_data.display_name = to_utf16(content_entry->name()); - content_aggregate_data.file_name = content_entry->name(); - content_aggregate_data.title_id = title_id; - content_aggregate_data.Write(item); + item->device_id = static_cast(DummyDeviceId::ODD); + item->content_type = content_type; + item->set_display_name(to_utf16(content_entry->name())); + item->set_file_name(content_entry->name()); + item->title_id = title_id; } } } @@ -98,25 +97,34 @@ dword_result_t XamContentAggregateCreateEnumerator(qword_t xuid, extra->magic = kXObjSignature; extra->handle = e->handle(); + auto content_type_enum = XContentType(uint32_t(content_type)); + if (!device_info || device_info->device_type == DeviceType::HDD) { - // Get all content data. - auto content_datas = kernel_state()->content_manager()->ListContent( - static_cast(DummyDeviceId::HDD), content_type); - for (const auto& content_data : content_datas) { - auto item = reinterpret_cast(e->AppendItem()); - assert_not_null(item); - ContentAggregateData content_aggregate_data = {}; - content_aggregate_data.device_id = content_data.device_id; - content_aggregate_data.content_type = content_data.content_type; - content_aggregate_data.display_name = content_data.display_name; - content_aggregate_data.file_name = content_data.file_name; - content_aggregate_data.title_id = kernel_state()->title_id(); - content_aggregate_data.Write(item); + // Fetch any alternate title IDs defined in the XEX header + // (used by games to load saves from other titles, etc) + std::vector title_ids{kCurrentlyRunningTitleId}; + auto exe_module = kernel_state()->GetExecutableModule(); + if (exe_module && exe_module->xex_module()) { + const auto& alt_ids = exe_module->xex_module()->opt_alternate_title_ids(); + std::copy(alt_ids.cbegin(), alt_ids.cend(), + std::back_inserter(title_ids)); + } + + for (auto& title_id : title_ids) { + // Get all content data. + auto content_datas = kernel_state()->content_manager()->ListContent( + static_cast(DummyDeviceId::HDD), content_type_enum, + title_id); + for (const auto& content_data : content_datas) { + auto item = reinterpret_cast(e->AppendItem()); + assert_not_null(item); + *item = content_data; + } } } if (!device_info || device_info->device_type == DeviceType::ODD) { - AddODDContentTest(e, content_type); + AddODDContentTest(e, content_type_enum); } XELOGD("XamContentAggregateCreateEnumerator: added {} items to enumerator", diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index 2d98aa1e0..11457e822 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -546,7 +546,12 @@ DECLARE_XAM_EXPORT1(XamUserAreUsersFriends, kUserProfiles, kStub); dword_result_t XamShowSigninUI(dword_t unk, dword_t unk_mask) { // Mask values vary. Probably matching user types? Local/remote? - // Games seem to sit and loop until we trigger this notification. + + // To fix game modes that display a 4 profile signin UI (even if playing alone): + // XN_SYS_SIGNINCHANGED + kernel_state()->BroadcastNotification(0x0000000A, 1); + // Games seem to sit and loop until we trigger this notification: + // XN_SYS_UI (off) kernel_state()->BroadcastNotification(0x00000009, 0); return X_ERROR_SUCCESS; } diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc index bbe78ec87..3a323d579 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc @@ -960,14 +960,14 @@ void KeReleaseSpinLockFromRaisedIrql(lpdword_t lock_ptr) { DECLARE_XBOXKRNL_EXPORT2(KeReleaseSpinLockFromRaisedIrql, kThreading, kImplemented, kHighFrequency); -void KeEnterCriticalRegion() { XThread::EnterCriticalRegion(); } +void KeEnterCriticalRegion() { + XThread::GetCurrentThread()->EnterCriticalRegion(); +} DECLARE_XBOXKRNL_EXPORT2(KeEnterCriticalRegion, kThreading, kImplemented, kHighFrequency); void KeLeaveCriticalRegion() { - XThread::LeaveCriticalRegion(); - - XThread::GetCurrentThread()->CheckApcs(); + XThread::GetCurrentThread()->LeaveCriticalRegion(); } DECLARE_XBOXKRNL_EXPORT2(KeLeaveCriticalRegion, kThreading, kImplemented, kHighFrequency); diff --git a/src/xenia/kernel/xthread.cc b/src/xenia/kernel/xthread.cc index 8776b43dc..e11a94296 100644 --- a/src/xenia/kernel/xthread.cc +++ b/src/xenia/kernel/xthread.cc @@ -226,7 +226,7 @@ void XThread::InitializeGuestObject() { } bool XThread::AllocateStack(uint32_t size) { - auto heap = memory()->LookupHeap(0x40000000); + auto heap = memory()->LookupHeap(kStackAddressRangeBegin); auto alignment = heap->page_size(); auto padding = heap->page_size() * 2; // Guard page size * 2 @@ -234,10 +234,10 @@ bool XThread::AllocateStack(uint32_t size) { auto actual_size = size + padding; uint32_t address = 0; - if (!heap->AllocRange(0x40000000, 0x7F000000, actual_size, alignment, - kMemoryAllocationReserve | kMemoryAllocationCommit, - kMemoryProtectRead | kMemoryProtectWrite, false, - &address)) { + if (!heap->AllocRange( + kStackAddressRangeBegin, kStackAddressRangeEnd, actual_size, + alignment, kMemoryAllocationReserve | kMemoryAllocationCommit, + kMemoryProtectRead | kMemoryProtectWrite, false, &address)) { return false; } @@ -258,7 +258,7 @@ bool XThread::AllocateStack(uint32_t size) { void XThread::FreeStack() { if (stack_alloc_base_) { - auto heap = memory()->LookupHeap(0x40000000); + auto heap = memory()->LookupHeap(kStackAddressRangeBegin); heap->Release(stack_alloc_base_); stack_alloc_base_ = 0; @@ -578,11 +578,15 @@ void XThread::Reenter(uint32_t address) { } void XThread::EnterCriticalRegion() { - xe::global_critical_region::mutex().lock(); + guest_object()->apc_disable_count--; } void XThread::LeaveCriticalRegion() { - xe::global_critical_region::mutex().unlock(); + auto kthread = guest_object(); + auto apc_disable_count = ++kthread->apc_disable_count; + if (apc_disable_count == 0) { + CheckApcs(); + } } uint32_t XThread::RaiseIrql(uint32_t new_irql) { @@ -593,11 +597,11 @@ void XThread::LowerIrql(uint32_t new_irql) { irql_ = new_irql; } void XThread::CheckApcs() { DeliverAPCs(); } -void XThread::LockApc() { EnterCriticalRegion(); } +void XThread::LockApc() { global_critical_region_.mutex().lock(); } void XThread::UnlockApc(bool queue_delivery) { bool needs_apc = apc_list_.HasPending(); - LeaveCriticalRegion(); + global_critical_region_.mutex().unlock(); if (needs_apc && queue_delivery) { thread_->QueueUserCallback([this]() { DeliverAPCs(); }); } @@ -632,7 +636,8 @@ void XThread::DeliverAPCs() { // https://www.drdobbs.com/inside-nts-asynchronous-procedure-call/184416590?pgno=7 auto processor = kernel_state()->processor(); LockApc(); - while (apc_list_.HasPending()) { + auto kthread = guest_object(); + while (apc_list_.HasPending() && kthread->apc_disable_count == 0) { // Get APC entry (offset for LIST_ENTRY offset) and cache what we need. // Calling the routine may delete the memory/overwrite it. uint32_t apc_ptr = apc_list_.Shift() - 8; diff --git a/src/xenia/kernel/xthread.h b/src/xenia/kernel/xthread.h index 045769bda..9eef807b2 100644 --- a/src/xenia/kernel/xthread.h +++ b/src/xenia/kernel/xthread.h @@ -113,7 +113,9 @@ struct X_KTHREAD { uint8_t unk_8B; // 0x8B uint8_t unk_8C[0x10]; // 0x8C xe::be unk_9C; // 0x9C - uint8_t unk_A0[0x1C]; // 0xA0 + uint8_t unk_A0[0x10]; // 0xA0 + int32_t apc_disable_count; // 0xB0 + uint8_t unk_B4[0x8]; // 0xB4 uint8_t suspend_count; // 0xBC uint8_t unk_BD; // 0xBD uint8_t unk_BE; // 0xBE @@ -147,6 +149,9 @@ class XThread : public XObject, public cpu::Thread { public: static const XObject::Type kObjectType = XObject::Type::Thread; + static constexpr uint32_t kStackAddressRangeBegin = 0x70000000; + static constexpr uint32_t kStackAddressRangeEnd = 0x7F000000; + struct CreationParams { uint32_t stack_size; uint32_t xapi_thread_startup; @@ -192,8 +197,8 @@ class XThread : public XObject, public cpu::Thread { virtual void Reenter(uint32_t address); - static void EnterCriticalRegion(); - static void LeaveCriticalRegion(); + void EnterCriticalRegion(); + void LeaveCriticalRegion(); uint32_t RaiseIrql(uint32_t new_irql); void LowerIrql(uint32_t new_irql); diff --git a/src/xenia/vfs/devices/stfs_xbox.h b/src/xenia/vfs/devices/stfs_xbox.h index 9378f1a86..ce755ea9c 100644 --- a/src/xenia/vfs/devices/stfs_xbox.h +++ b/src/xenia/vfs/devices/stfs_xbox.h @@ -36,43 +36,6 @@ enum class XContentPackageType : uint32_t { kLive = 0x4C495645, }; -enum XContentType : uint32_t { - kSavedGame = 0x00000001, - kMarketplaceContent = 0x00000002, - kPublisher = 0x00000003, - kXbox360Title = 0x00001000, - kIptvPauseBuffer = 0x00002000, - kXNACommunity = 0x00003000, - kInstalledGame = 0x00004000, - kXboxTitle = 0x00005000, - kSocialTitle = 0x00006000, - kGamesOnDemand = 0x00007000, - kSUStoragePack = 0x00008000, - kAvatarItem = 0x00009000, - kProfile = 0x00010000, - kGamerPicture = 0x00020000, - kTheme = 0x00030000, - kCacheFile = 0x00040000, - kStorageDownload = 0x00050000, - kXboxSavedGame = 0x00060000, - kXboxDownload = 0x00070000, - kGameDemo = 0x00080000, - kVideo = 0x00090000, - kGameTitle = 0x000A0000, - kInstaller = 0x000B0000, - kGameTrailer = 0x000C0000, - kArcadeTitle = 0x000D0000, - kXNA = 0x000E0000, - kLicenseStore = 0x000F0000, - kMovie = 0x00100000, - kTV = 0x00200000, - kMusicVideo = 0x00300000, - kGameVideo = 0x00400000, - kPodcastVideo = 0x00500000, - kViralVideo = 0x00600000, - kCommunityGame = 0x02000000, -}; - enum class XContentVolumeType : uint32_t { kStfs = 0, kSvod = 1, diff --git a/src/xenia/xbox.h b/src/xenia/xbox.h index bdf6e3f38..d27d37661 100644 --- a/src/xenia/xbox.h +++ b/src/xenia/xbox.h @@ -344,6 +344,43 @@ enum class XLanguage : uint32_t { kMaxLanguages = 13 }; +enum class XContentType : uint32_t { + kSavedGame = 0x00000001, + kMarketplaceContent = 0x00000002, + kPublisher = 0x00000003, + kXbox360Title = 0x00001000, + kIptvPauseBuffer = 0x00002000, + kXNACommunity = 0x00003000, + kInstalledGame = 0x00004000, + kXboxTitle = 0x00005000, + kSocialTitle = 0x00006000, + kGamesOnDemand = 0x00007000, + kSUStoragePack = 0x00008000, + kAvatarItem = 0x00009000, + kProfile = 0x00010000, + kGamerPicture = 0x00020000, + kTheme = 0x00030000, + kCacheFile = 0x00040000, + kStorageDownload = 0x00050000, + kXboxSavedGame = 0x00060000, + kXboxDownload = 0x00070000, + kGameDemo = 0x00080000, + kVideo = 0x00090000, + kGameTitle = 0x000A0000, + kInstaller = 0x000B0000, + kGameTrailer = 0x000C0000, + kArcadeTitle = 0x000D0000, + kXNA = 0x000E0000, + kLicenseStore = 0x000F0000, + kMovie = 0x00100000, + kTV = 0x00200000, + kMusicVideo = 0x00300000, + kGameVideo = 0x00400000, + kPodcastVideo = 0x00500000, + kViralVideo = 0x00600000, + kCommunityGame = 0x02000000, +}; + } // namespace xe // clang-format on diff --git a/tools/build/scripts/test_suite.lua b/tools/build/scripts/test_suite.lua index afb8a9b31..563624c09 100644 --- a/tools/build/scripts/test_suite.lua +++ b/tools/build/scripts/test_suite.lua @@ -34,6 +34,11 @@ local function combined_test_suite(test_suite_name, project_root, base_path, con project_root.."/src/xenia/base/main_"..platform_suffix..".cc", base_path.."/**_test.cc", }) + filter("toolset:msc") + -- Edit and Continue in MSVC can cause the __LINE__ macro to produce + -- invalid values, which breaks the usability of Catch2 output on + -- failed tests. + editAndContinue("Off") end local function split_test_suite(test_suite_name, project_root, base_path, config) @@ -58,6 +63,11 @@ local function split_test_suite(test_suite_name, project_root, base_path, config project_root.."/"..build_tools_src.."/test_suite_main.cc", file_path, }) + filter("toolset:msc") + -- Edit and Continue in MSVC can cause the __LINE__ macro to produce + -- invalid values, which breaks the usability of Catch2 output on + -- failed tests. + editAndContinue("Off") end end