Compare commits

..

61 Commits

Author SHA1 Message Date
Xphalnos c9a70cab65 [d3d12] Optimize shaders 2025-01-17 10:54:27 +01:00
Marco Rodolfi 55bbb28a80 [threading] Linux fixes on setting an incorrect priority
This call was failing since SCHED_FIFO doesn't support negative priorities, but only positive ones, see third paragraph of scheduling policies: https://man7.org/linux/man-pages/man7/sched.7.html.

Additionally Linux do provice up to 99 levels, but I've limited myself to the required UNIX standard of 32, and split the priority levels evenly from that.

I've also added a couple of rows to debug additional issues in the future like this.
2025-01-17 09:56:43 +01:00
The-Little-Wolf 4d7b30e844 [Xam/XamUser] - Stub XamUserGetOnlineLanguageFromXUID
- Stubs XamUserGetOnlineLanguageFromXUID and have it return cvars::user_language
- Leave notes for future implementation once we have proper profile support
2025-01-17 08:32:46 +01:00
Adrian ae23222ba8 [Emulator] Validate module is an executable before launching 2025-01-17 08:04:49 +01:00
Adrian d99d053408 [Patcher] Replace stoi with from_string for parsing plugin title id 2025-01-16 15:08:20 +01:00
Gliniak 1688ea5d69 [Kernel] Replaced TranslateAnsiString with TranslateAnsiPath for file paths.
This removes paths that starts or ends with whitespace characters
2025-01-14 22:45:43 +01:00
Gliniak e80d4effa1 [Kernel] IsValidPath: Removed ; and = from invalid characters list
Path like: fxprefabs;scale=fx_dropship_02_fade.* is processed as a proper one internally and it expects no file error instead of invalid parameter
2025-01-14 21:05:00 +01:00
Gliniak 57da74814b [XAM] Fixed issue with missing DLCs due to invalid XUID 2025-01-12 17:01:20 +01:00
The-Little-Wolf a77213dcdb [Xam/Xam_Profile] - Stub XamProfileCreate and more
- Move XamProfileFindAccount
- Leave notes for XamProfileOpen
- Stub XamProfileCreate
2025-01-12 16:31:39 +01:00
Adrian e134bbccd1 [App] Fixed RunTitle crash via hotkey 2025-01-12 16:11:16 +01:00
Marco Rodolfi 1822bca890 [BUILD] Fix build test generation for Linux systems
This allows a Linux system to generate all the PPC tests just by running ./xb gentests as on a Windows system. Tested locally.
2025-01-12 13:45:50 +01:00
Gliniak 09be7e874a [All] Fixed multiple issues during build on Linux
- Added some fixes introduced by RodoMa92 in PR198

- Lack of AVX2 extension (should be done differently in the future)
- Disable deprecated-volatile warning
- Added missing override in posix EventInfo, ImGui notification class and XContent class
- Removed not used XAudio2.h include in XMP
- Fixed missing switch-case in XObject
- Added fugly template in native_list.h
- Fixed multiple smaller issues
2025-01-10 21:41:45 +01:00
Gliniak cdd3f161fa [XAM] Fixed invalid param count in XNetUnregisterKey 2025-01-10 21:20:06 +01:00
Gliniak ccf4d6d5f8 [Emulator] Added logging file extension and magic value 2025-01-10 21:19:58 +01:00
Gliniak 2253cb978d [XAM] Added stub for: Refresh 2025-01-10 13:34:10 +01:00
Gliniak b50e32ab45 [XAM] Removed Windows specific code from xam_info.cc 2025-01-09 19:43:15 +01:00
Gliniak 4620fa93d8 [XAM] Added XamProfileOpen support and xam_profile.cc dedicated file for XamProfile exports
- Modified implementation of MountProfile to allow custom mount paths
2025-01-08 22:12:41 +01:00
Gliniak 7e51efeec5 [3PP] ZLib: Ignore warnings-errors 2025-01-08 21:36:30 +01:00
marko1616 34da168284 [XEX] Check if header exists during XexInfoCache init 2025-01-08 11:22:26 +01:00
Margen67 762145687b Update to Premake 5.0-beta3 2025-01-07 23:02:48 -08:00
Margen67 6f1cb9e253 Fix clang not building with C++20 2025-01-07 23:02:48 -08:00
Gliniak 7437c020d6 [Misc] Fixed some issues during compilation process on Linux 2025-01-07 22:54:16 +01:00
The-Little-Wolf 57eeed86b7 [XboxKrnl/xconfig.cc] - Stub ExSetXConfigSetting and ExReadModifyWriteXConfigSettingUlong
- Stubbing ExSetXConfigSetting and ExReadModifyWriteXConfigSettingUlong
- ExSetXConfigSetting is a simple value swap from buffer_ptr
- ExReadModifyWriteXConfigSettingUlong handles combined values with buffer_ptr holding the replacement values
- added log to show what the new setting should be and what is being replaced
- To Implement would need to convert settings to strucs
2025-01-07 19:52:11 +01:00
Margen67 3b49054d6b [CI] Only build release 2025-01-06 13:28:07 -08:00
Margen67 72df2129a0 [Readme] Add Linux 2025-01-06 12:14:07 -08:00
Margen67 fff79eb41d xenia-build improvements
Add -j to speed up getting submodules.
Add newer clang-format.
2025-01-06 12:14:07 -08:00
Margen67 affb2fb7af CI: Add debug/checked configuration 2025-01-06 12:14:07 -08:00
Margen67 26bf95d50f CI: Add Linux
Also fix build error.
2025-01-06 12:14:07 -08:00
The-Little-Wolf b5d319834e [XboxKrnl/xconfig.cc] - Fixes and logging changes
- Adding more information in logs for easier debugging
- XamSetDashContext correction
- XCONFIG_USER_VIDEO_FLAGS reports widescreen when set in config by user
- XCONFIG_SECURED_AV_REGION reports proper values when set in config by user
- Added missing \n
2025-01-06 19:49:48 +01:00
Xphalnos bb20ada9bf 3PP: Bump submodules 2025-01-06 18:01:45 +01:00
Gliniak 13badbb4c0 [XAM] Fixed issue with savefile removal via XamContentDeleteInternal
Removed all lpunknown_t in xam_content.cc file and replaced with lpvoid_t
2025-01-06 12:14:55 +01:00
Gliniak d660a82997 [XContent] Aggregate license mask from all licenses while installing package
This should fix issues with some DLCs
2025-01-05 17:59:15 +01:00
Gliniak ad323dd9d8 [HID] Fixed lack of controller input introduced in previous commit 2025-01-05 10:09:32 +01:00
Gliniak 4584794e24 [HID] Added drivers filter for GetState 2025-01-04 21:42:54 +01:00
The-Little-Wolf d6ed8af4e7 [Xam/Avatar] - Stubbing Avatar Functions
Stubbing avatar functions and leaving notes for future reference
2025-01-02 20:38:56 +01:00
The-Little-Wolf 95df198d8a [Xbox] kXNotification Additions
- Discovered new unknown kXNotificationID use by XNotifyBroadcast together and wanted to record them before I forget them.
- Changed kXNotificationID  to kXNotification
- Added kXNotificationSystemPXLiveSystemUpdate
2025-01-02 18:19:21 +01:00
Gliniak b757601f01 [XAM] XamUserGetName: Fixed issue introduced in previous commit 2025-01-01 18:43:52 +01:00
Gliniak 3f0a7f171f [XAM] XamUserGetName: Prevent false-positive in MS defender 2025-01-01 14:14:57 +01:00
Adrian ceb94e019a [XAM/UI] Implemented profile passcode UI 2025-01-01 00:26:13 +01:00
Gliniak fe85be8817 [XAM] Fixed possible crash caused by printing invalid characters in XamUserGetGamerTag and XamUserGetName 2024-12-30 19:47:10 +01:00
Gliniak c3301d9281 [Base] Fixed issue with initialization deadlock on Proton
For whatever reason Proton doesn't like it when Xenia is compiled with 2022 MSVC
2024-12-30 16:41:47 +01:00
Gliniak 1ba30c519c [HID] Fixed issues with double input in specific config HID configuration.
This was the case in hid set to "any" or to "winkey" and game that requires input from any user
2024-12-30 15:43:40 +01:00
Adrian 3dac88113f [XBDM] Implemented DmSetMemory and DmGetMemory 2024-12-29 18:35:44 +01:00
The-Little-Wolf 160d80d5cc [Xam/Enum] Implement EnumerateMediaObjects Functions
According to xam versions 8955 and 12611 all EnumerateMediaObjects are the same function and only return 0x80004001
2024-12-29 17:08:43 +01:00
Gliniak 580b1f4345 [XAM] Fixed nullptr crash in XamShowMessageBoxUI 2024-12-27 10:25:20 +01:00
Gliniak 9dfb0d0b68 [Base] Moved IsUseNexusForGameBarEnabled from emulator_window to system.h
This will remove direct usage of system specific code from emulator_window
2024-12-26 23:42:31 +01:00
Gliniak bcc3c3172d [Base] Added NtSystemClock to_local for quick time conversion to current timezone
- Changed achievement reward time to local time (in UI)
2024-12-26 23:42:31 +01:00
Gliniak c3586bc165 [C++] Uplift version to C++20 2024-12-26 23:42:31 +01:00
Gliniak a6e3d77504 [HID] Report passthrough as keyboard type and subtype device.
- Added enums for: X_INPUT_DEVTYPE and X_INPUT_DEVSUBTYPE
- Fixed issue with xinput failure with keyboard flag received
2024-12-23 15:50:00 +01:00
Gliniak 2e521383c2 [HID] Fixed some issues with controller/passthrough visibility.
- Moved X_INPUT flags from kernel to HID
- Added ability to return input type from driver
2024-12-23 10:43:27 +01:00
Gliniak 11f14e8488 [HID] Fixed spam caused by incorrect controller visibility
- Little cleanup in HID related code
2024-12-21 19:51:01 +01:00
Adrian 3d79874828 [XAM] Fixed profile setting for preferred color
Fixes Supreme Commander from crashing.
2024-12-21 14:58:16 +01:00
The-Little-Wolf 919f7403e2 [Xam/UI] - Implement XamShowMarketplaceUIEx & XamShowForcedNameChangeUI
Xam version 4548 and up XamShowMarketplaceUI transfers its params to XamShowMarketplaceUIEx.
2024-12-20 08:00:43 +01:00
Gliniak 263c722a40 [XAM] Added X_ACHIEVEMENT_UNLOCK_TIME ctor to support unix timestamp 2024-12-18 20:59:27 +01:00
Gliniak b98ff2d278 [XAM] Added specific ctor for X_ACHIEVEMENT_UNLOCK_TIME
Added method X_ACHIEVEMENT_UNLOCK_TIME::to_time_point
2024-12-18 20:32:52 +01:00
Gliniak ef8619b0a8 [HID] Fixed issue with controller input introduced in previous commit.
Added option to switch keyboard working mode.
2024-12-18 20:11:38 +01:00
Gliniak f6ae651cc2 [HID] Enable Keyboard Passthru
Thanks Adrian for figuring out missing XINPUT_KEYSTROKE_VALIDUNICODE flag.
Thanks Clippy95 for figuring out shift and capslock
2024-12-17 08:59:13 +01:00
Margen67 2596aef111 [CI] Use main branch of actions 2024-12-16 19:49:10 -08:00
Gliniak b187ffeef7 [UI] Fixed crash while opening titles menu while not being logged in 2024-12-16 19:36:33 +01:00
Xphalnos c8c62fc07c [Base] Fixed compilation error in math.h 2024-12-16 18:55:46 +01:00
Gliniak cfd965342a [XAM] Fixed issue with lack of loaded achievements while switching profile in-game 2024-12-15 14:16:17 +01:00
119 changed files with 3759 additions and 2132 deletions

77
.github/workflows/Linux_build.yml vendored Normal file
View File

@ -0,0 +1,77 @@
name: Linux build
on:
push:
paths-ignore:
- '.clang-format'
- '.drone.star'
- '.gitattributes'
- '.gitignore'
- '.gdbinit'
- '.github/*'
- '.github/workflows/Windows_build.yml'
- '.github/*_TEMPLATE/**'
- '*.md'
- '*.yml'
- '*.txt'
- 'docs/**'
- 'src/**/*_windows.*'
- 'src/**/*_android.*'
- 'src/**/*_mac.*'
- 'LICENSE'
pull_request:
paths-ignore:
- '.clang-format'
- '.drone.star'
- '.gitattributes'
- '.gitignore'
- '.gdbinit'
- '.github/*'
- '.github/workflows/Windows_build.yml'
- '.github/*_TEMPLATE/**'
- '*.md'
- '*.yml'
- '*.txt'
- 'docs/**'
- 'src/**/*_windows.*'
- 'src/**/*_android.*'
- 'src/**/*_mac.*'
- 'LICENSE'
workflow_dispatch:
jobs:
# lint:
# name: Lint
# runs-on: ubuntu-24.04
# steps:
# - uses: actions/checkout@main
# - name: Check Clang-Format Version
# run: clang-format --version
# - name: Lint
# run: ./xb lint --all
build-linux:
name: Build (Linux) # runner.os can't be used here
# needs: lint
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@main
with:
fetch-depth: 0
- name: Set environment variables
run: |
LLVM_VERSION=19
echo "LLVM_VERSION=$LLVM_VERSION" >> $GITHUB_ENV
echo "UBUNTU_BASE=jammy" >> $GITHUB_ENV
echo "CC=clang-${LLVM_VERSION}" >> $GITHUB_ENV
echo "CXX=clang++-${LLVM_VERSION}" >> $GITHUB_ENV
echo "AR=llvm-ar-${LLVM_VERSION}" >> $GITHUB_ENV
- name: Setup
run: |
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
sudo apt-add-repository "deb http://apt.llvm.org/${UBUNTU_BASE}/ llvm-toolchain-${UBUNTU_BASE}-$LLVM_VERSION main"
sudo apt-get -y update
sudo apt-get -y install mesa-vulkan-drivers valgrind libc++-dev libc++abi-dev libgtk-3-dev libsdl2-dev libvulkan-dev libx11-dev libx11-xcb-dev clang-$LLVM_VERSION clang-format-$LLVM_VERSION llvm-$LLVM_VERSION
./xb setup
- name: Build
run: ./xb build --config=Release

View File

@ -9,6 +9,7 @@ on:
- '.gitignore' - '.gitignore'
- '.gdbinit' - '.gdbinit'
- '.github/*' - '.github/*'
- '.github/workflows/Linux_build.yml'
- '.github/*_TEMPLATE/**' - '.github/*_TEMPLATE/**'
- '*.md' - '*.md'
- '*.yml' - '*.yml'
@ -30,6 +31,7 @@ on:
- '.gitignore' - '.gitignore'
- '.gdbinit' - '.gdbinit'
- '.github/*' - '.github/*'
- '.github/workflows/Linux_build.yml'
- '.github/*_TEMPLATE/**' - '.github/*_TEMPLATE/**'
- '*.md' - '*.md'
- '*.yml' - '*.yml'
@ -50,7 +52,7 @@ jobs:
name: Lint name: Lint
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@main
- name: Check Clang-Format Version - name: Check Clang-Format Version
run: clang-format --version run: clang-format --version
- name: Lint - name: Lint
@ -67,7 +69,7 @@ jobs:
env: env:
POWERSHELL_TELEMETRY_OPTOUT: 1 POWERSHELL_TELEMETRY_OPTOUT: 1
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@main
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup - name: Setup
@ -75,12 +77,15 @@ jobs:
- name: Build - name: Build
run: .\xb build --config=Release --target=src\xenia-app run: .\xb build --config=Release --target=src\xenia-app
- name: Prepare artifacts - name: Prepare artifacts
id: prepare_artifacts
run: | run: |
robocopy . build\bin\${{ runner.os }}\Release LICENSE /r:0 /w:0 robocopy . build\bin\Windows\Release LICENSE /r:0 /w:0
robocopy build\bin\${{ runner.os }}\Release artifacts\xenia_canary xenia_canary.exe xenia_canary.pdb LICENSE /r:0 /w:0 robocopy build\bin\Windows\Release artifacts\xenia_canary xenia_canary.exe xenia_canary.pdb LICENSE /r:0 /w:0
If ($LastExitCode -le 7) { echo "LastExitCode = $LastExitCode";$LastExitCode = 0 } If ($LastExitCode -le 7) { echo "LastExitCode = $LastExitCode";$LastExitCode = 0 }
- name: Upload xenia canary artifacts - name: Upload xenia canary artifacts
uses: actions/upload-artifact@v4 if: steps.prepare_artifacts.outcome == 'success'
id: upload_artifacts
uses: actions/upload-artifact@main
with: with:
name: xenia_canary_vs${{ matrix.vsver }} name: xenia_canary_vs${{ matrix.vsver }}
path: artifacts\xenia_canary path: artifacts\xenia_canary
@ -89,7 +94,8 @@ jobs:
if: | if: |
github.repository == 'xenia-canary/xenia-canary' && github.repository == 'xenia-canary/xenia-canary' &&
github.event.action != 'pull_request' && github.event.action != 'pull_request' &&
github.ref == 'refs/heads/canary_experimental' github.ref == 'refs/heads/canary_experimental' &&
steps.upload_artifacts.outcome == 'success'
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |

2
.gitmodules vendored
View File

@ -12,7 +12,7 @@
url = https://github.com/catchorg/Catch2.git url = https://github.com/catchorg/Catch2.git
[submodule "third_party/premake-core"] [submodule "third_party/premake-core"]
path = third_party/premake-core path = third_party/premake-core
url = https://github.com/xenia-project/premake-core.git url = https://github.com/premake/premake-core.git
[submodule "third_party/snappy"] [submodule "third_party/snappy"]
path = third_party/snappy path = third_party/snappy
url = https://github.com/xenia-project/snappy.git url = https://github.com/xenia-project/snappy.git

View File

@ -21,7 +21,7 @@ Discussing illegal activities will get you banned.
Buildbot | Status | Releases Buildbot | Status | Releases
-------- | ------ | -------- -------- | ------ | --------
Windows | [![CI](https://github.com/xenia-canary/xenia-canary/actions/workflows/Windows_build.yml/badge.svg?branch=canary_experimental)](https://github.com/xenia-canary/xenia-canary/actions/workflows/Windows_build.yml) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/cd506034fd8148309a45034925648499)](https://app.codacy.com/gh/xenia-canary/xenia-canary/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) | [Latest](https://github.com/xenia-canary/xenia-canary/releases/latest) ◦ [All](https://github.com/xenia-canary/xenia-canary/releases) Windows | [![CI](https://github.com/xenia-canary/xenia-canary/actions/workflows/Windows_build.yml/badge.svg?branch=canary_experimental)](https://github.com/xenia-canary/xenia-canary/actions/workflows/Windows_build.yml) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/cd506034fd8148309a45034925648499)](https://app.codacy.com/gh/xenia-canary/xenia-canary/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) | [Latest](https://github.com/xenia-canary/xenia-canary/releases/latest) ◦ [All](https://github.com/xenia-canary/xenia-canary/releases)
Linux | Curently unsupported Linux | [![CI](https://github.com/xenia-canary/xenia-canary/actions/workflows/Linux_build.yml/badge.svg?branch=canary_experimental)](https://github.com/xenia-canary/xenia-canary/actions/workflows/Linux_build.yml)
Netplay Build | | [Latest](https://github.com/AdrianCassar/xenia-canary/releases/latest) Netplay Build | | [Latest](https://github.com/AdrianCassar/xenia-canary/releases/latest)
## Quickstart ## Quickstart
@ -36,7 +36,7 @@ See the [frequently asked questions](https://github.com/xenia-canary/xenia-canar
See the [Game compatibility list](https://github.com/xenia-canary/game-compatibility/issues) See the [Game compatibility list](https://github.com/xenia-canary/game-compatibility/issues)
for currently tracked games, and feel free to contribute your own updates, for currently tracked games, and feel free to contribute your own updates,
screenshots, and information there following the [existing conventions](https://github.com/xenia-canary/game-compatibility/blob/master/README.md). screenshots, and information there following the [existing conventions](https://github.com/xenia-canary/game-compatibility/blob/canary/README.md).
## Building ## Building

View File

@ -26,7 +26,7 @@ defines({
"UNICODE", "UNICODE",
}) })
cppdialect("C++17") cppdialect("C++20")
exceptionhandling("On") exceptionhandling("On")
rtti("On") rtti("On")
symbols("On") symbols("On")
@ -113,12 +113,18 @@ filter("platforms:Linux")
"rt", "rt",
}) })
filter({"platforms:Linux"})
vectorextensions("AVX2")
filter({"platforms:Linux", "kind:*App"}) filter({"platforms:Linux", "kind:*App"})
linkgroups("On") linkgroups("On")
filter({"platforms:Linux", "language:C++", "toolset:gcc"}) filter({"platforms:Linux", "language:C++", "toolset:gcc"})
disablewarnings({ disablewarnings({
"unused-result" "unused-result",
"deprecated-volatile",
"switch",
"deprecated-enum-enum-conversion",
}) })
filter({"platforms:Linux", "toolset:gcc"}) filter({"platforms:Linux", "toolset:gcc"})
@ -135,11 +141,15 @@ filter({"platforms:Linux", "toolset:gcc"})
filter({"platforms:Linux", "language:C++", "toolset:clang"}) filter({"platforms:Linux", "language:C++", "toolset:clang"})
disablewarnings({ disablewarnings({
"deprecated-register" "deprecated-register",
"deprecated-volatile",
"switch",
"deprecated-enum-enum-conversion",
}) })
filter({"platforms:Linux", "language:C++", "toolset:clang", "files:*.cc or *.cpp"}) filter({"platforms:Linux", "language:C++", "toolset:clang", "files:*.cc or *.cpp"})
buildoptions({ buildoptions({
"-stdlib=libstdc++", "-stdlib=libstdc++",
"-std=c++20", -- clang doesn't respect cppdialect(?)
}) })
filter("platforms:Android-*") filter("platforms:Android-*")

View File

@ -1405,15 +1405,13 @@ void EmulatorWindow::ToggleDisplayConfigDialog() {
void EmulatorWindow::ToggleProfilesConfigDialog() { void EmulatorWindow::ToggleProfilesConfigDialog() {
if (!profile_config_dialog_) { if (!profile_config_dialog_) {
disable_hotkeys_ = true; disable_hotkeys_ = true;
emulator_->kernel_state()->BroadcastNotification(kXNotificationIDSystemUI, emulator_->kernel_state()->BroadcastNotification(kXNotificationSystemUI, 1);
1);
profile_config_dialog_ = profile_config_dialog_ =
std::make_unique<ProfileConfigDialog>(imgui_drawer_.get(), this); std::make_unique<ProfileConfigDialog>(imgui_drawer_.get(), this);
kernel::xam::xam_dialogs_shown_++; kernel::xam::xam_dialogs_shown_++;
} else { } else {
disable_hotkeys_ = false; disable_hotkeys_ = false;
emulator_->kernel_state()->BroadcastNotification(kXNotificationIDSystemUI, emulator_->kernel_state()->BroadcastNotification(kXNotificationSystemUI, 0);
0);
profile_config_dialog_.reset(); profile_config_dialog_.reset();
kernel::xam::xam_dialogs_shown_--; kernel::xam::xam_dialogs_shown_--;
} }
@ -1462,17 +1460,17 @@ void EmulatorWindow::UpdateTitle() {
// Title information, if available // Title information, if available
if (emulator()->is_title_open()) { if (emulator()->is_title_open()) {
sb.AppendFormat(u8" | [{:08X}", emulator()->title_id()); sb.AppendFormat(" | [{:08X}", emulator()->title_id());
auto title_version = emulator()->title_version(); auto title_version = emulator()->title_version();
if (!title_version.empty()) { if (!title_version.empty()) {
sb.Append(u8" v"); sb.Append(" v");
sb.Append(title_version); sb.Append(title_version);
} }
sb.Append(u8"]"); sb.Append("]");
auto title_name = emulator()->title_name(); auto title_name = emulator()->title_name();
if (!title_name.empty()) { if (!title_name.empty()) {
sb.Append(u8" "); sb.Append(" ");
sb.Append(title_name); sb.Append(title_name);
} }
} }
@ -1482,28 +1480,28 @@ void EmulatorWindow::UpdateTitle() {
if (graphics_system) { if (graphics_system) {
auto graphics_name = graphics_system->name(); auto graphics_name = graphics_system->name();
if (!graphics_name.empty()) { if (!graphics_name.empty()) {
sb.Append(u8" <"); sb.Append(" <");
sb.Append(graphics_name); sb.Append(graphics_name);
sb.Append(u8">"); sb.Append(">");
} }
} }
if (Clock::guest_time_scalar() != 1.0) { if (Clock::guest_time_scalar() != 1.0) {
sb.AppendFormat(u8" (@{:.2f}x)", Clock::guest_time_scalar()); sb.AppendFormat(" (@{:.2f}x)", Clock::guest_time_scalar());
} }
if (initializing_shader_storage_) { if (initializing_shader_storage_) {
sb.Append(u8" (Preloading shaders\u2026)"); sb.Append(" (Preloading shaders\u2026)");
} }
patcher::Patcher* patcher = emulator()->patcher(); patcher::Patcher* patcher = emulator()->patcher();
if (patcher && patcher->IsAnyPatchApplied()) { if (patcher && patcher->IsAnyPatchApplied()) {
sb.Append(u8" [Patches Applied]"); sb.Append(" [Patches Applied]");
} }
patcher::PluginLoader* pluginloader = emulator()->plugin_loader(); patcher::PluginLoader* pluginloader = emulator()->plugin_loader();
if (pluginloader && pluginloader->IsAnyPluginLoaded()) { if (pluginloader && pluginloader->IsAnyPluginLoaded()) {
sb.Append(u8" [Plugins Loaded]"); sb.Append(" [Plugins Loaded]");
} }
window_->SetTitle(sb.to_string_view()); window_->SetTitle(sb.to_string_view());
@ -1664,11 +1662,16 @@ EmulatorWindow::ControllerHotKey EmulatorWindow::ProcessControllerHotkey(
xe::threading::Sleep(delay); xe::threading::Sleep(delay);
break; break;
case ButtonFunctions::RunTitle: { case ButtonFunctions::RunTitle: {
if (selected_title_index == -1) selected_title_index++; if (selected_title_index == -1) {
selected_title_index++;
}
if (selected_title_index < recently_launched_titles_.size()) {
app_context().CallInUIThread([this]() { app_context().CallInUIThread([this]() {
RunTitle(recently_launched_titles_[selected_title_index].path_to_file); RunTitle(
recently_launched_titles_[selected_title_index].path_to_file);
}); });
}
} break; } break;
case ButtonFunctions::ClearMemoryPageState: case ButtonFunctions::ClearMemoryPageState:
ToggleGPUSetting(gpu_cvar::ClearMemoryPageState); ToggleGPUSetting(gpu_cvar::ClearMemoryPageState);
@ -1761,8 +1764,9 @@ EmulatorWindow::ControllerHotKey EmulatorWindow::ProcessControllerHotkey(
if ((button_combination.function == ButtonFunctions::IncTitleSelect || if ((button_combination.function == ButtonFunctions::IncTitleSelect ||
button_combination.function == ButtonFunctions::DecTitleSelect) && button_combination.function == ButtonFunctions::DecTitleSelect) &&
recently_launched_titles_.size() > 0) { recently_launched_titles_.size() > 0) {
selected_title_index = std::clamp( selected_title_index =
selected_title_index, 0, (int)recently_launched_titles_.size() - 1); std::clamp(selected_title_index, 0,
static_cast<int32_t>(recently_launched_titles_.size() - 1));
// Must clear dialogs to prevent stacking // Must clear dialogs to prevent stacking
ClearDialogs(); ClearDialogs();
@ -1835,7 +1839,8 @@ void EmulatorWindow::GamepadHotKeys() {
for (uint32_t user_index = 0; user_index < XUserMaxUserCount; for (uint32_t user_index = 0; user_index < XUserMaxUserCount;
++user_index) { ++user_index) {
X_RESULT result = input_sys->GetState(user_index, &state); X_RESULT result = input_sys->GetState(
user_index, X_INPUT_FLAG::X_INPUT_FLAG_GAMEPAD, &state);
// Release the lock before processing the hotkey // Release the lock before processing the hotkey
input_lock.mutex()->unlock(); input_lock.mutex()->unlock();
@ -1870,24 +1875,6 @@ void EmulatorWindow::ToggleGPUSetting(gpu_cvar value) {
} }
} }
// Determine if the Xbox Gamebar is enabled via the Windows registry
bool EmulatorWindow::IsUseNexusForGameBarEnabled() {
#ifdef _WIN32
const LPWSTR reg_path = L"SOFTWARE\\Microsoft\\GameBar";
const LPWSTR key = L"UseNexusForGameBarEnabled";
DWORD value = 0;
DWORD dataSize = sizeof(value);
RegGetValue(HKEY_CURRENT_USER, reg_path, key, RRF_RT_DWORD, nullptr, &value,
&dataSize);
return (bool)value;
#else
return false;
#endif
}
void EmulatorWindow::DisplayHotKeysConfig() { void EmulatorWindow::DisplayHotKeysConfig() {
std::string msg = ""; std::string msg = "";
std::string msg_passthru = ""; std::string msg_passthru = "";

View File

@ -236,7 +236,6 @@ class EmulatorWindow {
bool vibrate = true); bool vibrate = true);
void GamepadHotKeys(); void GamepadHotKeys();
void ToggleGPUSetting(gpu_cvar index); void ToggleGPUSetting(gpu_cvar index);
bool IsUseNexusForGameBarEnabled();
void DisplayHotKeysConfig(); void DisplayHotKeysConfig();
static std::string CanonicalizeFileExtension( static std::string CanonicalizeFileExtension(

View File

@ -176,25 +176,51 @@ class EmulatorApp final : public xe::ui::WindowedApp {
std::vector<std::unique_ptr<T>> CreateAll(const std::string_view name, std::vector<std::unique_ptr<T>> CreateAll(const std::string_view name,
Args... args) { Args... args) {
std::vector<std::unique_ptr<T>> instances; std::vector<std::unique_ptr<T>> instances;
if (!name.empty() && name != "any") {
// "Any" path
if (name.empty() || name == "any") {
for (const auto& creator : creators_) {
if (!creator.is_available()) {
continue;
}
// Skip xinput for "any" and use SDL
if (creator.name.compare("xinput") == 0) {
continue;
}
auto instance = creator.instantiate(std::forward<Args>(args)...);
if (instance) {
instances.emplace_back(std::move(instance));
}
}
return instances;
}
// "Specified" path. Winkey is always added on windows.
if (name != "winkey") {
auto it = std::find_if( auto it = std::find_if(
creators_.cbegin(), creators_.cend(), creators_.cbegin(), creators_.cend(),
[&name](const auto& f) { return name.compare(f.name) == 0; }); [&name](const auto& f) { return name.compare(f.name) == 0; });
if (it != creators_.cend() && (*it).is_available()) { if (it != creators_.cend() && (*it).is_available()) {
auto instance = (*it).instantiate(std::forward<Args>(args)...); auto instance = (*it).instantiate(std::forward<Args>(args)...);
if (instance) { if (instance) {
instances.emplace_back(std::move(instance)); instances.emplace_back(std::move(instance));
} }
} }
} else { }
for (const auto& creator : creators_) {
if (!creator.is_available()) continue; // Always add winkey for passthrough.
auto instance = creator.instantiate(std::forward<Args>(args)...); auto it = std::find_if(
creators_.cbegin(), creators_.cend(),
[&name](const auto& f) { return f.name.compare("winkey") == 0; });
if (it != creators_.cend() && (*it).is_available()) {
auto instance = (*it).instantiate(std::forward<Args>(args)...);
if (instance) { if (instance) {
instances.emplace_back(std::move(instance)); instances.emplace_back(std::move(instance));
} }
} }
}
return instances; return instances;
} }
}; };

View File

@ -13,8 +13,6 @@
#include "xenia/apu/xma_context.h" #include "xenia/apu/xma_context.h"
#include "xenia/base/logging.h" #include "xenia/base/logging.h"
#include <XAudio2.h>
extern "C" { extern "C" {
#if XE_COMPILER_MSVC #if XE_COMPILER_MSVC
#pragma warning(push) #pragma warning(push)
@ -451,7 +449,7 @@ void AudioMediaPlayer::RemovePlaylist(uint32_t handle) {
X_STATUS AudioMediaPlayer::SetVolume(float volume) { X_STATUS AudioMediaPlayer::SetVolume(float volume) {
volume_ = std::min(volume, 1.0f); volume_ = std::min(volume, 1.0f);
std::unique_lock<xe::xe_fast_mutex> guard(driver_mutex_); std::unique_lock<xe_mutex> guard(driver_mutex_);
if (!driver_) { if (!driver_) {
return X_STATUS_UNSUCCESSFUL; return X_STATUS_UNSUCCESSFUL;
} }
@ -483,7 +481,7 @@ void AudioMediaPlayer::SetCaptureCallback(uint32_t callback, uint32_t context,
} }
void AudioMediaPlayer::OnStateChanged() { void AudioMediaPlayer::OnStateChanged() {
kernel_state_->BroadcastNotification(kNotificationXmpStateChanged, kernel_state_->BroadcastNotification(kXNotificationXmpStateChanged,
static_cast<uint32_t>(state_)); static_cast<uint32_t>(state_));
} }
@ -517,7 +515,7 @@ void AudioMediaPlayer::ProcessAudioBuffer(std::vector<float>* buffer) {
bool AudioMediaPlayer::SetupDriver(uint32_t sample_rate, uint32_t channels) { bool AudioMediaPlayer::SetupDriver(uint32_t sample_rate, uint32_t channels) {
DeleteDriver(); DeleteDriver();
std::unique_lock<xe::xe_fast_mutex> guard(driver_mutex_); std::unique_lock<xe_mutex> guard(driver_mutex_);
driver_semaphore_ = xe::threading::Semaphore::Create( driver_semaphore_ = xe::threading::Semaphore::Create(
AudioSystem::kMaximumQueuedFrames, AudioSystem::kMaximumQueuedFrames); AudioSystem::kMaximumQueuedFrames, AudioSystem::kMaximumQueuedFrames);
@ -543,7 +541,7 @@ bool AudioMediaPlayer::SetupDriver(uint32_t sample_rate, uint32_t channels) {
} }
void AudioMediaPlayer::DeleteDriver() { void AudioMediaPlayer::DeleteDriver() {
std::unique_lock<xe::xe_fast_mutex> guard(driver_mutex_); std::unique_lock<xe_mutex> guard(driver_mutex_);
if (driver_) { if (driver_) {
if (driver_semaphore_) { if (driver_semaphore_) {
driver_semaphore_.reset(); driver_semaphore_.reset();

View File

@ -130,7 +130,7 @@ class AudioMediaPlayer {
// really compatible with it. // really compatible with it.
std::unique_ptr<AudioDriver> driver_ = nullptr; std::unique_ptr<AudioDriver> driver_ = nullptr;
std::unique_ptr<xe::threading::Semaphore> driver_semaphore_ = {}; std::unique_ptr<xe::threading::Semaphore> driver_semaphore_ = {};
xe::xe_fast_mutex driver_mutex_ = {}; xe_mutex driver_mutex_ = {};
bool SetupDriver(uint32_t sample_rate, uint32_t channels); bool SetupDriver(uint32_t sample_rate, uint32_t channels);
void DeleteDriver(); void DeleteDriver();

View File

@ -35,7 +35,7 @@ static void _generic_sequential_6_BE_to_interleaved_6_LE(
} }
} }
} }
#if XE_COMPILER_CLANG_CL != 1 #if XE_COMPILER_CLANG_CL != 1 && !XE_PLATFORM_LINUX
// load_be_u32 unavailable on clang-cl // load_be_u32 unavailable on clang-cl
XE_NOINLINE XE_NOINLINE
static void _movbe_sequential_6_BE_to_interleaved_6_LE( static void _movbe_sequential_6_BE_to_interleaved_6_LE(

View File

@ -74,6 +74,15 @@ inline int32_t atomic_inc(volatile int32_t* value) {
inline int32_t atomic_dec(volatile int32_t* value) { inline int32_t atomic_dec(volatile int32_t* value) {
return __sync_sub_and_fetch(value, 1); return __sync_sub_and_fetch(value, 1);
} }
inline int32_t atomic_or(volatile int32_t* value, int32_t nv) {
return __sync_or_and_fetch(value, nv);
}
inline int32_t atomic_and(volatile int32_t* value, int32_t nv) {
return __sync_and_and_fetch(value, nv);
}
inline int32_t atomic_xor(volatile int32_t* value, int32_t nv) {
return __sync_xor_and_fetch(value, nv);
}
inline int32_t atomic_exchange(int32_t new_value, volatile int32_t* value) { inline int32_t atomic_exchange(int32_t new_value, volatile int32_t* value) {
return __sync_val_compare_and_swap(value, *value, new_value); return __sync_val_compare_and_swap(value, *value, new_value);

View File

@ -91,6 +91,14 @@ struct NtSystemClock {
return sys_time{cdp.time_since_epoch()}; return sys_time{cdp.time_since_epoch()};
} }
template <Domain domain_fresh_ = domain_>
static constexpr std::enable_if_t<
domain_fresh_ == Domain::Host,
std::chrono::local_time<std::chrono::system_clock::duration>>
to_local(const time_point& tp) {
return std::chrono::current_zone()->to_local(to_sys(tp));
}
template <Domain domain_fresh_ = domain_> template <Domain domain_fresh_ = domain_>
static constexpr std::enable_if_t<domain_fresh_ == Domain::Host, time_point> static constexpr std::enable_if_t<domain_fresh_ == Domain::Host, time_point>
from_sys(const std::chrono::system_clock::time_point& tp) { from_sys(const std::chrono::system_clock::time_point& tp) {

View File

@ -23,7 +23,7 @@ extern "C" int main(int argc, char** argv) {
} }
// Initialize logging. Needs parsed cvars. // Initialize logging. Needs parsed cvars.
xe::InitializeLogging(entry_info.name); // xe::InitializeLogging(entry_info.name);
std::vector<std::string> args; std::vector<std::string> args;
for (int n = 0; n < argc; n++) { for (int n = 0; n < argc; n++) {
@ -32,7 +32,7 @@ extern "C" int main(int argc, char** argv) {
int result = entry_info.entry_point(args); int result = entry_info.entry_point(args);
xe::ShutdownLogging(); // xe::ShutdownLogging();
return result; return result;
} }

View File

@ -110,24 +110,24 @@ std::string EscapeBasicString(const std::string_view view) {
for (auto it = begin; it != end; ++it) { for (auto it = begin; it != end; ++it) {
auto c = *it; auto c = *it;
if (c == '\b') { if (c == '\b') {
result += u8"\\b"; result += "\\b";
} else if (c == '\t') { } else if (c == '\t') {
result += u8"\\t"; result += "\\t";
} else if (c == '\n') { } else if (c == '\n') {
result += u8"\\n"; result += "\\n";
} else if (c == '\f') { } else if (c == '\f') {
result += u8"\\f"; result += "\\f";
} else if (c == '\r') { } else if (c == '\r') {
result += u8"\\r"; result += "\\r";
} else if (c == '"') { } else if (c == '"') {
result += u8"\\\""; result += "\\\"";
} else if (c == '\\') { } else if (c == '\\') {
result += u8"\\\\"; result += "\\\\";
} else if (c < 0x20 || c == 0x7F) { } else if (c < 0x20 || c == 0x7F) {
if (c <= 0xFFFF) { if (c <= 0xFFFF) {
result += fmt::format(u8"\\u{:04X}", c); result += fmt::format("\\u{:04X}", c);
} else { } else {
result += fmt::format(u8"\\u{:08X}", c); result += fmt::format("\\u{:08X}", c);
} }
} else { } else {
utfcpp::append(static_cast<char32_t>(c), result); utfcpp::append(static_cast<char32_t>(c), result);
@ -150,30 +150,30 @@ std::string EscapeMultilineBasicString(const std::string_view view) {
} }
for (int i = 0; i < quote_run; ++i) { for (int i = 0; i < quote_run; ++i) {
if ((i % 3) == 2) { if ((i % 3) == 2) {
result += u8"\\"; result += "\\";
} }
result += u8"\""; result += "\"";
} }
quote_run = 0; quote_run = 0;
} }
if (c == '\b') { if (c == '\b') {
result += u8"\\b"; result += "\\b";
} else if (c == '\t' || c == '\n') { } else if (c == '\t' || c == '\n') {
result += c; result += c;
} else if (c == '\f') { } else if (c == '\f') {
result += u8"\\f"; result += "\\f";
} else if (c == '\r') { } else if (c == '\r') {
// Silently drop \r. // Silently drop \r.
// result += c; // result += c;
} else if (c == '"') { } else if (c == '"') {
quote_run = 1; quote_run = 1;
} else if (c == '\\') { } else if (c == '\\') {
result += u8"\\\\"; result += "\\\\";
} else if (c < 0x20 || c == 0x7F) { } else if (c < 0x20 || c == 0x7F) {
if (c <= 0xFFFF) { if (c <= 0xFFFF) {
result += fmt::format(u8"\\u{:04X}", c); result += fmt::format("\\u{:04X}", c);
} else { } else {
result += fmt::format(u8"\\u{:08X}", c); result += fmt::format("\\u{:08X}", c);
} }
} else { } else {
utfcpp::append(static_cast<char32_t>(c), result); utfcpp::append(static_cast<char32_t>(c), result);
@ -181,9 +181,9 @@ std::string EscapeMultilineBasicString(const std::string_view view) {
} }
for (int i = 0; i < quote_run; ++i) { for (int i = 0; i < quote_run; ++i) {
if ((i % 3) == 2) { if ((i % 3) == 2) {
result += u8"\\"; result += "\\";
} }
result += u8"\""; result += "\"";
} }
return result; return result;
} }
@ -207,11 +207,11 @@ std::string EscapeString(const std::string_view view) {
} else { } else {
// multi line // multi line
if (xe::utf8::find_any_of(view, escape_chars) == std::string_view::npos && if (xe::utf8::find_any_of(view, escape_chars) == std::string_view::npos &&
xe::utf8::find_first_of(view, u8"'''") == std::string_view::npos) { xe::utf8::find_first_of(view, "'''") == std::string_view::npos) {
return "'''\n" + std::string(view) + "'''"; return "'''\n" + std::string(view) + "'''";
} else { } else {
return u8"\"\"\"\n" + toml_internal::EscapeMultilineBasicString(view) + return "\"\"\"\n" + toml_internal::EscapeMultilineBasicString(view) +
u8"\"\"\""; "\"\"\"";
} }
} }
} }

View File

@ -99,10 +99,11 @@ void AppendLogLine(LogLevel log_level, const char prefix_char, size_t written);
// might as well be noalias // might as well be noalias
template <typename... Args> template <typename... Args>
XE_NOALIAS XE_NOINLINE XE_COLD static void AppendLogLineFormat_Impl( XE_NOALIAS XE_NOINLINE XE_COLD static void AppendLogLineFormat_Impl(
LogLevel log_level, const char prefix_char, const char* format, LogLevel log_level, const char prefix_char, std::string_view format,
const Args&... args) { const Args&... args) noexcept {
auto target = internal::GetThreadBuffer(); auto target = internal::GetThreadBuffer();
auto result = fmt::format_to_n(target.first, target.second, format, args...); auto result = fmt::format_to_n(target.first, target.second,
fmt::runtime(format), args...);
internal::AppendLogLine(log_level, prefix_char, result.size); internal::AppendLogLine(log_level, prefix_char, result.size);
} }
@ -113,8 +114,8 @@ template <typename... Args>
XE_FORCEINLINE static void AppendLogLineFormat(uint32_t log_src_mask, XE_FORCEINLINE static void AppendLogLineFormat(uint32_t log_src_mask,
LogLevel log_level, LogLevel log_level,
const char prefix_char, const char prefix_char,
const char* format, std::string_view format,
const Args&... args) { const Args&... args) noexcept {
if (!internal::ShouldLog(log_level, log_src_mask)) { if (!internal::ShouldLog(log_level, log_src_mask)) {
return; return;
} }
@ -143,7 +144,8 @@ struct LoggerBatch {
LoggerBatch() { reset(); } LoggerBatch() { reset(); }
template <size_t fmtlen, typename... Ts> template <size_t fmtlen, typename... Ts>
void operator()(const char (&fmt)[fmtlen], Ts&&... args) { void operator()(const char (&fmt)[fmtlen], Ts&&... args) {
auto tmpres = fmt::format_to_n(thrd_buf, thrd_buf_rem, fmt, args...); auto tmpres =
fmt::format_to_n(thrd_buf, thrd_buf_rem, fmt::runtime(fmt), args...);
thrd_buf_rem -= tmpres.size; thrd_buf_rem -= tmpres.size;
thrd_buf = tmpres.out; thrd_buf = tmpres.out;
total_size += tmpres.size; total_size += tmpres.size;
@ -164,55 +166,55 @@ struct LoggerBatch {
#if XE_OPTION_ENABLE_LOGGING #if XE_OPTION_ENABLE_LOGGING
template <typename... Args> template <typename... Args>
XE_COLD void XELOGE(const char* format, const Args&... args) { XE_COLD void XELOGE(std::string_view format, const Args&... args) {
xe::logging::AppendLogLineFormat(xe::LogSrc::Uncategorized, xe::logging::AppendLogLineFormat(xe::LogSrc::Uncategorized,
xe::LogLevel::Error, '!', format, args...); xe::LogLevel::Error, '!', format, args...);
} }
template <typename... Args> template <typename... Args>
XE_COLD void XELOGW(const char* format, const Args&... args) { XE_COLD void XELOGW(std::string_view format, const Args&... args) {
xe::logging::AppendLogLineFormat(xe::LogSrc::Uncategorized, xe::logging::AppendLogLineFormat(xe::LogSrc::Uncategorized,
xe::LogLevel::Warning, 'w', format, args...); xe::LogLevel::Warning, 'w', format, args...);
} }
template <typename... Args> template <typename... Args>
void XELOGI(const char* format, const Args&... args) { void XELOGI(std::string_view format, const Args&... args) {
xe::logging::AppendLogLineFormat(xe::LogSrc::Uncategorized, xe::logging::AppendLogLineFormat(xe::LogSrc::Uncategorized,
xe::LogLevel::Info, 'i', format, args...); xe::LogLevel::Info, 'i', format, args...);
} }
template <typename... Args> template <typename... Args>
void XELOGD(const char* format, const Args&... args) { void XELOGD(std::string_view format, const Args&... args) {
xe::logging::AppendLogLineFormat(xe::LogSrc::Uncategorized, xe::logging::AppendLogLineFormat(xe::LogSrc::Uncategorized,
xe::LogLevel::Debug, 'd', format, args...); xe::LogLevel::Debug, 'd', format, args...);
} }
template <typename... Args> template <typename... Args>
void XELOGCPU(const char* format, const Args&... args) { void XELOGCPU(std::string_view format, const Args&... args) {
xe::logging::AppendLogLineFormat(xe::LogSrc::Cpu, xe::LogLevel::Info, 'C', xe::logging::AppendLogLineFormat(xe::LogSrc::Cpu, xe::LogLevel::Info, 'C',
format, args...); format, args...);
} }
template <typename... Args> template <typename... Args>
void XELOGAPU(const char* format, const Args&... args) { void XELOGAPU(std::string_view format, const Args&... args) {
xe::logging::AppendLogLineFormat(xe::LogSrc::Apu, xe::LogLevel::Debug, 'A', xe::logging::AppendLogLineFormat(xe::LogSrc::Apu, xe::LogLevel::Debug, 'A',
format, args...); format, args...);
} }
template <typename... Args> template <typename... Args>
void XELOGGPU(const char* format, const Args&... args) { void XELOGGPU(std::string_view format, const Args&... args) {
xe::logging::AppendLogLineFormat(xe::LogSrc::Uncategorized, xe::logging::AppendLogLineFormat(xe::LogSrc::Uncategorized,
xe::LogLevel::Debug, 'G', format, args...); xe::LogLevel::Debug, 'G', format, args...);
} }
template <typename... Args> template <typename... Args>
void XELOGKERNEL(const char* format, const Args&... args) { void XELOGKERNEL(std::string_view format, const Args&... args) {
xe::logging::AppendLogLineFormat(xe::LogSrc::Kernel, xe::LogLevel::Info, 'K', xe::logging::AppendLogLineFormat(xe::LogSrc::Kernel, xe::LogLevel::Info, 'K',
format, args...); format, args...);
} }
template <typename... Args> template <typename... Args>
void XELOGFS(const char* format, const Args&... args) { void XELOGFS(std::string_view format, const Args&... args) {
xe::logging::AppendLogLineFormat(xe::LogSrc::Uncategorized, xe::logging::AppendLogLineFormat(xe::LogSrc::Uncategorized,
xe::LogLevel::Info, 'F', format, args...); xe::LogLevel::Info, 'F', format, args...);
} }

View File

@ -11,6 +11,7 @@
#define XENIA_BASE_MATH_H_ #define XENIA_BASE_MATH_H_
#include <algorithm> #include <algorithm>
#include <climits>
#include <cmath> #include <cmath>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
@ -330,7 +331,7 @@ inline T rotate_right(T v, uint8_t sh) {
constexpr unsigned char SHIFT_MASK = (CHAR_BIT * sizeof(T)) - 1; constexpr unsigned char SHIFT_MASK = (CHAR_BIT * sizeof(T)) - 1;
uint8_t rshr = sh & SHIFT_MASK; uint8_t rshr = sh & SHIFT_MASK;
uint8_t lshl = static_cast<uint8_t>(-static_cast<int8_t>(sh)) & SHIFT_MASK; uint8_t lshl = static_cast<uint8_t>(-static_cast<int8_t>(sh)) & SHIFT_MASK;
return (n >> rshr) | (n << lshl); return (v >> rshr) | (v << lshl);
} }
#if XE_PLATFORM_WIN32 #if XE_PLATFORM_WIN32
template <> template <>

View File

@ -193,7 +193,7 @@ std::unique_ptr<Socket> Socket::Connect(std::string hostname, uint16_t port) {
InitializeWinsock(); InitializeWinsock();
auto socket = std::make_unique<Win32Socket>(); auto socket = std::make_unique<Win32Socket>();
if (!socket->Connect(std::move(hostname), port)) { if (!socket->Connect(hostname, port)) {
return nullptr; return nullptr;
} }
return std::unique_ptr<Socket>(socket.release()); return std::unique_ptr<Socket>(socket.release());

View File

@ -35,7 +35,7 @@ class StringBuffer {
template <typename... Args> template <typename... Args>
void AppendFormat(const char* format, const Args&... args) { void AppendFormat(const char* format, const Args&... args) {
auto s = fmt::format(format, args...); auto s = fmt::format(fmt::runtime(format), args...);
Append(s.c_str()); Append(s.c_str());
} }

View File

@ -29,6 +29,9 @@ void LaunchFileExplorer(const std::filesystem::path& path);
bool SetProcessPriorityClass(const uint32_t priority_class); bool SetProcessPriorityClass(const uint32_t priority_class);
// Determine if the Xbox Gamebar is enabled via the Windows registry
bool IsUseNexusForGameBarEnabled();
enum class SimpleMessageBoxType { enum class SimpleMessageBoxType {
Help, Help,
Warning, Warning,

View File

@ -296,4 +296,5 @@ void ShowSimpleMessageBox(SimpleMessageBoxType type, std::string_view message) {
bool SetProcessPriorityClass(const uint32_t priority_class) { return true; } bool SetProcessPriorityClass(const uint32_t priority_class) { return true; }
bool IsUseNexusForGameBarEnabled() { return false; }
} // namespace xe } // namespace xe

View File

@ -69,4 +69,5 @@ void ShowSimpleMessageBox(SimpleMessageBoxType type, std::string_view message) {
bool SetProcessPriorityClass(const uint32_t priority_class) { return true; } bool SetProcessPriorityClass(const uint32_t priority_class) { return true; }
bool IsUseNexusForGameBarEnabled() { return false; }
} // namespace xe } // namespace xe

View File

@ -64,4 +64,17 @@ bool SetProcessPriorityClass(const uint32_t priority_class) {
xeniaToWindowsPriorityClassMapping[priority_class]); xeniaToWindowsPriorityClassMapping[priority_class]);
} }
bool IsUseNexusForGameBarEnabled() {
const LPCWSTR reg_path = L"SOFTWARE\\Microsoft\\GameBar";
const LPCWSTR key = L"UseNexusForGameBarEnabled";
DWORD value = 0;
DWORD dataSize = sizeof(value);
RegGetValue(HKEY_CURRENT_USER, reg_path, key, RRF_RT_DWORD, nullptr, &value,
&dataSize);
return static_cast<bool>(value);
}
} // namespace xe } // namespace xe

View File

@ -407,7 +407,7 @@ TEST_CASE("Wait on Multiple Events", "[event]") {
std::array<char, 8> order = {0}; std::array<char, 8> order = {0};
std::atomic_uint index(0); std::atomic_uint index(0);
auto sign_in = [&order, &index](uint32_t id) { auto sign_in = [&order, &index](uint32_t id) {
auto i = index.fetch_add(1, std::memory_order::memory_order_relaxed); auto i = index.fetch_add(1, std::memory_order_relaxed);
order[i] = static_cast<char>('0' + id); order[i] = static_cast<char>('0' + id);
}; };
@ -1071,8 +1071,8 @@ TEST_CASE("Test Thread QueueUserCallback", "[thread]") {
int is_modified; int is_modified;
int has_finished; int has_finished;
auto callback = [&is_modified, &order] { auto callback = [&is_modified, &order] {
is_modified = std::atomic_fetch_add_explicit( is_modified =
&order, 1, std::memory_order::memory_order_relaxed); std::atomic_fetch_add_explicit(&order, 1, std::memory_order_relaxed);
}; };
// Without alertable // Without alertable
@ -1084,8 +1084,8 @@ TEST_CASE("Test Thread QueueUserCallback", "[thread]") {
order++; // 1 order++; // 1
Sleep(90ms); Sleep(90ms);
order++; // 2 order++; // 2
has_finished = std::atomic_fetch_add_explicit( has_finished =
&order, 1, std::memory_order::memory_order_relaxed); std::atomic_fetch_add_explicit(&order, 1, std::memory_order_relaxed);
}); });
REQUIRE(!spin_wait_for(50ms, [&] { return order == 2; })); REQUIRE(!spin_wait_for(50ms, [&] { return order == 2; }));
REQUIRE(is_modified == -1); REQUIRE(is_modified == -1);
@ -1104,8 +1104,8 @@ TEST_CASE("Test Thread QueueUserCallback", "[thread]") {
order++; // 1 order++; // 1
AlertableSleep(90ms); AlertableSleep(90ms);
order++; // 3 order++; // 3
has_finished = std::atomic_fetch_add_explicit( has_finished =
&order, 1, std::memory_order::memory_order_relaxed); std::atomic_fetch_add_explicit(&order, 1, std::memory_order_relaxed);
}); });
REQUIRE(!spin_wait_for(50ms, [&] { return order == 2; })); REQUIRE(!spin_wait_for(50ms, [&] { return order == 2; }));
REQUIRE(is_modified == -1); REQUIRE(is_modified == -1);
@ -1120,8 +1120,8 @@ TEST_CASE("Test Thread QueueUserCallback", "[thread]") {
is_modified = -1; is_modified = -1;
has_finished = -1; has_finished = -1;
thread = Thread::Create(params, [&is_modified, &has_finished, &order] { thread = Thread::Create(params, [&is_modified, &has_finished, &order] {
is_modified = std::atomic_fetch_add_explicit( is_modified =
&order, 1, std::memory_order::memory_order_relaxed); std::atomic_fetch_add_explicit(&order, 1, std::memory_order_relaxed);
// Using Alertable so callback is registered // Using Alertable so callback is registered
order++; // 2 order++; // 2
AlertableSleep(1s); AlertableSleep(1s);

View File

@ -37,121 +37,121 @@ namespace examples {
const size_t kDanishCount = 1; const size_t kDanishCount = 1;
const char* kDanishValues[kDanishCount] = { const char* kDanishValues[kDanishCount] = {
u8"Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Wolther " "Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Wolther "
u8"spillede på xylofon.", "spillede på xylofon.",
}; };
#define TEST_LANGUAGE_EXAMPLES_Danish(func, results) \ #define TEST_LANGUAGE_EXAMPLES_Danish(func, results) \
TEST_EXAMPLES_1(func, Danish, results) TEST_EXAMPLES_1(func, Danish, results)
const size_t kGermanCount = 3; const size_t kGermanCount = 3;
const char* kGermanValues[kGermanCount] = { const char* kGermanValues[kGermanCount] = {
u8"Falsches Üben von Xylophonmusik quält jeden größeren Zwerg", "Falsches Üben von Xylophonmusik quält jeden größeren Zwerg",
u8"Zwölf Boxkämpfer jagten Eva quer über den Sylter Deich", "Zwölf Boxkämpfer jagten Eva quer über den Sylter Deich",
u8"Heizölrückstoßabdämpfung", "Heizölrückstoßabdämpfung",
}; };
#define TEST_LANGUAGE_EXAMPLES_German(func, results) \ #define TEST_LANGUAGE_EXAMPLES_German(func, results) \
TEST_EXAMPLES_2(func, German, results) TEST_EXAMPLES_2(func, German, results)
const size_t kGreekCount = 2; const size_t kGreekCount = 2;
const char* kGreekValues[kGreekCount] = { const char* kGreekValues[kGreekCount] = {
u8"Γαζέες καὶ μυρτιὲς δὲν θὰ βρῶ πιὰ στὸ χρυσαφὶ ξέφωτο", "Γαζέες καὶ μυρτιὲς δὲν θὰ βρῶ πιὰ στὸ χρυσαφὶ ξέφωτο",
u8"Ξεσκεπάζω τὴν ψυχοφθόρα βδελυγμία", "Ξεσκεπάζω τὴν ψυχοφθόρα βδελυγμία",
}; };
#define TEST_LANGUAGE_EXAMPLES_Greek(func, results) \ #define TEST_LANGUAGE_EXAMPLES_Greek(func, results) \
TEST_EXAMPLES_2(func, Greek, results) TEST_EXAMPLES_2(func, Greek, results)
const size_t kEnglishCount = 1; const size_t kEnglishCount = 1;
const char* kEnglishValues[kEnglishCount] = { const char* kEnglishValues[kEnglishCount] = {
u8"The quick brown fox jumps over the lazy dog", "The quick brown fox jumps over the lazy dog",
}; };
#define TEST_LANGUAGE_EXAMPLES_English(func, results) \ #define TEST_LANGUAGE_EXAMPLES_English(func, results) \
TEST_EXAMPLES_1(func, English, results) TEST_EXAMPLES_1(func, English, results)
const size_t kSpanishCount = 1; const size_t kSpanishCount = 1;
const char* kSpanishValues[kSpanishCount] = { const char* kSpanishValues[kSpanishCount] = {
u8"El pingüino Wenceslao hizo kilómetros bajo exhaustiva lluvia y frío, " "El pingüino Wenceslao hizo kilómetros bajo exhaustiva lluvia y frío, "
u8"añoraba a su querido cachorro.", "añoraba a su querido cachorro.",
}; };
#define TEST_LANGUAGE_EXAMPLES_Spanish(func, results) \ #define TEST_LANGUAGE_EXAMPLES_Spanish(func, results) \
TEST_EXAMPLES_1(func, Spanish, results) TEST_EXAMPLES_1(func, Spanish, results)
const size_t kFrenchCount = 3; const size_t kFrenchCount = 3;
const char* kFrenchValues[kFrenchCount] = { const char* kFrenchValues[kFrenchCount] = {
u8"Portez ce vieux whisky au juge blond qui fume sur son île intérieure, à " "Portez ce vieux whisky au juge blond qui fume sur son île intérieure, à "
u8"côté de l'alcôve ovoïde, où les bûches se consument dans l'âtre, ce qui " "côté de l'alcôve ovoïde, où les bûches se consument dans l'âtre, ce qui "
u8"lui permet de penser à la cænogenèse de l'être dont il est question " "lui permet de penser à la cænogenèse de l'être dont il est question "
u8"dans la cause ambiguë entendue à Moÿ, dans un capharnaüm qui, " "dans la cause ambiguë entendue à Moÿ, dans un capharnaüm qui, "
u8"pense-t-il, diminue çà et là la qualité de son œuvre.", "pense-t-il, diminue çà et là la qualité de son œuvre.",
u8"l'île exiguë\n" "l'île exiguë\n"
u8"Où l'obèse jury mûr\n" "Où l'obèse jury mûr\n"
u8"Fête l'haï volapük,\n" "Fête l'haï volapük,\n"
u8"Âne ex aéquo au whist,\n" "Âne ex aéquo au whist,\n"
u8"Ôtez ce vœu déçu.", "Ôtez ce vœu déçu.",
u8"Le cœur déçu mais l'âme plutôt naïve, Louÿs rêva de crapaüter en canoë " "Le cœur déçu mais l'âme plutôt naïve, Louÿs rêva de crapaüter en canoë "
u8"au delà des îles, près du mälström où brûlent les novæ.", "au delà des îles, près du mälström où brûlent les novæ.",
}; };
#define TEST_LANGUAGE_EXAMPLES_French(func, results) \ #define TEST_LANGUAGE_EXAMPLES_French(func, results) \
TEST_EXAMPLES_3(func, French, results) TEST_EXAMPLES_3(func, French, results)
const size_t kIrishGaelicCount = 1; const size_t kIrishGaelicCount = 1;
const char* kIrishGaelicValues[kIrishGaelicCount] = { const char* kIrishGaelicValues[kIrishGaelicCount] = {
u8"D'fhuascail Íosa, Úrmhac na hÓighe Beannaithe, pór Éava agus Ádhaimh", "D'fhuascail Íosa, Úrmhac na hÓighe Beannaithe, pór Éava agus Ádhaimh",
}; };
#define TEST_LANGUAGE_EXAMPLES_IrishGaelic(func, results) \ #define TEST_LANGUAGE_EXAMPLES_IrishGaelic(func, results) \
TEST_EXAMPLES_1(func, IrishGaelic, results) TEST_EXAMPLES_1(func, IrishGaelic, results)
const size_t kHungarianCount = 1; const size_t kHungarianCount = 1;
const char* kHungarianValues[kHungarianCount] = { const char* kHungarianValues[kHungarianCount] = {
u8"Árvíztűrő tükörfúrógép", "Árvíztűrő tükörfúrógép",
}; };
#define TEST_LANGUAGE_EXAMPLES_Hungarian(func, results) \ #define TEST_LANGUAGE_EXAMPLES_Hungarian(func, results) \
TEST_EXAMPLES_1(func, Hungarian, results) TEST_EXAMPLES_1(func, Hungarian, results)
const size_t kIcelandicCount = 2; const size_t kIcelandicCount = 2;
const char* kIcelandicValues[kIcelandicCount] = { const char* kIcelandicValues[kIcelandicCount] = {
u8"Kæmi ný öxi hér ykist þjófum nú bæði víl og ádrepa", "Kæmi ný öxi hér ykist þjófum nú bæði víl og ádrepa",
u8"Sævör grét áðan því úlpan var ónýt", "Sævör grét áðan því úlpan var ónýt",
}; };
#define TEST_LANGUAGE_EXAMPLES_Icelandic(func, results) \ #define TEST_LANGUAGE_EXAMPLES_Icelandic(func, results) \
TEST_EXAMPLES_2(func, Icelandic, results) TEST_EXAMPLES_2(func, Icelandic, results)
const size_t kJapaneseCount = 2; const size_t kJapaneseCount = 2;
const char* kJapaneseValues[kJapaneseCount] = { const char* kJapaneseValues[kJapaneseCount] = {
u8"いろはにほへとちりぬるを\n" "いろはにほへとちりぬるを\n"
u8"わかよたれそつねならむ\n" "わかよたれそつねならむ\n"
u8"うゐのおくやまけふこえて\n" "うゐのおくやまけふこえて\n"
u8"あさきゆめみしゑひもせす\n", "あさきゆめみしゑひもせす\n",
u8"イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム\n" "イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム\n"
u8"ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン", "ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン",
}; };
#define TEST_LANGUAGE_EXAMPLES_Japanese(func, results) \ #define TEST_LANGUAGE_EXAMPLES_Japanese(func, results) \
TEST_EXAMPLES_2(func, Japanese, results) TEST_EXAMPLES_2(func, Japanese, results)
const size_t kHebrewCount = 1; const size_t kHebrewCount = 1;
const char* kHebrewValues[kHebrewCount] = { const char* kHebrewValues[kHebrewCount] = {
u8"? דג סקרן שט בים מאוכזב ולפתע מצא לו חברה איך הקליטה", "? דג סקרן שט בים מאוכזב ולפתע מצא לו חברה איך הקליטה",
}; };
#define TEST_LANGUAGE_EXAMPLES_Hebrew(func, results) \ #define TEST_LANGUAGE_EXAMPLES_Hebrew(func, results) \
TEST_EXAMPLES_1(func, Hebrew, results) TEST_EXAMPLES_1(func, Hebrew, results)
const size_t kPolishCount = 1; const size_t kPolishCount = 1;
const char* kPolishValues[kPolishCount] = { const char* kPolishValues[kPolishCount] = {
u8"Pchnąć w tę łódź jeża lub ośm skrzyń fig", "Pchnąć w tę łódź jeża lub ośm skrzyń fig",
}; };
#define TEST_LANGUAGE_EXAMPLES_Polish(func, results) \ #define TEST_LANGUAGE_EXAMPLES_Polish(func, results) \
TEST_EXAMPLES_1(func, Polish, results) TEST_EXAMPLES_1(func, Polish, results)
const size_t kRussianCount = 2; const size_t kRussianCount = 2;
const char* kRussianValues[kRussianCount] = { const char* kRussianValues[kRussianCount] = {
u8"В чащах юга жил бы цитрус? Да, но фальшивый экземпляр!", "В чащах юга жил бы цитрус? Да, но фальшивый экземпляр!",
u8"Съешь же ещё этих мягких французских булок да выпей чаю", "Съешь же ещё этих мягких французских булок да выпей чаю",
}; };
#define TEST_LANGUAGE_EXAMPLES_Russian(func, results) \ #define TEST_LANGUAGE_EXAMPLES_Russian(func, results) \
TEST_EXAMPLES_2(func, Russian, results) TEST_EXAMPLES_2(func, Russian, results)
const size_t kTurkishCount = 1; const size_t kTurkishCount = 1;
const char* kTurkishValues[kTurkishCount] = { const char* kTurkishValues[kTurkishCount] = {
u8"Pijamalı hasta, yağız şoföre çabucak güvendi.", "Pijamalı hasta, yağız şoföre çabucak güvendi.",
}; };
#define TEST_LANGUAGE_EXAMPLES_Turkish(func, results) \ #define TEST_LANGUAGE_EXAMPLES_Turkish(func, results) \
TEST_EXAMPLES_1(func, Turkish, results) TEST_EXAMPLES_1(func, Turkish, results)
@ -229,54 +229,54 @@ TEST_CASE("UTF-8 Split", "[utf8]") {
std::vector<std::string_view> parts; std::vector<std::string_view> parts;
// Danish // Danish
parts = utf8::split(examples::kDanishValues[0], u8"æcå"); parts = utf8::split(examples::kDanishValues[0], "æcå");
REQUIRE(parts.size() == 4); REQUIRE(parts.size() == 4);
REQUIRE(parts[0] == u8"Quizdeltagerne spiste jordb"); REQUIRE(parts[0] == "Quizdeltagerne spiste jordb");
REQUIRE(parts[1] == u8"r med fløde, mens "); REQUIRE(parts[1] == "r med fløde, mens ");
REQUIRE(parts[2] == u8"irkusklovnen Wolther spillede p"); REQUIRE(parts[2] == "irkusklovnen Wolther spillede p");
REQUIRE(parts[3] == u8" xylofon."); REQUIRE(parts[3] == " xylofon.");
// German // German
parts = utf8::split(examples::kGermanValues[0], u8"ßS"); parts = utf8::split(examples::kGermanValues[0], "ßS");
REQUIRE(parts.size() == 2); REQUIRE(parts.size() == 2);
REQUIRE(parts[0] == u8"Falsches Üben von Xylophonmusik quält jeden grö"); REQUIRE(parts[0] == "Falsches Üben von Xylophonmusik quält jeden grö");
REQUIRE(parts[1] == u8"eren Zwerg"); REQUIRE(parts[1] == "eren Zwerg");
parts = utf8::split(examples::kGermanValues[1], u8"ßS"); parts = utf8::split(examples::kGermanValues[1], "ßS");
REQUIRE(parts.size() == 2); REQUIRE(parts.size() == 2);
REQUIRE(parts[0] == u8"Zwölf Boxkämpfer jagten Eva quer über den "); REQUIRE(parts[0] == "Zwölf Boxkämpfer jagten Eva quer über den ");
REQUIRE(parts[1] == u8"ylter Deich"); REQUIRE(parts[1] == "ylter Deich");
parts = utf8::split(examples::kGermanValues[2], u8"ßS"); parts = utf8::split(examples::kGermanValues[2], "ßS");
REQUIRE(parts.size() == 2); REQUIRE(parts.size() == 2);
REQUIRE(parts[0] == u8"Heizölrücksto"); REQUIRE(parts[0] == "Heizölrücksto");
REQUIRE(parts[1] == u8"abdämpfung"); REQUIRE(parts[1] == "abdämpfung");
// Greek // Greek
parts = utf8::split(examples::kGreekValues[0], u8"πφ"); parts = utf8::split(examples::kGreekValues[0], "πφ");
REQUIRE(parts.size() == 4); REQUIRE(parts.size() == 4);
REQUIRE(parts[0] == u8"Γαζέες καὶ μυρτιὲς δὲν θὰ βρῶ "); REQUIRE(parts[0] == "Γαζέες καὶ μυρτιὲς δὲν θὰ βρῶ ");
REQUIRE(parts[1] == u8"ιὰ στὸ χρυσα"); REQUIRE(parts[1] == "ιὰ στὸ χρυσα");
REQUIRE(parts[2] == u8"ὶ ξέ"); REQUIRE(parts[2] == "ὶ ξέ");
REQUIRE(parts[3] == u8"ωτο"); REQUIRE(parts[3] == "ωτο");
parts = utf8::split(examples::kGreekValues[1], u8"πφ"); parts = utf8::split(examples::kGreekValues[1], "πφ");
REQUIRE(parts.size() == 3); REQUIRE(parts.size() == 3);
REQUIRE(parts[0] == u8"Ξεσκε"); REQUIRE(parts[0] == "Ξεσκε");
REQUIRE(parts[1] == u8"άζω τὴν ψυχο"); REQUIRE(parts[1] == "άζω τὴν ψυχο");
REQUIRE(parts[2] == u8"θόρα βδελυγμία"); REQUIRE(parts[2] == "θόρα βδελυγμία");
// English // English
parts = utf8::split(examples::kEnglishValues[0], "xy"); parts = utf8::split(examples::kEnglishValues[0], "xy");
REQUIRE(parts.size() == 3); REQUIRE(parts.size() == 3);
REQUIRE(parts[0] == u8"The quick brown fo"); REQUIRE(parts[0] == "The quick brown fo");
REQUIRE(parts[1] == u8" jumps over the laz"); REQUIRE(parts[1] == " jumps over the laz");
REQUIRE(parts[2] == u8" dog"); REQUIRE(parts[2] == " dog");
// Spanish // Spanish
parts = utf8::split(examples::kSpanishValues[0], u8"ójd"); parts = utf8::split(examples::kSpanishValues[0], "ójd");
REQUIRE(parts.size() == 4); REQUIRE(parts.size() == 4);
REQUIRE(parts[0] == u8"El pingüino Wenceslao hizo kil"); REQUIRE(parts[0] == "El pingüino Wenceslao hizo kil");
REQUIRE(parts[1] == u8"metros ba"); REQUIRE(parts[1] == "metros ba");
REQUIRE(parts[2] == u8"o exhaustiva lluvia y frío, añoraba a su queri"); REQUIRE(parts[2] == "o exhaustiva lluvia y frío, añoraba a su queri");
REQUIRE(parts[3] == u8"o cachorro."); REQUIRE(parts[3] == "o cachorro.");
// TODO(gibbed): French // TODO(gibbed): French
// TODO(gibbed): Irish Gaelic // TODO(gibbed): Irish Gaelic
@ -291,18 +291,18 @@ TEST_CASE("UTF-8 Split", "[utf8]") {
} }
TEST_CASE("UTF-8 Equal Z", "[utf8]") { TEST_CASE("UTF-8 Equal Z", "[utf8]") {
REQUIRE(utf8::equal_z(u8"foo", u8"foo\0")); REQUIRE(utf8::equal_z("foo", "foo\0"));
REQUIRE_FALSE(utf8::equal_z(u8"bar", u8"baz\0")); REQUIRE_FALSE(utf8::equal_z("bar", "baz\0"));
} }
TEST_CASE("UTF-8 Equal Case", "[utf8]") { TEST_CASE("UTF-8 Equal Case", "[utf8]") {
REQUIRE(utf8::equal_case(u8"foo", u8"foo\0")); REQUIRE(utf8::equal_case("foo", "foo\0"));
REQUIRE_FALSE(utf8::equal_case(u8"bar", u8"baz\0")); REQUIRE_FALSE(utf8::equal_case("bar", "baz\0"));
} }
TEST_CASE("UTF-8 Equal Case Z", "[utf8]") { TEST_CASE("UTF-8 Equal Case Z", "[utf8]") {
REQUIRE(utf8::equal_case_z(u8"foo", u8"foo\0")); REQUIRE(utf8::equal_case_z("foo", "foo\0"));
REQUIRE_FALSE(utf8::equal_case_z(u8"bar", u8"baz\0")); REQUIRE_FALSE(utf8::equal_case_z("bar", "baz\0"));
} }
// TODO(gibbed): find_any_of // TODO(gibbed): find_any_of
@ -346,11 +346,11 @@ TEST_CASE("UTF-8 Equal Case Z", "[utf8]") {
} while (0) } while (0)
TEST_CASE("UTF-8 Join Paths", "[utf8]") { TEST_CASE("UTF-8 Join Paths", "[utf8]") {
TEST_PATHS(utf8::join_paths, u8""); TEST_PATHS(utf8::join_paths, "");
TEST_PATHS(utf8::join_paths, u8"foo", u8"foo"); TEST_PATHS(utf8::join_paths, "foo", "foo");
TEST_PATHS(utf8::join_paths, u8"foo/bar", u8"foo", u8"bar"); TEST_PATHS(utf8::join_paths, "foo/bar", "foo", "bar");
TEST_PATHS(utf8::join_paths, "X:/foo/bar/baz/qux", u8"X:", u8"foo", u8"bar", TEST_PATHS(utf8::join_paths, "X:/foo/bar/baz/qux", "X:", "foo", "bar", "baz",
u8"baz", u8"qux"); "qux");
} }
// TODO(gibbed): join_guest_paths // TODO(gibbed): join_guest_paths

View File

@ -402,6 +402,7 @@ class Timer : public WaitHandle {
virtual bool Cancel() = 0; virtual bool Cancel() = 0;
}; };
#if XE_PLATFORM_WINDOWS
struct ThreadPriority { struct ThreadPriority {
static const int32_t kLowest = -2; static const int32_t kLowest = -2;
static const int32_t kBelowNormal = -1; static const int32_t kBelowNormal = -1;
@ -409,6 +410,15 @@ struct ThreadPriority {
static const int32_t kAboveNormal = 1; static const int32_t kAboveNormal = 1;
static const int32_t kHighest = 2; static const int32_t kHighest = 2;
}; };
#else
struct ThreadPriority {
static const int32_t kLowest = 1;
static const int32_t kBelowNormal = 8;
static const int32_t kNormal = 16;
static const int32_t kAboveNormal = 24;
static const int32_t kHighest = 32;
};
#endif
// Models a Win32-like thread object. // Models a Win32-like thread object.
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms682453(v=vs.85).aspx // https://msdn.microsoft.com/en-us/library/windows/desktop/ms682453(v=vs.85).aspx

View File

@ -27,6 +27,8 @@
#include <ctime> #include <ctime>
#include <memory> #include <memory>
#include "logging.h"
#if XE_PLATFORM_ANDROID #if XE_PLATFORM_ANDROID
#include <dlfcn.h> #include <dlfcn.h>
@ -660,8 +662,18 @@ class PosixCondition<Thread> : public PosixConditionBase {
WaitStarted(); WaitStarted();
sched_param param{}; sched_param param{};
param.sched_priority = new_priority; param.sched_priority = new_priority;
if (pthread_setschedparam(thread_, SCHED_FIFO, &param) != 0) int res = pthread_setschedparam(thread_, SCHED_FIFO, &param);
if (res != 0) {
switch (res) {
case EPERM:
XELOGW("Permission denied while setting priority");
break;
case EINVAL:
assert_always(); assert_always();
default:
XELOGW("Unknown error while setting priority");
}
}
} }
void QueueUserCallback(std::function<void()> callback) { void QueueUserCallback(std::function<void()> callback) {
@ -905,7 +917,7 @@ class PosixEvent : public PosixConditionHandle<Event> {
~PosixEvent() override = default; ~PosixEvent() override = default;
void Set() override { handle_.Signal(); } void Set() override { handle_.Signal(); }
void Reset() override { handle_.Reset(); } void Reset() override { handle_.Reset(); }
EventInfo Query() { EventInfo Query() override {
EventInfo result{}; EventInfo result{};
assert_always(); assert_always();
return result; return result;

View File

@ -38,7 +38,12 @@ using WaitItem = TimerQueueWaitItem;
condition_variable::wait_until) but now builds condition_variable::wait_until) but now builds
*/ */
using WaitStrat = dp::blocking_wait_strategy;
/*
edit2: (30.12.2024) After uplifting version of MSVC compiler Xenia cannot be
correctly initialized if you're using proton.
*/
using WaitStrat = dp::spin_wait_strategy;
class TimerQueue { class TimerQueue {
public: public:

View File

@ -469,7 +469,7 @@ bool ends_with_case(const std::string_view haystack,
} }
std::vector<std::string_view> split_path(const std::string_view path) { std::vector<std::string_view> split_path(const std::string_view path) {
return split(path, u8"\\/", true); return split(path, "\\/", true);
} }
std::string join_paths(const std::string_view left_path, std::string join_paths(const std::string_view left_path,

View File

@ -99,7 +99,8 @@ class HIRBuilder {
void CommentFormat(const std::string_view format, const Args&... args) { void CommentFormat(const std::string_view format, const Args&... args) {
static const uint32_t kMaxCommentSize = 1024; static const uint32_t kMaxCommentSize = 1024;
char* p = reinterpret_cast<char*>(arena_->Alloc(kMaxCommentSize, 1)); char* p = reinterpret_cast<char*>(arena_->Alloc(kMaxCommentSize, 1));
auto result = fmt::format_to_n(p, kMaxCommentSize - 1, format, args...); auto result =
fmt::format_to_n(p, kMaxCommentSize - 1, fmt::runtime(format), args...);
p[result.size] = '\0'; p[result.size] = '\0';
size_t rewind = kMaxCommentSize - 1 - result.size; size_t rewind = kMaxCommentSize - 1 - result.size;
arena_->Rewind(rewind); arena_->Rewind(rewind);

View File

@ -433,17 +433,14 @@ typedef struct alignas(64) PPCContext_s {
template <typename T = uint8_t*> template <typename T = uint8_t*>
inline T TranslateVirtual(uint32_t guest_address) XE_RESTRICT const { inline T TranslateVirtual(uint32_t guest_address) XE_RESTRICT const {
static_assert(std::is_pointer_v<T>); static_assert(std::is_pointer_v<T>);
#if XE_PLATFORM_WIN32 == 1
uint8_t* host_address = virtual_membase + guest_address; uint8_t* host_address = virtual_membase + guest_address;
#if XE_PLATFORM_WIN32 == 1
if (guest_address >= if (guest_address >=
static_cast<uint32_t>(reinterpret_cast<uintptr_t>(this))) { static_cast<uint32_t>(reinterpret_cast<uintptr_t>(this))) {
host_address += 0x1000; host_address += 0x1000;
} }
return reinterpret_cast<T>(host_address);
#else
return processor->memory()->TranslateVirtual<T>(guest_address);
#endif #endif
return reinterpret_cast<T>(host_address);
} }
template <typename T> template <typename T>
inline xe::be<T>* TranslateVirtualBE(uint32_t guest_address) inline xe::be<T>* TranslateVirtualBE(uint32_t guest_address)
@ -464,17 +461,14 @@ typedef struct alignas(64) PPCContext_s {
} }
template <typename T> template <typename T>
inline uint32_t HostToGuestVirtual(T* host_ptr) XE_RESTRICT const { inline uint32_t HostToGuestVirtual(T* host_ptr) XE_RESTRICT const {
#if XE_PLATFORM_WIN32 == 1
uint32_t guest_tmp = static_cast<uint32_t>( uint32_t guest_tmp = static_cast<uint32_t>(
reinterpret_cast<const uint8_t*>(host_ptr) - virtual_membase); reinterpret_cast<const uint8_t*>(host_ptr) - virtual_membase);
#if XE_PLATFORM_WIN32 == 1
if (guest_tmp >= static_cast<uint32_t>(reinterpret_cast<uintptr_t>(this))) { if (guest_tmp >= static_cast<uint32_t>(reinterpret_cast<uintptr_t>(this))) {
guest_tmp -= 0x1000; guest_tmp -= 0x1000;
} }
return guest_tmp;
#else
return processor->memory()->HostToGuestVirtual(
reinterpret_cast<void*>(host_ptr));
#endif #endif
return guest_tmp;
} }
static std::string GetRegisterName(PPCRegister reg); static std::string GetRegisterName(PPCRegister reg);
std::string GetStringFromValue(PPCRegister reg) const; std::string GetStringFromValue(PPCRegister reg) const;

View File

@ -57,15 +57,15 @@ Memory* PPCFrontend::memory() const { return processor_->memory(); }
// Checks the state of the global lock and sets scratch to the current MSR // Checks the state of the global lock and sets scratch to the current MSR
// value. // value.
void CheckGlobalLock(PPCContext* ppc_context, void* arg0, void* arg1) { void CheckGlobalLock(PPCContext* ppc_context, void* arg0, void* arg1) {
auto global_mutex = reinterpret_cast<xe_global_mutex*>(arg0); auto global_mutex = reinterpret_cast<global_mutex_type*>(arg0);
auto global_lock_count = reinterpret_cast<int32_t*>(arg1); auto global_lock_count = reinterpret_cast<int32_t*>(arg1);
std::lock_guard<xe_global_mutex> lock(*global_mutex); std::lock_guard<global_mutex_type> lock(*global_mutex);
ppc_context->scratch = *global_lock_count ? 0 : 0x8000; ppc_context->scratch = *global_lock_count ? 0 : 0x8000;
} }
// Enters the global lock. Safe to recursion. // Enters the global lock. Safe to recursion.
void EnterGlobalLock(PPCContext* ppc_context, void* arg0, void* arg1) { void EnterGlobalLock(PPCContext* ppc_context, void* arg0, void* arg1) {
auto global_mutex = reinterpret_cast<xe_global_mutex*>(arg0); auto global_mutex = reinterpret_cast<global_mutex_type*>(arg0);
auto global_lock_count = reinterpret_cast<int32_t*>(arg1); auto global_lock_count = reinterpret_cast<int32_t*>(arg1);
global_mutex->lock(); global_mutex->lock();
xe::atomic_inc(global_lock_count); xe::atomic_inc(global_lock_count);
@ -73,7 +73,7 @@ void EnterGlobalLock(PPCContext* ppc_context, void* arg0, void* arg1) {
// Leaves the global lock. Safe to recursion. // Leaves the global lock. Safe to recursion.
void LeaveGlobalLock(PPCContext* ppc_context, void* arg0, void* arg1) { void LeaveGlobalLock(PPCContext* ppc_context, void* arg0, void* arg1) {
auto global_mutex = reinterpret_cast<xe_global_mutex*>(arg0); auto global_mutex = reinterpret_cast<global_mutex_type*>(arg0);
auto global_lock_count = reinterpret_cast<int32_t*>(arg1); auto global_lock_count = reinterpret_cast<int32_t*>(arg1);
auto new_lock_count = xe::atomic_dec(global_lock_count); auto new_lock_count = xe::atomic_dec(global_lock_count);
assert_true(new_lock_count >= 0); assert_true(new_lock_count >= 0);

View File

@ -151,11 +151,15 @@ void PPCTranslator::DumpHIR(GuestFunction* function, PPCHIRBuilder* builder) {
{ {
wchar_t tmpbuf[64]; wchar_t tmpbuf[64];
#ifdef XE_PLATFORM_WIN32
_snwprintf(tmpbuf, 64, L"%X", function->address()); _snwprintf(tmpbuf, 64, L"%X", function->address());
#else
swprintf(tmpbuf, 64, L"%X", function->address());
#endif
folder_path.append(&tmpbuf[0]); folder_path.append(&tmpbuf[0]);
} }
FILE* f = fopen(folder_path.generic_u8string().c_str(), "w"); FILE* f = fopen(folder_path.string().c_str(), "w");
if (f) { if (f) {
fputs(buffer.buffer(), f); fputs(buffer.buffer(), f);
fclose(f); fclose(f);

View File

@ -447,7 +447,7 @@ bool Processor::Restore(ByteStream* stream) {
std::vector<uint32_t> to_delete; std::vector<uint32_t> to_delete;
for (auto& it : thread_debug_infos_) { for (auto& it : thread_debug_infos_) {
if (it.second->state == ThreadDebugInfo::State::kZombie) { if (it.second->state == ThreadDebugInfo::State::kZombie) {
it.second->thread_handle = NULL; it.second->thread_handle = 0;
to_delete.push_back(it.first); to_delete.push_back(it.first);
} }
} }
@ -502,7 +502,7 @@ void Processor::OnThreadDestroyed(uint32_t thread_id) {
auto global_lock = global_critical_region_.Acquire(); auto global_lock = global_critical_region_.Acquire();
auto it = thread_debug_infos_.find(thread_id); auto it = thread_debug_infos_.find(thread_id);
assert_true(it != thread_debug_infos_.end()); assert_true(it != thread_debug_infos_.end());
it->second->thread_handle = NULL; it->second->thread_handle = 0;
thread_debug_infos_.erase(it); thread_debug_infos_.erase(it);
} }

View File

@ -1136,10 +1136,14 @@ void XexModule::Precompile() {
high_code); high_code);
final_image_sha_.finalize(image_sha_bytes_); final_image_sha_.finalize(image_sha_bytes_);
char fmtbuf[16]; char fmtbuf[20];
for (unsigned i = 0; i < 20; ++i) { for (unsigned i = 0; i < 20; ++i) {
#ifdef XE_PLATFORM_WIN32
sprintf_s(fmtbuf, "%X", image_sha_bytes_[i]); sprintf_s(fmtbuf, "%X", image_sha_bytes_[i]);
#else
snprintf(fmtbuf, sizeof(fmtbuf), "%X", image_sha_bytes_[i]);
#endif
image_sha_str_ += &fmtbuf[0]; image_sha_str_ += &fmtbuf[0];
} }
@ -1397,6 +1401,10 @@ void XexInfoCache::Init(XexModule* xexmod) {
}; };
bool did_exist = try_open(); bool did_exist = try_open();
if (!GetHeader()) {
return;
}
if (!did_exist) { if (!did_exist) {
GetHeader()->version = CURRENT_INFOCACHE_VERSION; GetHeader()->version = CURRENT_INFOCACHE_VERSION;

View File

@ -309,7 +309,7 @@ void DebugWindow::DrawToolbar() {
case cpu::ThreadDebugInfo::State::kAlive: case cpu::ThreadDebugInfo::State::kAlive:
case cpu::ThreadDebugInfo::State::kExited: case cpu::ThreadDebugInfo::State::kExited:
case cpu::ThreadDebugInfo::State::kWaiting: case cpu::ThreadDebugInfo::State::kWaiting:
if (thread_info->thread_handle == NULL || thread_info->thread == NULL) { if (!thread_info->thread_handle || thread_info->thread == nullptr) {
thread_combo.Append("(invalid)"); thread_combo.Append("(invalid)");
} else { } else {
thread_combo.Append(thread_info->thread->thread_name()); thread_combo.Append(thread_info->thread->thread_name());

View File

@ -451,7 +451,7 @@ Emulator::FileSignatureType Emulator::GetFileSignature(
} }
char file_magic[header_size]; char file_magic[header_size];
fread_s(file_magic, sizeof(file_magic), 1, header_size, file); fread(file_magic, sizeof(file_magic), 1, file);
fourcc_t magic_value = fourcc_t magic_value =
make_fourcc(file_magic[0], file_magic[1], file_magic[2], file_magic[3]); make_fourcc(file_magic[0], file_magic[1], file_magic[2], file_magic[3]);
@ -485,7 +485,7 @@ Emulator::FileSignatureType Emulator::GetFileSignature(
file = xe::filesystem::OpenFile(path, "rb"); file = xe::filesystem::OpenFile(path, "rb");
xe::filesystem::Seek(file, -header_size, SEEK_END); xe::filesystem::Seek(file, -header_size, SEEK_END);
fread_s(file_magic, sizeof(file_magic), 1, header_size, file); fread(file_magic, 1, header_size, file);
fclose(file); fclose(file);
magic_value = magic_value =
@ -505,6 +505,7 @@ Emulator::FileSignatureType Emulator::GetFileSignature(
return FileSignatureType::XISO; return FileSignatureType::XISO;
} }
XELOGE("{}: {} ({:08X})", __func__, path.extension(), magic_value);
return FileSignatureType::Unknown; return FileSignatureType::Unknown;
} }
@ -718,7 +719,7 @@ X_STATUS Emulator::DataMigration(const uint64_t xuid) {
const auto old_profile_data = const auto old_profile_data =
xe::filesystem::ListDirectories(title.path / title.name / "profile"); xe::filesystem::ListDirectories(title.path / title.name / "profile");
xe::filesystem::FileInfo& entry_to_copy = xe::filesystem::FileInfo(); xe::filesystem::FileInfo entry_to_copy = xe::filesystem::FileInfo();
if (old_profile_data.size() != 1) { if (old_profile_data.size() != 1) {
for (const auto& entry : old_profile_data) { for (const auto& entry : old_profile_data) {
if (entry.name == "User") { if (entry.name == "User") {
@ -835,8 +836,7 @@ X_STATUS Emulator::InstallContentPackage(
return error_code; return error_code;
} }
kernel_state()->BroadcastNotification(kXNotificationIDLiveContentInstalled, kernel_state()->BroadcastNotification(kXNotificationLiveContentInstalled, 0);
0);
return error_code; return error_code;
} }
@ -938,8 +938,7 @@ X_STATUS Emulator::CreateZarchivePackage(
uint64_t total_bytes_read = 0; uint64_t total_bytes_read = 0;
while (total_bytes_read < file_size) { while (total_bytes_read < file_size) {
uint64_t bytes_read = uint64_t bytes_read = fread(buffer.data(), 1, buffer.size(), file);
fread_s(buffer.data(), buffer.size(), 1, buffer.size(), file);
total_bytes_read += bytes_read; total_bytes_read += bytes_read;
@ -1373,6 +1372,12 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
return X_STATUS_NOT_FOUND; return X_STATUS_NOT_FOUND;
} }
if (!module->is_executable()) {
kernel_state_->UnloadUserModule(module, false);
XELOGE("Failed to load user module {}", path);
return X_STATUS_NOT_SUPPORTED;
}
X_RESULT result = kernel_state_->ApplyTitleUpdate(module); X_RESULT result = kernel_state_->ApplyTitleUpdate(module);
if (XFAILED(result)) { if (XFAILED(result)) {
XELOGE("Failed to apply title update! Cannot run module {}", path); XELOGE("Failed to apply title update! Cannot run module {}", path);

View File

@ -245,8 +245,7 @@ class Emulator {
ContentInstallationInfo& installation_info); ContentInstallationInfo& installation_info);
// Extract content of zar package to desired directory. // Extract content of zar package to desired directory.
X_STATUS Emulator::ExtractZarchivePackage( X_STATUS ExtractZarchivePackage(const std::filesystem::path& path,
const std::filesystem::path& path,
const std::filesystem::path& extract_dir); const std::filesystem::path& extract_dir);
// Pack contents of a folder into a zar package. // Pack contents of a folder into a zar package.

View File

@ -580,10 +580,17 @@ static inline void GetScissorTmpl(const RegisterFile& XE_RESTRICT regs,
__m128i pa_sc_scissor = _mm_setr_epi32( __m128i pa_sc_scissor = _mm_setr_epi32(
pa_sc_screen_scissor_tl_tl_x, pa_sc_screen_scissor_tl_tl_y, pa_sc_screen_scissor_tl_tl_x, pa_sc_screen_scissor_tl_tl_y,
pa_sc_screen_scissor_br_br_x, pa_sc_screen_scissor_br_br_y); pa_sc_screen_scissor_br_br_x, pa_sc_screen_scissor_br_br_y);
#if XE_PLATFORM_WIN32
__m128i xyoffsetadd = _mm_cvtsi64x_si128( __m128i xyoffsetadd = _mm_cvtsi64x_si128(
static_cast<unsigned long long>(pa_sc_window_offset_window_x_offset) | static_cast<unsigned long long>(pa_sc_window_offset_window_x_offset) |
(static_cast<unsigned long long>(pa_sc_window_offset_window_y_offset) (static_cast<unsigned long long>(pa_sc_window_offset_window_y_offset)
<< 32)); << 32));
#else
__m128i xyoffsetadd = _mm_cvtsi64_si128(
static_cast<unsigned long long>(pa_sc_window_offset_window_x_offset) |
(static_cast<unsigned long long>(pa_sc_window_offset_window_y_offset)
<< 32));
#endif
xyoffsetadd = _mm_unpacklo_epi64(xyoffsetadd, xyoffsetadd); xyoffsetadd = _mm_unpacklo_epi64(xyoffsetadd, xyoffsetadd);
// chrispy: put this here to make it clear that the shift by 31 is extracting // chrispy: put this here to make it clear that the shift by 31 is extracting
// this field // this field

View File

@ -100,7 +100,7 @@ constexpr bool IsPrimitivePolygonal(bool vgt_output_path_is_tessellation_enable,
return (primitive_polygonal_table & (1U << static_cast<uint32_t>(type))) != 0; return (primitive_polygonal_table & (1U << static_cast<uint32_t>(type))) != 0;
} }
XE_FORCEINLINE XE_FORCEINLINE
bool IsPrimitivePolygonal(const RegisterFile& regs) { static bool IsPrimitivePolygonal(const RegisterFile& regs) {
return IsPrimitivePolygonal( return IsPrimitivePolygonal(
regs.Get<reg::VGT_OUTPUT_PATH_CNTL>().path_select == regs.Get<reg::VGT_OUTPUT_PATH_CNTL>().path_select ==
xenos::VGTOutputPath::kTessellationEnable, xenos::VGTOutputPath::kTessellationEnable,
@ -383,7 +383,7 @@ struct GetViewportInfoArgs {
depth_format = regs.Get<reg::RB_DEPTH_INFO>().depth_format; depth_format = regs.Get<reg::RB_DEPTH_INFO>().depth_format;
} }
XE_FORCEINLINE XE_FORCEINLINE
bool operator==(const GetViewportInfoArgs& prev) { bool operator==(const GetViewportInfoArgs& prev) const {
#if XE_ARCH_AMD64 == 0 #if XE_ARCH_AMD64 == 0
bool result = true; bool result = true;

View File

@ -35,7 +35,7 @@ DEFINE_uint32(internal_display_resolution, 8,
"Allow games that support different resolutions to render " "Allow games that support different resolutions to render "
"in a specific resolution.\n" "in a specific resolution.\n"
"This is not guaranteed to work with all games or improve " "This is not guaranteed to work with all games or improve "
"performance." "performance.\n"
" 0=640x480\n" " 0=640x480\n"
" 1=640x576\n" " 1=640x576\n"
" 2=720x480\n" " 2=720x480\n"

View File

@ -482,7 +482,7 @@ void SharedMemory::TryFindUploadRange(const uint32_t& block_first,
} }
static bool UploadRange_DoBestScanForward(uint64_t v, uint32_t* out) { static bool UploadRange_DoBestScanForward(uint64_t v, uint32_t* out) {
#if XE_ARCH_AMD64 == 1 #if XE_ARCH_AMD64 == 1 && XE_PLATFORM_WIN32
if (!v) { if (!v) {
return false; return false;
} }

View File

@ -94,9 +94,9 @@ void TextureDump(const TextureInfo& src, void* buffer, size_t length) {
static int dump_counter = 0; static int dump_counter = 0;
std::filesystem::path path = "texture_dumps"; std::filesystem::path path = "texture_dumps";
path /= fmt::format("{:05d}_{:08X}_{:08X}_{:08X}.dds", dump_counter++, path /= fmt::format(fmt::runtime("{:05d}_{:08X}_{:08X}_{:08X}.dds"),
src.memory.base_address, src.memory.mip_address, dump_counter++, src.memory.base_address,
src.format_name()); src.memory.mip_address, src.format_name());
FILE* handle = filesystem::OpenFile(path, "wb"); FILE* handle = filesystem::OpenFile(path, "wb");
if (handle) { if (handle) {

View File

@ -89,7 +89,7 @@ void TracePlayer::PlayTrace(const uint8_t* trace_data, size_t trace_size,
TracePlaybackMode playback_mode, TracePlaybackMode playback_mode,
bool clear_caches) { bool clear_caches) {
playing_trace_ = true; playing_trace_ = true;
graphics_system_->command_processor()->CallInThread([=]() { graphics_system_->command_processor()->CallInThread([=, this]() {
PlayTraceOnThread(trace_data, trace_size, playback_mode, clear_caches); PlayTraceOnThread(trace_data, trace_size, playback_mode, clear_caches);
}); });
} }

View File

@ -64,7 +64,7 @@ TraceViewer::TraceViewer(xe::ui::WindowedAppContext& app_context,
TraceViewer::~TraceViewer() = default; TraceViewer::~TraceViewer() = default;
bool TraceViewer::OnInitialize() { bool TraceViewer::OnInitialize() {
std::string path = cvars::target_trace_file.u8string(); std::string path = cvars::target_trace_file.string();
// If no path passed, ask the user. // If no path passed, ask the user.
// On Android, however, there's no synchronous file picker, and the trace file // On Android, however, there's no synchronous file picker, and the trace file
@ -899,9 +899,11 @@ void TraceViewer::DrawVertexFetcher(Shader* shader,
} break; } break;
case xenos::VertexFormat::k_16_16_FLOAT: { case xenos::VertexFormat::k_16_16_FLOAT: {
auto e0 = LOADEL(uint32_t, 0); auto e0 = LOADEL(uint32_t, 0);
ImGui::Text("%.2f", half_float::detail::uint16((e0 >> 16) & 0xFFFF)); ImGui::Text("%.2f", static_cast<float>(half_float::detail::uint16(
(e0 >> 16) & 0xFFFF)));
ImGui::NextColumn(); ImGui::NextColumn();
ImGui::Text("%.2f", half_float::detail::uint16((e0 >> 0) & 0xFFFF)); ImGui::Text("%.2f", static_cast<float>(half_float::detail::uint16(
(e0 >> 0) & 0xFFFF)));
ImGui::NextColumn(); ImGui::NextColumn();
} break; } break;
case xenos::VertexFormat::k_32_32: case xenos::VertexFormat::k_32_32:
@ -972,13 +974,17 @@ void TraceViewer::DrawVertexFetcher(Shader* shader,
case xenos::VertexFormat::k_16_16_16_16_FLOAT: { case xenos::VertexFormat::k_16_16_16_16_FLOAT: {
auto e0 = LOADEL(uint32_t, 0); auto e0 = LOADEL(uint32_t, 0);
auto e1 = LOADEL(uint32_t, 1); auto e1 = LOADEL(uint32_t, 1);
ImGui::Text("%.2f", half_float::detail::uint16((e0 >> 16) & 0xFFFF)); ImGui::Text("%.2f", static_cast<float>(half_float::detail::uint16(
(e0 >> 16) & 0xFFFF)));
ImGui::NextColumn(); ImGui::NextColumn();
ImGui::Text("%.2f", half_float::detail::uint16((e0 >> 0) & 0xFFFF)); ImGui::Text("%.2f", static_cast<float>(half_float::detail::uint16(
(e0 >> 0) & 0xFFFF)));
ImGui::NextColumn(); ImGui::NextColumn();
ImGui::Text("%.2f", half_float::detail::uint16((e1 >> 16) & 0xFFFF)); ImGui::Text("%.2f", static_cast<float>(half_float::detail::uint16(
(e1 >> 16) & 0xFFFF)));
ImGui::NextColumn(); ImGui::NextColumn();
ImGui::Text("%.2f", half_float::detail::uint16((e1 >> 0) & 0xFFFF)); ImGui::Text("%.2f", static_cast<float>(half_float::detail::uint16(
(e1 >> 0) & 0xFFFF)));
ImGui::NextColumn(); ImGui::NextColumn();
} break; } break;
case xenos::VertexFormat::k_32_32_32_32_FLOAT: case xenos::VertexFormat::k_32_32_32_32_FLOAT:

View File

@ -26,6 +26,22 @@ enum X_INPUT_CAPS {
enum X_INPUT_FLAG { enum X_INPUT_FLAG {
X_INPUT_FLAG_GAMEPAD = 0x00000001, X_INPUT_FLAG_GAMEPAD = 0x00000001,
X_INPUT_FLAG_KEYBOARD = 0x00000002,
X_INPUT_FLAG_UNKNOWN = 0x00000004,
X_INPUT_FLAG_UNKNOWN2 = 0x00000008,
X_INPUT_FLAG_MIC = 0x00000020,
X_INPUT_FLAG_ANYDEVICE = 0x000000FF,
X_INPUT_FLAG_ANY_USER = 1 << 30
};
enum X_INPUT_DEVTYPE {
XINPUT_DEVTYPE_GAMEPAD = 0x00000001,
XINPUT_DEVTYPE_KEYBOARD = 0x00000002,
};
enum X_INPUT_DEVSUBTYPE {
XINPUT_DEVSUBTYPE_USB_KEYBOARD = 0x00000000,
XINPUT_DEVSUBTYPE_GAMEPAD = 0x00000001,
}; };
enum X_INPUT_GAMEPAD_BUTTON { enum X_INPUT_GAMEPAD_BUTTON {

View File

@ -28,6 +28,8 @@ namespace hid {
class InputSystem; class InputSystem;
enum InputType { None, Controller = 1, Keyboard = 2, Other = 4 };
class InputDriver { class InputDriver {
public: public:
virtual ~InputDriver() = default; virtual ~InputDriver() = default;
@ -42,6 +44,8 @@ class InputDriver {
virtual X_RESULT GetKeystroke(uint32_t user_index, uint32_t flags, virtual X_RESULT GetKeystroke(uint32_t user_index, uint32_t flags,
X_INPUT_KEYSTROKE* out_keystroke) = 0; X_INPUT_KEYSTROKE* out_keystroke) = 0;
virtual InputType GetInputType() const = 0;
void set_is_active_callback(std::function<bool()> is_active_callback) { void set_is_active_callback(std::function<bool()> is_active_callback) {
is_active_callback_ = is_active_callback; is_active_callback_ = is_active_callback;
} }

View File

@ -41,7 +41,13 @@ void InputSystem::AddDriver(std::unique_ptr<InputDriver> driver) {
void InputSystem::UpdateUsedSlot(InputDriver* driver, uint8_t slot, void InputSystem::UpdateUsedSlot(InputDriver* driver, uint8_t slot,
bool connected) { bool connected) {
if (slot == XUserIndexAny) { if (slot == XUserIndexAny) {
slot = 0; XELOGW("{} received requrest for slot any! Unsupported", __func__);
return;
}
// Do not report passthrough as a controller.
if (driver && driver->GetInputType() == InputType::Keyboard) {
return;
} }
if (connected_slots.test(slot) == connected) { if (connected_slots.test(slot) == connected) {
@ -53,7 +59,7 @@ void InputSystem::UpdateUsedSlot(InputDriver* driver, uint8_t slot,
connected_slots.flip(slot); connected_slots.flip(slot);
if (kernel::kernel_state()) { if (kernel::kernel_state()) {
kernel::kernel_state()->BroadcastNotification( kernel::kernel_state()->BroadcastNotification(
kXNotificationIDSystemInputDevicesChanged, 0); kXNotificationSystemInputDevicesChanged, 0);
} }
if (driver) { if (driver) {
@ -69,36 +75,45 @@ void InputSystem::UpdateUsedSlot(InputDriver* driver, uint8_t slot,
} }
} }
std::vector<InputDriver*> InputSystem::FilterDrivers(uint32_t flags) {
std::vector<InputDriver*> filtered_drivers;
for (auto& driver : drivers_) {
if (driver->GetInputType() == InputType::None) {
continue;
}
if ((flags & driver->GetInputType()) != 0) {
filtered_drivers.push_back(driver.get());
}
}
return filtered_drivers;
}
X_RESULT InputSystem::GetCapabilities(uint32_t user_index, uint32_t flags, X_RESULT InputSystem::GetCapabilities(uint32_t user_index, uint32_t flags,
X_INPUT_CAPABILITIES* out_caps) { X_INPUT_CAPABILITIES* out_caps) {
SCOPE_profile_cpu_f("hid"); SCOPE_profile_cpu_f("hid");
bool any_connected = false; std::vector<InputDriver*> filtered_drivers = FilterDrivers(flags);
for (auto& driver : drivers_) {
for (auto& driver : filtered_drivers) {
X_RESULT result = driver->GetCapabilities(user_index, flags, out_caps); X_RESULT result = driver->GetCapabilities(user_index, flags, out_caps);
if (result != X_ERROR_DEVICE_NOT_CONNECTED) {
any_connected = true;
}
if (result == X_ERROR_SUCCESS) { if (result == X_ERROR_SUCCESS) {
UpdateUsedSlot(driver.get(), user_index, any_connected);
return result; return result;
} }
} }
UpdateUsedSlot(nullptr, user_index, any_connected); return X_ERROR_DEVICE_NOT_CONNECTED;
return any_connected ? X_ERROR_EMPTY : X_ERROR_DEVICE_NOT_CONNECTED;
} }
X_RESULT InputSystem::GetState(uint32_t user_index, X_INPUT_STATE* out_state) { X_RESULT InputSystem::GetState(uint32_t user_index, uint32_t flags,
X_INPUT_STATE* out_state) {
SCOPE_profile_cpu_f("hid"); SCOPE_profile_cpu_f("hid");
bool any_connected = false; std::vector<InputDriver*> filtered_drivers = FilterDrivers(flags);
for (auto& driver : drivers_) {
for (auto& driver : filtered_drivers) {
X_RESULT result = driver->GetState(user_index, out_state); X_RESULT result = driver->GetState(user_index, out_state);
if (result != X_ERROR_DEVICE_NOT_CONNECTED) {
any_connected = true;
}
if (result == X_ERROR_SUCCESS) { if (result == X_ERROR_SUCCESS) {
UpdateUsedSlot(driver.get(), user_index, any_connected); UpdateUsedSlot(driver, user_index, true);
AdjustDeadzoneLevels(user_index, &out_state->gamepad); AdjustDeadzoneLevels(user_index, &out_state->gamepad);
if (out_state->gamepad.buttons != 0) { if (out_state->gamepad.buttons != 0) {
@ -107,49 +122,49 @@ X_RESULT InputSystem::GetState(uint32_t user_index, X_INPUT_STATE* out_state) {
return result; return result;
} }
} }
UpdateUsedSlot(nullptr, user_index, any_connected); UpdateUsedSlot(nullptr, user_index, false);
return any_connected ? X_ERROR_EMPTY : X_ERROR_DEVICE_NOT_CONNECTED; return X_ERROR_DEVICE_NOT_CONNECTED;
} }
X_RESULT InputSystem::SetState(uint32_t user_index, X_RESULT InputSystem::SetState(uint32_t user_index,
X_INPUT_VIBRATION* vibration) { X_INPUT_VIBRATION* vibration) {
SCOPE_profile_cpu_f("hid"); SCOPE_profile_cpu_f("hid");
X_INPUT_VIBRATION modified_vibration = ModifyVibrationLevel(vibration); X_INPUT_VIBRATION modified_vibration = ModifyVibrationLevel(vibration);
bool any_connected = false;
for (auto& driver : drivers_) { for (auto& driver : drivers_) {
X_RESULT result = driver->SetState(user_index, &modified_vibration); X_RESULT result = driver->SetState(user_index, &modified_vibration);
if (result != X_ERROR_DEVICE_NOT_CONNECTED) {
any_connected = true;
}
if (result == X_ERROR_SUCCESS) { if (result == X_ERROR_SUCCESS) {
UpdateUsedSlot(driver.get(), user_index, any_connected);
return result; return result;
} }
} }
UpdateUsedSlot(nullptr, user_index, any_connected); return X_ERROR_DEVICE_NOT_CONNECTED;
return any_connected ? X_ERROR_EMPTY : X_ERROR_DEVICE_NOT_CONNECTED;
} }
X_RESULT InputSystem::GetKeystroke(uint32_t user_index, uint32_t flags, X_RESULT InputSystem::GetKeystroke(uint32_t user_index, uint32_t flags,
X_INPUT_KEYSTROKE* out_keystroke) { X_INPUT_KEYSTROKE* out_keystroke) {
SCOPE_profile_cpu_f("hid"); SCOPE_profile_cpu_f("hid");
std::vector<InputDriver*> filtered_drivers = FilterDrivers(flags);
bool any_connected = false; bool any_connected = false;
for (auto& driver : drivers_) { for (auto& driver : filtered_drivers) {
// connected_slots
X_RESULT result = driver->GetKeystroke(user_index, flags, out_keystroke); X_RESULT result = driver->GetKeystroke(user_index, flags, out_keystroke);
if (result != X_ERROR_DEVICE_NOT_CONNECTED) { if (result == X_ERROR_INVALID_PARAMETER ||
any_connected = true; result == X_ERROR_DEVICE_NOT_CONNECTED) {
continue;
} }
if (result == X_ERROR_SUCCESS || result == X_ERROR_EMPTY) {
UpdateUsedSlot(driver.get(), user_index, any_connected); any_connected = true;
if (result == X_ERROR_SUCCESS) { if (result == X_ERROR_SUCCESS) {
last_used_slot = user_index; last_used_slot = user_index;
}
return result; return result;
} }
if (result == X_ERROR_EMPTY) {
continue;
}
} }
UpdateUsedSlot(nullptr, user_index, any_connected);
return any_connected ? X_ERROR_EMPTY : X_ERROR_DEVICE_NOT_CONNECTED; return any_connected ? X_ERROR_EMPTY : X_ERROR_DEVICE_NOT_CONNECTED;
} }

View File

@ -40,13 +40,13 @@ class InputSystem {
X_RESULT GetCapabilities(uint32_t user_index, uint32_t flags, X_RESULT GetCapabilities(uint32_t user_index, uint32_t flags,
X_INPUT_CAPABILITIES* out_caps); X_INPUT_CAPABILITIES* out_caps);
X_RESULT GetState(uint32_t user_index, X_INPUT_STATE* out_state); X_RESULT GetState(uint32_t user_index, uint32_t flags,
X_INPUT_STATE* out_state);
X_RESULT SetState(uint32_t user_index, X_INPUT_VIBRATION* vibration); X_RESULT SetState(uint32_t user_index, X_INPUT_VIBRATION* vibration);
X_RESULT GetKeystroke(uint32_t user_index, uint32_t flags, X_RESULT GetKeystroke(uint32_t user_index, uint32_t flags,
X_INPUT_KEYSTROKE* out_keystroke); X_INPUT_KEYSTROKE* out_keystroke);
bool GetVibrationCvar(); bool GetVibrationCvar();
void ToggleVibration(); void ToggleVibration();
const std::bitset<XUserMaxUserCount> GetConnectedSlots() const { const std::bitset<XUserMaxUserCount> GetConnectedSlots() const {
@ -68,6 +68,8 @@ class InputSystem {
void AdjustDeadzoneLevels(const uint8_t slot, X_INPUT_GAMEPAD* gamepad); void AdjustDeadzoneLevels(const uint8_t slot, X_INPUT_GAMEPAD* gamepad);
X_INPUT_VIBRATION ModifyVibrationLevel(X_INPUT_VIBRATION* vibration); X_INPUT_VIBRATION ModifyVibrationLevel(X_INPUT_VIBRATION* vibration);
std::vector<InputDriver*> FilterDrivers(uint32_t flags);
xe::ui::Window* window_ = nullptr; xe::ui::Window* window_ = nullptr;
std::vector<std::unique_ptr<InputDriver>> drivers_; std::vector<std::unique_ptr<InputDriver>> drivers_;

View File

@ -45,6 +45,8 @@ X_RESULT NopInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
return X_ERROR_DEVICE_NOT_CONNECTED; return X_ERROR_DEVICE_NOT_CONNECTED;
} }
InputType NopInputDriver::GetInputType() const { return InputType::Other; }
} // namespace nop } // namespace nop
} // namespace hid } // namespace hid
} // namespace xe } // namespace xe

View File

@ -29,6 +29,7 @@ class NopInputDriver final : public InputDriver {
X_RESULT SetState(uint32_t user_index, X_INPUT_VIBRATION* vibration) override; X_RESULT SetState(uint32_t user_index, X_INPUT_VIBRATION* vibration) override;
X_RESULT GetKeystroke(uint32_t user_index, uint32_t flags, X_RESULT GetKeystroke(uint32_t user_index, uint32_t flags,
X_INPUT_KEYSTROKE* out_keystroke) override; X_INPUT_KEYSTROKE* out_keystroke) override;
virtual InputType GetInputType() const override;
}; };
} // namespace nop } // namespace nop

View File

@ -155,7 +155,7 @@ void SDLInputDriver::LoadGameControllerDB() {
std::string guid = row[0]; std::string guid = row[0];
std::string controller_name = row[1]; std::string controller_name = row[1];
auto format = [](std::string& ss, std::string& s) { auto format = [](std::string ss, const std::string& s) {
return ss.empty() ? s : ss + "," + s; return ss.empty() ? s : ss + "," + s;
}; };
@ -429,6 +429,8 @@ X_RESULT SDLInputDriver::GetKeystroke(uint32_t users, uint32_t flags,
return X_ERROR_EMPTY; return X_ERROR_EMPTY;
} }
InputType SDLInputDriver::GetInputType() const { return InputType::Controller; }
void SDLInputDriver::HandleEvent(const SDL_Event& event) { void SDLInputDriver::HandleEvent(const SDL_Event& event) {
// This callback will likely run on the thread that posts the event, which // This callback will likely run on the thread that posts the event, which
// may be a dedicated thread SDL has created for the joystick subsystem. // may be a dedicated thread SDL has created for the joystick subsystem.

View File

@ -44,6 +44,7 @@ class SDLInputDriver final : public InputDriver {
X_RESULT SetState(uint32_t user_index, X_INPUT_VIBRATION* vibration) override; X_RESULT SetState(uint32_t user_index, X_INPUT_VIBRATION* vibration) override;
X_RESULT GetKeystroke(uint32_t user_index, uint32_t flags, X_RESULT GetKeystroke(uint32_t user_index, uint32_t flags,
X_INPUT_KEYSTROKE* out_keystroke) override; X_INPUT_KEYSTROKE* out_keystroke) override;
virtual InputType GetInputType() const override;
private: private:
struct ControllerState { struct ControllerState {

View File

@ -25,13 +25,36 @@
#include "winkey_binding_table.inc" #include "winkey_binding_table.inc"
#undef XE_HID_WINKEY_BINDING #undef XE_HID_WINKEY_BINDING
DEFINE_int32(keyboard_user_index, 0, "Controller port that keyboard emulates", DEFINE_int32(keyboard_mode, 0,
"HID.WinKey"); "Allows user do specify keyboard working mode. Possible values: 0 "
"- Disabled, 1 - Enabled, 2 - Passthrough. Passthrough requires "
"controller being connected!",
"HID");
DEFINE_int32(
keyboard_user_index, 0,
"Controller port that keyboard emulates. [0, 3] - Keyboard is assigned to "
"selected slot. Passthrough does not require assigning slot.",
"HID");
namespace xe { namespace xe {
namespace hid { namespace hid {
namespace winkey { namespace winkey {
bool static IsPassthroughEnabled() {
return static_cast<KeyboardMode>(cvars::keyboard_mode) ==
KeyboardMode::Passthrough;
}
bool static IsKeyboardForUserEnabled(uint32_t user_index) {
if (static_cast<KeyboardMode>(cvars::keyboard_mode) !=
KeyboardMode::Enabled) {
return false;
}
return cvars::keyboard_user_index == user_index;
}
bool __inline IsKeyToggled(uint8_t key) { bool __inline IsKeyToggled(uint8_t key) {
return (GetKeyState(key) & 0x1) == 0x1; return (GetKeyState(key) & 0x1) == 0x1;
} }
@ -105,13 +128,18 @@ X_STATUS WinKeyInputDriver::Setup() { return X_STATUS_SUCCESS; }
X_RESULT WinKeyInputDriver::GetCapabilities(uint32_t user_index, uint32_t flags, X_RESULT WinKeyInputDriver::GetCapabilities(uint32_t user_index, uint32_t flags,
X_INPUT_CAPABILITIES* out_caps) { X_INPUT_CAPABILITIES* out_caps) {
if (user_index != cvars::keyboard_user_index) { if (!IsKeyboardForUserEnabled(user_index) && !IsPassthroughEnabled()) {
return X_ERROR_DEVICE_NOT_CONNECTED; return X_ERROR_DEVICE_NOT_CONNECTED;
} }
// TODO(benvanik): confirm with a real XInput controller. if (IsPassthroughEnabled()) {
out_caps->type = 0x01; // XINPUT_DEVTYPE_GAMEPAD out_caps->type = X_INPUT_DEVTYPE::XINPUT_DEVTYPE_KEYBOARD;
out_caps->sub_type = 0x01; // XINPUT_DEVSUBTYPE_GAMEPAD out_caps->sub_type = X_INPUT_DEVSUBTYPE::XINPUT_DEVSUBTYPE_USB_KEYBOARD;
return X_ERROR_SUCCESS;
}
out_caps->type = X_INPUT_DEVTYPE::XINPUT_DEVTYPE_GAMEPAD;
out_caps->sub_type = X_INPUT_DEVSUBTYPE::XINPUT_DEVSUBTYPE_GAMEPAD;
out_caps->flags = 0; out_caps->flags = 0;
out_caps->gamepad.buttons = 0xFFFF; out_caps->gamepad.buttons = 0xFFFF;
out_caps->gamepad.left_trigger = 0xFF; out_caps->gamepad.left_trigger = 0xFF;
@ -127,7 +155,7 @@ X_RESULT WinKeyInputDriver::GetCapabilities(uint32_t user_index, uint32_t flags,
X_RESULT WinKeyInputDriver::GetState(uint32_t user_index, X_RESULT WinKeyInputDriver::GetState(uint32_t user_index,
X_INPUT_STATE* out_state) { X_INPUT_STATE* out_state) {
if (user_index != cvars::keyboard_user_index) { if (!IsKeyboardForUserEnabled(user_index)) {
return X_ERROR_DEVICE_NOT_CONNECTED; return X_ERROR_DEVICE_NOT_CONNECTED;
} }
@ -237,12 +265,16 @@ X_RESULT WinKeyInputDriver::GetState(uint32_t user_index,
out_state->gamepad.thumb_rx = thumb_rx; out_state->gamepad.thumb_rx = thumb_rx;
out_state->gamepad.thumb_ry = thumb_ry; out_state->gamepad.thumb_ry = thumb_ry;
if (IsPassthroughEnabled()) {
memset(out_state, 0, sizeof(out_state));
}
return X_ERROR_SUCCESS; return X_ERROR_SUCCESS;
} }
X_RESULT WinKeyInputDriver::SetState(uint32_t user_index, X_RESULT WinKeyInputDriver::SetState(uint32_t user_index,
X_INPUT_VIBRATION* vibration) { X_INPUT_VIBRATION* vibration) {
if (user_index != cvars::keyboard_user_index) { if (!IsKeyboardForUserEnabled(user_index) && !IsPassthroughEnabled()) {
return X_ERROR_DEVICE_NOT_CONNECTED; return X_ERROR_DEVICE_NOT_CONNECTED;
} }
@ -251,21 +283,13 @@ X_RESULT WinKeyInputDriver::SetState(uint32_t user_index,
X_RESULT WinKeyInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags, X_RESULT WinKeyInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
X_INPUT_KEYSTROKE* out_keystroke) { X_INPUT_KEYSTROKE* out_keystroke) {
if (user_index != cvars::keyboard_user_index) { if (!is_active()) {
return X_ERROR_DEVICE_NOT_CONNECTED; return X_ERROR_DEVICE_NOT_CONNECTED;
} }
if (!is_active()) { if (!IsKeyboardForUserEnabled(user_index) && !IsPassthroughEnabled()) {
return X_ERROR_EMPTY; return X_ERROR_DEVICE_NOT_CONNECTED;
} }
X_RESULT result = X_ERROR_EMPTY;
ui::VirtualKey xinput_virtual_key = ui::VirtualKey::kNone;
uint16_t unicode = 0;
uint16_t keystroke_flags = 0;
uint8_t hid_code = 0;
// Pop from the queue. // Pop from the queue.
KeyEvent evt; KeyEvent evt;
{ {
@ -278,7 +302,17 @@ X_RESULT WinKeyInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
key_events_.pop(); key_events_.pop();
} }
X_RESULT result = X_ERROR_EMPTY;
ui::VirtualKey xinput_virtual_key = ui::VirtualKey::kNone;
uint16_t unicode = 0;
uint16_t keystroke_flags = 0;
uint8_t hid_code = 0;
bool capital = IsKeyToggled(VK_CAPITAL) || IsKeyDown(VK_SHIFT); bool capital = IsKeyToggled(VK_CAPITAL) || IsKeyDown(VK_SHIFT);
if (!IsPassthroughEnabled()) {
if (IsKeyboardForUserEnabled(user_index)) {
for (const KeyBinding& b : key_bindings_) { for (const KeyBinding& b : key_bindings_) {
if (b.input_key == evt.virtual_key && if (b.input_key == evt.virtual_key &&
((b.lowercase == b.uppercase) || (b.lowercase && !capital) || ((b.lowercase == b.uppercase) || (b.lowercase && !capital) ||
@ -286,16 +320,42 @@ X_RESULT WinKeyInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
xinput_virtual_key = b.output_key; xinput_virtual_key = b.output_key;
} }
} }
}
} else {
xinput_virtual_key = evt.virtual_key;
if (capital) {
keystroke_flags |= 0x0008; // XINPUT_KEYSTROKE_SHIFT
}
if (IsKeyToggled(VK_CONTROL)) {
keystroke_flags |= 0x0010; // XINPUT_KEYSTROKE_CTRL
}
if (IsKeyToggled(VK_MENU)) {
keystroke_flags |= 0x0020; // XINPUT_KEYSTROKE_ALT
}
}
if (xinput_virtual_key != ui::VirtualKey::kNone) { if (xinput_virtual_key != ui::VirtualKey::kNone) {
if (evt.transition == true) { if (evt.transition == true) {
keystroke_flags |= 0x0001; // XINPUT_KEYSTROKE_KEYDOWN keystroke_flags |= 0x0001; // XINPUT_KEYSTROKE_KEYDOWN
if (evt.prev_state == evt.transition) {
keystroke_flags |= 0x0004; // XINPUT_KEYSTROKE_REPEAT
}
} else if (evt.transition == false) { } else if (evt.transition == false) {
keystroke_flags |= 0x0002; // XINPUT_KEYSTROKE_KEYUP keystroke_flags |= 0x0002; // XINPUT_KEYSTROKE_KEYUP
} }
if (evt.prev_state == evt.transition) { if (IsPassthroughEnabled()) {
keystroke_flags |= 0x0004; // XINPUT_KEYSTROKE_REPEAT if (GetKeyboardState(key_map_)) {
WCHAR buf;
if (ToUnicode(uint8_t(xinput_virtual_key), 0, key_map_, &buf, 1, 0) ==
1) {
keystroke_flags |= 0x1000; // XINPUT_KEYSTROKE_VALIDUNICODE
unicode = buf;
}
}
} }
result = X_ERROR_SUCCESS; result = X_ERROR_SUCCESS;
@ -304,7 +364,7 @@ X_RESULT WinKeyInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
out_keystroke->virtual_key = uint16_t(xinput_virtual_key); out_keystroke->virtual_key = uint16_t(xinput_virtual_key);
out_keystroke->unicode = unicode; out_keystroke->unicode = unicode;
out_keystroke->flags = keystroke_flags; out_keystroke->flags = keystroke_flags;
out_keystroke->user_index = 0; out_keystroke->user_index = user_index;
out_keystroke->hid_code = hid_code; out_keystroke->hid_code = hid_code;
// X_ERROR_EMPTY if no new keys // X_ERROR_EMPTY if no new keys
@ -322,7 +382,8 @@ void WinKeyInputDriver::WinKeyWindowInputListener::OnKeyUp(ui::KeyEvent& e) {
} }
void WinKeyInputDriver::OnKey(ui::KeyEvent& e, bool is_down) { void WinKeyInputDriver::OnKey(ui::KeyEvent& e, bool is_down) {
if (!is_active()) { if (!is_active() || static_cast<KeyboardMode>(cvars::keyboard_mode) ==
KeyboardMode::Disabled) {
return; return;
} }
@ -336,6 +397,20 @@ void WinKeyInputDriver::OnKey(ui::KeyEvent& e, bool is_down) {
key_events_.push(key); key_events_.push(key);
} }
InputType WinKeyInputDriver::GetInputType() const {
switch (static_cast<KeyboardMode>(cvars::keyboard_mode)) {
case KeyboardMode::Disabled:
return InputType::None;
case KeyboardMode::Enabled:
return InputType::Controller;
case KeyboardMode::Passthrough:
return InputType::Keyboard;
default:
break;
}
return InputType::Controller;
}
} // namespace winkey } // namespace winkey
} // namespace hid } // namespace hid
} // namespace xe } // namespace xe

View File

@ -20,6 +20,8 @@ namespace xe {
namespace hid { namespace hid {
namespace winkey { namespace winkey {
enum class KeyboardMode { Disabled, Enabled, Passthrough };
class WinKeyInputDriver final : public InputDriver { class WinKeyInputDriver final : public InputDriver {
public: public:
explicit WinKeyInputDriver(xe::ui::Window* window, size_t window_z_order); explicit WinKeyInputDriver(xe::ui::Window* window, size_t window_z_order);
@ -33,6 +35,7 @@ class WinKeyInputDriver final : public InputDriver {
X_RESULT SetState(uint32_t user_index, X_INPUT_VIBRATION* vibration) override; X_RESULT SetState(uint32_t user_index, X_INPUT_VIBRATION* vibration) override;
X_RESULT GetKeystroke(uint32_t user_index, uint32_t flags, X_RESULT GetKeystroke(uint32_t user_index, uint32_t flags,
X_INPUT_KEYSTROKE* out_keystroke) override; X_INPUT_KEYSTROKE* out_keystroke) override;
virtual InputType GetInputType() const override;
protected: protected:
struct KeyEvent { struct KeyEvent {
@ -72,7 +75,7 @@ class WinKeyInputDriver final : public InputDriver {
xe::global_critical_region global_critical_region_; xe::global_critical_region global_critical_region_;
std::queue<KeyEvent> key_events_; std::queue<KeyEvent> key_events_;
std::vector<KeyBinding> key_bindings_; std::vector<KeyBinding> key_bindings_;
uint8_t key_map_[256];
uint32_t packet_number_ = 1; uint32_t packet_number_ = 1;
}; };

View File

@ -110,7 +110,9 @@ X_RESULT XInputInputDriver::GetCapabilities(uint32_t user_index, uint32_t flags,
} }
XINPUT_CAPABILITIES native_caps; XINPUT_CAPABILITIES native_caps;
auto xigc = (decltype(&XInputGetCapabilities))XInputGetCapabilities_; auto xigc = (decltype(&XInputGetCapabilities))XInputGetCapabilities_;
DWORD result = xigc(user_index, flags, &native_caps); DWORD result =
xigc(user_index, flags & ~X_INPUT_DEVTYPE::XINPUT_DEVTYPE_KEYBOARD,
&native_caps);
if (result) { if (result) {
if (result == ERROR_DEVICE_NOT_CONNECTED) { if (result == ERROR_DEVICE_NOT_CONNECTED) {
set_skip(user_index); set_skip(user_index);
@ -239,6 +241,10 @@ X_RESULT XInputInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
return result; return result;
} }
InputType XInputInputDriver::GetInputType() const {
return InputType::Controller;
}
} // namespace xinput } // namespace xinput
} // namespace hid } // namespace hid
} // namespace xe } // namespace xe

View File

@ -29,6 +29,7 @@ class XInputInputDriver final : public InputDriver {
X_RESULT SetState(uint32_t user_index, X_INPUT_VIBRATION* vibration) override; X_RESULT SetState(uint32_t user_index, X_INPUT_VIBRATION* vibration) override;
X_RESULT GetKeystroke(uint32_t user_index, uint32_t flags, X_RESULT GetKeystroke(uint32_t user_index, uint32_t flags,
X_INPUT_KEYSTROKE* out_keystroke) override; X_INPUT_KEYSTROKE* out_keystroke) override;
virtual InputType GetInputType() const override;
private: private:
void* module_; void* module_;

View File

@ -867,11 +867,11 @@ void KernelState::RegisterNotifyListener(XNotifyListener* listener) {
if (!has_notified_startup_ && listener->mask() & kXNotifySystem) { if (!has_notified_startup_ && listener->mask() & kXNotifySystem) {
has_notified_startup_ = true; has_notified_startup_ = true;
// XN_SYS_UI (on, off) // XN_SYS_UI (on, off)
listener->EnqueueNotification(kXNotificationIDSystemUI, 1); listener->EnqueueNotification(kXNotificationSystemUI, 1);
listener->EnqueueNotification(kXNotificationIDSystemUI, 0); listener->EnqueueNotification(kXNotificationSystemUI, 0);
// XN_SYS_SIGNINCHANGED x2 // XN_SYS_SIGNINCHANGED x2
listener->EnqueueNotification(kXNotificationIDSystemSignInChanged, 1); listener->EnqueueNotification(kXNotificationSystemSignInChanged, 1);
listener->EnqueueNotification(kXNotificationIDSystemSignInChanged, 1); listener->EnqueueNotification(kXNotificationSystemSignInChanged, 1);
} }
} }

View File

@ -52,7 +52,7 @@ class NativeList {
}; };
template <typename VirtualTranslator> template <typename VirtualTranslator>
static X_LIST_ENTRY* XeHostList(uint32_t ptr, VirtualTranslator context) { static X_LIST_ENTRY* XeHostList(uint32_t ptr, VirtualTranslator context) {
return context->TranslateVirtual<X_LIST_ENTRY*>(ptr); return context->template TranslateVirtual<X_LIST_ENTRY*>(ptr);
} }
template <typename VirtualTranslator> template <typename VirtualTranslator>
static uint32_t XeGuestList(X_LIST_ENTRY* ptr, VirtualTranslator context) { static uint32_t XeGuestList(X_LIST_ENTRY* ptr, VirtualTranslator context) {
@ -193,7 +193,8 @@ struct X_TYPED_LIST : public X_LIST_ENTRY {
inline ForwardIterator& operator++() { inline ForwardIterator& operator++() {
current_entry = current_entry =
vt->TranslateVirtual<X_LIST_ENTRY*>(current_entry)->flink_ptr; vt->template TranslateVirtual<X_LIST_ENTRY*>(current_entry)
->flink_ptr;
return *this; return *this;
} }
inline bool operator!=(uint32_t other_ptr) const { inline bool operator!=(uint32_t other_ptr) const {
@ -202,7 +203,7 @@ struct X_TYPED_LIST : public X_LIST_ENTRY {
inline TObject& operator*() { inline TObject& operator*() {
return *ListEntryObject( return *ListEntryObject(
vt->TranslateVirtual<X_LIST_ENTRY*>(current_entry)); vt->template TranslateVirtual<X_LIST_ENTRY*>(current_entry));
} }
}; };
template <typename VirtualTranslator> template <typename VirtualTranslator>
@ -236,15 +237,17 @@ struct X_TYPED_LIST : public X_LIST_ENTRY {
} }
template <typename VirtualTranslator> template <typename VirtualTranslator>
bool empty(VirtualTranslator vt) const { bool empty(VirtualTranslator vt) const {
return vt->TranslateVirtual<X_LIST_ENTRY*>(flink_ptr) == this; return vt->template TranslateVirtual<X_LIST_ENTRY*>(flink_ptr) == this;
} }
template <typename VirtualTranslator> template <typename VirtualTranslator>
TObject* HeadObject(VirtualTranslator vt) { TObject* HeadObject(VirtualTranslator vt) {
return ListEntryObject(vt->TranslateVirtual<X_LIST_ENTRY*>(flink_ptr)); return ListEntryObject(
vt->template TranslateVirtual<X_LIST_ENTRY*>(flink_ptr));
} }
template <typename VirtualTranslator> template <typename VirtualTranslator>
TObject* TailObject(VirtualTranslator vt) { TObject* TailObject(VirtualTranslator vt) {
return ListEntryObject(vt->TranslateVirtual<X_LIST_ENTRY*>(blink_ptr)); return ListEntryObject(
vt->template TranslateVirtual<X_LIST_ENTRY*>(blink_ptr));
} }
}; };
} // namespace util } // namespace util

View File

@ -77,4 +77,4 @@ class AttributeStringFormatter {
} // namespace kernel } // namespace kernel
} // namespace xe } // namespace xe
#endif XENIA_KERNEL_UTIL_PRESENCE_STRING_BUILDER_H_ #endif // XENIA_KERNEL_UTIL_PRESENCE_STRING_BUILDER_H_

View File

@ -93,6 +93,12 @@ inline std::string_view TranslateAnsiString(const Memory* memory,
ansi_string->length); ansi_string->length);
} }
inline std::string TranslateAnsiPath(const Memory* memory,
const X_ANSI_STRING* ansi_string) {
return string_util::trim(
std::string(TranslateAnsiString(memory, ansi_string)));
}
inline std::string_view TranslateAnsiStringAddress(const Memory* memory, inline std::string_view TranslateAnsiStringAddress(const Memory* memory,
uint32_t guest_address) { uint32_t guest_address) {
if (!guest_address) { if (!guest_address) {

View File

@ -43,8 +43,7 @@ void GpdAchievementBackend::EarnAchievement(const uint64_t xuid,
achievement->flags = achievement->flags | achievement->flags = achievement->flags |
static_cast<uint32_t>(AchievementFlags::kAchieved) | static_cast<uint32_t>(AchievementFlags::kAchieved) |
static_cast<uint32_t>(AchievementFlags::kAchievedOnline); static_cast<uint32_t>(AchievementFlags::kAchievedOnline);
achievement->unlock_time.high_part = static_cast<uint32_t>(unlock_time >> 32); achievement->unlock_time = unlock_time;
achievement->unlock_time.low_part = static_cast<uint32_t>(unlock_time);
SaveAchievementData(xuid, title_id, achievement_id); SaveAchievementData(xuid, title_id, achievement_id);
} }
@ -72,6 +71,10 @@ bool GpdAchievementBackend::IsAchievementUnlocked(
const auto achievement = const auto achievement =
GetAchievementInfoInternal(xuid, title_id, achievement_id); GetAchievementInfoInternal(xuid, title_id, achievement_id);
if (!achievement) {
return false;
}
return (achievement->flags & return (achievement->flags &
static_cast<uint32_t>(AchievementFlags::kAchieved)) != 0; static_cast<uint32_t>(AchievementFlags::kAchieved)) != 0;
} }

View File

@ -15,6 +15,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "xenia/base/chrono.h"
#include "xenia/kernel/util/xdbf_utils.h" #include "xenia/kernel/util/xdbf_utils.h"
#include "xenia/xbox.h" #include "xenia/xbox.h"
@ -53,6 +54,32 @@ enum class AchievementFlags : uint32_t {
struct X_ACHIEVEMENT_UNLOCK_TIME { struct X_ACHIEVEMENT_UNLOCK_TIME {
xe::be<uint32_t> high_part; xe::be<uint32_t> high_part;
xe::be<uint32_t> low_part; xe::be<uint32_t> low_part;
X_ACHIEVEMENT_UNLOCK_TIME() {
high_part = 0;
low_part = 0;
}
X_ACHIEVEMENT_UNLOCK_TIME(uint64_t filetime) {
high_part = static_cast<uint32_t>(filetime >> 32);
low_part = static_cast<uint32_t>(filetime);
}
X_ACHIEVEMENT_UNLOCK_TIME(std::time_t time) {
const auto file_time =
chrono::WinSystemClock::to_file_time(chrono::WinSystemClock::from_sys(
std::chrono::system_clock::from_time_t(time)));
high_part = static_cast<uint32_t>(file_time >> 32);
low_part = static_cast<uint32_t>(file_time);
}
chrono::WinSystemClock::time_point to_time_point() const {
const uint64_t filetime =
(static_cast<uint64_t>(high_part) << 32) | low_part;
return chrono::WinSystemClock::from_file_time(filetime);
}
}; };
struct X_ACHIEVEMENT_DETAILS { struct X_ACHIEVEMENT_DETAILS {

View File

@ -108,7 +108,7 @@ X_HRESULT XmpApp::XMPPlayTitlePlaylist(uint32_t playlist_handle,
XELOGD("XMPPlayTitlePlaylist({:08X}, {:08X})", playlist_handle, song_handle); XELOGD("XMPPlayTitlePlaylist({:08X}, {:08X})", playlist_handle, song_handle);
kernel_state_->emulator()->audio_media_player()->Play(playlist_handle, kernel_state_->emulator()->audio_media_player()->Play(playlist_handle,
song_handle, false); song_handle, false);
kernel_state_->BroadcastNotification(kNotificationXmpPlaybackBehaviorChanged, kernel_state_->BroadcastNotification(kXNotificationXmpPlaybackBehaviorChanged,
1); 1);
return X_E_SUCCESS; return X_E_SUCCESS;
} }
@ -214,7 +214,7 @@ X_HRESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
static_cast<PlaybackFlags>(uint32_t(args->flags))); static_cast<PlaybackFlags>(uint32_t(args->flags)));
kernel_state_->BroadcastNotification( kernel_state_->BroadcastNotification(
kNotificationXmpPlaybackBehaviorChanged, 0); kXNotificationXmpPlaybackBehaviorChanged, 0);
return X_E_SUCCESS; return X_E_SUCCESS;
} }
case 0x00070009: { case 0x00070009: {
@ -359,7 +359,7 @@ X_HRESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
PlaybackClient(uint32_t(args->playback_client))); PlaybackClient(uint32_t(args->playback_client)));
kernel_state_->BroadcastNotification( kernel_state_->BroadcastNotification(
kNotificationXmpPlaybackControllerChanged, kXNotificationXmpPlaybackControllerChanged,
kernel_state_->emulator() kernel_state_->emulator()
->audio_media_player() ->audio_media_player()
->IsTitleInPlaybackControl()); ->IsTitleInPlaybackControl());

View File

@ -103,7 +103,14 @@ std::filesystem::path ContentManager::ResolvePackagePath(
// Content path: // Content path:
// content_root/title_id/content_type/data_file_name/ // content_root/title_id/content_type/data_file_name/
auto get_package_path = [&, data, disc_number](const uint32_t title_id) { auto get_package_path = [&, data, disc_number](const uint32_t title_id) {
uint64_t used_xuid = (data.xuid != -1 && data.xuid != 0) ? data.xuid : xuid; uint64_t used_xuid =
(data.xuid != -1 && data.xuid != 0) ? data.xuid.get() : xuid;
// All DLCs are stored in common directory, so we need to override xuid for
// them and probably some other types.
if (data.content_type == XContentType::kMarketplaceContent) {
used_xuid = 0;
}
auto package_root = auto package_root =
ResolvePackageRoot(used_xuid, title_id, data.content_type); ResolvePackageRoot(used_xuid, title_id, data.content_type);
@ -263,7 +270,8 @@ X_RESULT ContentManager::WriteContentHeaderFile(const uint64_t xuid,
if (data.xuid == -1) { if (data.xuid == -1) {
data.xuid = xuid; data.xuid = xuid;
} }
uint64_t used_xuid = (data.xuid != -1 && data.xuid != 0) ? data.xuid : xuid; uint64_t used_xuid =
(data.xuid != -1 && data.xuid != 0) ? data.xuid.get() : xuid;
auto header_path = ResolvePackageHeaderPath(data.file_name(), used_xuid, auto header_path = ResolvePackageHeaderPath(data.file_name(), used_xuid,
data.title_id, data.content_type); data.title_id, data.content_type);

View File

@ -253,9 +253,12 @@ void ProfileManager::ModifyGamertag(const uint64_t xuid, std::string gamertag) {
DismountProfile(xuid); DismountProfile(xuid);
} }
bool ProfileManager::MountProfile(const uint64_t xuid) { bool ProfileManager::MountProfile(const uint64_t xuid, std::string mount_path) {
std::filesystem::path profile_path = GetProfilePath(xuid); std::filesystem::path profile_path = GetProfilePath(xuid);
std::string mount_path = fmt::format("{:016X}", xuid) + ':'; if (mount_path.empty()) {
mount_path = fmt::format("{:016X}", xuid);
}
mount_path += ':';
auto device = auto device =
std::make_unique<vfs::HostPathDevice>(mount_path, profile_path, false); std::make_unique<vfs::HostPathDevice>(mount_path, profile_path, false);
@ -314,8 +317,17 @@ void ProfileManager::Login(const uint64_t xuid, const uint8_t user_index,
logged_profiles_[assigned_user_slot] = logged_profiles_[assigned_user_slot] =
std::make_unique<UserProfile>(xuid, &profile); std::make_unique<UserProfile>(xuid, &profile);
if (kernel_state_->emulator()->is_title_open()) {
const kernel::util::XdbfGameData db = kernel_state_->title_xdbf();
if (db.is_valid()) {
kernel_state_->xam_state()->achievement_manager()->LoadTitleAchievements(
xuid, db);
}
}
if (notify) { if (notify) {
kernel_state_->BroadcastNotification(kXNotificationIDSystemSignInChanged, kernel_state_->BroadcastNotification(kXNotificationSystemSignInChanged,
GetUsedUserSlots().to_ulong()); GetUsedUserSlots().to_ulong());
} }
UpdateConfig(xuid, assigned_user_slot); UpdateConfig(xuid, assigned_user_slot);
@ -329,7 +341,7 @@ void ProfileManager::Logout(const uint8_t user_index, bool notify) {
DismountProfile(profile->second->xuid()); DismountProfile(profile->second->xuid());
logged_profiles_.erase(profile); logged_profiles_.erase(profile);
if (notify) { if (notify) {
kernel_state_->BroadcastNotification(kXNotificationIDSystemSignInChanged, kernel_state_->BroadcastNotification(kXNotificationSystemSignInChanged,
GetUsedUserSlots().to_ulong()); GetUsedUserSlots().to_ulong());
} }
UpdateConfig(0, user_index); UpdateConfig(0, user_index);
@ -343,7 +355,7 @@ void ProfileManager::LoginMultiple(
slots_mask |= (1 << slot); slots_mask |= (1 << slot);
} }
kernel_state_->BroadcastNotification(kXNotificationIDSystemSignInChanged, kernel_state_->BroadcastNotification(kXNotificationSystemSignInChanged,
slots_mask); slots_mask);
} }

View File

@ -86,7 +86,7 @@ class ProfileManager {
void ModifyGamertag(const uint64_t xuid, std::string gamertag); void ModifyGamertag(const uint64_t xuid, std::string gamertag);
bool MountProfile(const uint64_t xuid); bool MountProfile(const uint64_t xuid, std::string mount_path = "");
bool DismountProfile(const uint64_t xuid); bool DismountProfile(const uint64_t xuid);
void Login(const uint64_t xuid, const uint8_t user_index = XUserIndexAny, void Login(const uint64_t xuid, const uint8_t user_index = XUserIndexAny,

View File

@ -52,9 +52,9 @@ UserProfile::UserProfile(uint64_t xuid, X_XAMACCOUNTINFO* account_info)
// XPROFILE_GAMER_CONTROL_SENSITIVITY // XPROFILE_GAMER_CONTROL_SENSITIVITY
AddSetting(std::make_unique<UserSetting>(0x10040018, 0)); AddSetting(std::make_unique<UserSetting>(0x10040018, 0));
// Preferred color 1 // Preferred color 1
AddSetting(std::make_unique<UserSetting>(0x1004001D, 0xFFFF0000u)); AddSetting(std::make_unique<UserSetting>(0x1004001D, PREFERRED_COLOR_NONE));
// Preferred color 2 // Preferred color 2
AddSetting(std::make_unique<UserSetting>(0x1004001E, 0xFF00FF00u)); AddSetting(std::make_unique<UserSetting>(0x1004001E, PREFERRED_COLOR_NONE));
// XPROFILE_GAMER_ACTION_AUTO_AIM // XPROFILE_GAMER_ACTION_AUTO_AIM
AddSetting(std::make_unique<UserSetting>(0x10040022, 1)); AddSetting(std::make_unique<UserSetting>(0x10040022, 1));
// XPROFILE_GAMER_ACTION_AUTO_CENTER // XPROFILE_GAMER_ACTION_AUTO_CENTER
@ -170,7 +170,8 @@ void UserProfile::LoadSetting(UserSetting* setting) {
} else { } else {
// Unsupported for now. Other settings aren't per-game and need to be // Unsupported for now. Other settings aren't per-game and need to be
// stored some other way. // stored some other way.
XELOGW("Attempting to load unsupported profile setting from disk"); XELOGW("Attempting to load unsupported profile setting 0x{:08X} from disk",
setting->GetSettingId());
} }
} }
@ -204,7 +205,8 @@ void UserProfile::SaveSetting(UserSetting* setting) {
} else { } else {
// Unsupported for now. Other settings aren't per-game and need to be // Unsupported for now. Other settings aren't per-game and need to be
// stored some other way. // stored some other way.
XELOGW("Attempting to save unsupported profile setting to disk"); XELOGW("Attempting to save unsupported profile setting 0x{:08X} from disk",
setting->GetSettingId());
} }
} }

View File

@ -35,6 +35,21 @@ enum class X_USER_PROFILE_SETTING_SOURCE : uint32_t {
UNKNOWN = 3, UNKNOWN = 3,
}; };
enum PREFERRED_COLOR_OPTIONS : uint32_t {
PREFERRED_COLOR_NONE,
PREFERRED_COLOR_BLACK,
PREFERRED_COLOR_WHITE,
PREFERRED_COLOR_YELLOW,
PREFERRED_COLOR_ORANGE,
PREFERRED_COLOR_PINK,
PREFERRED_COLOR_RED,
PREFERRED_COLOR_PURPLE,
PREFERRED_COLOR_BLUE,
PREFERRED_COLOR_GREEN,
PREFERRED_COLOR_BROWN,
PREFERRED_COLOR_SILVER
};
// Each setting contains 0x18 bytes long header // Each setting contains 0x18 bytes long header
struct X_USER_PROFILE_SETTING_HEADER { struct X_USER_PROFILE_SETTING_HEADER {
xe::be<uint32_t> setting_id; xe::be<uint32_t> setting_id;
@ -165,6 +180,10 @@ class UserProfile {
uint32_t GetSubscriptionTier() const { uint32_t GetSubscriptionTier() const {
return account_info_.GetSubscriptionTier(); return account_info_.GetSubscriptionTier();
} }
void GetPasscode(uint16_t* passcode) const {
std::memcpy(passcode, account_info_.passcode,
sizeof(account_info_.passcode));
};
void AddSetting(std::unique_ptr<UserSetting> setting); void AddSetting(std::unique_ptr<UserSetting> setting);
UserSetting* GetSetting(uint32_t setting_id); UserSetting* GetSetting(uint32_t setting_id);

View File

@ -2,7 +2,7 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2022 Ben Vanik. All rights reserved. * * Copyright 2024 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
@ -17,24 +17,248 @@ namespace xe {
namespace kernel { namespace kernel {
namespace xam { namespace xam {
// Start/End
dword_result_t XamAvatarInitialize_entry( dword_result_t XamAvatarInitialize_entry(
dword_t unk1, // 1, 4, etc dword_t unk1, // 1, 2, 4, etc
dword_t unk2, // 0 or 1 dword_t unk2, // 0 or 1
dword_t processor_number, // for thread creation? dword_t processor_number, // for thread creation?
lpdword_t function_ptrs, // 20b, 5 pointers lpdword_t function_ptrs, // 20b, 5 pointers
lpunknown_t unk5, // ptr in data segment lpunknown_t unk5, // ptr in data segment
dword_t unk6 // flags - 0x00300000, 0x30, etc dword_t unk6 // flags - 0x00300000, 0x30, etc
) { ) {
// This should only work for avatar editor to test avatar rendering
auto title_id = kernel_state()->title_id();
if (title_id == 0x584D07D1) {
return X_STATUS_SUCCESS;
}
// Negative to fail. Game should immediately call XamAvatarShutdown. // Negative to fail. Game should immediately call XamAvatarShutdown.
return ~0u; return ~0u;
} }
DECLARE_XAM_EXPORT1(XamAvatarInitialize, kAvatars, kStub); DECLARE_XAM_EXPORT1(XamAvatarInitialize, kAvatars, kStub);
void XamAvatarShutdown_entry() { void XamAvatarShutdown_entry() {
// No-op. // Calls XMsgStartIORequestEx(0xf3,0x600002,0,0,0,0).
} }
DECLARE_XAM_EXPORT1(XamAvatarShutdown, kAvatars, kStub); DECLARE_XAM_EXPORT1(XamAvatarShutdown, kAvatars, kStub);
// Get & Set
dword_result_t XamAvatarGetManifestLocalUser_entry(dword_t user_index,
lpdword_t avatar_metadata,
qword_t unk3) {
// XMsgStartIORequestEx(0xf3, 0x600003, unk3, stack1, 8, 0)
return X_STATUS_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamAvatarGetManifestLocalUser, kAvatars, kStub);
dword_result_t XamAvatarGetManifestsByXuid_entry(dword_t unk1, qword_t unk2,
qword_t unk3, dword_t unk4,
dword_t unk5, qword_t unk6) {
// Function_818D1738(0x600004,param_6,&uStack_d0,0x90,0)
return X_STATUS_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamAvatarGetManifestsByXuid, kAvatars, kStub)
dword_result_t XamAvatarGetAssetsResultSize_entry(qword_t unk1, qword_t unk2,
dword_t unk3) {
// return XMsgInProcessCall(0xf3,0x600005,local_20,0);
return X_STATUS_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamAvatarGetAssetsResultSize, kAvatars, kStub);
dword_result_t XamAvatarGetAssets_entry(qword_t unk1, word_t unk2, dword_t unk3,
dword_t unk4, dword_t unk5,
qword_t unk6) {
return X_STATUS_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamAvatarGetAssets, kAvatars, kStub);
dword_result_t XamAvatarSetCustomAsset_entry(qword_t unk1, qword_t unk2,
dword_t unk3, dword_t unk4,
dword_t unk5, dword_t unk6) {
// XMsgInProcessCall(0xf3,0x60000a,local_30,0);
return X_STATUS_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamAvatarSetCustomAsset, kAvatars, kStub)
dword_result_t XamAvatarSetManifest_entry(qword_t unk1, qword_t unk2,
qword_t unk3) {
// Function_818D1738(0x600009,param_3,&local_410,0x3ec,0);
return X_STATUS_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamAvatarSetManifest, kAvatars, kStub);
dword_result_t XamAvatarGetMetadataRandom_entry(word_t body_type, word_t unk2,
lpdword_t avatar_metadata,
qword_t unk4) {
// some_function(0x600010,param_4,local_28,0xc,local_30)
return X_STATUS_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamAvatarGetMetadataRandom, kAvatars, kStub);
dword_result_t XamAvatarGetMetadataSignedOutProfileCount_entry(dword_t unk1,
qword_t unk2) {
// Function_818D1738(0x600013,param_2,local_10,4,0);
return X_STATUS_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamAvatarGetMetadataSignedOutProfileCount, kAvatars, kStub);
dword_result_t XamAvatarGetMetadataSignedOutProfile_entry(dword_t unk1,
dword_t unk2,
qword_t unk3) {
// Function_818D1738(0x600014,param_3,local_10,8,0);
return X_STATUS_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamAvatarGetMetadataSignedOutProfile, kAvatars, kStub);
dword_result_t XamAvatarManifestGetBodyType_entry(qword_t body_type) {
/* Notes:
- Calls XMsgStartIORequestEx(0xf3, 0x60000F, unk4, stack1, 0x18,
0x10000000)
- return either char of 1 - male, 2 - female, else - unknown
*/
return '1';
}
DECLARE_XAM_EXPORT1(XamAvatarManifestGetBodyType, kAvatars, kStub);
dword_result_t XamAvatarGetInstrumentation_entry(qword_t unk1, lpdword_t unk2) {
return 1;
}
DECLARE_XAM_EXPORT1(XamAvatarGetInstrumentation, kAvatars, kStub);
dword_result_t XamAvatarGetAssetIcon_entry(lpqword_t unk1, dword_t unk2,
dword_t unk3, dword_t unk4,
qword_t unk5) {
// XMsgStartIORequestEx(0xf3, 0x60000B, unk5, stack1, 0x1C, 0x10000000)
return X_STATUS_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamAvatarGetAssetIcon, kAvatars, kStub);
dword_result_t XamAvatarGetAssetBinary_entry(lpqword_t unk1, dword_t unk2,
dword_t unk3, dword_t unk4,
qword_t unk5) {
// Function_818D1738(0x600008,param_5,&uStack_60,0x1c,auStack_70);
return X_STATUS_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamAvatarGetAssetBinary, kAvatars, kStub);
dword_result_t XamAvatarGetInstalledAssetPackageDescription_entry(
qword_t unk1, qword_t unk2) {
return X_STATUS_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamAvatarGetInstalledAssetPackageDescription, kAvatars,
kStub);
void XamAvatarSetMocks_entry() {
// No-op.
}
DECLARE_XAM_EXPORT1(XamAvatarSetMocks, kAvatars, kStub);
// Animation
const static std::map<uint64_t, std::string> XAnimationTypeMap = {
// Animation Generic Stand
{0x0040000000030003, "Animation Generic Stand 0"},
{0x0040000000040003, "Animation Generic Stand 1"},
{0x0040000000050003, "Animation Generic Stand 2"},
{0x0040000000270003, "Animation Generic Stand 3"},
{0x0040000000280003, "Animation Generic Stand 4"},
{0x0040000000290003, "Animation Generic Stand 5"},
{0x00400000002A0003, "Animation Generic Stand 6"},
{0x00400000002B0003, "Animation Generic Stand 7"},
// Animation Idle
{0x0040000000130001, "Animation Male Idle Looks Around"},
{0x0040000000140001, "Animation Male Idle Stretch"},
{0x0040000000150001, "Animation Male Idle Shifts Weight"},
{0x0040000000260001, "Animation Male Idle Checks Hand"},
{0x0040000000090002, "Animation Female Idle Check Nails"},
{0x00400000000A0002, "Animation Female Idle Looks Around"},
{0x00400000000B0002, "Animation Female Idle Shifts Weight"},
{0x00400000000C0002, "Animation Female Idle Fixes Shoe"},
};
// https://github.com/xenia-canary/xenia-canary/commit/212c99eee2724de15f471148d10197d89794ff32
dword_result_t XamAvatarLoadAnimation_entry(lpqword_t asset_id_ptr,
dword_t flags, dword_t output,
dword_t unk1) {
/* Notes:
- Calls XMsgStartIORequestEx(0xf3, 0x60000F, unk4, stack1, 0x18,
0x10000000)
- I cannot find an error code for when asset_id_ptr returns nullptr. It
could be due to decompiling issues
*/
assert_true(asset_id_ptr);
std::string summary = "Request to load avatar animation: ";
if (XAnimationTypeMap.find(*asset_id_ptr) != XAnimationTypeMap.cend()) {
summary += XAnimationTypeMap.at(*asset_id_ptr);
} else {
summary += fmt::format("Unknown animation {}",
static_cast<uint64_t>(*asset_id_ptr));
}
XELOGD("{}", summary);
return X_STATUS_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamAvatarLoadAnimation, kAvatars, kStub);
// Enum
dword_result_t XamAvatarBeginEnumAssets_entry(qword_t unk1, word_t unk2,
dword_t unk3, word_t unk4,
word_t unk5, qword_t unk6) {
// XMsgStartIORequestEx(0xf3, 0x60000c, unkn6, stack1, 0x14, stack2)
return X_STATUS_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamAvatarBeginEnumAssets, kAvatars, kStub);
dword_result_t XamAvatarEnumAssets_entry(lpqword_t unk1, dword_t unk2,
qword_t unk3) {
// XMsgStartIORequestEx(0xf3, 0x60000d, unk3, stack1, 8, stack2)
return X_E_NO_MORE_FILES; // Stop it from calling endlessly
}
DECLARE_XAM_EXPORT1(XamAvatarEnumAssets, kAvatars, kStub);
dword_result_t XamAvatarEndEnumAssets_entry(qword_t unk1) {
// some_function(0x60000e,param_1,0,0,local_10)
return X_STATUS_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamAvatarEndEnumAssets, kAvatars, kStub);
// Other
dword_result_t XamAvatarGenerateMipMaps_entry(qword_t unk1, qword_t unk2,
dword_t unk3, dword_t unk4,
qword_t unk5) {
// Function_818D1738(0x600007,param_5,local_30,0x10,local_40);
return X_STATUS_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamAvatarGenerateMipMaps, kAvatars, kStub);
dword_result_t XamAvatarWearNow_entry(qword_t unk1, lpdword_t unk2,
qword_t unk3) {
return X_STATUS_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamAvatarWearNow, kAvatars, kStub);
dword_result_t XamAvatarReinstallAwardedAsset_entry(qword_t unk1, qword_t unk2,
qword_t unk3) {
return X_STATUS_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamAvatarReinstallAwardedAsset, kAvatars, kStub);
} // namespace xam } // namespace xam
} // namespace kernel } // namespace kernel
} // namespace xe } // namespace xe

View File

@ -74,9 +74,9 @@ DECLARE_XAM_EXPORT2(XamContentGetLicenseMask, kContent, kStub, kHighFrequency);
dword_result_t XamContentResolve_entry(dword_t user_index, dword_result_t XamContentResolve_entry(dword_t user_index,
lpvoid_t content_data_ptr, lpvoid_t content_data_ptr,
lpunknown_t buffer_ptr, lpvoid_t buffer_ptr, dword_t buffer_size,
dword_t buffer_size, dword_t unk1, dword_t unk1, dword_t unk2,
dword_t unk2, dword_t unk3) { dword_t unk3) {
auto content_data = content_data_ptr.as<XCONTENT_DATA*>(); auto content_data = content_data_ptr.as<XCONTENT_DATA*>();
// Result of buffer_ptr is sent to RtlInitAnsiString. // Result of buffer_ptr is sent to RtlInitAnsiString.
@ -351,7 +351,7 @@ dword_result_t XamContentOpenFile_entry(dword_t user_index,
DECLARE_XAM_EXPORT1(XamContentOpenFile, kContent, kStub); DECLARE_XAM_EXPORT1(XamContentOpenFile, kContent, kStub);
dword_result_t XamContentFlush_entry(lpstring_t root_name, dword_result_t XamContentFlush_entry(lpstring_t root_name,
lpunknown_t overlapped_ptr) { lpvoid_t overlapped_ptr) {
X_RESULT result = X_ERROR_SUCCESS; X_RESULT result = X_ERROR_SUCCESS;
if (overlapped_ptr) { if (overlapped_ptr) {
kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result); kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result);
@ -363,7 +363,7 @@ dword_result_t XamContentFlush_entry(lpstring_t root_name,
DECLARE_XAM_EXPORT1(XamContentFlush, kContent, kStub); DECLARE_XAM_EXPORT1(XamContentFlush, kContent, kStub);
dword_result_t XamContentClose_entry(lpstring_t root_name, dword_result_t XamContentClose_entry(lpstring_t root_name,
lpunknown_t overlapped_ptr) { lpvoid_t overlapped_ptr) {
// Closes a previously opened root from XamContentCreate*. // Closes a previously opened root from XamContentCreate*.
auto result = auto result =
kernel_state()->content_manager()->CloseContent(root_name.value()); kernel_state()->content_manager()->CloseContent(root_name.value());
@ -381,7 +381,7 @@ dword_result_t XamContentGetCreator_entry(dword_t user_index,
lpvoid_t content_data_ptr, lpvoid_t content_data_ptr,
lpdword_t is_creator_ptr, lpdword_t is_creator_ptr,
lpqword_t creator_xuid_ptr, lpqword_t creator_xuid_ptr,
lpunknown_t overlapped_ptr) { lpvoid_t overlapped_ptr) {
if (!is_creator_ptr) { if (!is_creator_ptr) {
return X_ERROR_INVALID_PARAMETER; return X_ERROR_INVALID_PARAMETER;
} }
@ -449,7 +449,7 @@ dword_result_t XamContentGetThumbnail_entry(dword_t user_index,
lpvoid_t content_data_ptr, lpvoid_t content_data_ptr,
lpvoid_t buffer_ptr, lpvoid_t buffer_ptr,
lpdword_t buffer_size_ptr, lpdword_t buffer_size_ptr,
lpunknown_t overlapped_ptr) { lpvoid_t overlapped_ptr) {
const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index); const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index);
if (!user) { if (!user) {
@ -494,7 +494,7 @@ dword_result_t XamContentSetThumbnail_entry(dword_t user_index,
lpvoid_t content_data_ptr, lpvoid_t content_data_ptr,
lpvoid_t buffer_ptr, lpvoid_t buffer_ptr,
dword_t buffer_size, dword_t buffer_size,
lpunknown_t overlapped_ptr) { lpvoid_t overlapped_ptr) {
const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index); const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index);
if (!user) { if (!user) {
@ -518,10 +518,15 @@ dword_result_t XamContentSetThumbnail_entry(dword_t user_index,
} }
DECLARE_XAM_EXPORT1(XamContentSetThumbnail, kContent, kImplemented); DECLARE_XAM_EXPORT1(XamContentSetThumbnail, kContent, kImplemented);
dword_result_t XamContentDelete_entry(dword_t user_index, dword_result_t xeXamContentDelete(dword_t user_index, lpvoid_t content_data_ptr,
lpvoid_t content_data_ptr, dword_t content_data_size,
lpunknown_t overlapped_ptr) { lpvoid_t overlapped_ptr) {
uint64_t xuid = 0; uint64_t xuid = 0;
XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();
if (content_data_size == sizeof(XCONTENT_AGGREGATE_DATA)) {
content_data = *content_data_ptr.as<XCONTENT_AGGREGATE_DATA*>();
}
if (user_index != XUserIndexNone) { if (user_index != XUserIndexNone) {
const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index); const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index);
@ -532,8 +537,6 @@ dword_result_t XamContentDelete_entry(dword_t user_index,
xuid = user->xuid(); xuid = user->xuid();
} }
XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();
auto result = auto result =
kernel_state()->content_manager()->DeleteContent(xuid, content_data); kernel_state()->content_manager()->DeleteContent(xuid, content_data);
@ -544,14 +547,22 @@ dword_result_t XamContentDelete_entry(dword_t user_index,
return result; return result;
} }
} }
dword_result_t XamContentDelete_entry(dword_t user_index,
lpvoid_t content_data_ptr,
lpvoid_t overlapped_ptr) {
return xeXamContentDelete(user_index, content_data_ptr, sizeof(XCONTENT_DATA),
overlapped_ptr);
}
DECLARE_XAM_EXPORT1(XamContentDelete, kContent, kImplemented); DECLARE_XAM_EXPORT1(XamContentDelete, kContent, kImplemented);
dword_result_t XamContentDeleteInternal_entry(lpvoid_t content_data_ptr, dword_result_t XamContentDeleteInternal_entry(lpvoid_t content_data_ptr,
lpunknown_t overlapped_ptr) { lpvoid_t overlapped_ptr) {
// INFO: Analysis of xam.xex shows that "internal" functions are wrappers with // INFO: Analysis of xam.xex shows that "internal" functions are wrappers with
// 0xFE as user_index // 0xFE as user_index.
return XamContentDelete_entry(XUserIndexNone, content_data_ptr, // In XAM content size is set to 0x200.
overlapped_ptr); return xeXamContentDelete(XUserIndexNone, content_data_ptr,
sizeof(XCONTENT_AGGREGATE_DATA), overlapped_ptr);
} }
DECLARE_XAM_EXPORT1(XamContentDeleteInternal, kContent, kImplemented); DECLARE_XAM_EXPORT1(XamContentDeleteInternal, kContent, kImplemented);
@ -621,8 +632,8 @@ DECLARE_XAM_EXPORT1(XamLoaderGetMediaInfoEx, kContent, kStub);
dword_result_t XamContentLaunchImageFromFileInternal_entry( dword_result_t XamContentLaunchImageFromFileInternal_entry(
lpstring_t image_location, lpstring_t xex_name, dword_t unk) { lpstring_t image_location, lpstring_t xex_name, dword_t unk) {
const std::string image_path = image_location; const std::string image_path = static_cast<std::string>(image_location);
const std::string xex_name_ = xex_name; const std::string xex_name_ = static_cast<std::string>(xex_name);
vfs::Entry* entry = kernel_state()->file_system()->ResolvePath(image_path); vfs::Entry* entry = kernel_state()->file_system()->ResolvePath(image_path);
@ -696,7 +707,7 @@ dword_result_t XamContentLaunchImageInternal_entry(lpvoid_t content_data_ptr,
auto& loader_data = xam->loader_data(); auto& loader_data = xam->loader_data();
loader_data.host_path = xe::path_to_utf8(host_path); loader_data.host_path = xe::path_to_utf8(host_path);
loader_data.launch_path = xex_path; loader_data.launch_path = xex_path.value();
xam->SaveLoaderData(); xam->SaveLoaderData();

View File

@ -115,8 +115,9 @@ dword_result_t XamContentAggregateCreateEnumerator_entry(qword_t xuid,
for (auto& title_id : title_ids) { for (auto& title_id : title_ids) {
// Get all content data. // Get all content data.
auto content_datas = kernel_state()->content_manager()->ListContent( auto content_datas = kernel_state()->content_manager()->ListContent(
static_cast<uint32_t>(DummyDeviceId::HDD), xuid == -1 ? 0 : xuid, static_cast<uint32_t>(DummyDeviceId::HDD),
title_id, content_type_enum); xuid == -1 ? 0 : static_cast<uint64_t>(xuid), title_id,
content_type_enum);
for (const auto& content_data : content_datas) { for (const auto& content_data : content_datas) {
auto item = e->AppendItem(); auto item = e->AppendItem();
assert_not_null(item); assert_not_null(item);

View File

@ -167,6 +167,18 @@ dword_result_t XamProfileEnumerate_entry(dword_t handle, dword_t flags,
} }
DECLARE_XAM_EXPORT1(XamProfileEnumerate, kNone, kImplemented); DECLARE_XAM_EXPORT1(XamProfileEnumerate, kNone, kImplemented);
dword_result_t EnumerateMediaObjects_entry() { return X_E_NOT_IMPLEMENTED; }
DECLARE_XAM_EXPORT1(EnumerateMediaObjects, kNone, kStub);
dword_result_t EnumerateMediaObjects__entry() { return X_E_NOT_IMPLEMENTED; }
DECLARE_XAM_EXPORT1(EnumerateMediaObjects_, kNone, kStub);
dword_result_t EnumerateMediaObjects_0_entry() { return X_E_NOT_IMPLEMENTED; }
DECLARE_XAM_EXPORT1(EnumerateMediaObjects_0, kNone, kStub);
dword_result_t EnumerateMediaObjects_1_entry() { return X_E_NOT_IMPLEMENTED; }
DECLARE_XAM_EXPORT1(EnumerateMediaObjects_1, kNone, kStub);
} // namespace xam } // namespace xam
} // namespace kernel } // namespace kernel
} // namespace xe } // namespace xe

View File

@ -27,10 +27,6 @@
#include "xenia/ui/windowed_app_context.h" #include "xenia/ui/windowed_app_context.h"
#include "xenia/xbox.h" #include "xenia/xbox.h"
#if XE_PLATFORM_WIN32
#include "xenia/base/platform_win.h"
#endif
#include "third_party/fmt/include/fmt/format.h" #include "third_party/fmt/include/fmt/format.h"
#include "third_party/fmt/include/fmt/xchar.h" #include "third_party/fmt/include/fmt/xchar.h"
@ -91,51 +87,37 @@ dword_result_t XamGetOnlineSchema_entry() {
} }
DECLARE_XAM_EXPORT1(XamGetOnlineSchema, kNone, kImplemented); DECLARE_XAM_EXPORT1(XamGetOnlineSchema, kNone, kImplemented);
#if XE_PLATFORM_WIN32 void XamFormatDateString_entry(dword_t locale_format, qword_t filetime,
static SYSTEMTIME xeGetLocalSystemTime(uint64_t filetime) {
FILETIME t;
t.dwHighDateTime = filetime >> 32;
t.dwLowDateTime = (uint32_t)filetime;
SYSTEMTIME st;
SYSTEMTIME local_st;
FileTimeToSystemTime(&t, &st);
SystemTimeToTzSpecificLocalTime(NULL, &st, &local_st);
return local_st;
}
#endif
void XamFormatDateString_entry(dword_t unk, qword_t filetime,
lpvoid_t output_buffer, dword_t output_count) { lpvoid_t output_buffer, dword_t output_count) {
std::memset(output_buffer, 0, output_count * sizeof(char16_t)); output_buffer.Zero(output_count * sizeof(char16_t));
// TODO: implement this for other platforms auto tp = xe::chrono::WinSystemClock::to_sys(
#if XE_PLATFORM_WIN32 xe::chrono::WinSystemClock::from_file_time(filetime));
auto st = xeGetLocalSystemTime(filetime); auto dp = date::floor<date::days>(tp);
// TODO: format this depending on users locale? auto year_month_day = date::year_month_day{dp};
auto str = fmt::format(u"{:02d}/{:02d}/{}", st.wMonth, st.wDay, st.wYear);
auto str = fmt::format(u"{:02d}/{:02d}/{}",
static_cast<unsigned>(year_month_day.month()),
static_cast<unsigned>(year_month_day.day()),
static_cast<int>(year_month_day.year()));
xe::string_util::copy_and_swap_truncating(output_buffer.as<char16_t*>(), str, xe::string_util::copy_and_swap_truncating(output_buffer.as<char16_t*>(), str,
output_count); output_count);
#else
assert_always();
#endif
} }
DECLARE_XAM_EXPORT1(XamFormatDateString, kNone, kImplemented); DECLARE_XAM_EXPORT1(XamFormatDateString, kNone, kImplemented);
void XamFormatTimeString_entry(dword_t unk, qword_t filetime, void XamFormatTimeString_entry(dword_t unk, qword_t filetime,
lpvoid_t output_buffer, dword_t output_count) { lpvoid_t output_buffer, dword_t output_count) {
std::memset(output_buffer, 0, output_count * sizeof(char16_t)); output_buffer.Zero(output_count * sizeof(char16_t));
// TODO: implement this for other platforms auto tp = xe::chrono::WinSystemClock::to_sys(
#if XE_PLATFORM_WIN32 xe::chrono::WinSystemClock::from_file_time(filetime));
auto st = xeGetLocalSystemTime(filetime); auto dp = date::floor<date::days>(tp);
// TODO: format this depending on users locale? auto time = date::hh_mm_ss{date::floor<std::chrono::milliseconds>(tp - dp)};
auto str = fmt::format(u"{:02d}:{:02d}", st.wHour, st.wMinute);
auto str = fmt::format(u"{:02d}:{:02d}", time.hours().count(),
time.minutes().count());
xe::string_util::copy_and_swap_truncating(output_buffer.as<char16_t*>(), str, xe::string_util::copy_and_swap_truncating(output_buffer.as<char16_t*>(), str,
output_count); output_count);
#else
assert_always();
#endif
} }
DECLARE_XAM_EXPORT1(XamFormatTimeString, kNone, kImplemented); DECLARE_XAM_EXPORT1(XamFormatTimeString, kNone, kImplemented);
@ -477,20 +459,19 @@ DECLARE_XAM_EXPORT1(XamQueryLiveHiveW, kNone, kStub);
// http://www.noxa.org/blog/2011/02/28/building-an-xbox-360-emulator-part-3-feasibilityos/ // http://www.noxa.org/blog/2011/02/28/building-an-xbox-360-emulator-part-3-feasibilityos/
// http://www.noxa.org/blog/2011/08/13/building-an-xbox-360-emulator-part-5-xex-files/ // http://www.noxa.org/blog/2011/08/13/building-an-xbox-360-emulator-part-5-xex-files/
dword_result_t RtlSleep_entry(dword_t dwMilliseconds, dword_t bAlertable) { dword_result_t RtlSleep_entry(dword_t dwMilliseconds, dword_t bAlertable) {
LARGE_INTEGER delay{}; uint64_t delay{};
// Convert the delay time to 100-nanosecond intervals // Convert the delay time to 100-nanosecond intervals
delay.QuadPart = dwMilliseconds == -1 delay = dwMilliseconds == -1 ? LLONG_MAX
? LLONG_MAX : static_cast<int64_t>(-10000) * dwMilliseconds;
: static_cast<LONGLONG>(-10000) * dwMilliseconds;
X_STATUS result = xboxkrnl::KeDelayExecutionThread( X_STATUS result = xboxkrnl::KeDelayExecutionThread(MODE::UserMode, bAlertable,
MODE::UserMode, bAlertable, (uint64_t*)&delay, nullptr); &delay, nullptr);
// If the delay was interrupted by an APC, keep delaying the thread // If the delay was interrupted by an APC, keep delaying the thread
while (bAlertable && result == X_STATUS_ALERTED) { while (bAlertable && result == X_STATUS_ALERTED) {
result = xboxkrnl::KeDelayExecutionThread(MODE::UserMode, bAlertable, result = xboxkrnl::KeDelayExecutionThread(MODE::UserMode, bAlertable,
(uint64_t*)&delay, nullptr); &delay, nullptr);
} }
return result == X_STATUS_SUCCESS ? X_STATUS_SUCCESS : X_STATUS_USER_APC; return result == X_STATUS_SUCCESS ? X_STATUS_SUCCESS : X_STATUS_USER_APC;
@ -504,7 +485,7 @@ DECLARE_XAM_EXPORT1(SleepEx, kNone, kImplemented);
// https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sleep // https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sleep
void Sleep_entry(dword_t dwMilliseconds) { void Sleep_entry(dword_t dwMilliseconds) {
RtlSleep_entry(dwMilliseconds, FALSE); RtlSleep_entry(dwMilliseconds, false);
} }
DECLARE_XAM_EXPORT1(Sleep, kNone, kImplemented); DECLARE_XAM_EXPORT1(Sleep, kNone, kImplemented);
@ -619,28 +600,29 @@ DECLARE_XAM_EXPORT1(GetCurrentThreadId, kNone, kImplemented);
qword_result_t XapiFormatTimeOut_entry(lpqword_t result, qword_result_t XapiFormatTimeOut_entry(lpqword_t result,
dword_t dwMilliseconds) { dword_t dwMilliseconds) {
LARGE_INTEGER delay{}; if (dwMilliseconds == -1) {
return 0;
}
// Convert the delay time to 100-nanosecond intervals *result = static_cast<int64_t>(-10000) * dwMilliseconds;
delay.QuadPart = return result.host_address();
dwMilliseconds == -1 ? 0 : static_cast<LONGLONG>(-10000) * dwMilliseconds;
return (uint64_t)&delay;
} }
DECLARE_XAM_EXPORT1(XapiFormatTimeOut, kNone, kImplemented); DECLARE_XAM_EXPORT1(XapiFormatTimeOut, kNone, kImplemented);
dword_result_t WaitForSingleObjectEx_entry(dword_t hHandle, dword_result_t WaitForSingleObjectEx_entry(dword_t hHandle,
dword_t dwMilliseconds, dword_t dwMilliseconds,
dword_t bAlertable) { dword_t bAlertable) {
uint64_t* timeout = nullptr; // TODO(Gliniak): Figure it out to be less janky.
uint64_t timeout_ptr = XapiFormatTimeOut_entry(timeout, dwMilliseconds); uint64_t timeout;
uint64_t* timeout_ptr = reinterpret_cast<uint64_t*>(
static_cast<uint64_t>(XapiFormatTimeOut_entry(&timeout, dwMilliseconds)));
X_STATUS result = xe::kernel::xboxkrnl::NtWaitForSingleObjectEx( X_STATUS result =
hHandle, 1, bAlertable, &timeout_ptr); xboxkrnl::NtWaitForSingleObjectEx(hHandle, 1, bAlertable, timeout_ptr);
while (bAlertable && result == X_STATUS_ALERTED) { while (bAlertable && result == X_STATUS_ALERTED) {
result = xe::kernel::xboxkrnl::NtWaitForSingleObjectEx( result =
hHandle, 1, bAlertable, &timeout_ptr); xboxkrnl::NtWaitForSingleObjectEx(hHandle, 1, bAlertable, timeout_ptr);
} }
RtlSetLastNTError_entry(result); RtlSetLastNTError_entry(result);
@ -716,6 +698,9 @@ dword_result_t RtlRandom_entry(lpdword_t seed_out) {
DECLARE_XAM_EXPORT1(RtlRandom, kNone, kImplemented); DECLARE_XAM_EXPORT1(RtlRandom, kNone, kImplemented);
dword_result_t Refresh_entry() { return X_ERROR_SUCCESS; }
DECLARE_XAM_EXPORT1(Refresh, kNone, kStub);
} // namespace xam } // namespace xam
} // namespace kernel } // namespace kernel
} // namespace xe } // namespace xe

View File

@ -21,16 +21,11 @@ namespace kernel {
namespace xam { namespace xam {
using xe::hid::X_INPUT_CAPABILITIES; using xe::hid::X_INPUT_CAPABILITIES;
using xe::hid::X_INPUT_FLAG;
using xe::hid::X_INPUT_KEYSTROKE; using xe::hid::X_INPUT_KEYSTROKE;
using xe::hid::X_INPUT_STATE; using xe::hid::X_INPUT_STATE;
using xe::hid::X_INPUT_VIBRATION; using xe::hid::X_INPUT_VIBRATION;
constexpr uint32_t XINPUT_FLAG_GAMEPAD = 0x01;
constexpr uint32_t XINPUT_FLAG_KEYBOARD = 0x02;
constexpr uint32_t XINPUT_FLAG_MIC = 0x20; // Based on "karaoke" titles
constexpr uint32_t XINPUT_FLAG_ANYDEVICE = 0xFF;
constexpr uint32_t XINPUT_FLAG_ANY_USER = 1 << 30;
dword_result_t XAutomationpUnbindController_entry(dword_t user_index) { dword_result_t XAutomationpUnbindController_entry(dword_t user_index) {
if (user_index >= XUserMaxUserCount) { if (user_index >= XUserMaxUserCount) {
return 0; return 0;
@ -65,7 +60,7 @@ dword_result_t XamInputGetCapabilitiesEx_entry(
caps.Zero(); caps.Zero();
if ((flags & XINPUT_FLAG_ANY_USER) != 0) { if ((flags & X_INPUT_FLAG::X_INPUT_FLAG_ANY_USER) != 0) {
// should trap // should trap
} }
@ -73,21 +68,22 @@ dword_result_t XamInputGetCapabilitiesEx_entry(
// should trap // should trap
} }
if ((flags & XINPUT_FLAG_ANYDEVICE) && (flags & XINPUT_FLAG_GAMEPAD) == 0) {
// Ignore any query for other types of devices.
return X_ERROR_DEVICE_NOT_CONNECTED;
}
uint32_t actual_user_index = user_index; uint32_t actual_user_index = user_index;
if ((actual_user_index & XUserIndexAny) == XUserIndexAny || if ((actual_user_index & XUserIndexAny) == XUserIndexAny ||
(flags & XINPUT_FLAG_ANY_USER)) { (flags & X_INPUT_FLAG::X_INPUT_FLAG_ANY_USER)) {
// Always pin user to 0. // Always pin user to 0.
actual_user_index = 0; actual_user_index = 0;
} }
uint32_t actual_flags = flags;
if (!flags) {
actual_flags = X_INPUT_FLAG::X_INPUT_FLAG_GAMEPAD |
X_INPUT_FLAG::X_INPUT_FLAG_KEYBOARD;
}
auto input_system = kernel_state()->emulator()->input_system(); auto input_system = kernel_state()->emulator()->input_system();
auto lock = input_system->lock(); auto lock = input_system->lock();
return input_system->GetCapabilities(actual_user_index, flags, caps); return input_system->GetCapabilities(actual_user_index, actual_flags, caps);
} }
DECLARE_XAM_EXPORT1(XamInputGetCapabilitiesEx, kInput, kSketchy); DECLARE_XAM_EXPORT1(XamInputGetCapabilitiesEx, kInput, kSketchy);
@ -112,22 +108,19 @@ dword_result_t XamInputGetState_entry(dword_t user_index, dword_t flags,
// Games call this with a NULL state ptr, probably as a query. // Games call this with a NULL state ptr, probably as a query.
if ((flags & XINPUT_FLAG_ANYDEVICE) && (flags & XINPUT_FLAG_GAMEPAD) == 0) {
// Ignore any query for other types of devices.
return X_ERROR_DEVICE_NOT_CONNECTED;
}
uint32_t actual_user_index = user_index; uint32_t actual_user_index = user_index;
// chrispy: change this, logic is not right // chrispy: change this, logic is not right
if ((actual_user_index & XUserIndexAny) == XUserIndexAny || if ((actual_user_index & XUserIndexAny) == XUserIndexAny ||
(flags & XINPUT_FLAG_ANY_USER)) { (flags & X_INPUT_FLAG::X_INPUT_FLAG_ANY_USER)) {
// Always pin user to 0. // Always pin user to 0.
actual_user_index = 0; actual_user_index = 0;
} }
auto input_system = kernel_state()->emulator()->input_system(); auto input_system = kernel_state()->emulator()->input_system();
auto lock = input_system->lock(); auto lock = input_system->lock();
return input_system->GetState(user_index, input_state); return input_system->GetState(
user_index, !flags ? X_INPUT_FLAG::X_INPUT_FLAG_GAMEPAD : flags,
input_state);
} }
DECLARE_XAM_EXPORT2(XamInputGetState, kInput, kImplemented, kHighFrequency); DECLARE_XAM_EXPORT2(XamInputGetState, kInput, kImplemented, kHighFrequency);
@ -160,14 +153,9 @@ dword_result_t XamInputGetKeystroke_entry(
return X_ERROR_BAD_ARGUMENTS; return X_ERROR_BAD_ARGUMENTS;
} }
if ((flags & XINPUT_FLAG_ANYDEVICE) && (flags & XINPUT_FLAG_GAMEPAD) == 0) {
// Ignore any query for other types of devices.
return X_ERROR_DEVICE_NOT_CONNECTED;
}
uint32_t actual_user_index = user_index; uint32_t actual_user_index = user_index;
if ((actual_user_index & XUserIndexAny) == XUserIndexAny || if ((actual_user_index & XUserIndexAny) == XUserIndexAny ||
(flags & XINPUT_FLAG_ANY_USER)) { (flags & X_INPUT_FLAG::X_INPUT_FLAG_ANY_USER)) {
// Always pin user to 0. // Always pin user to 0.
actual_user_index = 0; actual_user_index = 0;
} }
@ -186,11 +174,6 @@ dword_result_t XamInputGetKeystrokeEx_entry(
return X_ERROR_BAD_ARGUMENTS; return X_ERROR_BAD_ARGUMENTS;
} }
if ((flags & XINPUT_FLAG_ANYDEVICE) && (flags & XINPUT_FLAG_GAMEPAD) == 0) {
// Ignore any query for other types of devices.
return X_ERROR_DEVICE_NOT_CONNECTED;
}
uint32_t user_index = *user_index_ptr; uint32_t user_index = *user_index_ptr;
auto input_system = kernel_state()->emulator()->input_system(); auto input_system = kernel_state()->emulator()->input_system();
auto lock = input_system->lock(); auto lock = input_system->lock();
@ -199,7 +182,7 @@ dword_result_t XamInputGetKeystrokeEx_entry(
user_index = 0; user_index = 0;
} }
if (flags & XINPUT_FLAG_ANY_USER) { if (flags & X_INPUT_FLAG::X_INPUT_FLAG_ANY_USER) {
// That flag means we should iterate over every connected controller and // That flag means we should iterate over every connected controller and
// check which one have pending request. // check which one have pending request.
auto result = X_ERROR_DEVICE_NOT_CONNECTED; auto result = X_ERROR_DEVICE_NOT_CONNECTED;
@ -224,8 +207,9 @@ dword_result_t XamInputGetKeystrokeEx_entry(
} }
DECLARE_XAM_EXPORT1(XamInputGetKeystrokeEx, kInput, kImplemented); DECLARE_XAM_EXPORT1(XamInputGetKeystrokeEx, kInput, kImplemented);
X_HRESULT_result_t XamUserGetDeviceContext_entry(dword_t user_index, X_HRESULT_result_t XamUserGetDeviceContext_entry(
dword_t unk, dword_t user_index,
dword_t unk, // It's set to 3 for a big button
lpdword_t out_ptr) { lpdword_t out_ptr) {
// Games check the result - usually with some masking. // Games check the result - usually with some masking.
// If this function fails they assume zero, so let's fail AND // If this function fails they assume zero, so let's fail AND

View File

@ -111,14 +111,14 @@ void XamModule::SaveLoaderData() {
std::filesystem::path host_path = loader_data_.host_path; std::filesystem::path host_path = loader_data_.host_path;
std::string launch_path = loader_data_.launch_path; std::string launch_path = loader_data_.launch_path;
auto remove_prefix = [&launch_path](std::string& prefix) { auto remove_prefix = [&launch_path](std::string_view prefix) {
if (launch_path.compare(0, prefix.length(), prefix) == 0) { if (launch_path.compare(0, prefix.length(), prefix) == 0) {
launch_path = launch_path.substr(prefix.length()); launch_path = launch_path.substr(prefix.length());
} }
}; };
remove_prefix(std::string("game:\\")); remove_prefix("game:\\");
remove_prefix(std::string("d:\\")); remove_prefix("d:\\");
if (host_path.extension() == ".xex") { if (host_path.extension() == ".xex") {
host_path.remove_filename(); host_path.remove_filename();

View File

@ -24,6 +24,7 @@ XE_MODULE_EXPORT_GROUP(xam, Net)
XE_MODULE_EXPORT_GROUP(xam, Notify) XE_MODULE_EXPORT_GROUP(xam, Notify)
XE_MODULE_EXPORT_GROUP(xam, NUI) XE_MODULE_EXPORT_GROUP(xam, NUI)
XE_MODULE_EXPORT_GROUP(xam, Party) XE_MODULE_EXPORT_GROUP(xam, Party)
XE_MODULE_EXPORT_GROUP(xam, Profile)
XE_MODULE_EXPORT_GROUP(xam, Task) XE_MODULE_EXPORT_GROUP(xam, Task)
XE_MODULE_EXPORT_GROUP(xam, UI) XE_MODULE_EXPORT_GROUP(xam, UI)
XE_MODULE_EXPORT_GROUP(xam, User) XE_MODULE_EXPORT_GROUP(xam, User)

View File

@ -722,7 +722,7 @@ dword_result_t NetDll_getsockopt_entry(dword_t caller, dword_t socket_handle,
return -1; return -1;
} }
int native_len = *optlen; uint32_t native_len = *optlen;
X_STATUS status = socket->GetOption(level, optname, optval_ptr, &native_len); X_STATUS status = socket->GetOption(level, optname, optval_ptr, &native_len);
return XSUCCEEDED(status) ? 0 : -1; return XSUCCEEDED(status) ? 0 : -1;
} }
@ -1083,8 +1083,8 @@ dword_result_t NetDll_XNetRegisterKey_entry(dword_t caller, lpdword_t key_id,
} }
DECLARE_XAM_EXPORT1(NetDll_XNetRegisterKey, kNetworking, kStub); DECLARE_XAM_EXPORT1(NetDll_XNetRegisterKey, kNetworking, kStub);
dword_result_t NetDll_XNetUnregisterKey_entry(dword_t caller, lpdword_t key_id, dword_result_t NetDll_XNetUnregisterKey_entry(dword_t caller,
lpdword_t exchange_key) { lpdword_t key_id) {
return 0; return 0;
} }
DECLARE_XAM_EXPORT1(NetDll_XNetUnregisterKey, kNetworking, kStub); DECLARE_XAM_EXPORT1(NetDll_XNetUnregisterKey, kNetworking, kStub);

View File

@ -0,0 +1,75 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/util/shim_utils.h"
#include "xenia/kernel/xam/xam_private.h"
namespace xe {
namespace kernel {
namespace xam {
dword_result_t XamProfileFindAccount_entry(
qword_t offline_xuid, pointer_t<X_XAMACCOUNTINFO> account_ptr,
lpdword_t device_id) {
if (!account_ptr) {
return X_ERROR_INVALID_PARAMETER;
}
account_ptr.Zero();
const auto& account =
kernel_state()->xam_state()->profile_manager()->GetAccount(offline_xuid);
if (!account) {
return X_ERROR_NO_SUCH_USER;
}
std::memcpy(account_ptr, &account, sizeof(X_XAMACCOUNTINFO));
xe::string_util::copy_and_swap_truncating(
account_ptr->gamertag, account->gamertag, sizeof(account->gamertag));
if (device_id) {
*device_id = 1;
}
return X_ERROR_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamProfileFindAccount, kUserProfiles, kImplemented);
dword_result_t XamProfileOpen_entry(qword_t xuid, lpstring_t mount_path,
dword_t flags, lpvoid_t content_data) {
/* Notes:
- If xuid is not local then returns X_ERROR_INVALID_PARAMETER
*/
const bool result =
kernel_state()->xam_state()->profile_manager()->MountProfile(
xuid, mount_path.value());
return result ? X_ERROR_SUCCESS : X_ERROR_INVALID_PARAMETER;
}
DECLARE_XAM_EXPORT1(XamProfileOpen, kNone, kImplemented);
dword_result_t XamProfileCreate_entry(dword_t flags, lpdword_t device_id,
qword_t xuid,
pointer_t<X_XAMACCOUNTINFO> account,
dword_t unk1, dword_t unk2, dword_t unk3,
dword_t unk4) {
// **unk4
return X_ERROR_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamProfileCreate, kNone, kStub);
} // namespace xam
} // namespace kernel
} // namespace xe
DECLARE_XAM_EMPTY_REGISTER_EXPORTS(Profile);

View File

@ -85,7 +85,7 @@ X_RESULT xeXamDispatchDialog(T* dialog,
std::function<X_RESULT(T*)> close_callback, std::function<X_RESULT(T*)> close_callback,
uint32_t overlapped) { uint32_t overlapped) {
auto pre = []() { auto pre = []() {
kernel_state()->BroadcastNotification(kXNotificationIDSystemUI, true); kernel_state()->BroadcastNotification(kXNotificationSystemUI, true);
}; };
auto run = [dialog, close_callback]() -> X_RESULT { auto run = [dialog, close_callback]() -> X_RESULT {
X_RESULT result; X_RESULT result;
@ -108,7 +108,7 @@ X_RESULT xeXamDispatchDialog(T* dialog,
}; };
auto post = []() { auto post = []() {
xe::threading::Sleep(std::chrono::milliseconds(100)); xe::threading::Sleep(std::chrono::milliseconds(100));
kernel_state()->BroadcastNotification(kXNotificationIDSystemUI, false); kernel_state()->BroadcastNotification(kXNotificationSystemUI, false);
}; };
if (!overlapped) { if (!overlapped) {
pre(); pre();
@ -126,7 +126,7 @@ X_RESULT xeXamDispatchDialogEx(
T* dialog, std::function<X_RESULT(T*, uint32_t&, uint32_t&)> close_callback, T* dialog, std::function<X_RESULT(T*, uint32_t&, uint32_t&)> close_callback,
uint32_t overlapped) { uint32_t overlapped) {
auto pre = []() { auto pre = []() {
kernel_state()->BroadcastNotification(kXNotificationIDSystemUI, true); kernel_state()->BroadcastNotification(kXNotificationSystemUI, true);
}; };
auto run = [dialog, close_callback](uint32_t& extended_error, auto run = [dialog, close_callback](uint32_t& extended_error,
uint32_t& length) -> X_RESULT { uint32_t& length) -> X_RESULT {
@ -150,7 +150,7 @@ X_RESULT xeXamDispatchDialogEx(
}; };
auto post = []() { auto post = []() {
xe::threading::Sleep(std::chrono::milliseconds(100)); xe::threading::Sleep(std::chrono::milliseconds(100));
kernel_state()->BroadcastNotification(kXNotificationIDSystemUI, false); kernel_state()->BroadcastNotification(kXNotificationSystemUI, false);
}; };
if (!overlapped) { if (!overlapped) {
pre(); pre();
@ -168,11 +168,11 @@ X_RESULT xeXamDispatchDialogEx(
X_RESULT xeXamDispatchHeadless(std::function<X_RESULT()> run_callback, X_RESULT xeXamDispatchHeadless(std::function<X_RESULT()> run_callback,
uint32_t overlapped) { uint32_t overlapped) {
auto pre = []() { auto pre = []() {
kernel_state()->BroadcastNotification(kXNotificationIDSystemUI, true); kernel_state()->BroadcastNotification(kXNotificationSystemUI, true);
}; };
auto post = []() { auto post = []() {
xe::threading::Sleep(std::chrono::milliseconds(100)); xe::threading::Sleep(std::chrono::milliseconds(100));
kernel_state()->BroadcastNotification(kXNotificationIDSystemUI, false); kernel_state()->BroadcastNotification(kXNotificationSystemUI, false);
}; };
if (!overlapped) { if (!overlapped) {
pre(); pre();
@ -190,11 +190,11 @@ X_RESULT xeXamDispatchHeadlessEx(
std::function<X_RESULT(uint32_t&, uint32_t&)> run_callback, std::function<X_RESULT(uint32_t&, uint32_t&)> run_callback,
uint32_t overlapped) { uint32_t overlapped) {
auto pre = []() { auto pre = []() {
kernel_state()->BroadcastNotification(kXNotificationIDSystemUI, true); kernel_state()->BroadcastNotification(kXNotificationSystemUI, true);
}; };
auto post = []() { auto post = []() {
xe::threading::Sleep(std::chrono::milliseconds(100)); xe::threading::Sleep(std::chrono::milliseconds(100));
kernel_state()->BroadcastNotification(kXNotificationIDSystemUI, false); kernel_state()->BroadcastNotification(kXNotificationSystemUI, false);
}; };
if (!overlapped) { if (!overlapped) {
pre(); pre();
@ -213,7 +213,7 @@ X_RESULT xeXamDispatchHeadlessEx(
template <typename T> template <typename T>
X_RESULT xeXamDispatchDialogAsync(T* dialog, X_RESULT xeXamDispatchDialogAsync(T* dialog,
std::function<void(T*)> close_callback) { std::function<void(T*)> close_callback) {
kernel_state()->BroadcastNotification(kXNotificationIDSystemUI, true); kernel_state()->BroadcastNotification(kXNotificationSystemUI, true);
++xam_dialogs_shown_; ++xam_dialogs_shown_;
// Important to pass captured vars by value here since we return from this // Important to pass captured vars by value here since we return from this
@ -226,7 +226,7 @@ X_RESULT xeXamDispatchDialogAsync(T* dialog,
auto run = []() -> void { auto run = []() -> void {
xe::threading::Sleep(std::chrono::milliseconds(100)); xe::threading::Sleep(std::chrono::milliseconds(100));
kernel_state()->BroadcastNotification(kXNotificationIDSystemUI, false); kernel_state()->BroadcastNotification(kXNotificationSystemUI, false);
}; };
std::thread thread(run); std::thread thread(run);
@ -237,7 +237,7 @@ X_RESULT xeXamDispatchDialogAsync(T* dialog,
} }
X_RESULT xeXamDispatchHeadlessAsync(std::function<void()> run_callback) { X_RESULT xeXamDispatchHeadlessAsync(std::function<void()> run_callback) {
kernel_state()->BroadcastNotification(kXNotificationIDSystemUI, true); kernel_state()->BroadcastNotification(kXNotificationSystemUI, true);
++xam_dialogs_shown_; ++xam_dialogs_shown_;
auto display_window = kernel_state()->emulator()->display_window(); auto display_window = kernel_state()->emulator()->display_window();
@ -248,7 +248,7 @@ X_RESULT xeXamDispatchHeadlessAsync(std::function<void()> run_callback) {
auto run = []() -> void { auto run = []() -> void {
xe::threading::Sleep(std::chrono::milliseconds(100)); xe::threading::Sleep(std::chrono::milliseconds(100));
kernel_state()->BroadcastNotification(kXNotificationIDSystemUI, false); kernel_state()->BroadcastNotification(kXNotificationSystemUI, false);
}; };
std::thread thread(run); std::thread thread(run);
@ -320,6 +320,119 @@ class MessageBoxDialog : public XamDialog {
uint32_t chosen_button_ = 0; uint32_t chosen_button_ = 0;
}; };
class ProfilePasscodeDialog : public XamDialog {
public:
const char* labelled_keys_[11] = {"None", "X", "Y", "RB", "LB", "LT",
"RT", "Up", "Down", "Left", "Right"};
const std::map<std::string, uint16_t> keys_map_ = {
{"None", 0},
{"X", X_BUTTON_PASSCODE},
{"Y", Y_BUTTON_PASSCODE},
{"RB", RIGHT_BUMPER_PASSCODE},
{"LB", LEFT_BUMPER_PASSCODE},
{"LT", LEFT_TRIGGER_PASSCODE},
{"RT", RIGHT_TRIGGER_PASSCODE},
{"Up", DPAD_UP_PASSCODE},
{"Down", DPAD_DOWN_PASSCODE},
{"Left", DPAD_LEFT_PASSCODE},
{"Right", DPAD_RIGHT_PASSCODE}};
ProfilePasscodeDialog(xe::ui::ImGuiDrawer* imgui_drawer, std::string& title,
std::string& description, MESSAGEBOX_RESULT* result_ptr)
: XamDialog(imgui_drawer),
title_(title),
description_(description),
result_ptr_(result_ptr) {
std::memset(result_ptr, 0, sizeof(MESSAGEBOX_RESULT));
if (title_.empty()) {
title_ = "Enter Pass Code";
}
if (description_.empty()) {
description_ = "Enter your Xbox LIVE pass code.";
}
}
void DrawPasscodeField(uint8_t key_id) {
const std::string label = fmt::format("##Key {}", key_id);
if (ImGui::BeginCombo(label.c_str(),
labelled_keys_[key_indexes_[key_id]])) {
for (uint8_t key_index = 0; key_index < keys_map_.size(); key_index++) {
bool is_selected = key_id == key_index;
if (ImGui::Selectable(labelled_keys_[key_index], is_selected)) {
key_indexes_[key_id] = key_index;
}
if (is_selected) {
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndCombo();
}
}
void OnDraw(ImGuiIO& io) override {
if (!has_opened_) {
ImGui::OpenPopup(title_.c_str());
has_opened_ = true;
}
if (ImGui::BeginPopupModal(title_.c_str(), nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
if (description_.size()) {
ImGui::Text("%s", description_.c_str());
}
for (uint8_t i = 0; i < passcode_length; i++) {
DrawPasscodeField(i);
// result_ptr_->Passcode[i] =
// keys_map_.at(labelled_keys_[key_indexes_[i]]);
}
ImGui::NewLine();
// We write each key on close to prevent simultaneous dialogs.
if (ImGui::Button("Sign In")) {
for (uint8_t i = 0; i < passcode_length; i++) {
result_ptr_->Passcode[i] =
keys_map_.at(labelled_keys_[key_indexes_[i]]);
}
selected_signed_in_ = true;
Close();
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
Close();
}
}
ImGui::EndPopup();
}
virtual ~ProfilePasscodeDialog() {}
bool SelectedSignedIn() const { return selected_signed_in_; }
private:
bool has_opened_ = false;
bool selected_signed_in_ = false;
std::string title_;
std::string description_;
static const uint8_t passcode_length = sizeof(X_XAMACCOUNTINFO::passcode);
int key_indexes_[passcode_length] = {0, 0, 0, 0};
MESSAGEBOX_RESULT* result_ptr_;
};
class GamertagModifyDialog final : public ui::ImGuiDialog { class GamertagModifyDialog final : public ui::ImGuiDialog {
public: public:
GamertagModifyDialog(ui::ImGuiDrawer* imgui_drawer, GamertagModifyDialog(ui::ImGuiDrawer* imgui_drawer,
@ -394,7 +507,7 @@ struct AchievementInfo {
uint32_t gamerscore; uint32_t gamerscore;
uint32_t image_id; uint32_t image_id;
uint32_t flags; uint32_t flags;
std::chrono::system_clock::time_point unlock_time; std::chrono::local_time<std::chrono::system_clock::duration> unlock_time;
bool IsUnlocked() const { bool IsUnlocked() const {
return (flags & static_cast<uint32_t>(AchievementFlags::kAchieved)) || return (flags & static_cast<uint32_t>(AchievementFlags::kAchieved)) ||
@ -461,12 +574,8 @@ class GameAchievementsDialog final : public XamDialog {
info.unlock_time = {}; info.unlock_time = {};
if (entry.IsUnlocked()) { if (entry.IsUnlocked()) {
const uint64_t filetime_time = info.unlock_time =
(static_cast<uint64_t>(entry.unlock_time.high_part) << 32) | chrono::WinSystemClock::to_local(entry.unlock_time.to_time_point());
entry.unlock_time.low_part;
info.unlock_time = chrono::WinSystemClock::to_sys(
chrono::WinSystemClock::from_file_time(filetime_time));
} }
achievements_info_.insert({info.id, info}); achievements_info_.insert({info.id, info});
@ -533,14 +642,12 @@ class GameAchievementsDialog final : public XamDialog {
ImGui::PushFont(imgui_drawer()->GetTitleFont()); ImGui::PushFont(imgui_drawer()->GetTitleFont());
const auto primary_line_height = ImGui::GetTextLineHeight(); const auto primary_line_height = ImGui::GetTextLineHeight();
ImGui::TextUnformatted( ImGui::Text("%s", GetAchievementTitle(achievement_entry).c_str());
fmt::format("{}", GetAchievementTitle(achievement_entry)).c_str());
ImGui::PopFont(); ImGui::PopFont();
ImGui::PushTextWrapPos(ImGui::GetMainViewport()->Size.x * 0.5f); ImGui::PushTextWrapPos(ImGui::GetMainViewport()->Size.x * 0.5f);
ImGui::TextWrapped( ImGui::TextWrapped("%s",
fmt::format("{}", GetAchievementDescription(achievement_entry)) GetAchievementDescription(achievement_entry).c_str());
.c_str());
ImGui::PopTextWrapPos(); ImGui::PopTextWrapPos();
ImGui::SetCursorPosY(start_drawing_pos.y + default_image_icon_size.x - ImGui::SetCursorPosY(start_drawing_pos.y + default_image_icon_size.x -
@ -763,7 +870,7 @@ class GamesInfoDialog final : public ui::ImGuiDialog {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + textOffsetX); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + textOffsetX);
} }
ImGui::Text(no_entries_message.c_str()); ImGui::Text("%s", no_entries_message.c_str());
ImGui::PopFont(); ImGui::PopFont();
} }
@ -787,13 +894,10 @@ class GamesInfoDialog final : public ui::ImGuiDialog {
static dword_result_t XamShowMessageBoxUi( static dword_result_t XamShowMessageBoxUi(
dword_t user_index, lpu16string_t title_ptr, lpu16string_t text_ptr, dword_t user_index, lpu16string_t title_ptr, lpu16string_t text_ptr,
dword_t button_count, lpdword_t button_ptrs, dword_t active_button, dword_t button_count, lpdword_t button_ptrs, dword_t active_button,
dword_t flags, lpdword_t result_ptr, pointer_t<XAM_OVERLAPPED> overlapped) { dword_t flags, pointer_t<MESSAGEBOX_RESULT> result_ptr,
std::string title; pointer_t<XAM_OVERLAPPED> overlapped) {
if (title_ptr) { std::string title = title_ptr ? xe::to_utf8(title_ptr.value()) : "";
title = xe::to_utf8(title_ptr.value()); std::string text = text_ptr ? xe::to_utf8(text_ptr.value()) : "";
} else {
title = ""; // TODO(gibbed): default title based on flags?
}
std::vector<std::string> buttons; std::vector<std::string> buttons;
for (uint32_t i = 0; i < button_count; ++i) { for (uint32_t i = 0; i < button_count; ++i) {
@ -807,37 +911,53 @@ static dword_result_t XamShowMessageBoxUi(
if (cvars::headless) { if (cvars::headless) {
// Auto-pick the focused button. // Auto-pick the focused button.
auto run = [result_ptr, active_button]() -> X_RESULT { auto run = [result_ptr, active_button]() -> X_RESULT {
*result_ptr = static_cast<uint32_t>(active_button); result_ptr->ButtonPressed = static_cast<uint32_t>(active_button);
return X_ERROR_SUCCESS; return X_ERROR_SUCCESS;
}; };
result = xeXamDispatchHeadless(run, overlapped); result = xeXamDispatchHeadless(run, overlapped);
} else { } else {
// TODO(benvanik): setup icon states.
switch (flags & 0xF) { switch (flags & 0xF) {
case 0: case XMBox_NOICON: {
// config.pszMainIcon = nullptr; } break;
break; case XMBox_ERRORICON: {
case 1: } break;
// config.pszMainIcon = TD_ERROR_ICON; case XMBox_WARNINGICON: {
break; } break;
case 2: case XMBox_ALERTICON: {
// config.pszMainIcon = TD_WARNING_ICON; } break;
break;
case 3:
// config.pszMainIcon = TD_INFORMATION_ICON;
break;
} }
auto close = [result_ptr](MessageBoxDialog* dialog) -> X_RESULT {
*result_ptr = dialog->chosen_button();
return X_ERROR_SUCCESS;
};
const Emulator* emulator = kernel_state()->emulator(); const Emulator* emulator = kernel_state()->emulator();
ui::ImGuiDrawer* imgui_drawer = emulator->imgui_drawer(); ui::ImGuiDrawer* imgui_drawer = emulator->imgui_drawer();
if (flags & XMBox_PASSCODEMODE || flags & XMBox_VERIFYPASSCODEMODE) {
auto close = [result_ptr,
active_button](ProfilePasscodeDialog* dialog) -> X_RESULT {
if (dialog->SelectedSignedIn()) {
// Logged in
return X_ERROR_SUCCESS;
} else {
return X_ERROR_FUNCTION_FAILED;
}
};
result = xeXamDispatchDialog<ProfilePasscodeDialog>(
new ProfilePasscodeDialog(imgui_drawer, title, text, result_ptr),
close, overlapped);
} else {
auto close = [result_ptr](MessageBoxDialog* dialog) -> X_RESULT {
result_ptr->ButtonPressed = dialog->chosen_button();
return X_ERROR_SUCCESS;
};
result = xeXamDispatchDialog<MessageBoxDialog>( result = xeXamDispatchDialog<MessageBoxDialog>(
new MessageBoxDialog(imgui_drawer, title, xe::to_utf8(text_ptr.value()), new MessageBoxDialog(imgui_drawer, title, text, buttons,
buttons, active_button), static_cast<uint32_t>(active_button)),
close, overlapped); close, overlapped);
} }
}
return result; return result;
} }
@ -845,7 +965,8 @@ static dword_result_t XamShowMessageBoxUi(
dword_result_t XamShowMessageBoxUI_entry( dword_result_t XamShowMessageBoxUI_entry(
dword_t user_index, lpu16string_t title_ptr, lpu16string_t text_ptr, dword_t user_index, lpu16string_t title_ptr, lpu16string_t text_ptr,
dword_t button_count, lpdword_t button_ptrs, dword_t active_button, dword_t button_count, lpdword_t button_ptrs, dword_t active_button,
dword_t flags, lpdword_t result_ptr, pointer_t<XAM_OVERLAPPED> overlapped) { dword_t flags, pointer_t<MESSAGEBOX_RESULT> result_ptr,
pointer_t<XAM_OVERLAPPED> overlapped) {
return XamShowMessageBoxUi(user_index, title_ptr, text_ptr, button_count, return XamShowMessageBoxUi(user_index, title_ptr, text_ptr, button_count,
button_ptrs, active_button, flags, result_ptr, button_ptrs, active_button, flags, result_ptr,
overlapped); overlapped);
@ -855,7 +976,8 @@ DECLARE_XAM_EXPORT1(XamShowMessageBoxUI, kUI, kImplemented);
dword_result_t XamShowMessageBoxUIEx_entry( dword_result_t XamShowMessageBoxUIEx_entry(
dword_t user_index, lpu16string_t title_ptr, lpu16string_t text_ptr, dword_t user_index, lpu16string_t title_ptr, lpu16string_t text_ptr,
dword_t button_count, lpdword_t button_ptrs, dword_t active_button, dword_t button_count, lpdword_t button_ptrs, dword_t active_button,
dword_t flags, dword_t unknown_unused, lpdword_t result_ptr, dword_t flags, dword_t unknown_unused,
pointer_t<MESSAGEBOX_RESULT> result_ptr,
pointer_t<XAM_OVERLAPPED> overlapped) { pointer_t<XAM_OVERLAPPED> overlapped) {
return XamShowMessageBoxUi(user_index, title_ptr, text_ptr, button_count, return XamShowMessageBoxUi(user_index, title_ptr, text_ptr, button_count,
button_ptrs, active_button, flags, result_ptr, button_ptrs, active_button, flags, result_ptr,
@ -1016,11 +1138,14 @@ dword_result_t XamShowKeyboardUI_entry(
}; };
const Emulator* emulator = kernel_state()->emulator(); const Emulator* emulator = kernel_state()->emulator();
ui::ImGuiDrawer* imgui_drawer = emulator->imgui_drawer(); ui::ImGuiDrawer* imgui_drawer = emulator->imgui_drawer();
std::string title_str = title ? xe::to_utf8(title.value()) : "";
std::string desc_str = description ? xe::to_utf8(description.value()) : "";
std::string def_text_str =
default_text ? xe::to_utf8(default_text.value()) : "";
result = xeXamDispatchDialogEx<KeyboardInputDialog>( result = xeXamDispatchDialogEx<KeyboardInputDialog>(
new KeyboardInputDialog( new KeyboardInputDialog(imgui_drawer, title_str, desc_str, def_text_str,
imgui_drawer, title ? xe::to_utf8(title.value()) : "",
description ? xe::to_utf8(description.value()) : "",
default_text ? xe::to_utf8(default_text.value()) : "",
buffer_length), buffer_length),
close, overlapped); close, overlapped);
} }
@ -1128,6 +1253,8 @@ DECLARE_XAM_EXPORT1(XamShowCommunitySessionsUI, kNone, kStub);
dword_result_t XamSetDashContext_entry(dword_t value, dword_result_t XamSetDashContext_entry(dword_t value,
const ppc_context_t& ctx) { const ppc_context_t& ctx) {
ctx->kernel_state->dash_context_ = value; ctx->kernel_state->dash_context_ = value;
kernel_state()->BroadcastNotification(
kXNotificationDvdDriveUnknownDashContext, 0);
return 0; return 0;
} }
@ -1139,9 +1266,11 @@ dword_result_t XamGetDashContext_entry(const ppc_context_t& ctx) {
DECLARE_XAM_EXPORT1(XamGetDashContext, kNone, kImplemented); DECLARE_XAM_EXPORT1(XamGetDashContext, kNone, kImplemented);
dword_result_t XamShowMarketplaceUI_entry(dword_t user_index, dword_t ui_type, // https://gitlab.com/GlitchyScripts/xlivelessness/-/blob/master/xlivelessness/xlive/xdefs.hpp?ref_type=heads#L1235
qword_t offer_id, X_HRESULT xeXShowMarketplaceUIEx(dword_t user_index, dword_t ui_type,
dword_t content_types) { qword_t offer_id, dword_t content_types,
unknown_t unk5, unknown_t unk6, unknown_t unk7,
unknown_t unk8) {
// ui_type: // ui_type:
// 0 - view all content for the current title // 0 - view all content for the current title
// 1 - view content specified by offer id // 1 - view content specified by offer id
@ -1166,7 +1295,7 @@ dword_result_t XamShowMarketplaceUI_entry(dword_t user_index, dword_t ui_type,
cvars::license_mask = 1; cvars::license_mask = 1;
kernel_state()->BroadcastNotification( kernel_state()->BroadcastNotification(
kXNotificationIDLiveContentInstalled, 0); kXNotificationLiveContentInstalled, 0);
} }
} }
}; };
@ -1176,15 +1305,59 @@ dword_result_t XamShowMarketplaceUI_entry(dword_t user_index, dword_t ui_type,
cxxopts::OptionNames buttons; cxxopts::OptionNames buttons;
switch (ui_type) { switch (ui_type) {
case 0: case X_MARKETPLACE_ENTRYPOINT::ContentList:
desc = desc =
"Game requested to open marketplace page with all content for the " "Game requested to open marketplace page with all content for the "
"current title ID."; "current title ID.";
break; break;
case 1: case X_MARKETPLACE_ENTRYPOINT::ContentItem:
desc = fmt::format( desc = fmt::format(
"Game requested to open marketplace page for offer ID 0x{:016X}.", "Game requested to open marketplace page for offer ID 0x{:016X}.",
static_cast<uint32_t>(offer_id)); static_cast<uint64_t>(offer_id));
break;
case X_MARKETPLACE_ENTRYPOINT::MembershipList:
desc = fmt::format(
"Game requested to open marketplace page with all xbox live "
"memberships 0x{:016X}.",
static_cast<uint64_t>(offer_id));
break;
case X_MARKETPLACE_ENTRYPOINT::MembershipItem:
desc = fmt::format(
"Game requested to open marketplace page for an xbox live "
"memberships 0x{:016X}.",
static_cast<uint64_t>(offer_id));
break;
case X_MARKETPLACE_ENTRYPOINT::ContentList_Background:
// Used when accessing microsoft points
desc = fmt::format(
"Xbox Marketplace requested access to Microsoft Points offer page "
"0x{:016X}.",
static_cast<uint64_t>(offer_id));
break;
case X_MARKETPLACE_ENTRYPOINT::ContentItem_Background:
// Used when accessing credit card information and calls
// XamShowCreditCardUI
desc = fmt::format(
"Xbox Marketplace requested access to credit card information page "
"0x{:016X}.",
static_cast<uint64_t>(offer_id));
break;
case X_MARKETPLACE_ENTRYPOINT::ForcedNameChangeV1:
// Used by XamShowForcedNameChangeUI v1888
desc = fmt::format("Changing gamertag currently not implemented");
break;
case X_MARKETPLACE_ENTRYPOINT::ForcedNameChangeV2:
// Used by XamShowForcedNameChangeUI NXE and up
desc = fmt::format("Changing gamertag currently not implemented");
break;
case X_MARKETPLACE_ENTRYPOINT::ProfileNameChange:
// Used by dashboard when selecting change gamertag in profile menu
desc = fmt::format("Changing gamertag currently not implemented");
break;
case X_MARKETPLACE_ENTRYPOINT::ActiveDownloads:
// Used in profile tabs when clicking active downloads
desc = fmt::format(
"There are no current plans to download files from xbox servers");
break; break;
default: default:
desc = fmt::format("Unknown marketplace op {:d}", desc = fmt::format("Unknown marketplace op {:d}",
@ -1197,11 +1370,11 @@ dword_result_t XamShowMarketplaceUI_entry(dword_t user_index, dword_t ui_type,
"installed manually using File -> Install Content."; "installed manually using File -> Install Content.";
switch (ui_type) { switch (ui_type) {
case 0: case X_MARKETPLACE_ENTRYPOINT::ContentList:
default: default:
buttons.push_back("OK"); buttons.push_back("OK");
break; break;
case 1: case X_MARKETPLACE_ENTRYPOINT::ContentItem:
desc += desc +=
"\n\nTo start trial games in full mode, set license_mask to 1 in " "\n\nTo start trial games in full mode, set license_mask to 1 in "
"Xenia config file.\n\nDo you wish to change license_mask to 1 for " "Xenia config file.\n\nDo you wish to change license_mask to 1 for "
@ -1216,8 +1389,26 @@ dword_result_t XamShowMarketplaceUI_entry(dword_t user_index, dword_t ui_type,
return xeXamDispatchDialogAsync<MessageBoxDialog>( return xeXamDispatchDialogAsync<MessageBoxDialog>(
new MessageBoxDialog(imgui_drawer, title, desc, buttons, 0), close); new MessageBoxDialog(imgui_drawer, title, desc, buttons, 0), close);
} }
dword_result_t XamShowMarketplaceUI_entry(dword_t user_index, dword_t ui_type,
qword_t offer_id,
dword_t content_types, unknown_t unk5,
unknown_t unk6) {
return xeXShowMarketplaceUIEx(user_index, ui_type, offer_id, content_types,
unk5, 0, 0, unk6);
}
DECLARE_XAM_EXPORT1(XamShowMarketplaceUI, kUI, kSketchy); DECLARE_XAM_EXPORT1(XamShowMarketplaceUI, kUI, kSketchy);
dword_result_t XamShowMarketplaceUIEx_entry(dword_t user_index, dword_t ui_type,
qword_t offer_id,
dword_t content_types,
unknown_t unk5, unknown_t unk6,
unknown_t unk7, unknown_t unk8) {
return xeXShowMarketplaceUIEx(user_index, ui_type, offer_id, content_types,
unk5, unk6, unk7, unk8);
}
DECLARE_XAM_EXPORT1(XamShowMarketplaceUIEx, kUI, kSketchy);
dword_result_t XamShowMarketplaceDownloadItemsUI_entry( dword_result_t XamShowMarketplaceDownloadItemsUI_entry(
dword_t user_index, dword_t ui_type, lpqword_t offers, dword_t num_offers, dword_t user_index, dword_t ui_type, lpqword_t offers, dword_t num_offers,
lpdword_t hresult_ptr, pointer_t<XAM_OVERLAPPED> overlapped) { lpdword_t hresult_ptr, pointer_t<XAM_OVERLAPPED> overlapped) {
@ -1291,6 +1482,12 @@ dword_result_t XamShowMarketplaceDownloadItemsUI_entry(
} }
DECLARE_XAM_EXPORT1(XamShowMarketplaceDownloadItemsUI, kUI, kSketchy); DECLARE_XAM_EXPORT1(XamShowMarketplaceDownloadItemsUI, kUI, kSketchy);
dword_result_t XamShowForcedNameChangeUI_entry(dword_t user_index) {
// Changes from 6 to 8 past NXE
return xeXShowMarketplaceUIEx(user_index, 6, 0, 0xffffffff, 0, 0, 0, 0);
}
DECLARE_XAM_EXPORT1(XamShowForcedNameChangeUI, kUI, kImplemented);
bool xeDrawProfileContent(ui::ImGuiDrawer* imgui_drawer, const uint64_t xuid, bool xeDrawProfileContent(ui::ImGuiDrawer* imgui_drawer, const uint64_t xuid,
const uint8_t user_index, const uint8_t user_index,
const X_XAMACCOUNTINFO* account, const X_XAMACCOUNTINFO* account,
@ -1376,10 +1573,13 @@ bool xeDrawProfileContent(ui::ImGuiDrawer* imgui_drawer, const uint64_t xuid,
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
const bool is_signedin = profile_manager->GetProfile(xuid) != nullptr;
ImGui::BeginDisabled(!is_signedin);
if (ImGui::MenuItem("Show Achievements")) { if (ImGui::MenuItem("Show Achievements")) {
new GamesInfoDialog(imgui_drawer, next_window_position, new GamesInfoDialog(imgui_drawer, next_window_position,
profile_manager->GetProfile(user_index)); profile_manager->GetProfile(user_index));
} }
ImGui::EndDisabled();
if (ImGui::MenuItem("Show Content Directory")) { if (ImGui::MenuItem("Show Content Directory")) {
const auto path = profile_manager->GetProfileContentPath( const auto path = profile_manager->GetProfileContentPath(

View File

@ -139,28 +139,36 @@ X_HRESULT_result_t XamUserGetSigninInfo_entry(
} }
DECLARE_XAM_EXPORT1(XamUserGetSigninInfo, kUserProfiles, kImplemented); DECLARE_XAM_EXPORT1(XamUserGetSigninInfo, kUserProfiles, kImplemented);
dword_result_t XamUserGetName_entry(dword_t user_index, lpstring_t buffer, dword_result_t XamUserGetName_entry(dword_t user_index, dword_t buffer,
dword_t buffer_len) { dword_t buffer_len) {
if (user_index >= XUserMaxUserCount) { if (user_index >= XUserMaxUserCount) {
return X_ERROR_INVALID_PARAMETER; return X_ERROR_INVALID_PARAMETER;
} }
if (kernel_state()->xam_state()->IsUserSignedIn(user_index)) { if (!kernel_state()->xam_state()->IsUserSignedIn(user_index)) {
const auto& user_profile = // Based on XAM only first byte is cleared in case of lack of user.
kernel_state()->xam_state()->GetUserProfile(user_index); kernel_memory()->Zero(buffer, 1);
const auto& user_name = user_profile->name();
xe::string_util::copy_truncating(
buffer, user_name, std::min(buffer_len.value(), uint32_t(16)));
} else {
*buffer = 0;
return X_ERROR_NO_SUCH_USER; return X_ERROR_NO_SUCH_USER;
} }
const auto& user_profile =
kernel_state()->xam_state()->GetUserProfile(user_index);
// Because name is always limited to 15 characters we can assume length will
// never exceed that limit.
const auto& user_name = user_profile->name();
// buffer_len includes null-terminator. user_name does not.
const uint32_t bytes_to_copy = std::min(
buffer_len.value(), static_cast<uint32_t>(user_name.length()) + 1);
char* str_buffer = kernel_memory()->TranslateVirtual<char*>(buffer);
xe::string_util::copy_truncating(str_buffer, user_name, bytes_to_copy);
return X_ERROR_SUCCESS; return X_ERROR_SUCCESS;
} }
DECLARE_XAM_EXPORT1(XamUserGetName, kUserProfiles, kImplemented); DECLARE_XAM_EXPORT1(XamUserGetName, kUserProfiles, kImplemented);
dword_result_t XamUserGetGamerTag_entry(dword_t user_index, dword_result_t XamUserGetGamerTag_entry(dword_t user_index, dword_t buffer,
lpu16string_t buffer,
dword_t buffer_len) { dword_t buffer_len) {
if (!buffer || buffer_len < 16) { if (!buffer || buffer_len < 16) {
return X_E_INVALIDARG; return X_E_INVALIDARG;
@ -177,8 +185,11 @@ dword_result_t XamUserGetGamerTag_entry(dword_t user_index,
const auto& user_profile = const auto& user_profile =
kernel_state()->xam_state()->GetUserProfile(user_index); kernel_state()->xam_state()->GetUserProfile(user_index);
auto user_name = xe::to_utf16(user_profile->name()); auto user_name = xe::to_utf16(user_profile->name());
char16_t* str_buffer = kernel_memory()->TranslateVirtual<char16_t*>(buffer);
xe::string_util::copy_and_swap_truncating( xe::string_util::copy_and_swap_truncating(
buffer, user_name, std::min(buffer_len.value(), uint32_t(16))); str_buffer, user_name, std::min(buffer_len.value(), uint32_t(16)));
return X_E_SUCCESS; return X_E_SUCCESS;
} }
DECLARE_XAM_EXPORT1(XamUserGetGamerTag, kUserProfiles, kImplemented); DECLARE_XAM_EXPORT1(XamUserGetGamerTag, kUserProfiles, kImplemented);
@ -620,7 +631,8 @@ dword_result_t XamUserCreateAchievementEnumerator_entry(
} }
const util::XdbfGameData db = kernel_state()->title_xdbf(); const util::XdbfGameData db = kernel_state()->title_xdbf();
uint32_t title_id_ = title_id ? title_id : kernel_state()->title_id(); uint32_t title_id_ =
title_id ? static_cast<uint32_t>(title_id) : kernel_state()->title_id();
const auto user_title_achievements = const auto user_title_achievements =
kernel_state()->achievement_manager()->GetTitleAchievements( kernel_state()->achievement_manager()->GetTitleAchievements(
@ -787,6 +799,18 @@ dword_result_t XamUserGetUserFlagsFromXUID_entry(qword_t xuid) {
} }
DECLARE_XAM_EXPORT1(XamUserGetUserFlagsFromXUID, kUserProfiles, kImplemented); DECLARE_XAM_EXPORT1(XamUserGetUserFlagsFromXUID, kUserProfiles, kImplemented);
dword_result_t XamUserGetOnlineLanguageFromXUID_entry(qword_t xuid) {
/* Notes:
- Calls XamUserGetUserFlagsFromXUID and returns (ulonglong)(cached_flag <<
0x20) >> 0x39 & 0x1f;
- XamUserGetMembershipTierFromXUID and XamUserGetOnlineCountryFromXUID also
call it
- Removed in metro
*/
return cvars::user_language;
}
DECLARE_XAM_EXPORT1(XamUserGetOnlineLanguageFromXUID, kUserProfiles, kStub);
constexpr uint8_t kStatsMaxAmount = 64; constexpr uint8_t kStatsMaxAmount = 64;
struct X_STATS_DETAILS { struct X_STATS_DETAILS {
@ -832,35 +856,6 @@ dword_result_t XamUserCreateStatsEnumerator_entry(
} }
DECLARE_XAM_EXPORT1(XamUserCreateStatsEnumerator, kUserProfiles, kSketchy); DECLARE_XAM_EXPORT1(XamUserCreateStatsEnumerator, kUserProfiles, kSketchy);
dword_result_t XamProfileFindAccount_entry(
qword_t offline_xuid, pointer_t<X_XAMACCOUNTINFO> account_ptr,
lpdword_t device_id) {
if (!account_ptr) {
return X_ERROR_INVALID_PARAMETER;
}
account_ptr.Zero();
const auto& account =
kernel_state()->xam_state()->profile_manager()->GetAccount(offline_xuid);
if (!account) {
return X_ERROR_NO_SUCH_USER;
}
std::memcpy(account_ptr, &account, sizeof(X_XAMACCOUNTINFO));
xe::string_util::copy_and_swap_truncating(
account_ptr->gamertag, account->gamertag, sizeof(account->gamertag));
if (device_id) {
*device_id = 1;
}
return X_ERROR_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamProfileFindAccount, kUserProfiles, kImplemented);
} // namespace xam } // namespace xam
} // namespace kernel } // namespace kernel
} // namespace xe } // namespace xe

View File

@ -171,6 +171,88 @@ dword_result_t DmGetSystemInfo_entry(pointer_t<XBDM_SYSTEM_INFO> info) {
} }
DECLARE_XBDM_EXPORT1(DmGetSystemInfo, kDebug, kStub); DECLARE_XBDM_EXPORT1(DmGetSystemInfo, kDebug, kStub);
dword_result_t DmSetMemory_entry(lpvoid_t dest_ptr, dword_t buf_size,
lpvoid_t src_ptr, lpdword_t bytes_written) {
if (!dest_ptr || !src_ptr || !buf_size) {
return XBDM_UNSUCCESSFUL;
}
if (bytes_written) {
*bytes_written = 0;
}
const uint32_t dest_guest_address = dest_ptr.guest_address();
const uint32_t dest_guest_high_address = dest_guest_address + buf_size;
memory::PageAccess access = memory::PageAccess::kNoAccess;
BaseHeap* dest_heap_ptr =
kernel_state()->memory()->LookupHeap(dest_guest_address);
if (!dest_heap_ptr) {
return XBDM_UNSUCCESSFUL;
}
access = dest_heap_ptr->QueryRangeAccess(dest_guest_address,
dest_guest_high_address);
if (access == memory::PageAccess::kReadWrite) {
memcpy(dest_ptr, src_ptr, buf_size);
if (bytes_written) {
*bytes_written = static_cast<uint32_t>(buf_size);
}
} else {
XELOGE("DmSetMemory failed with page access {}",
static_cast<uint32_t>(access));
return XBDM_UNSUCCESSFUL;
}
return XBDM_SUCCESSFUL;
}
DECLARE_XBDM_EXPORT1(DmSetMemory, kDebug, kImplemented);
dword_result_t DmGetMemory_entry(lpvoid_t src_ptr, dword_t buf_size,
lpvoid_t dest_ptr, lpdword_t bytes_written) {
if (!dest_ptr || !src_ptr || !buf_size) {
return XBDM_UNSUCCESSFUL;
}
if (bytes_written) {
*bytes_written = 0;
}
const uint32_t dest_guest_address = dest_ptr.guest_address();
const uint32_t dest_guest_high_address = dest_guest_address + buf_size;
memory::PageAccess access = memory::PageAccess::kNoAccess;
BaseHeap* dest_heap_ptr =
kernel_state()->memory()->LookupHeap(dest_guest_address);
if (!dest_heap_ptr) {
return XBDM_UNSUCCESSFUL;
}
access = dest_heap_ptr->QueryRangeAccess(dest_guest_address,
dest_guest_high_address);
if (access == memory::PageAccess::kReadWrite) {
memcpy(dest_ptr, src_ptr, buf_size);
if (bytes_written) {
*bytes_written = static_cast<uint32_t>(buf_size);
}
} else {
XELOGE("DmGetMemory failed with page access {}",
static_cast<uint32_t>(access));
return XBDM_UNSUCCESSFUL;
}
return XBDM_SUCCESSFUL;
}
DECLARE_XBDM_EXPORT1(DmGetMemory, kDebug, kImplemented);
dword_result_t DmIsFastCAPEnabled_entry() { return XBDM_UNSUCCESSFUL; } dword_result_t DmIsFastCAPEnabled_entry() { return XBDM_UNSUCCESSFUL; }
DECLARE_XBDM_EXPORT1(DmIsFastCAPEnabled, kDebug, kStub); DECLARE_XBDM_EXPORT1(DmIsFastCAPEnabled, kDebug, kStub);

View File

@ -63,7 +63,7 @@ dword_result_t NtCreateFile_entry(lpdword_t handle_out, dword_t desired_access,
vfs::Entry* root_entry = nullptr; vfs::Entry* root_entry = nullptr;
// Compute path, possibly attrs relative. // Compute path, possibly attrs relative.
auto target_path = util::TranslateAnsiString(kernel_memory(), object_name); auto target_path = util::TranslateAnsiPath(kernel_memory(), object_name);
// Enforce that the path is ASCII. // Enforce that the path is ASCII.
if (!IsValidPath(target_path, false)) { if (!IsValidPath(target_path, false)) {
@ -462,7 +462,7 @@ dword_result_t NtQueryFullAttributesFile_entry(
assert_always(); assert_always();
} }
auto target_path = util::TranslateAnsiString(kernel_memory(), object_name); auto target_path = util::TranslateAnsiPath(kernel_memory(), object_name);
// Enforce that the path is ASCII. // Enforce that the path is ASCII.
if (!IsValidPath(target_path, false)) { if (!IsValidPath(target_path, false)) {
@ -501,7 +501,7 @@ dword_result_t NtQueryDirectoryFile_entry(
uint32_t info = 0; uint32_t info = 0;
auto file = kernel_state()->object_table()->LookupObject<XFile>(file_handle); auto file = kernel_state()->object_table()->LookupObject<XFile>(file_handle);
auto name = util::TranslateAnsiString(kernel_memory(), file_name); auto name = util::TranslateAnsiPath(kernel_memory(), file_name);
// Enforce that the path is ASCII. // Enforce that the path is ASCII.
if (!IsValidPath(name, true)) { if (!IsValidPath(name, true)) {
@ -558,7 +558,7 @@ dword_result_t NtOpenSymbolicLinkObject_entry(
auto object_name = auto object_name =
kernel_memory()->TranslateVirtual<X_ANSI_STRING*>(object_attrs->name_ptr); kernel_memory()->TranslateVirtual<X_ANSI_STRING*>(object_attrs->name_ptr);
auto target_path = util::TranslateAnsiString(kernel_memory(), object_name); auto target_path = util::TranslateAnsiPath(kernel_memory(), object_name);
// Enforce that the path is ASCII. // Enforce that the path is ASCII.
if (!IsValidPath(target_path, false)) { if (!IsValidPath(target_path, false)) {

View File

@ -234,7 +234,7 @@ dword_result_t NtSetInformationFile_entry(
auto info = info_ptr.as<X_FILE_RENAME_INFORMATION*>(); auto info = info_ptr.as<X_FILE_RENAME_INFORMATION*>();
// Compute path, possibly attrs relative. // Compute path, possibly attrs relative.
std::filesystem::path target_path = std::filesystem::path target_path =
util::TranslateAnsiString(kernel_memory(), &info->ansi_string); util::TranslateAnsiPath(kernel_memory(), &info->ansi_string);
// Place IsValidPath in path from where it can be accessed everywhere // Place IsValidPath in path from where it can be accessed everywhere
if (!IsValidPath(target_path.string(), false)) { if (!IsValidPath(target_path.string(), false)) {

View File

@ -263,7 +263,7 @@ dword_result_t XexLoadImageHeaders_entry(pointer_t<X_ANSI_STRING> path,
return X_STATUS_BUFFER_TOO_SMALL; return X_STATUS_BUFFER_TOO_SMALL;
} }
auto current_kernel = ctx->kernel_state; auto current_kernel = ctx->kernel_state;
auto target_path = util::TranslateAnsiString(current_kernel->memory(), path); auto target_path = util::TranslateAnsiPath(current_kernel->memory(), path);
vfs::File* vfs_file = nullptr; vfs::File* vfs_file = nullptr;
vfs::FileAction file_action; vfs::FileAction file_action;

View File

@ -350,11 +350,11 @@ DECLARE_XBOXKRNL_EXPORT1(ObReferenceObject, kNone, kImplemented);
dword_result_t ObCreateSymbolicLink_entry(pointer_t<X_ANSI_STRING> path_ptr, dword_result_t ObCreateSymbolicLink_entry(pointer_t<X_ANSI_STRING> path_ptr,
pointer_t<X_ANSI_STRING> target_ptr) { pointer_t<X_ANSI_STRING> target_ptr) {
auto path = xe::utf8::canonicalize_guest_path( auto path = xe::utf8::canonicalize_guest_path(
util::TranslateAnsiString(kernel_memory(), path_ptr)); util::TranslateAnsiPath(kernel_memory(), path_ptr));
auto target = xe::utf8::canonicalize_guest_path( auto target = xe::utf8::canonicalize_guest_path(
util::TranslateAnsiString(kernel_memory(), target_ptr)); util::TranslateAnsiPath(kernel_memory(), target_ptr));
if (xe::utf8::starts_with(path, u8"\\??\\")) { if (xe::utf8::starts_with(path, "\\??\\")) {
path = path.substr(4); // Strip the full qualifier path = path.substr(4); // Strip the full qualifier
} }
@ -367,7 +367,7 @@ dword_result_t ObCreateSymbolicLink_entry(pointer_t<X_ANSI_STRING> path_ptr,
DECLARE_XBOXKRNL_EXPORT1(ObCreateSymbolicLink, kNone, kImplemented); DECLARE_XBOXKRNL_EXPORT1(ObCreateSymbolicLink, kNone, kImplemented);
dword_result_t ObDeleteSymbolicLink_entry(pointer_t<X_ANSI_STRING> path_ptr) { dword_result_t ObDeleteSymbolicLink_entry(pointer_t<X_ANSI_STRING> path_ptr) {
auto path = util::TranslateAnsiString(kernel_memory(), path_ptr); auto path = util::TranslateAnsiPath(kernel_memory(), path_ptr);
if (!kernel_state()->file_system()->UnregisterSymbolicLink(path)) { if (!kernel_state()->file_system()->UnregisterSymbolicLink(path)) {
return X_STATUS_UNSUCCESSFUL; return X_STATUS_UNSUCCESSFUL;
} }

View File

@ -44,7 +44,7 @@ DEFINE_double(kernel_display_gamma_power, 2.22222233,
"Display gamma to use with kernel_display_gamma_type 3.", "Display gamma to use with kernel_display_gamma_type 3.",
"Kernel"); "Kernel");
inline constexpr static uint32_t GetVideoStandard() { inline const static uint32_t GetVideoStandard() {
if (cvars::video_standard < 1 || cvars::video_standard > 3) { if (cvars::video_standard < 1 || cvars::video_standard > 3) {
return 1; return 1;
} }
@ -52,11 +52,11 @@ inline constexpr static uint32_t GetVideoStandard() {
return cvars::video_standard; return cvars::video_standard;
} }
inline constexpr static float GetVideoRefreshRate() { inline const static float GetVideoRefreshRate() {
return cvars::use_50Hz_mode ? 50.0f : 60.0f; return cvars::use_50Hz_mode ? 50.0f : 60.0f;
} }
inline constexpr static std::pair<uint16_t, uint16_t> GetDisplayAspectRatio() { inline const static std::pair<uint16_t, uint16_t> GetDisplayAspectRatio() {
if (cvars::widescreen) { if (cvars::widescreen) {
return {16, 9}; return {16, 9};
} }

View File

@ -37,6 +37,10 @@ DEFINE_int32(user_country, 103,
" 102=UA 103=US 104=UY 105=UZ 106=VE 107=VN 108=YE 109=ZA\n", " 102=UA 103=US 104=UY 105=UZ 106=VE 107=VN 108=YE 109=ZA\n",
"XConfig"); "XConfig");
DECLARE_bool(widescreen);
DECLARE_bool(use_50Hz_mode);
DECLARE_int32(video_standard);
namespace xe { namespace xe {
namespace kernel { namespace kernel {
namespace xboxkrnl { namespace xboxkrnl {
@ -56,9 +60,26 @@ X_STATUS xeExGetXConfigSetting(uint16_t category, uint16_t setting,
switch (setting) { switch (setting) {
case 0x0002: // XCONFIG_SECURED_AV_REGION case 0x0002: // XCONFIG_SECURED_AV_REGION
setting_size = 4; setting_size = 4;
xe::store_and_swap<uint32_t>(value, 0x00001000); // USA/Canada switch (cvars::video_standard) {
case 1: // NTSCM
xe::store_and_swap<uint32_t>(value, 0x00400100);
break;
case 2: // NTSCJ
xe::store_and_swap<uint32_t>(value, 0x00400200);
break;
case 3: // PAL
xe::store_and_swap<uint32_t>(
value, cvars::use_50Hz_mode ? 0x00800300 : 0x00400400);
break; break;
default: default:
xe::store_and_swap<uint32_t>(value, 0);
break;
}
break;
default:
XELOGW(
"An unimplemented setting 0x{:04X} in XCONFIG SECURED CATEGORY",
static_cast<uint16_t>(setting));
assert_unhandled_case(setting); assert_unhandled_case(setting);
return X_STATUS_INVALID_PARAMETER_2; return X_STATUS_INVALID_PARAMETER_2;
} }
@ -83,7 +104,10 @@ X_STATUS xeExGetXConfigSetting(uint16_t category, uint16_t setting,
break; break;
case 0x000A: // XCONFIG_USER_VIDEO_FLAGS case 0x000A: // XCONFIG_USER_VIDEO_FLAGS
setting_size = 4; setting_size = 4;
xe::store_and_swap<uint32_t>(value, 0x00040000); // 0x00040000 normal
// 0x00050000 widescreen
xe::store_and_swap<uint32_t>(
value, cvars::widescreen ? 0x00050000 : 0x00040000);
break; break;
case 0x000C: // XCONFIG_USER_RETAIL_FLAGS case 0x000C: // XCONFIG_USER_RETAIL_FLAGS
setting_size = 4; setting_size = 4;
@ -95,11 +119,15 @@ X_STATUS xeExGetXConfigSetting(uint16_t category, uint16_t setting,
value[0] = static_cast<uint8_t>(cvars::user_country); value[0] = static_cast<uint8_t>(cvars::user_country);
break; break;
default: default:
XELOGW("An unimplemented setting 0x{:04X} in XCONFIG USER CATEGORY",
static_cast<uint16_t>(setting));
assert_unhandled_case(setting); assert_unhandled_case(setting);
return X_STATUS_INVALID_PARAMETER_2; return X_STATUS_INVALID_PARAMETER_2;
} }
break; break;
default: default:
XELOGW("An unimplemented category 0x{:04X}",
static_cast<uint16_t>(category));
assert_unhandled_case(category); assert_unhandled_case(category);
return X_STATUS_INVALID_PARAMETER_1; return X_STATUS_INVALID_PARAMETER_1;
} }
@ -138,6 +166,36 @@ dword_result_t ExGetXConfigSetting_entry(word_t category, word_t setting,
} }
DECLARE_XBOXKRNL_EXPORT1(ExGetXConfigSetting, kModules, kImplemented); DECLARE_XBOXKRNL_EXPORT1(ExGetXConfigSetting, kModules, kImplemented);
dword_result_t ExSetXConfigSetting_entry(word_t category, word_t setting,
lpvoid_t buffer_ptr,
dword_t buffer_size) {
/* Notes:
Handles settings the only have a single flag/value like
XCONFIG_USER_VIDEO_FLAGS to swap
*/
XELOGI("ExSetXConfigSetting: category: 0X{:04x}, setting: 0X{:04x}",
static_cast<uint16_t>(category), static_cast<uint16_t>(setting));
return X_STATUS_SUCCESS;
}
DECLARE_XBOXKRNL_EXPORT1(ExSetXConfigSetting, kModules, kStub);
dword_result_t ExReadModifyWriteXConfigSettingUlong_entry(word_t category,
word_t setting,
dword_t bit_affected,
dword_t flag) {
/* Notes:
Handles settings with multiple flags like XCONFIG_USER_RETAIL_FLAGS and
XCONFIG_CONSOLE_RETAIL_EX_FLAGS
*/
XELOGI(
"ExReadModifyWriteXConfigSettingUlong: category: 0x{:04x}, setting: "
"{:04x}, changed bits: 0X{:08x}, setting flag 0X{:08x}",
static_cast<uint16_t>(category), static_cast<uint16_t>(setting),
static_cast<uint32_t>(bit_affected), static_cast<uint32_t>(flag));
return X_STATUS_SUCCESS;
}
DECLARE_XBOXKRNL_EXPORT1(ExReadModifyWriteXConfigSettingUlong, kModules, kStub);
} // namespace xboxkrnl } // namespace xboxkrnl
} // namespace kernel } // namespace kernel
} // namespace xe } // namespace xe

View File

@ -98,9 +98,9 @@ static bool IsValidPath(const std::string_view s, bool is_pattern) {
case '+': case '+':
case ',': case ',':
// case ':': // case ':':
case ';': // case ';':
case '<': case '<':
case '=': // case '=':
case '>': case '>':
// case '?': // case '?':
case '|': { case '|': {

View File

@ -135,6 +135,8 @@ class XObject {
case Type::Thread: case Type::Thread:
case Type::Timer: case Type::Timer:
return true; return true;
default:
return false;
} }
return false; return false;
} }
@ -349,7 +351,9 @@ class object_ref {
void reset(T* value) noexcept { object_ref(value).swap(*this); } void reset(T* value) noexcept { object_ref(value).swap(*this); }
inline bool operator==(const T* right) noexcept { return value_ == right; } inline bool operator==(const T* right) const noexcept {
return value_ == right;
}
private: private:
T* value_ = nullptr; T* value_ = nullptr;

View File

@ -74,9 +74,10 @@ X_STATUS XSocket::Close() {
} }
X_STATUS XSocket::GetOption(uint32_t level, uint32_t optname, void* optval_ptr, X_STATUS XSocket::GetOption(uint32_t level, uint32_t optname, void* optval_ptr,
int* optlen) { uint32_t* optlen) {
int ret = int ret =
getsockopt(native_handle_, level, optname, (char*)optval_ptr, optlen); getsockopt(native_handle_, level, optname, static_cast<char*>(optval_ptr),
reinterpret_cast<socklen_t*>(optlen));
if (ret < 0) { if (ret < 0) {
// TODO: WSAGetLastError() // TODO: WSAGetLastError()
return X_STATUS_UNSUCCESSFUL; return X_STATUS_UNSUCCESSFUL;

View File

@ -108,7 +108,7 @@ class XSocket : public XObject {
X_STATUS Close(); X_STATUS Close();
X_STATUS GetOption(uint32_t level, uint32_t optname, void* optval_ptr, X_STATUS GetOption(uint32_t level, uint32_t optname, void* optval_ptr,
int* optlen); uint32_t* optlen);
X_STATUS SetOption(uint32_t level, uint32_t optname, void* optval_ptr, X_STATUS SetOption(uint32_t level, uint32_t optname, void* optval_ptr,
uint32_t optlen); uint32_t optlen);
X_STATUS IOControl(uint32_t cmd, uint8_t* arg_ptr); X_STATUS IOControl(uint32_t cmd, uint8_t* arg_ptr);

View File

@ -48,8 +48,8 @@ void PluginLoader::LoadConfigs() {
xe::filesystem::FilterByName(dir_files, std::regex("[A-Fa-f0-9]{8}")); xe::filesystem::FilterByName(dir_files, std::regex("[A-Fa-f0-9]{8}"));
for (const auto& entry : dir_files) { for (const auto& entry : dir_files) {
const uint32_t title_id = const uint32_t title_id = string_util::from_string<uint32_t>(
std::stoi(entry.name.filename().string(), nullptr, 16); entry.name.filename().string(), true);
LoadTitleConfig(title_id); LoadTitleConfig(title_id);
} }

View File

@ -95,12 +95,11 @@ void ImGuiGuestNotification::UpdateNotificationState() {
const ImVec2 ImGuiGuestNotification::CalculateNotificationSize(ImVec2 text_size, const ImVec2 ImGuiGuestNotification::CalculateNotificationSize(ImVec2 text_size,
float scale) { float scale) {
const ImVec2 result = const ImVec2 result = ImVec2(floorf((default_notification_icon_size.x +
ImVec2(std::floorf((default_notification_icon_size.x +
default_notification_margin_size.x) * default_notification_margin_size.x) *
scale) + scale) +
text_size.x, text_size.x,
std::floorf((default_notification_icon_size.y + floorf((default_notification_icon_size.y +
default_notification_margin_size.y) * default_notification_margin_size.y) *
scale)); scale));
@ -134,13 +133,14 @@ void AchievementNotificationWindow::OnDraw(ImGuiIO& io) {
const ImVec2 notification_position = CalculateNotificationScreenPosition( const ImVec2 notification_position = CalculateNotificationScreenPosition(
screen_size, final_notification_size, GetPositionId()); screen_size, final_notification_size, GetPositionId());
if (isnan(notification_position.x) || isnan(notification_position.y)) { if (std::isnan(notification_position.x) ||
std::isnan(notification_position.y)) {
return; return;
} }
ImVec2 current_notification_size = final_notification_size; ImVec2 current_notification_size = final_notification_size;
current_notification_size.x *= notification_draw_progress_; current_notification_size.x *= notification_draw_progress_;
current_notification_size.x = std::floorf(current_notification_size.x); current_notification_size.x = floorf(current_notification_size.x);
// Initialize position and window size // Initialize position and window size
ImGui::SetNextWindowSize(current_notification_size); ImGui::SetNextWindowSize(current_notification_size);
@ -170,7 +170,7 @@ void AchievementNotificationWindow::OnDraw(ImGuiIO& io) {
ImGui::SameLine(); ImGui::SameLine();
if (notification_draw_progress_ > 0.5f) { if (notification_draw_progress_ > 0.5f) {
ImGui::TextColored(white_color, GetNotificationText().c_str()); ImGui::TextColored(white_color, "%s", GetNotificationText().c_str());
} }
} }
// Restore previous style // Restore previous style
@ -207,13 +207,14 @@ void XNotifyWindow::OnDraw(ImGuiIO& io) {
const ImVec2 notification_position = CalculateNotificationScreenPosition( const ImVec2 notification_position = CalculateNotificationScreenPosition(
screen_size, final_notification_size, GetPositionId()); screen_size, final_notification_size, GetPositionId());
if (isnan(notification_position.x) || isnan(notification_position.y)) { if (std::isnan(notification_position.x) ||
std::isnan(notification_position.y)) {
return; return;
} }
ImVec2 current_notification_size = final_notification_size; ImVec2 current_notification_size = final_notification_size;
current_notification_size.x *= notification_draw_progress_; current_notification_size.x *= notification_draw_progress_;
current_notification_size.x = std::floorf(current_notification_size.x); current_notification_size.x = floorf(current_notification_size.x);
// Initialize position and window size // Initialize position and window size
ImGui::SetNextWindowSize(current_notification_size); ImGui::SetNextWindowSize(current_notification_size);
@ -247,7 +248,7 @@ void XNotifyWindow::OnDraw(ImGuiIO& io) {
ImGui::SameLine(); ImGui::SameLine();
if (notification_draw_progress_ > 0.5f) { if (notification_draw_progress_ > 0.5f) {
ImGui::TextColored(white_color, GetDescription().c_str()); ImGui::TextColored(white_color, "%s", GetDescription().c_str());
} }
} }
// Restore previous style // Restore previous style

View File

@ -57,7 +57,7 @@ class ImGuiGuestNotification : public ImGuiNotification {
const ImVec2 CalculateNotificationSize(ImVec2 text_size, const ImVec2 CalculateNotificationSize(ImVec2 text_size,
float scale) override; float scale) override;
virtual void OnDraw(ImGuiIO& io) {} virtual void OnDraw(ImGuiIO& io) override {}
float notification_draw_progress_; float notification_draw_progress_;

View File

@ -68,7 +68,8 @@ void HostNotificationWindow::OnDraw(ImGuiIO& io) {
const ImVec2 notification_position = CalculateNotificationScreenPosition( const ImVec2 notification_position = CalculateNotificationScreenPosition(
screen_size, notification_size, GetPositionId()); screen_size, notification_size, GetPositionId());
if (isnan(notification_position.x) || isnan(notification_position.y)) { if (std::isnan(notification_position.x) ||
std::isnan(notification_position.y)) {
return; return;
} }
@ -78,9 +79,9 @@ void HostNotificationWindow::OnDraw(ImGuiIO& io) {
{ {
ImGui::SetWindowFontScale(window_scale); ImGui::SetWindowFontScale(window_scale);
ImGui::Text(GetTitle().c_str()); ImGui::Text("%s", GetTitle().c_str());
ImGui::Separator(); ImGui::Separator();
ImGui::Text(GetDescription().c_str()); ImGui::Text("%s", GetDescription().c_str());
} }
ImGui::End(); ImGui::End();
} }

View File

@ -32,7 +32,7 @@ class ImGuiHostNotification : public ImGuiNotification {
const ImVec2 CalculateNotificationSize(ImVec2 text_size, const ImVec2 CalculateNotificationSize(ImVec2 text_size,
float scale) override; float scale) override;
virtual void OnDraw(ImGuiIO& io) {} virtual void OnDraw(ImGuiIO& io) override {}
}; };
class HostNotificationWindow final : ImGuiHostNotification { class HostNotificationWindow final : ImGuiHostNotification {

View File

@ -468,10 +468,12 @@ struct XContentMetadata {
}; };
static_assert_size(XContentMetadata, 0x93D6); static_assert_size(XContentMetadata, 0x93D6);
static constexpr uint8_t license_count = 0x10;
struct XContentHeader { struct XContentHeader {
be<XContentPackageType> magic; be<XContentPackageType> magic;
uint8_t signature[0x228]; uint8_t signature[0x228];
XContentLicense licenses[0x10]; XContentLicense licenses[license_count];
uint8_t content_id[0x14]; uint8_t content_id[0x14];
be<uint32_t> header_size; be<uint32_t> header_size;

View File

@ -37,7 +37,7 @@ class XContentContainerDevice : public Device {
~XContentContainerDevice() override; ~XContentContainerDevice() override;
bool Initialize(); bool Initialize() override;
const std::string& name() const override { return name_; } const std::string& name() const override { return name_; }
uint32_t attributes() const override { return 0; } uint32_t attributes() const override { return 0; }
@ -65,7 +65,13 @@ class XContentContainerDevice : public Device {
kernel::xam::XCONTENT_AGGREGATE_DATA content_header() const; kernel::xam::XCONTENT_AGGREGATE_DATA content_header() const;
uint32_t license_mask() const { uint32_t license_mask() const {
return header_->content_header.licenses[0].license_bits; uint32_t final_license = 0;
for (uint8_t i = 0; i < license_count; i++) {
if (header_->content_header.licenses[i].license_flags) {
final_license |= header_->content_header.licenses[i].license_bits;
}
}
return final_license;
} }
protected: protected:
@ -87,9 +93,9 @@ class XContentContainerDevice : public Device {
// Initialize any container specific fields. // Initialize any container specific fields.
virtual void SetupContainer() {}; virtual void SetupContainer() {};
Entry* ResolvePath(const std::string_view path); Entry* ResolvePath(const std::string_view path) override;
void CloseFiles(); void CloseFiles();
void Dump(StringBuffer* string_buffer); void Dump(StringBuffer* string_buffer) override;
Result ReadHeaderAndVerify(FILE* header_file); Result ReadHeaderAndVerify(FILE* header_file);
void SetName(std::string name) { name_ = name; } void SetName(std::string name) { name_ = name; }

View File

@ -121,6 +121,8 @@ typedef uint32_t X_HRESULT;
#define X_E_FALSE static_cast<X_HRESULT>(0x80000000L) #define X_E_FALSE static_cast<X_HRESULT>(0x80000000L)
#define X_E_SUCCESS X_HRESULT_FROM_WIN32(X_ERROR_SUCCESS) #define X_E_SUCCESS X_HRESULT_FROM_WIN32(X_ERROR_SUCCESS)
#define X_E_ACCESS_DENIED X_HRESULT_FROM_WIN32(X_ERROR_ACCESS_DENIED)
#define X_E_NOT_IMPLEMENTED static_cast<X_HRESULT>(0x80004001L)
#define X_E_FAIL static_cast<X_HRESULT>(0x80004005L) #define X_E_FAIL static_cast<X_HRESULT>(0x80004005L)
#define X_E_NO_MORE_FILES X_HRESULT_FROM_WIN32(X_ERROR_NO_MORE_FILES) #define X_E_NO_MORE_FILES X_HRESULT_FROM_WIN32(X_ERROR_NO_MORE_FILES)
#define X_E_INVALIDARG X_HRESULT_FROM_WIN32(X_ERROR_INVALID_PARAMETER) #define X_E_INVALIDARG X_HRESULT_FROM_WIN32(X_ERROR_INVALID_PARAMETER)
@ -275,55 +277,70 @@ enum : XNotificationID {
kXNotifyLive = 0x00000002, kXNotifyLive = 0x00000002,
kXNotifyFriends = 0x00000004, kXNotifyFriends = 0x00000004,
kXNotifyCustom = 0x00000008, kXNotifyCustom = 0x00000008,
kXNotifyDvdDrive = 0x00000010, // ?
kXNotifyXmp = 0x00000020, kXNotifyXmp = 0x00000020,
kXNotifyMsgr = 0x00000040, kXNotifyMsgr = 0x00000040,
kXNotifyParty = 0x00000080, kXNotifyParty = 0x00000080,
kXNotifyAll = 0x000000EF, kXNotifyAll = 0x000000EF,
// XNotification System // XNotification System (35 total)
kXNotificationIDSystemUI = 0x00000009, kXNotificationSystemUI = 0x00000009,
kXNotificationIDSystemSignInChanged = 0x0000000A, kXNotificationSystemSignInChanged = 0x0000000A,
kXNotificationIDSystemStorageDevicesChanged = 0x0000000B, kXNotificationSystemStorageDevicesChanged = 0x0000000B,
kXNotificationIDSystemProfileSettingChanged = 0x0000000E, kXNotificationSystemProfileSettingChanged = 0x0000000E,
kXNotificationIDSystemMuteListChanged = 0x00000011, kXNotificationSystemMuteListChanged = 0x00000011,
kXNotificationIDSystemInputDevicesChanged = 0x00000012, kXNotificationSystemInputDevicesChanged = 0x00000012,
kXNotificationIDSystemInputDeviceConfigChanged = 0x00000013, kXNotificationSystemInputDeviceConfigChanged = 0x00000013,
kXNotificationIDSystemPlayerTimerNotice = 0x00000015, kXNotificationSystemPlayerTimerNotice = 0x00000015,
kXNotificationIDSystemAvatarChanged = 0x00000017, kXNotificationSystemPXLiveSystemUpdate = 0x00000016,
kXNotificationIDSystemNUIHardwareStatusChanged = 0x00000019, kXNotificationSystemAvatarChanged = 0x00000017,
kXNotificationIDSystemNUIPause = 0x0000001A, kXNotificationSystemUnknown = 0x00000018,
kXNotificationIDSystemNUIUIApproach = 0x0000001B, kXNotificationSystemNUIHardwareStatusChanged = 0x00000019,
kXNotificationIDSystemDeviceRemap = 0x0000001C, kXNotificationSystemNUIPause = 0x0000001A,
kXNotificationIDSystemNUIBindingChanged = 0x0000001D, kXNotificationSystemNUIUIApproach = 0x0000001B,
kXNotificationIDSystemAudioLatencyChanged = 0x0000001E, kXNotificationSystemDeviceRemap = 0x0000001C,
kXNotificationIDSystemNUIChatBindingChanged = 0x0000001F, kXNotificationSystemNUIBindingChanged = 0x0000001D,
kXNotificationIDSystemInputActivityChanged = 0x00000020, kXNotificationSystemAudioLatencyChanged = 0x0000001E,
kXNotificationSystemNUIChatBindingChanged = 0x0000001F,
kXNotificationSystemInputActivityChanged = 0x00000020,
// XNotification Live // XNotification Live (20 total)
kXNotificationIDLiveConnectionChanged = 0x02000001, kXNotificationLiveConnectionChanged = 0x02000001,
kXNotificationIDLiveInviteAccepted = 0x02000002, kXNotificationLiveInviteAccepted = 0x02000002,
kXNotificationIDLiveLinkStateChanged = 0x02000003, kXNotificationLiveLinkStateChanged = 0x02000003,
kXNotificationIDLiveContentInstalled = 0x02000007, kXNotificationLiveContentInstalled = 0x02000007,
kXNotificationIDLiveMembershipPurchased = 0x02000008, kXNotificationLiveMembershipPurchased = 0x02000008,
kXNotificationIDLiveVoicechatAway = 0x02000009, kXNotificationLiveVoicechatAway = 0x02000009,
kXNotificationIDLivePresenceChanged = 0x0200000A, kXNotificationLivePresenceChanged = 0x0200000A,
kXNotificationLiveUnknown = 0x02000012,
// XNotification Friends // XNotification Friends (9 total)
kXNotificationIDFriendsPresenceChanged = 0x04000001, kXNotificationFriendsPresenceChanged = 0x04000001,
kXNotificationIDFriendsFriendAdded = 0x04000002, kXNotificationFriendsFriendAdded = 0x04000002,
kXNotificationIDFriendsFriendRemoved = 0x04000003, kXNotificationFriendsFriendRemoved = 0x04000003,
kXNotificationFriendsUnknown = 0x04000008,
// XNotification Custom // XNotification Custom (5 total)
kXNotificationIDCustomActionPressed = 0x06000003, kXNotificationCustomActionPressed = 0x06000003,
kXNotificationIDCustomGamercard = 0x06000004, kXNotificationCustomGamercard = 0x06000004,
// XNotification XMP // XNotification Dvd ?
kNotificationXmpStateChanged = 0x0A000001, kXNotificationDvdDriveUnknown = 0x80000003,
kNotificationXmpPlaybackBehaviorChanged = 0x0A000002, kXNotificationDvdDriveUnknownDashContext = 0x8000000C,
kNotificationXmpPlaybackControllerChanged = 0x0A000003, kXNotificationDvdDriveTrayStateChanged = 0x8000000D,
// XNotification Party // XNotification XMP (13 total)
kXNotificationIDPartyMembersChanged = 0x0E000002, kXNotificationXmpStateChanged = 0x0A000001,
kXNotificationXmpPlaybackBehaviorChanged = 0x0A000002,
kXNotificationXmpPlaybackControllerChanged = 0x0A000003,
kXNotificationXmpUnknown = 0x0A00000C,
// XNotification Party (6 total)
kXNotificationPartyMembersChanged = 0x0E000002,
kXNotificationFriendUnknown = 0x0E000005,
// XNotification Msgr
kXNotificationMsgrUnknown = 0x0C00000E,
}; };
// https://github.com/CodeAsm/ffplay360/blob/master/Common/XTLOnPC.h // https://github.com/CodeAsm/ffplay360/blob/master/Common/XTLOnPC.h
@ -540,6 +557,19 @@ const static std::map<XContentType, std::string> XContentTypeMap = {
{XContentType::kCommunityGame, "Community Game"}, {XContentType::kCommunityGame, "Community Game"},
}; };
enum X_MARKETPLACE_ENTRYPOINT : uint32_t {
ContentList = 0,
ContentItem = 1,
MembershipList = 2,
MembershipItem = 3,
ContentList_Background = 4,
ContentItem_Background = 5,
ForcedNameChangeV1 = 6,
ForcedNameChangeV2 = 8,
ProfileNameChange = 9,
ActiveDownloads = 12
};
enum class XDeploymentType : uint32_t { enum class XDeploymentType : uint32_t {
kOpticalDisc = 0, kOpticalDisc = 0,
kHardDrive = 1, // Like extracted? kHardDrive = 1, // Like extracted?
@ -650,8 +680,43 @@ struct X_PROFILEENUMRESULT {
}; };
static_assert_size(X_PROFILEENUMRESULT, 0x188); static_assert_size(X_PROFILEENUMRESULT, 0x188);
} // namespace xe struct MESSAGEBOX_RESULT {
union {
xe::be<uint32_t> ButtonPressed;
xe::be<uint16_t> Passcode[4];
};
};
// clang-format off
#define XMBox_NOICON 0x00000000
#define XMBox_ERRORICON 0x00000001
#define XMBox_WARNINGICON 0x00000002
#define XMBox_ALERTICON 0x00000003
#define XMBox_PASSCODEMODE 0x00010000
#define XMBox_VERIFYPASSCODEMODE 0x00020000
#define XMBox_WAITANIMATION 0x00001000
#define XMBox_LIVEPASSCODEMODE 0x00030000
#define XMBox_MODEMASK 0x00030000
#define XMBox_OK 1
#define XMBox_CANCEL 2
#define X_BUTTON_PASSCODE 0x00005802
#define Y_BUTTON_PASSCODE 0x00005803
#define RIGHT_BUMPER_PASSCODE 0x00005804
#define LEFT_BUMPER_PASSCODE 0x00005805
#define LEFT_TRIGGER_PASSCODE 0x00005806
#define RIGHT_TRIGGER_PASSCODE 0x00005807
#define DPAD_UP_PASSCODE 0x00005810
#define DPAD_DOWN_PASSCODE 0x00005811
#define DPAD_LEFT_PASSCODE 0x00005812
#define DPAD_RIGHT_PASSCODE 0x00005813
// clang-format on // clang-format on
} // namespace xe
#endif // XENIA_XBOX_H_ #endif // XENIA_XBOX_H_

Some files were not shown because too many files have changed in this diff Show More