From b8b33ecabfd9bdf57575e085aef877ea0d082bfa Mon Sep 17 00:00:00 2001 From: Gliniak Date: Sat, 3 Jan 2026 22:20:44 +0100 Subject: [PATCH 1/6] [VFS] XContent: Added support for SVOD normal layout with multiple files --- .../xcontent_devices/svod_container_device.cc | 22 +++++++++---------- .../xcontent_devices/svod_container_device.h | 1 + 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/xenia/vfs/devices/xcontent_devices/svod_container_device.cc b/src/xenia/vfs/devices/xcontent_devices/svod_container_device.cc index 97a03bc65..9830d3d78 100644 --- a/src/xenia/vfs/devices/xcontent_devices/svod_container_device.cc +++ b/src/xenia/vfs/devices/xcontent_devices/svod_container_device.cc @@ -93,7 +93,7 @@ XContentContainerDevice::Result SvodContainerDevice::Read() { static_assert_size(root_data, 0x10); if (fread(&root_data, sizeof(root_data), 1, svod_header) != 1) { - XELOGE("ReadSVOD failed to read root block data at 0x{X}", + XELOGE("ReadSVOD failed to read root block data at 0x{:016X}", magic_offset + 0x14); return Result::kReadError; } @@ -335,9 +335,11 @@ XContentContainerDevice::Result SvodContainerDevice::SetNormalLayout( FILE* header, size_t& magic_offset) { uint8_t magic_buf[20]; - xe::filesystem::Seek(header, 0xD000, SEEK_SET); + const uint32_t magic_pos = + header_->content_metadata.data_file_count == 1 ? 0xD000 : 0x2000; + xe::filesystem::Seek(header, magic_pos, SEEK_SET); if (fread(magic_buf, 1, countof(magic_buf), header) != countof(magic_buf)) { - XELOGE("ReadSVOD failed to read SVOD magic at 0xD000"); + XELOGE("ReadSVOD failed to read SVOD magic at 0x{:04X}", magic_pos); return Result::kReadError; } @@ -350,18 +352,16 @@ XContentContainerDevice::Result SvodContainerDevice::SetNormalLayout( // is a single-file system. The STFS Header is 0xB000 bytes and the // remaining 0x2000 is from hash tables. In most cases, these will be // STFS, not SVOD. - svod_base_offset_ = 0xB000; - magic_offset = 0xD000; - - // Check for single file system if (header_->content_metadata.data_file_count == 1) { + svod_base_offset_ = 0xB000; svod_layout_ = SvodLayoutType::kSingleFile; XELOGI("SVOD is a single file. Magic block present at 0xD000."); + magic_offset = 0xD000; } else { - svod_layout_ = SvodLayoutType::kUnknown; - XELOGE( - "SVOD is not a single file, but the magic block was found at " - "0xD000."); + svod_base_offset_ = 0x0000; + svod_layout_ = SvodLayoutType::kMultipleFiles; + XELOGI("SVOD is a multiple files. Magic block present at 0x2000."); + magic_offset = 0x2000; } return Result::kSuccess; } diff --git a/src/xenia/vfs/devices/xcontent_devices/svod_container_device.h b/src/xenia/vfs/devices/xcontent_devices/svod_container_device.h index a1b805f85..086094c83 100644 --- a/src/xenia/vfs/devices/xcontent_devices/svod_container_device.h +++ b/src/xenia/vfs/devices/xcontent_devices/svod_container_device.h @@ -41,6 +41,7 @@ class SvodContainerDevice : public XContentContainerDevice { kEnhancedGDF = 0x1, kXSF = 0x2, kSingleFile = 0x4, + kMultipleFiles = 0x8, }; const char* MEDIA_MAGIC = "MICROSOFT*XBOX*MEDIA"; From b723d6e7c26b702fdb38eba0c364118d2c3d37cb Mon Sep 17 00:00:00 2001 From: Adrian <78108584+AdrianCassar@users.noreply.github.com> Date: Sun, 4 Jan 2026 16:12:01 +0000 Subject: [PATCH 2/6] [UI] Free image data from stbi_load --- src/xenia/ui/imgui_drawer.cc | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/xenia/ui/imgui_drawer.cc b/src/xenia/ui/imgui_drawer.cc index 756f63710..9fc1aa90e 100644 --- a/src/xenia/ui/imgui_drawer.cc +++ b/src/xenia/ui/imgui_drawer.cc @@ -289,9 +289,13 @@ std::unique_ptr ImGuiDrawer::LoadImGuiIcon( return {}; } - return immediate_drawer_->CreateTexture( + auto texture = immediate_drawer_->CreateTexture( width, height, ImmediateTextureFilter::kLinear, true, reinterpret_cast(image_data)); + + stbi_image_free(image_data); + + return texture; } std::map> ImGuiDrawer::LoadIcons( @@ -328,6 +332,8 @@ void ImGuiDrawer::SetupNotificationTextures() { notification_icon_textures_.push_back(immediate_drawer_->CreateTexture( width, height, ImmediateTextureFilter::kLinear, true, reinterpret_cast(image_data))); + + stbi_image_free(image_data); } } @@ -568,6 +574,8 @@ void ImGuiDrawer::SetImmediateDrawer(ImmediateDrawer* new_immediate_drawer) { locked_achievement_icon_ = immediate_drawer_->CreateTexture( width, height, ImmediateTextureFilter::kLinear, true, reinterpret_cast(image_data)); + + stbi_image_free(image_data); } } From ccd5c50c74ca9ac5807dcc21e9c233c4377b927e Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sun, 4 Jan 2026 02:37:23 +0300 Subject: [PATCH 3/6] [XAM] Count empty path as exit request in XamLoaderGetLaunchData --- src/xenia/kernel/xam/xam_info.cc | 33 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/xenia/kernel/xam/xam_info.cc b/src/xenia/kernel/xam/xam_info.cc index 6adbedac6..0fd67f63a 100644 --- a/src/xenia/kernel/xam/xam_info.cc +++ b/src/xenia/kernel/xam/xam_info.cc @@ -383,30 +383,21 @@ void XamLoaderLaunchTitle_entry(lpstring_t raw_name_ptr, dword_t flags) { loader_data.launch_flags = flags; // Translate the launch path to a full path. - if (raw_name_ptr) { - auto path = raw_name_ptr.value(); - if (path.empty()) { - loader_data.launch_path = "game:\\default.xex"; - } else { - loader_data.launch_path = xe::path_to_utf8(path); - loader_data.launch_data_present = true; - } - + if (raw_name_ptr && !raw_name_ptr.value().empty()) { + loader_data.launch_path = xe::path_to_utf8(raw_name_ptr.value()); + loader_data.launch_data_present = true; xam->SaveLoaderData(); - if (loader_data.launch_data_present) { - auto display_window = kernel_state()->emulator()->display_window(); - auto imgui_drawer = kernel_state()->emulator()->imgui_drawer(); + auto display_window = kernel_state()->emulator()->display_window(); + auto imgui_drawer = kernel_state()->emulator()->imgui_drawer(); - if (display_window && imgui_drawer) { - display_window->app_context().CallInUIThreadSynchronous( - [imgui_drawer]() { - xe::ui::ImGuiDialog::ShowMessageBox( - imgui_drawer, "Title was restarted", - "Title closed with new launch data. \nPlease restart Xenia. " - "Game will be loaded automatically."); - }); - } + if (display_window && imgui_drawer) { + display_window->app_context().CallInUIThreadSynchronous([imgui_drawer]() { + xe::ui::ImGuiDialog::ShowMessageBox( + imgui_drawer, "Title was restarted", + "Title closed with new launch data. \nPlease restart Xenia. " + "Game will be loaded automatically."); + }); } } else { assert_always("Game requested exit to dashboard via XamLoaderLaunchTitle"); From 2e61df76f0021bb1e1c4f30b7af4dd6c6f556cbc Mon Sep 17 00:00:00 2001 From: The-Little-Wolf <116989599+The-Little-Wolf@users.noreply.github.com> Date: Tue, 16 Dec 2025 16:31:01 -0800 Subject: [PATCH 4/6] [XAM/USER] - Implement XamUserIsParentalControlled - Implement XamUserIsParentalControlled which is used in 584807F7 - Move some structs into xam.h --- src/xenia/kernel/xam/xam.h | 25 +++++++++++++++++++++ src/xenia/kernel/xam/xam_user.cc | 37 +++++++++++--------------------- 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/src/xenia/kernel/xam/xam.h b/src/xenia/kernel/xam/xam.h index d111fbef9..2b57e842b 100644 --- a/src/xenia/kernel/xam/xam.h +++ b/src/xenia/kernel/xam/xam.h @@ -222,6 +222,22 @@ static_assert_size(X_PASSPORT_SESSION_TOKEN, 0x1C); #pragma pack(pop) +struct X_USER_SIGNIN_INFO { + xe::be xuid; + xe::be flags; + xe::be signin_state; + xe::be guest_num; + xe::be sponsor_user_index; + char name[16]; +}; +static_assert_size(X_USER_SIGNIN_INFO, 40); + +struct X_USER_READ_PROFILE_SETTINGS { + xe::be setting_count; + xe::be settings_ptr; +}; +static_assert_size(X_USER_READ_PROFILE_SETTINGS, 8); + // clang-format off #define XMBox_NOICON 0x00000000 #define XMBox_ERRORICON 0x00000001 @@ -343,6 +359,15 @@ struct XMP_USER_PLAYLIST_INFO { }; static_assert_size(XMP_USER_PLAYLIST_INFO, 0x334); +constexpr uint8_t kStatsMaxAmount = 64; + +struct X_STATS_DETAILS { + xe::be id; + xe::be stats_amount; + xe::be stats[kStatsMaxAmount]; +}; +static_assert_size(X_STATS_DETAILS, 8 + kStatsMaxAmount * 2); + } // namespace xam } // namespace kernel } // namespace xe diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index afcae57f7..84a51b9b6 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -103,16 +103,6 @@ dword_result_t XamUserGetSigninState_entry(dword_t user_index) { DECLARE_XAM_EXPORT2(XamUserGetSigninState, kUserProfiles, kImplemented, kHighFrequency); -typedef struct { - xe::be xuid; - xe::be flags; - xe::be signin_state; - xe::be guest_num; - xe::be sponsor_user_index; - char name[16]; -} X_USER_SIGNIN_INFO; -static_assert_size(X_USER_SIGNIN_INFO, 40); - X_HRESULT_result_t XamUserGetSigninInfo_entry( dword_t user_index, dword_t flags, pointer_t info) { if (!info) { @@ -199,12 +189,6 @@ dword_result_t XamUserGetGamerTag_entry(dword_t user_index, dword_t buffer, } DECLARE_XAM_EXPORT1(XamUserGetGamerTag, kUserProfiles, kImplemented); -typedef struct { - xe::be setting_count; - xe::be settings_ptr; -} X_USER_READ_PROFILE_SETTINGS; -static_assert_size(X_USER_READ_PROFILE_SETTINGS, 8); - // https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/Generic/xboxtools.cpp uint32_t XamUserReadProfileSettingsEx(uint32_t title_id, uint32_t user_index, uint32_t xuid_count, be* xuids, @@ -993,14 +977,19 @@ dword_result_t XamUserGetOnlineCountryFromXUID_entry(qword_t xuid) { DECLARE_XAM_EXPORT1(XamUserGetOnlineCountryFromXUID, kUserProfiles, kImplemented); -constexpr uint8_t kStatsMaxAmount = 64; - -struct X_STATS_DETAILS { - xe::be id; - xe::be stats_amount; - xe::be stats[kStatsMaxAmount]; -}; -static_assert_size(X_STATS_DETAILS, 8 + kStatsMaxAmount * 2); +dword_result_t XamUserIsParentalControlled_entry(dword_t user_index) { + /* Notes: + - if (data_address < 1 || XamExecutingOnBehalfOfTitle == 0 || title id == + kDashboardID || user_type != offline) (type mask used in XamUserGetXUID) go + forward else return false + */ + const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index); + if (!user || !user->IsParentalControlled()) { + return false; + } + return true; +} +DECLARE_XAM_EXPORT1(XamUserIsParentalControlled, kUserProfiles, kImplemented); dword_result_t XamUserCreateStatsEnumerator_entry( dword_t title_id, dword_t user_index, dword_t count, dword_t flags, From beb52110e38f9de4d6d9e1af6cde266dc9f2a9df Mon Sep 17 00:00:00 2001 From: Gliniak Date: Sat, 3 Jan 2026 23:53:45 +0100 Subject: [PATCH 5/6] [Memory] Use randomized values for scribble_heap instead of hardcoded one This fixes Watchman - The End is Nigh which relay on UB on boot (OOR) They're checking in hardcoded "game:" for '/' character. If it doesn't exist then adds 0x7FFFFFFE to start address and start searching from that location downward --- src/xenia/memory.cc | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/xenia/memory.cc b/src/xenia/memory.cc index ae3aab6bc..aa25cb899 100644 --- a/src/xenia/memory.cc +++ b/src/xenia/memory.cc @@ -10,6 +10,7 @@ #include "xenia/memory.h" #include +#include #include "third_party/fmt/include/fmt/format.h" #include "xenia/base/assert.h" @@ -30,7 +31,12 @@ DEFINE_bool(protect_zero, true, "Protect the zero page from reads and writes.", DEFINE_bool(protect_on_release, false, "Protect released memory to prevent accesses.", "Memory"); DEFINE_bool(scribble_heap, false, - "Scribble 0xCD into all allocated heap memory.", "Memory"); + "Scribble specific or random value into all allocated heap memory.", + "Memory"); +DEFINE_int32(scribble_heap_value, 0, + "Value used to fill all allocated heap memory. 0 - Random value. " + "Valid range: [1-255]", + "Memory"); namespace xe { uint32_t get_page_count(uint32_t value, uint32_t page_size) { @@ -96,6 +102,24 @@ xe::memory::PageAccess ToPageAccess(uint32_t protect) { } } +void RandomizeMemory(void* range_start, uint32_t size) { + if (!cvars::scribble_heap) { + return; + } + + if (!cvars::scribble_heap_value) { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(0, std::numeric_limits::max()); + + std::generate(static_cast(range_start), + static_cast(range_start) + size, + [&]() { return dis(gen); }); + } else { + std::memset(range_start, cvars::scribble_heap_value, size); + } +} + Memory::Memory() { system_page_size_ = uint32_t(xe::memory::page_size()); system_allocation_granularity_ = @@ -975,7 +999,7 @@ bool BaseHeap::AllocFixed(uint32_t base_address, uint32_t size, } if (cvars::scribble_heap && protect & kMemoryProtectWrite) { - std::memset(result, 0xCD, page_count * page_size_); + RandomizeMemory(result, page_count * page_size_); } } @@ -1137,7 +1161,7 @@ bool BaseHeap::AllocRange(uint32_t low_address, uint32_t high_address, } if (cvars::scribble_heap && (protect & kMemoryProtectWrite)) { - std::memset(result, 0xCD, page_count << page_size_shift_); + RandomizeMemory(result, page_count << page_size_shift_); } } From 4b73839bf1eec853e926bf999f0f9e650c6ec39d Mon Sep 17 00:00:00 2001 From: Adrian <78108584+AdrianCassar@users.noreply.github.com> Date: Sun, 4 Jan 2026 16:44:51 +0000 Subject: [PATCH 6/6] [XAM] Added std::optional to SPA getters --- src/xenia/kernel/util/game_info_database.cc | 76 +++++++++++++-------- src/xenia/kernel/util/game_info_database.h | 14 ++-- 2 files changed, 57 insertions(+), 33 deletions(-) diff --git a/src/xenia/kernel/util/game_info_database.cc b/src/xenia/kernel/util/game_info_database.cc index 8c3293dcb..e13b847cc 100644 --- a/src/xenia/kernel/util/game_info_database.cc +++ b/src/xenia/kernel/util/game_info_database.cc @@ -85,19 +85,19 @@ std::string GameInfoDatabase::GetLocalizedString(const uint32_t id, spa_gamedata_->GetExistingLanguage(language), id); } -GameInfoDatabase::Context GameInfoDatabase::GetContext( +std::optional GameInfoDatabase::GetContext( const uint32_t id) const { - Context context = {.id = xam::kInvalidContextId}; - if (!is_valid_) { - return context; + return std::nullopt; } const auto xdbf_context = spa_gamedata_->GetContext(id); if (!xdbf_context) { - return context; + return std::nullopt; } + Context context = {}; + context.id = xdbf_context->id; context.default_value = xdbf_context->default_value; context.max_value = xdbf_context->max_value; @@ -109,19 +109,19 @@ GameInfoDatabase::Context GameInfoDatabase::GetContext( return context; } -GameInfoDatabase::Property GameInfoDatabase::GetProperty( +std::optional GameInfoDatabase::GetProperty( const uint32_t id) const { - Property property = {.id = xam::kInvalidPropertyId}; - if (!is_valid_) { - return property; + return std::nullopt; } const auto xdbf_property = spa_gamedata_->GetProperty(id); if (!xdbf_property) { - return property; + return std::nullopt; } + Property property = {}; + property.id = xdbf_property->id; property.data_size = xdbf_property->data_size; property.is_system = xam::UserData::is_system_property(xdbf_property->id); @@ -132,14 +132,14 @@ GameInfoDatabase::Property GameInfoDatabase::GetProperty( return property; } -GameInfoDatabase::Achievement GameInfoDatabase::GetAchievement( +std::optional GameInfoDatabase::GetAchievement( const uint32_t id) const { - Achievement achievement = {}; - if (!is_valid_) { - return achievement; + return std::nullopt; } + Achievement achievement = {}; + const auto xdbf_achievement = spa_gamedata_->GetAchievement(id); if (!xdbf_achievement) { return achievement; @@ -190,20 +190,20 @@ GameInfoDatabase::Field GameInfoDatabase::GetField( return field; } -GameInfoDatabase::StatsView GameInfoDatabase::GetStatsView( +std::optional GameInfoDatabase::GetStatsView( const uint32_t id) const { - StatsView stats_view = {}; - if (!is_valid_) { - return stats_view; + return std::nullopt; } const auto xdbf_stats_view = spa_gamedata_->GetStatsView(id); if (!xdbf_stats_view.has_value()) { - return stats_view; + return std::nullopt; } + StatsView stats_view = {}; + stats_view.view.id = xdbf_stats_view->view_entry.id; stats_view.view.arbitrated = @@ -252,14 +252,14 @@ GameInfoDatabase::Presence GameInfoDatabase::GetPresence() const { return presence; } -GameInfoDatabase::PresenceMode GameInfoDatabase::GetPresenceMode( +std::optional GameInfoDatabase::GetPresenceMode( const uint32_t context_value) const { - PresenceMode presence_mode = {}; - if (!is_valid_) { - return presence_mode; + return std::nullopt; } + PresenceMode presence_mode = {}; + const auto xdbf_presence_mode = spa_gamedata_->GetPresenceMode(context_value); if (!xdbf_presence_mode.has_value()) { @@ -369,7 +369,11 @@ std::vector GameInfoDatabase::GetContexts() const { const auto xdbf_contexts = spa_gamedata_->GetContexts(); for (const auto& entry : xdbf_contexts) { - contexts.push_back(GetContext(entry->id)); + const auto context = GetContext(entry->id); + + if (context.has_value()) { + contexts.push_back(context.value()); + } } return contexts; @@ -385,7 +389,11 @@ std::vector GameInfoDatabase::GetProperties() const auto xdbf_properties = spa_gamedata_->GetProperties(); for (const auto& entry : xdbf_properties) { - properties.push_back(GetProperty(entry->id)); + const auto property = GetProperty(entry->id); + + if (property.has_value()) { + properties.push_back(property.value()); + } } return properties; @@ -401,7 +409,11 @@ std::vector GameInfoDatabase::GetAchievements() const auto xdbf_achievements = spa_gamedata_->GetAchievements(); for (const auto& entry : xdbf_achievements) { - achievements.push_back(GetAchievement(entry->id)); + auto achievement = GetAchievement(entry->id); + + if (achievement.has_value()) { + achievements.push_back(achievement.value()); + } } return achievements; @@ -418,7 +430,11 @@ std::vector GameInfoDatabase::GetStatsViews() const auto xdbf_stats_views = spa_gamedata_->GetStatsViews(); for (const auto& entry : *xdbf_stats_views) { - stats_views.push_back(GetStatsView(entry.view_entry.id)); + auto stats_view = GetStatsView(entry.view_entry.id); + + if (stats_view.has_value()) { + stats_views.push_back(stats_view.value()); + } } return stats_views; @@ -437,7 +453,11 @@ std::vector GameInfoDatabase::GetPresenceModes() for (uint32_t context_value = 0; context_value < xdbf_presence_modes.size(); context_value++) { - presence_modes.push_back(GetPresenceMode(context_value)); + const auto presence_mode = GetPresenceMode(context_value); + + if (presence_mode.has_value()) { + presence_modes.push_back(presence_mode.value()); + } } return presence_modes; diff --git a/src/xenia/kernel/util/game_info_database.h b/src/xenia/kernel/util/game_info_database.h index bccef285d..16a2b1529 100644 --- a/src/xenia/kernel/util/game_info_database.h +++ b/src/xenia/kernel/util/game_info_database.h @@ -144,14 +144,18 @@ class GameInfoDatabase { std::vector GetIcon() const; - Context GetContext(const uint32_t id) const; - Property GetProperty(const uint32_t id) const; - Achievement GetAchievement(const uint32_t id) const; + std::optional GetContext(const uint32_t id) const; + std::optional GetProperty( + const uint32_t id) const; + std::optional GetAchievement( + const uint32_t id) const; PropertyBag GetPropertyBag(const xam::PropertyBag& property_bag) const; Field GetField(const xam::ViewFieldEntry& field_entry) const; - StatsView GetStatsView(const uint32_t id) const; + std::optional GetStatsView( + const uint32_t id) const; Presence GetPresence() const; - PresenceMode GetPresenceMode(const uint32_t context_value) const; + std::optional GetPresenceMode( + const uint32_t context_value) const; std::vector GetPresenceModes( const std::vector property_bags) const;