Merge branch 'master' into vulkan

This commit is contained in:
Triang3l 2021-06-26 14:12:21 +03:00
commit cccc707048
19 changed files with 430 additions and 273 deletions

View File

@ -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: You will also need some development libraries. To get them on an Ubuntu system:
```bash ```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. In addition, you will need up to date Vulkan libraries and drivers for your hardware, which most distributions have in their standard repositories nowadays.

View File

@ -388,20 +388,45 @@ TEST_CASE("UTF-8 Fix Path Separators", "[utf8]") {
TEST_CASE("UTF-8 Find Name From Path", "[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, "///", "");
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///", "qux");
TEST_PATH(utf8::find_name_from_path, "foo/bar/baz/qux.txt", "qux.txt"); 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, "ほげ/ぴよ/ふが/ほげら/ほげほげ///",
"ほげほげ");
TEST_PATH(utf8::find_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", TEST_PATH(utf8::find_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ.txt",
"ほげほげ.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, "/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, "/ほげ/ぴよ/ふが/ほげら/ほげほげ///",
"ほげほげ");
TEST_PATH(utf8::find_name_from_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", TEST_PATH(utf8::find_name_from_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt",
"ほげほげ.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:/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:/ほげ/ぴよ/ふが/ほげら/ほげほげ///",
"ほげほげ");
TEST_PATH(utf8::find_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", TEST_PATH(utf8::find_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt",
"ほげほげ.txt"); "ほげほげ.txt");
TEST_PATH(utf8::find_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら.ほげほげ", 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_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.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, "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, TEST_PATH(utf8::find_base_name_from_path,
"ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", "ほげほげ"); "ほげ/ぴよ/ふが/ほげら/ほげほげ.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, "ほげ/ぴよ/ふが/ほげら.ほげほげ", 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.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, "/foo/bar/baz/qux///", "qux");
TEST_PATH(utf8::find_base_name_from_path, TEST_PATH(utf8::find_base_name_from_path,
"/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", "ほげほげ"); "/ほげ/ぴよ/ふが/ほげら/ほげほげ.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, "/ほげ/ぴよ/ふが/ほげら.ほげほげ", 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.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:/foo/bar/baz/qux///", "qux");
TEST_PATH(utf8::find_base_name_from_path, TEST_PATH(utf8::find_base_name_from_path,
"X:/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", "ほげほげ"); "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", "ほげほげ");
TEST_PATH(utf8::find_base_name_from_path, TEST_PATH(utf8::find_base_name_from_path,
"X:/ほげ/ぴよ/ふが/ほげら/ほげほげ/", "ほげほげ"); "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:/ほげ/ぴよ/ふが/ほげら.ほげほげ", 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, "//", ""); 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//", "");
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//", "/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, "/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, "/ほげ/ぴよ/ふが/ほげら/ほげほげ//",
"/ほげ/ぴよ/ふが/ほげら");
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/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//", "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, "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, "ほげ/ぴよ/ふが/ほげら/ほげほげ//",
"ほげ/ぴよ/ふが/ほげら");
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://", "");
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//", "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//", "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:/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:/ほげ/ぴよ/ふが/ほげら/ほげほげ", TEST_PATH(utf8::find_base_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ",
"X:/ほげ/ぴよ/ふが/ほげら"); "X:/ほげ/ぴよ/ふが/ほげら");
TEST_PATH(utf8::find_base_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ/", TEST_PATH(utf8::find_base_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ/",
"X:/ほげ/ぴよ/ふが/ほげら"); "X:/ほげ/ぴよ/ふが/ほげら");
TEST_PATH(utf8::find_base_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ//",
"X:/ほげ/ぴよ/ふが/ほげら");
TEST_PATH(utf8::find_base_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ///",
"X:/ほげ/ぴよ/ふが/ほげら");
} }
// TODO(gibbed): find_base_guest_path // 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_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/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/", "foo/baz/qux"); TEST_PATH(utf8::canonicalize_path, "foo/./baz/qux/", "foo/baz/qux");
TEST_PATH(utf8::canonicalize_path, "foo/../baz/qux", "baz/qux"); TEST_PATH(utf8::canonicalize_path, "foo/../baz/qux", "baz/qux");

View File

@ -582,43 +582,40 @@ std::string find_name_from_path(const std::string_view path,
auto it = end; auto it = end;
--it; --it;
// path is padded with separator // skip trailing separators at the end of the path
size_t padding = 0; while (*it == uint32_t(separator)) {
if (*it == uint32_t(separator)) {
if (it == begin) { if (it == begin) {
// path is all separators, name is empty
return std::string(); return std::string();
} }
--it; --it;
padding = 1;
} }
// path is just separator // update end so it is before any trailing separators
end = std::next(it);
// skip non-separators
while (*it != uint32_t(separator)) {
if (it == begin) { if (it == begin) {
return std::string();
}
// search for separator
while (it != begin) {
if (*it == uint32_t(separator)) {
break; break;
} }
--it; --it;
} }
// no separator -- copy entire string (except trailing separator) // if the iterator is on a separator, advance
if (it == begin) { if (*it == uint32_t(separator)) {
return std::string(path.substr(0, path.size() - padding)); ++it;
} }
auto length = byte_length(std::next(it), end); auto offset = byte_length(begin, it);
auto offset = path.length() - length; auto length = byte_length(it, end);
return std::string(path.substr(offset, length - padding)); return std::string(path.substr(offset, length));
} }
std::string find_base_name_from_path(const std::string_view path, std::string find_base_name_from_path(const std::string_view path,
char32_t separator) { char32_t separator) {
auto name = find_name_from_path(path, separator); auto name = find_name_from_path(path, separator);
if (!name.size()) { if (!name.length()) {
return std::string(); return std::string();
} }
@ -653,28 +650,34 @@ std::string find_base_path(const std::string_view path, char32_t separator) {
auto it = end; auto it = end;
--it; --it;
// skip trailing separator // skip trailing separators at the end of the path
if (*it == uint32_t(separator)) { while (*it == uint32_t(separator)) {
if (it == begin) { if (it == begin) {
return std::string(); return std::string();
} }
--it; --it;
} }
while (it != begin) { // skip non-separators
if (*it == uint32_t(separator)) { while (*it != uint32_t(separator)) {
break; if (it == begin) {
// there are no separators, base path is empty
return std::string();
} }
--it; --it;
} }
// skip trailing separators at the end of the base path
while (*it == uint32_t(separator)) {
if (it == begin) { if (it == begin) {
// base path is all separators, base path is empty
return std::string(); return std::string();
} }
--it;
}
auto length = byte_length(it, end); auto length = byte_length(begin, std::next(it));
auto offset = path.length() - length; return std::string(path.substr(0, length));
return std::string(path.substr(0, offset));
} }
std::string canonicalize_path(const std::string_view path, char32_t separator) { std::string canonicalize_path(const std::string_view path, char32_t separator) {

View File

@ -966,6 +966,16 @@ bool XexModule::LoadContinue() {
return false; 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. // Scan and find the low/high addresses.
// All code sections are continuous, so this should be easy. // All code sections are continuous, so this should be easy.
auto heap = memory()->LookupHeap(base_address_); auto heap = memory()->LookupHeap(base_address_);

View File

@ -107,6 +107,10 @@ class XexModule : public xe::cpu::Module {
return retval; return retval;
} }
std::vector<uint32_t> opt_alternate_title_ids() const {
return opt_alternate_title_ids_;
}
const uint32_t base_address() const { return base_address_; } const uint32_t base_address() const { return base_address_; }
const bool is_dev_kit() const { return is_dev_kit_; } 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 import_libs_; // pre-loaded import libraries for ease of use
std::vector<PESection> pe_sections_; std::vector<PESection> pe_sections_;
// XEX_HEADER_ALTERNATE_TITLE_IDS loaded into a safe std::vector
std::vector<uint32_t> opt_alternate_title_ids_;
uint8_t session_key_[0x10]; uint8_t session_key_[0x10];
bool is_dev_kit_ = false; bool is_dev_kit_ = false;

View File

@ -205,6 +205,10 @@ X_STATUS ObjectTable::RemoveHandle(X_HANDLE handle) {
XELOGI("Removed handle:{:08X} for {}", handle, typeid(*object).name()); 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. // Release now that the object has been removed from the table.
object->Release(); object->Release();
} }

View File

@ -529,6 +529,13 @@ struct xex2_import_library {
} }
}; };
struct xex2_opt_generic_u32 {
xe::be<uint32_t> size;
xe::be<uint32_t> values[1];
uint32_t count() const { return (size - 4) / 4; }
};
struct xex2_opt_header { struct xex2_opt_header {
xe::be<uint32_t> key; // 0x0 xe::be<uint32_t> key; // 0x0

View File

@ -59,13 +59,8 @@ X_HRESULT XamApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
uint32_t item_count = 0; uint32_t item_count = 0;
auto result = e->WriteItems(data->buffer_ptr, buffer, data->buffer_size, auto result = e->WriteItems(data->buffer_ptr, buffer, data->buffer_size,
&item_count); &item_count);
assert_true(XSUCCEEDED(result));
assert_true(item_count <= 1); if (result == X_ERROR_SUCCESS && item_count >= 1) {
if (XSUCCEEDED(result) && item_count == 1) {
auto content_data = reinterpret_cast<XCONTENT_AGGREGATE_DATA*>(buffer);
// TODO(gibbed): WTF?
*reinterpret_cast<be<uint32_t>*>(&buffer[0x140]) =
content_data->title_id;
if (data->length_ptr) { if (data->length_ptr) {
auto length_ptr = auto length_ptr =
memory_->TranslateVirtual<be<uint32_t>*>(data->length_ptr); memory_->TranslateVirtual<be<uint32_t>*>(data->length_ptr);

View File

@ -31,7 +31,7 @@ static int content_device_id_ = 0;
ContentPackage::ContentPackage(KernelState* kernel_state, ContentPackage::ContentPackage(KernelState* kernel_state,
const std::string_view root_name, const std::string_view root_name,
const ContentData& data, const XCONTENT_AGGREGATE_DATA& data,
const std::filesystem::path& package_path) const std::filesystem::path& package_path)
: kernel_state_(kernel_state), root_name_(root_name) { : kernel_state_(kernel_state), root_name_(root_name) {
device_path_ = fmt::format("\\Device\\Content\\{0}\\", ++content_device_id_); device_path_ = fmt::format("\\Device\\Content\\{0}\\", ++content_device_id_);
@ -58,63 +58,49 @@ ContentManager::ContentManager(KernelState* kernel_state,
ContentManager::~ContentManager() = default; ContentManager::~ContentManager() = default;
std::filesystem::path ContentManager::ResolvePackageRoot( std::filesystem::path ContentManager::ResolvePackageRoot(
uint32_t content_type) { XContentType content_type, uint32_t title_id) {
auto title_id = fmt::format("{:8X}", kernel_state_->title_id()); if (title_id == kCurrentlyRunningTitleId) {
title_id = 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();
} }
auto title_id_str = fmt::format("{:08X}", title_id);
auto content_type_str = fmt::format("{:08X}", uint32_t(content_type));
// Package root path: // Package root path:
// content_root/title_id/type_name/ // content_root/title_id/content_type/
return root_path_ / title_id / type_name; return root_path_ / title_id_str / content_type_str;
} }
std::filesystem::path ContentManager::ResolvePackagePath( std::filesystem::path ContentManager::ResolvePackagePath(
const ContentData& data) { const XCONTENT_AGGREGATE_DATA& data) {
// Content path: // Content path:
// content_root/title_id/type_name/data_file_name/ // content_root/title_id/content_type/data_file_name/
auto package_root = ResolvePackageRoot(data.content_type); auto package_root = ResolvePackageRoot(data.content_type, data.title_id);
return package_root / xe::to_path(data.file_name); return package_root / xe::to_path(data.file_name());
} }
std::vector<ContentData> ContentManager::ListContent(uint32_t device_id, std::vector<XCONTENT_AGGREGATE_DATA> ContentManager::ListContent(
uint32_t content_type) { uint32_t device_id, XContentType content_type, uint32_t title_id) {
std::vector<ContentData> result; std::vector<XCONTENT_AGGREGATE_DATA> result;
if (title_id == kCurrentlyRunningTitleId) {
title_id = kernel_state_->title_id();
}
// Search path: // Search path:
// content_root/title_id/type_name/* // 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); auto file_infos = xe::filesystem::ListFiles(package_root);
for (const auto& file_info : file_infos) { for (const auto& file_info : file_infos) {
if (file_info.type != xe::filesystem::FileInfo::Type::kDirectory) { if (file_info.type != xe::filesystem::FileInfo::Type::kDirectory) {
// Directories only. // Directories only.
continue; continue;
} }
ContentData content_data; XCONTENT_AGGREGATE_DATA content_data;
content_data.device_id = device_id; content_data.device_id = device_id;
content_data.content_type = content_type; content_data.content_type = content_type;
content_data.display_name = xe::path_to_utf16(file_info.name); content_data.set_display_name(xe::path_to_utf16(file_info.name));
content_data.file_name = xe::path_to_utf8(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)); result.emplace_back(std::move(content_data));
} }
@ -122,7 +108,7 @@ std::vector<ContentData> ContentManager::ListContent(uint32_t device_id,
} }
std::unique_ptr<ContentPackage> ContentManager::ResolvePackage( std::unique_ptr<ContentPackage> 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); auto package_path = ResolvePackagePath(data);
if (!std::filesystem::exists(package_path)) { if (!std::filesystem::exists(package_path)) {
return nullptr; return nullptr;
@ -135,13 +121,13 @@ std::unique_ptr<ContentPackage> ContentManager::ResolvePackage(
return package; return package;
} }
bool ContentManager::ContentExists(const ContentData& data) { bool ContentManager::ContentExists(const XCONTENT_AGGREGATE_DATA& data) {
auto path = ResolvePackagePath(data); auto path = ResolvePackagePath(data);
return std::filesystem::exists(path); return std::filesystem::exists(path);
} }
X_RESULT ContentManager::CreateContent(const std::string_view root_name, 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(); auto global_lock = global_critical_region_.Acquire();
if (open_packages_.count(string_key(root_name))) { 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, 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(); auto global_lock = global_critical_region_.Acquire();
if (open_packages_.count(string_key(root_name))) { 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; return X_ERROR_SUCCESS;
} }
X_RESULT ContentManager::GetContentThumbnail(const ContentData& data, X_RESULT ContentManager::GetContentThumbnail(
std::vector<uint8_t>* buffer) { const XCONTENT_AGGREGATE_DATA& data, std::vector<uint8_t>* buffer) {
auto global_lock = global_critical_region_.Acquire(); auto global_lock = global_critical_region_.Acquire();
auto package_path = ResolvePackagePath(data); auto package_path = ResolvePackagePath(data);
auto thumb_path = package_path / kThumbnailFileName; auto thumb_path = package_path / kThumbnailFileName;
@ -226,8 +212,8 @@ X_RESULT ContentManager::GetContentThumbnail(const ContentData& data,
} }
} }
X_RESULT ContentManager::SetContentThumbnail(const ContentData& data, X_RESULT ContentManager::SetContentThumbnail(
std::vector<uint8_t> buffer) { const XCONTENT_AGGREGATE_DATA& data, std::vector<uint8_t> buffer) {
auto global_lock = global_critical_region_.Acquire(); auto global_lock = global_critical_region_.Acquire();
auto package_path = ResolvePackagePath(data); auto package_path = ResolvePackagePath(data);
std::filesystem::create_directories(package_path); 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(); auto global_lock = global_critical_region_.Acquire();
if (IsContentOpen(data)) { if (IsContentOpen(data)) {
@ -267,7 +253,7 @@ std::filesystem::path ContentManager::ResolveGameUserContentPath() {
return root_path_ / title_id / kGameUserContentDirName / user_name; 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(), return std::any_of(open_packages_.cbegin(), open_packages_.cend(),
[data](std::pair<string_key, ContentPackage*> content) { [data](std::pair<string_key, ContentPackage*> content) {
return data == content.second->GetPackageContentData(); return data == content.second->GetPackageContentData();

View File

@ -31,107 +31,108 @@ namespace xe {
namespace kernel { namespace kernel {
namespace xam { 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 { struct XCONTENT_DATA {
be<uint32_t> device_id; be<uint32_t> device_id;
be<uint32_t> content_type; be<XContentType> content_type;
union { union {
be<uint16_t> display_name[128]; // this should be be<uint16_t>, but that stops copy constructor being
char16_t display_name_chars[128]; // generated...
}; uint16_t uint[128];
char file_name[42]; char16_t chars[128];
uint8_t padding[2]; } display_name_raw;
};
static_assert_size(XCONTENT_DATA, 308);
struct XCONTENT_AGGREGATE_DATA { char file_name_raw[42];
be<uint32_t> device_id;
be<uint32_t> content_type; // Some games use this padding field as a null-terminator, as eg.
union { // DLC packages usually fill the entire file_name_raw array
be<uint16_t> display_name[128]; // Not every game sets it to 0 though, so make sure any file_name_raw reads
char16_t display_name_chars[128]; // only go up to 42 chars!
};
char file_name[42];
uint8_t padding[2]; 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<std::u16string>(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<string_util::Safety::IKnowWhatIAmDoing>(
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<uint64_t> unk134; // some titles store XUID here?
be<uint32_t> title_id; be<uint32_t> title_id;
};
static_assert_size(XCONTENT_AGGREGATE_DATA, 312);
struct ContentData { XCONTENT_AGGREGATE_DATA() = default;
uint32_t device_id; XCONTENT_AGGREGATE_DATA(const XCONTENT_DATA& other) {
uint32_t content_type; device_id = other.device_id;
std::u16string display_name; content_type = other.content_type;
std::string file_name; set_display_name(other.display_name());
set_file_name(other.file_name());
ContentData() = default; padding[0] = padding[1] = 0;
unk134 = 0;
bool operator==(const ContentData& rhs) const { title_id = kCurrentlyRunningTitleId;
return device_id == rhs.device_id && content_type == rhs.content_type &&
file_name == rhs.file_name;
} }
explicit ContentData(const XCONTENT_DATA& data) { bool operator==(const XCONTENT_AGGREGATE_DATA& other) const {
device_id = data.device_id; // Package is located via device_id/title_id/content_type/file_name, so only
content_type = data.content_type; // need to compare those
display_name = xe::load_and_swap<std::u16string>(data.display_name); return device_id == other.device_id && title_id == other.title_id &&
file_name = xe::load_and_swap<std::string>(data.file_name); content_type == other.content_type &&
} file_name() == other.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<std::u16string>(data.display_name);
file_name = xe::load_and_swap<std::string>(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;
} }
}; };
static_assert_size(XCONTENT_AGGREGATE_DATA, 0x148);
class ContentPackage { class ContentPackage {
public: public:
ContentPackage(KernelState* kernel_state, const std::string_view root_name, ContentPackage(KernelState* kernel_state, const std::string_view root_name,
const ContentData& data, const XCONTENT_AGGREGATE_DATA& data,
const std::filesystem::path& package_path); const std::filesystem::path& package_path);
~ContentPackage(); ~ContentPackage();
const ContentData& GetPackageContentData() const { return content_data_; } const XCONTENT_AGGREGATE_DATA& GetPackageContentData() const {
return content_data_;
}
private: private:
KernelState* kernel_state_; KernelState* kernel_state_;
std::string root_name_; std::string root_name_;
std::string device_path_; std::string device_path_;
ContentData content_data_; XCONTENT_AGGREGATE_DATA content_data_;
}; };
class ContentManager { class ContentManager {
@ -140,30 +141,32 @@ class ContentManager {
const std::filesystem::path& root_path); const std::filesystem::path& root_path);
~ContentManager(); ~ContentManager();
std::vector<ContentData> ListContent(uint32_t device_id, std::vector<XCONTENT_AGGREGATE_DATA> ListContent(uint32_t device_id,
uint32_t content_type); XContentType content_type,
uint32_t title_id = -1);
std::unique_ptr<ContentPackage> ResolvePackage( std::unique_ptr<ContentPackage> 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, 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, 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 CloseContent(const std::string_view root_name);
X_RESULT GetContentThumbnail(const ContentData& data, X_RESULT GetContentThumbnail(const XCONTENT_AGGREGATE_DATA& data,
std::vector<uint8_t>* buffer); std::vector<uint8_t>* buffer);
X_RESULT SetContentThumbnail(const ContentData& data, X_RESULT SetContentThumbnail(const XCONTENT_AGGREGATE_DATA& data,
std::vector<uint8_t> buffer); std::vector<uint8_t> buffer);
X_RESULT DeleteContent(const ContentData& data); X_RESULT DeleteContent(const XCONTENT_AGGREGATE_DATA& data);
std::filesystem::path ResolveGameUserContentPath(); 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); void CloseOpenedFilesFromContent(const std::string_view root_name);
private: private:
std::filesystem::path ResolvePackageRoot(uint32_t content_type); std::filesystem::path ResolvePackageRoot(XContentType content_type,
std::filesystem::path ResolvePackagePath(const ContentData& data); uint32_t title_id = -1);
std::filesystem::path ResolvePackagePath(const XCONTENT_AGGREGATE_DATA& data);
KernelState* kernel_state_; KernelState* kernel_state_;
std::filesystem::path root_path_; std::filesystem::path root_path_;

View File

@ -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) { if (!device_info || device_info->device_id == DummyDeviceId::HDD) {
// Get all content data. // Get all content data.
auto content_datas = kernel_state()->content_manager()->ListContent( auto content_datas = kernel_state()->content_manager()->ListContent(
static_cast<uint32_t>(DummyDeviceId::HDD), content_type); static_cast<uint32_t>(DummyDeviceId::HDD),
XContentType(uint32_t(content_type)));
for (const auto& content_data : content_datas) { for (const auto& content_data : content_datas) {
auto item = reinterpret_cast<XCONTENT_DATA*>(e->AppendItem()); auto item = reinterpret_cast<XCONTENT_DATA*>(e->AppendItem());
assert_not_null(item); 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); DECLARE_XAM_EXPORT1(XamContentCreateEnumerator, kContent, kImplemented);
dword_result_t XamContentCreateEx(dword_t user_index, lpstring_t root_name, dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
lpvoid_t content_data_ptr, dword_t flags, lpvoid_t content_data_ptr,
dword_t content_data_size, dword_t flags,
lpdword_t disposition_ptr, lpdword_t disposition_ptr,
lpdword_t license_mask_ptr, lpdword_t license_mask_ptr,
dword_t cache_size, qword_t content_size, dword_t cache_size, qword_t content_size,
lpvoid_t overlapped_ptr) { lpvoid_t overlapped_ptr) {
X_RESULT result = X_ERROR_INVALID_PARAMETER; X_RESULT result = X_ERROR_INVALID_PARAMETER;
auto content_data = XCONTENT_AGGREGATE_DATA content_data;
static_cast<ContentData>(*content_data_ptr.as<XCONTENT_DATA*>()); if (content_data_size == sizeof(XCONTENT_DATA)) {
content_data = *content_data_ptr.as<XCONTENT_DATA*>();
} else if (content_data_size == sizeof(XCONTENT_AGGREGATE_DATA)) {
content_data = *content_data_ptr.as<XCONTENT_AGGREGATE_DATA*>();
} else {
assert_always();
return result;
}
auto content_manager = kernel_state()->content_manager(); auto content_manager = kernel_state()->content_manager();
bool create = false; bool create = false;
@ -210,6 +219,18 @@ dword_result_t XamContentCreateEx(dword_t user_index, lpstring_t root_name,
return result; 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); DECLARE_XAM_EXPORT1(XamContentCreateEx, kContent, kImplemented);
dword_result_t XamContentCreate(dword_t user_index, lpstring_t root_name, 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 disposition_ptr,
lpdword_t license_mask_ptr, lpdword_t license_mask_ptr,
lpvoid_t overlapped_ptr) { lpvoid_t overlapped_ptr) {
return XamContentCreateEx(user_index, root_name, content_data_ptr, flags, return xeXamContentCreate(user_index, root_name, content_data_ptr,
disposition_ptr, license_mask_ptr, 0, 0, sizeof(XCONTENT_DATA), flags, disposition_ptr,
overlapped_ptr); license_mask_ptr, 0, 0, overlapped_ptr);
} }
DECLARE_XAM_EXPORT1(XamContentCreate, kContent, kImplemented); 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, 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, lpdword_t disposition_ptr, lpdword_t license_mask_ptr, dword_t cache_size,
qword_t content_size, lpvoid_t overlapped_ptr) { 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, disposition_ptr, license_mask_ptr, cache_size,
content_size, overlapped_ptr); content_size, overlapped_ptr);
} }
@ -277,14 +299,13 @@ dword_result_t XamContentGetCreator(dword_t user_index,
lpunknown_t overlapped_ptr) { lpunknown_t overlapped_ptr) {
auto result = X_ERROR_SUCCESS; auto result = X_ERROR_SUCCESS;
auto content_data = XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();
static_cast<ContentData>(*content_data_ptr.as<XCONTENT_DATA*>());
bool content_exists = bool content_exists =
kernel_state()->content_manager()->ContentExists(content_data); kernel_state()->content_manager()->ContentExists(content_data);
if (content_exists) { if (content_exists) {
if (content_data.content_type == 1) { if (content_data.content_type == XContentType::kSavedGame) {
// User always creates saves. // User always creates saves.
*is_creator_ptr = 1; *is_creator_ptr = 1;
if (creator_xuid_ptr) { if (creator_xuid_ptr) {
@ -316,8 +337,7 @@ dword_result_t XamContentGetThumbnail(dword_t user_index,
lpunknown_t overlapped_ptr) { lpunknown_t overlapped_ptr) {
assert_not_null(buffer_size_ptr); assert_not_null(buffer_size_ptr);
uint32_t buffer_size = *buffer_size_ptr; uint32_t buffer_size = *buffer_size_ptr;
auto content_data = XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();
static_cast<ContentData>(*content_data_ptr.as<XCONTENT_DATA*>());
// Get thumbnail (if it exists). // Get thumbnail (if it exists).
std::vector<uint8_t> buffer; std::vector<uint8_t> buffer;
@ -353,8 +373,7 @@ dword_result_t XamContentSetThumbnail(dword_t user_index,
lpvoid_t content_data_ptr, lpvoid_t content_data_ptr,
lpvoid_t buffer_ptr, dword_t buffer_size, lpvoid_t buffer_ptr, dword_t buffer_size,
lpunknown_t overlapped_ptr) { lpunknown_t overlapped_ptr) {
auto content_data = XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();
static_cast<ContentData>(*content_data_ptr.as<XCONTENT_DATA*>());
// Buffer is PNG data. // Buffer is PNG data.
auto buffer = std::vector<uint8_t>((uint8_t*)buffer_ptr, auto buffer = std::vector<uint8_t>((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, dword_result_t XamContentDelete(dword_t user_index, lpvoid_t content_data_ptr,
lpunknown_t overlapped_ptr) { lpunknown_t overlapped_ptr) {
auto content_data = XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();
static_cast<ContentData>(*content_data_ptr.as<XCONTENT_DATA*>());
auto result = kernel_state()->content_manager()->DeleteContent(content_data); auto result = kernel_state()->content_manager()->DeleteContent(content_data);

View File

@ -11,6 +11,7 @@
#include "xenia/base/math.h" #include "xenia/base/math.h"
#include "xenia/base/string_util.h" #include "xenia/base/string_util.h"
#include "xenia/kernel/kernel_state.h" #include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/user_module.h"
#include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/util/shim_utils.h"
#include "xenia/kernel/xam/xam_content_device.h" #include "xenia/kernel/xam/xam_content_device.h"
#include "xenia/kernel/xam/xam_private.h" #include "xenia/kernel/xam/xam_private.h"
@ -22,14 +23,15 @@ namespace xe {
namespace kernel { namespace kernel {
namespace xam { namespace xam {
void AddODDContentTest(object_ref<XStaticEnumerator> e, uint32_t content_type) { void AddODDContentTest(object_ref<XStaticEnumerator> e,
XContentType content_type) {
auto root_entry = kernel_state()->file_system()->ResolvePath( auto root_entry = kernel_state()->file_system()->ResolvePath(
"game:\\Content\\0000000000000000"); "game:\\Content\\0000000000000000");
if (!root_entry) { if (!root_entry) {
return; 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; xe::filesystem::WildcardEngine title_find_engine;
title_find_engine.SetRule("????????"); title_find_engine.SetRule("????????");
@ -62,14 +64,11 @@ void AddODDContentTest(object_ref<XStaticEnumerator> e, uint32_t content_type) {
auto item = reinterpret_cast<XCONTENT_AGGREGATE_DATA*>(e->AppendItem()); auto item = reinterpret_cast<XCONTENT_AGGREGATE_DATA*>(e->AppendItem());
assert_not_null(item); assert_not_null(item);
ContentAggregateData content_aggregate_data = {}; item->device_id = static_cast<uint32_t>(DummyDeviceId::ODD);
content_aggregate_data.device_id = item->content_type = content_type;
static_cast<uint32_t>(DummyDeviceId::ODD); item->set_display_name(to_utf16(content_entry->name()));
content_aggregate_data.content_type = content_type; item->set_file_name(content_entry->name());
content_aggregate_data.display_name = to_utf16(content_entry->name()); item->title_id = title_id;
content_aggregate_data.file_name = content_entry->name();
content_aggregate_data.title_id = title_id;
content_aggregate_data.Write(item);
} }
} }
} }
@ -98,25 +97,34 @@ dword_result_t XamContentAggregateCreateEnumerator(qword_t xuid,
extra->magic = kXObjSignature; extra->magic = kXObjSignature;
extra->handle = e->handle(); extra->handle = e->handle();
auto content_type_enum = XContentType(uint32_t(content_type));
if (!device_info || device_info->device_type == DeviceType::HDD) { if (!device_info || device_info->device_type == DeviceType::HDD) {
// Fetch any alternate title IDs defined in the XEX header
// (used by games to load saves from other titles, etc)
std::vector<uint32_t> 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. // Get all content data.
auto content_datas = kernel_state()->content_manager()->ListContent( auto content_datas = kernel_state()->content_manager()->ListContent(
static_cast<uint32_t>(DummyDeviceId::HDD), content_type); static_cast<uint32_t>(DummyDeviceId::HDD), content_type_enum,
title_id);
for (const auto& content_data : content_datas) { for (const auto& content_data : content_datas) {
auto item = reinterpret_cast<XCONTENT_AGGREGATE_DATA*>(e->AppendItem()); auto item = reinterpret_cast<XCONTENT_AGGREGATE_DATA*>(e->AppendItem());
assert_not_null(item); assert_not_null(item);
ContentAggregateData content_aggregate_data = {}; *item = content_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);
} }
} }
if (!device_info || device_info->device_type == DeviceType::ODD) { if (!device_info || device_info->device_type == DeviceType::ODD) {
AddODDContentTest(e, content_type); AddODDContentTest(e, content_type_enum);
} }
XELOGD("XamContentAggregateCreateEnumerator: added {} items to enumerator", XELOGD("XamContentAggregateCreateEnumerator: added {} items to enumerator",

View File

@ -546,7 +546,12 @@ DECLARE_XAM_EXPORT1(XamUserAreUsersFriends, kUserProfiles, kStub);
dword_result_t XamShowSigninUI(dword_t unk, dword_t unk_mask) { dword_result_t XamShowSigninUI(dword_t unk, dword_t unk_mask) {
// Mask values vary. Probably matching user types? Local/remote? // 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); kernel_state()->BroadcastNotification(0x00000009, 0);
return X_ERROR_SUCCESS; return X_ERROR_SUCCESS;
} }

View File

@ -960,14 +960,14 @@ void KeReleaseSpinLockFromRaisedIrql(lpdword_t lock_ptr) {
DECLARE_XBOXKRNL_EXPORT2(KeReleaseSpinLockFromRaisedIrql, kThreading, DECLARE_XBOXKRNL_EXPORT2(KeReleaseSpinLockFromRaisedIrql, kThreading,
kImplemented, kHighFrequency); kImplemented, kHighFrequency);
void KeEnterCriticalRegion() { XThread::EnterCriticalRegion(); } void KeEnterCriticalRegion() {
XThread::GetCurrentThread()->EnterCriticalRegion();
}
DECLARE_XBOXKRNL_EXPORT2(KeEnterCriticalRegion, kThreading, kImplemented, DECLARE_XBOXKRNL_EXPORT2(KeEnterCriticalRegion, kThreading, kImplemented,
kHighFrequency); kHighFrequency);
void KeLeaveCriticalRegion() { void KeLeaveCriticalRegion() {
XThread::LeaveCriticalRegion(); XThread::GetCurrentThread()->LeaveCriticalRegion();
XThread::GetCurrentThread()->CheckApcs();
} }
DECLARE_XBOXKRNL_EXPORT2(KeLeaveCriticalRegion, kThreading, kImplemented, DECLARE_XBOXKRNL_EXPORT2(KeLeaveCriticalRegion, kThreading, kImplemented,
kHighFrequency); kHighFrequency);

View File

@ -226,7 +226,7 @@ void XThread::InitializeGuestObject() {
} }
bool XThread::AllocateStack(uint32_t size) { bool XThread::AllocateStack(uint32_t size) {
auto heap = memory()->LookupHeap(0x40000000); auto heap = memory()->LookupHeap(kStackAddressRangeBegin);
auto alignment = heap->page_size(); auto alignment = heap->page_size();
auto padding = heap->page_size() * 2; // Guard page size * 2 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; auto actual_size = size + padding;
uint32_t address = 0; uint32_t address = 0;
if (!heap->AllocRange(0x40000000, 0x7F000000, actual_size, alignment, if (!heap->AllocRange(
kMemoryAllocationReserve | kMemoryAllocationCommit, kStackAddressRangeBegin, kStackAddressRangeEnd, actual_size,
kMemoryProtectRead | kMemoryProtectWrite, false, alignment, kMemoryAllocationReserve | kMemoryAllocationCommit,
&address)) { kMemoryProtectRead | kMemoryProtectWrite, false, &address)) {
return false; return false;
} }
@ -258,7 +258,7 @@ bool XThread::AllocateStack(uint32_t size) {
void XThread::FreeStack() { void XThread::FreeStack() {
if (stack_alloc_base_) { if (stack_alloc_base_) {
auto heap = memory()->LookupHeap(0x40000000); auto heap = memory()->LookupHeap(kStackAddressRangeBegin);
heap->Release(stack_alloc_base_); heap->Release(stack_alloc_base_);
stack_alloc_base_ = 0; stack_alloc_base_ = 0;
@ -578,11 +578,15 @@ void XThread::Reenter(uint32_t address) {
} }
void XThread::EnterCriticalRegion() { void XThread::EnterCriticalRegion() {
xe::global_critical_region::mutex().lock(); guest_object<X_KTHREAD>()->apc_disable_count--;
} }
void XThread::LeaveCriticalRegion() { void XThread::LeaveCriticalRegion() {
xe::global_critical_region::mutex().unlock(); auto kthread = guest_object<X_KTHREAD>();
auto apc_disable_count = ++kthread->apc_disable_count;
if (apc_disable_count == 0) {
CheckApcs();
}
} }
uint32_t XThread::RaiseIrql(uint32_t new_irql) { 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::CheckApcs() { DeliverAPCs(); }
void XThread::LockApc() { EnterCriticalRegion(); } void XThread::LockApc() { global_critical_region_.mutex().lock(); }
void XThread::UnlockApc(bool queue_delivery) { void XThread::UnlockApc(bool queue_delivery) {
bool needs_apc = apc_list_.HasPending(); bool needs_apc = apc_list_.HasPending();
LeaveCriticalRegion(); global_critical_region_.mutex().unlock();
if (needs_apc && queue_delivery) { if (needs_apc && queue_delivery) {
thread_->QueueUserCallback([this]() { DeliverAPCs(); }); thread_->QueueUserCallback([this]() { DeliverAPCs(); });
} }
@ -632,7 +636,8 @@ void XThread::DeliverAPCs() {
// https://www.drdobbs.com/inside-nts-asynchronous-procedure-call/184416590?pgno=7 // https://www.drdobbs.com/inside-nts-asynchronous-procedure-call/184416590?pgno=7
auto processor = kernel_state()->processor(); auto processor = kernel_state()->processor();
LockApc(); LockApc();
while (apc_list_.HasPending()) { auto kthread = guest_object<X_KTHREAD>();
while (apc_list_.HasPending() && kthread->apc_disable_count == 0) {
// Get APC entry (offset for LIST_ENTRY offset) and cache what we need. // Get APC entry (offset for LIST_ENTRY offset) and cache what we need.
// Calling the routine may delete the memory/overwrite it. // Calling the routine may delete the memory/overwrite it.
uint32_t apc_ptr = apc_list_.Shift() - 8; uint32_t apc_ptr = apc_list_.Shift() - 8;

View File

@ -113,7 +113,9 @@ struct X_KTHREAD {
uint8_t unk_8B; // 0x8B uint8_t unk_8B; // 0x8B
uint8_t unk_8C[0x10]; // 0x8C uint8_t unk_8C[0x10]; // 0x8C
xe::be<uint32_t> unk_9C; // 0x9C xe::be<uint32_t> 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 suspend_count; // 0xBC
uint8_t unk_BD; // 0xBD uint8_t unk_BD; // 0xBD
uint8_t unk_BE; // 0xBE uint8_t unk_BE; // 0xBE
@ -147,6 +149,9 @@ class XThread : public XObject, public cpu::Thread {
public: public:
static const XObject::Type kObjectType = XObject::Type::Thread; static const XObject::Type kObjectType = XObject::Type::Thread;
static constexpr uint32_t kStackAddressRangeBegin = 0x70000000;
static constexpr uint32_t kStackAddressRangeEnd = 0x7F000000;
struct CreationParams { struct CreationParams {
uint32_t stack_size; uint32_t stack_size;
uint32_t xapi_thread_startup; uint32_t xapi_thread_startup;
@ -192,8 +197,8 @@ class XThread : public XObject, public cpu::Thread {
virtual void Reenter(uint32_t address); virtual void Reenter(uint32_t address);
static void EnterCriticalRegion(); void EnterCriticalRegion();
static void LeaveCriticalRegion(); void LeaveCriticalRegion();
uint32_t RaiseIrql(uint32_t new_irql); uint32_t RaiseIrql(uint32_t new_irql);
void LowerIrql(uint32_t new_irql); void LowerIrql(uint32_t new_irql);

View File

@ -36,43 +36,6 @@ enum class XContentPackageType : uint32_t {
kLive = 0x4C495645, 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 { enum class XContentVolumeType : uint32_t {
kStfs = 0, kStfs = 0,
kSvod = 1, kSvod = 1,

View File

@ -344,6 +344,43 @@ enum class XLanguage : uint32_t {
kMaxLanguages = 13 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 } // namespace xe
// clang-format on // clang-format on

View File

@ -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", project_root.."/src/xenia/base/main_"..platform_suffix..".cc",
base_path.."/**_test.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 end
local function split_test_suite(test_suite_name, project_root, base_path, config) 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", project_root.."/"..build_tools_src.."/test_suite_main.cc",
file_path, 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
end end