From 99ea6da18a8902efd2644ce4789f09f39dd3db99 Mon Sep 17 00:00:00 2001 From: Gliniak <153369+Gliniak@users.noreply.github.com> Date: Sun, 17 May 2026 21:30:10 +0200 Subject: [PATCH] [XConfig] Implementation of XConfig --- src/xenia/app/console_settings_dialog.cc | 426 +++++++ src/xenia/app/console_settings_dialog.h | 62 + src/xenia/app/emulator_window.cc | 44 +- src/xenia/app/emulator_window.h | 5 + src/xenia/app/xenia_main.cc | 11 +- src/xenia/apu/audio_media_player.cc | 5 +- src/xenia/emulator.cc | 7 +- src/xenia/gpu/graphics_system.cc | 75 +- src/xenia/gpu/graphics_system.h | 24 +- src/xenia/kernel/kernel_flags.cc | 2 +- src/xenia/kernel/kernel_state.cc | 2 + src/xenia/kernel/kernel_state.h | 4 + src/xenia/kernel/xam/apps/xam_app.cc | 10 + src/xenia/kernel/xam/user_tracker.cc | 3 +- src/xenia/kernel/xam/xam_avatar.cc | 4 +- src/xenia/kernel/xam/xam_info.cc | 26 +- src/xenia/kernel/xam/xam_locale.cc | 37 +- src/xenia/kernel/xam/xam_nui.cc | 24 +- src/xenia/kernel/xam/xam_user.cc | 6 +- src/xenia/kernel/xboxkrnl/xboxkrnl_audio.cc | 4 +- src/xenia/kernel/xboxkrnl/xboxkrnl_strings.cc | 2 +- src/xenia/kernel/xboxkrnl/xboxkrnl_video.cc | 124 +- src/xenia/kernel/xboxkrnl/xboxkrnl_xconfig.cc | 296 +---- src/xenia/kernel/xboxkrnl/xboxkrnl_xconfig.h | 395 ------ src/xenia/kernel/xconfig.cc | 174 +++ src/xenia/kernel/xconfig.h | 1055 +++++++++++++++++ src/xenia/ui/window.cc | 12 + src/xenia/ui/window.h | 7 + src/xenia/ui/window_gtk.cc | 4 +- src/xenia/ui/window_win.cc | 13 +- src/xenia/ui/window_win.h | 3 +- 31 files changed, 2020 insertions(+), 846 deletions(-) create mode 100644 src/xenia/app/console_settings_dialog.cc create mode 100644 src/xenia/app/console_settings_dialog.h delete mode 100644 src/xenia/kernel/xboxkrnl/xboxkrnl_xconfig.h create mode 100644 src/xenia/kernel/xconfig.cc create mode 100644 src/xenia/kernel/xconfig.h diff --git a/src/xenia/app/console_settings_dialog.cc b/src/xenia/app/console_settings_dialog.cc new file mode 100644 index 000000000..9bdd5f19b --- /dev/null +++ b/src/xenia/app/console_settings_dialog.cc @@ -0,0 +1,426 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2026 Xenia Canary. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ +#include "xenia/app/emulator_window.h" +#include "xenia/app/profile_dialogs.h" +#include "xenia/base/png_utils.h" +#include "xenia/base/system.h" +#include "xenia/kernel/util/shim_utils.h" +#include "xenia/kernel/xam/xam_ui.h" +#include "xenia/ui/file_picker.h" + +#include "xenia/app/console_settings_dialog.h" + +#include "third_party/fmt/include/fmt/format.h" + +namespace xe { +namespace app { + +static const std::map kLanguageMap = { + {1, "English"}, {2, "Japanese"}, + {3, "German"}, {4, "French"}, + {5, "Spanish"}, {6, "Italian"}, + {7, "Korean"}, {8, "Traditional Chinese"}, + {9, "Portuguese"}, {10, "Simplified Chinese"}, + {11, "Polish"}, {12, "Russian"}}; + +static const std::map kCountryMap = { + {1, "AE"}, {2, "AL"}, {3, "AM"}, {4, "AR"}, {5, "AT"}, + {6, "AU"}, {7, "AZ"}, {8, "BE"}, {9, "BG"}, {10, "BH"}, + {11, "BN"}, {12, "BO"}, {13, "BR"}, {14, "BY"}, {15, "BZ"}, + {16, "CA"}, {18, "CH"}, {19, "CL"}, {20, "CN"}, {21, "CO"}, + {22, "CR"}, {23, "CZ"}, {24, "DE"}, {25, "DK"}, {26, "DO"}, + {27, "DZ"}, {28, "EC"}, {29, "EE"}, {30, "EG"}, {31, "ES"}, + {32, "FI"}, {33, "FO"}, {34, "FR"}, {35, "GB"}, {36, "GE"}, + {37, "GR"}, {38, "GT"}, {39, "HK"}, {40, "HN"}, {41, "HR"}, + {42, "HU"}, {43, "ID"}, {44, "IE"}, {45, "IL"}, {46, "IN"}, + {47, "IQ"}, {48, "IR"}, {49, "IS"}, {50, "IT"}, {51, "JM"}, + {52, "JO"}, {53, "JP"}, {54, "KE"}, {55, "KG"}, {56, "KR"}, + {57, "KW"}, {58, "KZ"}, {59, "LB"}, {60, "LI"}, {61, "LT"}, + {62, "LU"}, {63, "LV"}, {64, "LY"}, {65, "MA"}, {66, "MC"}, + {67, "MK"}, {68, "MN"}, {69, "MO"}, {70, "MV"}, {71, "MX"}, + {72, "MY"}, {73, "NI"}, {74, "NL"}, {75, "NO"}, {76, "NZ"}, + {77, "OM"}, {78, "PA"}, {79, "PE"}, {80, "PH"}, {81, "PK"}, + {82, "PL"}, {83, "PR"}, {84, "PT"}, {85, "PY"}, {86, "QA"}, + {87, "RO"}, {88, "RU"}, {89, "SA"}, {90, "SE"}, {91, "SG"}, + {92, "SI"}, {93, "SK"}, {95, "SV"}, {96, "SY"}, {97, "TH"}, + {98, "TN"}, {99, "TR"}, {100, "TT"}, {101, "TW"}, {102, "UA"}, + {103, "US"}, {104, "UY"}, {105, "UZ"}, {106, "VE"}, {107, "VN"}, + {108, "YE"}, {109, "ZA"}, +}; + +static const std::map kAVRegion = { + {0x00400100, "NTSC"}, + {0x00400200, "NTSC-J"}, + {0x00400400, "PAL"}, + {0x00800300, "PAL 50Hz"}}; + +template +void DrawCombobox(ImGuiIO& io, std::string_view combobox_name, + const std::map& options, xe::be& value) { + const char* preview = "Unknown"; + for (const auto& opt : options) { + if (opt.first == value.get()) { + preview = opt.second.data(); + break; + } + } + + if (ImGui::BeginCombo(combobox_name.data(), preview)) { + for (const auto& opt : options) { + const bool selected = (opt.first == value.get()); + if (ImGui::Selectable(opt.second.data(), selected)) { + value = opt.first; + } + if (selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } +} + +void DrawResolutionCombobox(ImGuiIO& io, + const std::span options, + xe::be& resolution, + xe::be& video_flags) { + const char* preview = "Unknown"; + for (const auto& opt : options) { + if (opt.to_host() == resolution.get()) { + preview = opt.name_.c_str(); + break; + } + } + + if (ImGui::BeginCombo("Resolution", preview)) { + for (const auto& opt : options) { + const bool selected = (opt.to_host() == resolution.get()); + if (ImGui::Selectable(opt.name_.c_str(), selected)) { + resolution = opt.to_host(); + + if (opt.is_widescreen()) { + video_flags = video_flags | static_cast( + kernel::X_VIDEO_FLAGS::Widescreen); + } else { + video_flags = video_flags & ~static_cast( + kernel::X_VIDEO_FLAGS::Widescreen); + } + } + + if (selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } +} + +void DrawTimezoneCombobox(ImGuiIO& io, kernel::XConfigData* xdata) { + const kernel::TimeZone current_tz = { + xdata->user.time_zone_bias, xdata->user.tz_std_name, + xdata->user.tz_dlt_name, xdata->user.tz_std_date, + xdata->user.tz_dlt_date, xdata->user.tz_std_bias, + xdata->user.tz_dlt_bias}; + + size_t index = 0x19; // Index of GMT+00 + + auto it = std::find(kernel::kTimezones.cbegin(), kernel::kTimezones.cend(), + current_tz); + if (it != kernel::kTimezones.cend()) { + index = std::distance(kernel::kTimezones.cbegin(), it); + } + + if (ImGui::BeginCombo("Timezone", kernel::kTimezones[index].name.c_str())) { + for (size_t i = 0; i < kernel::kTimezones.size(); ++i) { + const bool is_selected = + (kernel::kTimezones[i] == kernel::kTimezones[index]); + + if (ImGui::Selectable(kernel::kTimezones[i].name.c_str(), is_selected)) { + index = i; + + // Update timezone values + const auto& tz = kernel::kTimezones[index]; + xdata->user.time_zone_bias = tz.timezone_bias; + memcpy(xdata->user.tz_std_name.data(), tz.tz_std_name.data(), 4); + memcpy(xdata->user.tz_dlt_name.data(), tz.tz_dlt_name.data(), 4); + memcpy(xdata->user.tz_std_date.data(), tz.tz_std_date.data(), 4); + memcpy(xdata->user.tz_dlt_date.data(), tz.tz_dlt_date.data(), 4); + xdata->user.tz_std_bias = tz.tz_std_bias; + xdata->user.tz_dlt_bias = tz.tz_dlt_bias; + } + if (is_selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } +} + +template +static bool FlagCheckbox(const char* label, xe::be& field, T value) { + bool set = (field.get() & value) != 0; + if (ImGui::Checkbox(label, &set)) { + if (set) { + field = field.get() | value; + } else { + field = field.get() & ~value; + } + return true; + } + return false; +} + +template +static void ByteArray(const char* label, T* array, size_t count) { + for (size_t i = 0; i < count; i++) { + ImGui::SetNextItemWidth(ImGui::CalcTextSize("CC_").x); + ImGui::InputScalar(fmt::format("##{}{}", label, i).c_str(), + ImGuiDataType_U8, &array[i], nullptr, nullptr, "%02X"); + + if (i + 1 < count) { + ImGui::SameLine(); + } + } +} + +void GroupBox(const char* label, auto draw_fn) { + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 text_size = ImGui::CalcTextSize(label); + float border_offset = text_size.y * 0.5f; + ImGuiStyle& style = ImGui::GetStyle(); + + // Push content down so it doesn't overlap the label + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + border_offset); + ImGui::Indent(8.0f); + + ImGui::BeginGroup(); + ImGui::Dummy(ImVec2(ImGui::GetContentRegionAvail().x - 16.0f, 0)); + ImGui::Spacing(); + draw_fn(); + ImGui::Spacing(); + ImGui::EndGroup(); + + ImGui::Unindent(8.0f); + + // NOW we know the rect + ImVec2 rect_min = ImGui::GetItemRectMin(); + ImVec2 rect_max = ImGui::GetItemRectMax(); + + // Expand rect a bit for padding + rect_min.x -= 8.0f; + rect_max.x += 8.0f; + rect_max.y += style.ItemSpacing.y; + + // Move the top of the border up to account for label offset + rect_min.y -= border_offset; + + ImU32 border_color = ImGui::GetColorU32(ImGuiCol_Border); + float label_start = rect_min.x + 8.0f; + float label_end = label_start + text_size.x + 4.0f; + + // Top edge with gap for label + draw_list->AddLine(ImVec2(rect_min.x, rect_min.y), + ImVec2(label_start, rect_min.y), border_color); + draw_list->AddLine(ImVec2(label_end, rect_min.y), + ImVec2(rect_max.x, rect_min.y), border_color); + // Left, right, bottom + draw_list->AddLine(rect_min, ImVec2(rect_min.x, rect_max.y), border_color); + draw_list->AddLine(ImVec2(rect_max.x, rect_min.y), + ImVec2(rect_max.x, rect_max.y), border_color); + draw_list->AddLine(ImVec2(rect_min.x, rect_max.y), rect_max, border_color); + + // Label + draw_list->AddText(ImVec2(label_start + 2.0f, rect_min.y - border_offset), + ImGui::GetColorU32(ImGuiCol_Text), label); + + ImGui::SetCursorPosY(rect_max.y + style.ItemSpacing.y); +} + +void ConsoleSettingsDialog::OnDraw(ImGuiIO& io) { + ImGui::SetNextWindowPos(ImVec2(20, 20), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(20, 20), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowBgAlpha(0.90f); + bool dialog_open = true; + if (!ImGui::Begin("Console settings", &dialog_open, + ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoMove)) { + ImGui::End(); + return; + } + + if (!dialog_open) { + Close(); + ImGui::End(); + emulator_window_.ToggleConsoleSettingsDialog(); + return; + } + + if (ImGui::BeginTabBar("##Categories")) { + if (ImGui::BeginTabItem("User")) { + ImGui::BeginDisabled(emulator_window_.emulator()->is_title_open()); + ImGui::Dummy(ImVec2(2.f, 2.f)); + + GroupBox("Time", [&]() { + DrawTimezoneCombobox(io, &xconfig_data_); + + FlagCheckbox("Disable Daylight-Saving Time", + xconfig_data_.user.retail_flags, + static_cast(kernel::X_RETAIL_FLAGS::DSTOff)); + + FlagCheckbox( + "24H Time", xconfig_data_.user.retail_flags, + static_cast(kernel::X_RETAIL_FLAGS::TwentyFourHourClock)); + }); + + GroupBox("Locale", [&]() { + DrawCombobox(io, "Language", kLanguageMap, xconfig_data_.user.language); + DrawCombobox(io, "Country", kCountryMap, xconfig_data_.user.country); + }); + + GroupBox("Profile", [&]() { + DrawCombobox(io, "Default Profile", profiles_, + xconfig_data_.user.default_profile); + + FlagCheckbox("Parental Control", + xconfig_data_.user.parental_control_flags, + static_cast(kernel::X_PC_FLAGS::PCEnabled)); + }); + + ImGui::Dummy(ImVec2(2.f, 2.f)); + + GroupBox("Retail Options", [&]() { + FlagCheckbox("Dashboard Initialized", xconfig_data_.user.retail_flags, + static_cast( + kernel::X_RETAIL_FLAGS::DashboardInitialized)); + FlagCheckbox( + "IPTV Initialized", xconfig_data_.user.retail_flags, + static_cast(kernel::X_RETAIL_FLAGS::IPTVEnabled)); + FlagCheckbox( + "DVR Initialized", xconfig_data_.user.retail_flags, + static_cast(kernel::X_RETAIL_FLAGS::IPTVDVREnabled)); + FlagCheckbox( + "Kinect Initialized", xconfig_data_.user.retail_flags, + static_cast(kernel::X_RETAIL_FLAGS::KinectInitialized)); + }); + ImGui::EndDisabled(); + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("System")) { + ImGui::BeginDisabled(emulator_window_.emulator()->is_title_open()); + ImGui::Dummy(ImVec2(2.f, 2.f)); + + GroupBox("Video Options", [&]() { + DrawCombobox(io, "AV Region", kAVRegion, + xconfig_data_.secured.av_region); + + DrawResolutionCombobox( + io, {kernel::XVGAResolution.data(), kernel::XVGAResolution.size()}, + xconfig_data_.user.av_pack_hdmi_sz, xconfig_data_.user.video_flags); + + const auto res = + kernel::Resolution(xconfig_data_.user.av_pack_hdmi_sz.get()); + + ImGui::BeginDisabled(res.is_widescreen()); + FlagCheckbox("Widescreen", xconfig_data_.user.video_flags, + static_cast(kernel::X_VIDEO_FLAGS::Widescreen)); + ImGui::EndDisabled(); + }); + + GroupBox("Audio Options", [&]() { + FlagCheckbox("Mono", xconfig_data_.user.audio_flags, + static_cast(kernel::X_AUDIO_FLAGS::AnalogMono)); + + FlagCheckbox( + "Dolby Pro Logic", xconfig_data_.user.audio_flags, + static_cast(kernel::X_AUDIO_FLAGS::DolbyProLogic)); + + FlagCheckbox( + "Dolby Digital", xconfig_data_.user.audio_flags, + static_cast(kernel::X_AUDIO_FLAGS::DolbyDigital)); + + FlagCheckbox("Dolby Digital WMA PRO", xconfig_data_.user.audio_flags, + static_cast( + kernel::X_AUDIO_FLAGS::DolbyDigitalWithWMAPRO)); + + FlagCheckbox("Low Latency (unsupported)", + xconfig_data_.user.audio_flags, + static_cast(kernel::X_AUDIO_FLAGS::LowLatency)); + + float volume = xconfig_data_.user.music_volume.get(); + if (ImGui::SliderFloat("Audio player volume", &volume, 0.0f, 1.0f, + "%.2f")) { + xconfig_data_.user.music_volume = volume; + } + }); + + GroupBox("Network", [&]() { + if (ImGui::BeginTable("##NetworkTable", 2)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + ImGui::Text("MAC Address: "); + ImGui::TableNextColumn(); + + ByteArray("mac", xconfig_data_.secured.mac_address.data(), + xconfig_data_.secured.mac_address.size()); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + ImGui::Text("Network ID: "); + ImGui::TableNextColumn(); + + ByteArray("netid", xconfig_data_.secured.online_network_id.data(), + xconfig_data_.secured.online_network_id.size()); + + ImGui::EndTable(); + } + }); + ImGui::EndDisabled(); + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + } + + ImGui::Dummy(ImVec2(2.f, 2.f)); + // Bottom + ImGui::Separator(); + + ImGui::BeginDisabled(emulator_window_.emulator()->is_title_open()); + if (ImGui::Button("Save")) { + SaveConfig(); + save_confirmation_disappearance_ = ImGui::GetTime() + 3.0; + } + + if (save_confirmation_disappearance_ > ImGui::GetTime()) { + ImGui::SameLine(); + ImGui::Text("Settings Saved!"); + } + + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + + ImGui::GetContentRegionAvail().x - 55.f); + + if (ImGui::Button("Reset", ImVec2(55.f, 0.0f))) { + xconfig_->SetDefaults(); + xconfig_data_ = *xconfig_->GetXConfig(); + save_confirmation_disappearance_ = ImGui::GetTime() + 3.0; + } + ImGui::EndDisabled(); + ImGui::End(); +} + +void ConsoleSettingsDialog::SaveConfig() { + xconfig_->WriteXConfig(&xconfig_data_); +} + +} // namespace app +} // namespace xe diff --git a/src/xenia/app/console_settings_dialog.h b/src/xenia/app/console_settings_dialog.h new file mode 100644 index 000000000..fd8bc478e --- /dev/null +++ b/src/xenia/app/console_settings_dialog.h @@ -0,0 +1,62 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2026 Xenia Canary. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_APP_CONSOLE_SETTING_DIALOG_H_ +#define XENIA_APP_CONSOLE_SETTING_DIALOG_H_ + +#include "xenia/ui/imgui_dialog.h" +#include "xenia/ui/imgui_drawer.h" +#include "xenia/xbox.h" + +#include "xenia/kernel/xconfig.h" + +namespace xe { +namespace app { + +class EmulatorWindow; + +class ConsoleSettingsDialog final : public ui::ImGuiDialog { + public: + ConsoleSettingsDialog(ui::ImGuiDrawer* imgui_drawer, + EmulatorWindow& emulator_window, + kernel::XConfig* xconfig) + : ui::ImGuiDialog(imgui_drawer), + emulator_window_(emulator_window), + xconfig_(xconfig), + xconfig_data_(*xconfig->GetXConfig()) { + const auto profiles = emulator_window.emulator() + ->kernel_state() + ->xam_state() + ->profile_manager() + ->GetAccounts(); + + for (const auto& [xuid, profile] : *profiles) { + profiles_.insert({xuid, profile.GetGamertagString()}); + } + } + + protected: + void OnDraw(ImGuiIO& io) override; + + private: + void SaveConfig(); + + EmulatorWindow& emulator_window_; + kernel::XConfig* xconfig_ = nullptr; + kernel::XConfigData xconfig_data_{}; + std::map profiles_; + + // UI specific variables + double save_confirmation_disappearance_ = 0.0; +}; + +} // namespace app +} // namespace xe + +#endif diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index 78dae2a20..48e84a7c5 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -19,6 +19,8 @@ #if defined(__clang__) #pragma clang diagnostic pop #endif + +#include "xenia/app/console_settings_dialog.h" #include "xenia/base/assert.h" #include "xenia/base/clock.h" #include "xenia/base/cvar.h" @@ -36,6 +38,7 @@ #include "xenia/kernel/xam/profile_manager.h" #include "xenia/kernel/xam/xam_module.h" #include "xenia/kernel/xam/xam_state.h" +#include "xenia/kernel/xconfig.h" #include "xenia/ui/file_picker.h" #include "xenia/ui/graphics_provider.h" #include "xenia/ui/imgui_dialog.h" @@ -725,7 +728,8 @@ void EmulatorWindow::XMPConfigDialog::OnDraw(ImGuiIO& io) { volume_ = emulator_window_.emulator_->audio_media_player()->GetVolume()->load(); - if (ImGui::SliderFloat("Audio player volume", &volume_, 0.0f, 1.0f)) { + if (ImGui::SliderFloat("Audio player volume", &volume_, 0.0f, 1.0f, + "%.2f")) { audio_player->SetVolume(volume_); } } @@ -882,6 +886,15 @@ bool EmulatorWindow::Initialize() { } main_menu->AddChild(std::move(xmp_menu)); + // Console menu + auto console_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Console"); + { + console_menu->AddChild(MenuItem::Create( + MenuItem::Type::kString, "&Open console settings", "", + std::bind(&EmulatorWindow::ToggleConsoleSettingsDialog, this))); + } + main_menu->AddChild(std::move(console_menu)); + // Help menu. auto help_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Help"); { @@ -1541,12 +1554,15 @@ void EmulatorWindow::GpuClearCaches() { emulator()->graphics_system()->ClearCaches(); } -void EmulatorWindow::SetFullscreen(bool fullscreen) { - if (window_->IsFullscreen() == fullscreen) { +void EmulatorWindow::SetFullscreen(bool fullscreen_) { + if (window_->IsFullscreen() == fullscreen_) { return; } - window_->SetFullscreen(fullscreen); - window_->SetCursorVisibility(fullscreen + + OVERRIDE_bool(fullscreen, fullscreen_); + + window_->SetFullscreen(fullscreen_); + window_->SetCursorVisibility(fullscreen_ ? ui::Window::CursorVisibility::kAutoHidden : ui::Window::CursorVisibility::kVisible); } @@ -1596,6 +1612,20 @@ void EmulatorWindow::ToggleXMPConfigDialog() { } } +void EmulatorWindow::ToggleConsoleSettingsDialog() { + if (!console_settings_dialog_) { + console_settings_dialog_ = + std::unique_ptr(new ConsoleSettingsDialog( + imgui_drawer_.get(), *this, emulator_->kernel_state()->xconfig())); + } else { + if (console_settings_dialog_->IsClosing()) { + console_settings_dialog_.release(); + } else { + console_settings_dialog_.reset(); + } + } +} + void EmulatorWindow::ToggleControllerVibration() { auto input_sys = emulator()->input_system(); if (input_sys) { @@ -2341,6 +2371,10 @@ void EmulatorWindow::ClearDialogs() { display_config_dialog_.reset(); } + if (console_settings_dialog_) { + console_settings_dialog_.reset(); + } + imgui_drawer_.get()->ClearDialogs(); emulator_->kernel_state()->xam_state()->xam_dialogs_shown_ = 0; } diff --git a/src/xenia/app/emulator_window.h b/src/xenia/app/emulator_window.h index bbc1a7c63..88cf43906 100644 --- a/src/xenia/app/emulator_window.h +++ b/src/xenia/app/emulator_window.h @@ -29,6 +29,8 @@ namespace xe { namespace app { +class ConsoleSettingsDialog; + struct RecentTitleEntry { std::string title_name; std::filesystem::path path_to_file; @@ -94,6 +96,8 @@ class EmulatorWindow { void ToggleProfilesConfigDialog(); void ToggleXMPConfigDialog(); + void ToggleConsoleSettingsDialog(); + void SetHotkeysState(bool enabled) { disable_hotkeys_ = !enabled; } // Types of button functions for hotkeys. @@ -313,6 +317,7 @@ class EmulatorWindow { bool initializing_shader_storage_ = false; std::unique_ptr display_config_dialog_; + std::unique_ptr console_settings_dialog_; // Storing pointers and toggling dialog state is useful for broadcasting // messages back to guest. diff --git a/src/xenia/app/xenia_main.cc b/src/xenia/app/xenia_main.cc index 8092ebf0e..2dbefb924 100644 --- a/src/xenia/app/xenia_main.cc +++ b/src/xenia/app/xenia_main.cc @@ -130,7 +130,8 @@ DECLARE_bool(debug); DEFINE_bool(discord, true, "Enable Discord rich presence", "General"); -DECLARE_bool(widescreen); +DECLARE_int32(window_size_x); +DECLARE_int32(window_size_y); namespace xe { namespace app { @@ -538,12 +539,10 @@ bool EmulatorApp::OnInitialize() { emulator_ = std::make_unique("", storage_root, content_root, cache_root); - // Determine window size based on user setting. - auto res = xe::gpu::GraphicsSystem::GetInternalDisplayResolution(); - // Main emulator display window. - emulator_window_ = EmulatorWindow::Create(emulator_.get(), app_context(), - res.first, res.second); + emulator_window_ = + EmulatorWindow::Create(emulator_.get(), app_context(), + cvars::window_size_x, cvars::window_size_y); if (!emulator_window_) { XELOGE("Failed to create the main emulator window"); return false; diff --git a/src/xenia/apu/audio_media_player.cc b/src/xenia/apu/audio_media_player.cc index 4dfd1f01d..ca234960c 100644 --- a/src/xenia/apu/audio_media_player.cc +++ b/src/xenia/apu/audio_media_player.cc @@ -27,8 +27,6 @@ extern "C" { } // extern "C" DEFINE_bool(enable_xmp, true, "Enables Music Player playback.", "APU"); -DEFINE_int32(xmp_default_volume, 70, - "Default music volume if game doesn't set it [0-100].", "APU"); namespace xe { namespace apu { @@ -246,7 +244,8 @@ void AudioMediaPlayer::Play() { OnStateChanged(); if (volume_ == 0.0f) { - volume_ = cvars::xmp_default_volume / 100.0f; + volume_ = kernel_state_->xconfig()->ReadSetting( + kernel::XCONFIG_USER_CATEGORY, kernel::XCONFIG_USER_MUSIC_VOLUME); } // Always apply the stored volume to the newly created driver diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 6345e0f60..adc628563 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -84,8 +84,6 @@ DEFINE_bool(allow_game_relative_writes, false, "generating test data to compare with original hardware. ", "General"); -DECLARE_int32(user_language); - DECLARE_bool(allow_plugins); DEFINE_int32(priority_class, 0, @@ -1555,8 +1553,9 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path, kernel_state_->xam_state()->user_tracker()->AddTitleToPlayedList(); if (game_info_database_->IsValid()) { - title_name_ = game_info_database_->GetTitleName( - static_cast(cvars::user_language)); + title_name_ = game_info_database_->GetTitleName(static_cast( + kernel_state_->xconfig()->ReadSetting( + kernel::XCONFIG_USER_CATEGORY, kernel::XCONFIG_USER_LANGUAGE))); XELOGI("Title name: {}", title_name_); // Show achievments data diff --git a/src/xenia/gpu/graphics_system.cc b/src/xenia/gpu/graphics_system.cc index f43672a8e..913697410 100644 --- a/src/xenia/gpu/graphics_system.cc +++ b/src/xenia/gpu/graphics_system.cc @@ -23,34 +23,10 @@ #include "xenia/ui/window.h" #include "xenia/ui/windowed_app_context.h" -DEFINE_uint32(internal_display_resolution, 8, - "Allow games that support different resolutions to render " - "in a specific resolution.\n" - "This is not guaranteed to work with all games or improve " - "performance.\n" - " 0=640x480\n" - " 1=640x576\n" - " 2=720x480\n" - " 3=720x576\n" - " 4=800x600\n" - " 5=848x480\n" - " 6=1024x768\n" - " 7=1152x864\n" - " 8=1280x720 (Default)\n" - " 9=1280x768\n" - " 10=1280x960\n" - " 11=1280x1024\n" - " 12=1360x768\n" - " 13=1440x900\n" - " 14=1680x1050\n" - " 15=1920x540\n" - " 16=1920x1080\n" - " 17=internal_display_resolution_x/y", - "Video"); -DEFINE_uint32(internal_display_resolution_x, 1280, +DEFINE_uint32(custom_internal_display_resolution_x, 0, "Custom width. See internal_display_resolution. Range 1-1920.", "Video"); -DEFINE_uint32(internal_display_resolution_y, 720, +DEFINE_uint32(custom_internal_display_resolution_y, 0, "Custom height. See internal_display_resolution. Range 1-1080.\n", "Video"); @@ -95,21 +71,6 @@ X_STATUS GraphicsSystem::Setup(cpu::Processor* processor, scaled_aspect_x_ = 16; scaled_aspect_y_ = 9; - auto custom_res_x = cvars::internal_display_resolution_x; - auto custom_res_y = cvars::internal_display_resolution_y; - if (!custom_res_x || custom_res_x > 1920 || !custom_res_y || - custom_res_y > 1080) { - OVERRIDE_uint32(internal_display_resolution_x, - internal_display_resolution_entries[8].first); - OVERRIDE_uint32(internal_display_resolution_y, - internal_display_resolution_entries[8].second); - config::SaveConfig(); - xe::FatalError(fmt::format( - "Invalid custom resolution specified: {}x{}\n" - "Width must be between 1-1920.\nHeight must be between 1-1080.", - custom_res_x, custom_res_y)); - } - if (with_presentation && provider_) { // Safe if either the UI thread call or the presenter creation fails. if (app_context_) { @@ -175,8 +136,15 @@ X_STATUS GraphicsSystem::Setup(cpu::Processor* processor, #endif while (frame_limiter_worker_running_) { + // If there is no title running then there is no need for guest + // frame limiter thread. + if (!kernel_state_->title_id()) { + xe::threading::Sleep(std::chrono::milliseconds(100)); + continue; + } + register_file()->values[XE_GPU_REG_D1MODE_V_COUNTER] += - GetInternalDisplayResolution().second; + GetResolution().second; #if XE_PLATFORM_WIN32 if (cvars::vsync) { @@ -454,14 +422,23 @@ bool GraphicsSystem::Restore(ByteStream* stream) { return command_processor_->Restore(stream); } -std::pair GraphicsSystem::GetInternalDisplayResolution() { - if (cvars::internal_display_resolution >= - internal_display_resolution_entries.size()) { - return {cvars::internal_display_resolution_x, - cvars::internal_display_resolution_y}; +std::pair GraphicsSystem::GetResolution() const { + if (!kernel_state_) { + return {1280, 720}; } - return internal_display_resolution_entries - [cvars::internal_display_resolution]; + + if (cvars::custom_internal_display_resolution_x != 0 && + cvars::custom_internal_display_resolution_y != 0) { + return {cvars::custom_internal_display_resolution_x, + cvars::custom_internal_display_resolution_y}; + } + + const auto resolution = + kernel::Resolution(kernel_state()->xconfig()->ReadSetting( + kernel::XCONFIG_USER_CATEGORY, + kernel::XCONFIG_USER_AV_COMPOSITE_SCREENSZ)); + + return {resolution.width_, resolution.height_}; } } // namespace gpu diff --git a/src/xenia/gpu/graphics_system.h b/src/xenia/gpu/graphics_system.h index 3f8d5d7a9..0c616dd7f 100644 --- a/src/xenia/gpu/graphics_system.h +++ b/src/xenia/gpu/graphics_system.h @@ -34,28 +34,6 @@ class Emulator; namespace xe { namespace gpu { -constexpr std::array, 17> - internal_display_resolution_entries = {{{640, 480}, - {640, 576}, - {720, 480}, - {720, 576}, - {800, 600}, - {848, 480}, - {1024, 768}, - {1152, 864}, - {1280, 720}, - {1280, 768}, - {1280, 960}, - {1280, 1024}, - {1360, 768}, - {1440, 900}, - {1680, 1050}, - {1920, 540}, - {1920, 1080}}}; - -constexpr std::array, 3> - driver_display_resolution = {{{1440, 900}, {1280, 720}, {1680, 1050}}}; - class CommandProcessor; class GraphicsSystem { @@ -109,7 +87,7 @@ class GraphicsSystem { bool Save(ByteStream* stream); bool Restore(ByteStream* stream); - static std::pair GetInternalDisplayResolution(); + std::pair GetResolution() const; std::pair GetScaledAspectRatio() const { return {scaled_aspect_x_, scaled_aspect_y_}; diff --git a/src/xenia/kernel/kernel_flags.cc b/src/xenia/kernel/kernel_flags.cc index db10c2367..bb9e9200e 100644 --- a/src/xenia/kernel/kernel_flags.cc +++ b/src/xenia/kernel/kernel_flags.cc @@ -13,4 +13,4 @@ DEFINE_bool(headless, false, "Don't display any UI, using defaults for prompts as needed.", "UI"); DEFINE_bool(log_high_frequency_kernel_calls, false, - "Log kernel calls with the kHighFrequency tag.", "Kernel"); + "Log kernel calls with the kHighFrequency tag.", "Logging"); diff --git a/src/xenia/kernel/kernel_state.cc b/src/xenia/kernel/kernel_state.cc index ebff0ebf0..800653051 100644 --- a/src/xenia/kernel/kernel_state.cc +++ b/src/xenia/kernel/kernel_state.cc @@ -64,6 +64,8 @@ KernelState::KernelState(Emulator* emulator) file_system_ = emulator->file_system(); xam_state_ = std::make_unique(emulator, this); smc_ = std::make_unique(); + xconfig_ = + std::make_unique(emulator->storage_root() / "xconfig.settings"); InitializeKernelGuestGlobals(); kernel_version_ = KernelVersion(cvars::kernel_build_version); diff --git a/src/xenia/kernel/kernel_state.h b/src/xenia/kernel/kernel_state.h index 096948de5..75e27f307 100644 --- a/src/xenia/kernel/kernel_state.h +++ b/src/xenia/kernel/kernel_state.h @@ -31,6 +31,7 @@ #include "xenia/kernel/xam/user_profile.h" #include "xenia/kernel/xam/xam_state.h" #include "xenia/kernel/xam/xdbf/spa_info.h" +#include "xenia/kernel/xconfig.h" #include "xenia/kernel/xevent.h" #include "xenia/vfs/virtual_file_system.h" @@ -193,6 +194,8 @@ class KernelState { XmpVolumePatch* xmp_volume_patch() const { return xmp_volume_patch_.get(); } void InitXmpVolumePatch(); + XConfig* xconfig() const { return xconfig_.get(); } + std::bitset<4> GetConnectedUsers() const; // Access must be guarded by the global critical region. @@ -350,6 +353,7 @@ class KernelState { std::unique_ptr xam_state_; std::unique_ptr smc_; std::unique_ptr xmp_volume_patch_; + std::unique_ptr xconfig_; KernelVersion kernel_version_; diff --git a/src/xenia/kernel/xam/apps/xam_app.cc b/src/xenia/kernel/xam/apps/xam_app.cc index c1b859208..c76d3dcfe 100644 --- a/src/xenia/kernel/xam/apps/xam_app.cc +++ b/src/xenia/kernel/xam/apps/xam_app.cc @@ -169,6 +169,16 @@ X_HRESULT XamApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr, args->unk1.get(), args->unk2.get(), args->unk3.get()); return X_E_SUCCESS; } + // Causes dashboard to correctly process language/region change. It does not + // contain any buffer. + case 0x8000000D: { + const bool is_pc_enabled = + (kernel_state_->xconfig()->ReadSetting( + XCONFIG_USER_CATEGORY, XCONFIG_USER_PC_FLAGS) & + X_PC_FLAGS::PCEnabled) != 0; + + return is_pc_enabled ? X_E_ACCESS_DENIED : X_E_SUCCESS; + } } XELOGE( "Unimplemented XAM message app={:08X}, msg={:08X}, arg1={:08X}, " diff --git a/src/xenia/kernel/xam/user_tracker.cc b/src/xenia/kernel/xam/user_tracker.cc index e9f3588ed..d6dc2686e 100644 --- a/src/xenia/kernel/xam/user_tracker.cc +++ b/src/xenia/kernel/xam/user_tracker.cc @@ -349,7 +349,8 @@ void UserTracker::UpdateTitleGpdFile() { } auto user_language = spa_data_->GetExistingLanguage( - static_cast(cvars::user_language)); + static_cast(kernel_state()->xconfig()->ReadSetting( + kernel::XCONFIG_USER_CATEGORY, kernel::XCONFIG_USER_LANGUAGE))); // First add achievements because of lowest ID for (const auto& entry : spa_data_->GetAchievements()) { diff --git a/src/xenia/kernel/xam/xam_avatar.cc b/src/xenia/kernel/xam/xam_avatar.cc index f1dee5e20..df7c38743 100644 --- a/src/xenia/kernel/xam/xam_avatar.cc +++ b/src/xenia/kernel/xam/xam_avatar.cc @@ -14,8 +14,8 @@ DEFINE_bool(allow_avatar_initialization, false, "Enable Avatar Initialization\n" - " Only set true when testing Avatar games. Certain games may\n" - " require kinect implementation.", + "Only set true when testing Avatar games. Certain games may crash " + "due to requirement of full avatar implementation.", "Kernel"); namespace xe { diff --git a/src/xenia/kernel/xam/xam_info.cc b/src/xenia/kernel/xam/xam_info.cc index 49776e249..ba82f6b91 100644 --- a/src/xenia/kernel/xam/xam_info.cc +++ b/src/xenia/kernel/xam/xam_info.cc @@ -22,6 +22,7 @@ #include "xenia/kernel/xboxkrnl/xboxkrnl_memory.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_modules.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_threading.h" +#include "xenia/kernel/xconfig.h" #include "xenia/kernel/xenumerator.h" #include "xenia/kernel/xthread.h" #include "xenia/ui/imgui_dialog.h" @@ -45,8 +46,6 @@ DEFINE_int32(avpack, 8, " 7 = TV PAL-60\n" " 8 = HDMI (default)", "Video"); -DECLARE_uint32(audio_flag); - DEFINE_bool(staging_mode, 0, "Enables preview mode in dashboards to render debug information.", "Kernel"); @@ -605,11 +604,11 @@ dword_result_t XGetAudioFlags_entry() { return 2; } - if (!cvars::audio_flag) { - return 0x10000 | 0x1; - } + const auto audio_flags = kernel_state()->xconfig()->ReadSetting( + XCONFIG_USER_CATEGORY, + XCONFIG_USER_CATEGORY_ENTRIES::XCONFIG_USER_AUDIO_FLAGS); - return cvars::audio_flag; + return audio_flags ? audio_flags : 0x10000 | 0x1; } DECLARE_XAM_EXPORT1(XGetAudioFlags, kNone, kImplemented); @@ -764,6 +763,21 @@ void GetSystemTimeAsFileTime_entry(lpqword_t time_ptr, } DECLARE_XAM_EXPORT1(GetSystemTimeAsFileTime, kNone, kImplemented); +dword_result_t XamIsIptvEnabled_entry() { + const bool iptv_enabled = + kernel_state()->xconfig()->ReadSetting( + X_CONFIG_CATEGORY::XCONFIG_USER_CATEGORY, XCONFIG_USER_RETAIL_FLAGS) & + X_RETAIL_FLAGS::IPTVEnabled; + + return !iptv_enabled ? X_E_FAIL : X_ERROR_SUCCESS; +} +DECLARE_XAM_EXPORT1(XamIsIptvEnabled, kNone, kImplemented); + +dword_result_t XamLookupCommonStringByIndex_entry(dword_t string_index) { + return 0; +} +DECLARE_XAM_EXPORT1(XamLookupCommonStringByIndex, kNone, kImplemented); + } // namespace xam } // namespace kernel } // namespace xe diff --git a/src/xenia/kernel/xam/xam_locale.cc b/src/xenia/kernel/xam/xam_locale.cc index ffaaa67a6..0212f8006 100644 --- a/src/xenia/kernel/xam/xam_locale.cc +++ b/src/xenia/kernel/xam/xam_locale.cc @@ -21,8 +21,6 @@ #include "third_party/fmt/include/fmt/format.h" #include "third_party/fmt/include/fmt/xchar.h" -DECLARE_int32(user_country); -DECLARE_int32(user_language); // TODO(gibbed): put these forward decls in a header somewhere. namespace xe { @@ -205,17 +203,14 @@ uint8_t xeXamGetLocaleFromCountry(uint8_t id) { // Helpers. uint8_t xeXamGetLocaleEx(uint8_t max_country_id, uint8_t max_locale_id) { - // TODO(gibbed): rework when XConfig is cleanly implemented. - uint8_t country_id = static_cast(cvars::user_country); - /*if (XSUCCEEDED(xboxkrnl::xeExGetXConfigSetting( - 3, 14, &country_id, sizeof(country_id), nullptr))) {*/ + uint8_t country_id = kernel_state()->xconfig()->ReadSetting( + XCONFIG_USER_CATEGORY, XCONFIG_USER_COUNTRY); if (country_id <= max_country_id) { uint8_t locale_id = xeXamGetLocaleFromCountry(country_id); if (locale_id <= max_locale_id) { return locale_id; } } - /*}*/ // couldn't find locale, fallback from game region. auto game_region = xeXGetGameRegion(); @@ -529,7 +524,9 @@ uint32_t xeXGetGameRegion() { 0x02FEu, 0x03FFu, 0x02FEu, 0x03FFu, 0x02FEu, 0x02FEu, 0xFFFFu, 0x03FFu, 0x03FFu, 0x03FFu, 0x03FFu, 0x02FEu, 0x03FFu, 0x03FFu, 0x02FEu, 0x00FFu, 0x03FFu, 0x03FFu, 0x03FFu, 0x03FFu, 0x03FFu, 0x03FFu, 0x03FFu}; - auto country = static_cast(cvars::user_country); + auto country = kernel_state()->xconfig()->ReadSetting( + XCONFIG_USER_CATEGORY, + XCONFIG_USER_CATEGORY_ENTRIES::XCONFIG_USER_COUNTRY); return country < xe::countof(table) ? table[country] : 0xFFFFu; } @@ -537,7 +534,11 @@ dword_result_t XGetGameRegion_entry() { return xeXGetGameRegion(); } DECLARE_XAM_EXPORT1(XGetGameRegion, kNone, kStub); XLanguage xeGetLanguage(bool extended_languages_support) { - auto desired_language = static_cast(cvars::user_language); + auto desired_language = + static_cast(kernel_state()->xconfig()->ReadSetting( + XCONFIG_USER_CATEGORY, + XCONFIG_USER_CATEGORY_ENTRIES::XCONFIG_USER_LANGUAGE)); + uint32_t region = xeXGetGameRegion(); auto max_languages = extended_languages_support ? XLanguage::kMaxLanguages : XLanguage::kSChinese; @@ -599,6 +600,24 @@ pointer_result_t XamGetLanguageTypefacePatch_entry(dword_t language) { } DECLARE_XAM_EXPORT1(XamGetLanguageTypefacePatch, kNone, kStub); +dword_result_t XamGetCountry_entry() { + return kernel_state()->xconfig()->ReadSetting( + XCONFIG_USER_CATEGORY, + XCONFIG_USER_CATEGORY_ENTRIES::XCONFIG_USER_COUNTRY); +} +DECLARE_XAM_EXPORT1(XamGetCountry, kNone, kSketchy); + +dword_result_t XamSetCountry_entry(dword_t country) { + const uint8_t country_real = country; + + kernel_state()->xconfig()->WriteSetting( + XCONFIG_USER_CATEGORY, + XCONFIG_USER_CATEGORY_ENTRIES::XCONFIG_USER_COUNTRY, &country_real); + + return 0; +} +DECLARE_XAM_EXPORT1(XamSetCountry, kNone, kSketchy); + } // namespace xam } // namespace kernel } // namespace xe diff --git a/src/xenia/kernel/xam/xam_nui.cc b/src/xenia/kernel/xam/xam_nui.cc index aa2417e84..f4ca00046 100644 --- a/src/xenia/kernel/xam/xam_nui.cc +++ b/src/xenia/kernel/xam/xam_nui.cc @@ -19,12 +19,6 @@ #include "xenia/ui/windowed_app_context.h" #include "xenia/xbox.h" -DEFINE_bool(allow_nui_initialization, false, - "Enable NUI initialization\n" - " Only set true when testing kinect games. Certain games may\n" - " require avatar implementation.", - "Kernel"); - namespace xe { namespace kernel { namespace xam { @@ -69,8 +63,14 @@ dword_result_t XamNuiGetDeviceStatus_entry( */ status_ptr.Zero(); - status_ptr->status = cvars::allow_nui_initialization; - return cvars::allow_nui_initialization ? X_ERROR_SUCCESS : 0xC0050006; + + const bool kinect_initialized = + kernel_state()->xconfig()->ReadSetting( + X_CONFIG_CATEGORY::XCONFIG_USER_CATEGORY, XCONFIG_USER_RETAIL_FLAGS) & + X_RETAIL_FLAGS::KinectInitialized; + + status_ptr->status = kinect_initialized; + return kinect_initialized ? X_ERROR_SUCCESS : 0xC0050006; } DECLARE_XAM_EXPORT1(XamNuiGetDeviceStatus, kNone, kStub); @@ -195,8 +195,12 @@ dword_result_t XamNuiIsDeviceReady_entry() { - 0x0004 - 0x0040 */ - uint16_t device_state = cvars::allow_nui_initialization ? 1 : 0; - return device_state >> 1 & 1; + const bool kinect_initialized = + kernel_state()->xconfig()->ReadSetting( + X_CONFIG_CATEGORY::XCONFIG_USER_CATEGORY, XCONFIG_USER_RETAIL_FLAGS) & + X_RETAIL_FLAGS::KinectInitialized; + + return kinect_initialized >> 1 & 1; } DECLARE_XAM_EXPORT1(XamNuiIsDeviceReady, kNone, kImplemented); diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index 6f481a0c0..f12bce44b 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -988,7 +988,8 @@ DECLARE_XAM_EXPORT1(XamUserGetUserFlagsFromXUID, kUserProfiles, kImplemented); dword_result_t XamUserGetOnlineLanguageFromXUID_entry(qword_t xuid) { const auto& user = kernel_state()->xam_state()->GetUserProfile(xuid); if (!user) { - return cvars::user_language; + return kernel_state()->xconfig()->ReadSetting( + XCONFIG_USER_CATEGORY, XCONFIG_USER_LANGUAGE); } return user->GetLanguage(); } @@ -998,7 +999,8 @@ DECLARE_XAM_EXPORT1(XamUserGetOnlineLanguageFromXUID, kUserProfiles, dword_result_t XamUserGetOnlineCountryFromXUID_entry(qword_t xuid) { const auto& user = kernel_state()->xam_state()->GetUserProfile(xuid); if (!user) { - return cvars::user_country; + return kernel_state()->xconfig()->ReadSetting( + XCONFIG_USER_CATEGORY, XCONFIG_USER_COUNTRY); } return user->GetCountry(); } diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_audio.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_audio.cc index a0c7651b2..cf59c490c 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_audio.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_audio.cc @@ -21,7 +21,9 @@ namespace kernel { namespace xboxkrnl { dword_result_t XAudioGetSpeakerConfig_entry(lpdword_t config_ptr) { - *config_ptr = cvars::audio_flag; + kernel_state()->xconfig()->ReadSetting( + XCONFIG_USER_CATEGORY, + XCONFIG_USER_CATEGORY_ENTRIES::XCONFIG_USER_AUDIO_FLAGS, config_ptr); return X_ERROR_SUCCESS; } DECLARE_XBOXKRNL_EXPORT1(XAudioGetSpeakerConfig, kAudio, kImplemented); diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_strings.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_strings.cc index 39875ea3d..6e1b3caef 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_strings.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_strings.cc @@ -12,7 +12,7 @@ #include "xenia/kernel/xboxkrnl/xboxkrnl_private.h" DEFINE_bool(log_string_format_kernel_calls, false, - "Log kernel calls with the kHighFrequency tag.", "Logging"); + "Log usage of print formatters like sprintf.", "Logging"); namespace xe { namespace kernel { diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_video.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_video.cc index 9fa9808c1..f4da2c288 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_video.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_video.cc @@ -16,21 +16,10 @@ #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_private.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_rtl.h" +#include "xenia/kernel/xconfig.h" #include "xenia/xbox.h" -DEFINE_int32( - video_standard, 1, - "Enables switching between different video signals.\n 1=NTSC\n " - "2=NTSC-J\n 3=PAL\n", - "Video"); - -DEFINE_bool(use_50Hz_mode, false, "Enables usage of PAL-50 mode.", "Video"); DEFINE_bool(interlaced, false, "Toggles interlaced mode.", "Video"); - -// TODO: This is stored in XConfig somewhere, probably in video flags. -DEFINE_bool(widescreen, true, "Toggles between 16:9 and 4:3 aspect ratio.", - "Video"); - // BT.709 on modern monitors and TVs looks the closest to the Xbox 360 connected // to an HDTV. DEFINE_uint32(kernel_display_gamma_type, 2, @@ -42,37 +31,22 @@ DEFINE_double(kernel_display_gamma_power, 2.22222233, "Display gamma to use with kernel_display_gamma_type 3.", "Kernel"); -inline const static uint32_t GetVideoStandard() { - if (cvars::video_standard < 1 || cvars::video_standard > 3) { - return 1; - } - - return cvars::video_standard; -} - -inline const static float GetVideoRefreshRate() { - return cvars::use_50Hz_mode ? 50.0f : 60.0f; -} - -inline const static std::pair GetDisplayAspectRatio() { - if (cvars::widescreen) { - return {16, 9}; - } - - return {4, 3}; -} - -static std::pair CalculateScaledAspectRatio(uint32_t fb_x, - uint32_t fb_y) { +static std::pair CalculateScaledAspectRatio( + uint32_t fb_x, uint32_t fb_y, bool forced_widescreen) { // Calculate the game's final aspect ratio as it would appear on a physical // TV. - auto dar = GetDisplayAspectRatio(); - uint32_t display_x = dar.first; - uint32_t display_y = dar.second; + const auto res = xe::kernel::Resolution(fb_x, fb_y); - auto res = xe::gpu::GraphicsSystem::GetInternalDisplayResolution(); - uint32_t res_x = res.first; - uint32_t res_y = res.second; + uint32_t display_x = res.aspect_ratio().first; + uint32_t display_y = res.aspect_ratio().second; + + if (forced_widescreen) { + display_x = 16; + display_y = 9; + } + + uint32_t res_x = res.width_.get(); + uint32_t res_y = res.height_.get(); uint32_t x_factor = std::gcd(fb_x, res_x); res_x /= x_factor; @@ -103,6 +77,47 @@ namespace xe { namespace kernel { namespace xboxkrnl { +bool IsWidescreen(KernelState* kernel_state, Resolution res) { + if (res.is_widescreen()) { + return true; + } + + return kernel_state->xconfig()->ReadSetting( + XCONFIG_USER_CATEGORY, XCONFIG_USER_VIDEO_FLAGS) & + X_VIDEO_FLAGS::Widescreen; +} + +// Video standard only supports value from 1 to 3. PAL50 is not included here +// and PAL50 is converted to PAL with 50Hz in GetVideoRefreshRate. +X_AV_VIDEO_STANDARD GetVideoStandard(KernelState* kernel_state) { + auto av_region = static_cast( + (kernel_state->xconfig()->ReadSetting( + XCONFIG_SECURED_CATEGORY, XCONFIG_SECURED_AV_REGION) & + 0xFF00) >> + 8); + + if (av_region == X_AV_VIDEO_STANDARD::PAL_50) { + av_region = X_AV_VIDEO_STANDARD::PAL; + } + + if (av_region < X_AV_VIDEO_STANDARD::NTSCM || + av_region > X_AV_VIDEO_STANDARD::PAL_50) { + return X_AV_VIDEO_STANDARD::NTSCM; + } + + return av_region; +} + +float GetVideoRefreshRate(KernelState* kernel_state) { + const bool is_50Hz = + (kernel_state->xconfig()->ReadSetting( + XCONFIG_SECURED_CATEGORY, XCONFIG_SECURED_AV_REGION) >> + 23) & + 0x1; + + return !is_50Hz ? 60.0f : 50.0f; +} + // https://web.archive.org/web/20150805074003/https://www.tweakoz.com/orkid/ // http://www.tweakoz.com/orkid/dox/d3/d52/xb360init_8cpp_source.html // https://github.com/Free60Project/xenosfb/ @@ -205,17 +220,25 @@ void VdQueryVideoMode(X_VIDEO_MODE* video_mode, // TODO(benvanik): get info from actual display. std::memset(video_mode, 0, sizeof(X_VIDEO_MODE)); - auto display_res = gpu::GraphicsSystem::GetInternalDisplayResolution(); + // Later calculate if resolution is widescreen or not and apply flag + // accordingly. Technically possible resolutions should depend on av_pack, but + // we can ignore it. + const auto resolution = + Resolution(kernel_state()->xconfig()->ReadSetting( + XCONFIG_USER_CATEGORY, XCONFIG_USER_AV_COMPOSITE_SCREENSZ)); - video_mode->display_width = display_res.first; - video_mode->display_height = display_res.second; + const auto is_widescreen = IsWidescreen(kernel_state(), resolution); + + video_mode->display_width = resolution.width_.get(); + video_mode->display_height = resolution.height_.get(); video_mode->is_interlaced = cvars::interlaced; - video_mode->is_widescreen = cvars::widescreen; + video_mode->is_widescreen = is_widescreen; video_mode->is_hi_def = video_mode->display_width >= 0x500; - video_mode->refresh_rate = GetVideoRefreshRate(); - video_mode->video_standard = GetVideoStandard(); + video_mode->refresh_rate = GetVideoRefreshRate(kernel_state()); + video_mode->video_standard = + static_cast(GetVideoStandard(kernel_state())); video_mode->pixel_rate = 0x8A; - video_mode->widescreen_flag = cvars::widescreen ? 0x01 : 0x03; + video_mode->widescreen_flag = is_widescreen ? 0x01 : 0x03; } void VdQueryRealVideoMode_entry(pointer_t video_mode) { @@ -370,7 +393,12 @@ dword_result_t VdInitializeScalerCommandBuffer_entry( uint32_t fb_x = (scaled_output_wh >> 16) & 0xFFFF; uint32_t fb_y = scaled_output_wh & 0xFFFF; - auto aspect = CalculateScaledAspectRatio(fb_x, fb_y); + const bool forced_widescreen = + kernel_state()->xconfig()->ReadSetting( + XCONFIG_USER_CATEGORY, XCONFIG_USER_VIDEO_FLAGS) & + X_VIDEO_FLAGS::Widescreen; + + auto aspect = CalculateScaledAspectRatio(fb_x, fb_y, forced_widescreen); auto graphics_system = kernel_state()->emulator()->graphics_system(); graphics_system->SetScaledAspectRatio(aspect.first, aspect.second); diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_xconfig.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_xconfig.cc index 562f46607..79a97e380 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_xconfig.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_xconfig.cc @@ -7,295 +7,35 @@ ****************************************************************************** */ -#include "xenia/kernel/xboxkrnl/xboxkrnl_xconfig.h" #include "xenia/base/logging.h" #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_private.h" +#include "xenia/kernel/xconfig.h" #include "xenia/xbox.h" -DEFINE_int32(user_language, 1, - "User language ID.\n" - " 1=en 2=ja 3=de 4=fr 5=es 6=it 7=ko 8=zh\n" - " 9=pt 11=pl 12=ru 13=sv 14=tr 15=nb 16=nl 17=zh", - "XConfig"); - -DEFINE_int32(user_country, 103, - "User country ID.\n" - " 1=AE 2=AL 3=AM 4=AR 5=AT 6=AU 7=AZ 8=BE 9=BG\n" - " 10=BH 11=BN 12=BO 13=BR 14=BY 15=BZ 16=CA 18=CH 19=CL\n" - " 20=CN 21=CO 22=CR 23=CZ 24=DE 25=DK 26=DO 27=DZ 28=EC\n" - " 29=EE 30=EG 31=ES 32=FI 33=FO 34=FR 35=GB 36=GE 37=GR\n" - " 38=GT 39=HK 40=HN 41=HR 42=HU 43=ID 44=IE 45=IL 46=IN\n" - " 47=IQ 48=IR 49=IS 50=IT 51=JM 52=JO 53=JP 54=KE 55=KG\n" - " 56=KR 57=KW 58=KZ 59=LB 60=LI 61=LT 62=LU 63=LV 64=LY\n" - " 65=MA 66=MC 67=MK 68=MN 69=MO 70=MV 71=MX 72=MY 73=NI\n" - " 74=NL 75=NO 76=NZ 77=OM 78=PA 79=PE 80=PH 81=PK 82=PL\n" - " 83=PR 84=PT 85=PY 86=QA 87=RO 88=RU 89=SA 90=SE 91=SG\n" - " 92=SI 93=SK 95=SV 96=SY 97=TH 98=TN 99=TR 100=TT 101=TW\n" - " 102=UA 103=US 104=UY 105=UZ 106=VE 107=VN 108=YE 109=ZA\n", - "XConfig"); - -DEFINE_uint32( - audio_flag, 0x00010001, - "Audio Mode Analog.\n" - " 0x00000001 = Dolby Pro Logic\n" - " 0x00000002 = Analog Mono\n" - "Audio Mode Digital.\n" - " 0x00000000 = Digital Stereo (choose one of the above by itself)\n" - " 0x00010000 = Dolby Digital\n" - " 0x00030000 = Dolby Digital with WMA PRO\n" - "Special Flags.\n" - " 0x00000003 = Stereo Bypass\n" - " 0x80000000 = Low Latency\n" - " This Config requires you to pair an analog and digitial flag together\n" - " while digital stereo only requires an analog flag. Bonus flags are\n" - " optional. Ex) 0x00010001\n", - "XConfig"); - -DECLARE_bool(widescreen); -DECLARE_bool(use_50Hz_mode); -DECLARE_int32(video_standard); -DECLARE_uint32(internal_display_resolution); - namespace xe { namespace kernel { namespace xboxkrnl { -X_STATUS xeExGetXConfigSetting(X_CONFIG_CATEGORY category, uint16_t setting, - void* buffer, uint16_t buffer_size, - uint16_t* required_size) { - uint16_t setting_size = 0; - alignas(uint32_t) uint8_t value[4]; - - // TODO(benvanik): have real structs here that just get copied from. - // https://free60project.github.io/wiki/XConfig.html - // https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/Generic/ExConfig.h - switch (category) { - case XCONFIG_SECURED_CATEGORY: - switch (setting) { - case XCONFIG_SECURED_AV_REGION: - setting_size = 4; - switch (cvars::video_standard) { - case 1: // NTSCM - xe::store_and_swap(value, X_AV_REGION::NTSCM); - break; - case 2: // NTSCJ - xe::store_and_swap(value, X_AV_REGION::NTSCJ); - break; - case 3: // PAL - xe::store_and_swap(value, cvars::use_50Hz_mode - ? X_AV_REGION::PAL_50 - : X_AV_REGION::PAL); - break; - default: - xe::store_and_swap(value, 0); - break; - } - break; - default: - XELOGW( - "An unimplemented setting 0x{:04X} in XCONFIG SECURED CATEGORY", - static_cast(setting)); - assert_unhandled_case(setting); - return X_STATUS_INVALID_PARAMETER_2; - } - break; - case XCONFIG_USER_CATEGORY: - switch (setting) { - case XCONFIG_USER_TIME_ZONE_BIAS: - case XCONFIG_USER_TIME_ZONE_STD_NAME: - case XCONFIG_USER_TIME_ZONE_DLT_NAME: - case XCONFIG_USER_TIME_ZONE_STD_DATE: - case XCONFIG_USER_TIME_ZONE_DLT_DATE: - case XCONFIG_USER_TIME_ZONE_STD_BIAS: - case XCONFIG_USER_TIME_ZONE_DLT_BIAS: - setting_size = 4; - // TODO(benvanik): get this value. - xe::store_and_swap(value, 0); - break; - case XCONFIG_USER_LANGUAGE: - setting_size = 4; - xe::store_and_swap(value, cvars::user_language); - break; - case XCONFIG_USER_VIDEO_FLAGS: - setting_size = 4; - xe::store_and_swap(value, cvars::widescreen - ? X_VIDEO_FLAGS::Widescreen - : X_VIDEO_FLAGS::RatioNormal); - break; - case XCONFIG_USER_AUDIO_FLAGS: - setting_size = 4; - xe::store_and_swap(value, cvars::audio_flag); - break; - case XCONFIG_USER_RETAIL_FLAGS: - setting_size = 4; - // TODO(benvanik): get this value. - xe::store_and_swap(value, 0x40); - break; - case XCONFIG_USER_COUNTRY: - setting_size = 1; - value[0] = static_cast(cvars::user_country); - break; - case XCONFIG_USER_PC_FLAGS: - setting_size = 1; - // All related flags must be set for PC to function - // Both flags set even when PC are off - value[0] = - X_PC_FLAGS::XBLAllowed | X_PC_FLAGS::XBLMembershipCreationAllowed; - break; - case XCONFIG_USER_AV_COMPONENT_SCREENSZ: - setting_size = 4; - // int16_t* value[2]; - if (XHDTVResolution.find(cvars::internal_display_resolution) != - XHDTVResolution.cend()) { - xe::store_and_swap( - value, XHDTVResolution.at(cvars::internal_display_resolution)); - } else { - XELOGW("Resolution not supported for AV Component"); - xe::store_and_swap(value, 0); - } - break; - case XCONFIG_USER_AV_VGA_SCREENSZ: - setting_size = 4; - // int16_t* value[2]; - if (XVGAResolution.find(cvars::internal_display_resolution) != - XVGAResolution.cend()) { - xe::store_and_swap( - value, XVGAResolution.at(cvars::internal_display_resolution)); - } else { - XELOGW("Resolution not supported for VGA"); - xe::store_and_swap(value, 0); - } - break; - case XCONFIG_USER_PC_GAME: - setting_size = 4; - xe::store_and_swap(value, - X_PC_GAMES_FLAGS::NoGameRestrictions); - break; - case XCONFIG_USER_PC_PASSWORD: - setting_size = 4; - std::memset(value, 0, 4); - break; - case XCONFIG_USER_PC_MOVIE: - setting_size = 4; - xe::store_and_swap(value, - X_PC_MOVIE_FLAGS::NoMovieRestrictions); - break; - case XCONFIG_USER_PC_GAME_RATING: - setting_size = 4; - xe::store_and_swap(value, - X_PC_GAME_RATING_FLAGS::DefaultGame); - break; - case XCONFIG_USER_PC_MOVIE_RATING: - setting_size = 4; - xe::store_and_swap(value, - X_PC_MOVIE_RATING_FLAGS::DefaultMovie); - break; - case XCONFIG_USER_PC_HINT: - // ExSetXConfigSetting and ExGetXConfigSetting request size of 0x40 - setting_size = 0x40; - store_and_swap(value, ""); - break; - case XCONFIG_USER_PC_HINT_ANSWER: - setting_size = 0x20; - store_and_swap(value, ""); - break; - case XCONFIG_USER_ARCADE_FLAGS: - setting_size = 4; - xe::store_and_swap(value, X_ARCADE_FLAGS::AutoDownloadOff); - break; - case XCONFIG_USER_PC_VERSION: - setting_size = 4; - xe::store_and_swap(value, X_PC_VERSION::VersionOne); - break; - case XCONFIG_USER_PC_TV: - setting_size = 4; - xe::store_and_swap(value, X_PC_TV::NoTVRestrictions); - break; - case XCONFIG_USER_PC_TV_RATING: - setting_size = 4; - xe::store_and_swap(value, X_PC_TV_RATING::DefaultTV); - break; - case XCONFIG_USER_PC_EXPLICIT_VIDEO: - setting_size = 4; - xe::store_and_swap( - value, X_PC_EXPLICIT_VIDEO::ExplicitVideoAllowed); - break; - case XCONFIG_USER_PC_EXPLICIT_VIDEO_RATING: - setting_size = 4; - xe::store_and_swap( - value, X_PC_EXPLICIT_VIDEO_RATING::ExplicitAllowed); - break; - case XCONFIG_USER_PC_UNRATED_VIDEO: - setting_size = 4; - xe::store_and_swap(value, - X_PC_EXPLICIT_UNRATED::UnratedALL); - break; - case XCONFIG_USER_PC_UNRATED_VIDEO_RATING: - setting_size = 4; - xe::store_and_swap( - value, X_PC_EXPLICIT_UNRATED_RATING::DefaultExplicitUnrated); - break; - case XCONFIG_USER_VIDEO_OUTPUT_BLACK_LEVELS: - setting_size = 4; - xe::store_and_swap(value, X_BLACK_LEVEL::LevelNormal); - break; - default: - XELOGW("An unimplemented setting 0x{:04X} in XCONFIG USER CATEGORY", - static_cast(setting)); - assert_unhandled_case(setting); - return X_STATUS_INVALID_PARAMETER_2; - } - break; - case XCONFIG_CONSOLE_CATEGORY: - switch (setting) { - case XCONFIG_CONSOLE_SCREEN_SAVER: - setting_size = 2; - xe::store_and_swap(value, X_SCREENSAVER::ScreensaverOff); - break; - case XCONFIG_CONSOLE_AUTO_SHUT_OFF: - setting_size = 2; - xe::store_and_swap(value, X_AUTO_SHUTDOWN::AutoShutdownOff); - break; - case XCONFIG_CONSOLE_CAMERA_SETTINGS: - // Camera Flags are added together and last byte is always 0x1 - setting_size = 4; - xe::store_and_swap(value, X_CAMERA_FLAGS::AutoAll); - break; - case XCONFIG_CONSOLE_KEYBOARD_LAYOUT: - setting_size = 2; - xe::store_and_swap(value, - X_KEYBOARD_LAYOUT::KeyboardDefault); - break; - default: - XELOGW( - "An unimplemented setting 0x{:04X} in XCONFIG CONSOLE CATEGORY", - static_cast(setting)); - assert_unhandled_case(setting); - return X_STATUS_INVALID_PARAMETER_2; - } - break; - default: - XELOGW("An unimplemented category 0x{:04X}", - static_cast(category)); - assert_unhandled_case(category); - return X_STATUS_INVALID_PARAMETER_1; +X_STATUS xeExGetXConfigSetting(X_CONFIG_CATEGORY category, + const uint16_t setting_id, void* buffer, + uint16_t buffer_size, uint16_t* required_size) { + if (!buffer && buffer_size) { + return X_STATUS_INVALID_PARAMETER_3; } if (buffer) { - if (buffer_size < setting_size) { + if (buffer_size < + kernel_state()->xconfig()->GetSettingSize(category, setting_id)) { return X_STATUS_BUFFER_TOO_SMALL; } - std::memcpy(buffer, value, setting_size); - } else { - if (buffer_size) { - return X_STATUS_INVALID_PARAMETER_3; - } + kernel_state()->xconfig()->ReadSetting(category, setting_id, buffer); } if (required_size) { - *required_size = setting_size; + *required_size = static_cast( + kernel_state()->xconfig()->GetSettingSize(category, setting_id)); } return X_STATUS_SUCCESS; @@ -325,7 +65,11 @@ dword_result_t ExSetXConfigSetting_entry(word_t category, word_t setting, Handles settings the only have a single flag/value like XCONFIG_USER_VIDEO_FLAGS to swap */ - XELOGI("ExSetXConfigSetting: category: 0X{:04x}, setting: 0X{:04x}", + kernel_state()->xconfig()->WriteSetting( + static_cast(category.value()), setting.value(), + kernel_memory()->TranslateVirtual(buffer_ptr)); + + XELOGI("ExSetXConfigSetting: category: 0x{:04x}, setting: 0x{:04x}", static_cast(category), static_cast(setting)); return X_STATUS_SUCCESS; } @@ -339,6 +83,14 @@ dword_result_t ExReadModifyWriteXConfigSettingUlong_entry(word_t category, Handles settings with multiple flags like XCONFIG_USER_RETAIL_FLAGS and XCONFIG_CONSOLE_RETAIL_EX_FLAGS */ + uint32_t value = kernel_state()->xconfig()->ReadSetting( + static_cast(category.value()), setting); + + value = xe::byte_swap((value & bit_affected) | flag); + + kernel_state()->xconfig()->WriteSetting( + static_cast(category.value()), setting, &value); + XELOGI( "ExReadModifyWriteXConfigSettingUlong: category: 0x{:04x}, setting: " "{:04x}, changed bits: 0X{:08x}, setting flag 0X{:08x}", diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_xconfig.h b/src/xenia/kernel/xboxkrnl/xboxkrnl_xconfig.h deleted file mode 100644 index d9d79c0c7..000000000 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_xconfig.h +++ /dev/null @@ -1,395 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2025 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#ifndef XENIA_KERNEL_XBOXKRNL_XBOXKRNL_XCONFIG_H_ -#define XENIA_KERNEL_XBOXKRNL_XBOXKRNL_XCONFIG_H_ - -#include -#include - -namespace xe { -namespace kernel { -namespace xboxkrnl { - -enum X_CONFIG_CATEGORY : uint16_t { - XCONFIG_STATIC_CATEGORY = 0x00, - XCONFIG_STATISTIC_CATEGORY = 0x01, - XCONFIG_SECURED_CATEGORY = 0x02, - XCONFIG_USER_CATEGORY = 0x03, - XCONFIG_XNET_MACHINE_ACCOUNT_CATEGORY = 0x04, - XCONFIG_XNET_PARAMETERS_CATEGORY = 0x05, - XCONFIG_MEDIA_CENTER_CATEGORY = 0x06, - XCONFIG_CONSOLE_CATEGORY = 0x07, - XCONFIG_DVD_CATEGORY = 0x08, - XCONFIG_IPTV_CATEGORY = 0x09, - XCONFIG_SYSTEM_CATEGORY = 0x0A, - XCONFIG_DEVKIT_CATEGORY = 0x0B -}; - -enum XCONFIG_STATIC_CATEGORY_ENTRIES : uint8_t { - XCONFIG_STATIC_FIRST_POWER_ON_DATE = 0x01, - XCONFIG_STATIC_SMC_CONFIG = 0x02 -}; - -enum XCONFIG_STATISTIC_CATEGORY_ENTRIES : uint8_t { - XCONFIG_STATISTICS_XUID_MAC_ADDRESS = 0x01, - XCONFIG_STATISTICS_XUID_COUNT = 0x02, - XCONFIG_STATISTICS_ODD_FAILURES = 0x03, - XCONFIG_STATISTICS_HDD_SMART_DATA = 0x04, - XCONFIG_STATISTICS_UEM_ERRORS = 0x05, - XCONFIG_STATISTICS_FPM_ERRORS = 0x06, - XCONFIG_STATISTICS_LAST_REPORT_TIME = 0x07, - XCONFIG_STATISTICS_BUG_CHECK_DATA = 0x08, - XCONFIG_STATISTICS_TEMPERATURE = 0x09, - XCONFIG_STATISTICS_XEKEYS_WRITE_FAILURE = 0x0A -}; - -enum XCONFIG_SECURED_CATEGORY_ENTRIES : uint8_t { - XCONFIG_SECURED_MAC_ADDRESS = 0x01, - XCONFIG_SECURED_AV_REGION = 0x02, - XCONFIG_SECURED_GAME_REGION = 0x03, - XCONFIG_SECURED_DVD_REGION = 0x04, - XCONFIG_SECURED_RESET_KEY = 0x05, - XCONFIG_SECURED_SYSTEM_FLAGS = 0x06, - XCONFIG_SECURED_POWER_MODE = 0x07, - XCONFIG_SECURED_ONLINE_NETWORK_ID = 0x08, - XCONFIG_SECURED_POWER_VCS_CONTROL = 0x09 -}; - -enum XCONFIG_USER_CATEGORY_ENTRIES : uint8_t { - XCONFIG_USER_TIME_ZONE_BIAS = 0x01, - XCONFIG_USER_TIME_ZONE_STD_NAME = 0x02, - XCONFIG_USER_TIME_ZONE_DLT_NAME = 0x03, - XCONFIG_USER_TIME_ZONE_STD_DATE = 0x04, - XCONFIG_USER_TIME_ZONE_DLT_DATE = 0x05, - XCONFIG_USER_TIME_ZONE_STD_BIAS = 0x06, - XCONFIG_USER_TIME_ZONE_DLT_BIAS = 0x07, - XCONFIG_USER_DEFAULT_PROFILE = 0x08, - XCONFIG_USER_LANGUAGE = 0x09, - XCONFIG_USER_VIDEO_FLAGS = 0x0A, - XCONFIG_USER_AUDIO_FLAGS = 0x0B, - XCONFIG_USER_RETAIL_FLAGS = 0x0C, - XCONFIG_USER_DEVKIT_FLAGS = 0x0D, - XCONFIG_USER_COUNTRY = 0x0E, - XCONFIG_USER_PC_FLAGS = 0x0F, - XCONFIG_USER_SMB_CONFIG = 0x10, - XCONFIG_USER_LIVE_PUID = 0x11, - XCONFIG_USER_LIVE_CREDENTIALS = 0x12, - XCONFIG_USER_AV_COMPOSITE_SCREENSZ = 0x13, - XCONFIG_USER_AV_COMPONENT_SCREENSZ = 0x14, - XCONFIG_USER_AV_VGA_SCREENSZ = 0x15, - XCONFIG_USER_PC_GAME = 0x16, - XCONFIG_USER_PC_PASSWORD = 0x17, - XCONFIG_USER_PC_MOVIE = 0x18, - XCONFIG_USER_PC_GAME_RATING = 0x19, - XCONFIG_USER_PC_MOVIE_RATING = 0x1A, - XCONFIG_USER_PC_HINT = 0x1B, - XCONFIG_USER_PC_HINT_ANSWER = 0x1C, - XCONFIG_USER_PC_OVERRIDE = 0x1D, - XCONFIG_USER_MUSIC_PLAYBACK_MODE = 0x1E, - XCONFIG_USER_MUSIC_VOLUME = 0x1F, - XCONFIG_USER_MUSIC_FLAGS = 0x20, - XCONFIG_USER_ARCADE_FLAGS = 0x21, - XCONFIG_USER_PC_VERSION = 0x22, - XCONFIG_USER_PC_TV = 0x23, - XCONFIG_USER_PC_TV_RATING = 0x24, - XCONFIG_USER_PC_EXPLICIT_VIDEO = 0x25, - XCONFIG_USER_PC_EXPLICIT_VIDEO_RATING = 0x26, - XCONFIG_USER_PC_UNRATED_VIDEO = 0x27, - XCONFIG_USER_PC_UNRATED_VIDEO_RATING = 0x28, - XCONFIG_USER_VIDEO_OUTPUT_BLACK_LEVELS = 0x29, - XCONFIG_USER_VIDEO_PLAYER_DISPLAY_MODE = 0x2A, - XCONFIG_USER_ALTERNATE_VIDEO_TIMING_ID = 0x2B, - XCONFIG_USER_VIDEO_DRIVER_OPTIONS = 0x2C, - XCONFIG_USER_MUSIC_UI_FLAGS = 0x2D, - XCONFIG_USER_VIDEO_MEDIA_SOURCE_TYPE = 0x2E, - XCONFIG_USER_MUSIC_MEDIA_SOURCE_TYPE = 0x2F, - XCONFIG_USER_PHOTO_MEDIA_SOURCE_TYPE = 0x30 -}; - -enum XCONFIG_XNET_CATEGORY_ENTRIES : uint8_t { XCONFIG_XNET_DATA = 0x01 }; - -enum XCONFIG_MEDIA_CENTER_CATEGORY_ENTRIES : uint8_t { - XCONFIG_MEDIA_CENTER_MEDIA_PLAYER = 0x01, - XCONFIG_MEDIA_CENTER_XESLED_VERSION = 0x02, - XCONFIG_MEDIA_CENTER_XESLED_TRUST_SECRET = 0x03, - XCONFIG_MEDIA_CENTER_XESLED_TRUST_CODE = 0x04, - XCONFIG_MEDIA_CENTER_XESLED_HOST_ID = 0x05, - XCONFIG_MEDIA_CENTER_XESLED_KEY = 0x06, - XCONFIG_MEDIA_CENTER_XESLED_HOST_MAC_ADDRESS = 0x07, - XCONFIG_MEDIA_CENTER_SERVER_UUID = 0x08, - XCONFIG_MEDIA_CENTER_SERVER_NAME = 0x09, - XCONFIG_MEDIA_CENTER_SERVER_FLAG = 0x0A -}; - -enum XCONFIG_CONSOLE_CATEGORY_ENTRIES : uint8_t { - XCONFIG_CONSOLE_SCREEN_SAVER = 0x01, - XCONFIG_CONSOLE_AUTO_SHUT_OFF = 0x02, - XCONFIG_CONSOLE_WIRELESS_SETTINGS = 0x03, - XCONFIG_CONSOLE_CAMERA_SETTINGS = 0x04, - XCONFIG_CONSOLE_PLAYTIMERDATA = 0x05, - XCONFIG_CONSOLE_MEDIA_DISABLEAUTOLAUNCH = 0x06, - XCONFIG_CONSOLE_KEYBOARD_LAYOUT = 0x07, - XCONFIG_CONSOLE_PC_TITLE_EXEMPTIONS = 0x08, - XCONFIG_CONSOLE_NUI = 0x09, - XCONFIG_CONSOLE_VOICE = 0x0A, - XCONFIG_CONSOLE_RETAIL_EX_FLAGS = 0x0B, - XCONFIG_CONSOLE_DASH_FIRST_USE_TUTORIAL_FLAGS = 0x0C, - XCONFIG_CONSOLE_TV_DIAGONAL_SIZE_IN_CM = 0x0D, - XCONFIG_CONSOLE_NETWORKSTORAGEDEVICE_SERIALNUMBER = 0x0E, - XCONFIG_CONSOLE_DISCOVERABLE = 0x0F, - XCONFIG_CONSOLE_LIVE_TV_PROVIDER = 0x10 -}; - -enum XCONFIG_DVD_CATEGORY_ENTRIES : uint8_t { - XCONFIG_DVD_VOLUME_ID = 0x01, - XCONFIG_DVD_BOOKMARK = 0x02 -}; - -enum XCONFIG_IPTV_CATEGORY_ENTRIES : uint8_t { - XCONFIG_IPTV_SERVICE_PROVIDER_NAME = 0x01, - XCONFIG_IPTV_PROVISIONING_SERVER_URL = 0x02, - XCONFIG_IPTV_SUPPORT_INFO = 0x03, - XCONFIG_IPTV_BOOTSTRAP_SERVER_URL = 0x04 -}; - -enum XCONFIG_SYSTEM_CATEGORY_ENTRIES : uint8_t { - XCONFIG_SYSTEM_ALARM_TIME = 0x01, - XCONFIG_SYSTEM_PREVIOUS_FLASH_VERSION = 0x02 -}; - -enum XCONFIG_DEVKIT_CATEGORY_ENTRIES : uint8_t { - XCONFIG_DEVKIT_USBD_ROOT_HUB_PORT_DISABLE_MASK = 0x01, - XCONFIG_DEVKIT_XAM_FEATURE_ENABLE_DISABLE_MASK = 0x02, - XCONFIG_DEVKIT_KIOSK_ID = 0x03 -}; - -// XCONFIG_SECURED_AV_REGION -enum X_AV_REGION : uint32_t { - NTSCM = 0x00400100, - NTSCJ = 0x00400200, - PAL = 0x00400400, - PAL_50 = 0x00800300, -}; - -// XCONFIG_USER_VIDEO_FLAGS -enum X_VIDEO_FLAGS : uint32_t { - RatioNormal = 0x00000000, - Widescreen = 0x00010000, -}; - -// XCONFIG_USER_AV_COMPONENT_SCREENSZ -const static std::map XHDTVResolution = { - {0, 0x028001E0}, // 480p - {8, 0x050002D0}, // 720p, always widescreen - {16, 0x07800438}, // 1080, interlaced added in 1888, always widescreen -}; - -// XCONFIG_USER_AV_COMPONENT_SCREENSZ -const static std::map XVGAResolution = { - // Existed since 1888 - {0, 0x028001E0}, // 680x480 - {5, 0x035001E0}, // 848x480 - {6, 0x04000300}, // 1024x768 - {8, 0x050002D0}, // 1280x720 - {9, 0x05000300}, // 1280x768 - {12, 0x05500300}, // 1360x768 - // added in later Versions - {11, 0x05000400}, // 1280x1024 - {13, 0x05A00384}, // 1440x900 - {14, 0x0690041A}, // 1680x1050 - {16, 0x07800438}, // 1920x1080 -}; - -// XCONFIG_USER_AUDIO_FLAGS -enum X_AUDIO_FLAGS : uint32_t { - // Audio Mode Analog - DolbyProLogic = 0x00000001, - AnalogMono = 0x00000002, - // Audio Mode Digital - DigitalStereo = 0x00000000, - DolbyDigital = 0x00010000, - DolbyDigitalWithWMAPRO = 0x00030000, - // Special Flags - StereoBypass = 0x00000003, - LowLatency = 0x80000000, -}; - -// XCONFIG_USER_RETAIL_FLAGS -enum X_RETAIL_FLAGS : uint32_t { - // Clock - DSTOff = 0x00000002, - TwentyFourHourClock = 0x00000008, - - // Startup - DashboardStartup = 0x00000080, - IPTVStartup = 0x00000800, - DiscStartup = 0x00002000, - MCXDownloaderStartup = 0x00020000, - // IPTV - IPTVEnabled = 0x00001000, - IPTVDVRENABLED = 0x00080000, - IPTVDisabled = 0x02000000, - // Kinect - KinectInitialized = 0x20000000, - KinectDisabled = 0x80000000, - - // Other - DashboardInitialized = 0x00000040, - BackgroundDownloadOn = 0x00010000, -}; - -// XCONFIG_USER_PC_FLAGS -enum X_PC_FLAGS : uint8_t { - XBLAllowed = 0x01, - XBLMembershipCreationAllowed = 0x02, - XboxOneGameAllowed = 0x04, - PCEnabled = 0x80, -}; - -// XCONFIG_USER_PC_GAMES -enum X_PC_GAMES_FLAGS : uint32_t { - EarlyChildhoodMax = 0x00000000, - EveryoneMax = 0x00000002, - Everyone_10Max = 0x00000004, - TeenMax = 0x00000006, - MatureMax = 0x00000008, - NoGameRestrictions = 0x000000FF, -}; - -// XCONFIG_USER_PC_PASSWORD -enum X_PC_PASSWORD_FLAGS : uint32_t { - XButton = 0x00000001, - YButton = 0x00000002, - LeftButton = 0x00000003, - RightButton = 0x00000004, - UPButton = 0x00000005, - DownButton = 0x00000006, - LTButton = 0x00000009, - RTButton = 0x0000000A, - LBButton = 0x0000000B, - RBButton = 0x0000000C, -}; - -// XCONFIG_USER_PC_MOVIE -enum X_PC_MOVIE_FLAGS : uint32_t { - GeneralAudiences = 0x00000001, - ParentalGuidance = 0x00000003, - ParentalGuidance13 = 0x00000004, - Restricted = 0x00000006, - NoMovieRestrictions = 0x000000FF, -}; - -// XCONFIG_USER_PC_GAME_RATING -enum X_PC_GAME_RATING_FLAGS : uint32_t { - DefaultGame = 0x00000000, -}; - -// XCONFIG_USER_PC_MOVIE_RATING -enum X_PC_MOVIE_RATING_FLAGS : uint32_t { - DefaultMovie = 0x00000000, -}; - -// XCONFIG_USER_ARCADE_FLAGS -enum X_ARCADE_FLAGS : uint32_t { - AutoDownloadOff = 0x00000000, - AutoDownloadNewReleases = 0x00000001, -}; - -// XCONFIG_USER_PC_VERSION -enum X_PC_VERSION : uint32_t { - VersionOne = 0x00000001, -}; - -// XCONFIG_USER_PC_TV -enum X_PC_TV : uint32_t { - TVG = 0X00000006, - TVPG = 0X00000008, - TV14 = 0X0000000a, - TVMatureAudience = 0x0000000C, - NoTVRestrictions = 0X000000FF, -}; - -// XCONFIG_USER_PC_TV_RATING -enum X_PC_TV_RATING : uint32_t { - DefaultTV = 0X00000000, -}; - -// XCONFIG_USER_PC_EXPLICIT_VIDEO -enum X_PC_EXPLICIT_VIDEO : uint32_t { - ExplicitVideoAllowed = 0X000000FF, - ExplicitVideoBanned = 0X00000000, -}; - -// XCONFIG_USER_PC_EXPLICIT_VIDEO_RATING -enum X_PC_EXPLICIT_VIDEO_RATING : uint32_t { - ExplicitAllowed = 0X00000000, -}; - -// XCONFIG_USER_PC_UNRATED_VIDEO -enum X_PC_EXPLICIT_UNRATED : uint32_t { - ExplicitUnratedBanned = 0X00000000, - UnratedALL = 0X000000FF, -}; - -// XCONFIG_USER_PC_UNRATED_VIDEO_RATING -enum X_PC_EXPLICIT_UNRATED_RATING : uint32_t { - DefaultExplicitUnrated = 0X00000000, -}; - -// XCONFIG_USER_VIDEO_OUTPUT_BLACK_LEVELS -enum X_BLACK_LEVEL : uint32_t { - High = 0x00000100, - Intermediate = 0x00000200, - LevelNormal = 0x00000300, -}; - -// XCONFIG_CONSOLE_SCREENSAVER -enum X_SCREENSAVER : uint32_t { - ScreensaverOn = 0x000A, - ScreensaverOff = 0x1000, -}; - -// XCONFIG_CONSOLE_AUTO_SHUTDOWN -enum X_AUTO_SHUTDOWN : uint32_t { - AutoShutdownOff = 0x0000, - AutoShutdownOneHr = 0x003C, - AutoShutdownSixHr = 0x0168, -}; - -// XCONFIG_CONSOLE_CAMERA_SETTINGS -enum X_CAMERA_FLAGS : uint32_t { - // Room - RoomAuto = 0x00000000, - DarkWall = 0x00000001, - LightWall = 0x00000002, - // Lighting - LightingAuto = 0x00000000, - Incandescent = 0x00000004, - Flourescent = 0x00000008, - Daylight = 0x0000000C, - // Flourescent Anti-Flicker - AntiFlickerAuto = 0x00000000, - AntiFlickerOn = 0x00000001, - AntiFlickerOff = 0x00000002, - // Default - AutoAll = 0x00000001, -}; - -// XCONFIG_CONSOLE_KEYBOARD_LAYOUT -enum X_KEYBOARD_LAYOUT : uint16_t { - KeyboardDefault = 0x0000, - EnglishQWERTY = 0x0001, -}; - -} // namespace xboxkrnl -} // namespace kernel -} // namespace xe - -#endif // XENIA_KERNEL_XBOXKRNL_XBOXKRNL_XCONFIG_H_ diff --git a/src/xenia/kernel/xconfig.cc b/src/xenia/kernel/xconfig.cc new file mode 100644 index 000000000..c623a7761 --- /dev/null +++ b/src/xenia/kernel/xconfig.cc @@ -0,0 +1,174 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2026 Xenia Canary. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/kernel/xconfig.h" + +#include "xenia/base/logging.h" + +#include "xenia/base/filesystem.h" + +#include "xenia/base/byte_order.h" +#include "xenia/xbox.h" + +#include + +namespace xe { +namespace kernel { + +XConfig::XConfig(const std::filesystem::path& xconfig_path) + : file_path_(xconfig_path) { + if (!std::filesystem::exists(xconfig_path)) { + SetDefaults(); + + if (xe::filesystem::CreateEmptyFile(xconfig_path)) { + FlushToFile(); + } + } + + FILE* file = xe::filesystem::OpenFile(xconfig_path, "rb"); + + if (!file) { + SetDefaults(); + return; + } + + fread(&xconfig_data_, sizeof(XConfigData), 1, file); + fclose(file); +} + +void XConfig::ReadSetting(const X_CONFIG_CATEGORY category, + const uint16_t setting_id, void* buffer) { + const auto setting = FindField(category, setting_id); + + if (!setting) { + return; + } + + std::lock_guard lock(lock_); + std::memcpy(buffer, CategoryBase(category) + setting->block_offset, + setting->size); +} + +void XConfig::WriteSetting(const X_CONFIG_CATEGORY category, + const uint16_t setting_id, const void* buffer) { + const auto setting = FindField(category, setting_id); + + if (!setting) { + return; + } + + std::lock_guard lock(lock_); + std::memcpy(CategoryBase(category) + setting->block_offset, buffer, + setting->size); + + FlushToFile(); +} + +uint16_t XConfig::GetSettingSize(const X_CONFIG_CATEGORY category, + const uint16_t setting_id) { + const auto setting = FindField(category, setting_id); + + if (!setting) { + return 0; + } + + return setting->size; +} + +void XConfig::WriteXConfig(const XConfigData* data) { + xconfig_data_ = *data; + FlushToFile(); +} + +void XConfig::SetDefaults() { + xconfig_data_ = {}; + + xconfig_data_.secured.av_region = X_AV_REGION::NTSCM; + xconfig_data_.user.language = static_cast(XLanguage::kEnglish); + xconfig_data_.user.country = + static_cast(XOnlineCountry::kUnitedStates); + xconfig_data_.user.audio_flags = DolbyDigital | DolbyProLogic; + xconfig_data_.user.av_pack_hdmi_sz = XHDTVResolution.at(1).to_host(); + xconfig_data_.user.av_pack_component_sz = XHDTVResolution.at(1).to_host(); + xconfig_data_.user.av_pack_vga_sz = XVGAResolution.at(3).to_host(); + xconfig_data_.user.retail_flags = DashboardInitialized; + xconfig_data_.user.video_flags = RatioNormal; + xconfig_data_.user.parental_control_flags = + XBLAllowed | XBLMembershipCreationAllowed; + xconfig_data_.user.parental_control_game = NoGameRestrictions; + xconfig_data_.user.music_volume = 0.7f; + + const auto& tz = kTimezones[0x19]; + + xconfig_data_.user.time_zone_bias = tz.timezone_bias; + memcpy(xconfig_data_.user.tz_std_name.data(), tz.tz_std_name.data(), 4); + memcpy(xconfig_data_.user.tz_dlt_name.data(), tz.tz_dlt_name.data(), 4); + memcpy(xconfig_data_.user.tz_std_date.data(), tz.tz_std_date.data(), 4); + memcpy(xconfig_data_.user.tz_dlt_date.data(), tz.tz_dlt_date.data(), 4); + xconfig_data_.user.tz_std_bias = tz.tz_std_bias; + xconfig_data_.user.tz_dlt_bias = tz.tz_dlt_bias; +} + +void XConfig::FlushToFile() { + if (!std::filesystem::exists(file_path_)) { + return; + } + + FILE* file = xe::filesystem::OpenFile(file_path_, "wb"); + if (!file) { + return; + } + + fwrite(&xconfig_data_, 1, sizeof(XConfigData), file); + fclose(file); +} + +const XConfig::FieldDescriptor* XConfig::FindField(X_CONFIG_CATEGORY category, + uint16_t setting) { + auto it = std::ranges::find_if(kFields, [&](const FieldDescriptor& f) { + return f.category == category && f.setting == setting; + }); + + return it == std::ranges::end(kFields) ? nullptr : &*it; +} + +uint8_t* XConfig::CategoryBase(X_CONFIG_CATEGORY category) { + switch (category) { + case X_CONFIG_CATEGORY::XCONFIG_STATIC_CATEGORY: + return reinterpret_cast(&xconfig_data_.static_settings); + case X_CONFIG_CATEGORY::XCONFIG_STATISTIC_CATEGORY: + return reinterpret_cast(&xconfig_data_.statistic); + case X_CONFIG_CATEGORY::XCONFIG_SECURED_CATEGORY: + return reinterpret_cast(&xconfig_data_.secured); + case X_CONFIG_CATEGORY::XCONFIG_USER_CATEGORY: + return reinterpret_cast(&xconfig_data_.user); + case X_CONFIG_CATEGORY::XCONFIG_XNET_MACHINE_ACCOUNT_CATEGORY: + return reinterpret_cast(&xconfig_data_.xnet_machine_account); + case X_CONFIG_CATEGORY::XCONFIG_XNET_PARAMETERS_CATEGORY: + return reinterpret_cast(&xconfig_data_.xnet_parameters); + case X_CONFIG_CATEGORY::XCONFIG_MEDIA_CENTER_CATEGORY: + return reinterpret_cast(&xconfig_data_.media_center); + case X_CONFIG_CATEGORY::XCONFIG_CONSOLE_CATEGORY: + return reinterpret_cast(&xconfig_data_.console); + case X_CONFIG_CATEGORY::XCONFIG_DVD_CATEGORY: + return reinterpret_cast(&xconfig_data_.dvd); + case X_CONFIG_CATEGORY::XCONFIG_IPTV_CATEGORY: + return reinterpret_cast(&xconfig_data_.iptv); + case X_CONFIG_CATEGORY::XCONFIG_SYSTEM_CATEGORY: + return reinterpret_cast(&xconfig_data_.system); + } + return nullptr; +} + +const uint8_t* XConfig::CategoryBase(X_CONFIG_CATEGORY category) const { + return const_cast(this)->CategoryBase(category); +} + +} // namespace kernel +} // namespace xe diff --git a/src/xenia/kernel/xconfig.h b/src/xenia/kernel/xconfig.h new file mode 100644 index 000000000..4df66451f --- /dev/null +++ b/src/xenia/kernel/xconfig.h @@ -0,0 +1,1055 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2026 Xenia Canary. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_KERNEL_XCONFIG_H_ +#define XENIA_KERNEL_XCONFIG_H_ + +#include +#include +#include +#include + +#include "xenia/base/byte_order.h" +#include "xenia/base/mutex.h" + +#include "third_party/fmt/include/fmt/format.h" + +namespace xe { +namespace kernel { + +enum X_CONFIG_CATEGORY : uint16_t { + XCONFIG_STATIC_CATEGORY = 0x00, + XCONFIG_STATISTIC_CATEGORY = 0x01, + XCONFIG_SECURED_CATEGORY = 0x02, + XCONFIG_USER_CATEGORY = 0x03, + XCONFIG_XNET_MACHINE_ACCOUNT_CATEGORY = 0x04, + XCONFIG_XNET_PARAMETERS_CATEGORY = 0x05, + XCONFIG_MEDIA_CENTER_CATEGORY = 0x06, + XCONFIG_CONSOLE_CATEGORY = 0x07, + XCONFIG_DVD_CATEGORY = 0x08, + XCONFIG_IPTV_CATEGORY = 0x09, + XCONFIG_SYSTEM_CATEGORY = 0x0A, + XCONFIG_DEVKIT_CATEGORY = 0x0B +}; + +enum XCONFIG_STATIC_CATEGORY_ENTRIES : uint8_t { + XCONFIG_STATIC_FIRST_POWER_ON_DATE = 0x01, + XCONFIG_STATIC_SMC_CONFIG = 0x02 +}; + +enum XCONFIG_STATISTIC_CATEGORY_ENTRIES : uint8_t { + XCONFIG_STATISTICS_XUID_MAC_ADDRESS = 0x01, + XCONFIG_STATISTICS_XUID_COUNT = 0x02, + XCONFIG_STATISTICS_ODD_FAILURES = 0x03, + XCONFIG_STATISTICS_HDD_SMART_DATA = 0x04, + XCONFIG_STATISTICS_UEM_ERRORS = 0x05, + XCONFIG_STATISTICS_FPM_ERRORS = 0x06, + XCONFIG_STATISTICS_LAST_REPORT_TIME = 0x07, + XCONFIG_STATISTICS_BUG_CHECK_DATA = 0x08, + XCONFIG_STATISTICS_TEMPERATURE = 0x09, + XCONFIG_STATISTICS_XEKEYS_WRITE_FAILURE = 0x0A +}; + +enum XCONFIG_SECURED_CATEGORY_ENTRIES : uint8_t { + XCONFIG_SECURED_MAC_ADDRESS = 0x01, + XCONFIG_SECURED_AV_REGION = 0x02, + XCONFIG_SECURED_GAME_REGION = 0x03, + XCONFIG_SECURED_DVD_REGION = 0x04, + XCONFIG_SECURED_RESET_KEY = 0x05, + XCONFIG_SECURED_SYSTEM_FLAGS = 0x06, + XCONFIG_SECURED_POWER_MODE = 0x07, + XCONFIG_SECURED_ONLINE_NETWORK_ID = 0x08, + XCONFIG_SECURED_POWER_VCS_CONTROL = 0x09, + XCONFIG_SECURED_UNKNOWN = 0x10 +}; + +enum XCONFIG_USER_CATEGORY_ENTRIES : uint8_t { + XCONFIG_USER_TIME_ZONE_BIAS = 0x01, + XCONFIG_USER_TIME_ZONE_STD_NAME = 0x02, + XCONFIG_USER_TIME_ZONE_DLT_NAME = 0x03, + XCONFIG_USER_TIME_ZONE_STD_DATE = 0x04, + XCONFIG_USER_TIME_ZONE_DLT_DATE = 0x05, + XCONFIG_USER_TIME_ZONE_STD_BIAS = 0x06, + XCONFIG_USER_TIME_ZONE_DLT_BIAS = 0x07, + XCONFIG_USER_DEFAULT_PROFILE = 0x08, + XCONFIG_USER_LANGUAGE = 0x09, + XCONFIG_USER_VIDEO_FLAGS = 0x0A, + XCONFIG_USER_AUDIO_FLAGS = 0x0B, + XCONFIG_USER_RETAIL_FLAGS = 0x0C, + XCONFIG_USER_DEVKIT_FLAGS = 0x0D, + XCONFIG_USER_COUNTRY = 0x0E, + XCONFIG_USER_PC_FLAGS = 0x0F, + XCONFIG_USER_SMB_CONFIG = 0x10, + XCONFIG_USER_LIVE_PUID = 0x11, + XCONFIG_USER_LIVE_CREDENTIALS = 0x12, + XCONFIG_USER_AV_COMPOSITE_SCREENSZ = 0x13, // Used for any other case + XCONFIG_USER_AV_COMPONENT_SCREENSZ = + 0x14, // Used only if avpack is set to component (3) + XCONFIG_USER_AV_VGA_SCREENSZ = 0x15, // Used only if avpack is set to VGA (6) + XCONFIG_USER_PC_GAME = 0x16, + XCONFIG_USER_PC_PASSWORD = 0x17, + XCONFIG_USER_PC_MOVIE = 0x18, + XCONFIG_USER_PC_GAME_RATING = 0x19, + XCONFIG_USER_PC_MOVIE_RATING = 0x1A, + XCONFIG_USER_PC_HINT = 0x1B, + XCONFIG_USER_PC_HINT_ANSWER = 0x1C, + XCONFIG_USER_PC_OVERRIDE = 0x1D, + XCONFIG_USER_MUSIC_PLAYBACK_MODE = 0x1E, + XCONFIG_USER_MUSIC_VOLUME = 0x1F, + XCONFIG_USER_MUSIC_FLAGS = 0x20, + XCONFIG_USER_ARCADE_FLAGS = 0x21, + XCONFIG_USER_PC_VERSION = 0x22, + XCONFIG_USER_PC_TV = 0x23, + XCONFIG_USER_PC_TV_RATING = 0x24, + XCONFIG_USER_PC_EXPLICIT_VIDEO = 0x25, + XCONFIG_USER_PC_EXPLICIT_VIDEO_RATING = 0x26, + XCONFIG_USER_PC_UNRATED_VIDEO = 0x27, + XCONFIG_USER_PC_UNRATED_VIDEO_RATING = 0x28, + XCONFIG_USER_VIDEO_OUTPUT_BLACK_LEVELS = 0x29, + XCONFIG_USER_VIDEO_PLAYER_DISPLAY_MODE = 0x2A, + XCONFIG_USER_ALTERNATE_VIDEO_TIMING_ID = 0x2B, + XCONFIG_USER_VIDEO_DRIVER_OPTIONS = 0x2C, + XCONFIG_USER_MUSIC_UI_FLAGS = 0x2D, + XCONFIG_USER_VIDEO_MEDIA_SOURCE_TYPE = 0x2E, + XCONFIG_USER_MUSIC_MEDIA_SOURCE_TYPE = 0x2F, + XCONFIG_USER_PHOTO_MEDIA_SOURCE_TYPE = 0x30 +}; + +enum XCONFIG_XNET_MACHINE_ACCOUNT_CATEGORY_ENTRIES : uint8_t { + XCONFIG_XNET_DATA = 0x01 +}; + +enum XCONFIG_MEDIA_CENTER_CATEGORY_ENTRIES : uint8_t { + XCONFIG_MEDIA_CENTER_MEDIA_PLAYER = 0x01, + XCONFIG_MEDIA_CENTER_XESLED_VERSION = 0x02, + XCONFIG_MEDIA_CENTER_XESLED_TRUST_SECRET = 0x03, + XCONFIG_MEDIA_CENTER_XESLED_TRUST_CODE = 0x04, + XCONFIG_MEDIA_CENTER_XESLED_HOST_ID = 0x05, + XCONFIG_MEDIA_CENTER_XESLED_KEY = 0x06, + XCONFIG_MEDIA_CENTER_XESLED_HOST_MAC_ADDRESS = 0x07, + XCONFIG_MEDIA_CENTER_SERVER_UUID = 0x08, + XCONFIG_MEDIA_CENTER_SERVER_NAME = 0x09, + XCONFIG_MEDIA_CENTER_SERVER_FLAG = 0x0A +}; + +enum XCONFIG_CONSOLE_CATEGORY_ENTRIES : uint8_t { + XCONFIG_CONSOLE_SCREEN_SAVER = 0x01, + XCONFIG_CONSOLE_AUTO_SHUT_OFF = 0x02, + XCONFIG_CONSOLE_WIRELESS_SETTINGS = 0x03, + XCONFIG_CONSOLE_CAMERA_SETTINGS = 0x04, + XCONFIG_CONSOLE_PLAYTIMERDATA = 0x05, + XCONFIG_CONSOLE_MEDIA_DISABLEAUTOLAUNCH = 0x06, + XCONFIG_CONSOLE_KEYBOARD_LAYOUT = 0x07, + XCONFIG_CONSOLE_PC_TITLE_EXEMPTIONS = 0x08, + XCONFIG_CONSOLE_NUI = 0x09, + XCONFIG_CONSOLE_VOICE = 0x0A, + XCONFIG_CONSOLE_RETAIL_EX_FLAGS = 0x0B, + XCONFIG_CONSOLE_DASH_FIRST_USE_TUTORIAL_FLAGS = 0x0C, + XCONFIG_CONSOLE_TV_DIAGONAL_SIZE_IN_CM = 0x0D, + XCONFIG_CONSOLE_NETWORKSTORAGEDEVICE_SERIALNUMBER = 0x0E, + XCONFIG_CONSOLE_DISCOVERABLE = 0x0F, + XCONFIG_CONSOLE_LIVE_TV_PROVIDER = 0x10 +}; + +enum XCONFIG_DVD_CATEGORY_ENTRIES : uint8_t { + XCONFIG_DVD_VOLUME_ID = 0x01, + XCONFIG_DVD_BOOKMARK = 0x02 +}; + +enum XCONFIG_IPTV_CATEGORY_ENTRIES : uint8_t { + XCONFIG_IPTV_SERVICE_PROVIDER_NAME = 0x01, + XCONFIG_IPTV_PROVISIONING_SERVER_URL = 0x02, + XCONFIG_IPTV_SUPPORT_INFO = 0x03, + XCONFIG_IPTV_BOOTSTRAP_SERVER_URL = 0x04 +}; + +enum XCONFIG_SYSTEM_CATEGORY_ENTRIES : uint8_t { + XCONFIG_SYSTEM_ALARM_TIME = 0x01, + XCONFIG_SYSTEM_PREVIOUS_FLASH_VERSION = 0x02 +}; + +enum XCONFIG_DEVKIT_CATEGORY_ENTRIES : uint8_t { + XCONFIG_DEVKIT_USBD_ROOT_HUB_PORT_DISABLE_MASK = 0x01, + XCONFIG_DEVKIT_XAM_FEATURE_ENABLE_DISABLE_MASK = 0x02, + XCONFIG_DEVKIT_KIOSK_ID = 0x03 +}; + +enum class X_AV_VIDEO_STANDARD : uint32_t { + NTSCM = 1, + NTSCJ = 2, + PAL = 3, + PAL_50 = 4, +}; + +// XCONFIG_SECURED_AV_REGION +enum X_AV_REGION : uint32_t { + NTSCM = 0x00400100, + NTSCJ = 0x00400200, + PAL = 0x00400400, + PAL_50 = 0x00800300, +}; + +// XCONFIG_USER_VIDEO_FLAGS +enum X_VIDEO_FLAGS : uint32_t { + RatioNormal = 0x00000000, + Widescreen = 0x00010000, +}; + +constexpr std::array kNoDate = {0x00, 0x00, 0x00, 0x00}; +constexpr std::array kUS_StdDate = {0x0A, 0x05, 0x00, 0x02}; +constexpr std::array kUS_DltDate = {0x04, 0x01, 0x00, 0x02}; +constexpr std::array kEU_StdDate = {0x0A, 0x05, 0x00, 0x03}; +constexpr std::array kEU_DltDate = {0x03, 0x05, 0x00, 0x02}; + +struct TimeZone { + xe::be timezone_bias; + std::array tz_std_name; + std::array tz_dlt_name; + std::array tz_std_date; + std::array tz_dlt_date; + xe::be tz_std_bias; + xe::be tz_dlt_bias; + + std::string name; + + TimeZone() = default; + + TimeZone(xe::be timezone_bias, const std::array std_name, + const std::array dlt_name, + const std::array std_date, + const std::array dlt_date, xe::be tz_std_bias, + xe::be tz_dlt_bias, std::string name = "") + : name(name), + timezone_bias(timezone_bias), + tz_std_bias(tz_std_bias), + tz_dlt_bias(tz_dlt_bias) { + std::memcpy(tz_std_name.data(), std_name.data(), 4); + std::memcpy(tz_dlt_name.data(), dlt_name.data(), 4); + std::memcpy(tz_std_date.data(), std_date.data(), 4); + std::memcpy(tz_dlt_date.data(), dlt_date.data(), 4); + } + + // Ctors used only to make entries in table below. + static TimeZone NoDST(int32_t bias, std::string_view std_name, + std::string name) { + std::array std_arr{}; + std::memcpy(std_arr.data(), std_name.data(), + std::min(std_name.size(), std_arr.size())); + return {bias, std_arr, {'\0', '\0', '\0', '\0'}, kNoDate, kNoDate, 0, + 0, name}; + } + + static TimeZone WithDST(int32_t bias, std::string_view std_name, + std::string_view dlt_name, + std::array std_date, + std::array dlt_date, int32_t dlt_bias, + std::string name) { + std::array std_arr{}; + std::array dlt_arr{}; + std::memcpy(std_arr.data(), std_name.data(), + std::min(std_name.size(), std_arr.size())); + std::memcpy(dlt_arr.data(), dlt_name.data(), + std::min(dlt_name.size(), dlt_arr.size())); + return {bias, std_arr, dlt_arr, std_date, dlt_date, 0, dlt_bias, name}; + } + + bool operator==(const TimeZone& other) const { + return timezone_bias == other.timezone_bias && + std::memcmp(tz_std_name.data(), other.tz_std_name.data(), 4) == 0 && + std::memcmp(tz_dlt_name.data(), other.tz_dlt_name.data(), 4) == 0 && + std::memcmp(tz_std_date.data(), other.tz_std_date.data(), 4) == 0 && + std::memcmp(tz_dlt_date.data(), other.tz_dlt_date.data(), 4) == 0 && + tz_std_bias == other.tz_std_bias && tz_dlt_bias == other.tz_dlt_bias; + } +}; + +inline const std::array kTimezones = { + TimeZone::NoDST(720, "IDLW", "GMT-12 Tokelau"), + TimeZone::NoDST(660, "NT", "GMT-11 Samoa"), + TimeZone::NoDST(600, "HST", "GMT-10 Hawaii"), + TimeZone::WithDST(540, "YST", "YDT", kUS_StdDate, kUS_DltDate, -60, + "GMT-09 Alaska"), + TimeZone::WithDST(480, "PST", "PDT", kUS_StdDate, kUS_DltDate, -60, + "GMT-08 Pacific (U.S. & Canada)"), + TimeZone::NoDST(420, "AMST", "GMT-07 Arizona"), + TimeZone::WithDST(420, "MST", "MST", kUS_StdDate, kUS_DltDate, -60, + "GMT-07 Mountain (U.S. & Canada)"), + TimeZone::NoDST(360, "CAST", "GMT-06 Central America"), + TimeZone::WithDST(360, "CST", "CDT", kUS_StdDate, kUS_DltDate, -60, + "GMT-06 Central (U.S. & Canada)"), + TimeZone::WithDST(360, "MST", "MDT", kUS_StdDate, kUS_DltDate, -60, + "GMT-06 Mexico City"), + TimeZone::NoDST(360, "CCST", "GMT-06 Saskatchewan"), + TimeZone::WithDST(300, "EST", "EDT", kUS_StdDate, kUS_DltDate, -60, + "GMT-05 Eastern (U.S. & Canada)"), + TimeZone::NoDST(300, "EST", "GMT-05 Indiana"), + TimeZone::NoDST(300, "SPST", "GMT-05 Bogota"), + TimeZone::WithDST(240, "AST", "ADT", kUS_StdDate, kUS_DltDate, -60, + "GMT-04 Atlantic (U.S. & Canada)"), + TimeZone::NoDST(240, "SWST", "GMT-04 Caracas"), + TimeZone::WithDST(240, "PSST", "PSDT", {0x03, 0x02, 0x06, 0x00}, + {0x0A, 0x02, 0x06, 0x00}, -60, "GMT-04 Santiago"), + TimeZone::WithDST(210, "NST", "NDT", kUS_StdDate, kUS_DltDate, -60, + "GMT-03:30 Newfoundland"), + TimeZone::WithDST(180, "ESST", "ESDT", {0x02, 0x02, 0x00, 0x02}, + {0x0A, 0x03, 0x00, 0x02}, -60, "GMT-03 Brasilia"), + TimeZone::NoDST(180, "SEST", "GMT-03 Buenos Aires"), + TimeZone::WithDST(180, "GST", "GDT", kUS_StdDate, kUS_DltDate, -60, + "GMT-03 Greenland"), + TimeZone::WithDST(120, "MAST", "MADT", {0x09, 0x05, 0x00, 0x02}, + {0x03, 0x05, 0x00, 0x02}, -60, "GMT-02 Mid-Atlantic"), + TimeZone::WithDST(60, "AST", "ADT", {0x0A, 0x05, 0x00, 0x03}, + {0x03, 0x05, 0x00, 0x02}, -60, "GMT-01 Azores"), + TimeZone::NoDST(60, "WAT", "GMT-01 Cape Verde"), + TimeZone::NoDST(0, "GST", "GMT+00 Casablanca"), + TimeZone::WithDST(0, "GMT", "BST", {0x0A, 0x05, 0x00, 0x02}, + {0x03, 0x05, 0x00, 0x01}, -60, "GMT+00 London"), + TimeZone::WithDST(-60, "WEST", "WEDT", kEU_StdDate, kEU_DltDate, -60, + "GMT+01 Berlin"), + TimeZone::WithDST(-60, "CEST", "CEDT", kEU_StdDate, kEU_DltDate, -60, + "GMT+01 Belgrade"), + TimeZone::WithDST(-60, "RST", "RDT", kEU_StdDate, kEU_DltDate, -60, + "GMT+01 Paris, Madrid"), + TimeZone::WithDST(-60, "SCST", "SCDT", kEU_StdDate, kEU_DltDate, -60, + "GMT+01 Sarajevo"), + TimeZone::NoDST(-60, "WAST", "GMT+01 W. Central Africa"), + TimeZone::WithDST(-120, "GTST", "GTDT", kEU_StdDate, kEU_DltDate, -60, + "GMT+02 Athens"), + TimeZone::WithDST(-120, "EEST", "EEDT", {0x09, 0x05, 0x00, 0x01}, + {0x03, 0x05, 0x00, 0x00}, -60, "GMT+02 Bucharest"), + TimeZone::WithDST(-120, "EST", "EDT", {0x09, 0x05, 0x03, 0x02}, + {0x05, 0x01, 0x05, 0x02}, -60, "GMT+02 Cairo"), + TimeZone::NoDST(-120, "SAST", "GMT+02 Pretoria"), + TimeZone::WithDST(-120, "FLST", "FLDT", {0x0A, 0x05, 0x00, 0x04}, + {0x03, 0x05, 0x00, 0x03}, -60, "GMT+02 Helsinki"), + TimeZone::NoDST(-120, "JST", "GMT+02 Jerusalem"), + TimeZone::WithDST(-180, "AST", "ADT", {0x0A, 0x01, 0x00, 0x04}, + {0x04, 0x01, 0x00, 0x03}, -60, "GMT+03 Baghdad"), + TimeZone::NoDST(-180, "AST", "GMT+03 Kuwait"), + TimeZone::WithDST(-180, "RST", "RDT", kEU_StdDate, kEU_DltDate, -60, + "GMT+03 Moscow"), + TimeZone::NoDST(-180, "EAST", "GMT+03 Nairobi"), + TimeZone::WithDST(-210, "IST", "IDT", {0x09, 0x04, 0x02, 0x02}, + {0x03, 0x01, 0x00, 0x02}, -60, "GMT+03:30 Tehran"), + TimeZone::NoDST(-240, "AST", "GMT+04 Abu Dhabi"), + TimeZone::WithDST(-240, "CST", "CDT", kEU_StdDate, kEU_DltDate, -60, + "GMT+04 Baku"), + TimeZone::NoDST(-270, "AST", "GMT+04:30 Kabul"), + TimeZone::WithDST(-300, "EST", "EDT", kEU_StdDate, kEU_DltDate, -60, + "GMT+05 Ekaterinburg"), + TimeZone::NoDST(-300, "WAST", "GMT+05 Islamabad"), + TimeZone::NoDST(-330, "IST", "GMT+05:30 New Delhi"), + TimeZone::NoDST(-345, "NST", "GMT+05:45 Kathmandu"), + TimeZone::WithDST(-360, "NCST", "NCDT", kEU_StdDate, kEU_DltDate, -60, + "GMT+06 Almaty"), + TimeZone::NoDST(-360, "CAST", "GMT+06 Dhaka"), + TimeZone::NoDST(-360, "SRST", "GMT+06 Sri Lanka"), + TimeZone::NoDST(-390, "MST", "GMT+06:30 Yangon"), + TimeZone::NoDST(-420, "SAST", "GMT+07 Bangkok"), + TimeZone::WithDST(-420, "NAST", "NADT", kEU_StdDate, kEU_DltDate, -60, + "GMT+07 Krasnoyarsk"), + TimeZone::NoDST(-480, "CST", "GMT+08 Beijing"), + TimeZone::WithDST(-480, "NEST", "NEDT", kEU_StdDate, kEU_DltDate, -60, + "GMT+08 Irkutsk"), + TimeZone::NoDST(-480, "MPST", "GMT+08 Singapore"), + TimeZone::NoDST(-480, "AWST", "GMT+08 Perth"), + TimeZone::NoDST(-480, "TST", "GMT+08 Taipei"), + TimeZone::NoDST(-540, "TST", "GMT+09 Tokyo"), + TimeZone::NoDST(-540, "KST", "GMT+09 Seoul"), + TimeZone::WithDST(-540, "YST", "YDT", kEU_StdDate, kEU_DltDate, -60, + "GMT+09 Yakutsk"), + TimeZone::WithDST(-570, "ACST", "ACDT", {0x03, 0x05, 0x00, 0x02}, + {0x0A, 0x05, 0x00, 0x02}, -60, "GMT+09:30 Adelaide"), + TimeZone::NoDST(-570, "ACST", "GMT+09:30 Darwin"), + TimeZone::NoDST(-600, "AEST", "GMT+10 Brisbane"), + TimeZone::WithDST(-600, "AEST", "AEDT", {0x03, 0x05, 0x00, 0x02}, + {0x0A, 0x05, 0x00, 0x02}, -60, "GMT+10 Sydney"), + TimeZone::NoDST(-600, "WPST", "GMT+10 Guam"), + TimeZone::WithDST(-600, "TST", "TDT", {0x03, 0x05, 0x00, 0x02}, + {0x0A, 0x01, 0x00, 0x02}, -60, "GMT+10 Hobart"), + TimeZone::WithDST(-600, "VST", "VDT", kEU_StdDate, kEU_DltDate, -60, + "GMT+10 Vladivostok"), + TimeZone::NoDST(-660, "CPST", "GMT+11 Solomon Islands"), + TimeZone::WithDST(-720, "NZST", "NZDT", {0x03, 0x03, 0x00, 0x02}, + {0x0A, 0x01, 0x00, 0x02}, -60, "GMT+12 Auckland"), + TimeZone::NoDST(-720, "FST", "GMT+12 Fiji Islands"), + TimeZone::NoDST(-780, "TST", "GMT+13 Nuku'alofa"), + TimeZone::NoDST(-840, "KST", "GMT+14 Kiribati"), +}; + +struct Resolution { + std::string name_; + xe::be width_; + xe::be height_; + + Resolution(uint16_t width, uint16_t height) : width_(width), height_(height) { + name_ = fmt::format("{}x{}", width, height); + }; + + Resolution(uint32_t value) : width_(value >> 16), height_(value) { + name_ = fmt::format("{}x{}", width_.get(), height_.get()); + }; + + uint32_t to_host() const { + return (static_cast(width_.get()) << 16) | + static_cast(height_.get()); + } + + std::pair aspect_ratio() const { + const uint16_t divisor = std::gcd(width_.get(), height_.get()); + return {width_.get() / divisor, height_.get() / divisor}; + } + + bool is_widescreen() const { + return aspect_ratio() != std::make_pair(4, 3); + } +}; + +// Not sure why HDMI is assigned to composite, but ok. +// XCONFIG_USER_AV_COMPOSITE_SCREENSZ +inline const std::array XHDTVResolution = { + Resolution(640, 480), // 0x028001E0 + Resolution(1280, 720), // 0x050002D0, always widescreen + Resolution(1920, + 1080) // 0x07800438, interlaced added in 1888, always widescreen +}; + +// XCONFIG_USER_AV_COMPONENT_SCREENSZ +inline const std::array XVGAResolution = { + Resolution(640, 480), Resolution(640, 576), Resolution(720, 480), + Resolution(720, 576), Resolution(800, 600), Resolution(848, 480), + Resolution(1024, 768), Resolution(1152, 864), Resolution(1280, 720), + Resolution(1280, 768), Resolution(1280, 960), Resolution(1280, 1024), + Resolution(1360, 768), Resolution(1440, 768), Resolution(1440, 900), + Resolution(1600, 768), Resolution(1680, 720), Resolution(1680, 1050), + Resolution(1920, 540), Resolution(1920, 1080)}; + +// XCONFIG_USER_AUDIO_FLAGS +enum X_AUDIO_FLAGS : uint32_t { + // Audio Mode Analog + DolbyProLogic = 0x00000001, + AnalogMono = 0x00000002, + // Audio Mode Digital + DigitalStereo = 0x00000000, + DolbyDigital = 0x00010000, + DolbyDigitalWithWMAPRO = 0x00030000, + // Special Flags + StereoBypass = 0x00000003, + LowLatency = 0x80000000, +}; + +// XCONFIG_USER_RETAIL_FLAGS +enum X_RETAIL_FLAGS : uint32_t { + // Clock + DSTOff = 0x00000002, + TwentyFourHourClock = 0x00000008, + + // Startup + DashboardStartup = 0x00000080, + IPTVStartup = 0x00000800, + DiscStartup = 0x00002000, + MCXDownloaderStartup = 0x00020000, + // IPTV + IPTVEnabled = 0x00001000, + IPTVDVREnabled = 0x00080000, + IPTVDisabled = 0x02000000, + // Kinect + KinectInitialized = 0x20000000, + KinectDisabled = 0x80000000, + + // Other + DashboardInitialized = 0x00000040, + BackgroundDownloadOn = 0x00010000, +}; + +// XCONFIG_USER_PC_FLAGS +enum X_PC_FLAGS : uint8_t { + XBLAllowed = 0x01, + XBLMembershipCreationAllowed = 0x02, + XboxOneGameAllowed = 0x04, + PCEnabled = 0x80, +}; + +// XCONFIG_USER_PC_GAMES +enum X_PC_GAMES_FLAGS : uint32_t { + EarlyChildhoodMax = 0x00000000, + EveryoneMax = 0x00000002, + Everyone_10Max = 0x00000004, + TeenMax = 0x00000006, + MatureMax = 0x00000008, + NoGameRestrictions = 0x000000FF, +}; + +// XCONFIG_USER_PC_PASSWORD +enum X_PC_PASSWORD_FLAGS : uint32_t { + XButton = 0x00000001, + YButton = 0x00000002, + LeftButton = 0x00000003, + RightButton = 0x00000004, + UPButton = 0x00000005, + DownButton = 0x00000006, + LTButton = 0x00000009, + RTButton = 0x0000000A, + LBButton = 0x0000000B, + RBButton = 0x0000000C, +}; + +// XCONFIG_USER_PC_MOVIE +enum X_PC_MOVIE_FLAGS : uint32_t { + GeneralAudiences = 0x00000001, + ParentalGuidance = 0x00000003, + ParentalGuidance13 = 0x00000004, + Restricted = 0x00000006, + NoMovieRestrictions = 0x000000FF, +}; + +// XCONFIG_USER_PC_GAME_RATING +enum X_PC_GAME_RATING_FLAGS : uint32_t { + DefaultGame = 0x00000000, +}; + +// XCONFIG_USER_PC_MOVIE_RATING +enum X_PC_MOVIE_RATING_FLAGS : uint32_t { + DefaultMovie = 0x00000000, +}; + +// XCONFIG_USER_ARCADE_FLAGS +enum X_ARCADE_FLAGS : uint32_t { + AutoDownloadOff = 0x00000000, + AutoDownloadNewReleases = 0x00000001, +}; + +// XCONFIG_USER_PC_VERSION +enum X_PC_VERSION : uint32_t { + VersionOne = 0x00000001, +}; + +// XCONFIG_USER_PC_TV +enum X_PC_TV : uint32_t { + TVG = 0X00000006, + TVPG = 0X00000008, + TV14 = 0X0000000a, + TVMatureAudience = 0x0000000C, + NoTVRestrictions = 0X000000FF, +}; + +// XCONFIG_USER_PC_TV_RATING +enum X_PC_TV_RATING : uint32_t { + DefaultTV = 0X00000000, +}; + +// XCONFIG_USER_PC_EXPLICIT_VIDEO +enum X_PC_EXPLICIT_VIDEO : uint32_t { + ExplicitVideoAllowed = 0X000000FF, + ExplicitVideoBanned = 0X00000000, +}; + +// XCONFIG_USER_PC_EXPLICIT_VIDEO_RATING +enum X_PC_EXPLICIT_VIDEO_RATING : uint32_t { + ExplicitAllowed = 0X00000000, +}; + +// XCONFIG_USER_PC_UNRATED_VIDEO +enum X_PC_EXPLICIT_UNRATED : uint32_t { + ExplicitUnratedBanned = 0X00000000, + UnratedALL = 0X000000FF, +}; + +// XCONFIG_USER_PC_UNRATED_VIDEO_RATING +enum X_PC_EXPLICIT_UNRATED_RATING : uint32_t { + DefaultExplicitUnrated = 0X00000000, +}; + +// XCONFIG_USER_VIDEO_OUTPUT_BLACK_LEVELS +enum X_BLACK_LEVEL : uint32_t { + High = 0x00000100, + Intermediate = 0x00000200, + LevelNormal = 0x00000300, +}; + +// XCONFIG_CONSOLE_SCREENSAVER +enum X_SCREENSAVER : uint32_t { + ScreensaverOn = 0x000A, + ScreensaverOff = 0x1000, +}; + +// XCONFIG_CONSOLE_AUTO_SHUTDOWN +enum X_AUTO_SHUTDOWN : uint32_t { + AutoShutdownOff = 0x0000, + AutoShutdownOneHr = 0x003C, + AutoShutdownSixHr = 0x0168, +}; + +// XCONFIG_CONSOLE_CAMERA_SETTINGS +enum X_CAMERA_FLAGS : uint32_t { + // Room + RoomAuto = 0x00000000, + DarkWall = 0x00000001, + LightWall = 0x00000002, + // Lighting + LightingAuto = 0x00000000, + Incandescent = 0x00000004, + Flourescent = 0x00000008, + Daylight = 0x0000000C, + // Flourescent Anti-Flicker + AntiFlickerAuto = 0x00000000, + AntiFlickerOn = 0x00000001, + AntiFlickerOff = 0x00000002, + // Default + AutoAll = 0x00000001, +}; + +// XCONFIG_CONSOLE_KEYBOARD_LAYOUT +enum X_KEYBOARD_LAYOUT : uint16_t { + KeyboardDefault = 0x0000, + EnglishQWERTY = 0x0001, +}; + +#pragma pack(push, 1) + +struct XConfigData { + // ------------------------------------------------------------------------- + // 0x0 XCONFIG_STATIC_SETTINGS + // ------------------------------------------------------------------------- + struct Static { + xe::be checksum; // 0x000 + xe::be version; // 0x004 + std::array first_power_on; // 0x008 date; key 0x1 + uint8_t _reserved0; // 0x00D + std::array smc_block; // 0x00E union_SMC_BLOCK + } static_settings; + + // ------------------------------------------------------------------------- + // 0x1 XCONFIG_STATISTIC_SETTINGS + // ------------------------------------------------------------------------- + struct Statistic { + xe::be checksum; // 0x0000 + xe::be version; // 0x0004 + std::array xuid_mac_address; // 0x0008 + std::array _reserved0; // 0x000E + xe::be xuid_count; // 0x0010 + std::array odd_failures; // 0x0014 + std::array bug_check_data; // 0x0034 + std::array temperature_data; // 0x0099 + std::array _unused; // 0x0161 + std::array hdd_smart_data; // 0x0334 + std::array uem_errors; // 0x0534 + std::array fpm_errors; // 0x0598 + xe::be last_report_time; // 0x05D0 FILETIME? + } statistic; + + // ------------------------------------------------------------------------- + // 0x2 XCONFIG_SECURED_SETTINGS + // ------------------------------------------------------------------------- + struct Secured { + xe::be checksum; // 0x00 + xe::be version; // 0x04 + std::array online_network_id; // 0x08 + std::array _reserved1; // 0x0C + std::array _reserved2; // 0x14 + std::array mac_address; // 0x20 + std::array _reserved3; // 0x26 + xe::be av_region; // 0x28 + xe::be game_region; // 0x2C + std::array _reserved4; // 0x2E + xe::be dvd_region; // 0x34 + xe::be reset_key; // 0x38 + xe::be system_flags; // 0x3C + std::array power_mode; // 0x40 + std::array power_vcs_control; // 0x42 + std::array _reserved_region; // 0x44 + } secured; + + // ------------------------------------------------------------------------- + // 0x3 XCONFIG_USER_SETTINGS + // ------------------------------------------------------------------------- + struct User { + xe::be checksum; // 0x000 + xe::be version; // 0x004 + xe::be time_zone_bias; // 0x008 + std::array tz_std_name; // 0x00C + std::array tz_dlt_name; // 0x010 + std::array tz_std_date; // 0x014 XCONFIG_TIMEZONE_DATE + std::array tz_dlt_date; // 0x018 XCONFIG_TIMEZONE_DATE + xe::be tz_std_bias; // 0x01C + xe::be tz_dlt_bias; // 0x020 + xe::be default_profile; // 0x024 + xe::be language; // 0x02C + xe::be video_flags; // 0x030 + xe::be audio_flags; // 0x034 + xe::be retail_flags; // 0x038 + xe::be devkit_flags; // 0x03C + xe::be country; // 0x040 + xe::be parental_control_flags; // 0x041 + std::array _reserved_flag; // 0x042 + std::array smb_config; // 0x044 + xe::be live_puid; // 0x144 + std::array live_credentials; // 0x14C + xe::be av_pack_hdmi_sz; // 0x15C + xe::be av_pack_component_sz; // 0x160 + xe::be av_pack_vga_sz; // 0x164 + xe::be parental_control_game; // 0x168 + xe::be parental_control_password; // 0x16C + xe::be parental_control_movie; // 0x170 + xe::be parental_control_game_rating; // 0x174 + xe::be parental_control_movie_rating; // 0x178 + uint8_t parental_control_hint; // 0x17C + std::array parental_control_hint_answer; // 0x17D + std::array parental_control_override; // 0x19D + xe::be music_playback_mode; // 0x1BD + xe::be music_volume; // 0x1C1 + xe::be music_flags; // 0x1C5 + xe::be arcade_flags; // 0x1C9 + xe::be parental_control_version; // 0x1CD + xe::be parental_control_tv; // 0x1D1 + xe::be parental_control_tv_rating; // 0x1D5 + xe::be parental_control_explicit_video; // 0x1D9 + xe::be parental_control_explicit_video_rating; // 0x1DD + xe::be parental_control_unrated_video; // 0x1E1 + xe::be parental_control_unrated_video_rating; // 0x1E5 + xe::be video_output_black_levels; // 0x1E9 + uint8_t video_player_display_mode; // 0x1ED + xe::be alternative_video_timing_ids; // 0x1EE + xe::be video_driver_options; // 0x1F2 + xe::be music_ui_flags; // 0x1F6 + uint8_t video_media_source_type; // 0x1FA + uint8_t music_media_source_type; // 0x1FB + uint8_t photo_media_source_type; // 0x1FC + } user; + + // ------------------------------------------------------------------------- + // 0x4 XCONFIG_XNET_MACHINE_ACCOUNT + // ------------------------------------------------------------------------- + struct XnetMachineAccount { + xe::be version; // 0x000 + std::array data; // 0x004 + } xnet_machine_account; + + // ------------------------------------------------------------------------- + // 0x5 XCONFIG_XNET_PARAMETERS + // ------------------------------------------------------------------------- + struct XnetParameters { + uint8_t cfg_size_of_struct; // 0x00 + uint8_t cfg_flags; // 0x01 + uint8_t cfg_sock_max_dgram_sockets; // 0x02 + uint8_t cfg_sock_max_stream_sockets; // 0x03 + uint8_t cfg_sock_default_recv_bufsize_in_k; // 0x04 + uint8_t cfg_sock_default_send_bufsize_in_k; // 0x05 + uint8_t cfg_key_reg_max; // 0x06 + uint8_t cfg_sec_reg_max; // 0x07 + uint8_t cfg_qos_data_limit_div4; // 0x08 + uint8_t cfg_qos_probe_timeout_in_seconds; // 0x09 + uint8_t cfg_qos_probe_entries; // 0x0A + uint8_t cfg_qos_srv_max_simultaneous_responses; // 0x0B + uint8_t cfg_qos_pair_wait_time_in_seconds; // 0x0C + } xnet_parameters; + + // ------------------------------------------------------------------------- + // 0x6 XCONFIG_MEDIA_CENTER_SETTINGS + // ------------------------------------------------------------------------- + struct MediaCenter { + xe::be checksum; // 0x0000 + xe::be version; // 0x0004 + std::array media_player; // 0x0008 + std::array xe_sled_version; // 0x001C + std::array xe_sled_trust_secret; // 0x0026 + std::array xe_sled_trust_code; // 0x003A + std::array xe_sled_host_id; // 0x0042 + std::array xe_sled_key; // 0x0056 + std::array xe_sled_host_mac_address; // 0x06B2 + std::array server_uuid; // 0x06B8 + std::array server_name; // 0x06C8 + std::array server_flags; // 0x0748 + } media_center; + + // ------------------------------------------------------------------------- + // 0x7 XCONFIG_CONSOLE_SETTINGS + // ------------------------------------------------------------------------- + struct Console { + xe::be checksum; // 0x000 + xe::be version; // 0x004 + xe::be screen_saver; // 0x008 + xe::be auto_shut_off; // 0x00A + std::array wireless_settings; // 0x00C + xe::be camera_settings; // 0x10C + std::array camera_settings_reserved; // 0x110 + std::array play_timer_data; // 0x12C XCONFIG_PLAY_TIMER_DATA + xe::be media_disable_auto_launch; // 0x140 + xe::be keyboard_layout; // 0x142 + } console; + + // ------------------------------------------------------------------------- + // 0x8 XCONFIG_DVD_SETTINGS + // ------------------------------------------------------------------------- + struct Dvd { + xe::be version; // 0x000 + std::array volume_id; // 0x004 + std::array data; // 0x018 + } dvd; + + // ------------------------------------------------------------------------- + // 0x9 XCONFIG_IPTV_SETTINGS + // ------------------------------------------------------------------------- + struct Iptv { + xe::be checksum; // 0x000 + xe::be version; // 0x004 + std::array service_provider_name; // 0x008 wchar + std::array provisioning_server_url; // 0x080 wchar + std::array support_info; // 0x100 wchar + std::array bootstrap_server_url; // 0x180 wchar + } iptv; + + // ------------------------------------------------------------------------- + // 0xA XCONFIG_SYSTEM_SETTINGS + // ------------------------------------------------------------------------- + struct System { + xe::be version; // 0x00 + xe::be alarm_time; // 0x04 union_LARGE_INTEGER + xe::be previous_flash_version; // 0x0C + } system; +}; + +static_assert(offsetof(XConfigData::Secured, mac_address) == 32); +static_assert(offsetof(XConfigData::Secured, av_region) == 40); +static_assert(offsetof(XConfigData::User, language) == 44); +static_assert(offsetof(XConfigData::User, audio_flags) == 52); +static_assert(offsetof(XConfigData::User, retail_flags) == 56); +static_assert(offsetof(XConfigData::User, country) == 64); +static_assert(offsetof(XConfigData::User, music_volume) == 449); + +#pragma pack(pop) + +template +concept XConfigValidValueType = + std::same_as || std::same_as || + std::same_as || std::same_as || + std::same_as; + +class XConfig { + public: + XConfig() = delete; + XConfig(const std::filesystem::path& xconfig_path); + + ~XConfig() = default; + + void WriteSetting(const X_CONFIG_CATEGORY category, const uint16_t setting_id, + const void* buffer); + + void ReadSetting(const X_CONFIG_CATEGORY category, const uint16_t setting_id, + void* buffer); + template + T ReadSetting(const X_CONFIG_CATEGORY category, const uint16_t setting_id) { + const auto field = FindField(category, setting_id); + + if (!field || sizeof(T) != field->size) { + return {}; + } + + T buffer; + ReadSetting(category, setting_id, &buffer); + + buffer = xe::byte_swap(buffer); + + return buffer; + } + + uint16_t GetSettingSize(const X_CONFIG_CATEGORY category, + const uint16_t setting_id); + + const XConfigData* GetXConfig() const { return &xconfig_data_; } + void WriteXConfig(const XConfigData* data); + + void SetDefaults(); + + private: + void FlushToFile(); + + struct FieldDescriptor { + X_CONFIG_CATEGORY category; + uint16_t setting; + uint16_t size; + size_t block_offset; + }; + + static const FieldDescriptor* FindField(X_CONFIG_CATEGORY category, + uint16_t setting); + uint8_t* CategoryBase(X_CONFIG_CATEGORY category); + const uint8_t* CategoryBase(X_CONFIG_CATEGORY category) const; + +#define XCONFIG_FIELD(category, setting, block, member) \ + {(category), (setting), \ + static_cast(sizeof(XConfigData::block::member)), \ + offsetof(XConfigData::block, member)} + + static inline constexpr XConfig::FieldDescriptor kFields[] = { + // -- XCONFIG_STATIC (0x0) --------------------------------------------- + XCONFIG_FIELD(XCONFIG_STATIC_CATEGORY, XCONFIG_STATIC_FIRST_POWER_ON_DATE, + Static, first_power_on), + + // -- XCONFIG_SECURED (0x2) -------------------------------------------- + XCONFIG_FIELD(XCONFIG_SECURED_CATEGORY, XCONFIG_SECURED_MAC_ADDRESS, + Secured, mac_address), + XCONFIG_FIELD(XCONFIG_SECURED_CATEGORY, XCONFIG_SECURED_AV_REGION, + Secured, av_region), + XCONFIG_FIELD(XCONFIG_SECURED_CATEGORY, XCONFIG_SECURED_GAME_REGION, + Secured, game_region), + XCONFIG_FIELD(XCONFIG_SECURED_CATEGORY, XCONFIG_SECURED_DVD_REGION, + Secured, dvd_region), + XCONFIG_FIELD(XCONFIG_SECURED_CATEGORY, XCONFIG_SECURED_RESET_KEY, + Secured, reset_key), + XCONFIG_FIELD(XCONFIG_SECURED_CATEGORY, XCONFIG_SECURED_SYSTEM_FLAGS, + Secured, system_flags), + XCONFIG_FIELD(XCONFIG_SECURED_CATEGORY, XCONFIG_SECURED_POWER_MODE, + Secured, power_mode), + XCONFIG_FIELD(XCONFIG_SECURED_CATEGORY, XCONFIG_SECURED_ONLINE_NETWORK_ID, + Secured, online_network_id), + XCONFIG_FIELD(XCONFIG_SECURED_CATEGORY, XCONFIG_SECURED_POWER_VCS_CONTROL, + Secured, power_vcs_control), + + // -- XCONFIG_USER (0x3) ----------------------------------------------- + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_TIME_ZONE_BIAS, User, + time_zone_bias), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_TIME_ZONE_STD_NAME, + User, tz_std_name), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_TIME_ZONE_DLT_NAME, + User, tz_dlt_name), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_TIME_ZONE_STD_DATE, + User, tz_std_date), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_TIME_ZONE_DLT_DATE, + User, tz_dlt_date), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_TIME_ZONE_STD_BIAS, + User, tz_std_bias), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_TIME_ZONE_DLT_BIAS, + User, tz_dlt_bias), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_DEFAULT_PROFILE, User, + default_profile), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_LANGUAGE, User, + language), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_VIDEO_FLAGS, User, + video_flags), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_AUDIO_FLAGS, User, + audio_flags), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_RETAIL_FLAGS, User, + retail_flags), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_DEVKIT_FLAGS, User, + devkit_flags), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_COUNTRY, User, country), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_PC_FLAGS, User, + parental_control_flags), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_SMB_CONFIG, User, + smb_config), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_LIVE_PUID, User, + live_puid), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_LIVE_CREDENTIALS, User, + live_credentials), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_AV_COMPOSITE_SCREENSZ, + User, av_pack_hdmi_sz), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_AV_COMPONENT_SCREENSZ, + User, av_pack_component_sz), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_AV_VGA_SCREENSZ, User, + av_pack_vga_sz), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_PC_GAME, User, + parental_control_game), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_PC_PASSWORD, User, + parental_control_password), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_PC_MOVIE, User, + parental_control_movie), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_PC_GAME_RATING, User, + parental_control_game_rating), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_PC_MOVIE_RATING, User, + parental_control_movie_rating), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_PC_HINT, User, + parental_control_hint), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_PC_HINT_ANSWER, User, + parental_control_hint_answer), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_PC_OVERRIDE, User, + parental_control_override), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_MUSIC_PLAYBACK_MODE, + User, music_playback_mode), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_MUSIC_VOLUME, User, + music_volume), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_MUSIC_FLAGS, User, + music_flags), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_ARCADE_FLAGS, User, + arcade_flags), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_PC_VERSION, User, + parental_control_version), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_PC_TV, User, + parental_control_tv), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_PC_TV_RATING, User, + parental_control_tv_rating), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_PC_EXPLICIT_VIDEO, User, + parental_control_explicit_video), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, + XCONFIG_USER_PC_EXPLICIT_VIDEO_RATING, User, + parental_control_explicit_video_rating), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_PC_UNRATED_VIDEO, User, + parental_control_unrated_video), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_PC_UNRATED_VIDEO_RATING, + User, parental_control_unrated_video_rating), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, + XCONFIG_USER_VIDEO_OUTPUT_BLACK_LEVELS, User, + video_output_black_levels), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, + XCONFIG_USER_VIDEO_PLAYER_DISPLAY_MODE, User, + video_player_display_mode), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, + XCONFIG_USER_ALTERNATE_VIDEO_TIMING_ID, User, + alternative_video_timing_ids), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_VIDEO_DRIVER_OPTIONS, + User, video_driver_options), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_MUSIC_UI_FLAGS, User, + music_ui_flags), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_VIDEO_MEDIA_SOURCE_TYPE, + User, video_media_source_type), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_MUSIC_MEDIA_SOURCE_TYPE, + User, music_media_source_type), + XCONFIG_FIELD(XCONFIG_USER_CATEGORY, XCONFIG_USER_PHOTO_MEDIA_SOURCE_TYPE, + User, photo_media_source_type), + + // -- XCONFIG_CONSOLE (0x7) -------------------------------------------- + XCONFIG_FIELD(XCONFIG_CONSOLE_CATEGORY, XCONFIG_CONSOLE_SCREEN_SAVER, + Console, screen_saver), + XCONFIG_FIELD(XCONFIG_CONSOLE_CATEGORY, XCONFIG_CONSOLE_AUTO_SHUT_OFF, + Console, auto_shut_off), + XCONFIG_FIELD(XCONFIG_CONSOLE_CATEGORY, XCONFIG_CONSOLE_WIRELESS_SETTINGS, + Console, wireless_settings), + XCONFIG_FIELD(XCONFIG_CONSOLE_CATEGORY, XCONFIG_CONSOLE_CAMERA_SETTINGS, + Console, camera_settings), + XCONFIG_FIELD(XCONFIG_CONSOLE_CATEGORY, XCONFIG_CONSOLE_PLAYTIMERDATA, + Console, play_timer_data), + XCONFIG_FIELD(XCONFIG_CONSOLE_CATEGORY, + XCONFIG_CONSOLE_MEDIA_DISABLEAUTOLAUNCH, Console, + media_disable_auto_launch), + XCONFIG_FIELD(XCONFIG_CONSOLE_CATEGORY, XCONFIG_CONSOLE_KEYBOARD_LAYOUT, + Console, keyboard_layout), + // XCONFIG_FIELD(XCONFIG_CONSOLE_CATEGORY, + // XCONFIG_CONSOLE_PC_TITLE_EXEMPTIONS, + // Console, keyboard_layout), + + // -- XCONFIG_SYSTEM (0xA) --------------------------------------------- + XCONFIG_FIELD(XCONFIG_SYSTEM_CATEGORY, XCONFIG_SYSTEM_ALARM_TIME, System, + alarm_time), + XCONFIG_FIELD(XCONFIG_SYSTEM_CATEGORY, + XCONFIG_SYSTEM_PREVIOUS_FLASH_VERSION, System, + previous_flash_version)}; + +#undef XCONFIG_FIELD + + xe_mutex lock_{}; + + std::filesystem::path storage_path_; + std::filesystem::path file_path_; + XConfigData xconfig_data_{}; +}; + +} // namespace kernel +} // namespace xe + +#endif // XENIA_KERNEL_XCONFIG_H_ diff --git a/src/xenia/ui/window.cc b/src/xenia/ui/window.cc index 2185a8cfe..e543d36f5 100644 --- a/src/xenia/ui/window.cc +++ b/src/xenia/ui/window.cc @@ -16,6 +16,9 @@ #include "xenia/ui/imgui_drawer.h" #include "xenia/ui/presenter.h" +DEFINE_int32(window_size_x, 1280, "Xenia window width", "UI"); +DEFINE_int32(window_size_y, 720, "Xenia window height", "UI"); + namespace xe { namespace ui { @@ -242,6 +245,7 @@ void Window::SetFullscreen(bool new_fullscreen) { if (!CanApplyState()) { return; } + WindowDestructionReceiver destruction_receiver(this); ApplyNewFullscreen(); if (destruction_receiver.IsWindowDestroyedOrStateInapplicable()) { @@ -499,6 +503,7 @@ void Window::OnUsbDeviceChanged( bool Window::OnActualSizeUpdate( uint32_t new_physical_width, uint32_t new_physical_height, + WindowResizeAction cause_action, WindowDestructionReceiver& destruction_receiver) { if (actual_physical_width_ == new_physical_width && actual_physical_height_ == new_physical_height) { @@ -508,6 +513,13 @@ bool Window::OnActualSizeUpdate( actual_physical_height_ = new_physical_height; // The listeners may reference the presenter, update the presenter first. if (presenter_surface_) { + // Update variable only if window isn't in fullscreen mode. + if (!fullscreen_ && cause_action == WindowResizeAction::kManual) { + // Write new window size only if we know that window is present. + OVERRIDE_int32(window_size_x, SizeToLogical(new_physical_width)); + OVERRIDE_int32(window_size_y, SizeToLogical(new_physical_height)); + } + presenter_->OnSurfaceResizeFromUIThread(); } UISetupEvent e(this); diff --git a/src/xenia/ui/window.h b/src/xenia/ui/window.h index b0a207a47..487cda142 100644 --- a/src/xenia/ui/window.h +++ b/src/xenia/ui/window.h @@ -152,6 +152,12 @@ class Window { kHidden, }; + enum class WindowResizeAction { + kManual, + kAutoMaximize, + kAutoMinimize, + }; + static std::unique_ptr Create(WindowedAppContext& app_context, const std::string_view title, uint32_t desired_logical_width, @@ -572,6 +578,7 @@ class Window { // explicitly. bool OnActualSizeUpdate(uint32_t new_physical_width, uint32_t new_physical_height, + WindowResizeAction cause_action, WindowDestructionReceiver& destruction_receiver); void OnDesiredFullscreenUpdate(bool new_fullscreen) { fullscreen_ = new_fullscreen; diff --git a/src/xenia/ui/window_gtk.cc b/src/xenia/ui/window_gtk.cc index 233ae94d3..58c08a70d 100644 --- a/src/xenia/ui/window_gtk.cc +++ b/src/xenia/ui/window_gtk.cc @@ -124,7 +124,7 @@ bool GTKWindow::OpenImpl() { gtk_widget_get_allocation(drawing_area_, &drawing_area_allocation); OnActualSizeUpdate(uint32_t(drawing_area_allocation.width), uint32_t(drawing_area_allocation.height), - destruction_receiver); + WindowResizeAction::kManual, destruction_receiver); if (destruction_receiver.IsWindowDestroyedOrClosed()) { return true; } @@ -311,7 +311,7 @@ void GTKWindow::HandleSizeUpdate( gtk_widget_get_allocation(drawing_area_, &drawing_area_allocation); OnActualSizeUpdate(uint32_t(drawing_area_allocation.width), uint32_t(drawing_area_allocation.height), - destruction_receiver); + WindowResizeAction::kManual, destruction_receiver); if (destruction_receiver.IsWindowDestroyedOrClosed()) { return; } diff --git a/src/xenia/ui/window_win.cc b/src/xenia/ui/window_win.cc index 07857915e..f880aa181 100644 --- a/src/xenia/ui/window_win.cc +++ b/src/xenia/ui/window_win.cc @@ -279,7 +279,7 @@ bool Win32Window::OpenImpl() { if (GetClientRect(hwnd_, &shown_client_rect)) { OnActualSizeUpdate(uint32_t(shown_client_rect.right), uint32_t(shown_client_rect.bottom), - destruction_receiver); + WindowResizeAction::kManual, destruction_receiver); if (destruction_receiver.IsWindowDestroyedOrClosed()) { return true; } @@ -722,7 +722,7 @@ void Win32Window::ApplyFullscreenEntry( } void Win32Window::HandleSizeUpdate( - WindowDestructionReceiver& destruction_receiver) { + WindowDestructionReceiver& destruction_receiver, DWORD cause_action) { if (!hwnd_) { // Batched size update ended when the window has already been closed, for // instance. @@ -767,7 +767,10 @@ void Win32Window::HandleSizeUpdate( RECT client_rect; if (GetClientRect(hwnd_, &client_rect)) { OnActualSizeUpdate(uint32_t(client_rect.right), - uint32_t(client_rect.bottom), destruction_receiver); + uint32_t(client_rect.bottom), + cause_action == 0 ? WindowResizeAction::kManual + : WindowResizeAction::kAutoMaximize, + destruction_receiver); if (destruction_receiver.IsWindowDestroyedOrClosed()) { return; } @@ -792,7 +795,7 @@ void Win32Window::EndBatchedSizeUpdate( // handle the deferred messages twice. if (batched_size_update_contained_wm_size_) { batched_size_update_contained_wm_size_ = false; - HandleSizeUpdate(destruction_receiver); + HandleSizeUpdate(destruction_receiver, 0); if (destruction_receiver.IsWindowDestroyed()) { return; } @@ -1064,7 +1067,7 @@ LRESULT Win32Window::WndProc(HWND hWnd, UINT message, WPARAM wParam, batched_size_update_contained_wm_size_ = true; } else { WindowDestructionReceiver destruction_receiver(this); - HandleSizeUpdate(destruction_receiver); + HandleSizeUpdate(destruction_receiver, wParam); if (destruction_receiver.IsWindowDestroyedOrClosed()) { break; } diff --git a/src/xenia/ui/window_win.h b/src/xenia/ui/window_win.h index 93365939e..bd068d810 100644 --- a/src/xenia/ui/window_win.h +++ b/src/xenia/ui/window_win.h @@ -76,7 +76,8 @@ class Win32Window : public Window { void ApplyFullscreenEntry(WindowDestructionReceiver& destruction_receiver); - void HandleSizeUpdate(WindowDestructionReceiver& destruction_receiver); + void HandleSizeUpdate(WindowDestructionReceiver& destruction_receiver, + DWORD cause_action); // For updating multiple factors that may influence the window size at once, // without handling WM_SIZE multiple times (that may not only result in wasted // handling, but also in the state potentially changed to an inconsistent one