Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 83152c8cad | |||
| 35a89d616a |
@@ -1,37 +0,0 @@
|
||||
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
|
||||
|
||||
# Keep inherited review profiles, automatic reviews, tools, and pre-merge checks.
|
||||
inheritance: true
|
||||
|
||||
reviews:
|
||||
# Trim supplemental output and keep the remaining walkthrough collapsed.
|
||||
high_level_summary: false
|
||||
review_status: false
|
||||
review_details: false
|
||||
collapse_walkthrough: true
|
||||
changed_files_summary: false
|
||||
sequence_diagrams: false
|
||||
estimate_code_review_effort: false
|
||||
related_issues: false
|
||||
related_prs: false
|
||||
in_progress_fortune: false
|
||||
poem: false
|
||||
enable_prompt_for_ai_agents: false
|
||||
|
||||
# Hide standard finishing-touch controls; this also disables their commands.
|
||||
finishing_touches:
|
||||
docstrings:
|
||||
enabled: false
|
||||
unit_tests:
|
||||
enabled: false
|
||||
simplify:
|
||||
enabled: false
|
||||
autofix:
|
||||
enabled: false
|
||||
fix_ci:
|
||||
enabled: false
|
||||
resolve_merge_conflict:
|
||||
enabled: false
|
||||
|
||||
chat:
|
||||
art: false
|
||||
@@ -1,6 +1,6 @@
|
||||
name: Game Emulation Status Report
|
||||
description: Submit a KytyPS5 game compatibility result, including boot, rendering, audio, input, or gameplay behavior.
|
||||
title: "[GAME STATUS]: "
|
||||
name: Game Emulation Bug Report
|
||||
description: Report KytyPS5 game boot, crash, freeze, rendering, audio, input, or gameplay behavior problems.
|
||||
title: "[GAME BUG]: "
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
@@ -10,7 +10,6 @@ body:
|
||||
Before submitting, please:
|
||||
- Search existing issues first.
|
||||
- Re-test with a clean game dump.
|
||||
- Use the latest KytyPS5 build available to you.
|
||||
- Attach the complete log file instead of edited snippets.
|
||||
|
||||
- type: markdown
|
||||
@@ -45,28 +44,6 @@ body:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: compatibility-status
|
||||
attributes:
|
||||
label: Compatibility status
|
||||
description: Select the furthest point the game consistently reaches on this build.
|
||||
options:
|
||||
- "Doesn't boot"
|
||||
- Logo
|
||||
- Main menu
|
||||
- In game
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**Status guide**
|
||||
- **Doesn't boot:** The game does not show its first logo or startup screen.
|
||||
- **Logo:** The game shows a logo or startup screen but does not reach the main menu.
|
||||
- **Main menu:** The game reaches its menus but does not enter gameplay.
|
||||
- **In game:** The game reaches controllable gameplay, even if bugs prevent completion.
|
||||
|
||||
- type: input
|
||||
id: regression-builds
|
||||
attributes:
|
||||
@@ -77,22 +54,22 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## Test result
|
||||
## Problem details
|
||||
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: Result details
|
||||
description: Describe what worked, what failed, and any visible or audible problems.
|
||||
placeholder: "Example: The game reaches the main menu, then freezes after selecting Continue. Audio plays correctly until the freeze."
|
||||
label: What happened?
|
||||
description: Describe the failure or incorrect behavior as directly as possible.
|
||||
placeholder: "Example: The game reaches the main menu, then freezes after selecting Continue."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
attributes:
|
||||
label: Steps to reproduce the result
|
||||
description: List the shortest path to the reported status or failure from a fresh KytyPS5 start.
|
||||
label: Steps to reproduce
|
||||
description: List the shortest path that triggers the problem from a fresh KytyPS5 start.
|
||||
placeholder: |
|
||||
1. Open KytyPS5.
|
||||
2. Boot the game.
|
||||
@@ -104,7 +81,7 @@ body:
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: Explain what should happen at the same point in the game. If there is no known problem, write "No known issue".
|
||||
description: Explain what should happen at the same point in the game.
|
||||
placeholder: "Example: The save should load and gameplay should continue normally."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -6,32 +6,9 @@ on:
|
||||
branches: [ main, master ]
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
env:
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
KYTY_BUILD_ORIGIN: ${{ github.repository != 'KytyPS5/KytyPS5' && 'Fork' || github.event_name == 'push' && 'Official' || 'Source' }}
|
||||
KYTY_BUILD_REPOSITORY: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
metadata:
|
||||
name: Prepare build metadata
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
release_tag: ${{ steps.release_tag.outputs.value }}
|
||||
|
||||
steps:
|
||||
- name: Generate release tag
|
||||
id: release_tag
|
||||
shell: bash
|
||||
run: |
|
||||
echo "value=KytyPS5-$(date -u +'%Y-%m-%d')-${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
windows:
|
||||
name: Build KytyPS5 (Windows)
|
||||
needs: metadata
|
||||
runs-on: windows-2022
|
||||
|
||||
steps:
|
||||
@@ -43,6 +20,9 @@ jobs:
|
||||
- name: Setup Visual Studio environment
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
- name: Setup Ninja
|
||||
uses: seanmiddleditch/gha-setup-ninja@v5
|
||||
|
||||
- name: Locate vcpkg
|
||||
id: vcpkg
|
||||
shell: pwsh
|
||||
@@ -80,9 +60,6 @@ jobs:
|
||||
arch: win64_msvc2022_64
|
||||
cache: true
|
||||
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.11
|
||||
|
||||
- name: Verify toolchain
|
||||
shell: cmd
|
||||
run: |
|
||||
@@ -96,32 +73,22 @@ jobs:
|
||||
- name: Configure
|
||||
shell: cmd
|
||||
run: |
|
||||
cmake -S . -B _Build/windows ^
|
||||
cmake -S src -B _Build/windows ^
|
||||
-G Ninja ^
|
||||
-DCMAKE_BUILD_TYPE=Release ^
|
||||
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON ^
|
||||
-DKYTY_BUILD_ORIGIN=%KYTY_BUILD_ORIGIN% ^
|
||||
-DKYTY_BUILD_REPOSITORY=%KYTY_BUILD_REPOSITORY% ^
|
||||
-DKYTY_RELEASE_TAG=${{ needs.metadata.outputs.release_tag }} ^
|
||||
-DCMAKE_C_COMPILER=clang-cl ^
|
||||
-DCMAKE_CXX_COMPILER=clang-cl ^
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=sccache ^
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache ^
|
||||
-DCMAKE_PREFIX_PATH="%Qt6_DIR%"
|
||||
|
||||
- name: Build
|
||||
shell: cmd
|
||||
run: |
|
||||
cmake --build _Build/windows --target launcher ^
|
||||
audio_out2_port_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests ^
|
||||
shader_recompiler_compute_tests ^
|
||||
resource_tracking_tests scalar_provenance_tests ^
|
||||
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests --parallel
|
||||
cmake --build _Build/windows --target launcher audio_out2_port_tests virtual_memory_allocation_tests --parallel
|
||||
|
||||
- name: Test
|
||||
shell: cmd
|
||||
run: |
|
||||
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$"
|
||||
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|virtual_memory_allocation)$"
|
||||
|
||||
- name: Install
|
||||
shell: cmd
|
||||
@@ -137,7 +104,6 @@ jobs:
|
||||
|
||||
macos:
|
||||
name: Build KytyPS5 (macOS)
|
||||
needs: metadata
|
||||
runs-on: macos-15
|
||||
|
||||
steps:
|
||||
@@ -156,7 +122,7 @@ jobs:
|
||||
|
||||
- name: Install build dependencies
|
||||
shell: bash
|
||||
run: brew install glslang
|
||||
run: brew install glslang ninja
|
||||
|
||||
- name: Install Qt 6.10.3
|
||||
uses: jurplel/install-qt-action@v4
|
||||
@@ -167,9 +133,6 @@ jobs:
|
||||
arch: clang_64
|
||||
cache: true
|
||||
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.11
|
||||
|
||||
- name: Verify toolchain
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -184,35 +147,26 @@ jobs:
|
||||
- name: Configure
|
||||
shell: bash
|
||||
run: |
|
||||
cmake -S . -B _Build/macos \
|
||||
cmake -S src -B _Build/macos \
|
||||
-G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \
|
||||
-DKYTY_BUILD_ORIGIN="$KYTY_BUILD_ORIGIN" \
|
||||
-DKYTY_BUILD_REPOSITORY="$KYTY_BUILD_REPOSITORY" \
|
||||
-DKYTY_RELEASE_TAG="${{ needs.metadata.outputs.release_tag }}" \
|
||||
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=sccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache \
|
||||
-DCMAKE_PREFIX_PATH="$QT_ROOT_DIR"
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: |
|
||||
cmake --build _Build/macos \
|
||||
--target launcher audio_out2_port_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
|
||||
memory_tracker_tests page_manager_tests shader_recompiler_compute_tests \
|
||||
resource_tracking_tests scalar_provenance_tests \
|
||||
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests \
|
||||
--target launcher audio_out2_port_tests virtual_memory_allocation_tests \
|
||||
--parallel
|
||||
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
ctest --test-dir _Build/macos --output-on-failure \
|
||||
-R '^(audio_out2_port|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$'
|
||||
-R '^(audio_out2_port|virtual_memory_allocation)$'
|
||||
|
||||
- name: Install
|
||||
shell: bash
|
||||
@@ -227,73 +181,38 @@ jobs:
|
||||
run: |
|
||||
archive="$RUNNER_TEMP/MoltenVK-macos.tar"
|
||||
package="$RUNNER_TEMP/MoltenVK"
|
||||
install_dir="_Build/macos/install"
|
||||
app_dir="$install_dir/KytyPS5.app"
|
||||
moltenvk="$install_dir/libMoltenVK.dylib"
|
||||
curl --fail --location --retry 3 \
|
||||
--output "$archive" \
|
||||
"https://github.com/KhronosGroup/MoltenVK/releases/download/$MOLTENVK_VERSION/MoltenVK-macos.tar"
|
||||
echo "$MOLTENVK_SHA256 $archive" | shasum -a 256 --check
|
||||
tar -xf "$archive" -C "$RUNNER_TEMP"
|
||||
test -d "$app_dir"
|
||||
install -m 755 \
|
||||
"$package/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib" \
|
||||
"$moltenvk"
|
||||
install -m 644 "$package/LICENSE" "$install_dir/LICENSE.MoltenVK"
|
||||
codesign --force --sign - --timestamp=none "$moltenvk"
|
||||
install -d -m 755 "$app_dir/Contents/Frameworks" "$app_dir/Contents/Resources"
|
||||
install -m 755 "$moltenvk" "$app_dir/Contents/Frameworks/libMoltenVK.dylib"
|
||||
install -m 644 "$package/LICENSE" "$app_dir/Contents/Resources/LICENSE.MoltenVK"
|
||||
codesign --force --sign - --timestamp=none "$app_dir"
|
||||
_Build/macos/install/libMoltenVK.dylib
|
||||
install -m 644 "$package/LICENSE" _Build/macos/install/LICENSE.MoltenVK
|
||||
codesign --force --sign - --timestamp=none _Build/macos/install/libMoltenVK.dylib
|
||||
|
||||
- name: Verify artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
install_dir="_Build/macos/install"
|
||||
app_dir="$install_dir/KytyPS5.app"
|
||||
test -d "$app_dir"
|
||||
for binary in \
|
||||
"$install_dir/kyty_emulator" \
|
||||
"$install_dir/libMoltenVK.dylib" \
|
||||
"$app_dir/Contents/MacOS/KytyPS5" \
|
||||
"$app_dir/Contents/MacOS/kyty_emulator" \
|
||||
"$app_dir/Contents/MacOS/kyty_updater" \
|
||||
"$app_dir/Contents/Frameworks/libMoltenVK.dylib"; do
|
||||
file "$binary"
|
||||
lipo "$binary" -verify_arch x86_64
|
||||
done
|
||||
for binary in \
|
||||
"$install_dir/kyty_emulator" \
|
||||
"$install_dir/libMoltenVK.dylib" \
|
||||
"$app_dir/Contents/MacOS/KytyPS5" \
|
||||
"$app_dir/Contents/MacOS/kyty_emulator" \
|
||||
"$app_dir/Contents/MacOS/kyty_updater" \
|
||||
"$app_dir/Contents/Frameworks/libMoltenVK.dylib"; do
|
||||
codesign --verify --strict "$binary"
|
||||
done
|
||||
codesign --verify --deep --strict --verbose=2 "$app_dir"
|
||||
|
||||
- name: Package macOS artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
install_dir="_Build/macos/install"
|
||||
archive="$RUNNER_TEMP/KytyPS5-macOS-x86_64.zip"
|
||||
(
|
||||
cd "$install_dir"
|
||||
zip -qry "$archive" .
|
||||
)
|
||||
unzip -t "$archive"
|
||||
file _Build/macos/install/launcher
|
||||
file _Build/macos/install/kyty_emulator
|
||||
file _Build/macos/install/libMoltenVK.dylib
|
||||
lipo _Build/macos/install/launcher -verify_arch x86_64
|
||||
lipo _Build/macos/install/kyty_emulator -verify_arch x86_64
|
||||
lipo _Build/macos/install/libMoltenVK.dylib -verify_arch x86_64
|
||||
codesign --verify --strict _Build/macos/install/kyty_emulator
|
||||
codesign --verify --strict _Build/macos/install/libMoltenVK.dylib
|
||||
|
||||
- name: Upload macOS artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: KytyPS5-macOS-x86_64
|
||||
path: ${{ runner.temp }}/KytyPS5-macOS-x86_64.zip
|
||||
path: _Build/macos/install/**
|
||||
if-no-files-found: error
|
||||
|
||||
linux:
|
||||
name: Build KytyPS5 (Linux)
|
||||
needs: metadata
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
@@ -323,9 +242,8 @@ jobs:
|
||||
libxkbcommon-dev \
|
||||
libxrandr-dev \
|
||||
libxss-dev \
|
||||
libxtst-dev \
|
||||
lld \
|
||||
pkg-config \
|
||||
ninja-build \
|
||||
wayland-protocols
|
||||
|
||||
- name: Install Qt 6.10.3
|
||||
@@ -337,9 +255,6 @@ jobs:
|
||||
arch: linux_gcc_64
|
||||
cache: true
|
||||
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.11
|
||||
|
||||
- name: Verify toolchain
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -355,21 +270,15 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p _Build
|
||||
cmake -S . -B _Build/linux \
|
||||
cmake -S src -B _Build/linux \
|
||||
-G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \
|
||||
-DKYTY_BUILD_ORIGIN="$KYTY_BUILD_ORIGIN" \
|
||||
-DKYTY_BUILD_REPOSITORY="$KYTY_BUILD_REPOSITORY" \
|
||||
-DKYTY_RELEASE_TAG="${{ needs.metadata.outputs.release_tag }}" \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=sccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache \
|
||||
-DCMAKE_PREFIX_PATH="$Qt6_DIR" 2>&1 | tee _Build/configure.log
|
||||
exit "${PIPESTATUS[0]}"
|
||||
|
||||
- name: Verify SDL3 backends
|
||||
- name: Verify SDL2 backends
|
||||
shell: bash
|
||||
run: |
|
||||
status=0
|
||||
@@ -388,16 +297,14 @@ jobs:
|
||||
run: |
|
||||
cmake --build _Build/linux \
|
||||
--target launcher page_manager_tests memory_tracker_tests \
|
||||
audio_out2_port_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
|
||||
resource_tracking_tests scalar_provenance_tests \
|
||||
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests \
|
||||
audio_out2_port_tests virtual_memory_allocation_tests \
|
||||
--parallel
|
||||
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
ctest --test-dir _Build/linux --output-on-failure \
|
||||
-R '^(audio_out2_port|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$'
|
||||
-R '^(audio_out2_port|page_manager|memory_tracker|virtual_memory_allocation)$'
|
||||
|
||||
- name: Install
|
||||
shell: bash
|
||||
@@ -407,12 +314,11 @@ jobs:
|
||||
- name: Verify artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
file _Build/linux/install/kyty_launcher
|
||||
file _Build/linux/install/launcher
|
||||
file _Build/linux/install/kyty_emulator
|
||||
test -x _Build/linux/install/kyty_updater
|
||||
file _Build/linux/install/kyty_emulator | grep -q "ELF 64-bit LSB .*x86-64"
|
||||
ldd _Build/linux/install/kyty_emulator > /dev/null
|
||||
readelf -d _Build/linux/install/kyty_launcher | grep -q 'RPATH.*\$ORIGIN/lib'
|
||||
readelf -d _Build/linux/install/launcher | grep -q 'RPATH.*\$ORIGIN/lib'
|
||||
while IFS= read -r -d '' binary; do
|
||||
while read -r dependency; do
|
||||
test -e "_Build/linux/install/lib/$dependency"
|
||||
@@ -421,8 +327,8 @@ jobs:
|
||||
sed -n 's/.*Shared library: \[\(libQt6[^]]*\|libicu[^]]*\)\].*/\1/p'
|
||||
)
|
||||
done < <(
|
||||
find _Build/linux/install/kyty_launcher _Build/linux/install/plugins \
|
||||
-type f \( -name kyty_launcher -o -name '*.so' \) -print0
|
||||
find _Build/linux/install/launcher _Build/linux/install/plugins \
|
||||
-type f \( -name launcher -o -name '*.so' \) -print0
|
||||
)
|
||||
_Build/linux/install/kyty_emulator --help > /dev/null
|
||||
|
||||
@@ -435,71 +341,46 @@ jobs:
|
||||
|
||||
release:
|
||||
name: Release KytyPS5
|
||||
if: >-
|
||||
always() &&
|
||||
github.event_name == 'push' &&
|
||||
github.repository == 'KytyPS5/KytyPS5' &&
|
||||
(needs.windows.result == 'success' ||
|
||||
needs.macos.result == 'success' ||
|
||||
needs.linux.result == 'success')
|
||||
needs: [metadata, windows, macos, linux]
|
||||
if: github.event_name == 'push' && github.repository == 'KytyPS5/KytyPS5'
|
||||
needs: [windows, macos, linux]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
RELEASE_NAME: ${{ needs.metadata.outputs.release_tag }}
|
||||
|
||||
steps:
|
||||
- name: Download Windows build
|
||||
if: needs.windows.result == 'success'
|
||||
- name: Download builds
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: KytyPS5-Windows-x64
|
||||
path: artifacts/KytyPS5-Windows-x64
|
||||
path: artifacts
|
||||
|
||||
- name: Download macOS build
|
||||
if: needs.macos.result == 'success'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: KytyPS5-macOS-x86_64
|
||||
path: artifacts/KytyPS5-macOS-x86_64
|
||||
- name: Set release name
|
||||
shell: bash
|
||||
run: |
|
||||
echo "RELEASE_NAME=KytyPS5-$(date -u +'%Y-%m-%d')-${GITHUB_SHA::7}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Download Linux build
|
||||
if: needs.linux.result == 'success'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: KytyPS5-Linux-x86_64
|
||||
path: artifacts/KytyPS5-Linux-x86_64
|
||||
|
||||
- name: Package Windows build
|
||||
if: needs.windows.result == 'success'
|
||||
- name: Package builds
|
||||
shell: bash
|
||||
run: |
|
||||
windows_dir="artifacts/KytyPS5-Windows-x64"
|
||||
macos_dir="artifacts/KytyPS5-macOS-x86_64"
|
||||
linux_dir="artifacts/KytyPS5-Linux-x86_64"
|
||||
test -d "$windows_dir"
|
||||
test -d "$macos_dir"
|
||||
test -d "$linux_dir"
|
||||
chmod a+x \
|
||||
"$macos_dir/launcher" \
|
||||
"$macos_dir/kyty_emulator" \
|
||||
"$macos_dir/libMoltenVK.dylib" \
|
||||
"$linux_dir/launcher" \
|
||||
"$linux_dir/kyty_emulator"
|
||||
(
|
||||
cd "$windows_dir"
|
||||
zip -r "$GITHUB_WORKSPACE/$RELEASE_NAME-Windows-x64.zip" .
|
||||
)
|
||||
|
||||
- name: Package macOS build
|
||||
if: needs.macos.result == 'success'
|
||||
shell: bash
|
||||
run: |
|
||||
macos_archive="artifacts/KytyPS5-macOS-x86_64/KytyPS5-macOS-x86_64.zip"
|
||||
test -f "$macos_archive"
|
||||
cp "$macos_archive" "$RELEASE_NAME-macOS-x86_64.zip"
|
||||
|
||||
- name: Package Linux build
|
||||
if: needs.linux.result == 'success'
|
||||
shell: bash
|
||||
run: |
|
||||
linux_dir="artifacts/KytyPS5-Linux-x86_64"
|
||||
test -d "$linux_dir"
|
||||
chmod a+x \
|
||||
"$linux_dir/kyty_launcher" \
|
||||
"$linux_dir/kyty_emulator" \
|
||||
"$linux_dir/kyty_updater"
|
||||
(
|
||||
cd "$macos_dir"
|
||||
zip -r "$GITHUB_WORKSPACE/$RELEASE_NAME-macOS-x86_64.zip" .
|
||||
)
|
||||
tar -C "$linux_dir" -czf \
|
||||
"$GITHUB_WORKSPACE/$RELEASE_NAME-Linux-x86_64.tar.gz" .
|
||||
|
||||
@@ -508,16 +389,11 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
assets=()
|
||||
for artifact in \
|
||||
"$RELEASE_NAME-Windows-x64.zip" \
|
||||
"$RELEASE_NAME-macOS-x86_64.zip" \
|
||||
"$RELEASE_NAME-Linux-x86_64.tar.gz"; do
|
||||
if [[ -f "$artifact" ]]; then
|
||||
assets+=("$artifact")
|
||||
fi
|
||||
done
|
||||
test "${#assets[@]}" -gt 0
|
||||
assets=(
|
||||
"$RELEASE_NAME-Windows-x64.zip"
|
||||
"$RELEASE_NAME-macOS-x86_64.zip"
|
||||
"$RELEASE_NAME-Linux-x86_64.tar.gz"
|
||||
)
|
||||
if gh release view "$RELEASE_NAME" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
|
||||
gh release upload "$RELEASE_NAME" "${assets[@]}" \
|
||||
--clobber \
|
||||
|
||||
@@ -3,6 +3,4 @@
|
||||
.idea/
|
||||
build/
|
||||
_Build/vscode-clang/
|
||||
_Build/
|
||||
cmake-build-debug/
|
||||
_PipelineCache/
|
||||
_Build/
|
||||
@@ -1,7 +1,7 @@
|
||||
[submodule "3rdparty/SDL3"]
|
||||
path = 3rdparty/SDL3
|
||||
[submodule "3rdparty/SDL2"]
|
||||
path = 3rdparty/SDL2
|
||||
url = https://github.com/libsdl-org/SDL.git
|
||||
branch = release-3.4.x
|
||||
branch = SDL2
|
||||
[submodule "3rdparty/magic_enum"]
|
||||
path = 3rdparty/magic_enum
|
||||
url = https://github.com/Neargye/magic_enum.git
|
||||
@@ -22,7 +22,7 @@
|
||||
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
|
||||
[submodule "3rdparty/ffmpeg-core"]
|
||||
path = 3rdparty/ffmpeg-core
|
||||
url = https://github.com/KytyPS5/ext-ffmpeg-core.git
|
||||
url = https://github.com/shadps4-emu/ext-ffmpeg-core.git
|
||||
shallow = true
|
||||
[submodule "3rdparty/fmt"]
|
||||
path = 3rdparty/fmt
|
||||
@@ -39,6 +39,3 @@
|
||||
[submodule "3rdparty/nlohmann_json"]
|
||||
path = 3rdparty/nlohmann_json
|
||||
url = https://github.com/nlohmann/json.git
|
||||
[submodule "3rdparty/imgui"]
|
||||
path = 3rdparty/imgui
|
||||
url = https://github.com/ocornut/imgui.git
|
||||
|
||||
@@ -5,19 +5,12 @@
|
||||
"name": "Debug launcher UI",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}\\_Build\\vscode-clang\\launcher\\kyty_launcher.exe",
|
||||
"program": "${workspaceFolder}\\_Build\\vscode-clang\\launcher\\launcher.exe",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}\\_Build\\vscode-clang\\launcher",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"linux": {
|
||||
"type": "cppdbg",
|
||||
"program": "${workspaceFolder}/_Build/vscode-clang/launcher/kyty_launcher",
|
||||
"cwd": "${workspaceFolder}/_Build/vscode-clang/launcher",
|
||||
"MIMode": "gdb",
|
||||
"miDebuggerPath": "/usr/bin/gdb"
|
||||
}
|
||||
"externalConsole": false
|
||||
},
|
||||
{
|
||||
"name": "Debug kyty_emulator",
|
||||
@@ -32,40 +25,13 @@
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}\\_Build\\vscode-clang",
|
||||
"environment": [],
|
||||
"externalConsole": true,
|
||||
"linux": {
|
||||
"type": "cppdbg",
|
||||
"program": "${workspaceFolder}/_Build/vscode-clang/kyty_emulator",
|
||||
"cwd": "${workspaceFolder}/_Build/vscode-clang",
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"miDebuggerPath": "/usr/bin/gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Pass Kyty's internal signals to its handlers",
|
||||
"text": "handle SIG37 SIGSEGV SIGBUS SIGILL nostop noprint pass",
|
||||
"ignoreFailures": false
|
||||
}
|
||||
]
|
||||
}
|
||||
"externalConsole": true
|
||||
},
|
||||
{
|
||||
"name": "Attach to running kyty_emulator",
|
||||
"type": "cppvsdbg",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}",
|
||||
"linux": {
|
||||
"type": "cppdbg",
|
||||
"MIMode": "gdb",
|
||||
"miDebuggerPath": "/usr/bin/gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Pass Kyty's internal signals to its handlers",
|
||||
"text": "handle SIG37 SIGSEGV SIGBUS SIGILL nostop noprint pass",
|
||||
"ignoreFailures": false
|
||||
}
|
||||
]
|
||||
}
|
||||
"processId": "${command:pickProcess}"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
{
|
||||
"cmake.sourceDirectory": "${workspaceFolder}",
|
||||
"cmake.sourceDirectory": "${workspaceFolder}/src",
|
||||
"cmake.buildDirectory": "${workspaceFolder}/_Build/vscode-clang",
|
||||
"cmake.generator": "Ninja",
|
||||
"cmake.useVsDeveloperEnvironment": "always",
|
||||
"cmake.environment": {
|
||||
"CC": "clang-cl",
|
||||
"CXX": "clang-cl"
|
||||
"CXX": "clang-cl",
|
||||
"PATH": "${env:ProgramFiles}/Microsoft Visual Studio/18/Community/VC/Tools/Llvm/x64/bin;${env:ProgramFiles}/Microsoft Visual Studio/18/Community/Common7/IDE/CommonExtensions/Microsoft/CMake/Ninja;${env:PATH}"
|
||||
},
|
||||
"cmake.configureSettings": {
|
||||
"CMAKE_C_COMPILER": "clang-cl",
|
||||
"CMAKE_CXX_COMPILER": "clang-cl",
|
||||
"CMAKE_PREFIX_PATH": "C:/Qt/6.11.0/msvc2022_64"
|
||||
},
|
||||
"C_Cpp.default.intelliSenseMode": "windows-clang-x64",
|
||||
"C_Cpp.default.compileCommands": "${workspaceFolder}/_Build/vscode-clang/compile_commands.json",
|
||||
"C_Cpp.default.cppStandard": "c++20"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,103 +2,8 @@ set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
||||
|
||||
add_subdirectory(cpuinfo)
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
# The Bluetooth DualSense transports speaker audio as fixed-size Opus packets.
|
||||
set(kyty_build_testing_saved "${BUILD_TESTING}")
|
||||
set(BUILD_TESTING OFF)
|
||||
set(OPUS_BUILD_PROGRAMS OFF CACHE BOOL "" FORCE)
|
||||
set(OPUS_INSTALL_PKG_CONFIG_MODULE OFF CACHE BOOL "" FORCE)
|
||||
set(OPUS_INSTALL_CMAKE_CONFIG_MODULE OFF CACHE BOOL "" FORCE)
|
||||
if(MSVC AND CMAKE_C_COMPILER_ID STREQUAL "Clang")
|
||||
# Opus assumes MSVC accepts SSE4.1 intrinsics without per-file flags, but
|
||||
# clang-cl rejects them in its baseline x64 files. Its AVX2 dispatch also
|
||||
# depends on the SSE4.1 declarations, so retain the x64 SSE2 baseline only.
|
||||
set(OPUS_X86_MAY_HAVE_SSE4_1 OFF CACHE BOOL "" FORCE)
|
||||
set(OPUS_X86_MAY_HAVE_AVX2 OFF CACHE BOOL "" FORCE)
|
||||
endif()
|
||||
FetchContent_Declare(opus
|
||||
GIT_REPOSITORY https://github.com/xiph/opus.git
|
||||
GIT_TAG ddbe48383984d56acd9e1ab6a090c54ca6b735a6
|
||||
GIT_SHALLOW FALSE)
|
||||
FetchContent_MakeAvailable(opus)
|
||||
set(BUILD_TESTING "${kyty_build_testing_saved}")
|
||||
install(FILES "${opus_SOURCE_DIR}/COPYING" DESTINATION "licenses/opus")
|
||||
|
||||
# The guest instruction patcher needs a decoder/encoder and a small
|
||||
# trampoline assembler. Pin the same revisions used by shadPS4's implementation.
|
||||
set(XBYAK_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
FetchContent_Declare(
|
||||
xbyak
|
||||
GIT_REPOSITORY https://github.com/herumi/xbyak.git
|
||||
GIT_TAG 44a72f369268f7d552650891b296693e91db86bb
|
||||
GIT_SHALLOW FALSE
|
||||
)
|
||||
|
||||
set(ZYDIS_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
set(ZYDIS_BUILD_TOOLS OFF CACHE BOOL "" FORCE)
|
||||
set(ZYDIS_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(ZYDIS_BUILD_DOXYGEN OFF CACHE BOOL "" FORCE)
|
||||
FetchContent_Declare(
|
||||
zydis
|
||||
GIT_REPOSITORY https://github.com/zyantific/zydis.git
|
||||
GIT_TAG 120e0e705f8e3b507dc49377ac2879979f0d545c
|
||||
GIT_SUBMODULES_RECURSE TRUE
|
||||
GIT_SHALLOW FALSE
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(xbyak zydis)
|
||||
target_compile_definitions(xbyak INTERFACE XBYAK_NO_EXCEPTION)
|
||||
|
||||
# ZArchive provides random-access reads for compressed game dumps. Build only
|
||||
# the reader/writer library and its zstd dependency; the command-line tool is
|
||||
# not part of KytyPS5's runtime distribution.
|
||||
set(ZSTD_BUILD_PROGRAMS OFF CACHE BOOL "" FORCE)
|
||||
set(ZSTD_BUILD_SHARED OFF CACHE BOOL "" FORCE)
|
||||
set(ZSTD_BUILD_STATIC ON CACHE BOOL "" FORCE)
|
||||
set(ZSTD_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(ZSTD_LEGACY_SUPPORT OFF CACHE BOOL "" FORCE)
|
||||
set(ZSTD_MULTITHREAD_SUPPORT OFF CACHE BOOL "" FORCE)
|
||||
FetchContent_Declare(
|
||||
zstd
|
||||
GIT_REPOSITORY https://github.com/facebook/zstd.git
|
||||
GIT_TAG f8745da6ff1ad1e7bab384bd1f9d742439278e99
|
||||
GIT_SHALLOW FALSE
|
||||
SOURCE_SUBDIR build/cmake
|
||||
)
|
||||
FetchContent_Declare(
|
||||
zarchive_source
|
||||
GIT_REPOSITORY https://github.com/Exzap/ZArchive.git
|
||||
GIT_TAG d2c717730092c7bf8cbb033b12fd4001b7c4d932
|
||||
GIT_SHALLOW FALSE
|
||||
PATCH_COMMAND "${GIT_EXECUTABLE}" apply --ignore-whitespace
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/patches/zarchive-reader.patch"
|
||||
SOURCE_SUBDIR kyty-library-only
|
||||
)
|
||||
FetchContent_MakeAvailable(zstd zarchive_source)
|
||||
|
||||
add_library(zarchive STATIC
|
||||
"${zarchive_source_SOURCE_DIR}/src/zarchivereader.cpp"
|
||||
"${zarchive_source_SOURCE_DIR}/src/zarchivewriter.cpp"
|
||||
"${zarchive_source_SOURCE_DIR}/src/sha_256.c"
|
||||
)
|
||||
add_library(ZArchive::zarchive ALIAS zarchive)
|
||||
target_include_directories(zarchive SYSTEM PUBLIC "${zarchive_source_SOURCE_DIR}/include")
|
||||
target_include_directories(zarchive PRIVATE "${zstd_SOURCE_DIR}/lib")
|
||||
target_link_libraries(zarchive PRIVATE libzstd_static)
|
||||
target_compile_features(zarchive PRIVATE cxx_std_20)
|
||||
if(MSVC)
|
||||
target_compile_options(zarchive PRIVATE /W0)
|
||||
else()
|
||||
target_compile_options(zarchive PRIVATE -w)
|
||||
endif()
|
||||
|
||||
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
||||
if(APPLE)
|
||||
# SDL's built-in iconv fallback does not support CP932.
|
||||
set(SDL_SYSTEM_ICONV ON CACHE BOOL "Use system iconv for CP932 conversion" FORCE)
|
||||
endif()
|
||||
add_subdirectory(SDL3)
|
||||
add_subdirectory(SDL2)
|
||||
|
||||
add_library(Vulkan-Headers INTERFACE)
|
||||
add_library(Vulkan::Headers ALIAS Vulkan-Headers)
|
||||
@@ -106,25 +11,6 @@ target_include_directories(Vulkan-Headers INTERFACE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Vulkan-Headers/include"
|
||||
)
|
||||
|
||||
add_library(imgui STATIC
|
||||
imgui/imgui.cpp
|
||||
imgui/imgui_draw.cpp
|
||||
imgui/imgui_tables.cpp
|
||||
imgui/imgui_widgets.cpp
|
||||
imgui/backends/imgui_impl_vulkan.cpp
|
||||
)
|
||||
target_include_directories(imgui PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/imgui"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/imgui/backends"
|
||||
)
|
||||
target_compile_definitions(imgui PRIVATE IMGUI_IMPL_VULKAN_NO_PROTOTYPES)
|
||||
target_link_libraries(imgui PUBLIC Vulkan::Headers)
|
||||
if(MSVC)
|
||||
target_compile_options(imgui PRIVATE /W0)
|
||||
else()
|
||||
target_compile_options(imgui PRIVATE -w)
|
||||
endif()
|
||||
|
||||
set(SPIRV_SKIP_TESTS ON CACHE BOOL "" FORCE)
|
||||
set(SPIRV_SKIP_EXECUTABLES ON CACHE BOOL "" FORCE)
|
||||
set(SKIP_SPIRV_TOOLS_INSTALL ON CACHE BOOL "" FORCE)
|
||||
@@ -155,9 +41,6 @@ set(SPDLOG_BUILD_WARNINGS OFF CACHE BOOL "" FORCE)
|
||||
set(SPDLOG_INSTALL OFF CACHE BOOL "" FORCE)
|
||||
set(SPDLOG_FMT_EXTERNAL ON CACHE BOOL "" FORCE)
|
||||
set(SPDLOG_NO_EXCEPTIONS ON CACHE BOOL "" FORCE)
|
||||
if(WIN32)
|
||||
set(SPDLOG_WCHAR_FILENAMES ON CACHE BOOL "" FORCE)
|
||||
endif()
|
||||
add_subdirectory(spdlog EXCLUDE_FROM_ALL)
|
||||
|
||||
if (NOT TARGET FFmpeg::ffmpeg)
|
||||
@@ -189,7 +72,6 @@ endif()
|
||||
|
||||
set(TRACY_STATIC ON CACHE BOOL "" FORCE)
|
||||
set(TRACY_ENABLE ON CACHE BOOL "" FORCE)
|
||||
set(TRACY_NO_CRASH_HANDLER ON CACHE BOOL "" FORCE)
|
||||
set(TRACY_ON_DEMAND ON CACHE BOOL "" FORCE)
|
||||
set(TRACY_DELAYED_INIT ON CACHE BOOL "" FORCE)
|
||||
set(TRACY_MANUAL_LIFETIME ON CACHE BOOL "" FORCE)
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include <cpuinfo/internal-api.h>
|
||||
#include <cpuinfo/log.h>
|
||||
|
||||
/* Polyfill recent CPUFAMILY_ARM_* values for older platform headers */
|
||||
/* Polyfill recent CPUFAMILY_ARM_* values for older SDKs */
|
||||
#ifndef CPUFAMILY_ARM_MONSOON_MISTRAL
|
||||
#define CPUFAMILY_ARM_MONSOON_MISTRAL 0xE81E7EF6
|
||||
#endif
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
--- a/include/zarchive/zarchivecommon.h
|
||||
+++ b/include/zarchive/zarchivecommon.h
|
||||
@@ -256,7 +256,7 @@
|
||||
|
||||
bool IsWithinValidRange(uint64_t fileSize) const
|
||||
{
|
||||
- return (offset + size) <= fileSize;
|
||||
+ return offset <= fileSize && size <= fileSize - offset;
|
||||
}
|
||||
};
|
||||
|
||||
--- a/src/zarchivereader.cpp
|
||||
+++ b/src/zarchivereader.cpp
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
static bool _ifstream_readBytes(std::ifstream& file, uint64_t offset, void* buffer, uint32_t size)
|
||||
{
|
||||
+ file.clear();
|
||||
file.seekg(offset, std::ios_base::beg);
|
||||
file.read((char*)buffer, size);
|
||||
return file.gcount() == size;
|
||||
@@ -80,9 +81,27 @@
|
||||
// verify file tree
|
||||
if (fileTree[0].IsFile())
|
||||
return nullptr; // first entry must be root directory
|
||||
- auto rootName = GetName(nameTable, fileTree[0].GetNameOffset());
|
||||
- if (!rootName.empty())
|
||||
+ if (fileTree[0].GetNameOffset() != 0x7FFFFFFF)
|
||||
return nullptr; // root node must not have a name
|
||||
+ const uint64_t dataCapacity = (uint64_t)offsetRecords.size() * _ZARCHIVE::ENTRIES_PER_OFFSETRECORD * _ZARCHIVE::COMPRESSED_BLOCK_SIZE;
|
||||
+ for (size_t i = 0; i < fileTree.size(); ++i)
|
||||
+ {
|
||||
+ auto& entry = fileTree[i];
|
||||
+ if (i != 0)
|
||||
+ {
|
||||
+ auto name = GetName(nameTable, entry.GetNameOffset());
|
||||
+ if (name.empty() || name == "." || name == ".." || name.find_first_of("/\\") != std::string_view::npos || name.find('\0') != std::string_view::npos)
|
||||
+ return nullptr;
|
||||
+ }
|
||||
+ if (entry.IsFile())
|
||||
+ {
|
||||
+ if (entry.GetFileOffset() > dataCapacity || entry.GetFileSize() > dataCapacity - entry.GetFileOffset())
|
||||
+ return nullptr;
|
||||
+ }
|
||||
+ else if (entry.directoryRecord.nodeStartIndex > fileTree.size() ||
|
||||
+ entry.directoryRecord.count > fileTree.size() - entry.directoryRecord.nodeStartIndex)
|
||||
+ return nullptr;
|
||||
+ }
|
||||
// read meta data
|
||||
// todo
|
||||
|
||||
@@ -238,7 +257,7 @@
|
||||
uint32_t stepSize = std::min<uint32_t>(remainingBytes, _ZARCHIVE::COMPRESSED_BLOCK_SIZE - blockOffset);
|
||||
CacheBlock* block = GetCachedBlock(blockIdx);
|
||||
if (!block)
|
||||
- return 0;
|
||||
+ return bytesToRead - remainingBytes;
|
||||
std::memcpy(bufferU8, block->data + blockOffset, stepSize);
|
||||
rawReadOffset += stepSize;
|
||||
remainingBytes -= stepSize;
|
||||
@@ -325,14 +344,18 @@
|
||||
// determine offset and size of compressed block
|
||||
auto& record = m_offsetRecords[recordIndex];
|
||||
uint64_t offset = record.baseOffset;
|
||||
+ if (offset > m_compressedDataSize)
|
||||
+ return false;
|
||||
for (uint32_t i = 0; i < recordSubIndex; i++)
|
||||
{
|
||||
- offset += (uint64_t)record.size[i];
|
||||
- offset++;
|
||||
+ const uint64_t size = (uint64_t)record.size[i] + 1;
|
||||
+ if (size > m_compressedDataSize - offset)
|
||||
+ return false;
|
||||
+ offset += size;
|
||||
}
|
||||
uint32_t compressedSize = (uint32_t)record.size[recordSubIndex] + 1;
|
||||
// load file data
|
||||
- if ((offset + compressedSize) > m_compressedDataSize)
|
||||
+ if (compressedSize > m_compressedDataSize - offset)
|
||||
return false;
|
||||
offset += m_compressedDataOffset;
|
||||
if (compressedSize == _ZARCHIVE::COMPRESSED_BLOCK_SIZE)
|
||||
@@ -350,7 +373,7 @@
|
||||
// returns empty view on failure
|
||||
std::string_view ZArchiveReader::GetName(const std::vector<uint8_t>& nameTable, uint32_t nameOffset)
|
||||
{
|
||||
- if (nameOffset == 0x7FFFFFFF || nameOffset > nameTable.size())
|
||||
+ if (nameOffset == 0x7FFFFFFF || nameOffset >= nameTable.size())
|
||||
return "";
|
||||
// parse header
|
||||
uint16_t nameLength = nameTable[nameOffset] & 0x7F;
|
||||
@@ -359,7 +382,7 @@
|
||||
// extended 2-byte length
|
||||
if (nameOffset + 1 >= nameTable.size())
|
||||
return "";
|
||||
- nameLength |= ((uint16_t)nameTable[nameOffset] << 7);
|
||||
+ nameLength |= ((uint16_t)nameTable[nameOffset + 1] << 7);
|
||||
nameOffset += 2;
|
||||
}
|
||||
else
|
||||
@@ -1,875 +0,0 @@
|
||||
/******************************************************************************
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015-2026 Baldur Karlsson
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Documentation for the API is available at https://renderdoc.org/docs/in_application_api.html
|
||||
//
|
||||
|
||||
#if !defined(RENDERDOC_NO_STDINT)
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)
|
||||
#define RENDERDOC_CC __cdecl
|
||||
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__sun__) || defined(__OpenBSD__)
|
||||
#define RENDERDOC_CC
|
||||
#elif defined(__APPLE__)
|
||||
#define RENDERDOC_CC
|
||||
#else
|
||||
#error "Unknown platform"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constants not used directly in below API
|
||||
|
||||
// This is a GUID/magic value used for when applications pass a path where shader debug
|
||||
// information can be found to match up with a stripped shader.
|
||||
// the define can be used like so: const GUID RENDERDOC_ShaderDebugMagicValue =
|
||||
// RENDERDOC_ShaderDebugMagicValue_value
|
||||
#define RENDERDOC_ShaderDebugMagicValue_struct \
|
||||
{ \
|
||||
0xeab25520, 0x6670, 0x4865, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
|
||||
}
|
||||
|
||||
// as an alternative when you want a byte array (assuming x86 endianness):
|
||||
#define RENDERDOC_ShaderDebugMagicValue_bytearray \
|
||||
{ \
|
||||
0x20, 0x55, 0xb2, 0xea, 0x70, 0x66, 0x65, 0x48, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
|
||||
}
|
||||
|
||||
// truncated version when only a uint64_t is available (e.g. Vulkan tags):
|
||||
#define RENDERDOC_ShaderDebugMagicValue_truncated 0x48656670eab25520ULL
|
||||
|
||||
// this is a magic value for vulkan user tags to indicate which dispatchable API objects are which
|
||||
// for object annotations
|
||||
#define RENDERDOC_APIObjectAnnotationHelper 0xfbb3b337b664d0adULL
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// RenderDoc capture options
|
||||
//
|
||||
|
||||
typedef enum RENDERDOC_CaptureOption
|
||||
{
|
||||
// Allow the application to enable vsync
|
||||
//
|
||||
// Default - enabled
|
||||
//
|
||||
// 1 - The application can enable or disable vsync at will
|
||||
// 0 - vsync is force disabled
|
||||
eRENDERDOC_Option_AllowVSync = 0,
|
||||
|
||||
// Allow the application to enable fullscreen
|
||||
//
|
||||
// Default - enabled
|
||||
//
|
||||
// 1 - The application can enable or disable fullscreen at will
|
||||
// 0 - fullscreen is force disabled
|
||||
eRENDERDOC_Option_AllowFullscreen = 1,
|
||||
|
||||
// Record API debugging events and messages
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Enable built-in API debugging features and records the results into
|
||||
// the capture, which is matched up with events on replay
|
||||
// 0 - no API debugging is forcibly enabled
|
||||
eRENDERDOC_Option_APIValidation = 2,
|
||||
eRENDERDOC_Option_DebugDeviceMode = 2, // deprecated name of this enum
|
||||
|
||||
// Capture CPU callstacks for API events
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Enables capturing of callstacks
|
||||
// 0 - no callstacks are captured
|
||||
eRENDERDOC_Option_CaptureCallstacks = 3,
|
||||
|
||||
// When capturing CPU callstacks, only capture them from actions.
|
||||
// This option does nothing without the above option being enabled
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Only captures callstacks for actions.
|
||||
// Ignored if CaptureCallstacks is disabled
|
||||
// 0 - Callstacks, if enabled, are captured for every event.
|
||||
eRENDERDOC_Option_CaptureCallstacksOnlyDraws = 4,
|
||||
eRENDERDOC_Option_CaptureCallstacksOnlyActions = 4,
|
||||
|
||||
// Specify a delay in seconds to wait for a debugger to attach, after
|
||||
// creating or injecting into a process, before continuing to allow it to run.
|
||||
//
|
||||
// 0 indicates no delay, and the process will run immediately after injection
|
||||
//
|
||||
// Default - 0 seconds
|
||||
//
|
||||
eRENDERDOC_Option_DelayForDebugger = 5,
|
||||
|
||||
// Verify buffer access. This includes checking the memory returned by a Map() call to
|
||||
// detect any out-of-bounds modification, as well as initialising buffers with undefined contents
|
||||
// to a marker value to catch use of uninitialised memory.
|
||||
//
|
||||
// NOTE: This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do
|
||||
// not do the same kind of interception & checking and undefined contents are really undefined.
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Verify buffer access
|
||||
// 0 - No verification is performed, and overwriting bounds may cause crashes or corruption in
|
||||
// RenderDoc.
|
||||
eRENDERDOC_Option_VerifyBufferAccess = 6,
|
||||
|
||||
// The old name for eRENDERDOC_Option_VerifyBufferAccess was eRENDERDOC_Option_VerifyMapWrites.
|
||||
// This option now controls the filling of uninitialised buffers with 0xdddddddd which was
|
||||
// previously always enabled
|
||||
eRENDERDOC_Option_VerifyMapWrites = eRENDERDOC_Option_VerifyBufferAccess,
|
||||
|
||||
// Hooks any system API calls that create child processes, and injects
|
||||
// RenderDoc into them recursively with the same options.
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Hooks into spawned child processes
|
||||
// 0 - Child processes are not hooked by RenderDoc
|
||||
eRENDERDOC_Option_HookIntoChildren = 7,
|
||||
|
||||
// By default RenderDoc only includes resources in the final capture necessary
|
||||
// for that frame, this allows you to override that behaviour.
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - all live resources at the time of capture are included in the capture
|
||||
// and available for inspection
|
||||
// 0 - only the resources referenced by the captured frame are included
|
||||
eRENDERDOC_Option_RefAllResources = 8,
|
||||
|
||||
// **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or
|
||||
// getting it will be ignored, to allow compatibility with older versions.
|
||||
// In v1.1 the option acts as if it's always enabled.
|
||||
//
|
||||
// By default RenderDoc skips saving initial states for resources where the
|
||||
// previous contents don't appear to be used, assuming that writes before
|
||||
// reads indicate previous contents aren't used.
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - initial contents at the start of each captured frame are saved, even if
|
||||
// they are later overwritten or cleared before being used.
|
||||
// 0 - unless a read is detected, initial contents will not be saved and will
|
||||
// appear as black or empty data.
|
||||
eRENDERDOC_Option_SaveAllInitials = 9,
|
||||
|
||||
// In APIs that allow for the recording of command lists to be replayed later,
|
||||
// RenderDoc may choose to not capture command lists before a frame capture is
|
||||
// triggered, to reduce overheads. This means any command lists recorded once
|
||||
// and replayed many times will not be available and may cause a failure to
|
||||
// capture.
|
||||
//
|
||||
// NOTE: This is only true for APIs where multithreading is difficult or
|
||||
// discouraged. Newer APIs like Vulkan and D3D12 will ignore this option
|
||||
// and always capture all command lists since the API is heavily oriented
|
||||
// around it and the overheads have been reduced by API design.
|
||||
//
|
||||
// 1 - All command lists are captured from the start of the application
|
||||
// 0 - Command lists are only captured if their recording begins during
|
||||
// the period when a frame capture is in progress.
|
||||
eRENDERDOC_Option_CaptureAllCmdLists = 10,
|
||||
|
||||
// Mute API debugging output when the API validation mode option is enabled
|
||||
//
|
||||
// Default - enabled
|
||||
//
|
||||
// 1 - Mute any API debug messages from being displayed or passed through
|
||||
// 0 - API debugging is displayed as normal
|
||||
eRENDERDOC_Option_DebugOutputMute = 11,
|
||||
|
||||
// Option to allow vendor extensions to be used even when they may be
|
||||
// incompatible with RenderDoc and cause corrupted replays or crashes.
|
||||
//
|
||||
// Default - inactive
|
||||
//
|
||||
// No values are documented, this option should only be used when absolutely
|
||||
// necessary as directed by a RenderDoc developer.
|
||||
eRENDERDOC_Option_AllowUnsupportedVendorExtensions = 12,
|
||||
|
||||
// Define a soft memory limit which some APIs may aim to keep overhead under where
|
||||
// possible. Anything above this limit will where possible be saved directly to disk during
|
||||
// capture.
|
||||
// This will cause increased disk space use (which may cause a capture to fail if disk space is
|
||||
// exhausted) as well as slower capture times.
|
||||
//
|
||||
// Not all memory allocations may be deferred like this so it is not a guarantee of a memory
|
||||
// limit.
|
||||
//
|
||||
// Units are in MBs, suggested values would range from 200MB to 1000MB.
|
||||
//
|
||||
// Default - 0 Megabytes
|
||||
eRENDERDOC_Option_SoftMemoryLimit = 13,
|
||||
} RENDERDOC_CaptureOption;
|
||||
|
||||
// Sets an option that controls how RenderDoc behaves on capture.
|
||||
//
|
||||
// Returns 1 if the option and value are valid
|
||||
// Returns 0 if either is invalid and the option is unchanged
|
||||
typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionU32)(RENDERDOC_CaptureOption opt, uint32_t val);
|
||||
typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionF32)(RENDERDOC_CaptureOption opt, float val);
|
||||
|
||||
// Gets the current value of an option as a uint32_t
|
||||
//
|
||||
// If the option is invalid, 0xffffffff is returned
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionU32)(RENDERDOC_CaptureOption opt);
|
||||
|
||||
// Gets the current value of an option as a float
|
||||
//
|
||||
// If the option is invalid, -FLT_MAX is returned
|
||||
typedef float(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionF32)(RENDERDOC_CaptureOption opt);
|
||||
|
||||
typedef enum RENDERDOC_InputButton
|
||||
{
|
||||
// '0' - '9' matches ASCII values
|
||||
eRENDERDOC_Key_0 = 0x30,
|
||||
eRENDERDOC_Key_1 = 0x31,
|
||||
eRENDERDOC_Key_2 = 0x32,
|
||||
eRENDERDOC_Key_3 = 0x33,
|
||||
eRENDERDOC_Key_4 = 0x34,
|
||||
eRENDERDOC_Key_5 = 0x35,
|
||||
eRENDERDOC_Key_6 = 0x36,
|
||||
eRENDERDOC_Key_7 = 0x37,
|
||||
eRENDERDOC_Key_8 = 0x38,
|
||||
eRENDERDOC_Key_9 = 0x39,
|
||||
|
||||
// 'A' - 'Z' matches ASCII values
|
||||
eRENDERDOC_Key_A = 0x41,
|
||||
eRENDERDOC_Key_B = 0x42,
|
||||
eRENDERDOC_Key_C = 0x43,
|
||||
eRENDERDOC_Key_D = 0x44,
|
||||
eRENDERDOC_Key_E = 0x45,
|
||||
eRENDERDOC_Key_F = 0x46,
|
||||
eRENDERDOC_Key_G = 0x47,
|
||||
eRENDERDOC_Key_H = 0x48,
|
||||
eRENDERDOC_Key_I = 0x49,
|
||||
eRENDERDOC_Key_J = 0x4A,
|
||||
eRENDERDOC_Key_K = 0x4B,
|
||||
eRENDERDOC_Key_L = 0x4C,
|
||||
eRENDERDOC_Key_M = 0x4D,
|
||||
eRENDERDOC_Key_N = 0x4E,
|
||||
eRENDERDOC_Key_O = 0x4F,
|
||||
eRENDERDOC_Key_P = 0x50,
|
||||
eRENDERDOC_Key_Q = 0x51,
|
||||
eRENDERDOC_Key_R = 0x52,
|
||||
eRENDERDOC_Key_S = 0x53,
|
||||
eRENDERDOC_Key_T = 0x54,
|
||||
eRENDERDOC_Key_U = 0x55,
|
||||
eRENDERDOC_Key_V = 0x56,
|
||||
eRENDERDOC_Key_W = 0x57,
|
||||
eRENDERDOC_Key_X = 0x58,
|
||||
eRENDERDOC_Key_Y = 0x59,
|
||||
eRENDERDOC_Key_Z = 0x5A,
|
||||
|
||||
// leave the rest of the ASCII range free
|
||||
// in case we want to use it later
|
||||
eRENDERDOC_Key_NonPrintable = 0x100,
|
||||
|
||||
eRENDERDOC_Key_Divide,
|
||||
eRENDERDOC_Key_Multiply,
|
||||
eRENDERDOC_Key_Subtract,
|
||||
eRENDERDOC_Key_Plus,
|
||||
|
||||
eRENDERDOC_Key_F1,
|
||||
eRENDERDOC_Key_F2,
|
||||
eRENDERDOC_Key_F3,
|
||||
eRENDERDOC_Key_F4,
|
||||
eRENDERDOC_Key_F5,
|
||||
eRENDERDOC_Key_F6,
|
||||
eRENDERDOC_Key_F7,
|
||||
eRENDERDOC_Key_F8,
|
||||
eRENDERDOC_Key_F9,
|
||||
eRENDERDOC_Key_F10,
|
||||
eRENDERDOC_Key_F11,
|
||||
eRENDERDOC_Key_F12,
|
||||
|
||||
eRENDERDOC_Key_Home,
|
||||
eRENDERDOC_Key_End,
|
||||
eRENDERDOC_Key_Insert,
|
||||
eRENDERDOC_Key_Delete,
|
||||
eRENDERDOC_Key_PageUp,
|
||||
eRENDERDOC_Key_PageDn,
|
||||
|
||||
eRENDERDOC_Key_Backspace,
|
||||
eRENDERDOC_Key_Tab,
|
||||
eRENDERDOC_Key_PrtScrn,
|
||||
eRENDERDOC_Key_Pause,
|
||||
|
||||
eRENDERDOC_Key_Max,
|
||||
} RENDERDOC_InputButton;
|
||||
|
||||
// Sets which key or keys can be used to toggle focus between multiple windows
|
||||
//
|
||||
// If keys is NULL or num is 0, toggle keys will be disabled
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetFocusToggleKeys)(RENDERDOC_InputButton *keys, int num);
|
||||
|
||||
// Sets which key or keys can be used to capture the next frame
|
||||
//
|
||||
// If keys is NULL or num is 0, captures keys will be disabled
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureKeys)(RENDERDOC_InputButton *keys, int num);
|
||||
|
||||
typedef enum RENDERDOC_OverlayBits
|
||||
{
|
||||
// This single bit controls whether the overlay is enabled or disabled globally
|
||||
eRENDERDOC_Overlay_Enabled = 0x1,
|
||||
|
||||
// Show the average framerate over several seconds as well as min/max
|
||||
eRENDERDOC_Overlay_FrameRate = 0x2,
|
||||
|
||||
// Show the current frame number
|
||||
eRENDERDOC_Overlay_FrameNumber = 0x4,
|
||||
|
||||
// Show a list of recent captures, and how many captures have been made
|
||||
eRENDERDOC_Overlay_CaptureList = 0x8,
|
||||
|
||||
// Default values for the overlay mask
|
||||
eRENDERDOC_Overlay_Default = (eRENDERDOC_Overlay_Enabled | eRENDERDOC_Overlay_FrameRate |
|
||||
eRENDERDOC_Overlay_FrameNumber | eRENDERDOC_Overlay_CaptureList),
|
||||
|
||||
// Enable all bits
|
||||
eRENDERDOC_Overlay_All = 0x7ffffff,
|
||||
|
||||
// Disable all bits
|
||||
eRENDERDOC_Overlay_None = 0,
|
||||
} RENDERDOC_OverlayBits;
|
||||
|
||||
// returns the overlay bits that have been set
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetOverlayBits)(void);
|
||||
// sets the overlay bits with an and & or mask
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_MaskOverlayBits)(uint32_t And, uint32_t Or);
|
||||
|
||||
// this function will attempt to remove RenderDoc's hooks in the application.
|
||||
//
|
||||
// Note: that this can only work correctly if done immediately after
|
||||
// the module is loaded, before any API work happens. RenderDoc will remove its
|
||||
// injected hooks and shut down. Behaviour is undefined if this is called
|
||||
// after any API functions have been called, and there is still no guarantee of
|
||||
// success.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_RemoveHooks)(void);
|
||||
|
||||
// DEPRECATED: compatibility for code compiled against pre-1.4.1 headers.
|
||||
typedef pRENDERDOC_RemoveHooks pRENDERDOC_Shutdown;
|
||||
|
||||
// This function will unload RenderDoc's crash handler.
|
||||
//
|
||||
// If you use your own crash handler and don't want RenderDoc's handler to
|
||||
// intercede, you can call this function to unload it and any unhandled
|
||||
// exceptions will pass to the next handler.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_UnloadCrashHandler)(void);
|
||||
|
||||
// Sets the capture file path template
|
||||
//
|
||||
// pathtemplate is a UTF-8 string that gives a template for how captures will be named
|
||||
// and where they will be saved.
|
||||
//
|
||||
// Any extension is stripped off the path, and captures are saved in the directory
|
||||
// specified, and named with the filename and the frame number appended. If the
|
||||
// directory does not exist it will be created, including any parent directories.
|
||||
//
|
||||
// If pathtemplate is NULL, the template will remain unchanged
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// SetCaptureFilePathTemplate("my_captures/example");
|
||||
//
|
||||
// Capture #1 -> my_captures/example_frame123.rdc
|
||||
// Capture #2 -> my_captures/example_frame456.rdc
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFilePathTemplate)(const char *pathtemplate);
|
||||
|
||||
// returns the current capture path template, see SetCaptureFileTemplate above, as a UTF-8 string
|
||||
typedef const char *(RENDERDOC_CC *pRENDERDOC_GetCaptureFilePathTemplate)(void);
|
||||
|
||||
// DEPRECATED: compatibility for code compiled against pre-1.1.2 headers.
|
||||
typedef pRENDERDOC_SetCaptureFilePathTemplate pRENDERDOC_SetLogFilePathTemplate;
|
||||
typedef pRENDERDOC_GetCaptureFilePathTemplate pRENDERDOC_GetLogFilePathTemplate;
|
||||
|
||||
// returns the number of captures that have been made
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetNumCaptures)(void);
|
||||
|
||||
// This function returns the details of a capture, by index. New captures are added
|
||||
// to the end of the list.
|
||||
//
|
||||
// filename will be filled with the absolute path to the capture file, as a UTF-8 string
|
||||
// pathlength will be written with the length in bytes of the filename string
|
||||
// timestamp will be written with the time of the capture, in seconds since the Unix epoch
|
||||
//
|
||||
// Any of the parameters can be NULL and they'll be skipped.
|
||||
//
|
||||
// The function will return 1 if the capture index is valid, or 0 if the index is invalid
|
||||
// If the index is invalid, the values will be unchanged
|
||||
//
|
||||
// Note: when captures are deleted in the UI they will remain in this list, so the
|
||||
// capture path may not exist anymore.
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCapture)(uint32_t idx, char *filename,
|
||||
uint32_t *pathlength, uint64_t *timestamp);
|
||||
|
||||
// Sets the comments associated with a capture file. These comments are displayed in the
|
||||
// UI program when opening.
|
||||
//
|
||||
// filePath should be a path to the capture file to add comments to. If set to NULL or ""
|
||||
// the most recent capture file created made will be used instead.
|
||||
// comments should be a NULL-terminated UTF-8 string to add as comments.
|
||||
//
|
||||
// Any existing comments will be overwritten.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFileComments)(const char *filePath,
|
||||
const char *comments);
|
||||
|
||||
// returns 1 if the RenderDoc UI is connected to this application, 0 otherwise
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsTargetControlConnected)(void);
|
||||
|
||||
// DEPRECATED: compatibility for code compiled against pre-1.1.1 headers.
|
||||
// This was renamed to IsTargetControlConnected in API 1.1.1, the old typedef is kept here for
|
||||
// backwards compatibility with old code, it is castable either way since it's ABI compatible
|
||||
// as the same function pointer type.
|
||||
typedef pRENDERDOC_IsTargetControlConnected pRENDERDOC_IsRemoteAccessConnected;
|
||||
|
||||
// This function will launch the Replay UI associated with the RenderDoc library injected
|
||||
// into the running application.
|
||||
//
|
||||
// if connectTargetControl is 1, the Replay UI will be launched with a command line parameter
|
||||
// to connect to this application
|
||||
// cmdline is the rest of the command line, as a UTF-8 string. E.g. a captures to open
|
||||
// if cmdline is NULL, the command line will be empty.
|
||||
//
|
||||
// returns the PID of the replay UI if successful, 0 if not successful.
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_LaunchReplayUI)(uint32_t connectTargetControl,
|
||||
const char *cmdline);
|
||||
|
||||
// RenderDoc can return a higher version than requested if it's backwards compatible,
|
||||
// this function returns the actual version returned. If a parameter is NULL, it will be
|
||||
// ignored and the others will be filled out.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_GetAPIVersion)(int *major, int *minor, int *patch);
|
||||
|
||||
// Requests that the replay UI show itself (if hidden or not the current top window). This can be
|
||||
// used in conjunction with IsTargetControlConnected and LaunchReplayUI to intelligently handle
|
||||
// showing the UI after making a capture.
|
||||
//
|
||||
// This will return 1 if the request was successfully passed on, though it's not guaranteed that
|
||||
// the UI will be on top in all cases depending on OS rules. It will return 0 if there is no current
|
||||
// target control connection to make such a request, or if there was another error
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_ShowReplayUI)(void);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Capturing functions
|
||||
//
|
||||
|
||||
// A device pointer is a pointer to the API's root handle.
|
||||
//
|
||||
// This would be an ID3D11Device, HGLRC/GLXContext, ID3D12Device, etc
|
||||
typedef void *RENDERDOC_DevicePointer;
|
||||
|
||||
// A window handle is the OS's native window handle
|
||||
//
|
||||
// This would be an HWND, GLXDrawable, etc
|
||||
typedef void *RENDERDOC_WindowHandle;
|
||||
|
||||
// A helper macro for Vulkan, where the device handle cannot be used directly.
|
||||
//
|
||||
// Passing the VkInstance to this macro will return the RENDERDOC_DevicePointer to use.
|
||||
//
|
||||
// Specifically, the value needed is the dispatch table pointer, which sits as the first
|
||||
// pointer-sized object in the memory pointed to by the VkInstance. Thus we cast to a void** and
|
||||
// indirect once.
|
||||
#define RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(inst) (*((void **)(inst)))
|
||||
|
||||
// This sets the RenderDoc in-app overlay in the API/window pair as 'active' and it will
|
||||
// respond to keypresses. Neither parameter can be NULL
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetActiveWindow)(RENDERDOC_DevicePointer device,
|
||||
RENDERDOC_WindowHandle wndHandle);
|
||||
|
||||
// capture the next frame on whichever window and API is currently considered active
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_TriggerCapture)(void);
|
||||
|
||||
// capture the next N frames on whichever window and API is currently considered active
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_TriggerMultiFrameCapture)(uint32_t numFrames);
|
||||
|
||||
// When choosing either a device pointer or a window handle to capture, you can pass NULL.
|
||||
// Passing NULL specifies a 'wildcard' match against anything. This allows you to specify
|
||||
// any API rendering to a specific window, or a specific API instance rendering to any window,
|
||||
// or in the simplest case of one window and one API, you can just pass NULL for both.
|
||||
//
|
||||
// In either case, if there are two or more possible matching (device,window) pairs it
|
||||
// is undefined which one will be captured.
|
||||
//
|
||||
// Note: for headless rendering you can pass NULL for the window handle and either specify
|
||||
// a device pointer or leave it NULL as above.
|
||||
|
||||
// Immediately starts capturing API calls on the specified device pointer and window handle.
|
||||
//
|
||||
// If there is no matching thing to capture (e.g. no supported API has been initialised),
|
||||
// this will do nothing.
|
||||
//
|
||||
// The results are undefined (including crashes) if two captures are started overlapping,
|
||||
// even on separate devices and/oror windows.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_StartFrameCapture)(RENDERDOC_DevicePointer device,
|
||||
RENDERDOC_WindowHandle wndHandle);
|
||||
|
||||
// Returns whether or not a frame capture is currently ongoing anywhere.
|
||||
//
|
||||
// This will return 1 if a capture is ongoing, and 0 if there is no capture running
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsFrameCapturing)(void);
|
||||
|
||||
// Ends capturing immediately.
|
||||
//
|
||||
// This will return 1 if the capture succeeded, and 0 if there was an error capturing.
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_EndFrameCapture)(RENDERDOC_DevicePointer device,
|
||||
RENDERDOC_WindowHandle wndHandle);
|
||||
|
||||
// Ends capturing immediately and discard any data stored without saving to disk.
|
||||
//
|
||||
// This will return 1 if the capture was discarded, and 0 if there was an error or no capture
|
||||
// was in progress
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_DiscardFrameCapture)(RENDERDOC_DevicePointer device,
|
||||
RENDERDOC_WindowHandle wndHandle);
|
||||
|
||||
// Only valid to be called between a call to StartFrameCapture and EndFrameCapture. Gives a custom
|
||||
// title to the capture produced which will be displayed in the UI.
|
||||
//
|
||||
// If multiple captures are ongoing, this title will be applied to the first capture to end after
|
||||
// this call. The second capture to end will have no title, unless this function is called again.
|
||||
//
|
||||
// Calling this function has no effect if no capture is currently running, and if it is called
|
||||
// multiple times only the last title will be used.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureTitle)(const char *title);
|
||||
|
||||
// Annotations API:
|
||||
//
|
||||
// These functions allow you to specify annotations either on a per-command level, or a per-object
|
||||
// level.
|
||||
//
|
||||
// Basic types of annotations are supported, as well as vector versions and references to API objects.
|
||||
//
|
||||
// The annotations are stored as keys, with the key being a dot-separated path allowing arbitrary
|
||||
// nesting and user organisation. The keys are sorted in human order so `foo.2.bar` will be displayed
|
||||
// before `foo.10.bar` to allow creation of arrays if desired.
|
||||
//
|
||||
// Deleting an annotation can be done by assigning an empty value to it.
|
||||
|
||||
// the type of an annotation value, or Empty to delete an annotation
|
||||
typedef enum RENDERDOC_AnnotationType
|
||||
{
|
||||
eRENDERDOC_Empty,
|
||||
eRENDERDOC_Bool,
|
||||
eRENDERDOC_Int32,
|
||||
eRENDERDOC_UInt32,
|
||||
eRENDERDOC_Int64,
|
||||
eRENDERDOC_UInt64,
|
||||
eRENDERDOC_Float,
|
||||
eRENDERDOC_Double,
|
||||
eRENDERDOC_String,
|
||||
eRENDERDOC_APIObject,
|
||||
eRENDERDOC_AnnotationMax = 0x7FFFFFFF,
|
||||
} RENDERDOC_AnnotationType;
|
||||
|
||||
// a union with vector annotation value data
|
||||
typedef union RENDERDOC_AnnotationVectorValue
|
||||
{
|
||||
bool boolean[4];
|
||||
int32_t int32[4];
|
||||
int64_t int64[4];
|
||||
uint32_t uint32[4];
|
||||
uint64_t uint64[4];
|
||||
float float32[4];
|
||||
double float64[4];
|
||||
} RENDERDOC_AnnotationVectorValue;
|
||||
|
||||
// a union with scalar annotation value data
|
||||
typedef union RENDERDOC_AnnotationValue
|
||||
{
|
||||
bool boolean;
|
||||
int32_t int32;
|
||||
int64_t int64;
|
||||
uint32_t uint32;
|
||||
uint64_t uint64;
|
||||
float float32;
|
||||
double float64;
|
||||
|
||||
RENDERDOC_AnnotationVectorValue vector;
|
||||
|
||||
const char *string;
|
||||
void *apiObject;
|
||||
} RENDERDOC_AnnotationValue;
|
||||
|
||||
// a struct for specifying a GL object, as we don't have pointers we can use so instead we specify a
|
||||
// pointer to this struct giving both the type and the name
|
||||
typedef struct RENDERDOC_GLResourceReference
|
||||
{
|
||||
// this is the same GLenum identifier as passed to glObjectLabel
|
||||
uint32_t identifier;
|
||||
uint32_t name;
|
||||
} GLResourceReference;
|
||||
|
||||
// simple C++ helpers to avoid the need for a temporary objects for value passing and GL object specification
|
||||
#ifdef __cplusplus
|
||||
struct RDGLObjectHelper
|
||||
{
|
||||
RENDERDOC_GLResourceReference gl;
|
||||
|
||||
RDGLObjectHelper(uint32_t identifier, uint32_t name)
|
||||
{
|
||||
gl.identifier = identifier;
|
||||
gl.name = name;
|
||||
}
|
||||
|
||||
operator RENDERDOC_GLResourceReference *() { return ≷ }
|
||||
};
|
||||
|
||||
struct RDAnnotationHelper
|
||||
{
|
||||
RENDERDOC_AnnotationValue val;
|
||||
|
||||
RDAnnotationHelper(bool b) { val.boolean = b; }
|
||||
RDAnnotationHelper(int32_t i) { val.int32 = i; }
|
||||
RDAnnotationHelper(int64_t i) { val.int64 = i; }
|
||||
RDAnnotationHelper(uint32_t i) { val.uint32 = i; }
|
||||
RDAnnotationHelper(uint64_t i) { val.uint64 = i; }
|
||||
RDAnnotationHelper(float f) { val.float32 = f; }
|
||||
RDAnnotationHelper(double d) { val.float64 = d; }
|
||||
RDAnnotationHelper(const char *s) { val.string = s; }
|
||||
|
||||
operator RENDERDOC_AnnotationValue *() { return &val; }
|
||||
};
|
||||
#endif
|
||||
|
||||
// The device is specified in the same way as other API calls that take a RENDERDOC_DevicePointer
|
||||
// to specify the device.
|
||||
//
|
||||
// The object or queue/commandbuffer will depend on the graphics API in question.
|
||||
//
|
||||
// Return value:
|
||||
// 0 - The annotation was applied successfully.
|
||||
// 1 - The device is unknown/invalid
|
||||
// 2 - The device is valid but the annotation is not supported for API-specific reasons, such as an
|
||||
// unrecognised or invalid object or queue/commandbuffer
|
||||
// 3 - The call is ill-formed or invalid e.g. empty is specified with a value pointer, or non-empty
|
||||
// is specified with a NULL value pointer
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_SetObjectAnnotation)(RENDERDOC_DevicePointer device,
|
||||
void *object, const char *key,
|
||||
RENDERDOC_AnnotationType valueType,
|
||||
uint32_t valueVectorWidth,
|
||||
const RENDERDOC_AnnotationValue *value);
|
||||
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_SetCommandAnnotation)(
|
||||
RENDERDOC_DevicePointer device, void *queueOrCommandBuffer, const char *key,
|
||||
RENDERDOC_AnnotationType valueType, uint32_t valueVectorWidth,
|
||||
const RENDERDOC_AnnotationValue *value);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// RenderDoc API versions
|
||||
//
|
||||
|
||||
// RenderDoc uses semantic versioning (http://semver.org/).
|
||||
//
|
||||
// MAJOR version is incremented when incompatible API changes happen.
|
||||
// MINOR version is incremented when functionality is added in a backwards-compatible manner.
|
||||
// PATCH version is incremented when backwards-compatible bug fixes happen.
|
||||
//
|
||||
// Note that this means the API returned can be higher than the one you might have requested.
|
||||
// e.g. if you are running against a newer RenderDoc that supports 1.0.1, it will be returned
|
||||
// instead of 1.0.0. You can check this with the GetAPIVersion entry point
|
||||
typedef enum RENDERDOC_Version
|
||||
{
|
||||
eRENDERDOC_API_Version_1_0_0 = 10000, // RENDERDOC_API_1_0_0 = 1 00 00
|
||||
eRENDERDOC_API_Version_1_0_1 = 10001, // RENDERDOC_API_1_0_1 = 1 00 01
|
||||
eRENDERDOC_API_Version_1_0_2 = 10002, // RENDERDOC_API_1_0_2 = 1 00 02
|
||||
eRENDERDOC_API_Version_1_1_0 = 10100, // RENDERDOC_API_1_1_0 = 1 01 00
|
||||
eRENDERDOC_API_Version_1_1_1 = 10101, // RENDERDOC_API_1_1_1 = 1 01 01
|
||||
eRENDERDOC_API_Version_1_1_2 = 10102, // RENDERDOC_API_1_1_2 = 1 01 02
|
||||
eRENDERDOC_API_Version_1_2_0 = 10200, // RENDERDOC_API_1_2_0 = 1 02 00
|
||||
eRENDERDOC_API_Version_1_3_0 = 10300, // RENDERDOC_API_1_3_0 = 1 03 00
|
||||
eRENDERDOC_API_Version_1_4_0 = 10400, // RENDERDOC_API_1_4_0 = 1 04 00
|
||||
eRENDERDOC_API_Version_1_4_1 = 10401, // RENDERDOC_API_1_4_1 = 1 04 01
|
||||
eRENDERDOC_API_Version_1_4_2 = 10402, // RENDERDOC_API_1_4_2 = 1 04 02
|
||||
eRENDERDOC_API_Version_1_5_0 = 10500, // RENDERDOC_API_1_5_0 = 1 05 00
|
||||
eRENDERDOC_API_Version_1_6_0 = 10600, // RENDERDOC_API_1_6_0 = 1 06 00
|
||||
eRENDERDOC_API_Version_1_7_0 = 10700, // RENDERDOC_API_1_7_0 = 1 07 00
|
||||
} RENDERDOC_Version;
|
||||
|
||||
// API version changelog:
|
||||
//
|
||||
// 1.0.0 - initial release
|
||||
// 1.0.1 - Bugfix: IsFrameCapturing() was returning false for captures that were triggered
|
||||
// by keypress or TriggerCapture, instead of Start/EndFrameCapture.
|
||||
// 1.0.2 - Refactor: Renamed eRENDERDOC_Option_DebugDeviceMode to eRENDERDOC_Option_APIValidation
|
||||
// 1.1.0 - Add feature: TriggerMultiFrameCapture(). Backwards compatible with 1.0.x since the new
|
||||
// function pointer is added to the end of the struct, the original layout is identical
|
||||
// 1.1.1 - Refactor: Renamed remote access to target control (to better disambiguate from remote
|
||||
// replay/remote server concept in replay UI)
|
||||
// 1.1.2 - Refactor: Renamed "log file" in function names to just capture, to clarify that these
|
||||
// are captures and not debug logging files. This is the first API version in the v1.0
|
||||
// branch.
|
||||
// 1.2.0 - Added feature: SetCaptureFileComments() to add comments to a capture file that will be
|
||||
// displayed in the UI program on load.
|
||||
// 1.3.0 - Added feature: New capture option eRENDERDOC_Option_AllowUnsupportedVendorExtensions
|
||||
// which allows users to opt-in to allowing unsupported vendor extensions to function.
|
||||
// Should be used at the user's own risk.
|
||||
// Refactor: Renamed eRENDERDOC_Option_VerifyMapWrites to
|
||||
// eRENDERDOC_Option_VerifyBufferAccess, which now also controls initialisation to
|
||||
// 0xdddddddd of uninitialised buffer contents.
|
||||
// 1.4.0 - Added feature: DiscardFrameCapture() to discard a frame capture in progress and stop
|
||||
// capturing without saving anything to disk.
|
||||
// 1.4.1 - Refactor: Renamed Shutdown to RemoveHooks to better clarify what is happening
|
||||
// 1.4.2 - Refactor: Renamed 'draws' to 'actions' in callstack capture option.
|
||||
// 1.5.0 - Added feature: ShowReplayUI() to request that the replay UI show itself if connected
|
||||
// 1.6.0 - Added feature: SetCaptureTitle() which can be used to set a title for a
|
||||
// capture made with StartFrameCapture() or EndFrameCapture()
|
||||
// 1.7.0 - Added feature: SetObjectAnnotation() / SetCommandAnnotation() for adding rich
|
||||
// annotations to objects and command streams
|
||||
|
||||
typedef struct RENDERDOC_API_1_7_0
|
||||
{
|
||||
pRENDERDOC_GetAPIVersion GetAPIVersion;
|
||||
|
||||
pRENDERDOC_SetCaptureOptionU32 SetCaptureOptionU32;
|
||||
pRENDERDOC_SetCaptureOptionF32 SetCaptureOptionF32;
|
||||
|
||||
pRENDERDOC_GetCaptureOptionU32 GetCaptureOptionU32;
|
||||
pRENDERDOC_GetCaptureOptionF32 GetCaptureOptionF32;
|
||||
|
||||
pRENDERDOC_SetFocusToggleKeys SetFocusToggleKeys;
|
||||
pRENDERDOC_SetCaptureKeys SetCaptureKeys;
|
||||
|
||||
pRENDERDOC_GetOverlayBits GetOverlayBits;
|
||||
pRENDERDOC_MaskOverlayBits MaskOverlayBits;
|
||||
|
||||
// Shutdown was renamed to RemoveHooks in 1.4.1.
|
||||
// These unions allow old code to continue compiling without changes
|
||||
union
|
||||
{
|
||||
pRENDERDOC_Shutdown Shutdown;
|
||||
pRENDERDOC_RemoveHooks RemoveHooks;
|
||||
};
|
||||
pRENDERDOC_UnloadCrashHandler UnloadCrashHandler;
|
||||
|
||||
// Get/SetLogFilePathTemplate was renamed to Get/SetCaptureFilePathTemplate in 1.1.2.
|
||||
// These unions allow old code to continue compiling without changes
|
||||
union
|
||||
{
|
||||
// deprecated name
|
||||
pRENDERDOC_SetLogFilePathTemplate SetLogFilePathTemplate;
|
||||
// current name
|
||||
pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate;
|
||||
};
|
||||
union
|
||||
{
|
||||
// deprecated name
|
||||
pRENDERDOC_GetLogFilePathTemplate GetLogFilePathTemplate;
|
||||
// current name
|
||||
pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate;
|
||||
};
|
||||
|
||||
pRENDERDOC_GetNumCaptures GetNumCaptures;
|
||||
pRENDERDOC_GetCapture GetCapture;
|
||||
|
||||
pRENDERDOC_TriggerCapture TriggerCapture;
|
||||
|
||||
// IsRemoteAccessConnected was renamed to IsTargetControlConnected in 1.1.1.
|
||||
// This union allows old code to continue compiling without changes
|
||||
union
|
||||
{
|
||||
// deprecated name
|
||||
pRENDERDOC_IsRemoteAccessConnected IsRemoteAccessConnected;
|
||||
// current name
|
||||
pRENDERDOC_IsTargetControlConnected IsTargetControlConnected;
|
||||
};
|
||||
pRENDERDOC_LaunchReplayUI LaunchReplayUI;
|
||||
|
||||
pRENDERDOC_SetActiveWindow SetActiveWindow;
|
||||
|
||||
pRENDERDOC_StartFrameCapture StartFrameCapture;
|
||||
pRENDERDOC_IsFrameCapturing IsFrameCapturing;
|
||||
pRENDERDOC_EndFrameCapture EndFrameCapture;
|
||||
|
||||
// new function in 1.1.0
|
||||
pRENDERDOC_TriggerMultiFrameCapture TriggerMultiFrameCapture;
|
||||
|
||||
// new function in 1.2.0
|
||||
pRENDERDOC_SetCaptureFileComments SetCaptureFileComments;
|
||||
|
||||
// new function in 1.4.0
|
||||
pRENDERDOC_DiscardFrameCapture DiscardFrameCapture;
|
||||
|
||||
// new function in 1.5.0
|
||||
pRENDERDOC_ShowReplayUI ShowReplayUI;
|
||||
|
||||
// new function in 1.6.0
|
||||
pRENDERDOC_SetCaptureTitle SetCaptureTitle;
|
||||
|
||||
// new functions in 1.7.0
|
||||
pRENDERDOC_SetObjectAnnotation SetObjectAnnotation;
|
||||
pRENDERDOC_SetCommandAnnotation SetCommandAnnotation;
|
||||
} RENDERDOC_API_1_7_0;
|
||||
|
||||
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_0_0;
|
||||
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_0_1;
|
||||
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_0_2;
|
||||
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_1_0;
|
||||
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_1_1;
|
||||
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_1_2;
|
||||
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_2_0;
|
||||
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_3_0;
|
||||
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_4_0;
|
||||
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_4_1;
|
||||
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_4_2;
|
||||
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_5_0;
|
||||
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_6_0;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// RenderDoc API entry point
|
||||
//
|
||||
// This entry point can be obtained via GetProcAddress/dlsym if RenderDoc is available.
|
||||
//
|
||||
// The name is the same as the typedef - "RENDERDOC_GetAPI"
|
||||
//
|
||||
// This function is not thread safe, and should not be called on multiple threads at once.
|
||||
// Ideally, call this once as early as possible in your application's startup, before doing
|
||||
// any API work, since some configuration functionality etc has to be done also before
|
||||
// initialising any APIs.
|
||||
//
|
||||
// Parameters:
|
||||
// version is a single value from the RENDERDOC_Version above.
|
||||
//
|
||||
// outAPIPointers will be filled out with a pointer to the corresponding struct of function
|
||||
// pointers.
|
||||
//
|
||||
// Returns:
|
||||
// 1 - if the outAPIPointers has been filled with a pointer to the API struct requested
|
||||
// 0 - if the requested version is not supported or the arguments are invalid.
|
||||
//
|
||||
typedef int(RENDERDOC_CC *pRENDERDOC_GetAPI)(RENDERDOC_Version version, void **outAPIPointers);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
@@ -1,846 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.19...4.3)
|
||||
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.0)
|
||||
set(CMAKE_POLICY_VERSION_MINIMUM 3.10 CACHE STRING "Minimum policy version for bundled dependencies")
|
||||
endif()
|
||||
|
||||
get_property(KYTY_GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
||||
if(NOT KYTY_GENERATOR_IS_MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release RelWithDebInfo MinSizeRel)
|
||||
endif()
|
||||
|
||||
project(Kyty)
|
||||
|
||||
find_program(SCCACHE_PROGRAM sccache)
|
||||
find_program(CCACHE_PROGRAM ccache)
|
||||
|
||||
foreach(lang IN ITEMS C CXX)
|
||||
# An explicitly empty launcher disables caching for this language.
|
||||
if(NOT DEFINED CMAKE_${lang}_COMPILER_LAUNCHER)
|
||||
if(SCCACHE_PROGRAM)
|
||||
set(CMAKE_${lang}_COMPILER_LAUNCHER "${SCCACHE_PROGRAM}")
|
||||
message(STATUS "sccache enabled for ${lang}: ${SCCACHE_PROGRAM}")
|
||||
elseif(CCACHE_PROGRAM)
|
||||
set(CMAKE_${lang}_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
|
||||
message(STATUS "ccache enabled for ${lang}: ${CCACHE_PROGRAM}")
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(KYTY_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
set(KYTY_TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tests")
|
||||
set(KYTY_THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/3rdparty")
|
||||
|
||||
if(CMAKE_SYSTEM_NAME MATCHES ".*Linux")
|
||||
set(LINUX TRUE)
|
||||
endif()
|
||||
|
||||
if (NOT (WIN32 OR LINUX OR APPLE))
|
||||
message(FATAL_ERROR "only Windows, Linux, and macOS builds are supported")
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
# Prevent Windows headers from defining min/max macros in all targets.
|
||||
add_compile_definitions(NOMINMAX)
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
|
||||
|
||||
include("${KYTY_SOURCE_DIR}/utils.cmake")
|
||||
include(CTest)
|
||||
|
||||
option(KYTY_ENABLE_CLANG_TIDY "Run clang-tidy checks during builds" OFF)
|
||||
|
||||
set(KYTY_BUILD_ORIGIN "Source" CACHE STRING "Build provenance shown in diagnostics")
|
||||
set_property(CACHE KYTY_BUILD_ORIGIN PROPERTY STRINGS Source Official Fork)
|
||||
if(NOT KYTY_BUILD_ORIGIN STREQUAL "Source" AND NOT KYTY_BUILD_ORIGIN STREQUAL "Official" AND
|
||||
NOT KYTY_BUILD_ORIGIN STREQUAL "Fork")
|
||||
message(FATAL_ERROR "KYTY_BUILD_ORIGIN must be Source, Official, or Fork")
|
||||
endif()
|
||||
|
||||
set(KYTY_RELEASE_TAG "" CACHE STRING "Release tag embedded in official builds")
|
||||
set(KYTY_BUILD_REPOSITORY "" CACHE STRING "GitHub repository embedded in fork builds")
|
||||
set(KYTY_OFFICIAL_BUILD OFF)
|
||||
set(KYTY_FORK_BUILD OFF)
|
||||
if(KYTY_BUILD_ORIGIN STREQUAL "Official")
|
||||
if(KYTY_RELEASE_TAG STREQUAL "")
|
||||
message(FATAL_ERROR "Official builds require KYTY_RELEASE_TAG")
|
||||
endif()
|
||||
set(KYTY_OFFICIAL_BUILD ON)
|
||||
elseif(KYTY_BUILD_ORIGIN STREQUAL "Fork")
|
||||
if(KYTY_BUILD_REPOSITORY STREQUAL "")
|
||||
message(FATAL_ERROR "Fork builds require KYTY_BUILD_REPOSITORY")
|
||||
endif()
|
||||
set(KYTY_FORK_BUILD ON)
|
||||
endif()
|
||||
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
|
||||
include(TestBigEndian)
|
||||
|
||||
if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
message(FATAL_ERROR "only 64-bit builds are supported")
|
||||
endif()
|
||||
|
||||
test_big_endian(BIG_ENDIAN)
|
||||
if(BIG_ENDIAN)
|
||||
set(KYTY_ENDIAN KYTY_ENDIAN_BIG)
|
||||
else()
|
||||
set(KYTY_ENDIAN KYTY_ENDIAN_LITTLE)
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
set(KYTY_BUILD KYTY_BUILD_DEBUG)
|
||||
else()
|
||||
set(KYTY_BUILD KYTY_BUILD_RELEASE)
|
||||
endif()
|
||||
|
||||
if(LINUX OR APPLE)
|
||||
# macOS rides the POSIX/Linux code paths until it gets a dedicated platform
|
||||
set(KYTY_PLATFORM KYTY_PLATFORM_LINUX)
|
||||
else()
|
||||
set(KYTY_PLATFORM KYTY_PLATFORM_WINDOWS)
|
||||
endif()
|
||||
|
||||
set(CLANG FALSE)
|
||||
set(GCC FALSE)
|
||||
set(KYTY_CLANG_CL FALSE)
|
||||
get_filename_component(KYTY_CXX_COMPILER_NAME "${CMAKE_CXX_COMPILER}" NAME_WE)
|
||||
string(TOLOWER "${KYTY_CXX_COMPILER_NAME}" KYTY_CXX_COMPILER_NAME)
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "^(Apple)?Clang$")
|
||||
set(CLANG TRUE)
|
||||
set(KYTY_COMPILER CLANG)
|
||||
if(WIN32 AND KYTY_CXX_COMPILER_NAME STREQUAL "clang-cl")
|
||||
set(KYTY_CLANG_CL TRUE)
|
||||
set(KYTY_LINKER LLD_LINK)
|
||||
elseif(APPLE)
|
||||
set(KYTY_LINKER LD64) # Apple's default linker; lld flags don't apply
|
||||
else()
|
||||
set(KYTY_LINKER LLD)
|
||||
endif()
|
||||
if ((CMAKE_AR MATCHES "llvm") AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0.0) AND (CMAKE_CXX_COMPILER MATCHES "winlibs"))
|
||||
string(REPLACE "llvm-ar.exe" "ar.exe" CMAKE_AR ${CMAKE_AR}) # llvm-ar.exe 11.0.0 from winlibs is broken
|
||||
endif()
|
||||
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
set(GCC TRUE)
|
||||
set(KYTY_COMPILER GCC)
|
||||
set(KYTY_LINKER LD)
|
||||
else()
|
||||
message(FATAL_ERROR "only Clang and GCC are supported; found ${CMAKE_CXX_COMPILER_ID}")
|
||||
endif()
|
||||
|
||||
set(KYTY_PROJECT_NAME "Emulator" CACHE STRING "Project name suffix")
|
||||
|
||||
string(TOLOWER ${KYTY_COMPILER} KYTY_COMPILER_ID)
|
||||
string(TOLOWER ${KYTY_LINKER} KYTY_LINKER_ID)
|
||||
|
||||
if (CLANG AND (KYTY_LINKER STREQUAL LLD))
|
||||
set(KYTY_LD_OPTIONS "-fuse-ld=lld")
|
||||
endif()
|
||||
|
||||
if (KYTY_LINKER STREQUAL LD AND NOT LINUX)
|
||||
set(KYTY_LD_OPTIONS "-Wl,--image-base=0x100000000000")
|
||||
endif()
|
||||
|
||||
project(Kyty${KYTY_PROJECT_NAME}${CMAKE_BUILD_TYPE}${KYTY_COMPILER} VERSION 0.3.0)
|
||||
|
||||
include_directories(
|
||||
${KYTY_THIRD_PARTY_DIR}/gtest/include
|
||||
${KYTY_THIRD_PARTY_DIR}/gtest
|
||||
${KYTY_THIRD_PARTY_DIR}/fmt/include
|
||||
${KYTY_THIRD_PARTY_DIR}/magic_enum/include/magic_enum
|
||||
${PROJECT_BINARY_DIR}
|
||||
${KYTY_SOURCE_DIR}
|
||||
)
|
||||
|
||||
set(KYTY_VERSION "${PROJECT_VERSION}")
|
||||
|
||||
configure_file(
|
||||
${KYTY_SOURCE_DIR}/cmake_config.h.in
|
||||
${PROJECT_BINARY_DIR}/cmake_config.h
|
||||
)
|
||||
|
||||
find_package(Git)
|
||||
|
||||
add_custom_target(KytyGitVersion
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-D INPUT_FILE=${KYTY_SOURCE_DIR}/kytyGitVersion.h.in
|
||||
-D OUTPUT_FILE=${PROJECT_BINARY_DIR}/kytyGitVersion.h
|
||||
-D GIT_EXECUTABLE=${GIT_EXECUTABLE}
|
||||
-D GIT_WORKING_DIRECTORY=${CMAKE_CURRENT_SOURCE_DIR}
|
||||
-P ${KYTY_SOURCE_DIR}/generate_version.cmake
|
||||
BYPRODUCTS ${PROJECT_BINARY_DIR}/kytyGitVersion.h
|
||||
COMMENT "Generate kytyGitVersion.h"
|
||||
)
|
||||
|
||||
add_library(kyty_git_version INTERFACE)
|
||||
add_dependencies(kyty_git_version KytyGitVersion)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 12.0.0)
|
||||
list(APPEND KYTY_IWYU
|
||||
kyty_emulator
|
||||
common
|
||||
#launcher
|
||||
)
|
||||
list(APPEND KYTY_CLANG_TIDY
|
||||
kyty_emulator
|
||||
#common
|
||||
launcher
|
||||
)
|
||||
endif()
|
||||
|
||||
option(KYTY_BUILD_LAUNCHER "Build Qt launcher" ON)
|
||||
|
||||
config_compiler_and_linker()
|
||||
|
||||
add_subdirectory("${KYTY_THIRD_PARTY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/3rdparty")
|
||||
add_subdirectory("${KYTY_SOURCE_DIR}/common" "${CMAKE_CURRENT_BINARY_DIR}/common")
|
||||
|
||||
file(GLOB kyty_emulator_src CONFIGURE_DEPENDS
|
||||
"${KYTY_SOURCE_DIR}/libs/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/libs/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/command_processor/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/command_processor/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/cache/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/cache/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/image/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/image/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/pipeline/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/pipeline/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/cfg/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/cfg/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/decode/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/decode/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/translate/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/translate/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/backend/spirv/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/backend/spirv/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/opcodes/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/opcodes/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/opcodes/*.inc"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/presentation/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/presentation/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/presentation/window/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/presentation/window/*.h"
|
||||
"${KYTY_SOURCE_DIR}/kernel/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/kernel/*.h"
|
||||
"${KYTY_SOURCE_DIR}/loader/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/loader/*.h"
|
||||
)
|
||||
|
||||
find_program(KYTY_GLSLANG_VALIDATOR NAMES glslangValidator glslang REQUIRED)
|
||||
set(gpu_tiler_shader_dir "${KYTY_SOURCE_DIR}/graphics/host_gpu/shaders")
|
||||
set(gpu_tiler_generated_dir "${PROJECT_BINARY_DIR}/gpu_tiler_shaders")
|
||||
set(gpu_tiler_shader_names
|
||||
standard256
|
||||
standard4
|
||||
standard4_3d
|
||||
standard64
|
||||
standard64_3d
|
||||
prt
|
||||
prt_3d
|
||||
render_target
|
||||
depth
|
||||
promote_d16
|
||||
demote_d16
|
||||
color_transform
|
||||
)
|
||||
file(GLOB gpu_tiler_shader_includes CONFIGURE_DEPENDS "${gpu_tiler_shader_dir}/gpu_tiler_*.inc")
|
||||
foreach(shader_name IN LISTS gpu_tiler_shader_names)
|
||||
set(shader_source "${gpu_tiler_shader_dir}/gpu_tiler_${shader_name}.comp")
|
||||
set(shader_spv "${gpu_tiler_generated_dir}/gpu_tiler_${shader_name}.spv")
|
||||
set(shader_header "${gpu_tiler_generated_dir}/gpu_tiler_${shader_name}_spv.h")
|
||||
string(TOUPPER "GPU_TILER_${shader_name}_SPV" shader_symbol)
|
||||
add_custom_command(
|
||||
OUTPUT "${shader_header}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_tiler_generated_dir}"
|
||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os
|
||||
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
|
||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
|
||||
-DSYMBOL=${shader_symbol} -P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
|
||||
DEPENDS "${shader_source}" ${gpu_tiler_shader_includes}
|
||||
VERBATIM
|
||||
)
|
||||
list(APPEND gpu_tiler_shader_headers "${shader_header}")
|
||||
endforeach()
|
||||
|
||||
set(fault_buffer_shader_source "${gpu_tiler_shader_dir}/fault_buffer_process.comp")
|
||||
set(fault_buffer_shader_spv "${gpu_tiler_generated_dir}/fault_buffer_process.spv")
|
||||
set(fault_buffer_shader_header "${gpu_tiler_generated_dir}/fault_buffer_process_spv.h")
|
||||
add_custom_command(
|
||||
OUTPUT "${fault_buffer_shader_header}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_tiler_generated_dir}"
|
||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os
|
||||
-DCACHING_PAGEBITS=14 -DMAX_PAGE_FAULTS=1024
|
||||
-o "${fault_buffer_shader_spv}" "${fault_buffer_shader_source}"
|
||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${fault_buffer_shader_spv} -DOUTPUT=${fault_buffer_shader_header}
|
||||
-DSYMBOL=FAULT_BUFFER_PROCESS_SPV -P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
|
||||
DEPENDS "${fault_buffer_shader_source}"
|
||||
VERBATIM
|
||||
)
|
||||
list(APPEND kyty_emulator_src "${fault_buffer_shader_header}")
|
||||
list(APPEND kyty_emulator_src ${gpu_tiler_shader_headers})
|
||||
|
||||
set(gpu_blit_generated_dir "${PROJECT_BINARY_DIR}/gpu_blit_shaders")
|
||||
function(add_gpu_blit_shader shader_name shader_file)
|
||||
set(shader_source "${gpu_tiler_shader_dir}/${shader_file}")
|
||||
set(shader_spv "${gpu_blit_generated_dir}/${shader_name}.spv")
|
||||
set(shader_header "${gpu_blit_generated_dir}/${shader_name}_spv.h")
|
||||
string(TOUPPER "${shader_name}_SPV" shader_symbol)
|
||||
add_custom_command(
|
||||
OUTPUT "${shader_header}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_blit_generated_dir}"
|
||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os ${ARGN}
|
||||
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
|
||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
|
||||
-DSYMBOL=${shader_symbol} -P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
|
||||
DEPENDS "${shader_source}"
|
||||
VERBATIM
|
||||
)
|
||||
set(gpu_blit_shader_headers ${gpu_blit_shader_headers} "${shader_header}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
add_gpu_blit_shader(gpu_blit_fs_triangle gpu_blit_fs_triangle.vert)
|
||||
add_gpu_blit_shader(gpu_blit_color_to_ms_depth gpu_blit_color_to_ms_depth.frag)
|
||||
add_gpu_blit_shader(gpu_video_out_overlay gpu_video_out_overlay.frag)
|
||||
add_gpu_blit_shader(gpu_imgui_hdr gpu_video_out_overlay.frag -DIMGUI_HDR=1)
|
||||
list(APPEND kyty_emulator_src ${gpu_blit_shader_headers})
|
||||
|
||||
list(APPEND kyty_emulator_src
|
||||
"${KYTY_SOURCE_DIR}/emulator.h"
|
||||
"${KYTY_SOURCE_DIR}/emulator.cpp"
|
||||
)
|
||||
list(REMOVE_DUPLICATES kyty_emulator_src)
|
||||
|
||||
if (KYTY_CLANG_CL)
|
||||
set_source_files_properties(${kyty_emulator_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack -Wno-deprecated-declarations -D_TIMESPEC_DEFINED")
|
||||
endif()
|
||||
|
||||
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL3::SDL3-static imgui xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version Zydis::Zydis xbyak::xbyak opus)
|
||||
|
||||
# Linux system libraries.
|
||||
if(LINUX)
|
||||
find_package(Threads REQUIRED)
|
||||
list(APPEND kyty_emulator_link_libraries m ${CMAKE_DL_LIBS} Threads::Threads)
|
||||
endif()
|
||||
|
||||
set(inc_headers
|
||||
${KYTY_SOURCE_DIR}
|
||||
${KYTY_THIRD_PARTY_DIR}/VulkanMemoryAllocator/include
|
||||
${KYTY_THIRD_PARTY_DIR}/SPIRV-Tools/include
|
||||
${KYTY_THIRD_PARTY_DIR}/SPIRV-Headers/include
|
||||
${KYTY_THIRD_PARTY_DIR}/fmt/include
|
||||
${KYTY_THIRD_PARTY_DIR}/xxHash
|
||||
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
|
||||
${KYTY_THIRD_PARTY_DIR}/renderdoc
|
||||
${KYTY_THIRD_PARTY_DIR}/stb
|
||||
)
|
||||
|
||||
if (KYTY_CLANG_CL)
|
||||
list(APPEND kyty_emulator_link_libraries winpthread)
|
||||
list(APPEND inc_headers
|
||||
${KYTY_THIRD_PARTY_DIR}/winpthread/include
|
||||
)
|
||||
endif()
|
||||
|
||||
list(APPEND check_headers
|
||||
${KYTY_SOURCE_DIR}
|
||||
)
|
||||
|
||||
function(add_kyty_full_emulator_test target source)
|
||||
add_executable(${target} EXCLUDE_FROM_ALL ${source} ${kyty_emulator_src})
|
||||
target_link_libraries(${target} ${kyty_emulator_link_libraries})
|
||||
target_include_directories(${target} PRIVATE ${inc_headers})
|
||||
if (WIN32)
|
||||
target_link_libraries(${target} iphlpapi)
|
||||
endif()
|
||||
if (KYTY_CLANG_CL)
|
||||
target_link_libraries(${target} onecore)
|
||||
add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" $<TARGET_FILE_DIR:${target}>/libwinpthread-1.dll)
|
||||
endif()
|
||||
# The macOS x86_64 guest address space needs its .zerofill segments anchored
|
||||
# by linker flags, or the kernel kills the binary on load (posix_spawn EIO).
|
||||
configure_macos_guest_address_space(${target})
|
||||
endfunction()
|
||||
|
||||
function(configure_macos_guest_address_space target)
|
||||
if(APPLE AND (CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64" OR
|
||||
(NOT CMAKE_OSX_ARCHITECTURES AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64)$")))
|
||||
target_sources(${target} PRIVATE "${KYTY_SOURCE_DIR}/kernel/macosGuestAddressSpace.cpp")
|
||||
target_compile_definitions(${target} PRIVATE KYTY_LINKED_GUEST_ADDRESS_SPACE=1)
|
||||
target_link_options(${target} PRIVATE
|
||||
-Wl,-ld_classic,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x40000,-segaddr,SYSTEM_MANAGED,0x40000,-segaddr,SYSTEM_RESERVED,0x7ffffc000,-segaddr,USER_AREA,0x7000000000,-image_base,0x700000000000)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
add_kyty_full_emulator_test(shader_cfg_tests "${KYTY_TESTS_DIR}/shaderCfgTests.cpp")
|
||||
|
||||
add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/ScalarProvenanceTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/gpu_format.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ReadLaneElimination.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
|
||||
)
|
||||
target_link_libraries(scalar_provenance_tests fmt::fmt xbyak::xbyak)
|
||||
target_include_directories(scalar_provenance_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(page_manager_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/PageManagerTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp"
|
||||
)
|
||||
target_link_libraries(page_manager_tests common)
|
||||
target_include_directories(page_manager_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(archive_file_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/ArchiveFileTests.cpp"
|
||||
)
|
||||
target_link_libraries(archive_file_tests common ZArchive::zarchive)
|
||||
target_include_directories(archive_file_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(trophy_system_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/TrophySystemTests.cpp"
|
||||
)
|
||||
target_link_libraries(trophy_system_tests common)
|
||||
target_include_directories(trophy_system_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(avplayer_file_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/AvPlayerFileTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
|
||||
)
|
||||
target_link_libraries(avplayer_file_tests common FFmpeg::ffmpeg ZArchive::zarchive)
|
||||
target_include_directories(avplayer_file_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(vdecsw_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/VdecswTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/libs/videoDec2Decoder.cpp"
|
||||
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
|
||||
)
|
||||
target_link_libraries(vdecsw_tests common FFmpeg::ffmpeg)
|
||||
target_include_directories(vdecsw_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(bit_array_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/BitArrayTests.cpp"
|
||||
)
|
||||
target_include_directories(bit_array_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(lru_cache_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/LruCacheTests.cpp"
|
||||
)
|
||||
target_include_directories(lru_cache_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(ime_dialog_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/ImeDialogTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/libs/imeCommon.cpp"
|
||||
"${KYTY_SOURCE_DIR}/libs/imeDialog.cpp"
|
||||
)
|
||||
target_include_directories(ime_dialog_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(memory_tracker_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/MemoryTrackerTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/memoryTracker.cpp"
|
||||
)
|
||||
target_link_libraries(memory_tracker_tests fmt::fmt common)
|
||||
target_include_directories(memory_tracker_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(shader_vertex_metadata_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/ShaderVertexMetadataTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/shaderVertexMetadata.cpp"
|
||||
)
|
||||
target_include_directories(shader_vertex_metadata_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(resource_materialization_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/ResourceMaterializationTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/gpu_format.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
|
||||
)
|
||||
target_link_libraries(resource_materialization_tests fmt::fmt xbyak::xbyak)
|
||||
target_include_directories(resource_materialization_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/ResourceTrackingTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/gpu_format.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/shaderPixelParameter.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp"
|
||||
)
|
||||
target_link_libraries(resource_tracking_tests fmt::fmt xbyak::xbyak)
|
||||
target_include_directories(resource_tracking_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/AudioOut2PortTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/libs/libAudio2.cpp"
|
||||
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
|
||||
)
|
||||
target_link_libraries(audio_out2_port_tests common fmt::fmt)
|
||||
target_include_directories(audio_out2_port_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(audio_out_timing_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/AudioOutTimingTests.cpp")
|
||||
target_link_libraries(audio_out_timing_tests common fmt::fmt SDL3::SDL3-static)
|
||||
target_include_directories(audio_out_timing_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(ngs2_sampler_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/Ngs2SamplerTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
|
||||
)
|
||||
target_link_libraries(ngs2_sampler_tests common fmt::fmt LibAtrac9)
|
||||
target_include_directories(ngs2_sampler_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(pad_haptics_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/PadHapticsTests.cpp"
|
||||
)
|
||||
target_link_libraries(pad_haptics_tests common fmt::fmt SDL3::SDL3-static opus)
|
||||
target_include_directories(pad_haptics_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(controller_settings_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/ControllerSettingsTests.cpp"
|
||||
)
|
||||
target_link_libraries(controller_settings_tests common fmt::fmt SDL3::SDL3-static)
|
||||
target_include_directories(controller_settings_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(host_input_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/HostInputTests.cpp")
|
||||
target_link_libraries(host_input_tests common fmt::fmt SDL3::SDL3-static)
|
||||
target_include_directories(host_input_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(save_data_memory_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/SaveDataMemoryTests.cpp"
|
||||
)
|
||||
target_link_libraries(save_data_memory_tests common fmt::fmt)
|
||||
target_include_directories(save_data_memory_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(png_enc_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/PngEncTests.cpp")
|
||||
target_link_libraries(png_enc_tests common fmt::fmt)
|
||||
target_include_directories(png_enc_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(ces_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/CesTests.cpp")
|
||||
target_link_libraries(ces_tests common fmt::fmt SDL3::SDL3-static)
|
||||
target_include_directories(ces_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(save_data_dialog_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/SaveDataDialogTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/libs/dialog.cpp"
|
||||
)
|
||||
target_link_libraries(save_data_dialog_tests common fmt::fmt)
|
||||
target_include_directories(save_data_dialog_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_kyty_full_emulator_test(http_uri_parse_tests
|
||||
"${KYTY_TESTS_DIR}/HttpUriParseTests.cpp")
|
||||
|
||||
add_executable(event_queue_lifetime_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/EventQueueLifetimeTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/kernel/eventQueue.cpp"
|
||||
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
|
||||
)
|
||||
target_link_libraries(event_queue_lifetime_tests common fmt::fmt)
|
||||
target_include_directories(event_queue_lifetime_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_kyty_full_emulator_test(kernel_file_system_tests
|
||||
"${KYTY_TESTS_DIR}/KernelFileSystemTests.cpp")
|
||||
target_link_libraries(kernel_file_system_tests ZArchive::zarchive)
|
||||
|
||||
add_executable(sync_on_address_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/SyncOnAddressTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/kernel/syncOnAddress.cpp"
|
||||
)
|
||||
target_link_libraries(sync_on_address_tests common)
|
||||
target_include_directories(sync_on_address_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(image_page_table_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/ImagePageTableTests.cpp"
|
||||
)
|
||||
target_link_libraries(image_page_table_tests common fmt::fmt)
|
||||
target_include_directories(image_page_table_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_kyty_full_emulator_test(shader_recompiler_compute_tests "${KYTY_TESTS_DIR}/ShaderRecompilerComputeTests.cpp")
|
||||
|
||||
set(gpu_test_generated_dir "${PROJECT_BINARY_DIR}/gpu_test_shaders")
|
||||
set(gpu_test_ms_depth_source
|
||||
"${gpu_tiler_shader_dir}/gpu_test_ms_depth.comp")
|
||||
set(gpu_test_ms_depth_spv
|
||||
"${gpu_test_generated_dir}/gpu_test_ms_depth.spv")
|
||||
set(gpu_test_ms_depth_header
|
||||
"${gpu_test_generated_dir}/gpu_test_ms_depth_spv.h")
|
||||
add_custom_command(
|
||||
OUTPUT "${gpu_test_ms_depth_header}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_test_generated_dir}"
|
||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os
|
||||
-o "${gpu_test_ms_depth_spv}" "${gpu_test_ms_depth_source}"
|
||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${gpu_test_ms_depth_spv}
|
||||
-DOUTPUT=${gpu_test_ms_depth_header} -DSYMBOL=GPU_TEST_MS_DEPTH_SPV
|
||||
-P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
|
||||
DEPENDS "${gpu_test_ms_depth_source}"
|
||||
VERBATIM
|
||||
)
|
||||
target_sources(shader_recompiler_compute_tests PRIVATE
|
||||
"${gpu_test_ms_depth_header}")
|
||||
|
||||
add_kyty_full_emulator_test(virtual_memory_allocation_tests "${KYTY_TESTS_DIR}/VirtualMemoryAllocationTests.cpp")
|
||||
target_compile_definitions(virtual_memory_allocation_tests PRIVATE
|
||||
KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1)
|
||||
|
||||
# These tests use exceptions.
|
||||
if(NOT KYTY_CLANG_CL)
|
||||
foreach(kyty_exception_test scalar_provenance_tests resource_tracking_tests
|
||||
virtual_memory_allocation_tests)
|
||||
target_compile_options(${kyty_exception_test} PRIVATE -fexceptions)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test(NAME emulator_user_name_cli
|
||||
COMMAND $<TARGET_FILE:kyty_emulator> --help --user-name 1234567890123456)
|
||||
add_test(NAME emulator_user_name_too_long
|
||||
COMMAND $<TARGET_FILE:kyty_emulator> --help --user-name 12345678901234567)
|
||||
set_tests_properties(emulator_user_name_too_long PROPERTIES WILL_FAIL TRUE)
|
||||
add_test(NAME emulator_present_mode_cli
|
||||
COMMAND $<TARGET_FILE:kyty_emulator> --help --present-mode Immediate)
|
||||
add_test(NAME emulator_present_mode_invalid
|
||||
COMMAND $<TARGET_FILE:kyty_emulator> --help --present-mode Invalid)
|
||||
set_tests_properties(emulator_present_mode_invalid PROPERTIES WILL_FAIL TRUE)
|
||||
add_test(NAME ime_dialog COMMAND $<TARGET_FILE:ime_dialog_tests>)
|
||||
add_test(NAME shader_cfg COMMAND $<TARGET_FILE:shader_cfg_tests>)
|
||||
add_test(NAME scalar_provenance COMMAND $<TARGET_FILE:scalar_provenance_tests>)
|
||||
add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>)
|
||||
add_test(NAME memory_tracker COMMAND $<TARGET_FILE:memory_tracker_tests>)
|
||||
add_test(NAME page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
|
||||
add_test(NAME archive_file COMMAND $<TARGET_FILE:archive_file_tests>)
|
||||
add_test(NAME trophy_system COMMAND $<TARGET_FILE:trophy_system_tests>)
|
||||
add_test(NAME avplayer_file COMMAND $<TARGET_FILE:avplayer_file_tests>)
|
||||
add_test(NAME vdecsw COMMAND $<TARGET_FILE:vdecsw_tests>)
|
||||
add_test(NAME bit_array COMMAND $<TARGET_FILE:bit_array_tests>)
|
||||
add_test(NAME lru_cache COMMAND $<TARGET_FILE:lru_cache_tests>)
|
||||
add_test(NAME shader_vertex_metadata COMMAND $<TARGET_FILE:shader_vertex_metadata_tests>)
|
||||
add_test(NAME resource_materialization COMMAND $<TARGET_FILE:resource_materialization_tests>)
|
||||
add_test(NAME resource_tracking COMMAND $<TARGET_FILE:resource_tracking_tests>)
|
||||
add_test(NAME event_queue_lifetime COMMAND $<TARGET_FILE:event_queue_lifetime_tests>)
|
||||
add_test(NAME kernel_file_system COMMAND $<TARGET_FILE:kernel_file_system_tests>)
|
||||
add_test(NAME sync_on_address COMMAND $<TARGET_FILE:sync_on_address_tests>)
|
||||
add_test(NAME audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_tests>)
|
||||
add_test(NAME audio_out_timing COMMAND $<TARGET_FILE:audio_out_timing_tests>)
|
||||
add_test(NAME ngs2_sampler COMMAND $<TARGET_FILE:ngs2_sampler_tests>)
|
||||
add_test(NAME pad_haptics COMMAND $<TARGET_FILE:pad_haptics_tests>)
|
||||
add_test(NAME controller_settings COMMAND $<TARGET_FILE:controller_settings_tests>)
|
||||
add_test(NAME host_input COMMAND $<TARGET_FILE:host_input_tests>)
|
||||
add_test(NAME save_data_memory COMMAND $<TARGET_FILE:save_data_memory_tests>)
|
||||
add_test(NAME png_enc COMMAND $<TARGET_FILE:png_enc_tests>)
|
||||
add_test(NAME ces COMMAND $<TARGET_FILE:ces_tests>)
|
||||
add_test(NAME save_data_dialog COMMAND $<TARGET_FILE:save_data_dialog_tests>)
|
||||
add_test(NAME http_uri_parse COMMAND $<TARGET_FILE:http_uri_parse_tests>)
|
||||
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
|
||||
add_test(NAME shader_recompiler_alignbyte
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --alignbyte-only)
|
||||
add_test(NAME graphics_pipeline_rasterization
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --polygon-mode-only)
|
||||
add_test(NAME graphics_draw_offsets
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --draw-offset-only)
|
||||
add_test(NAME virtual_memory_allocation
|
||||
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests>)
|
||||
add_test(NAME guest_red_zone_patcher
|
||||
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests> --red-zone-patcher-only)
|
||||
add_test(NAME command_scheduler_timeline
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --scheduler-only)
|
||||
add_test(NAME stream_buffer_ring
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
|
||||
add_test(NAME gpu_command_lane
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
|
||||
add_test(NAME pm4_context_state
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --context-state-only)
|
||||
add_test(NAME gpu_tiler
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-tiler-only)
|
||||
add_test(NAME image_transfer_layout
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-layout-only)
|
||||
|
||||
add_test(NAME texture_cache_layered_image
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --layered-image-only)
|
||||
add_test(NAME host_image_allocation
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --host-image-allocation-only)
|
||||
add_test(NAME texture_cache_image_views
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-view-cache-only)
|
||||
add_test(NAME texture_cache_storage_sampled
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --storage-sampled-only)
|
||||
add_test(NAME texture_cache_depth_readback
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --depth-readback-only)
|
||||
add_test(NAME compute_meta_clear_classification
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --compute-meta-clear-only)
|
||||
add_test(NAME buffer_cache_dirty_gc
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-gc-only)
|
||||
add_test(NAME color_render_target_1d
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --rt1d-only)
|
||||
|
||||
add_test(NAME texture_cache_image_overlap
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-overlap-only)
|
||||
add_test(NAME texture_cache_htile_clear
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --htile-clear-only)
|
||||
add_test(NAME buffer_cache_ranges
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-range-only)
|
||||
|
||||
add_custom_target(kyty_tests DEPENDS
|
||||
ime_dialog_tests
|
||||
shader_cfg_tests
|
||||
scalar_provenance_tests
|
||||
image_page_table_tests
|
||||
memory_tracker_tests
|
||||
page_manager_tests
|
||||
archive_file_tests
|
||||
trophy_system_tests
|
||||
avplayer_file_tests
|
||||
vdecsw_tests
|
||||
bit_array_tests
|
||||
lru_cache_tests
|
||||
shader_vertex_metadata_tests
|
||||
resource_materialization_tests
|
||||
resource_tracking_tests
|
||||
event_queue_lifetime_tests
|
||||
kernel_file_system_tests
|
||||
sync_on_address_tests
|
||||
shader_recompiler_compute_tests
|
||||
virtual_memory_allocation_tests
|
||||
audio_out2_port_tests
|
||||
audio_out_timing_tests
|
||||
ngs2_sampler_tests
|
||||
pad_haptics_tests
|
||||
controller_settings_tests
|
||||
host_input_tests
|
||||
save_data_memory_tests
|
||||
png_enc_tests
|
||||
ces_tests
|
||||
save_data_dialog_tests
|
||||
http_uri_parse_tests
|
||||
)
|
||||
endif()
|
||||
|
||||
# Test Darwin signal-context restoration for SSE4a instructions.
|
||||
if(APPLE AND (CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64" OR
|
||||
(NOT CMAKE_OSX_ARCHITECTURES AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64)$")))
|
||||
add_executable(macos_sse4a_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/macosSse4aTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/loader/x64InstructionEmulator.cpp"
|
||||
"${KYTY_SOURCE_DIR}/loader/guestInstructionPatcher.cpp"
|
||||
)
|
||||
target_link_libraries(macos_sse4a_tests common fmt::fmt Zydis::Zydis xbyak::xbyak)
|
||||
target_include_directories(macos_sse4a_tests PRIVATE ${inc_headers})
|
||||
if(BUILD_TESTING)
|
||||
add_test(NAME macos_sse4a COMMAND $<TARGET_FILE:macos_sse4a_tests>)
|
||||
set_tests_properties(macos_sse4a PROPERTIES TIMEOUT 15)
|
||||
add_dependencies(kyty_tests macos_sse4a_tests)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_executable(kyty_emulator "${KYTY_SOURCE_DIR}/main.cpp" ${kyty_emulator_src})
|
||||
configure_macos_guest_address_space(kyty_emulator)
|
||||
|
||||
set(KYTY_TROPHY_UNLOCK_SOUND "${CMAKE_CURRENT_SOURCE_DIR}/assets/sounds/trophy-unlock.wav")
|
||||
add_custom_target(kyty_trophy_sound
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:kyty_emulator>/assets/sounds"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_TROPHY_UNLOCK_SOUND}"
|
||||
"$<TARGET_FILE_DIR:kyty_emulator>/assets/sounds/trophy-unlock.wav"
|
||||
VERBATIM)
|
||||
add_dependencies(kyty_emulator kyty_trophy_sound)
|
||||
install(FILES "${KYTY_TROPHY_UNLOCK_SOUND}" DESTINATION assets/sounds)
|
||||
|
||||
add_custom_target(kyty_touch_cursors
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/assets/cursors"
|
||||
"$<TARGET_FILE_DIR:kyty_emulator>/assets/cursors"
|
||||
VERBATIM)
|
||||
add_dependencies(kyty_emulator kyty_touch_cursors)
|
||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/assets/cursors" DESTINATION assets)
|
||||
|
||||
target_link_libraries(kyty_emulator ${kyty_emulator_link_libraries})
|
||||
if (WIN32)
|
||||
target_link_libraries(kyty_emulator opengl32)
|
||||
target_link_libraries(kyty_emulator iphlpapi)
|
||||
target_link_libraries(kyty_emulator setupapi)
|
||||
target_link_libraries(kyty_emulator ws2_32)
|
||||
endif()
|
||||
if (KYTY_CLANG_CL)
|
||||
target_link_libraries(kyty_emulator onecore)
|
||||
endif()
|
||||
if (CLANG AND NOT KYTY_CLANG_CL)
|
||||
target_link_libraries(kyty_emulator pthread)
|
||||
endif()
|
||||
# dlopen/dlsym/dladdr for RenderDoc.
|
||||
target_link_libraries(kyty_emulator ${CMAKE_DL_LIBS})
|
||||
target_include_directories(kyty_emulator PRIVATE ${inc_headers})
|
||||
|
||||
clang_tidy_check(kyty_emulator "" "${check_headers}" "${inc_headers}")
|
||||
|
||||
include_what_you_use(kyty_emulator "${inc_headers}")
|
||||
|
||||
set(KYTY_EMULATOR_MAP_NAME "kyty_emulator_${KYTY_COMPILER_ID}_${KYTY_LINKER_ID}.map")
|
||||
|
||||
set(KYTY_EMULATOR_MAP_LINK_PATH "${CMAKE_CURRENT_BINARY_DIR}/${KYTY_EMULATOR_MAP_NAME}")
|
||||
set(KYTY_EMULATOR_PDB_LINK_PATH "${CMAKE_CURRENT_BINARY_DIR}/kyty_emulator.pdb")
|
||||
|
||||
if(KYTY_CLANG_CL)
|
||||
target_link_options(kyty_emulator PRIVATE
|
||||
"/DYNAMICBASE:NO"
|
||||
"/DEBUG:FULL"
|
||||
"/PDB:${KYTY_EMULATOR_PDB_LINK_PATH}"
|
||||
"/lldmap:${KYTY_EMULATOR_MAP_LINK_PATH}"
|
||||
)
|
||||
add_custom_command(TARGET kyty_emulator POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" $<TARGET_FILE_DIR:kyty_emulator>/libwinpthread-1.dll)
|
||||
elseif(WIN32 OR LINUX)
|
||||
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS}")
|
||||
# LINK_FLAGS is a space-delimited string: a -Map path containing spaces would
|
||||
# be split into bogus arguments, so pass it as a discrete link option instead.
|
||||
target_link_options(kyty_emulator PRIVATE "-Wl,-Map=${KYTY_EMULATOR_MAP_LINK_PATH}")
|
||||
endif()
|
||||
|
||||
if(LINUX)
|
||||
target_link_options(kyty_emulator PRIVATE
|
||||
$<$<CONFIG:Release>:-Wl,--strip-all>
|
||||
)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
# The emulator writes x86-64 trampolines/PLT stubs into memory and executes them, and
|
||||
# runs guest code under Rosetta. That requires the JIT / unsigned-executable-memory
|
||||
# entitlements, so re-sign the binary after every link (an unsigned/relinked binary
|
||||
# reverts to the hardened defaults and aborts when it first executes written code).
|
||||
add_custom_command(TARGET kyty_emulator POST_BUILD
|
||||
COMMAND codesign -s - --force --options runtime
|
||||
--entitlements "${KYTY_SOURCE_DIR}/macos_jit.entitlements"
|
||||
$<TARGET_FILE:kyty_emulator>
|
||||
COMMENT "Codesign kyty_emulator with JIT entitlements (macOS)")
|
||||
endif()
|
||||
|
||||
install(TARGETS kyty_emulator DESTINATION .)
|
||||
if(KYTY_BUILD_LAUNCHER)
|
||||
add_subdirectory("${KYTY_SOURCE_DIR}/launcher" "${CMAKE_CURRENT_BINARY_DIR}/launcher")
|
||||
|
||||
if(LINUX)
|
||||
target_link_options(launcher PRIVATE $<$<CONFIG:Release>:-Wl,--strip-all>)
|
||||
endif()
|
||||
endif()
|
||||
if(KYTY_CLANG_CL)
|
||||
install(FILES "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" DESTINATION .)
|
||||
endif()
|
||||
@@ -1,20 +1,16 @@
|
||||
# KytyPS5
|
||||
|
||||
[](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
|
||||
[](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
|
||||
[](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
|
||||
[](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
|
||||
[](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
|
||||
[](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
|
||||
[](#system-requirements)
|
||||
[](#current-status)
|
||||
[](#current-status)
|
||||
[](LICENSE)
|
||||
|
||||
**[Weekly updates](https://github.com/KytyPS5/KytyPS5/discussions/862)** — game progress, recent fixes, and ongoing development.
|
||||
|
||||
**[Development on Discord](https://discord.gg/UNrkMqGaBg)** — KytyPS5 development.
|
||||
|
||||
KytyPS5 is a free and open-source PlayStation 5 translation layer written in C++ for Windows
|
||||
and Linux, with experimental macOS support. It is based on a heavily modified version of
|
||||
[Kyty](https://github.com/InoriRus/Kyty). The project is in active development, and behavior
|
||||
can change significantly between builds.
|
||||
KytyPS5 is a free and open-source PlayStation 5 emulator written in C++ for Windows and Linux,
|
||||
with experimental macOS support. It is based on a heavily modified version of
|
||||
[Kyty](https://github.com/InoriRus/Kyty). The project is in an early stage of development, so
|
||||
compatibility is limited and behavior may change significantly between builds.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> KytyPS5 is not affiliated with Sony Interactive Entertainment or PlayStation. The project does
|
||||
@@ -23,43 +19,40 @@ can change significantly between builds.
|
||||
|
||||
## Current Status
|
||||
|
||||
KytyPS5 can boot 2D and 3D games, including titles built with Unreal Engine
|
||||
4/5, Unity, and custom engines. External low-level emulation modules are neither required nor
|
||||
planned.
|
||||
KytyPS5 can boot 2D games and a selection of 3D games, including titles built with Unreal Engine
|
||||
4/5, Unity, and custom engines. No external low-level emulation modules are currently required.
|
||||
|
||||
Development is currently focused on expanding game compatibility and improving boot reliability.
|
||||
Development is focused on compatibility and boot reliability.
|
||||
|
||||
Windows and Linux are the primary target platforms and receive the most testing.
|
||||
Windows is the primary platform and receives the most testing. Linux builds and runs; see
|
||||
[Building on Linux](#building-on-linux).
|
||||
|
||||
macOS support is experimental. The emulator is built for x86-64 and runs on Apple Silicon under
|
||||
Rosetta 2, with Vulkan provided by MoltenVK. A small number of titles have been verified in-game
|
||||
on Apple Silicon hardware; see [Building on macOS](#building-on-macos).
|
||||
|
||||
Community game test results are available in the
|
||||
[KytyPS5 Compatibility List](https://kytyps5.github.io/).
|
||||
|
||||
## Bugs and Issues
|
||||
|
||||
Compatibility, stability, and performance can vary between versions. You may encounter crashes
|
||||
or graphical glitches, so please include the version you tested when reporting an issue.
|
||||
The project is in an early stage, so please be mindful when opening new issues. Expect crashes,
|
||||
graphical glitches, low compatibility, and poor performance.
|
||||
|
||||
## Screenshots
|
||||
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<strong>Astro Bot</strong><br>
|
||||
<img src="docs/screenshots/ps5-01.png" width="300" alt="Astro Bot running in KytyPS5">
|
||||
<strong>Disgaea 6</strong><br>
|
||||
<img src="docs/screenshots/ps5-01.png" width="300" alt="Disgaea 6 running in KytyPS5">
|
||||
</td>
|
||||
<td align="center">
|
||||
<strong>Saros</strong><br>
|
||||
<img src="docs/screenshots/ps5-03.png" width="300" alt="Saros running in KytyPS5">
|
||||
<strong>Dreaming Sarah</strong><br>
|
||||
<img src="docs/screenshots/ps5-03.png" width="300" alt="Dreaming Sarah running in KytyPS5">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<strong>Neptunia ReVerse</strong><br>
|
||||
<img src="docs/screenshots/ps5-04.png" width="300" alt="Neptunia ReVerse running in KytyPS5">
|
||||
<img src="docs/screenshots/ps5-04.png" width="300" alt="Minecraft Legends running in KytyPS5">
|
||||
</td>
|
||||
<td align="center">
|
||||
<strong>SILENT HILL: The Short Message</strong><br>
|
||||
@@ -68,12 +61,12 @@ or graphical glitches, so please include the version you tested when reporting a
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<strong>Demon's Souls</strong><br>
|
||||
<img src="docs/screenshots/ps5-02.png" width="300" alt="Demon's Souls running in KytyPS5">
|
||||
<strong>Hellboy</strong><br>
|
||||
<img src="docs/screenshots/ps5-02.png" width="300" alt="Disgaea 6 running in KytyPS5">
|
||||
</td>
|
||||
<td align="center">
|
||||
<strong>UFC 6</strong><br>
|
||||
<img src="docs/screenshots/ps5-06.jpg" width="300" alt="UFC 6 running in KytyPS5">
|
||||
<strong>Paleo Pines</strong><br>
|
||||
<img src="docs/screenshots/ps5-06.png" width="300" alt="Dreaming Sarah running in KytyPS5">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -83,31 +76,19 @@ or graphical glitches, so please include the version you tested when reporting a
|
||||
## Contributing
|
||||
|
||||
Testing games and submitting detailed bug reports are useful ways to contribute. Search existing
|
||||
issues first, then use the **Game Emulation Status Report** template and attach the complete log file.
|
||||
issues first, then use the **Game Emulation Bug Report** template and attach the complete log file.
|
||||
|
||||
**Pull requests for general fixes, architectural changes, or refactoring that do not address a concrete bug or launch problem in a specific game will be automatically closed.**
|
||||
|
||||
**Large, unfocused commits or pull requests that bundle unrelated changes without a clear purpose or game-related benefit will also be automatically closed.**
|
||||
|
||||
Keep each PR focused. Name the affected game, describe the exact problem, and provide evidence showing what the change fixes.
|
||||
|
||||
Windows and Linux are the primary target platforms, so changes to shared code should not
|
||||
introduce regressions on either platform. Changes confined to a platform's own code paths
|
||||
only need to build on that platform. Because KytyPS5 is still evolving quickly, consider
|
||||
opening an issue before starting a large change.
|
||||
Code contributions should be focused, build successfully on the platforms they touch, and include
|
||||
relevant tests where practical. Windows is the primary target, so a change that alters shared code
|
||||
should not regress it; changes confined to a platform's own code paths only need to build there. Because KytyPS5 is still evolving quickly, consider opening an issue before
|
||||
starting a large change.
|
||||
|
||||
### Formatting
|
||||
|
||||
Set up the clang-format hook after cloning.
|
||||
Set up the clang-format hook after cloning:
|
||||
|
||||
Install `pre-commit` using the method appropriate for your platform:
|
||||
|
||||
- **Arch Linux / CachyOS:** `sudo pacman -S pre-commit`
|
||||
- **Other Linux / macOS / Windows:** `python -m pip install pre-commit`
|
||||
|
||||
Then install the Git hook:
|
||||
|
||||
```bash
|
||||
```powershell
|
||||
python -m pip install pre-commit
|
||||
python -m pre_commit install --install-hooks
|
||||
```
|
||||
|
||||
@@ -142,13 +123,11 @@ the Vulkan/SPIR-V validation rules.
|
||||
### Build requirements (Windows)
|
||||
|
||||
- Git
|
||||
- CMake 3.22.1 or newer
|
||||
- CMake 3.12 or newer
|
||||
- Ninja
|
||||
- Visual Studio 2022 or Build Tools 2022 with the **Desktop development with C++** workload and
|
||||
**C++ Clang tools for Windows** component
|
||||
- Qt 6 for MSVC 2022 64-bit, including Concurrent, Network, and Widgets
|
||||
- [glslang](https://github.com/KhronosGroup/glslang/releases) (`glslang` or `glslangValidator`) on `PATH`
|
||||
- Python 3 on `PATH`; the bundled SPIRV-Tools runs it at configure time
|
||||
|
||||
The Microsoft C++ compiler (`cl.exe`) is not supported; use `clang-cl`.
|
||||
|
||||
@@ -162,7 +141,7 @@ git submodule update --init --recursive
|
||||
Configure the project. Replace the Qt path with the version installed on your system:
|
||||
|
||||
```powershell
|
||||
cmake -S . -B _Build/windows -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_PREFIX_PATH="C:/Qt/6.x.x/msvc2022_64"
|
||||
cmake -S src -B _Build/windows -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_PREFIX_PATH="C:/Qt/6.x.x/msvc2022_64"
|
||||
```
|
||||
|
||||
Build the launcher and stage a runnable installation:
|
||||
@@ -177,25 +156,25 @@ The finished application and its runtime dependencies will be placed in
|
||||
|
||||
### Building on Linux
|
||||
|
||||
Install the toolchain and the libraries the bundled SDL3 needs. Without the audio, Wayland, and
|
||||
udev development packages, SDL3 quietly configures itself without those backends, and the resulting
|
||||
build has no working audio or gamepad hotplug support:
|
||||
Install the toolchain and the libraries the bundled SDL2 needs. Without the audio, Wayland and
|
||||
udev development packages SDL2 quietly configures itself without those backends, and the resulting
|
||||
build has no working sound and no gamepad hotplug:
|
||||
|
||||
```bash
|
||||
sudo apt-get install --no-install-recommends \
|
||||
clang lld ninja-build cmake git glslang-tools python3 pkg-config \
|
||||
clang lld ninja-build cmake git glslang-tools \
|
||||
libgl1-mesa-dev libx11-dev libxcursor-dev libxext-dev libxfixes-dev \
|
||||
libxi-dev libxrandr-dev libxss-dev libxtst-dev libxkbcommon-dev \
|
||||
libxi-dev libxrandr-dev libxss-dev libxkbcommon-dev \
|
||||
libasound2-dev libpulse-dev libudev-dev libdbus-1-dev libwayland-dev wayland-protocols
|
||||
```
|
||||
|
||||
Qt 6 (Concurrent, Network, and Widgets) is required for the launcher. Use your distribution's
|
||||
packages (`qt6-base-dev`) or an official Qt installation.
|
||||
Qt 6 (Concurrent, Network, Widgets) is also required — either the distribution packages
|
||||
(`qt6-base-dev`) or an official Qt installation.
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
|
||||
cmake -S . -B _Build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||
cmake -S src -B _Build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DCMAKE_PREFIX_PATH="$Qt6_DIR"
|
||||
|
||||
@@ -204,86 +183,12 @@ cmake --install _Build/linux --prefix _Build/linux/install
|
||||
```
|
||||
|
||||
The install step copies the Qt libraries and plugins next to the binaries, so
|
||||
`_Build/linux/install` runs without a matching system Qt installation. FFmpeg is linked statically
|
||||
from the pinned [KytyPS5 FFmpeg core](https://github.com/KytyPS5/ext-ffmpeg-core)
|
||||
release, including VP9 and WebM support. System FFmpeg packages are not required.
|
||||
|
||||
To build `kyty_emulator` and the `kyty_tests` target without Qt, use a separate build directory:
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
|
||||
cmake -S . -B _Build/linux-no-qt -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DKYTY_BUILD_LAUNCHER=OFF
|
||||
|
||||
cmake --build _Build/linux-no-qt --target kyty_emulator kyty_tests --parallel
|
||||
```
|
||||
`_Build/linux/install` runs without a matching system Qt.
|
||||
|
||||
As on Windows, the MSVC compiler is not used; Clang is required. `cl.exe` is rejected at configure
|
||||
time.
|
||||
|
||||
The CMake source root is the repository root.
|
||||
|
||||
### Building on NixOS
|
||||
|
||||
A development shell provides Clang, CMake, Ninja, Qt 6, the Vulkan headers, and the SDL3 backend
|
||||
libraries. Enter it and configure exactly as on other Linux distributions; the shell exports
|
||||
`CMAKE_PREFIX_PATH` and `QT_PLUGIN_PATH`, so the `-DCMAKE_PREFIX_PATH="$Qt6_DIR"` argument is not
|
||||
needed:
|
||||
|
||||
```bash
|
||||
nix-shell # or: nix develop
|
||||
git submodule update --init --recursive
|
||||
|
||||
cmake -S . -B _Build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
|
||||
|
||||
cmake --build _Build/linux --target launcher --parallel
|
||||
cmake --install _Build/linux --prefix _Build/linux/install
|
||||
```
|
||||
|
||||
The configure step downloads the FFmpeg prebuilts and the `xbyak`, `zydis`, `zstd`, and ZArchive
|
||||
sources, so it needs network access; a fully sandboxed `nix build` would require vendoring those
|
||||
inputs. A Vulkan 1.3 driver must be available at runtime (on NixOS,
|
||||
`hardware.graphics.enable = true`).
|
||||
|
||||
### Building on Fedora and Fedora Atomic (Bazzite, Bluefin, Silverblue)
|
||||
|
||||
On regular Fedora, install dependencies on the host. On Fedora Atomic variants such as Bazzite,
|
||||
Bluefin and Silverblue, use [Distrobox](https://distrobox.it/) and run the install and build steps
|
||||
inside the container. Keep the checkout in your home directory so it is shared with the container.
|
||||
Add `--nvidia` to the create command for hosts using the proprietary NVIDIA driver:
|
||||
|
||||
```bash
|
||||
distrobox create --name kyty-build --image registry.fedoraproject.org/fedora:latest
|
||||
distrobox enter kyty-build
|
||||
```
|
||||
|
||||
Install the toolchain, Qt 6 and SDL3 dependencies:
|
||||
|
||||
```bash
|
||||
sudo dnf install -y \
|
||||
clang lld ninja-build cmake git glslang python3 pkgconf-pkg-config \
|
||||
mesa-libGL-devel libX11-devel libXcursor-devel libXext-devel libXfixes-devel \
|
||||
libXi-devel libXrandr-devel libXScrnSaver-devel libXtst-devel libxkbcommon-devel \
|
||||
alsa-lib-devel pulseaudio-libs-devel systemd-devel dbus-devel \
|
||||
wayland-devel wayland-protocols-devel qt6-qtbase-devel
|
||||
```
|
||||
|
||||
From the repository root, follow the launcher configure, build and install steps in
|
||||
[Building on Linux](#building-on-linux), omitting `-DCMAKE_PREFIX_PATH="$Qt6_DIR"`; CMake finds
|
||||
Fedora's packaged Qt 6 automatically.
|
||||
|
||||
Run `./_Build/linux/install/kyty_launcher` on regular Fedora or inside the container. To launch a
|
||||
container build from the host, leave the container with `exit`, then run from the repository root:
|
||||
|
||||
```bash
|
||||
distrobox enter kyty-build -- ./_Build/linux/install/kyty_launcher
|
||||
```
|
||||
|
||||
Qt libraries and plugins are bundled. You can also launch directly on a Fedora Atomic host
|
||||
if its other system libraries are compatible with the container build.
|
||||
Note that the CMake source root is `src`, not the repository root.
|
||||
|
||||
### Building on macOS
|
||||
|
||||
@@ -295,14 +200,14 @@ Requirements:
|
||||
|
||||
- An Apple Silicon Mac with Rosetta 2 installed (`softwareupdate --install-rosetta`)
|
||||
- Xcode (or the Command Line Tools)
|
||||
- Homebrew packages: `brew install cmake ninja glslang python`
|
||||
- Qt 6 (Concurrent, Network, and Widgets) with x86-64 support. The official Qt installation is
|
||||
- Homebrew packages: `brew install cmake ninja glslang`
|
||||
- Qt 6 (Concurrent, Network, Widgets) with x86-64 support. The official Qt installation is
|
||||
universal and works; Homebrew's Qt is arm64-only and will not link
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
|
||||
cmake -S . -B _Build/macos -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||
cmake -S src -B _Build/macos -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
|
||||
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DCMAKE_PREFIX_PATH="$Qt6_DIR"
|
||||
@@ -312,23 +217,17 @@ cmake --install _Build/macos --prefix _Build/macos/install
|
||||
```
|
||||
|
||||
The build re-signs `kyty_emulator` with the JIT entitlements it needs to execute translated
|
||||
guest code; no manual signing step is required. When the launcher is built, the installation
|
||||
also produces `_Build/macos/install/KytyPS5.app` — double-click to launch the GUI.
|
||||
A flat `kyty_emulator` is kept for CLI usage.
|
||||
guest code; no manual signing step is required.
|
||||
|
||||
Vulkan comes from MoltenVK. Download `MoltenVK-macos.tar` from the
|
||||
[MoltenVK releases](https://github.com/KhronosGroup/MoltenVK/releases), then copy
|
||||
`MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib` next to the flat `kyty_emulator`
|
||||
(and, for the bundle, into `KytyPS5.app/Contents/Frameworks/`) and sign it with an ad hoc signature:
|
||||
`MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib` next to `kyty_emulator` and ad-hoc sign it:
|
||||
|
||||
```bash
|
||||
codesign --force --sign - _Build/macos/install/libMoltenVK.dylib
|
||||
# For the bundle (if present):
|
||||
codesign --force --sign - _Build/macos/install/KytyPS5.app/Contents/Frameworks/libMoltenVK.dylib
|
||||
codesign --force --sign - _Build/macos/install/KytyPS5.app
|
||||
```
|
||||
|
||||
Release archives already include a signed `libMoltenVK.dylib` (both flat and inside the bundle).
|
||||
Release archives already include a signed `libMoltenVK.dylib`.
|
||||
|
||||
### Regression tests
|
||||
|
||||
@@ -345,7 +244,7 @@ Use `_Build/linux` instead of `_Build/windows` for a Linux build.
|
||||
|
||||
A ready-made Visual Studio Code setup is included in [`.vscode`](.vscode). It configures CMake
|
||||
Tools to build the project with Ninja and `clang-cl` and provides launch profiles for both
|
||||
`kyty_launcher.exe` and `kyty_emulator.exe`. It is Windows-only: VS Code settings cannot select a
|
||||
`launcher.exe` and `kyty_emulator.exe`. It is Windows-only: VS Code settings cannot select a
|
||||
compiler per platform, so on Linux configure from the command line as shown above.
|
||||
|
||||
Before using it:
|
||||
@@ -365,54 +264,33 @@ Update your graphics driver before reporting rendering problems.
|
||||
To use the graphical launcher:
|
||||
|
||||
```powershell
|
||||
.\_Build\windows\install\kyty_launcher.exe
|
||||
.\_Build\windows\install\launcher.exe
|
||||
```
|
||||
|
||||
```bash
|
||||
./_Build/linux/install/kyty_launcher
|
||||
```
|
||||
|
||||
```bash
|
||||
open _Build/macos/install/KytyPS5.app # or double-click in Finder
|
||||
./_Build/linux/install/launcher
|
||||
```
|
||||
|
||||
On first launch, add one or more game folders in the global settings. The launcher searches those
|
||||
folders recursively for game directories containing `eboot.bin` and ZArchive (`.zar`) game dumps
|
||||
whose archive root contains `eboot.bin`. Select a detected game and run it from the game list.
|
||||
ZArchive dumps are mounted read-only and streamed directly; they do not need to be extracted first.
|
||||
folders recursively for game directories containing `eboot.bin`. Select a detected game and run it
|
||||
from the game list.
|
||||
|
||||
Official release builds include a built-in updater. Leave **Check for updates on startup** enabled,
|
||||
or click **Check for updates**, then choose **Install Update**. The launcher downloads the package
|
||||
for your platform from GitHub, checks its SHA-256 checksum, and restarts after installation. Close
|
||||
any running games first. Settings, saves, patches, and other files outside the release package are
|
||||
preserved; replacement failures restore the previous files. The installation folder must be writable.
|
||||
If a package cannot be installed automatically, the updater offers the GitHub release page.
|
||||
Source builds and builds from forks do not replace themselves with official releases.
|
||||
|
||||
The emulator can also be started directly with a legally obtained game directory, ELF file, or
|
||||
ZArchive dump:
|
||||
The emulator can also be started directly with a legally obtained game directory or ELF file:
|
||||
|
||||
```powershell
|
||||
.\_Build\windows\install\kyty_emulator.exe --game "D:\Games\ExampleGame"
|
||||
.\_Build\windows\install\kyty_emulator.exe --game "D:\Games\ExampleGame.zar"
|
||||
```
|
||||
|
||||
```bash
|
||||
./_Build/linux/install/kyty_emulator --game "/games/ExampleGame"
|
||||
./_Build/linux/install/kyty_emulator --game "/games/ExampleGame.zar"
|
||||
```
|
||||
|
||||
On macOS, the adjacent flat or app-bundled `libMoltenVK.dylib` is found automatically; no
|
||||
environment variable is required:
|
||||
On macOS, point SDL at the MoltenVK library explicitly; the hardened runtime prevents it from
|
||||
being picked up from the executable's directory:
|
||||
|
||||
```bash
|
||||
./_Build/macos/install/kyty_emulator --game "/games/ExampleGame"
|
||||
```
|
||||
|
||||
To override the Vulkan loader, set `SDL_VULKAN_LIBRARY`:
|
||||
|
||||
```bash
|
||||
SDL_VULKAN_LIBRARY=/path/to/libMoltenVK.dylib ./kyty_emulator --game "/games/ExampleGame"
|
||||
cd _Build/macos/install
|
||||
SDL_VULKAN_LIBRARY="$PWD/libMoltenVK.dylib" ./kyty_emulator --game "/games/ExampleGame"
|
||||
```
|
||||
|
||||
Run `kyty_emulator --help` to see the available graphics, logging, validation, profiling, and
|
||||
@@ -443,6 +321,5 @@ licenses included with those components.
|
||||
|
||||
- [InoriRus/Kyty](https://github.com/InoriRus/Kyty) — KytyPS5 is based on a heavily modified version
|
||||
of the original Kyty project.
|
||||
- [shadps4-emu/shadPS4](https://github.com/shadps4-emu/shadPS4) — reference for understanding PS4
|
||||
memory behavior, GPU resource aliasing, cache coherency,
|
||||
and the AVPlayer implementation.
|
||||
- [shadps4-emu/shadPS4](https://github.com/shadps4-emu/shadPS4) — reference for memory-model
|
||||
understanding and the AVPlayer implementation.
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 8.8 KiB |
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "تم كسب الكأس!",
|
||||
"user.guest": "ضيف"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Trofej získána!",
|
||||
"user.guest": "Host"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Trofæ optjent!",
|
||||
"user.guest": "Gæst"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Trophäe erhalten!",
|
||||
"user.guest": "Gast"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Κερδίσατε ένα τρόπαιο!",
|
||||
"user.guest": "Επισκ."
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Trophy earned!",
|
||||
"user.guest": "Guest"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Trophy earned!",
|
||||
"user.guest": "Guest"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "¡Ganaste un trofeo!",
|
||||
"user.guest": "Invitado"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "¡Ganaste un trofeo!",
|
||||
"user.guest": "Invitado"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Pokaali ansaittu!",
|
||||
"user.guest": "Vieras"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Trophée obtenu !",
|
||||
"user.guest": "Invité"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Trophée obtenu !",
|
||||
"user.guest": "Invité"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Trófea megszerezve!",
|
||||
"user.guest": "Vendég"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Trofi diperoleh!",
|
||||
"user.guest": "Tamu"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Trofeo ottenuto!",
|
||||
"user.guest": "Ospite"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "トロフィーを獲得しました!",
|
||||
"user.guest": "ゲスト"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "트로피를 획득했습니다!",
|
||||
"user.guest": "게스트"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Trofee behaald!",
|
||||
"user.guest": "Gast"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Trofé opptjent!",
|
||||
"user.guest": "Gjest"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Zdobyto trofeum!",
|
||||
"user.guest": "Gość"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Troféu conquistado!",
|
||||
"user.guest": "Convidado"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Troféu conquistado!",
|
||||
"user.guest": "Convidado"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Trofeu câștigat!",
|
||||
"user.guest": "Invitat"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Трофей получен!",
|
||||
"user.guest": "Гость"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Trofé intjänad!",
|
||||
"user.guest": "Gäst"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "ได้รับถ้วยรางวัลแล้ว!",
|
||||
"user.guest": "แขก"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Kupa kazanıldı!",
|
||||
"user.guest": "Misafir"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "Đã nhận cúp!",
|
||||
"user.guest": "Khách"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "已获得奖杯!",
|
||||
"user.guest": "访客"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trophy.toast.earned": "已獲得獎盃!",
|
||||
"user.guest": "訪客"
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
# Notification localization
|
||||
|
||||
Trophy notification subtitles use the configured console language. Trophy names still come
|
||||
from the game's metadata. The launcher trophy dialogs are unchanged.
|
||||
|
||||
Edit `assets/localization/<locale>.json`, using flat UTF-8 string values:
|
||||
|
||||
```json
|
||||
{
|
||||
"trophy.toast.earned": "Trophy earned!"
|
||||
}
|
||||
```
|
||||
|
||||
CMake converts the catalogs into C++ byte arrays in the build directory. The emulator parses
|
||||
the embedded catalogs once and falls back to `en-US.json` for missing translations. No language
|
||||
files or translation service are needed at runtime. Rebuild after editing a catalog.
|
||||
|
||||
For local testing, rebuild `kyty_emulator`, choose a game's Console Language in the launcher,
|
||||
and unlock a trophy with notifications enabled. Installed system fonts provide non-Latin
|
||||
glyphs; the selected language needs an appropriate font on the host.
|
||||
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 326 KiB |
|
Before Width: | Height: | Size: 2.2 MiB After Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 253 KiB |
|
After Width: | Height: | Size: 2.4 MiB |
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1789426278,
|
||||
"narHash": "sha256-GnotcvKtbTMnoVx6G4E7ZdUX6487Dky74dqq3pU7iRk=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "efe6f071ede9d21c37462d2d6682d5e670099684",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
description = "KytyPS5 development shell";
|
||||
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
outputs = { self, nixpkgs }:
|
||||
let
|
||||
systems = [ "x86_64-linux" ];
|
||||
forAllSystems = nixpkgs.lib.genAttrs systems;
|
||||
in
|
||||
{
|
||||
devShells = forAllSystems (system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
in
|
||||
{
|
||||
default = import ./nix/devshell.nix { inherit pkgs; };
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
{ pkgs }:
|
||||
|
||||
let
|
||||
# Build-time dependencies for the bundled SDL3 audio, video and input backends.
|
||||
buildDeps = with pkgs; [
|
||||
qt6.qtbase # Concurrent, Network, Widgets (launcher)
|
||||
vulkan-headers
|
||||
vulkan-loader
|
||||
vulkan-tools
|
||||
vulkan-validation-layers
|
||||
libglvnd
|
||||
# X11 / input
|
||||
libX11
|
||||
libXcursor
|
||||
libXext
|
||||
libXfixes
|
||||
libXi
|
||||
libXrandr
|
||||
libXScrnSaver
|
||||
libXtst
|
||||
libxkbcommon
|
||||
# Wayland
|
||||
wayland
|
||||
wayland-protocols
|
||||
libdecor
|
||||
# audio + device/dbus
|
||||
alsa-lib
|
||||
libpulseaudio
|
||||
systemd # libudev
|
||||
dbus
|
||||
];
|
||||
|
||||
# Libraries the built binaries need at runtime.
|
||||
runtimeLibs = with pkgs; [
|
||||
stdenv.cc.cc.lib # libstdc++ / libgcc_s
|
||||
libgcc.lib
|
||||
qt6.qtbase # libQt6Concurrent/Network/Widgets
|
||||
vulkan-loader
|
||||
libglvnd
|
||||
alsa-lib
|
||||
libpulseaudio
|
||||
libX11
|
||||
libXcursor
|
||||
libXext
|
||||
libXfixes
|
||||
libXi
|
||||
libXrandr
|
||||
libXScrnSaver
|
||||
libXtst
|
||||
libxkbcommon
|
||||
wayland
|
||||
libdecor
|
||||
systemd
|
||||
dbus
|
||||
];
|
||||
in
|
||||
pkgs.mkShell {
|
||||
name = "kytyps5";
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
clang
|
||||
lld
|
||||
cmake
|
||||
ninja
|
||||
pkg-config
|
||||
git
|
||||
glslang # provides glslangValidator for the bundled shaders
|
||||
];
|
||||
|
||||
buildInputs = buildDeps;
|
||||
|
||||
shellHook = ''
|
||||
export CMAKE_PREFIX_PATH="${pkgs.qt6.qtbase}:''${CMAKE_PREFIX_PATH:-}"
|
||||
export QT_PLUGIN_PATH="${pkgs.qt6.qtbase}/lib/qt-6/plugins:${pkgs.qt6.qtbase}/lib/qt6/plugins:''${QT_PLUGIN_PATH:-}"
|
||||
export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath runtimeLibs}:''${LD_LIBRARY_PATH:-}"
|
||||
|
||||
if [ -z "''${KYTY_SHELL_QUIET:-}" ]; then
|
||||
echo "KytyPS5 dev shell"
|
||||
echo " cmake -S . -B _Build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release \\"
|
||||
echo " -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++"
|
||||
echo " cmake --build _Build/linux --target launcher --parallel"
|
||||
echo " cmake --install _Build/linux --prefix _Build/linux/install"
|
||||
fi
|
||||
'';
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
let
|
||||
pkgs = import <nixpkgs> { };
|
||||
in
|
||||
import ./nix/devshell.nix { inherit pkgs; }
|
||||
@@ -0,0 +1,584 @@
|
||||
cmake_minimum_required(VERSION 3.12...4.3)
|
||||
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.0)
|
||||
set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "Minimum policy version for bundled dependencies")
|
||||
endif()
|
||||
|
||||
project(Kyty)
|
||||
|
||||
if(CMAKE_SYSTEM_NAME MATCHES ".*Linux")
|
||||
set(LINUX TRUE)
|
||||
endif()
|
||||
|
||||
if (NOT (WIN32 OR LINUX OR APPLE))
|
||||
message(FATAL_ERROR "only Windows, Linux, and macOS builds are supported")
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
|
||||
|
||||
include(utils.cmake)
|
||||
include(CTest)
|
||||
|
||||
option(KYTY_ENABLE_CLANG_TIDY "Run clang-tidy checks during builds" OFF)
|
||||
|
||||
set(KYTY_THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty")
|
||||
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
|
||||
include(TestBigEndian)
|
||||
|
||||
if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
message(FATAL_ERROR "only 64-bit builds are supported")
|
||||
endif()
|
||||
|
||||
test_big_endian(BIG_ENDIAN)
|
||||
if(BIG_ENDIAN)
|
||||
set(KYTY_ENDIAN KYTY_ENDIAN_BIG)
|
||||
else()
|
||||
set(KYTY_ENDIAN KYTY_ENDIAN_LITTLE)
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
set(KYTY_BUILD KYTY_BUILD_DEBUG)
|
||||
else()
|
||||
set(KYTY_BUILD KYTY_BUILD_RELEASE)
|
||||
endif()
|
||||
|
||||
if(LINUX OR APPLE)
|
||||
# macOS rides the POSIX/Linux code paths until it gets a dedicated platform
|
||||
set(KYTY_PLATFORM KYTY_PLATFORM_LINUX)
|
||||
else()
|
||||
set(KYTY_PLATFORM KYTY_PLATFORM_WINDOWS)
|
||||
endif()
|
||||
|
||||
set(CLANG FALSE)
|
||||
set(GCC FALSE)
|
||||
set(KYTY_CLANG_CL FALSE)
|
||||
get_filename_component(KYTY_CXX_COMPILER_NAME "${CMAKE_CXX_COMPILER}" NAME_WE)
|
||||
string(TOLOWER "${KYTY_CXX_COMPILER_NAME}" KYTY_CXX_COMPILER_NAME)
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "^(Apple)?Clang$")
|
||||
set(CLANG TRUE)
|
||||
set(KYTY_COMPILER CLANG)
|
||||
if(WIN32 AND KYTY_CXX_COMPILER_NAME STREQUAL "clang-cl")
|
||||
set(KYTY_CLANG_CL TRUE)
|
||||
set(KYTY_LINKER LLD_LINK)
|
||||
elseif(APPLE)
|
||||
set(KYTY_LINKER LD64) # Apple's default linker; lld flags don't apply
|
||||
else()
|
||||
set(KYTY_LINKER LLD)
|
||||
endif()
|
||||
if ((CMAKE_AR MATCHES "llvm") AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0.0) AND (CMAKE_CXX_COMPILER MATCHES "winlibs"))
|
||||
string(REPLACE "llvm-ar.exe" "ar.exe" CMAKE_AR ${CMAKE_AR}) # llvm-ar.exe 11.0.0 from winlibs is broken
|
||||
endif()
|
||||
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
set(GCC TRUE)
|
||||
set(KYTY_COMPILER GCC)
|
||||
set(KYTY_LINKER LD)
|
||||
else()
|
||||
message(FATAL_ERROR "only Clang and GCC are supported; found ${CMAKE_CXX_COMPILER_ID}")
|
||||
endif()
|
||||
|
||||
set(KYTY_PROJECT_NAME "Emulator" CACHE STRING "Project name suffix")
|
||||
|
||||
string(TOLOWER ${KYTY_COMPILER} KYTY_COMPILER_ID)
|
||||
string(TOLOWER ${KYTY_LINKER} KYTY_LINKER_ID)
|
||||
|
||||
if (CLANG AND (KYTY_LINKER STREQUAL LLD))
|
||||
set(KYTY_LD_OPTIONS "-fuse-ld=lld")
|
||||
endif()
|
||||
|
||||
if (KYTY_LINKER STREQUAL LD AND NOT LINUX)
|
||||
set(KYTY_LD_OPTIONS "-Wl,--image-base=0x100000000000")
|
||||
endif()
|
||||
|
||||
project(Kyty${KYTY_PROJECT_NAME}${CMAKE_BUILD_TYPE}${KYTY_COMPILER} VERSION 0.2.2)
|
||||
|
||||
include_directories(
|
||||
${KYTY_THIRD_PARTY_DIR}/gtest/include
|
||||
${KYTY_THIRD_PARTY_DIR}/gtest
|
||||
${KYTY_THIRD_PARTY_DIR}/fmt/include
|
||||
${KYTY_THIRD_PARTY_DIR}/magic_enum/include/magic_enum
|
||||
${PROJECT_BINARY_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
set(KYTY_VERSION "${PROJECT_VERSION}")
|
||||
|
||||
configure_file(
|
||||
${PROJECT_SOURCE_DIR}/cmake_config.h.in
|
||||
${PROJECT_BINARY_DIR}/cmake_config.h
|
||||
)
|
||||
|
||||
find_package(Git)
|
||||
|
||||
add_custom_target( KytyGitVersion
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-D INPUT_FILE=${CMAKE_CURRENT_SOURCE_DIR}/kytyGitVersion.h.in
|
||||
-D OUTPUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/kytyGitVersion.h
|
||||
-D GIT_EXECUTABLE=${GIT_EXECUTABLE}
|
||||
-D GIT_WORKING_DIRECTORY=${CMAKE_CURRENT_SOURCE_DIR}
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/generate_version.cmake
|
||||
COMMENT "Generate kytyGitVersion.h"
|
||||
)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 12.0.0)
|
||||
list(APPEND KYTY_IWYU
|
||||
kyty_emulator
|
||||
common
|
||||
#launcher
|
||||
)
|
||||
list(APPEND KYTY_CLANG_TIDY
|
||||
kyty_emulator
|
||||
#common
|
||||
launcher
|
||||
)
|
||||
endif()
|
||||
|
||||
option(KYTY_BUILD_LAUNCHER "Build Qt launcher" ON)
|
||||
|
||||
config_compiler_and_linker()
|
||||
|
||||
add_subdirectory("${KYTY_THIRD_PARTY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/3rdparty")
|
||||
add_subdirectory(common)
|
||||
|
||||
file(GLOB kyty_emulator_src CONFIGURE_DEPENDS
|
||||
libs/*.cpp
|
||||
libs/*.h
|
||||
graphics/*.cpp
|
||||
graphics/*.h
|
||||
graphics/guest_gpu/*.cpp
|
||||
graphics/guest_gpu/*.h
|
||||
graphics/guest_gpu/command_processor/*.cpp
|
||||
graphics/guest_gpu/command_processor/*.h
|
||||
graphics/host_gpu/*.cpp
|
||||
graphics/host_gpu/*.h
|
||||
graphics/host_gpu/renderer/*.cpp
|
||||
graphics/host_gpu/renderer/*.h
|
||||
graphics/host_gpu/renderer/cache/*.cpp
|
||||
graphics/host_gpu/renderer/cache/*.h
|
||||
graphics/host_gpu/renderer/image/*.cpp
|
||||
graphics/host_gpu/renderer/image/*.h
|
||||
graphics/host_gpu/renderer/pipeline/*.cpp
|
||||
graphics/host_gpu/renderer/pipeline/*.h
|
||||
graphics/shader/*.cpp
|
||||
graphics/shader/*.h
|
||||
graphics/shader/recompiler/*.cpp
|
||||
graphics/shader/recompiler/*.h
|
||||
graphics/shader/recompiler/cfg/*.cpp
|
||||
graphics/shader/recompiler/cfg/*.h
|
||||
graphics/shader/recompiler/decompiler/*.cpp
|
||||
graphics/shader/recompiler/decompiler/*.h
|
||||
graphics/shader/recompiler/emitter/*.cpp
|
||||
graphics/shader/recompiler/emitter/*.h
|
||||
graphics/shader/recompiler/ir/*.cpp
|
||||
graphics/shader/recompiler/ir/*.h
|
||||
graphics/presentation/*.cpp
|
||||
graphics/presentation/*.h
|
||||
graphics/presentation/window/*.cpp
|
||||
graphics/presentation/window/*.h
|
||||
kernel/*.cpp
|
||||
kernel/*.h
|
||||
loader/*.cpp
|
||||
loader/*.h
|
||||
)
|
||||
|
||||
find_program(KYTY_GLSLANG_VALIDATOR glslangValidator REQUIRED)
|
||||
set(gpu_tiler_shader_dir "${CMAKE_CURRENT_SOURCE_DIR}/graphics/host_gpu/shaders")
|
||||
set(gpu_tiler_generated_dir "${PROJECT_BINARY_DIR}/gpu_tiler_shaders")
|
||||
set(gpu_tiler_shader_names
|
||||
standard256
|
||||
standard4
|
||||
standard4_3d
|
||||
standard64
|
||||
standard64_3d
|
||||
prt
|
||||
prt_3d
|
||||
render_target
|
||||
depth
|
||||
promote_d16
|
||||
demote_d16
|
||||
swap_bgra16
|
||||
)
|
||||
file(GLOB gpu_tiler_shader_includes CONFIGURE_DEPENDS "${gpu_tiler_shader_dir}/gpu_tiler_*.inc")
|
||||
foreach(shader_name IN LISTS gpu_tiler_shader_names)
|
||||
set(shader_source "${gpu_tiler_shader_dir}/gpu_tiler_${shader_name}.comp")
|
||||
set(shader_spv "${gpu_tiler_generated_dir}/gpu_tiler_${shader_name}.spv")
|
||||
set(shader_header "${gpu_tiler_generated_dir}/gpu_tiler_${shader_name}_spv.h")
|
||||
string(TOUPPER "GPU_TILER_${shader_name}_SPV" shader_symbol)
|
||||
add_custom_command(
|
||||
OUTPUT "${shader_header}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_tiler_generated_dir}"
|
||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.0 -Os
|
||||
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
|
||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
|
||||
-DSYMBOL=${shader_symbol} -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
|
||||
DEPENDS "${shader_source}" ${gpu_tiler_shader_includes}
|
||||
VERBATIM
|
||||
)
|
||||
list(APPEND gpu_tiler_shader_headers "${shader_header}")
|
||||
endforeach()
|
||||
list(APPEND kyty_emulator_src ${gpu_tiler_shader_headers})
|
||||
|
||||
set(gpu_blit_generated_dir "${PROJECT_BINARY_DIR}/gpu_blit_shaders")
|
||||
set(gpu_blit_shader_sources
|
||||
"${gpu_tiler_shader_dir}/gpu_blit_fs_triangle.vert"
|
||||
"${gpu_tiler_shader_dir}/gpu_blit_color_to_ms_depth.frag"
|
||||
)
|
||||
foreach(shader_source IN LISTS gpu_blit_shader_sources)
|
||||
get_filename_component(shader_name "${shader_source}" NAME_WE)
|
||||
set(shader_spv "${gpu_blit_generated_dir}/${shader_name}.spv")
|
||||
set(shader_header "${gpu_blit_generated_dir}/${shader_name}_spv.h")
|
||||
string(TOUPPER "${shader_name}_SPV" shader_symbol)
|
||||
add_custom_command(
|
||||
OUTPUT "${shader_header}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_blit_generated_dir}"
|
||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.0 -Os
|
||||
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
|
||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
|
||||
-DSYMBOL=${shader_symbol} -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
|
||||
DEPENDS "${shader_source}"
|
||||
VERBATIM
|
||||
)
|
||||
list(APPEND gpu_blit_shader_headers "${shader_header}")
|
||||
endforeach()
|
||||
list(APPEND kyty_emulator_src ${gpu_blit_shader_headers})
|
||||
|
||||
list(APPEND kyty_emulator_src
|
||||
emulator.h
|
||||
emulator.cpp
|
||||
)
|
||||
list(REMOVE_DUPLICATES kyty_emulator_src)
|
||||
|
||||
if (KYTY_CLANG_CL)
|
||||
set_source_files_properties(${kyty_emulator_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack -Wno-deprecated-declarations -D_TIMESPEC_DEFINED")
|
||||
endif()
|
||||
|
||||
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL2-static xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9)
|
||||
|
||||
# Linux system libraries required by the static FFmpeg archive.
|
||||
if(LINUX)
|
||||
find_package(Threads REQUIRED)
|
||||
list(APPEND kyty_emulator_link_libraries m ${CMAKE_DL_LIBS} Threads::Threads)
|
||||
|
||||
# Optional FFmpeg dependencies.
|
||||
find_package(ZLIB)
|
||||
if(ZLIB_FOUND)
|
||||
list(APPEND kyty_emulator_link_libraries ZLIB::ZLIB)
|
||||
endif()
|
||||
find_library(KYTY_BZ2_LIBRARY bz2)
|
||||
if(KYTY_BZ2_LIBRARY)
|
||||
list(APPEND kyty_emulator_link_libraries ${KYTY_BZ2_LIBRARY})
|
||||
endif()
|
||||
find_library(KYTY_LZMA_LIBRARY lzma)
|
||||
if(KYTY_LZMA_LIBRARY)
|
||||
list(APPEND kyty_emulator_link_libraries ${KYTY_LZMA_LIBRARY})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(inc_headers
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${KYTY_THIRD_PARTY_DIR}/SDL2/include
|
||||
${KYTY_THIRD_PARTY_DIR}/VulkanMemoryAllocator/include
|
||||
${KYTY_THIRD_PARTY_DIR}/SPIRV-Tools/include
|
||||
${KYTY_THIRD_PARTY_DIR}/fmt/include
|
||||
${KYTY_THIRD_PARTY_DIR}/xxhash
|
||||
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
|
||||
${KYTY_THIRD_PARTY_DIR}/stb
|
||||
)
|
||||
|
||||
if (KYTY_CLANG_CL)
|
||||
list(APPEND kyty_emulator_link_libraries winpthread)
|
||||
list(APPEND inc_headers
|
||||
${KYTY_THIRD_PARTY_DIR}/winpthread/include
|
||||
)
|
||||
endif()
|
||||
|
||||
list(APPEND check_headers
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
function(add_kyty_full_emulator_test target source)
|
||||
add_executable(${target} EXCLUDE_FROM_ALL ${source} ${kyty_emulator_src})
|
||||
target_link_libraries(${target} ${kyty_emulator_link_libraries})
|
||||
target_include_directories(${target} PRIVATE ${inc_headers})
|
||||
if (WIN32)
|
||||
target_link_libraries(${target} iphlpapi)
|
||||
endif()
|
||||
if (KYTY_CLANG_CL)
|
||||
target_link_libraries(${target} onecore)
|
||||
add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" $<TARGET_FILE_DIR:${target}>/libwinpthread-1.dll)
|
||||
endif()
|
||||
# The macOS x86_64 guest address space needs its .zerofill segments anchored
|
||||
# by linker flags, or the kernel kills the binary on load (posix_spawn EIO).
|
||||
configure_macos_guest_address_space(${target})
|
||||
endfunction()
|
||||
|
||||
function(configure_macos_guest_address_space target)
|
||||
if(APPLE AND (CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64" OR
|
||||
(NOT CMAKE_OSX_ARCHITECTURES AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64)$")))
|
||||
target_sources(${target} PRIVATE kernel/macosGuestAddressSpace.cpp)
|
||||
target_compile_definitions(${target} PRIVATE KYTY_LINKED_GUEST_ADDRESS_SPACE=1)
|
||||
target_link_options(${target} PRIVATE
|
||||
-Wl,-ld_classic,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x40000,-segaddr,SYSTEM_MANAGED,0x40000,-segaddr,SYSTEM_RESERVED,0x7ffffc000,-segaddr,USER_AREA,0x7000000000,-image_base,0x700000000000)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
add_kyty_full_emulator_test(shader_cfg_tests ../tests/shaderCfgTests.cpp)
|
||||
|
||||
add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
|
||||
../tests/ScalarProvenanceTests.cpp
|
||||
graphics/host_gpu/hostMemory.cpp
|
||||
graphics/shader/recompiler/ir/ReadLaneElimination.cpp
|
||||
graphics/shader/recompiler/ir/ScalarProvenance.cpp
|
||||
graphics/shader/recompiler/ir/SrtWalker.cpp
|
||||
)
|
||||
target_link_libraries(scalar_provenance_tests fmt::fmt)
|
||||
target_include_directories(scalar_provenance_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(page_manager_tests EXCLUDE_FROM_ALL
|
||||
../tests/PageManagerTests.cpp
|
||||
graphics/host_gpu/pageManager.cpp
|
||||
)
|
||||
target_include_directories(page_manager_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(bit_array_tests EXCLUDE_FROM_ALL
|
||||
../tests/BitArrayTests.cpp
|
||||
)
|
||||
target_include_directories(bit_array_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(memory_tracker_tests EXCLUDE_FROM_ALL
|
||||
../tests/MemoryTrackerTests.cpp
|
||||
graphics/host_gpu/pageManager.cpp
|
||||
graphics/host_gpu/memoryTracker.cpp
|
||||
)
|
||||
target_link_libraries(memory_tracker_tests fmt::fmt common)
|
||||
target_include_directories(memory_tracker_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(shader_vertex_metadata_tests EXCLUDE_FROM_ALL
|
||||
../tests/ShaderVertexMetadataTests.cpp
|
||||
graphics/host_gpu/hostMemory.cpp
|
||||
graphics/shader/shaderVertexMetadata.cpp
|
||||
)
|
||||
target_include_directories(shader_vertex_metadata_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(shader_stage_runtime_tests EXCLUDE_FROM_ALL
|
||||
../tests/ShaderStageRuntimeTests.cpp
|
||||
graphics/guest_gpu/gpu_format.cpp
|
||||
graphics/host_gpu/hostMemory.cpp
|
||||
graphics/shader/shaderStageRuntime.cpp
|
||||
graphics/shader/recompiler/ir/ResourceMaterialization.cpp
|
||||
graphics/shader/recompiler/ir/ScalarProvenance.cpp
|
||||
graphics/shader/recompiler/ir/SrtWalker.cpp
|
||||
)
|
||||
target_link_libraries(shader_stage_runtime_tests fmt::fmt)
|
||||
target_include_directories(shader_stage_runtime_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
|
||||
../tests/ResourceTrackingTests.cpp
|
||||
graphics/guest_gpu/gpu_format.cpp
|
||||
graphics/host_gpu/hostMemory.cpp
|
||||
graphics/shader/recompiler/ir/ScalarProvenance.cpp
|
||||
graphics/shader/recompiler/ir/SrtWalker.cpp
|
||||
graphics/shader/recompiler/ir/SrtPatcher.cpp
|
||||
graphics/shader/recompiler/ir/ResourceTracking.cpp
|
||||
graphics/shader/recompiler/ir/ResourceMaterialization.cpp
|
||||
graphics/shader/recompiler/ir/ShaderInfoCollection.cpp
|
||||
graphics/shader/recompiler/ir/BindingLayout.cpp
|
||||
)
|
||||
target_link_libraries(resource_tracking_tests fmt::fmt)
|
||||
target_include_directories(resource_tracking_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(resource_mutex_tests EXCLUDE_FROM_ALL
|
||||
../tests/ResourceMutexTests.cpp
|
||||
graphics/host_gpu/renderer/cache/resourceMutex.cpp
|
||||
)
|
||||
target_link_libraries(resource_mutex_tests common)
|
||||
target_include_directories(resource_mutex_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
|
||||
../tests/AudioOut2PortTests.cpp
|
||||
libs/libAudio2.cpp
|
||||
loader/timer.cpp
|
||||
)
|
||||
target_link_libraries(audio_out2_port_tests common fmt::fmt)
|
||||
target_include_directories(audio_out2_port_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(event_queue_lifetime_tests EXCLUDE_FROM_ALL
|
||||
../tests/EventQueueLifetimeTests.cpp
|
||||
kernel/eventQueue.cpp
|
||||
loader/timer.cpp
|
||||
)
|
||||
target_link_libraries(event_queue_lifetime_tests common fmt::fmt)
|
||||
target_include_directories(event_queue_lifetime_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(image_page_table_tests EXCLUDE_FROM_ALL
|
||||
../tests/ImagePageTableTests.cpp
|
||||
)
|
||||
target_link_libraries(image_page_table_tests common fmt::fmt)
|
||||
target_include_directories(image_page_table_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_kyty_full_emulator_test(shader_recompiler_compute_tests ../tests/ShaderRecompilerComputeTests.cpp)
|
||||
|
||||
set(gpu_test_generated_dir "${PROJECT_BINARY_DIR}/gpu_test_shaders")
|
||||
set(gpu_test_ms_depth_source
|
||||
"${gpu_tiler_shader_dir}/gpu_test_ms_depth.comp")
|
||||
set(gpu_test_ms_depth_spv
|
||||
"${gpu_test_generated_dir}/gpu_test_ms_depth.spv")
|
||||
set(gpu_test_ms_depth_header
|
||||
"${gpu_test_generated_dir}/gpu_test_ms_depth_spv.h")
|
||||
add_custom_command(
|
||||
OUTPUT "${gpu_test_ms_depth_header}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_test_generated_dir}"
|
||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.0 -Os
|
||||
-o "${gpu_test_ms_depth_spv}" "${gpu_test_ms_depth_source}"
|
||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${gpu_test_ms_depth_spv}
|
||||
-DOUTPUT=${gpu_test_ms_depth_header} -DSYMBOL=GPU_TEST_MS_DEPTH_SPV
|
||||
-P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
|
||||
DEPENDS "${gpu_test_ms_depth_source}"
|
||||
VERBATIM
|
||||
)
|
||||
target_sources(shader_recompiler_compute_tests PRIVATE
|
||||
"${gpu_test_ms_depth_header}")
|
||||
|
||||
add_kyty_full_emulator_test(virtual_memory_allocation_tests ../tests/VirtualMemoryAllocationTests.cpp)
|
||||
target_compile_definitions(virtual_memory_allocation_tests PRIVATE
|
||||
KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1)
|
||||
|
||||
# These tests use exceptions.
|
||||
if(NOT KYTY_CLANG_CL)
|
||||
foreach(kyty_exception_test scalar_provenance_tests resource_tracking_tests
|
||||
virtual_memory_allocation_tests)
|
||||
target_compile_options(${kyty_exception_test} PRIVATE -fexceptions)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test(NAME shader_cfg COMMAND $<TARGET_FILE:shader_cfg_tests>)
|
||||
add_test(NAME scalar_provenance COMMAND $<TARGET_FILE:scalar_provenance_tests>)
|
||||
add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>)
|
||||
add_test(NAME memory_tracker COMMAND $<TARGET_FILE:memory_tracker_tests>)
|
||||
add_test(NAME page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
|
||||
add_test(NAME bit_array COMMAND $<TARGET_FILE:bit_array_tests>)
|
||||
add_test(NAME shader_vertex_metadata COMMAND $<TARGET_FILE:shader_vertex_metadata_tests>)
|
||||
add_test(NAME shader_stage_runtime COMMAND $<TARGET_FILE:shader_stage_runtime_tests>)
|
||||
add_test(NAME resource_tracking COMMAND $<TARGET_FILE:resource_tracking_tests>)
|
||||
add_test(NAME resource_mutex COMMAND $<TARGET_FILE:resource_mutex_tests>)
|
||||
add_test(NAME event_queue_lifetime COMMAND $<TARGET_FILE:event_queue_lifetime_tests>)
|
||||
add_test(NAME audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_tests>)
|
||||
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
|
||||
add_test(NAME virtual_memory_allocation
|
||||
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests>)
|
||||
add_test(NAME command_scheduler_timeline
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --scheduler-only)
|
||||
add_test(NAME stream_buffer_ring
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
|
||||
add_test(NAME gpu_command_lane
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
|
||||
add_test(NAME gpu_tiler
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-tiler-only)
|
||||
|
||||
add_test(NAME texture_cache_layered_image
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --layered-image-only)
|
||||
add_test(NAME texture_cache_image_views
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-view-cache-only)
|
||||
add_test(NAME texture_cache_storage_sampled
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --storage-sampled-only)
|
||||
add_test(NAME texture_cache_depth_readback
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --depth-readback-only)
|
||||
add_test(NAME buffer_cache_dirty_gc
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-gc-only)
|
||||
|
||||
if(WIN32)
|
||||
# These tests still depend on the Windows multisample-depth path.
|
||||
add_test(NAME texture_cache_image_overlap
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-overlap-only)
|
||||
add_test(NAME texture_cache_htile_clear
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --htile-clear-only)
|
||||
add_test(NAME buffer_cache_ranges
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-range-only)
|
||||
endif()
|
||||
|
||||
add_custom_target(kyty_tests DEPENDS
|
||||
shader_cfg_tests
|
||||
scalar_provenance_tests
|
||||
image_page_table_tests
|
||||
memory_tracker_tests
|
||||
page_manager_tests
|
||||
bit_array_tests
|
||||
shader_vertex_metadata_tests
|
||||
shader_stage_runtime_tests
|
||||
resource_tracking_tests
|
||||
resource_mutex_tests
|
||||
event_queue_lifetime_tests
|
||||
shader_recompiler_compute_tests
|
||||
virtual_memory_allocation_tests
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
add_executable(kyty_emulator main.cpp ${kyty_emulator_src})
|
||||
configure_macos_guest_address_space(kyty_emulator)
|
||||
|
||||
target_link_libraries(kyty_emulator ${kyty_emulator_link_libraries})
|
||||
if (WIN32)
|
||||
target_link_libraries(kyty_emulator opengl32)
|
||||
target_link_libraries(kyty_emulator iphlpapi)
|
||||
target_link_libraries(kyty_emulator setupapi)
|
||||
target_link_libraries(kyty_emulator ws2_32)
|
||||
endif()
|
||||
if (KYTY_CLANG_CL)
|
||||
target_link_libraries(kyty_emulator onecore)
|
||||
endif()
|
||||
if (CLANG AND NOT KYTY_CLANG_CL)
|
||||
target_link_libraries(kyty_emulator pthread)
|
||||
endif()
|
||||
# dlopen/dlsym/dladdr for RenderDoc.
|
||||
target_link_libraries(kyty_emulator ${CMAKE_DL_LIBS})
|
||||
target_include_directories(kyty_emulator PRIVATE ${inc_headers})
|
||||
|
||||
clang_tidy_check(kyty_emulator "" "${check_headers}" "${inc_headers}")
|
||||
|
||||
include_what_you_use(kyty_emulator "${inc_headers}")
|
||||
|
||||
set(KYTY_EMULATOR_MAP_NAME "kyty_emulator_${KYTY_COMPILER_ID}_${KYTY_LINKER_ID}.map")
|
||||
|
||||
set(KYTY_EMULATOR_MAP_LINK_PATH "${CMAKE_CURRENT_BINARY_DIR}/${KYTY_EMULATOR_MAP_NAME}")
|
||||
set(KYTY_EMULATOR_PDB_LINK_PATH "${CMAKE_CURRENT_BINARY_DIR}/kyty_emulator.pdb")
|
||||
|
||||
if(KYTY_CLANG_CL)
|
||||
target_link_options(kyty_emulator PRIVATE
|
||||
"/DYNAMICBASE:NO"
|
||||
"/DEBUG:FULL"
|
||||
"/PDB:${KYTY_EMULATOR_PDB_LINK_PATH}"
|
||||
"/lldmap:${KYTY_EMULATOR_MAP_LINK_PATH}"
|
||||
)
|
||||
add_custom_command(TARGET kyty_emulator POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" $<TARGET_FILE_DIR:kyty_emulator>/libwinpthread-1.dll)
|
||||
elseif(WIN32 OR LINUX)
|
||||
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS} -Wl,-Map=${KYTY_EMULATOR_MAP_LINK_PATH}")
|
||||
endif()
|
||||
|
||||
add_dependencies(kyty_emulator KytyGitVersion)
|
||||
|
||||
if(APPLE)
|
||||
# The emulator writes x86-64 trampolines/PLT stubs into memory and executes them, and
|
||||
# runs guest code under Rosetta. That requires the JIT / unsigned-executable-memory
|
||||
# entitlements, so re-sign the binary after every link (an unsigned/relinked binary
|
||||
# reverts to the hardened defaults and aborts when it first executes written code).
|
||||
add_custom_command(TARGET kyty_emulator POST_BUILD
|
||||
COMMAND codesign -s - --force --options runtime
|
||||
--entitlements "${CMAKE_CURRENT_SOURCE_DIR}/macos_jit.entitlements"
|
||||
$<TARGET_FILE:kyty_emulator>
|
||||
COMMENT "Codesign kyty_emulator with JIT entitlements (macOS)")
|
||||
endif()
|
||||
|
||||
install(TARGETS kyty_emulator DESTINATION .)
|
||||
if(KYTY_BUILD_LAUNCHER)
|
||||
add_subdirectory(launcher)
|
||||
endif()
|
||||
if(KYTY_CLANG_CL)
|
||||
install(FILES "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" DESTINATION .)
|
||||
endif()
|
||||
@@ -1,8 +1,4 @@
|
||||
#define KYTY_VERSION "@KYTY_VERSION@"
|
||||
#define KYTY_BUILD_REPOSITORY "@KYTY_BUILD_REPOSITORY@"
|
||||
#define KYTY_RELEASE_TAG "@KYTY_RELEASE_TAG@"
|
||||
#cmakedefine KYTY_OFFICIAL_BUILD
|
||||
#cmakedefine KYTY_FORK_BUILD
|
||||
#define KYTY_ENDIAN @KYTY_ENDIAN@
|
||||
#define KYTY_PLATFORM @KYTY_PLATFORM@
|
||||
#define KYTY_COMPILER KYTY_COMPILER_@KYTY_COMPILER@
|
||||
|
||||
@@ -12,23 +12,9 @@ endif()
|
||||
|
||||
add_library(common STATIC ${common_src} ${common_headers})
|
||||
|
||||
set(localization_dir "${PROJECT_SOURCE_DIR}/assets/localization")
|
||||
file(GLOB localization_catalogs CONFIGURE_DEPENDS "${localization_dir}/*.json")
|
||||
set(localization_data "${CMAKE_CURRENT_BINARY_DIR}/localizationData.h")
|
||||
add_custom_command(
|
||||
OUTPUT "${localization_data}"
|
||||
COMMAND ${CMAKE_COMMAND} "-DINPUTS=${localization_catalogs}" "-DOUTPUT=${localization_data}"
|
||||
-P "${KYTY_SOURCE_DIR}/embed_localization.cmake"
|
||||
DEPENDS ${localization_catalogs} "${KYTY_SOURCE_DIR}/embed_localization.cmake"
|
||||
COMMENT "Embed notification localization catalogs"
|
||||
VERBATIM
|
||||
)
|
||||
target_sources(common PRIVATE "${localization_data}")
|
||||
|
||||
target_include_directories(common
|
||||
PUBLIC ${KYTY_SOURCE_DIR}
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
|
||||
)
|
||||
|
||||
@@ -38,15 +24,8 @@ target_link_libraries(common
|
||||
Tracy::TracyClient
|
||||
PRIVATE
|
||||
cpuinfo
|
||||
kyty_git_version
|
||||
spdlog::spdlog
|
||||
ZArchive::zarchive
|
||||
nlohmann_json::nlohmann_json
|
||||
)
|
||||
if(APPLE)
|
||||
# hostStats.cpp reads GPU utilization from the IOAccelerator registry entries.
|
||||
target_link_libraries(common PRIVATE "-framework CoreFoundation" "-framework IOKit")
|
||||
endif()
|
||||
|
||||
get_property(inc_headers TARGET common PROPERTY INCLUDE_DIRECTORIES)
|
||||
set(check_headers ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
#include "common/additionalContent.h"
|
||||
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <regex>
|
||||
#include <system_error>
|
||||
|
||||
namespace Common {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string GetString(const nlohmann::json& object, const std::string& key) {
|
||||
const auto it = object.find(key);
|
||||
return it != object.end() && it->is_string() ? it->get<std::string>() : std::string {};
|
||||
}
|
||||
|
||||
std::string GetTitle(const nlohmann::json& param) {
|
||||
const auto localized = param.find("localizedParameters");
|
||||
if (localized != param.end() && localized->is_object()) {
|
||||
const auto preferred = localized->find(GetString(*localized, "defaultLanguage"));
|
||||
if (preferred != localized->end()) {
|
||||
if (auto title = GetString(*preferred, "titleName"); !title.empty()) {
|
||||
return title;
|
||||
}
|
||||
}
|
||||
for (const auto& language: *localized) {
|
||||
if (auto title = GetString(language, "titleName"); !title.empty()) {
|
||||
return title;
|
||||
}
|
||||
}
|
||||
}
|
||||
return GetString(param, "titleName");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<AddContentEntry> FindAddContent(const std::filesystem::path& dir,
|
||||
const std::string& title_id) {
|
||||
if (dir.empty() || title_id.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
const auto root = std::filesystem::absolute(dir, ec);
|
||||
if (ec) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Scan directories separately so an unreadable child does not discard its siblings.
|
||||
std::vector<std::filesystem::path> pending {root};
|
||||
std::vector<std::filesystem::path> params;
|
||||
while (!pending.empty()) {
|
||||
const auto current = std::move(pending.back());
|
||||
pending.pop_back();
|
||||
std::filesystem::directory_iterator it(
|
||||
current, std::filesystem::directory_options::skip_permission_denied, ec);
|
||||
const std::filesystem::directory_iterator end;
|
||||
for (; !ec && it != end; it.increment(ec)) {
|
||||
std::error_code entry_ec;
|
||||
if (it->is_directory(entry_ec) && !it->is_symlink(entry_ec) && !entry_ec) {
|
||||
pending.push_back(it->path());
|
||||
} else if (it->is_regular_file(entry_ec) && !entry_ec &&
|
||||
EqualNoCase(PathToString(it->path().filename()), "param.json") &&
|
||||
EqualNoCase(PathToString(it->path().parent_path().filename()), "sce_sys")) {
|
||||
params.push_back(it->path());
|
||||
}
|
||||
}
|
||||
}
|
||||
std::sort(params.begin(), params.end());
|
||||
|
||||
static const std::regex content_id_pattern(
|
||||
"^[A-Z]{2}[0-9]{4}-[A-Z]{4}[0-9]{5}_[0-9]{2}-[A-Z0-9]{16}$");
|
||||
std::map<std::string, AddContentEntry> by_label;
|
||||
for (const auto& file: params) {
|
||||
const auto size = std::filesystem::file_size(file, ec);
|
||||
if (ec || size > 1024 * 1024) {
|
||||
continue;
|
||||
}
|
||||
std::ifstream stream(file, std::ios::binary);
|
||||
if (!stream) {
|
||||
continue;
|
||||
}
|
||||
const auto param = nlohmann::json::parse(stream, nullptr, false);
|
||||
// applicationCategoryType is required for applications, not additional content.
|
||||
if (!param.is_object() || param.contains("applicationCategoryType")) {
|
||||
continue;
|
||||
}
|
||||
const auto content_id = GetString(param, "contentId");
|
||||
if (!std::regex_match(content_id, content_id_pattern) ||
|
||||
content_id.compare(7, 9, title_id) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto mount_dir = file.parent_path().parent_path();
|
||||
const auto label = content_id.substr(20);
|
||||
by_label.try_emplace(label,
|
||||
AddContentEntry {content_id, label, mount_dir, GetTitle(param)});
|
||||
}
|
||||
|
||||
std::vector<AddContentEntry> entries;
|
||||
entries.reserve(by_label.size());
|
||||
for (auto& [label, entry]: by_label) {
|
||||
entries.push_back(std::move(entry));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -1,23 +0,0 @@
|
||||
#ifndef KYTY_COMMON_ADDITIONAL_CONTENT_H_
|
||||
#define KYTY_COMMON_ADDITIONAL_CONTENT_H_
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Common {
|
||||
|
||||
struct AddContentEntry {
|
||||
std::string content_id;
|
||||
std::string entitlement_label;
|
||||
std::filesystem::path mount_dir;
|
||||
std::string title;
|
||||
};
|
||||
|
||||
// Finds packages for this title, deduplicated by entitlement label.
|
||||
std::vector<AddContentEntry> FindAddContent(const std::filesystem::path& dir,
|
||||
const std::string& title_id);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_ADDITIONAL_CONTENT_H_ */
|
||||
@@ -1,25 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2014 Jannik Vogel <email@jannikvogel.de>
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] constexpr T AlignUp(T value, std::size_t size) {
|
||||
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
|
||||
auto mod{static_cast<T>(value % size)};
|
||||
value -= mod;
|
||||
return static_cast<T>(mod == T{0} ? value : value + size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] constexpr T AlignDown(T value, std::size_t size) {
|
||||
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
|
||||
return static_cast<T>(value - value % size);
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -1,397 +0,0 @@
|
||||
#include "common/archive.h"
|
||||
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <condition_variable>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <zarchive/zarchivereader.h>
|
||||
|
||||
// POSIX hosts and MinGW's pthread-backed libstdc++ have cancellable condition waits.
|
||||
// MSVC/ClangCL and MinGW's native Win32 threading backend do not use pthread waits.
|
||||
#if !defined(_WIN32) || defined(_GLIBCXX_GCC_GTHR_POSIX_H)
|
||||
#include <pthread.h>
|
||||
#define KYTY_ARCHIVE_PTHREAD_CANCELLATION
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
// Backends expose immutable metadata and offset-based reads. Cursor management, path routing,
|
||||
// reader lifetime and the host-stack worker are shared by every archive format.
|
||||
class ArchiveReader {
|
||||
public:
|
||||
struct Entry {
|
||||
uint64_t id;
|
||||
uint64_t size;
|
||||
bool is_file;
|
||||
};
|
||||
|
||||
virtual ~ArchiveReader() = default;
|
||||
virtual std::optional<Entry> Find(std::string_view member) = 0;
|
||||
virtual std::vector<File::DirEntry> List(std::string_view member) = 0;
|
||||
virtual uint64_t Read(uint64_t id, uint64_t offset, uint32_t size, void* data) = 0;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
class ZArchiveReaderBackend final: public ArchiveReader {
|
||||
public:
|
||||
explicit ZArchiveReaderBackend(std::unique_ptr<ZArchiveReader> reader)
|
||||
: m_reader(std::move(reader)) {}
|
||||
|
||||
std::optional<Entry> Find(std::string_view member) override {
|
||||
const auto node = m_reader->LookUp(member);
|
||||
if (node == ZARCHIVE_INVALID_NODE) {
|
||||
return {};
|
||||
}
|
||||
return Entry {node, m_reader->GetFileSize(node), m_reader->IsFile(node)};
|
||||
}
|
||||
|
||||
std::vector<File::DirEntry> List(std::string_view member) override {
|
||||
const auto node = m_reader->LookUp(member);
|
||||
if (!m_reader->IsDirectory(node)) {
|
||||
return {};
|
||||
}
|
||||
const auto count = m_reader->GetDirEntryCount(node);
|
||||
if (count > (1u << 20u)) {
|
||||
return {};
|
||||
}
|
||||
std::vector<File::DirEntry> result;
|
||||
result.reserve(count);
|
||||
for (uint32_t index = 0; index < count; ++index) {
|
||||
ZArchiveReader::DirEntry entry {};
|
||||
if (m_reader->GetDirEntry(node, index, entry)) {
|
||||
result.push_back({std::string(entry.name), entry.isFile});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
uint64_t Read(uint64_t id, uint64_t offset, uint32_t size, void* data) override {
|
||||
// ZArchiveReader already serializes access to its stream and decompression cache.
|
||||
return m_reader->ReadFromFile(static_cast<ZArchiveNodeHandle>(id), offset, size, data);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<ZArchiveReader> m_reader;
|
||||
};
|
||||
|
||||
std::shared_ptr<ArchiveReader> OpenZArchive(const std::filesystem::path& path) {
|
||||
auto reader = std::unique_ptr<ZArchiveReader>(ZArchiveReader::OpenFromFile(path));
|
||||
return reader ? std::make_shared<ZArchiveReaderBackend>(std::move(reader)) : nullptr;
|
||||
}
|
||||
|
||||
struct ArchiveFormat {
|
||||
std::string_view extension;
|
||||
std::shared_ptr<ArchiveReader> (*open)(const std::filesystem::path&);
|
||||
};
|
||||
|
||||
constexpr ArchiveFormat Formats[] = {{".zar", OpenZArchive}};
|
||||
using NativeView = std::basic_string_view<std::filesystem::path::value_type>;
|
||||
|
||||
const ArchiveFormat* FindFormat(NativeView path) {
|
||||
for (const auto& format: Formats) {
|
||||
if (path.size() < format.extension.size()) {
|
||||
continue;
|
||||
}
|
||||
const auto extension = path.substr(path.size() - format.extension.size());
|
||||
if (std::equal(
|
||||
extension.begin(), extension.end(), format.extension.begin(),
|
||||
[](auto a, char b) { return (a >= 'A' && a <= 'Z' ? a + ('a' - 'A') : a) == b; })) {
|
||||
return &format;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t FindMarker(NativeView path) {
|
||||
// Native files use this check on every operation. The usual path has no marker and allocates
|
||||
// nothing.
|
||||
for (auto marker = path.find('!'); marker != NativeView::npos;
|
||||
marker = path.find('!', marker + 1)) {
|
||||
if ((marker + 1 == path.size() || path[marker + 1] == '/' || path[marker + 1] == '\\') &&
|
||||
FindFormat(path.substr(0, marker)) != nullptr) {
|
||||
return marker;
|
||||
}
|
||||
}
|
||||
return NativeView::npos;
|
||||
}
|
||||
|
||||
struct ParsedPath {
|
||||
std::filesystem::path archive;
|
||||
std::string member;
|
||||
};
|
||||
|
||||
std::optional<ParsedPath> ParsePath(const std::filesystem::path& path) {
|
||||
const auto& native = path.native();
|
||||
const auto marker = FindMarker(native);
|
||||
if (marker == NativeView::npos) {
|
||||
return {};
|
||||
}
|
||||
ParsedPath parsed {.archive = std::filesystem::path(native.substr(0, marker))};
|
||||
auto member = PathToGenericString(std::filesystem::path(native.substr(marker + 1)));
|
||||
std::replace(member.begin(), member.end(), '\\', '/');
|
||||
std::string_view remaining = member;
|
||||
while (!remaining.empty()) {
|
||||
const auto separator = remaining.find('/');
|
||||
const auto component = remaining.substr(0, separator);
|
||||
remaining.remove_prefix(separator == std::string_view::npos ? remaining.size() : separator + 1);
|
||||
if (component.empty() || component == ".") {
|
||||
continue;
|
||||
}
|
||||
if (component == "..") {
|
||||
if (parsed.member.empty()) {
|
||||
return {};
|
||||
}
|
||||
const auto separator = parsed.member.rfind('/');
|
||||
parsed.member.resize(separator == std::string::npos ? 0 : separator);
|
||||
} else {
|
||||
if (!parsed.member.empty()) {
|
||||
parsed.member += '/';
|
||||
}
|
||||
parsed.member += component;
|
||||
}
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
std::mutex g_readers_mutex;
|
||||
std::unordered_map<std::filesystem::path, std::weak_ptr<ArchiveReader>> g_readers;
|
||||
|
||||
// Guest stacks can be only 64 KiB. Decompress on an ordinary host stack, then copy into guest
|
||||
// memory on the caller so GPU-tracked writes use its fault handler. A single worker bounds host
|
||||
// threads and avoids duplicating the archive's own block cache. Requests live on waiting callers.
|
||||
class ArchiveIoThread {
|
||||
public:
|
||||
static ArchiveIoThread& Instance() {
|
||||
// Guest exit can run static destructors while other guest threads are still reading.
|
||||
// Keep the worker alive until process termination, as with the guest threads themselves.
|
||||
static auto* instance = new ArchiveIoThread;
|
||||
return *instance;
|
||||
}
|
||||
|
||||
uint64_t Read(ArchiveReader& reader, uint64_t id, uint64_t offset, uint32_t size, void* data) {
|
||||
Request request {reader, id, offset, size, data};
|
||||
std::unique_lock lock(m_mutex);
|
||||
if (m_last != nullptr) {
|
||||
m_last->next = &request;
|
||||
} else {
|
||||
m_first = &request;
|
||||
}
|
||||
m_last = &request;
|
||||
m_wake.notify_one();
|
||||
request.ready.wait(lock, [&] { return request.done; });
|
||||
return request.result;
|
||||
}
|
||||
|
||||
KYTY_CLASS_NO_COPY(ArchiveIoThread);
|
||||
|
||||
private:
|
||||
struct Request {
|
||||
ArchiveReader& reader;
|
||||
uint64_t id;
|
||||
uint64_t offset;
|
||||
uint32_t size;
|
||||
void* data;
|
||||
Request* next = nullptr;
|
||||
uint64_t result = 0;
|
||||
bool done = false;
|
||||
std::condition_variable ready {};
|
||||
};
|
||||
|
||||
ArchiveIoThread() {
|
||||
std::thread([this] { Loop(); }).detach();
|
||||
}
|
||||
|
||||
void Loop() {
|
||||
for (;;) {
|
||||
Request* request;
|
||||
{
|
||||
std::unique_lock lock(m_mutex);
|
||||
m_wake.wait(lock, [this] { return m_first != nullptr; });
|
||||
request = m_first;
|
||||
m_first = request->next;
|
||||
if (m_first == nullptr) {
|
||||
m_last = nullptr;
|
||||
}
|
||||
}
|
||||
const auto result =
|
||||
request->reader.Read(request->id, request->offset, request->size, request->data);
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
request->result = result;
|
||||
request->done = true;
|
||||
// Notify before releasing the mutex: the waiting caller owns the condition
|
||||
// variable.
|
||||
request->ready.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_wake;
|
||||
Request* m_first = nullptr;
|
||||
Request* m_last = nullptr;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
struct ArchiveFile::Private {
|
||||
std::shared_ptr<ArchiveReader> reader;
|
||||
ArchiveReader::Entry entry;
|
||||
uint64_t position = 0;
|
||||
};
|
||||
|
||||
ArchiveFile::ArchiveFile(std::unique_ptr<Private> p): m_p(std::move(p)) {}
|
||||
ArchiveFile::~ArchiveFile() = default;
|
||||
|
||||
uint64_t ArchiveFile::Size() const {
|
||||
return m_p->entry.size;
|
||||
}
|
||||
uint64_t ArchiveFile::Tell() const {
|
||||
return m_p->position;
|
||||
}
|
||||
bool ArchiveFile::Seek(uint64_t offset) {
|
||||
m_p->position = offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ArchiveFile::Read(void* data, uint32_t size, uint32_t* bytes_read) {
|
||||
#if defined(KYTY_ARCHIVE_PTHREAD_CANCELLATION)
|
||||
// A pending cancellation must not destroy the waiting request or its TLS buffer while the
|
||||
// worker still uses them. Restore the caller's state only after the copy is complete.
|
||||
int cancellation_state = PTHREAD_CANCEL_ENABLE;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancellation_state);
|
||||
#endif
|
||||
constexpr uint32_t chunk_size = 1u << 20u;
|
||||
thread_local std::vector<uint8_t> chunk;
|
||||
uint32_t read = 0;
|
||||
if (data != nullptr && size != 0 && m_p->position < Size()) {
|
||||
const auto requested =
|
||||
static_cast<uint32_t>(std::min<uint64_t>(size, Size() - m_p->position));
|
||||
if (chunk.size() < std::min(chunk_size, requested)) {
|
||||
chunk.resize(std::min(chunk_size, requested));
|
||||
}
|
||||
while (read < requested) {
|
||||
const auto step = std::min(chunk_size, requested - read);
|
||||
const auto got = ArchiveIoThread::Instance().Read(*m_p->reader, m_p->entry.id,
|
||||
m_p->position, step, chunk.data());
|
||||
if (got == 0 || got > step) {
|
||||
break;
|
||||
}
|
||||
std::memcpy(static_cast<uint8_t*>(data) + read, chunk.data(), got);
|
||||
read += static_cast<uint32_t>(got);
|
||||
m_p->position += got;
|
||||
if (got < step) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bytes_read != nullptr) {
|
||||
*bytes_read = read;
|
||||
}
|
||||
#if defined(KYTY_ARCHIVE_PTHREAD_CANCELLATION)
|
||||
pthread_setcancelstate(cancellation_state, nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::filesystem::path MakeArchivePath(const std::filesystem::path& archive,
|
||||
const std::filesystem::path& member) {
|
||||
auto root = archive;
|
||||
root += "!";
|
||||
return member.empty() ? root : root / member.relative_path();
|
||||
}
|
||||
|
||||
bool IsSupportedArchive(const std::filesystem::path& path) {
|
||||
return FindFormat(path.native()) != nullptr;
|
||||
}
|
||||
|
||||
bool IsArchivePath(const std::filesystem::path& path) {
|
||||
return FindMarker(path.native()) != NativeView::npos;
|
||||
}
|
||||
|
||||
std::filesystem::path GetArchiveHostPath(const std::filesystem::path& path) {
|
||||
const auto marker = FindMarker(path.native());
|
||||
return marker == NativeView::npos ? std::filesystem::path {}
|
||||
: std::filesystem::path(path.native().substr(0, marker));
|
||||
}
|
||||
|
||||
std::shared_ptr<ArchiveReader> OpenArchive(const std::filesystem::path& path) {
|
||||
const auto archive = IsArchivePath(path) ? GetArchiveHostPath(path) : path;
|
||||
const auto* format = FindFormat(archive.native());
|
||||
if (format == nullptr) {
|
||||
return {};
|
||||
}
|
||||
std::error_code error;
|
||||
auto key = std::filesystem::absolute(archive, error);
|
||||
if (error) {
|
||||
return {};
|
||||
}
|
||||
{
|
||||
std::lock_guard lock(g_readers_mutex);
|
||||
if (auto it = g_readers.find(key); it != g_readers.end()) {
|
||||
if (auto reader = it->second.lock()) {
|
||||
return reader;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Do not hold the cache mutex while opening an unrelated archive from disk.
|
||||
auto reader = format->open(archive);
|
||||
if (reader == nullptr) {
|
||||
return {};
|
||||
}
|
||||
std::lock_guard lock(g_readers_mutex);
|
||||
std::erase_if(g_readers, [](const auto& entry) { return entry.second.expired(); });
|
||||
auto& cached = g_readers[key];
|
||||
if (auto existing = cached.lock()) {
|
||||
return existing;
|
||||
}
|
||||
cached = reader;
|
||||
return reader;
|
||||
}
|
||||
|
||||
std::optional<File::Info> GetArchiveInfo(const std::filesystem::path& path) {
|
||||
const auto parsed = ParsePath(path);
|
||||
if (parsed) {
|
||||
if (auto reader = OpenArchive(parsed->archive)) {
|
||||
if (auto entry = reader->Find(parsed->member)) {
|
||||
return File::Info {entry->is_file, entry->size};
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<File::DirEntry> GetArchiveDirEntries(const std::filesystem::path& path) {
|
||||
const auto parsed = ParsePath(path);
|
||||
if (parsed) {
|
||||
if (auto reader = OpenArchive(parsed->archive)) {
|
||||
return reader->List(parsed->member);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<ArchiveFile> OpenArchiveFile(const std::filesystem::path& path) {
|
||||
auto parsed = ParsePath(path);
|
||||
if (!parsed) {
|
||||
return {};
|
||||
}
|
||||
auto reader = OpenArchive(parsed->archive);
|
||||
const auto entry = reader ? reader->Find(parsed->member) : std::nullopt;
|
||||
if (!entry || !entry->is_file) {
|
||||
return {};
|
||||
}
|
||||
auto p = std::make_unique<ArchiveFile::Private>();
|
||||
p->reader = std::move(reader);
|
||||
p->entry = *entry;
|
||||
return std::unique_ptr<ArchiveFile>(new ArchiveFile(std::move(p)));
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -1,50 +0,0 @@
|
||||
#ifndef KYTY_COMMON_ARCHIVE_H_
|
||||
#define KYTY_COMMON_ARCHIVE_H_
|
||||
|
||||
#include "common/file.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Common {
|
||||
|
||||
class ArchiveReader;
|
||||
|
||||
class ArchiveFile {
|
||||
public:
|
||||
~ArchiveFile();
|
||||
|
||||
[[nodiscard]] uint64_t Size() const;
|
||||
[[nodiscard]] uint64_t Tell() const;
|
||||
bool Seek(uint64_t offset);
|
||||
void Read(void* data, uint32_t size, uint32_t* bytes_read);
|
||||
|
||||
KYTY_CLASS_NO_COPY(ArchiveFile);
|
||||
|
||||
private:
|
||||
struct Private;
|
||||
explicit ArchiveFile(std::unique_ptr<Private> p);
|
||||
std::unique_ptr<Private> m_p;
|
||||
|
||||
friend std::unique_ptr<ArchiveFile> OpenArchiveFile(const std::filesystem::path& path);
|
||||
};
|
||||
|
||||
// Virtual paths use "archive.ext!/member" and retain native host-path encoding.
|
||||
[[nodiscard]] std::filesystem::path MakeArchivePath(const std::filesystem::path& archive,
|
||||
const std::filesystem::path& member = {});
|
||||
[[nodiscard]] bool IsSupportedArchive(const std::filesystem::path& path);
|
||||
[[nodiscard]] bool IsArchivePath(const std::filesystem::path& path);
|
||||
[[nodiscard]] std::optional<File::Info> GetArchiveInfo(const std::filesystem::path& path);
|
||||
[[nodiscard]] std::vector<File::DirEntry> GetArchiveDirEntries(const std::filesystem::path& path);
|
||||
[[nodiscard]] std::filesystem::path GetArchiveHostPath(const std::filesystem::path& path);
|
||||
[[nodiscard]] std::unique_ptr<ArchiveFile> OpenArchiveFile(const std::filesystem::path& path);
|
||||
|
||||
// Retain this handle while a mount or metadata scan is active to reuse the index and block cache.
|
||||
// Accepts either a host archive filename or a virtual path inside the archive.
|
||||
[[nodiscard]] std::shared_ptr<ArchiveReader> OpenArchive(const std::filesystem::path& path);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_ARCHIVE_H_ */
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "common/assert.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/subsystems.h"
|
||||
#include "kytyGitVersion.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
@@ -11,15 +11,30 @@
|
||||
|
||||
namespace Common {
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_BUILD == KYTY_BUILD_DEBUG && \
|
||||
KYTY_COMPILER == KYTY_COMPILER_CLANG
|
||||
constexpr int PRINT_STACK_FROM = 4;
|
||||
#else
|
||||
constexpr int PRINT_STACK_FROM = 2;
|
||||
#endif
|
||||
|
||||
static std::string BuildFatalReport(const char* title, std::string_view text, const char* file,
|
||||
int line) {
|
||||
return fmt::format("--- Build ---\n{}\n{}\n{} in {}:{}\n", KYTY_BUILD_LABEL, title, text, file,
|
||||
line);
|
||||
DebugStack stack;
|
||||
DebugStack::Trace(&stack);
|
||||
|
||||
std::string report = "--- Stack Trace ---\n";
|
||||
for (int i = PRINT_STACK_FROM; i < stack.depth; i++) {
|
||||
report += fmt::format("[{}] {:016x}\n", i - PRINT_STACK_FROM,
|
||||
static_cast<uint64_t>(stack.GetAddr(i)));
|
||||
}
|
||||
report += fmt::format("{}\n{} in {}:{}\n", title, text, file, line);
|
||||
return report;
|
||||
}
|
||||
|
||||
static int DbgReport(const char* title, std::string_view text, const char* file, int line) {
|
||||
Log::WriteFatal(BuildFatalReport(title, text, file, line));
|
||||
Subsystems::EmergencyShutdownActive();
|
||||
SubsystemsListSingleton::Instance()->ShutdownAll();
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -43,7 +58,6 @@ int DbgExitHandler(const char* file, int line, fmt::text_style style, std::strin
|
||||
}
|
||||
|
||||
void DbgExit(int status) {
|
||||
Subsystems::EmergencyShutdownActive();
|
||||
std::fflush(nullptr);
|
||||
std::_Exit(status);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,11 @@ void DbgExit(int status);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS || KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
#define EXIT_HALT() (Common::DbgExit(321), 1)
|
||||
#else
|
||||
#define EXIT_HALT() (std::_Exit(321), 1)
|
||||
#endif
|
||||
|
||||
#ifndef KYTY_FINAL
|
||||
#define EXIT_IF(x) \
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
#ifndef KYTY_COMMON_BYTEBUFFER_H_
|
||||
#define KYTY_COMMON_BYTEBUFFER_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef> // IWYU pragma: export
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Common {
|
||||
|
||||
using Byte = std::byte;
|
||||
|
||||
class ByteBuffer {
|
||||
public:
|
||||
using DataType = std::vector<Byte>;
|
||||
|
||||
static constexpr uint32_t INVALID_INDEX = static_cast<uint32_t>(-1);
|
||||
|
||||
using iterator = DataType::iterator;
|
||||
using const_iterator = DataType::const_iterator;
|
||||
|
||||
ByteBuffer() = default;
|
||||
ByteBuffer(const ByteBuffer&) = default;
|
||||
ByteBuffer(ByteBuffer&&) = default;
|
||||
|
||||
ByteBuffer& operator=(const ByteBuffer&) = default;
|
||||
ByteBuffer& operator=(ByteBuffer&&) = default;
|
||||
|
||||
explicit ByteBuffer(uint32_t size): m_data(size) {}
|
||||
|
||||
ByteBuffer(std::initializer_list<uint8_t> list) {
|
||||
for (const uint8_t& e: list) {
|
||||
m_data.emplace_back(static_cast<Byte>(e));
|
||||
}
|
||||
}
|
||||
|
||||
explicit ByteBuffer(const void* buf, uint32_t size)
|
||||
: ByteBuffer(size) // @suppress("Ambiguous problem")
|
||||
{
|
||||
EXIT_IF(buf == nullptr && size != 0);
|
||||
|
||||
if (size != 0) {
|
||||
std::memcpy(m_data.data(), buf, size);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] uint32_t Size() const { return static_cast<uint32_t>(m_data.size()); }
|
||||
[[nodiscard]] bool IsEmpty() const { return m_data.empty(); }
|
||||
|
||||
void Clear() { m_data.clear(); }
|
||||
|
||||
void Add(const Byte& val) { m_data.push_back(val); }
|
||||
void Add(Byte&& val) { m_data.push_back(std::move(val)); }
|
||||
|
||||
void Add(const Byte* val, uint32_t num) {
|
||||
EXIT_IF(val == nullptr && num != 0);
|
||||
m_data.insert(m_data.end(), val, val + num);
|
||||
}
|
||||
|
||||
void Add(const ByteBuffer& v) {
|
||||
if (this == &v) {
|
||||
const ByteBuffer copy(v);
|
||||
Add(copy.GetDataConst(), copy.Size());
|
||||
return;
|
||||
}
|
||||
Add(v.GetDataConst(), v.Size());
|
||||
}
|
||||
|
||||
void InsertAt(uint32_t index, const Byte& val) {
|
||||
EXIT_IF(index > Size());
|
||||
m_data.insert(m_data.begin() + index, val);
|
||||
}
|
||||
|
||||
void InsertAt(uint32_t index, Byte&& val) {
|
||||
EXIT_IF(index > Size());
|
||||
m_data.insert(m_data.begin() + index, std::move(val));
|
||||
}
|
||||
|
||||
[[nodiscard]] uint32_t Find(const Byte& t) const {
|
||||
const auto it = std::find(m_data.begin(), m_data.end(), t);
|
||||
return (it == m_data.end() ? INVALID_INDEX
|
||||
: static_cast<uint32_t>(std::distance(m_data.begin(), it)));
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Contains(const Byte& t) const { return Find(t) != INVALID_INDEX; }
|
||||
|
||||
bool Remove(const Byte& t) {
|
||||
const auto it = std::find(m_data.begin(), m_data.end(), t);
|
||||
if (it == m_data.end()) {
|
||||
return false;
|
||||
}
|
||||
m_data.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RemoveAt(uint32_t index, uint32_t count = 1) {
|
||||
if (index >= Size() || count == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t erase_count = std::min(count, Size() - index);
|
||||
m_data.erase(m_data.begin() + index, m_data.begin() + index + erase_count);
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IndexValid(uint32_t index) const { return index < Size(); }
|
||||
|
||||
Byte& operator[](uint32_t index) {
|
||||
EXIT_IF(index >= Size());
|
||||
return m_data[index];
|
||||
}
|
||||
|
||||
const Byte& operator[](uint32_t index) const {
|
||||
EXIT_IF(index >= Size());
|
||||
return m_data[index];
|
||||
}
|
||||
|
||||
[[nodiscard]] const Byte& At(uint32_t index) const {
|
||||
EXIT_IF(index >= Size());
|
||||
return m_data[index];
|
||||
}
|
||||
|
||||
Byte* GetData() { return m_data.data(); }
|
||||
[[nodiscard]] const Byte* GetData() const { return m_data.data(); }
|
||||
[[nodiscard]] const Byte* GetDataConst() const { return m_data.data(); }
|
||||
|
||||
void Sort() {
|
||||
std::sort(m_data.begin(), m_data.end(), [](Byte a, Byte b) {
|
||||
return std::to_integer<uint8_t>(a) < std::to_integer<uint8_t>(b);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename OP>
|
||||
void Sort(OP&& comp_func) {
|
||||
std::sort(m_data.begin(), m_data.end(), std::forward<OP>(comp_func));
|
||||
}
|
||||
|
||||
bool operator==(const ByteBuffer& s) const { return m_data == s.m_data; }
|
||||
bool operator!=(const ByteBuffer& s) const { return !(*this == s); }
|
||||
|
||||
iterator begin() { return m_data.begin(); } // NOLINT(readability-identifier-naming)
|
||||
iterator end() { return m_data.end(); } // NOLINT(readability-identifier-naming)
|
||||
[[nodiscard]] const_iterator begin() const {
|
||||
return m_data.begin();
|
||||
} // NOLINT(readability-identifier-naming)
|
||||
[[nodiscard]] const_iterator end() const {
|
||||
return m_data.end();
|
||||
} // NOLINT(readability-identifier-naming)
|
||||
[[nodiscard]] const_iterator cbegin() const {
|
||||
return m_data.cbegin();
|
||||
} // NOLINT(readability-identifier-naming)
|
||||
[[nodiscard]] const_iterator cend() const {
|
||||
return m_data.cend();
|
||||
} // NOLINT(readability-identifier-naming)
|
||||
|
||||
private:
|
||||
DataType m_data;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_BYTEBUFFER_H_ */
|
||||
@@ -0,0 +1,21 @@
|
||||
#include "common/commonSubsystem.h"
|
||||
|
||||
#include "common/byteBuffer.h" // IWYU pragma: associated
|
||||
#include "common/common.h" // IWYU pragma: associated
|
||||
#include "common/debug.h" // IWYU pragma: associated
|
||||
#include "common/hash.h" // IWYU pragma: associated
|
||||
#include "common/magicEnum.h" // IWYU pragma: associated
|
||||
#include "common/singleton.h" // IWYU pragma: associated
|
||||
#include "common/virtualMemory.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
KYTY_SUBSYSTEM_INIT(Common) {
|
||||
VirtualMemory::Init();
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Common) {}
|
||||
|
||||
KYTY_SUBSYSTEM_DESTROY(Common) {}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef KYTY_COMMON_COMMON_SUBSYSTEM_H_
|
||||
#define KYTY_COMMON_COMMON_SUBSYSTEM_H_
|
||||
|
||||
#include "common/subsystems.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
KYTY_SUBSYSTEM_DEFINE(Common);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_COMMON_SUBSYSTEM_H_ */
|
||||
@@ -1,7 +1,20 @@
|
||||
#include "common/debug.h"
|
||||
|
||||
#include "common/platform/sysDbg.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
void DebugStack::Print(int from, [[maybe_unused]] bool with_name) const {
|
||||
for (int i = from; i < depth; i++) {
|
||||
printf("[%d] %016" PRIx64 "\n", i - from, static_cast<uint64_t>(GetAddr(i)));
|
||||
}
|
||||
}
|
||||
|
||||
void DebugStack::Trace(DebugStack* stack) {
|
||||
stack->depth = DEBUG_MAX_STACK_DEPTH;
|
||||
SysStackWalk(stack->stack.data(), &stack->depth);
|
||||
}
|
||||
|
||||
std::string Debug::GetCompiler() {
|
||||
#if KYTY_COMPILER == KYTY_COMPILER_CLANG
|
||||
return "clang";
|
||||
@@ -24,4 +37,8 @@ std::string Debug::GetLinker() {
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string Debug::GetBitness() {
|
||||
return "64";
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -2,16 +2,42 @@
|
||||
#define KYTY_COMMON_DEBUG_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
|
||||
namespace Common {
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_BUILD == KYTY_BUILD_DEBUG && \
|
||||
KYTY_COMPILER == KYTY_COMPILER_CLANG
|
||||
constexpr int DEBUG_MAX_STACK_DEPTH = 20;
|
||||
#else
|
||||
constexpr int DEBUG_MAX_STACK_DEPTH = 15;
|
||||
#endif
|
||||
|
||||
namespace Debug {
|
||||
std::string GetCompiler();
|
||||
std::string GetLinker();
|
||||
std::string GetBitness();
|
||||
} // namespace Debug
|
||||
|
||||
struct DebugStack {
|
||||
int depth {0};
|
||||
std::array<void*, DEBUG_MAX_STACK_DEPTH> stack {};
|
||||
|
||||
[[nodiscard]] uintptr_t GetAddr(int i) const { return reinterpret_cast<uintptr_t>(stack[i]); }
|
||||
|
||||
void Print(int from, bool with_name = true) const;
|
||||
|
||||
void CopyTo(DebugStack* s) const {
|
||||
std::memcpy(s->stack.data(), stack.data(), sizeof(void*) * depth);
|
||||
s->depth = depth;
|
||||
}
|
||||
|
||||
static void Trace(DebugStack* stack);
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_DEBUG_H_ */
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
#ifndef KYTY_COMMON_DEBUG_COUNTERS_H_
|
||||
#define KYTY_COMMON_DEBUG_COUNTERS_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Common::DebugCounters {
|
||||
|
||||
static_assert(std::atomic<uint64_t>::is_always_lock_free && std::atomic<bool>::is_always_lock_free);
|
||||
|
||||
// Collect events only while the panel is visible. Relaxed, lock-free atomics keep updates
|
||||
// cheap and safe in signal handlers. Rates come from comparing two snapshots.
|
||||
enum class Counter {
|
||||
// GPU work.
|
||||
Draws,
|
||||
DrawInstances,
|
||||
IndirectDraws,
|
||||
Dispatches,
|
||||
QueueSubmits,
|
||||
GpuWaits, // CPU blocked on a GPU timeline semaphore.
|
||||
GpuWaitNs,
|
||||
GpuFaults, // Guest writes to GPU-tracked memory.
|
||||
ShadersCompiled,
|
||||
ShaderCompileNs,
|
||||
PipelinesCreated,
|
||||
PipelineCreateNs,
|
||||
// Caches.
|
||||
TextureUploadBytes,
|
||||
TextureDownloadBytes,
|
||||
TextureEvictions,
|
||||
BufferUploadBytes,
|
||||
BufferDownloadBytes,
|
||||
BufferEvictions,
|
||||
// Presentation.
|
||||
FlipsRequested,
|
||||
FlipsRejected, // Guest flip queue was full.
|
||||
LateVblanks, // The present thread fell behind the vblank rate.
|
||||
SwapchainRecreations,
|
||||
DroppedPresents,
|
||||
// Emulator.
|
||||
EmulatedInstructions, // Host CPU lacked an instruction the guest used.
|
||||
UnresolvedImportCalls,
|
||||
GuestFileReadBytes,
|
||||
GuestFileWriteBytes,
|
||||
AudioUnderruns,
|
||||
Count
|
||||
};
|
||||
|
||||
enum class Peak { ShaderCompileNs, PipelineCreateNs, Count };
|
||||
|
||||
namespace Detail {
|
||||
inline std::atomic<bool> g_enabled {false};
|
||||
inline std::atomic<uint64_t> g_counters[static_cast<int>(Counter::Count)] {};
|
||||
inline std::atomic<uint64_t> g_peaks[static_cast<int>(Peak::Count)] {};
|
||||
} // namespace Detail
|
||||
|
||||
[[nodiscard]] inline bool Enabled() noexcept {
|
||||
return Detail::g_enabled.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
inline void SetEnabled(bool enabled) noexcept {
|
||||
Detail::g_enabled.store(enabled, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
inline void Add(Counter counter, uint64_t value = 1) noexcept {
|
||||
if (!Enabled()) {
|
||||
return;
|
||||
}
|
||||
Detail::g_counters[static_cast<int>(counter)].fetch_add(value, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
class ScopedTimer final {
|
||||
public:
|
||||
ScopedTimer(Counter count, Counter duration, Peak peak = Peak::Count)
|
||||
: m_count(count), m_duration(duration), m_peak(peak),
|
||||
m_start(Enabled() ? std::chrono::steady_clock::now()
|
||||
: std::chrono::steady_clock::time_point {}) {}
|
||||
~ScopedTimer() {
|
||||
if (m_start == std::chrono::steady_clock::time_point {} || !Enabled()) {
|
||||
return;
|
||||
}
|
||||
const auto elapsed =
|
||||
static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now() - m_start)
|
||||
.count());
|
||||
Add(m_count);
|
||||
Add(m_duration, elapsed);
|
||||
if (m_peak != Peak::Count) {
|
||||
auto& peak = Detail::g_peaks[static_cast<int>(m_peak)];
|
||||
auto previous = peak.load(std::memory_order_relaxed);
|
||||
while (previous < elapsed &&
|
||||
!peak.compare_exchange_weak(previous, elapsed, std::memory_order_relaxed)) {
|
||||
}
|
||||
}
|
||||
}
|
||||
ScopedTimer(const ScopedTimer&) = delete;
|
||||
ScopedTimer& operator=(const ScopedTimer&) = delete;
|
||||
|
||||
private:
|
||||
Counter m_count;
|
||||
Counter m_duration;
|
||||
Peak m_peak;
|
||||
std::chrono::steady_clock::time_point m_start;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline uint64_t Get(Counter counter) noexcept {
|
||||
return Detail::g_counters[static_cast<int>(counter)].load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline uint64_t Get(Peak peak) noexcept {
|
||||
return Detail::g_peaks[static_cast<int>(peak)].load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
} // namespace Common::DebugCounters
|
||||
|
||||
#endif /* KYTY_COMMON_DEBUG_COUNTERS_H_ */
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "common/emulatorConfig.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/debugCounters.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
@@ -10,26 +9,20 @@ namespace Config {
|
||||
|
||||
static std::unique_ptr<ConfigOptions> g_config;
|
||||
|
||||
void Initialize() {
|
||||
KYTY_SUBSYSTEM_INIT(Config) {
|
||||
EXIT_IF(g_config != nullptr);
|
||||
|
||||
g_config = std::make_unique<ConfigOptions>();
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
Common::DebugCounters::SetEnabled(false);
|
||||
g_config.reset();
|
||||
}
|
||||
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Config) {}
|
||||
|
||||
KYTY_SUBSYSTEM_DESTROY(Config) {}
|
||||
|
||||
void Load(const ConfigOptions& cfg) {
|
||||
EXIT_IF(g_config == nullptr);
|
||||
EXIT_IF(cfg.user_name.empty() || cfg.user_name.size() > MAX_USER_NAME_LENGTH);
|
||||
EXIT_IF(!IsConfiguredUserIdValid(cfg.user_id));
|
||||
EXIT_IF(cfg.controller_speaker_volume > 100 || cfg.controller_vibration_intensity > 100);
|
||||
EXIT_IF(cfg.keyboard_player > static_cast<uint32_t>(MAX_PLAYERS));
|
||||
|
||||
*g_config = cfg;
|
||||
Common::DebugCounters::SetEnabled(cfg.perf_overlay_enabled);
|
||||
}
|
||||
|
||||
uint32_t GetScreenWidth() {
|
||||
@@ -40,95 +33,14 @@ uint32_t GetScreenHeight() {
|
||||
return g_config->screen_height;
|
||||
}
|
||||
|
||||
const std::string& GetUserName() {
|
||||
return g_config->user_name;
|
||||
}
|
||||
|
||||
int32_t GetUserId() {
|
||||
return g_config->user_id;
|
||||
}
|
||||
|
||||
const std::string& GetAudioInputDevice() {
|
||||
return g_config->audio_input_device;
|
||||
}
|
||||
|
||||
const std::optional<ControllerColor>& GetControllerColor() {
|
||||
return g_config->controller_color;
|
||||
}
|
||||
|
||||
uint32_t GetControllerSpeakerVolume() {
|
||||
return g_config->controller_speaker_volume;
|
||||
}
|
||||
|
||||
uint32_t GetControllerVibrationIntensity() {
|
||||
return g_config->controller_vibration_intensity;
|
||||
}
|
||||
|
||||
uint32_t GetKeyboardPlayer() {
|
||||
return g_config->keyboard_player;
|
||||
}
|
||||
|
||||
int32_t GetPlayerUserId(int player) {
|
||||
return g_config->user_id + player * PLAYER_USER_ID_STEP;
|
||||
}
|
||||
|
||||
int GetPlayerFromUserId(int32_t user_id) {
|
||||
for (int player = 0; player < MAX_PLAYERS; player++) {
|
||||
if (user_id == GetPlayerUserId(player)) {
|
||||
return player;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool IsGuestUserId(int32_t user_id) {
|
||||
return GetPlayerFromUserId(user_id) > 0;
|
||||
}
|
||||
|
||||
PresentMode GetPresentMode() {
|
||||
return g_config->present_mode;
|
||||
}
|
||||
|
||||
int32_t GetGpuIndex() {
|
||||
return g_config->gpu_index;
|
||||
}
|
||||
|
||||
bool FullscreenEnabled() {
|
||||
return g_config->fullscreen_enabled;
|
||||
}
|
||||
|
||||
bool HdrEnabled() {
|
||||
return g_config->hdr_enabled;
|
||||
}
|
||||
|
||||
bool HideCursorEnabled() {
|
||||
return g_config->hide_cursor_enabled;
|
||||
}
|
||||
|
||||
bool VrEnabled() {
|
||||
return g_config->vr_enabled;
|
||||
}
|
||||
|
||||
bool AmdCpuEnabled() {
|
||||
return g_config->amd_cpu_enabled;
|
||||
}
|
||||
|
||||
uint32_t GetVblankFrequency() {
|
||||
return std::clamp(g_config->vblank_frequency, 30u, 360u);
|
||||
}
|
||||
|
||||
uint32_t GetConsoleLanguage() {
|
||||
return g_config->console_language;
|
||||
}
|
||||
|
||||
bool VulkanValidationEnabled() {
|
||||
return g_config->vulkan_validation_enabled;
|
||||
}
|
||||
|
||||
bool IsNetworkConnected() {
|
||||
return g_config->network_connected;
|
||||
}
|
||||
|
||||
bool ShaderValidationEnabled() {
|
||||
return g_config->shader_validation_enabled;
|
||||
}
|
||||
@@ -137,7 +49,7 @@ ShaderOptimizationType GetShaderOptimizationType() {
|
||||
return g_config->shader_optimization_type;
|
||||
}
|
||||
|
||||
LogDirection GetShaderLogDirection() {
|
||||
ShaderLogDirection GetShaderLogDirection() {
|
||||
return g_config->shader_log_direction;
|
||||
}
|
||||
|
||||
@@ -157,7 +69,7 @@ bool GraphicsDebugDumpEnabled() {
|
||||
return g_config->graphics_debug_dump_enabled;
|
||||
}
|
||||
|
||||
LogDirection GetPrintfDirection() {
|
||||
OutputDirection GetPrintfDirection() {
|
||||
return g_config->printf_direction;
|
||||
}
|
||||
|
||||
@@ -165,66 +77,24 @@ std::filesystem::path GetPrintfOutputFile() {
|
||||
return g_config->printf_output_file;
|
||||
}
|
||||
|
||||
bool ProfilerEnabled() {
|
||||
return g_config->profiler_enabled;
|
||||
ProfilerDirection GetProfilerDirection() {
|
||||
return g_config->profiler_direction;
|
||||
}
|
||||
|
||||
bool SpirvDebugPrintfEnabled() {
|
||||
return g_config->spirv_debug_printf_enabled;
|
||||
}
|
||||
|
||||
bool GpuAssistedValidationEnabled() {
|
||||
return g_config->gpu_assisted_validation_enabled && g_config->vulkan_validation_enabled;
|
||||
}
|
||||
|
||||
bool RenderDocEnabled() {
|
||||
return g_config->renderdoc_enabled;
|
||||
}
|
||||
|
||||
bool NggRectlistDrawEnabled() {
|
||||
return g_config->ngg_rectlist_draw_enabled;
|
||||
}
|
||||
|
||||
bool ReadbackLinearImagesEnabled() {
|
||||
return g_config->readback_linear_images;
|
||||
}
|
||||
|
||||
bool SyncRawImageBuffersEnabled() {
|
||||
return g_config->sync_raw_image_buffers;
|
||||
}
|
||||
|
||||
bool SupportZmaskEnabled() {
|
||||
return g_config->support_zmask;
|
||||
}
|
||||
|
||||
bool TessellationEnabled() {
|
||||
return g_config->tessellation_enabled;
|
||||
}
|
||||
|
||||
bool TrophyEnabled() {
|
||||
return g_config->trophy_enabled;
|
||||
}
|
||||
|
||||
uint32_t GetTrophyDurationSeconds() {
|
||||
return std::clamp(g_config->trophy_duration_seconds, 5u, 10u);
|
||||
}
|
||||
|
||||
bool PlayGoHackEnabled() {
|
||||
return g_config->playgo_hack_enabled;
|
||||
}
|
||||
|
||||
bool SkipNoticeScreen() {
|
||||
return g_config->skip_notice_screen;
|
||||
}
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
bool RedZoneProtectionEnabled() {
|
||||
return g_config->red_zone_protection_enabled;
|
||||
}
|
||||
#endif
|
||||
|
||||
const Keymap& GetKeymap() {
|
||||
return g_config->keymap;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& GetGuestArguments() {
|
||||
return g_config->guest_arguments;
|
||||
}
|
||||
|
||||
} // namespace Config
|
||||
|
||||
@@ -2,137 +2,53 @@
|
||||
#define KYTY_COMMON_EMULATOR_CONFIG_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/subsystems.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Config {
|
||||
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
|
||||
struct Lifecycle {
|
||||
static constexpr const char* name = "Config";
|
||||
static constexpr auto initialize = Config::Initialize;
|
||||
static constexpr auto shutdown = Config::Shutdown;
|
||||
};
|
||||
KYTY_SUBSYSTEM_DEFINE(Config);
|
||||
|
||||
enum class ShaderOptimizationType { None, Size, Performance };
|
||||
|
||||
enum class LogDirection { Silent, Console, File };
|
||||
enum class ShaderLogDirection { Silent, Console, File };
|
||||
|
||||
enum class PresentMode { Fifo, Mailbox, Immediate };
|
||||
enum class ProfilerDirection { None, Network };
|
||||
|
||||
using Keymap = std::vector<std::string>;
|
||||
using ControllerColor = std::array<uint8_t, 3>;
|
||||
|
||||
constexpr uint32_t DEFAULT_CONSOLE_LANGUAGE = 1;
|
||||
constexpr uint32_t MAX_CONSOLE_LANGUAGE = 29;
|
||||
constexpr std::size_t MAX_USER_NAME_LENGTH = 16;
|
||||
constexpr int32_t DEFAULT_USER_ID = 1000;
|
||||
|
||||
constexpr int MAX_PLAYERS = 4;
|
||||
constexpr int32_t PLAYER_USER_ID_STEP = 0x100;
|
||||
constexpr int32_t MAX_USER_ID =
|
||||
std::numeric_limits<int32_t>::max() - (MAX_PLAYERS - 1) * PLAYER_USER_ID_STEP;
|
||||
|
||||
constexpr bool IsConfiguredUserIdValid(int32_t user_id) {
|
||||
constexpr int32_t USER_ID_EVERYONE = 0xfe;
|
||||
constexpr int32_t USER_ID_SYSTEM = 0xff;
|
||||
return user_id >= 0 && user_id <= MAX_USER_ID && user_id != USER_ID_EVERYONE &&
|
||||
user_id != USER_ID_SYSTEM;
|
||||
}
|
||||
enum class OutputDirection { Silent, Console, File };
|
||||
|
||||
struct ConfigOptions {
|
||||
uint32_t screen_width = 1280;
|
||||
uint32_t screen_height = 720;
|
||||
std::string user_name = "Kyty";
|
||||
int32_t user_id = DEFAULT_USER_ID;
|
||||
std::string audio_input_device;
|
||||
std::optional<ControllerColor> controller_color;
|
||||
uint32_t controller_speaker_volume = 50;
|
||||
uint32_t controller_vibration_intensity = 100;
|
||||
// 0 shares player 1 with the first gamepad; 1..MAX_PLAYERS dedicates that player to the keyboard.
|
||||
uint32_t keyboard_player = 0;
|
||||
PresentMode present_mode = PresentMode::Mailbox;
|
||||
int32_t gpu_index = -1;
|
||||
bool fullscreen_enabled = false;
|
||||
bool hdr_enabled = false;
|
||||
bool hide_cursor_enabled = false;
|
||||
bool splash_enabled = true;
|
||||
bool vr_enabled = false;
|
||||
bool amd_cpu_enabled = false;
|
||||
bool network_connected = false;
|
||||
uint32_t vblank_frequency = 60;
|
||||
uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE;
|
||||
bool vulkan_validation_enabled = false;
|
||||
bool shader_validation_enabled = false;
|
||||
ShaderOptimizationType shader_optimization_type = ShaderOptimizationType::None;
|
||||
LogDirection shader_log_direction = LogDirection::Silent;
|
||||
ShaderLogDirection shader_log_direction = ShaderLogDirection::Silent;
|
||||
std::filesystem::path shader_log_folder = "_Shaders";
|
||||
bool command_buffer_dump_enabled = false;
|
||||
std::filesystem::path command_buffer_dump_folder = "_Buffers";
|
||||
bool graphics_debug_dump_enabled = false;
|
||||
LogDirection printf_direction = LogDirection::Silent;
|
||||
OutputDirection printf_direction = OutputDirection::Console;
|
||||
std::filesystem::path printf_output_file = "_kyty.txt";
|
||||
bool profiler_enabled = false;
|
||||
ProfilerDirection profiler_direction = ProfilerDirection::None;
|
||||
bool spirv_debug_printf_enabled = false;
|
||||
bool gpu_assisted_validation_enabled = false;
|
||||
bool renderdoc_enabled = false;
|
||||
bool ngg_rectlist_draw_enabled = true;
|
||||
bool readback_linear_images = false;
|
||||
bool sync_raw_image_buffers = false;
|
||||
bool support_zmask = false;
|
||||
bool tessellation_enabled = false;
|
||||
bool trophy_enabled = true;
|
||||
uint32_t trophy_duration_seconds = 6;
|
||||
bool perf_overlay_enabled = false;
|
||||
bool playgo_hack_enabled = false;
|
||||
bool skip_notice_screen = false;
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
bool red_zone_protection_enabled = false;
|
||||
#endif
|
||||
Keymap keymap;
|
||||
// Ordinary boots need a non-null argv[0]; --guest-exec supplies the caller's arguments.
|
||||
std::vector<std::string> guest_arguments {""};
|
||||
};
|
||||
|
||||
void Load(const ConfigOptions& cfg);
|
||||
|
||||
uint32_t GetScreenWidth();
|
||||
uint32_t GetScreenHeight();
|
||||
const std::string& GetUserName();
|
||||
int32_t GetUserId();
|
||||
const std::string& GetAudioInputDevice();
|
||||
const std::optional<ControllerColor>& GetControllerColor();
|
||||
uint32_t GetControllerSpeakerVolume();
|
||||
uint32_t GetControllerVibrationIntensity();
|
||||
uint32_t GetKeyboardPlayer();
|
||||
// Player 0 is the configured user; later players derive their IDs from it.
|
||||
int32_t GetPlayerUserId(int player);
|
||||
// Returns -1 when the user ID does not belong to any player.
|
||||
int GetPlayerFromUserId(int32_t user_id);
|
||||
// Players after the first are guests: they have no profile, trophies or NP account.
|
||||
bool IsGuestUserId(int32_t user_id);
|
||||
PresentMode GetPresentMode();
|
||||
int32_t GetGpuIndex();
|
||||
bool FullscreenEnabled();
|
||||
bool HdrEnabled();
|
||||
bool HideCursorEnabled();
|
||||
bool VrEnabled();
|
||||
bool AmdCpuEnabled();
|
||||
bool IsNetworkConnected();
|
||||
uint32_t GetVblankFrequency();
|
||||
uint32_t GetConsoleLanguage();
|
||||
bool VulkanValidationEnabled();
|
||||
|
||||
bool ShaderValidationEnabled();
|
||||
ShaderOptimizationType GetShaderOptimizationType();
|
||||
LogDirection GetShaderLogDirection();
|
||||
ShaderLogDirection GetShaderLogDirection();
|
||||
std::filesystem::path GetShaderLogFolder();
|
||||
|
||||
bool CommandBufferDumpEnabled();
|
||||
@@ -140,30 +56,16 @@ std::filesystem::path GetCommandBufferDumpFolder();
|
||||
|
||||
bool GraphicsDebugDumpEnabled();
|
||||
|
||||
LogDirection GetPrintfDirection();
|
||||
OutputDirection GetPrintfDirection();
|
||||
std::filesystem::path GetPrintfOutputFile();
|
||||
|
||||
bool ProfilerEnabled();
|
||||
ProfilerDirection GetProfilerDirection();
|
||||
|
||||
bool SpirvDebugPrintfEnabled();
|
||||
|
||||
bool GpuAssistedValidationEnabled();
|
||||
|
||||
bool RenderDocEnabled();
|
||||
bool NggRectlistDrawEnabled();
|
||||
bool ReadbackLinearImagesEnabled();
|
||||
bool SyncRawImageBuffersEnabled();
|
||||
bool SupportZmaskEnabled();
|
||||
bool TessellationEnabled();
|
||||
bool TrophyEnabled();
|
||||
uint32_t GetTrophyDurationSeconds();
|
||||
bool PlayGoHackEnabled();
|
||||
bool SkipNoticeScreen();
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
bool RedZoneProtectionEnabled();
|
||||
#endif
|
||||
|
||||
const Keymap& GetKeymap();
|
||||
const std::vector<std::string>& GetGuestArguments();
|
||||
|
||||
} // namespace Config
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "common/file.h"
|
||||
|
||||
#include "common/archive.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/platform/sysFileIO.h"
|
||||
@@ -26,6 +25,9 @@
|
||||
#ifdef CopyFile
|
||||
#undef CopyFile
|
||||
#endif
|
||||
#ifdef MoveFile
|
||||
#undef MoveFile
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
@@ -62,8 +64,7 @@ SysFileTimeStruct DateTimeToFileTimeUtc(const DateTime& date_time) {
|
||||
} // namespace
|
||||
|
||||
struct File::FilePrivate {
|
||||
sys_file_t* f;
|
||||
std::unique_ptr<ArchiveFile> archive;
|
||||
sys_file_t* f;
|
||||
};
|
||||
|
||||
File::File(): m_p(std::make_unique<FilePrivate>()) {
|
||||
@@ -71,7 +72,7 @@ File::File(): m_p(std::make_unique<FilePrivate>()) {
|
||||
}
|
||||
|
||||
File::~File() {
|
||||
Close();
|
||||
EXIT_IF(m_p->f != nullptr);
|
||||
}
|
||||
|
||||
File::File(const std::filesystem::path& name): m_p(std::make_unique<FilePrivate>()) {
|
||||
@@ -87,15 +88,11 @@ File::File(const std::filesystem::path& name, Mode mode): m_p(std::make_unique<F
|
||||
}
|
||||
|
||||
bool File::IsInvalid() const {
|
||||
return m_p->f == nullptr && m_p->archive == nullptr;
|
||||
return m_p->f == nullptr;
|
||||
}
|
||||
|
||||
bool File::Create(const std::filesystem::path& name) {
|
||||
EXIT_IF(!IsInvalid());
|
||||
|
||||
if (IsArchivePath(name)) {
|
||||
return false;
|
||||
}
|
||||
EXIT_IF(m_p->f != nullptr);
|
||||
|
||||
m_file_name = name;
|
||||
|
||||
@@ -110,16 +107,9 @@ bool File::Create(const std::filesystem::path& name) {
|
||||
}
|
||||
|
||||
bool File::Open(const std::filesystem::path& name, Mode mode) {
|
||||
EXIT_IF(!IsInvalid());
|
||||
EXIT_IF(m_p->f != nullptr);
|
||||
|
||||
m_file_name = name;
|
||||
if (IsArchivePath(name)) {
|
||||
if (mode != Mode::Read) {
|
||||
return false;
|
||||
}
|
||||
m_p->archive = OpenArchiveFile(name);
|
||||
return m_p->archive != nullptr;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case Mode::Read: m_p->f = SysFileOpenR(name); break;
|
||||
@@ -137,7 +127,7 @@ bool File::Open(const std::filesystem::path& name, Mode mode) {
|
||||
}
|
||||
|
||||
bool File::OpenInMem(void* buf, uint32_t buf_size) {
|
||||
EXIT_IF(!IsInvalid());
|
||||
EXIT_IF(m_p->f != nullptr);
|
||||
|
||||
m_p->f = SysFileOpen(static_cast<uint8_t*>(buf), buf_size);
|
||||
|
||||
@@ -149,8 +139,12 @@ bool File::OpenInMem(void* buf, uint32_t buf_size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::OpenInMem(ByteBuffer& buf) {
|
||||
return OpenInMem(buf.GetData(), buf.Size());
|
||||
}
|
||||
|
||||
bool File::CreateInMem() {
|
||||
EXIT_IF(!IsInvalid());
|
||||
EXIT_IF(m_p->f != nullptr);
|
||||
|
||||
m_p->f = SysFileCreate();
|
||||
|
||||
@@ -163,7 +157,6 @@ bool File::CreateInMem() {
|
||||
}
|
||||
|
||||
void File::Close() {
|
||||
m_p->archive.reset();
|
||||
if (m_p->f != nullptr) {
|
||||
SysFileClose(m_p->f);
|
||||
m_p->f = nullptr;
|
||||
@@ -171,11 +164,7 @@ void File::Close() {
|
||||
}
|
||||
|
||||
uint64_t File::Size() const {
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
if (m_p->archive != nullptr) {
|
||||
return m_p->archive->Size();
|
||||
}
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
if (m_p->f == nullptr) {
|
||||
return 0;
|
||||
@@ -185,76 +174,41 @@ uint64_t File::Size() const {
|
||||
}
|
||||
|
||||
uint64_t File::Remaining() const {
|
||||
EXIT_IF(IsInvalid());
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
const auto size = Size();
|
||||
const auto position = Tell();
|
||||
return position < size ? size - position : 0;
|
||||
return Size() - Tell();
|
||||
}
|
||||
|
||||
uint64_t File::Size(const std::filesystem::path& name) {
|
||||
if (IsArchivePath(name)) {
|
||||
const auto info = GetArchiveInfo(name);
|
||||
return info ? info->size : 0;
|
||||
}
|
||||
return SysFileSize(name);
|
||||
}
|
||||
|
||||
std::optional<File::Info> File::GetInfo(const std::filesystem::path& name) {
|
||||
if (IsArchivePath(name)) {
|
||||
return GetArchiveInfo(name);
|
||||
}
|
||||
Info info {};
|
||||
return SysFileGetInfo(name, &info.is_file, &info.size) ? std::optional(info) : std::nullopt;
|
||||
}
|
||||
|
||||
bool File::Seek(uint64_t offset) {
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
if (m_p->archive != nullptr) {
|
||||
return m_p->archive->Seek(offset);
|
||||
}
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
return SysFileSeek(*m_p->f, offset);
|
||||
}
|
||||
|
||||
bool File::Truncate(uint64_t size) {
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
if (m_p->archive != nullptr) {
|
||||
return false;
|
||||
}
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
return SysFileTruncate(*m_p->f, size);
|
||||
}
|
||||
|
||||
bool File::Unlink() {
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
if (m_p->archive != nullptr) {
|
||||
return false;
|
||||
}
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
return SysFileUnlink(*m_p->f, m_file_name);
|
||||
}
|
||||
|
||||
uint64_t File::Tell() const {
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
if (m_p->archive != nullptr) {
|
||||
return m_p->archive->Tell();
|
||||
}
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
return SysFileTell(*m_p->f);
|
||||
}
|
||||
|
||||
void File::Read(void* data, uint32_t size, uint32_t* bytes_read) {
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
if (m_p->archive != nullptr) {
|
||||
m_p->archive->Read(data, size, bytes_read);
|
||||
return;
|
||||
}
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
if (m_p->f != nullptr) {
|
||||
SysFileRead(data, size, *m_p->f, bytes_read);
|
||||
@@ -262,14 +216,7 @@ void File::Read(void* data, uint32_t size, uint32_t* bytes_read) {
|
||||
}
|
||||
|
||||
void File::Write(const void* data, uint32_t size, uint32_t* bytes_written) {
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
if (m_p->archive != nullptr) {
|
||||
if (bytes_written != nullptr) {
|
||||
*bytes_written = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
SysFileWrite(data, size, *m_p->f, bytes_written);
|
||||
}
|
||||
@@ -305,120 +252,103 @@ void File::Printf(const char* format, ...) {
|
||||
Write(s.data(), static_cast<uint32_t>(s.size()));
|
||||
}
|
||||
|
||||
static std::filesystem::path WithoutTrailingSeparator(
|
||||
const std::filesystem::path& path) {
|
||||
if (!path.empty() && !path.has_filename() &&
|
||||
path != path.root_path()) {
|
||||
return path.parent_path();
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
bool File::IsDirectoryExisting(const std::filesystem::path& path) {
|
||||
if (IsArchivePath(path)) {
|
||||
const auto info = GetArchiveInfo(path);
|
||||
return info && !info->is_file;
|
||||
}
|
||||
return SysFileIsDirectoryExisting(WithoutTrailingSeparator(path));
|
||||
auto path_str = PathToGenericString(path);
|
||||
return SysFileIsDirectoryExisting(
|
||||
Common::EndsWith(path_str, "/") ? Common::RemoveLast(path_str, 1) : path_str);
|
||||
}
|
||||
|
||||
bool File::IsFileExisting(const std::filesystem::path& name) {
|
||||
if (IsArchivePath(name)) {
|
||||
const auto info = GetArchiveInfo(name);
|
||||
return info && info->is_file;
|
||||
}
|
||||
return SysFileIsFileExisting(name);
|
||||
}
|
||||
|
||||
bool File::CreateDirectory(const std::filesystem::path& path) {
|
||||
if (IsArchivePath(path)) {
|
||||
return false;
|
||||
}
|
||||
return SysFileCreateDirectory(
|
||||
WithoutTrailingSeparator(path));
|
||||
bool File::CreateDirectory(
|
||||
const std::filesystem::path& path) // @suppress("Member declaration not found")
|
||||
{
|
||||
auto path_str = PathToGenericString(path);
|
||||
return SysFileCreateDirectory(Common::EndsWith(path_str, "/") ? Common::RemoveLast(path_str, 1)
|
||||
: path_str);
|
||||
}
|
||||
|
||||
bool File::DeleteDirectory(const std::filesystem::path& path) {
|
||||
if (IsArchivePath(path)) {
|
||||
return false;
|
||||
}
|
||||
return SysFileDeleteDirectory(
|
||||
WithoutTrailingSeparator(path));
|
||||
auto path_str = PathToGenericString(path);
|
||||
return SysFileDeleteDirectory(Common::EndsWith(path_str, "/") ? Common::RemoveLast(path_str, 1)
|
||||
: path_str);
|
||||
}
|
||||
|
||||
bool File::CreateDirectories(const std::filesystem::path& path) {
|
||||
if (IsArchivePath(path)) {
|
||||
return false;
|
||||
std::string real_path = Common::ReplaceChar(PathToGenericString(path), '\\', '/');
|
||||
|
||||
std::vector<std::string> list = Common::Split(real_path, "/");
|
||||
|
||||
std::string p;
|
||||
|
||||
for (uint32_t si = 0; si < list.size(); si++) {
|
||||
const std::string& s = list[si];
|
||||
|
||||
if (si != 0 || Common::StartsWith(real_path, "/")) {
|
||||
p += "/";
|
||||
}
|
||||
|
||||
p += s;
|
||||
|
||||
if (IsDirectoryExisting(p)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!CreateDirectory(p)) // @suppress("Invalid arguments")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const auto normalized_path =
|
||||
WithoutTrailingSeparator(path);
|
||||
|
||||
auto current_path = normalized_path.root_path();
|
||||
|
||||
for (const auto& component:
|
||||
normalized_path.relative_path()) {
|
||||
current_path /= component;
|
||||
|
||||
if (IsDirectoryExisting(current_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!CreateDirectory(current_path)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::DeleteDirectories(const std::filesystem::path& path) {
|
||||
if (IsArchivePath(path)) {
|
||||
return false;
|
||||
std::string real_path = Common::ReplaceChar(PathToGenericString(path), '\\', '/');
|
||||
|
||||
std::vector<std::string> list = Common::Split(real_path, "/");
|
||||
|
||||
std::string p;
|
||||
std::vector<std::string> list2;
|
||||
|
||||
for (uint32_t si = 0; si < list.size(); si++) {
|
||||
const std::string& s = list[si];
|
||||
|
||||
if (si != 0 || Common::StartsWith(real_path, "/")) {
|
||||
p += "/";
|
||||
}
|
||||
|
||||
p += s;
|
||||
|
||||
list2.push_back(p);
|
||||
}
|
||||
const auto normalized_path =
|
||||
WithoutTrailingSeparator(path);
|
||||
|
||||
std::vector<std::filesystem::path> directories;
|
||||
auto current_path = normalized_path.root_path();
|
||||
uint32_t num = list2.size();
|
||||
|
||||
for (const auto& component:
|
||||
normalized_path.relative_path()) {
|
||||
current_path /= component;
|
||||
directories.push_back(current_path);
|
||||
}
|
||||
for (uint32_t si = num - 1; si < num; si--) {
|
||||
if (!DeleteDirectory(list2[si])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = directories.rbegin();
|
||||
it != directories.rend(); ++it) {
|
||||
if (!DeleteDirectory(*it)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::DeleteFile(
|
||||
const std::filesystem::path& name) // @suppress("Member declaration not found")
|
||||
{
|
||||
if (IsArchivePath(name)) {
|
||||
return false;
|
||||
}
|
||||
return SysFileDeleteFile(name);
|
||||
}
|
||||
|
||||
bool File::Flush() {
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
if (m_p->archive != nullptr) {
|
||||
return true;
|
||||
}
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
return SysFileFlush(*m_p->f);
|
||||
}
|
||||
|
||||
std::vector<std::byte> File::ReadWholeBuffer() {
|
||||
ByteBuffer File::ReadWholeBuffer() {
|
||||
EXIT_IF(IsInvalid());
|
||||
EXIT_IF(Tell() != 0);
|
||||
|
||||
@@ -426,18 +356,26 @@ std::vector<std::byte> File::ReadWholeBuffer() {
|
||||
|
||||
EXIT_IF((s >> 32u) != 0);
|
||||
|
||||
const auto read_size = static_cast<uint32_t>(s);
|
||||
std::vector<std::byte> buf(read_size);
|
||||
ByteBuffer buf(s);
|
||||
|
||||
Read(buf.data(), read_size);
|
||||
Read(buf.GetData(), s);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
ByteBuffer File::Read(uint32_t size) {
|
||||
ByteBuffer buf(size);
|
||||
uint32_t b = 0;
|
||||
Read(buf.GetData(), size, &b);
|
||||
buf.RemoveAt(b, size - b);
|
||||
return buf;
|
||||
}
|
||||
|
||||
void File::Write(const ByteBuffer& buf, uint32_t* bytes_written) {
|
||||
Write(buf.GetDataConst(), buf.Size(), bytes_written);
|
||||
}
|
||||
|
||||
DateTime File::GetLastAccessTimeUTC(const std::filesystem::path& name) {
|
||||
if (IsArchivePath(name)) {
|
||||
return GetLastAccessTimeUTC(GetArchiveHostPath(name));
|
||||
}
|
||||
SysTimeStruct t {};
|
||||
SysFileToSystemTimeUtc(SysFileGetLastAccessTimeUtc(name), t);
|
||||
|
||||
@@ -449,9 +387,6 @@ DateTime File::GetLastAccessTimeUTC(const std::filesystem::path& name) {
|
||||
}
|
||||
|
||||
DateTime File::GetLastWriteTimeUTC(const std::filesystem::path& name) {
|
||||
if (IsArchivePath(name)) {
|
||||
return GetLastWriteTimeUTC(GetArchiveHostPath(name));
|
||||
}
|
||||
SysTimeStruct t {};
|
||||
SysFileToSystemTimeUtc(SysFileGetLastWriteTimeUtc(name), t);
|
||||
|
||||
@@ -466,10 +401,6 @@ void File::GetLastAccessAndWriteTimeUTC(const std::filesystem::path& name, DateT
|
||||
DateTime* write) {
|
||||
EXIT_IF(access == nullptr);
|
||||
EXIT_IF(write == nullptr);
|
||||
if (IsArchivePath(name)) {
|
||||
GetLastAccessAndWriteTimeUTC(GetArchiveHostPath(name), access, write);
|
||||
return;
|
||||
}
|
||||
|
||||
SysTimeStruct at {};
|
||||
SysTimeStruct wt {};
|
||||
@@ -496,12 +427,7 @@ void File::GetLastAccessAndWriteTimeUTC(DateTime* access, DateTime* write) {
|
||||
EXIT_IF(access == nullptr);
|
||||
EXIT_IF(write == nullptr);
|
||||
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
if (m_p->archive != nullptr) {
|
||||
GetLastAccessAndWriteTimeUTC(m_file_name, access, write);
|
||||
return;
|
||||
}
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
if (m_p->f == nullptr) {
|
||||
*access = DateTime();
|
||||
@@ -531,35 +457,68 @@ void File::GetLastAccessAndWriteTimeUTC(DateTime* access, DateTime* write) {
|
||||
}
|
||||
|
||||
bool File::SetLastAccessTimeUTC(const std::filesystem::path& name, const DateTime& dt) {
|
||||
if (IsArchivePath(name)) {
|
||||
return false;
|
||||
}
|
||||
auto f = DateTimeToFileTimeUtc(dt);
|
||||
return SysFileSetLastAccessTimeUtc(name, f);
|
||||
}
|
||||
|
||||
bool File::SetLastWriteTimeUTC(const std::filesystem::path& name, const DateTime& dt) {
|
||||
if (IsArchivePath(name)) {
|
||||
return false;
|
||||
}
|
||||
auto f = DateTimeToFileTimeUtc(dt);
|
||||
return SysFileSetLastWriteTimeUtc(name, f);
|
||||
}
|
||||
|
||||
bool File::SetLastAccessAndWriteTimeUTC(const std::filesystem::path& name, const DateTime& access,
|
||||
const DateTime& write) {
|
||||
if (IsArchivePath(name)) {
|
||||
return false;
|
||||
}
|
||||
auto af = DateTimeToFileTimeUtc(access);
|
||||
auto wf = DateTimeToFileTimeUtc(write);
|
||||
return SysFileSetLastAccessAndWriteTimeUtc(name, af, wf);
|
||||
}
|
||||
|
||||
std::vector<File::DirEntry> File::GetDirEntries(const std::filesystem::path& path) {
|
||||
if (IsArchivePath(path)) {
|
||||
return GetArchiveDirEntries(path);
|
||||
std::vector<File::FindInfo> File::FindFiles(const std::filesystem::path& path) {
|
||||
std::vector<sys_file_find_t> files;
|
||||
|
||||
SysFileFindFiles(path, files);
|
||||
|
||||
auto path_str = PathToGenericString(path);
|
||||
uint32_t len = static_cast<uint32_t>(path_str.size());
|
||||
|
||||
if (!Common::EndsWith(path_str, "/") && !Common::EndsWith(path_str, "\\")) {
|
||||
len++;
|
||||
}
|
||||
|
||||
std::vector<File::FindInfo> ret;
|
||||
ret.reserve(files.size());
|
||||
|
||||
for (const auto& f: files) {
|
||||
File::FindInfo r {};
|
||||
|
||||
r.path_with_name = f.path_with_name;
|
||||
r.rel_path_with_name = Common::Mid(PathToGenericString(f.path_with_name), len);
|
||||
r.size = f.size;
|
||||
|
||||
SysTimeStruct at {};
|
||||
SysTimeStruct wt {};
|
||||
|
||||
SysFileToSystemTimeUtc(f.last_access_time, at);
|
||||
SysFileToSystemTimeUtc(f.last_write_time, wt);
|
||||
|
||||
if (!at.is_invalid && !wt.is_invalid) {
|
||||
r.last_access_time = DateTime(Date(at.Year, at.Month, at.Day),
|
||||
Time(at.Hour, at.Minute, at.Second, at.Milliseconds));
|
||||
r.last_write_time = DateTime(Date(wt.Year, wt.Month, wt.Day),
|
||||
Time(wt.Hour, wt.Minute, wt.Second, wt.Milliseconds));
|
||||
}
|
||||
|
||||
// printf("%s, %s, %" PRIu64", %s, %s\n", r.path_with_name.c_str(),
|
||||
// r.rel_path_with_name.c_str(), r.size, r.last_access_time.ToString().c_str(),
|
||||
// r.last_write_time.ToString().c_str());
|
||||
|
||||
ret.push_back(std::move(r));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<File::DirEntry> File::GetDirEntries(const std::filesystem::path& path) {
|
||||
std::vector<sys_dir_entry_t> files;
|
||||
|
||||
SysFileGetDents(path, files);
|
||||
@@ -567,8 +526,13 @@ std::vector<File::DirEntry> File::GetDirEntries(const std::filesystem::path& pat
|
||||
std::vector<File::DirEntry> ret;
|
||||
ret.reserve(files.size());
|
||||
|
||||
for (auto& f: files) {
|
||||
ret.emplace_back(std::move(f.name), f.is_file);
|
||||
for (const auto& f: files) {
|
||||
File::DirEntry r {};
|
||||
|
||||
r.name = f.name;
|
||||
r.is_file = f.is_file;
|
||||
|
||||
ret.push_back(std::move(r));
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -577,29 +541,20 @@ std::vector<File::DirEntry> File::GetDirEntries(const std::filesystem::path& pat
|
||||
bool File::CopyFile(const std::filesystem::path& src,
|
||||
const std::filesystem::path& dst) // @suppress("Member declaration not found")
|
||||
{
|
||||
if (IsArchivePath(src) || IsArchivePath(dst)) {
|
||||
return false;
|
||||
}
|
||||
return SysFileCopyFile(src, dst);
|
||||
}
|
||||
|
||||
bool File::RenameFile(const std::filesystem::path& src,
|
||||
const std::filesystem::path& dst) // @suppress("Member declaration not found")
|
||||
bool File::MoveFile(const std::filesystem::path& src,
|
||||
const std::filesystem::path& dst) // @suppress("Member declaration not found")
|
||||
{
|
||||
if (IsArchivePath(src) || IsArchivePath(dst)) {
|
||||
return false;
|
||||
}
|
||||
if (IsFileExisting(dst)) {
|
||||
DeleteFile(dst); // @suppress("Invalid arguments")
|
||||
}
|
||||
|
||||
return SysFileRenameFile(src, dst);
|
||||
return SysFileMoveFile(src, dst);
|
||||
}
|
||||
|
||||
void File::RemoveReadonly(const std::filesystem::path& name) {
|
||||
if (IsArchivePath(name)) {
|
||||
return;
|
||||
}
|
||||
SysFileRemoveReadonly(name);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
#ifndef KYTY_COMMON_FILE_H_
|
||||
#define KYTY_COMMON_FILE_H_
|
||||
|
||||
#include "common/byteBuffer.h"
|
||||
#include "common/common.h"
|
||||
#include "common/dateTime.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -21,6 +20,9 @@
|
||||
#ifdef CopyFile
|
||||
#undef CopyFile
|
||||
#endif
|
||||
#ifdef MoveFile
|
||||
#undef MoveFile
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
@@ -31,14 +33,18 @@ class File {
|
||||
public:
|
||||
enum class Mode { Read, Write, ReadWrite, WriteRead };
|
||||
|
||||
struct FindInfo {
|
||||
std::filesystem::path path_with_name;
|
||||
std::filesystem::path rel_path_with_name;
|
||||
DateTime last_access_time;
|
||||
DateTime last_write_time;
|
||||
uint64_t size;
|
||||
};
|
||||
|
||||
struct DirEntry {
|
||||
std::string name;
|
||||
bool is_file;
|
||||
};
|
||||
struct Info {
|
||||
bool is_file;
|
||||
uint64_t size;
|
||||
};
|
||||
|
||||
File();
|
||||
explicit File(const std::filesystem::path& name);
|
||||
@@ -48,6 +54,7 @@ public:
|
||||
bool Create(const std::filesystem::path& name);
|
||||
bool Open(const std::filesystem::path& name, Mode mode);
|
||||
bool OpenInMem(void* buf, uint32_t buf_size);
|
||||
bool OpenInMem(ByteBuffer& buf); // NOLINT(google-runtime-references)
|
||||
bool CreateInMem();
|
||||
|
||||
void Close();
|
||||
@@ -69,15 +76,16 @@ public:
|
||||
|
||||
void GetLastAccessAndWriteTimeUTC(DateTime* access, DateTime* write);
|
||||
|
||||
void Read(void* data, uint32_t size, uint32_t* bytes_read = nullptr);
|
||||
void Write(const void* data, uint32_t size, uint32_t* bytes_written = nullptr);
|
||||
void Read(void* data, uint32_t size, uint32_t* bytes_read = nullptr);
|
||||
ByteBuffer Read(uint32_t size);
|
||||
void Write(const void* data, uint32_t size, uint32_t* bytes_written = nullptr);
|
||||
void Write(const ByteBuffer& buf, uint32_t* bytes_written = nullptr);
|
||||
|
||||
void Printf(const char* format, ...) KYTY_FORMAT_PRINTF(2, 3);
|
||||
|
||||
std::vector<std::byte> ReadWholeBuffer();
|
||||
ByteBuffer ReadWholeBuffer();
|
||||
|
||||
static uint64_t Size(const std::filesystem::path& name);
|
||||
static std::optional<Info> GetInfo(const std::filesystem::path& name);
|
||||
|
||||
static bool IsDirectoryExisting(const std::filesystem::path& path);
|
||||
static bool IsFileExisting(const std::filesystem::path& name);
|
||||
@@ -96,9 +104,10 @@ public:
|
||||
static bool SetLastAccessAndWriteTimeUTC(const std::filesystem::path& name,
|
||||
const DateTime& access, const DateTime& write);
|
||||
|
||||
static std::vector<FindInfo> FindFiles(const std::filesystem::path& path);
|
||||
static std::vector<DirEntry> GetDirEntries(const std::filesystem::path& path);
|
||||
static bool CopyFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
static bool RenameFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
static bool MoveFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
static void RemoveReadonly(const std::filesystem::path& name);
|
||||
|
||||
KYTY_CLASS_NO_COPY(File);
|
||||
|
||||
@@ -54,6 +54,103 @@ inline uint32_t hash(const void* key, uint32_t key_len) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline uint32_t hash8(uint8_t key) {
|
||||
uint32_t hash = 0;
|
||||
|
||||
uint8_t* ptr = &key;
|
||||
|
||||
hash += ptr[0];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
|
||||
hash += (hash << 3u);
|
||||
hash ^= (hash >> 11u);
|
||||
hash += (hash << 15u);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline uint32_t hash16(uint16_t key) {
|
||||
uint32_t hash = 0;
|
||||
|
||||
auto* ptr = reinterpret_cast<uint8_t*>(&key);
|
||||
|
||||
hash += ptr[0];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[1];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
|
||||
hash += (hash << 3u);
|
||||
hash ^= (hash >> 11u);
|
||||
hash += (hash << 15u);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline uint32_t hash32(uint32_t key) {
|
||||
uint32_t hash = 0;
|
||||
|
||||
auto* ptr = reinterpret_cast<uint8_t*>(&key);
|
||||
|
||||
hash += ptr[0];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[1];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[2];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[3];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
|
||||
hash += (hash << 3u);
|
||||
hash ^= (hash >> 11u);
|
||||
hash += (hash << 15u);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline uint32_t hash64(uint64_t key) {
|
||||
uint32_t hash = 0;
|
||||
|
||||
auto* ptr = reinterpret_cast<uint8_t*>(&key);
|
||||
|
||||
hash += ptr[0];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[1];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[2];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[3];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[4];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[5];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[6];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[7];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
|
||||
hash += (hash << 3u);
|
||||
hash ^= (hash >> 11u);
|
||||
hash += (hash << 15u);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_HASH_H_ */
|
||||
|
||||
@@ -2,20 +2,18 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#include <windows.h> // IWYU pragma: keep
|
||||
#else
|
||||
#include <algorithm>
|
||||
#elif defined(__APPLE__)
|
||||
#include <csignal>
|
||||
#include <cstdlib>
|
||||
#include <initializer_list>
|
||||
#include <unistd.h>
|
||||
#if defined(__APPLE__)
|
||||
#include <sys/ucontext.h>
|
||||
#else
|
||||
#include <csignal>
|
||||
#include <initializer_list>
|
||||
#include <ucontext.h> // IWYU pragma: keep
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
// IWYU pragma: no_include <errhandlingapi.h>
|
||||
@@ -30,59 +28,54 @@ namespace Common::HostException {
|
||||
|
||||
static std::atomic<Handler> g_handler {nullptr};
|
||||
static std::atomic_uint32_t g_install_state {0};
|
||||
static thread_local bool g_in_exception_filter = false;
|
||||
|
||||
static_assert(decltype(g_handler)::is_always_lock_free);
|
||||
static_assert(decltype(g_install_state)::is_always_lock_free);
|
||||
|
||||
[[noreturn]] static void FailFast(const char* reason) noexcept {
|
||||
std::fputs("HostException fail-fast: ", stderr);
|
||||
std::fputs(reason != nullptr ? reason : "unspecified", stderr);
|
||||
std::fputc('\n', stderr);
|
||||
std::fflush(stderr);
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
TerminateProcess(GetCurrentProcess(), static_cast<UINT>(EXCEPTION_NONCONTINUABLE_EXCEPTION));
|
||||
#endif
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
|
||||
// macOS uses the same POSIX platform setting and needs a signal stack too.
|
||||
class ThreadSignalStack {
|
||||
public:
|
||||
ThreadSignalStack() {
|
||||
const auto page_size = static_cast<size_t>(::getpagesize());
|
||||
const auto stack_size =
|
||||
(std::max<size_t>(64 * 1024, MINSIGSTKSZ) + page_size - 1) & ~(page_size - 1);
|
||||
if (::posix_memalign(&m_memory, page_size, stack_size) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
stack_t stack {};
|
||||
stack.ss_sp = m_memory;
|
||||
stack.ss_size = stack_size;
|
||||
if (::sigaltstack(&stack, &m_previous) != 0) {
|
||||
std::free(m_memory);
|
||||
m_memory = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
~ThreadSignalStack() {
|
||||
if (m_memory != nullptr && ::sigaltstack(&m_previous, nullptr) == 0) {
|
||||
std::free(m_memory);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsInitialized() const { return m_memory != nullptr; }
|
||||
|
||||
KYTY_CLASS_NO_COPY(ThreadSignalStack)
|
||||
|
||||
private:
|
||||
void* m_memory = nullptr;
|
||||
stack_t m_previous {};
|
||||
};
|
||||
|
||||
bool InitializeThreadSignalStack() {
|
||||
// Keep fault handling off guest stacks, which GPU tracking can make read-only.
|
||||
thread_local ThreadSignalStack signal_stack;
|
||||
return signal_stack.IsInitialized();
|
||||
std::_Exit(321);
|
||||
}
|
||||
|
||||
class FilterScope final {
|
||||
public:
|
||||
FilterScope() noexcept {
|
||||
if (g_in_exception_filter) {
|
||||
FailFast("nested exception while resolving a host fault");
|
||||
}
|
||||
g_in_exception_filter = true;
|
||||
}
|
||||
|
||||
~FilterScope() { g_in_exception_filter = false; }
|
||||
|
||||
KYTY_CLASS_NO_COPY(FilterScope);
|
||||
};
|
||||
|
||||
static Handler LoadInstalledHandler() noexcept {
|
||||
if (g_install_state.load(std::memory_order_acquire) == 0) {
|
||||
FailFast("host exception handler is not installed");
|
||||
}
|
||||
|
||||
const auto handler = g_handler.load(std::memory_order_acquire);
|
||||
if (handler == nullptr) {
|
||||
FailFast("host exception callback is null");
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
|
||||
static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) noexcept {
|
||||
static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) {
|
||||
FilterScope filter_scope;
|
||||
|
||||
auto* exception_record = exception->ExceptionRecord;
|
||||
|
||||
if (exception_record->ExceptionCode == DBG_PRINTEXCEPTION_C ||
|
||||
@@ -112,6 +105,12 @@ static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) noexcept {
|
||||
} else if (exception_record->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) {
|
||||
info.type = ExceptionType::IllegalInstruction;
|
||||
} else {
|
||||
printf("Unhandled win exception: code=0x%08" PRIx32 ", addr=0x%016" PRIx64
|
||||
", rip=0x%016" PRIx64 ", rsp=0x%016" PRIx64 ", rbp=0x%016" PRIx64 "\n",
|
||||
static_cast<uint32_t>(exception_record->ExceptionCode),
|
||||
reinterpret_cast<uint64_t>(exception_record->ExceptionAddress),
|
||||
exception->ContextRecord->Rip, exception->ContextRecord->Rsp,
|
||||
exception->ContextRecord->Rbp);
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
@@ -132,21 +131,28 @@ static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) noexcept {
|
||||
info.r14 = exception->ContextRecord->R14;
|
||||
info.r15 = exception->ContextRecord->R15;
|
||||
|
||||
const auto handler = g_handler.load(std::memory_order_acquire);
|
||||
if (handler != nullptr && handler(info)) {
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
const auto handler = LoadInstalledHandler();
|
||||
|
||||
return handler(info) ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
static std::atomic<Handler> g_handler {nullptr};
|
||||
static std::atomic_uint32_t g_install_state {0};
|
||||
static thread_local bool g_in_exception_filter = false;
|
||||
|
||||
static_assert(decltype(g_handler)::is_always_lock_free);
|
||||
static_assert(decltype(g_install_state)::is_always_lock_free);
|
||||
|
||||
[[noreturn]] static void FailFast(const char* reason) noexcept {
|
||||
std::fputs("HostException fail-fast: ", stderr);
|
||||
std::fputs(reason != nullptr ? reason : "unspecified", stderr);
|
||||
std::fputc('\n', stderr);
|
||||
std::fflush(stderr);
|
||||
std::_Exit(321);
|
||||
}
|
||||
|
||||
// Translate the x86-64 page-fault error code (mcontext __es.__err) into an access type.
|
||||
// bit 1 (0x2) = write, bit 4 (0x10) = instruction fetch, otherwise a read.
|
||||
static AccessViolationType DecodeAccess(uint64_t err) {
|
||||
@@ -164,6 +170,11 @@ static AccessViolationType DecodeAccess(uint64_t err) {
|
||||
// re-executing the faulting instruction against the now-fixed protection. An unresolved
|
||||
// fault restores the default disposition so the retry terminates the process.
|
||||
static void SignalHandler(int sig, siginfo_t* si, void* uctx) {
|
||||
if (g_in_exception_filter) {
|
||||
FailFast("nested exception while resolving a host fault");
|
||||
}
|
||||
g_in_exception_filter = true;
|
||||
|
||||
auto* uc = static_cast<ucontext_t*>(uctx);
|
||||
const auto* mc = uc->uc_mcontext;
|
||||
const auto& ss = mc->__ss;
|
||||
@@ -199,7 +210,14 @@ static void SignalHandler(int sig, siginfo_t* si, void* uctx) {
|
||||
info.r15 = ss.__r15;
|
||||
|
||||
const auto handler = g_handler.load(std::memory_order_acquire);
|
||||
if (handler != nullptr && handler(info)) {
|
||||
if (handler == nullptr) {
|
||||
FailFast("host exception callback is null");
|
||||
}
|
||||
|
||||
const bool resolved = handler(info);
|
||||
g_in_exception_filter = false;
|
||||
|
||||
if (resolved) {
|
||||
return; // retry the faulting instruction against the fixed mapping
|
||||
}
|
||||
|
||||
@@ -226,6 +244,8 @@ static void ChainToDefault(int signal_number) noexcept {
|
||||
}
|
||||
|
||||
static void SignalHandler(int signal_number, siginfo_t* signal_info, void* native_context) {
|
||||
FilterScope filter_scope;
|
||||
|
||||
auto* context = static_cast<ucontext_t*>(native_context);
|
||||
auto* gregs = context->uc_mcontext.gregs;
|
||||
|
||||
@@ -269,8 +289,9 @@ static void SignalHandler(int signal_number, siginfo_t* signal_info, void* nativ
|
||||
info.r14 = static_cast<uint64_t>(gregs[REG_R14]);
|
||||
info.r15 = static_cast<uint64_t>(gregs[REG_R15]);
|
||||
|
||||
const auto handler = g_handler.load(std::memory_order_acquire);
|
||||
if (handler != nullptr && handler(info)) {
|
||||
const auto handler = LoadInstalledHandler();
|
||||
|
||||
if (handler(info)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -292,7 +313,7 @@ bool InstallHandler(Handler handler) {
|
||||
g_handler.store(handler, std::memory_order_release);
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
if (AddVectoredExceptionHandler(0, ExceptionFilter) == nullptr) {
|
||||
if (AddVectoredExceptionHandler(1, ExceptionFilter) == nullptr) {
|
||||
g_handler.store(nullptr, std::memory_order_release);
|
||||
g_install_state.store(0, std::memory_order_release);
|
||||
printf("AddVectoredExceptionHandler() failed\n");
|
||||
@@ -301,7 +322,7 @@ bool InstallHandler(Handler handler) {
|
||||
#elif defined(__APPLE__)
|
||||
struct sigaction sa {};
|
||||
sa.sa_sigaction = SignalHandler;
|
||||
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
// The guest signal-dispatch path (KernelRaiseException) interrupts threads with
|
||||
// SIGUSR1; block it while a fault is being resolved so a stop-the-world request
|
||||
@@ -322,7 +343,8 @@ bool InstallHandler(Handler handler) {
|
||||
struct sigaction action {};
|
||||
action.sa_sigaction = SignalHandler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = SA_SIGINFO | SA_RESTART | SA_ONSTACK;
|
||||
// Fault resolution needs the normal thread stack.
|
||||
action.sa_flags = SA_SIGINFO | SA_RESTART;
|
||||
|
||||
for (const int signal_number: {SIGSEGV, SIGBUS, SIGILL}) {
|
||||
if (::sigaction(signal_number, &action, nullptr) != 0) {
|
||||
|
||||
@@ -39,10 +39,6 @@ using Handler = bool (*)(const ExceptionInfo&);
|
||||
|
||||
bool InstallHandler(Handler handler);
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
bool InitializeThreadSignalStack();
|
||||
#endif
|
||||
|
||||
} // namespace Common::HostException
|
||||
|
||||
#endif /* KYTY_COMMON_HOST_EXCEPTION_H_ */
|
||||
|
||||
@@ -1,580 +0,0 @@
|
||||
#include "common/hostStats.h"
|
||||
|
||||
#include "common/config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h> // IWYU pragma: keep
|
||||
// clang-format off
|
||||
#include <psapi.h>
|
||||
// clang-format on
|
||||
#elif defined(__APPLE__)
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <cstring>
|
||||
#include <libproc.h>
|
||||
#include <mach/mach.h>
|
||||
#include <notify.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <sys/resource.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
namespace {
|
||||
|
||||
// Cumulative counters; rates come from the difference between two samples.
|
||||
struct Counters {
|
||||
struct Thread {
|
||||
uint64_t cpu_ns;
|
||||
std::string name;
|
||||
};
|
||||
std::optional<uint64_t> process_cpu_ns;
|
||||
std::optional<uint64_t> system_busy;
|
||||
std::optional<uint64_t> system_total;
|
||||
std::optional<uint64_t> context_switches;
|
||||
std::optional<uint64_t> page_faults;
|
||||
std::optional<uint64_t> page_ins;
|
||||
std::optional<uint64_t> disk_read;
|
||||
std::optional<uint64_t> disk_write;
|
||||
std::unordered_map<uint64_t, Thread> threads;
|
||||
uint64_t wall_ns = 0;
|
||||
};
|
||||
|
||||
uint64_t WallNanoseconds() {
|
||||
return static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch())
|
||||
.count());
|
||||
}
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
|
||||
uint64_t FileTimeTo100Ns(const FILETIME& time) {
|
||||
return (static_cast<uint64_t>(time.dwHighDateTime) << 32u) | time.dwLowDateTime;
|
||||
}
|
||||
|
||||
struct PlatformState {};
|
||||
|
||||
void SamplePlatform(PlatformState& /*state*/, HostStats& stats, Counters& now) {
|
||||
const auto process = GetCurrentProcess();
|
||||
FILETIME creation {};
|
||||
FILETIME exit {};
|
||||
FILETIME kernel {};
|
||||
FILETIME user {};
|
||||
if (GetProcessTimes(process, &creation, &exit, &kernel, &user) != 0) {
|
||||
now.process_cpu_ns = (FileTimeTo100Ns(kernel) + FileTimeTo100Ns(user)) * 100u;
|
||||
}
|
||||
FILETIME idle {};
|
||||
if (GetSystemTimes(&idle, &kernel, &user) != 0) {
|
||||
// Kernel time includes idle time.
|
||||
now.system_total = FileTimeTo100Ns(kernel) + FileTimeTo100Ns(user);
|
||||
now.system_busy = *now.system_total - FileTimeTo100Ns(idle);
|
||||
}
|
||||
|
||||
PROCESS_MEMORY_COUNTERS memory {};
|
||||
if (K32GetProcessMemoryInfo(process, &memory, sizeof(memory)) != 0) {
|
||||
stats.process_memory = memory.WorkingSetSize;
|
||||
stats.process_resident = memory.WorkingSetSize;
|
||||
now.page_faults = memory.PageFaultCount;
|
||||
}
|
||||
IO_COUNTERS io {};
|
||||
if (GetProcessIoCounters(process, &io) != 0) {
|
||||
now.disk_read = io.ReadTransferCount;
|
||||
now.disk_write = io.WriteTransferCount;
|
||||
}
|
||||
|
||||
MEMORYSTATUSEX system {};
|
||||
system.dwLength = sizeof(system);
|
||||
if (GlobalMemoryStatusEx(&system) != 0) {
|
||||
stats.system_memory_total = system.ullTotalPhys;
|
||||
stats.system_memory_used = system.ullTotalPhys - system.ullAvailPhys;
|
||||
stats.system_free = system.ullAvailPhys;
|
||||
stats.memory_pressure = system.dwMemoryLoad >= 90 ? MemoryPressure::Critical
|
||||
: system.dwMemoryLoad >= 80 ? MemoryPressure::Warning
|
||||
: MemoryPressure::Normal;
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void SampleRusage(Counters& now) {
|
||||
rusage usage {};
|
||||
if (getrusage(RUSAGE_SELF, &usage) != 0) {
|
||||
return;
|
||||
}
|
||||
const auto to_ns = [](const timeval& time) {
|
||||
return static_cast<uint64_t>(time.tv_sec) * 1000000000u +
|
||||
static_cast<uint64_t>(time.tv_usec) * 1000u;
|
||||
};
|
||||
now.process_cpu_ns = to_ns(usage.ru_utime) + to_ns(usage.ru_stime);
|
||||
now.context_switches = static_cast<uint64_t>(usage.ru_nvcsw + usage.ru_nivcsw);
|
||||
now.page_faults = static_cast<uint64_t>(usage.ru_minflt + usage.ru_majflt);
|
||||
now.page_ins = static_cast<uint64_t>(usage.ru_majflt);
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
||||
template <typename T>
|
||||
std::optional<T> SysctlValue(const char* name) {
|
||||
T value {};
|
||||
size_t size = sizeof(value);
|
||||
if (sysctlbyname(name, &value, &size, nullptr, 0) != 0 || size != sizeof(value)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
struct PlatformState {
|
||||
mach_port_t host = mach_host_self();
|
||||
int thermal_token = -1;
|
||||
bool thermal_ready = false;
|
||||
std::string gpu_model;
|
||||
std::optional<uint32_t> gpu_cores;
|
||||
|
||||
~PlatformState() {
|
||||
if (thermal_ready) {
|
||||
notify_cancel(thermal_token);
|
||||
}
|
||||
mach_port_deallocate(mach_task_self(), host);
|
||||
}
|
||||
};
|
||||
|
||||
std::optional<int64_t> DictionaryNumber(CFDictionaryRef dictionary, CFStringRef key) {
|
||||
const auto* value = CFDictionaryGetValue(dictionary, key);
|
||||
int64_t number = 0;
|
||||
if (value == nullptr || CFGetTypeID(value) != CFNumberGetTypeID() ||
|
||||
!CFNumberGetValue(static_cast<CFNumberRef>(value), kCFNumberSInt64Type, &number) ||
|
||||
number < 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
std::string RegistryString(io_object_t service, CFStringRef key) {
|
||||
std::string result;
|
||||
auto* value = IORegistryEntryCreateCFProperty(service, key, kCFAllocatorDefault, 0);
|
||||
if (value == nullptr) {
|
||||
return result;
|
||||
}
|
||||
if (CFGetTypeID(value) == CFStringGetTypeID()) {
|
||||
char buffer[128] {};
|
||||
if (CFStringGetCString(static_cast<CFStringRef>(value), buffer, sizeof(buffer),
|
||||
kCFStringEncodingUTF8)) {
|
||||
result = buffer;
|
||||
}
|
||||
} else if (CFGetTypeID(value) == CFDataGetTypeID()) {
|
||||
// Intel Macs store the model as NUL-terminated data.
|
||||
const auto* data = static_cast<CFDataRef>(value);
|
||||
const auto* bytes = reinterpret_cast<const char*>(CFDataGetBytePtr(data));
|
||||
const auto length = static_cast<size_t>(CFDataGetLength(data));
|
||||
result.assign(bytes, strnlen(bytes, length));
|
||||
}
|
||||
CFRelease(value);
|
||||
return result;
|
||||
}
|
||||
|
||||
// IOAccelerator exposes whole-device statistics; there is no per-process figure.
|
||||
void SampleGpu(PlatformState& state, HostStats& stats) {
|
||||
io_iterator_t iterator = 0;
|
||||
if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceMatching("IOAccelerator"),
|
||||
&iterator) != KERN_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
const auto take_max = [](std::optional<double>& target, std::optional<int64_t> value) {
|
||||
if (value) {
|
||||
target = std::max(target.value_or(0.0), std::min(100.0, static_cast<double>(*value)));
|
||||
}
|
||||
};
|
||||
const auto add = [](std::optional<uint64_t>& target, std::optional<int64_t> value) {
|
||||
if (value) {
|
||||
target = target.value_or(0) + static_cast<uint64_t>(*value);
|
||||
}
|
||||
};
|
||||
for (io_object_t service = IOIteratorNext(iterator); service != 0;
|
||||
service = IOIteratorNext(iterator)) {
|
||||
if (state.gpu_model.empty()) {
|
||||
state.gpu_model = RegistryString(service, CFSTR("model"));
|
||||
auto* cores = IORegistryEntryCreateCFProperty(service, CFSTR("gpu-core-count"),
|
||||
kCFAllocatorDefault, 0);
|
||||
int64_t count = 0;
|
||||
if (cores != nullptr) {
|
||||
if (CFGetTypeID(cores) == CFNumberGetTypeID() &&
|
||||
CFNumberGetValue(static_cast<CFNumberRef>(cores), kCFNumberSInt64Type,
|
||||
&count) &&
|
||||
count > 0) {
|
||||
state.gpu_cores = static_cast<uint32_t>(count);
|
||||
}
|
||||
CFRelease(cores);
|
||||
}
|
||||
}
|
||||
auto* statistics = IORegistryEntryCreateCFProperty(service, CFSTR("PerformanceStatistics"),
|
||||
kCFAllocatorDefault, 0);
|
||||
if (statistics != nullptr) {
|
||||
if (CFGetTypeID(statistics) == CFDictionaryGetTypeID()) {
|
||||
const auto* values = static_cast<CFDictionaryRef>(statistics);
|
||||
take_max(stats.gpu_percent,
|
||||
DictionaryNumber(values, CFSTR("Device Utilization %")));
|
||||
take_max(stats.gpu_renderer_percent,
|
||||
DictionaryNumber(values, CFSTR("Renderer Utilization %")));
|
||||
take_max(stats.gpu_tiler_percent,
|
||||
DictionaryNumber(values, CFSTR("Tiler Utilization %")));
|
||||
add(stats.gpu_memory_in_use,
|
||||
DictionaryNumber(values, CFSTR("In use system memory")));
|
||||
add(stats.gpu_memory_allocated,
|
||||
DictionaryNumber(values, CFSTR("Alloc system memory")));
|
||||
add(stats.gpu_recoveries, DictionaryNumber(values, CFSTR("recoveryCount")));
|
||||
}
|
||||
CFRelease(statistics);
|
||||
}
|
||||
IOObjectRelease(service);
|
||||
}
|
||||
IOObjectRelease(iterator);
|
||||
stats.gpu_model = state.gpu_model;
|
||||
stats.gpu_cores = state.gpu_cores;
|
||||
}
|
||||
|
||||
void SampleThreads(HostStats& stats, Counters& now) {
|
||||
thread_act_array_t threads = nullptr;
|
||||
mach_msg_type_number_t count = 0;
|
||||
const auto task = mach_task_self();
|
||||
if (task_threads(task, &threads, &count) != KERN_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
stats.thread_count = count;
|
||||
for (mach_msg_type_number_t i = 0; i < count; i++) {
|
||||
thread_identifier_info_data_t identifier {};
|
||||
mach_msg_type_number_t size = THREAD_IDENTIFIER_INFO_COUNT;
|
||||
thread_basic_info_data_t basic {};
|
||||
mach_msg_type_number_t basic_size = THREAD_BASIC_INFO_COUNT;
|
||||
if (thread_info(threads[i], THREAD_IDENTIFIER_INFO,
|
||||
reinterpret_cast<thread_info_t>(&identifier), &size) == KERN_SUCCESS &&
|
||||
thread_info(threads[i], THREAD_BASIC_INFO, reinterpret_cast<thread_info_t>(&basic),
|
||||
&basic_size) == KERN_SUCCESS) {
|
||||
const auto to_ns = [](const time_value_t& time) {
|
||||
return static_cast<uint64_t>(time.seconds) * 1000000000u +
|
||||
static_cast<uint64_t>(time.microseconds) * 1000u;
|
||||
};
|
||||
auto& thread = now.threads[identifier.thread_id];
|
||||
thread.cpu_ns = to_ns(basic.user_time) + to_ns(basic.system_time);
|
||||
thread_extended_info_data_t extended {};
|
||||
mach_msg_type_number_t extended_size = THREAD_EXTENDED_INFO_COUNT;
|
||||
if (thread_info(threads[i], THREAD_EXTENDED_INFO,
|
||||
reinterpret_cast<thread_info_t>(&extended),
|
||||
&extended_size) == KERN_SUCCESS) {
|
||||
thread.name = std::string(extended.pth_name,
|
||||
strnlen(extended.pth_name, sizeof(extended.pth_name)));
|
||||
}
|
||||
}
|
||||
mach_port_deallocate(task, threads[i]);
|
||||
}
|
||||
vm_deallocate(task, reinterpret_cast<vm_address_t>(threads), count * sizeof(thread_act_t));
|
||||
}
|
||||
|
||||
void SamplePlatform(PlatformState& state, HostStats& stats, Counters& now) {
|
||||
SampleRusage(now);
|
||||
|
||||
host_cpu_load_info_data_t load {};
|
||||
mach_msg_type_number_t load_count = HOST_CPU_LOAD_INFO_COUNT;
|
||||
if (host_statistics(state.host, HOST_CPU_LOAD_INFO, reinterpret_cast<host_info_t>(&load),
|
||||
&load_count) == KERN_SUCCESS) {
|
||||
const uint64_t busy = static_cast<uint64_t>(load.cpu_ticks[CPU_STATE_USER]) +
|
||||
load.cpu_ticks[CPU_STATE_SYSTEM] + load.cpu_ticks[CPU_STATE_NICE];
|
||||
now.system_busy = busy;
|
||||
now.system_total = busy + load.cpu_ticks[CPU_STATE_IDLE];
|
||||
}
|
||||
|
||||
// Physical footprint is what Activity Monitor shows in its Memory column.
|
||||
task_vm_info_data_t vm_info {};
|
||||
mach_msg_type_number_t vm_info_count = TASK_VM_INFO_COUNT;
|
||||
if (task_info(mach_task_self(), TASK_VM_INFO, reinterpret_cast<task_info_t>(&vm_info),
|
||||
&vm_info_count) == KERN_SUCCESS) {
|
||||
stats.process_memory = vm_info.phys_footprint;
|
||||
stats.process_resident = vm_info.resident_size;
|
||||
stats.process_compressed = vm_info.compressed;
|
||||
stats.process_file_backed = vm_info.external;
|
||||
stats.process_virtual = vm_info.virtual_size;
|
||||
}
|
||||
rusage_info_v2 usage {};
|
||||
if (proc_pid_rusage(getpid(), RUSAGE_INFO_V2, reinterpret_cast<rusage_info_t*>(&usage)) == 0) {
|
||||
now.disk_read = usage.ri_diskio_bytesread;
|
||||
now.disk_write = usage.ri_diskio_byteswritten;
|
||||
}
|
||||
|
||||
stats.system_memory_total = SysctlValue<uint64_t>("hw.memsize");
|
||||
vm_size_t page = 0;
|
||||
vm_statistics64_data_t vm {};
|
||||
mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
|
||||
if (host_page_size(state.host, &page) == KERN_SUCCESS &&
|
||||
host_statistics64(state.host, HOST_VM_INFO64, reinterpret_cast<host_info64_t>(&vm),
|
||||
&count) == KERN_SUCCESS) {
|
||||
// Activity Monitor's "Memory Used": app memory + wired + compressed.
|
||||
const uint64_t app = vm.internal_page_count > vm.purgeable_count
|
||||
? vm.internal_page_count - vm.purgeable_count
|
||||
: 0;
|
||||
stats.system_memory_used = (app + vm.wire_count + vm.compressor_page_count) * page;
|
||||
stats.system_wired = static_cast<uint64_t>(vm.wire_count) * page;
|
||||
stats.system_compressed = static_cast<uint64_t>(vm.compressor_page_count) * page;
|
||||
stats.system_cached =
|
||||
(static_cast<uint64_t>(vm.external_page_count) + vm.purgeable_count) * page;
|
||||
stats.system_free = static_cast<uint64_t>(vm.free_count) * page;
|
||||
}
|
||||
|
||||
if (const auto swap = SysctlValue<xsw_usage>("vm.swapusage")) {
|
||||
stats.swap_used = swap->xsu_used;
|
||||
stats.swap_total = swap->xsu_total;
|
||||
}
|
||||
|
||||
// 1 = normal, 2 = warning, 4 = critical, as reported to dispatch memory-pressure sources.
|
||||
if (const auto level = SysctlValue<int>("kern.memorystatus_vm_pressure_level")) {
|
||||
stats.memory_pressure = *level >= 4 ? MemoryPressure::Critical
|
||||
: *level >= 2 ? MemoryPressure::Warning
|
||||
: MemoryPressure::Normal;
|
||||
}
|
||||
|
||||
// 0 nominal, 1 moderate, 2 heavy, 3 trapping, 4 sleeping.
|
||||
if (!state.thermal_ready) {
|
||||
state.thermal_ready = notify_register_check("com.apple.system.thermalpressurelevel",
|
||||
&state.thermal_token) == NOTIFY_STATUS_OK;
|
||||
}
|
||||
uint64_t thermal = 0;
|
||||
if (state.thermal_ready &&
|
||||
notify_get_state(state.thermal_token, &thermal) == NOTIFY_STATUS_OK) {
|
||||
stats.thermal_pressure = thermal >= 3 ? ThermalPressure::Critical
|
||||
: thermal == 2 ? ThermalPressure::Heavy
|
||||
: thermal == 1 ? ThermalPressure::Moderate
|
||||
: ThermalPressure::Nominal;
|
||||
}
|
||||
|
||||
SampleThreads(stats, now);
|
||||
SampleGpu(state, stats);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
using ProcValues = std::unordered_map<std::string, uint64_t>;
|
||||
|
||||
// Reads "<key>: <number> [kB]" lines; kB values are returned in bytes.
|
||||
ProcValues ReadProcValues(const char* path) {
|
||||
ProcValues values;
|
||||
std::ifstream file(path);
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
const auto colon = line.find(':');
|
||||
if (colon == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
std::istringstream stream(line.substr(colon + 1));
|
||||
uint64_t number = 0;
|
||||
std::string unit;
|
||||
if (stream >> number) {
|
||||
stream >> unit;
|
||||
values[line.substr(0, colon)] = unit == "kB" ? number * 1024u : number;
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
std::optional<uint64_t> Find(const ProcValues& values, const char* key) {
|
||||
const auto it = values.find(key);
|
||||
return it != values.end() ? std::optional(it->second) : std::nullopt;
|
||||
}
|
||||
|
||||
struct PlatformState {};
|
||||
|
||||
void SampleThreads(HostStats& stats, Counters& now) {
|
||||
static const double ns_per_tick = 1e9 / static_cast<double>(std::max(sysconf(_SC_CLK_TCK), 1L));
|
||||
std::error_code error;
|
||||
uint32_t count = 0;
|
||||
for (std::filesystem::directory_iterator it("/proc/self/task", error), end; !error && it != end;
|
||||
it.increment(error)) {
|
||||
count++;
|
||||
std::ifstream stat_file(it->path() / "stat");
|
||||
std::string stat;
|
||||
uint64_t id = 0;
|
||||
if (!(stat_file >> id)) {
|
||||
continue; // The thread may have exited during enumeration.
|
||||
}
|
||||
std::getline(stat_file, stat);
|
||||
const auto close = stat.rfind(')');
|
||||
if (close == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
// Fields after the command name start at field 3 (state); utime and stime are 14 and 15.
|
||||
std::istringstream fields(stat.substr(close + 2));
|
||||
std::string field;
|
||||
for (int index = 3; index < 14 && fields >> field; index++) {
|
||||
}
|
||||
uint64_t user = 0;
|
||||
uint64_t system = 0;
|
||||
if (!(fields >> user >> system)) {
|
||||
continue;
|
||||
}
|
||||
auto& thread = now.threads[id];
|
||||
thread.cpu_ns = static_cast<uint64_t>(static_cast<double>(user + system) * ns_per_tick);
|
||||
std::ifstream name_file(it->path() / "comm");
|
||||
std::getline(name_file, thread.name);
|
||||
}
|
||||
if (!error) {
|
||||
stats.thread_count = count;
|
||||
}
|
||||
}
|
||||
|
||||
void SamplePlatform(PlatformState& /*state*/, HostStats& stats, Counters& now) {
|
||||
SampleRusage(now);
|
||||
|
||||
std::ifstream cpu_file("/proc/stat");
|
||||
std::string label;
|
||||
if (cpu_file >> label && label == "cpu") {
|
||||
uint64_t total = 0;
|
||||
uint64_t idle = 0;
|
||||
uint64_t value = 0;
|
||||
for (int index = 0; index < 8 && cpu_file >> value; index++) {
|
||||
total += value;
|
||||
if (index == 3 || index == 4) { // idle, iowait
|
||||
idle += value;
|
||||
}
|
||||
}
|
||||
now.system_total = total;
|
||||
now.system_busy = total - idle;
|
||||
}
|
||||
|
||||
const auto status = ReadProcValues("/proc/self/status");
|
||||
stats.process_memory = Find(status, "VmRSS");
|
||||
stats.process_resident = Find(status, "VmRSS");
|
||||
stats.process_file_backed = Find(status, "RssFile");
|
||||
stats.process_compressed = Find(status, "VmSwap");
|
||||
stats.process_virtual = Find(status, "VmSize");
|
||||
|
||||
const auto io = ReadProcValues("/proc/self/io");
|
||||
now.disk_read = Find(io, "read_bytes");
|
||||
now.disk_write = Find(io, "write_bytes");
|
||||
|
||||
const auto memory = ReadProcValues("/proc/meminfo");
|
||||
const auto total = Find(memory, "MemTotal");
|
||||
const auto available = Find(memory, "MemAvailable");
|
||||
if (total && available && *available <= *total) {
|
||||
stats.system_memory_total = total;
|
||||
stats.system_memory_used = *total - *available;
|
||||
}
|
||||
stats.system_free = Find(memory, "MemFree");
|
||||
if (const auto cached = Find(memory, "Cached")) {
|
||||
stats.system_cached = *cached + Find(memory, "Buffers").value_or(0);
|
||||
}
|
||||
const auto swap_total = Find(memory, "SwapTotal");
|
||||
const auto swap_free = Find(memory, "SwapFree");
|
||||
if (swap_total && swap_free && *swap_free <= *swap_total) {
|
||||
stats.swap_total = swap_total;
|
||||
stats.swap_used = *swap_total - *swap_free;
|
||||
}
|
||||
|
||||
// Pressure stall information: share of the last 10 s some task waited on memory.
|
||||
std::ifstream pressure("/proc/pressure/memory");
|
||||
std::string kind;
|
||||
std::string average;
|
||||
double stalled = 0.0;
|
||||
if (pressure >> kind >> average && kind == "some" && average.rfind("avg10=", 0) == 0 &&
|
||||
std::istringstream(average.substr(6)) >> stalled) {
|
||||
stats.memory_pressure = stalled >= 20.0 ? MemoryPressure::Critical
|
||||
: stalled >= 5.0 ? MemoryPressure::Warning
|
||||
: MemoryPressure::Normal;
|
||||
} else if (total && available && *total != 0) {
|
||||
const double free_share = static_cast<double>(*available) / static_cast<double>(*total);
|
||||
stats.memory_pressure = free_share < 0.10 ? MemoryPressure::Critical
|
||||
: free_share < 0.25 ? MemoryPressure::Warning
|
||||
: MemoryPressure::Normal;
|
||||
}
|
||||
|
||||
SampleThreads(stats, now);
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
constexpr size_t BUSIEST_THREADS = 6;
|
||||
|
||||
} // namespace
|
||||
|
||||
struct HostStatsSampler::Impl {
|
||||
PlatformState state;
|
||||
Counters last;
|
||||
uint32_t logical_cores = std::max(std::thread::hardware_concurrency(), 1u);
|
||||
bool has_last = false;
|
||||
};
|
||||
|
||||
HostStatsSampler::HostStatsSampler(): m_impl(std::make_unique<Impl>()) {}
|
||||
|
||||
HostStatsSampler::~HostStatsSampler() = default;
|
||||
|
||||
HostStats HostStatsSampler::Sample() {
|
||||
HostStats stats;
|
||||
stats.logical_cores = m_impl->logical_cores;
|
||||
|
||||
Counters now;
|
||||
now.wall_ns = WallNanoseconds();
|
||||
SamplePlatform(m_impl->state, stats, now);
|
||||
|
||||
const auto& last = m_impl->last;
|
||||
const auto elapsed = static_cast<double>(now.wall_ns - last.wall_ns) / 1e9;
|
||||
if (m_impl->has_last && elapsed > 0.0) {
|
||||
const auto rate = [&](std::optional<uint64_t> current,
|
||||
std::optional<uint64_t> previous) -> std::optional<double> {
|
||||
if (!current || !previous || *current < *previous) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return static_cast<double>(*current - *previous) / elapsed;
|
||||
};
|
||||
if (const auto cpu = rate(now.process_cpu_ns, last.process_cpu_ns)) {
|
||||
stats.process_cpu_percent =
|
||||
std::clamp(*cpu / 1e9 * 100.0 / stats.logical_cores, 0.0, 100.0);
|
||||
}
|
||||
if (now.system_total && last.system_total && now.system_busy && last.system_busy &&
|
||||
*now.system_total > *last.system_total && *now.system_busy >= *last.system_busy) {
|
||||
stats.system_cpu_percent =
|
||||
std::clamp(static_cast<double>(*now.system_busy - *last.system_busy) /
|
||||
static_cast<double>(*now.system_total - *last.system_total) * 100.0,
|
||||
0.0, 100.0);
|
||||
}
|
||||
stats.context_switches_per_s = rate(now.context_switches, last.context_switches);
|
||||
stats.page_faults_per_s = rate(now.page_faults, last.page_faults);
|
||||
stats.page_ins_per_s = rate(now.page_ins, last.page_ins);
|
||||
stats.disk_read_per_s = rate(now.disk_read, last.disk_read);
|
||||
stats.disk_write_per_s = rate(now.disk_write, last.disk_write);
|
||||
|
||||
for (const auto& [id, thread]: now.threads) {
|
||||
// A thread missing from the last sample started during this interval.
|
||||
const auto previous = last.threads.find(id);
|
||||
const uint64_t before = previous != last.threads.end() ? previous->second.cpu_ns : 0;
|
||||
if (thread.cpu_ns < before) {
|
||||
continue;
|
||||
}
|
||||
const double percent =
|
||||
static_cast<double>(thread.cpu_ns - before) / 1e9 / elapsed * 100.0;
|
||||
stats.busiest_threads.push_back(
|
||||
{thread.name.empty() ? "thread " + std::to_string(id) : thread.name, percent});
|
||||
}
|
||||
std::sort(stats.busiest_threads.begin(), stats.busiest_threads.end(),
|
||||
[](const auto& a, const auto& b) { return a.cpu_percent > b.cpu_percent; });
|
||||
if (stats.busiest_threads.size() > BUSIEST_THREADS) {
|
||||
stats.busiest_threads.resize(BUSIEST_THREADS);
|
||||
}
|
||||
}
|
||||
m_impl->last = std::move(now);
|
||||
m_impl->has_last = true;
|
||||
return stats;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -1,86 +0,0 @@
|
||||
#ifndef KYTY_COMMON_HOST_STATS_H_
|
||||
#define KYTY_COMMON_HOST_STATS_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Common {
|
||||
|
||||
enum class MemoryPressure { Unknown, Normal, Warning, Critical };
|
||||
enum class ThermalPressure { Unknown, Nominal, Moderate, Heavy, Critical };
|
||||
|
||||
struct ThreadUsage {
|
||||
std::string name;
|
||||
double cpu_percent = 0.0; // Of one core.
|
||||
};
|
||||
|
||||
// Host-wide and process-wide resource usage. Missing values are std::nullopt; rates are per
|
||||
// second since the previous sample.
|
||||
struct HostStats {
|
||||
uint32_t logical_cores = 0;
|
||||
|
||||
// Processing.
|
||||
std::optional<double> process_cpu_percent; // Share of all logical cores, 0-100.
|
||||
std::optional<double> system_cpu_percent; // Whole machine, 0-100.
|
||||
std::optional<uint32_t> thread_count;
|
||||
std::vector<ThreadUsage> busiest_threads; // Busiest first.
|
||||
std::optional<double> context_switches_per_s;
|
||||
std::optional<double> page_faults_per_s;
|
||||
std::optional<double> page_ins_per_s; // Faults that read from disk.
|
||||
ThermalPressure thermal_pressure = ThermalPressure::Unknown;
|
||||
|
||||
// Emulator process memory.
|
||||
std::optional<uint64_t> process_memory; // Physical footprint / resident set.
|
||||
std::optional<uint64_t> process_resident;
|
||||
std::optional<uint64_t> process_compressed; // Compressed or swapped out.
|
||||
std::optional<uint64_t> process_file_backed;
|
||||
std::optional<uint64_t> process_virtual; // Reserved address space.
|
||||
|
||||
// System memory.
|
||||
std::optional<uint64_t> system_memory_used;
|
||||
std::optional<uint64_t> system_memory_total;
|
||||
std::optional<uint64_t> system_wired;
|
||||
std::optional<uint64_t> system_compressed;
|
||||
std::optional<uint64_t> system_cached; // File cache, reclaimable.
|
||||
std::optional<uint64_t> system_free;
|
||||
std::optional<uint64_t> swap_used;
|
||||
std::optional<uint64_t> swap_total;
|
||||
MemoryPressure memory_pressure = MemoryPressure::Unknown;
|
||||
|
||||
// Disk I/O of the emulator process.
|
||||
std::optional<double> disk_read_per_s;
|
||||
std::optional<double> disk_write_per_s;
|
||||
|
||||
// GPU, whole device (macOS IOAccelerator).
|
||||
std::string gpu_model;
|
||||
std::optional<uint32_t> gpu_cores;
|
||||
std::optional<double> gpu_percent;
|
||||
std::optional<double> gpu_renderer_percent;
|
||||
std::optional<double> gpu_tiler_percent;
|
||||
std::optional<uint64_t> gpu_memory_in_use; // System memory the GPU driver has in use.
|
||||
std::optional<uint64_t> gpu_memory_allocated;
|
||||
std::optional<uint64_t> gpu_recoveries; // GPU resets since boot.
|
||||
};
|
||||
|
||||
// May block for a few milliseconds (thread enumeration, OS registry reads), so call it off the
|
||||
// render thread. The first sample has no rates.
|
||||
class HostStatsSampler final {
|
||||
public:
|
||||
HostStatsSampler();
|
||||
~HostStatsSampler();
|
||||
HostStatsSampler(const HostStatsSampler&) = delete;
|
||||
HostStatsSampler& operator=(const HostStatsSampler&) = delete;
|
||||
|
||||
[[nodiscard]] HostStats Sample();
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_HOST_STATS_H_ */
|
||||
@@ -1,53 +0,0 @@
|
||||
#include "common/localization.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
#include "localizationData.h"
|
||||
|
||||
namespace Common::Localization {
|
||||
namespace {
|
||||
|
||||
constexpr std::array<std::string_view, 30> Locales = {
|
||||
"ja-JP", "en-US", "fr-FR", "es-ES", "de-DE", "it-IT", "nl-NL", "pt-PT",
|
||||
"ru-RU", "ko-KR", "zh-Hant", "zh-Hans", "fi-FI", "sv-SE", "da-DK", "no-NO",
|
||||
"pl-PL", "pt-BR", "en-GB", "tr-TR", "es-419", "ar-AE", "fr-CA", "cs-CZ",
|
||||
"hu-HU", "el-GR", "ro-RO", "th-TH", "vi-VN", "id-ID"};
|
||||
|
||||
size_t LanguageIndex(int console_language) {
|
||||
return console_language >= 0 && console_language < static_cast<int>(Locales.size())
|
||||
? static_cast<size_t>(console_language)
|
||||
: 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string_view LocaleName(int console_language) {
|
||||
return Locales[LanguageIndex(console_language)];
|
||||
}
|
||||
|
||||
std::string_view Get(int console_language, std::string_view key) {
|
||||
static const auto catalogs = [] {
|
||||
std::array<nlohmann::json, Locales.size()> result;
|
||||
for (size_t i = 0; i < Locales.size(); ++i) {
|
||||
const auto bytes = EmbeddedCatalog(Locales[i]);
|
||||
result[i] = nlohmann::json::parse(bytes.begin(), bytes.end(), nullptr, false);
|
||||
}
|
||||
return result;
|
||||
}();
|
||||
for (const auto index: std::array {LanguageIndex(console_language), size_t {1}}) {
|
||||
const auto& catalog = catalogs[index];
|
||||
const auto found = catalog.find(key);
|
||||
if (found != catalog.end() && found->is_string()) {
|
||||
const auto& text = found->get_ref<const std::string&>();
|
||||
if (!text.empty()) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
} // namespace Common::Localization
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace Common::Localization {
|
||||
|
||||
// Console language indices follow the system configuration; invalid indices use English.
|
||||
[[nodiscard]] std::string_view LocaleName(int console_language);
|
||||
|
||||
// Translations have static lifetime. Missing translations use English, then the supplied key.
|
||||
[[nodiscard]] std::string_view Get(int console_language, std::string_view key);
|
||||
|
||||
} // namespace Common::Localization
|
||||
@@ -3,9 +3,9 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fmt/format.h>
|
||||
#include <memory>
|
||||
@@ -46,8 +46,8 @@ std::shared_ptr<spdlog::logger> MakeFileLogger(std::string name
|
||||
std::filesystem::create_directories(parent);
|
||||
}
|
||||
|
||||
const bool truncate = std::getenv(Log::RelaunchedVariable) == nullptr;
|
||||
auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(path.native(), truncate);
|
||||
auto sink =
|
||||
std::make_shared<spdlog::sinks::basic_file_sink_mt>(Common::PathToString(path), true);
|
||||
return MakeLogger(std::move(name), std::move(sink));
|
||||
}
|
||||
|
||||
@@ -129,14 +129,6 @@ static void WriteImpl(std::string_view text, fmt::text_style style = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
void WriteToConsoleAndLog(std::string_view text) {
|
||||
WriteImpl(text);
|
||||
if (g_initialized && g_direction != Direction::Console) {
|
||||
WriteStdout(text);
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
|
||||
void WriteFatal(std::string_view text) {
|
||||
if (g_direction == Direction::Silent || !g_initialized) {
|
||||
WriteStdout(text);
|
||||
@@ -157,19 +149,25 @@ void WriteFatal(fmt::text_style style, std::string_view text) {
|
||||
Flush();
|
||||
}
|
||||
|
||||
void Initialize() {
|
||||
KYTY_SUBSYSTEM_INIT(Log) {
|
||||
g_initialized = true;
|
||||
switch (Config::GetPrintfDirection()) {
|
||||
case Config::LogDirection::Silent: g_direction = Direction::Silent; break;
|
||||
case Config::LogDirection::Console: g_direction = Direction::Console; break;
|
||||
case Config::LogDirection::File: g_direction = Direction::File; break;
|
||||
case Config::OutputDirection::Silent: g_direction = Direction::Silent; break;
|
||||
case Config::OutputDirection::Console: g_direction = Direction::Console; break;
|
||||
case Config::OutputDirection::File: g_direction = Direction::File; break;
|
||||
}
|
||||
g_output_file =
|
||||
(g_direction == Direction::File ? Config::GetPrintfOutputFile() : std::filesystem::path {});
|
||||
SetupLogger();
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Log) {
|
||||
Flush();
|
||||
std::lock_guard lock(g_logger_mutex);
|
||||
g_logger.reset();
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_DESTROY(Log) {
|
||||
Flush();
|
||||
std::lock_guard lock(g_logger_mutex);
|
||||
g_logger.reset();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define KYTY_COMMON_LOGGING_LOG_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/subsystems.h"
|
||||
|
||||
#include <fmt/color.h>
|
||||
#include <fmt/printf.h>
|
||||
@@ -9,27 +10,14 @@
|
||||
|
||||
namespace Log {
|
||||
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
|
||||
struct Lifecycle {
|
||||
static constexpr const char* name = "Log";
|
||||
static constexpr auto initialize = Log::Initialize;
|
||||
static constexpr auto shutdown = Log::Shutdown;
|
||||
static constexpr auto emergency_shutdown = Log::Shutdown;
|
||||
};
|
||||
KYTY_SUBSYSTEM_DEFINE(Log);
|
||||
|
||||
enum class Direction { Silent, Console, File };
|
||||
|
||||
// Set by LoadExec for the program it starts, which continues the log file of the one it replaces
|
||||
// instead of starting it over.
|
||||
inline constexpr const char* RelaunchedVariable = "KYTY_RELAUNCHED";
|
||||
|
||||
Direction GetDirection();
|
||||
bool IsSilent();
|
||||
void Write(std::string_view text);
|
||||
void Write(fmt::text_style style, std::string_view text);
|
||||
void WriteToConsoleAndLog(std::string_view text);
|
||||
void WriteFatal(std::string_view text);
|
||||
void WriteFatal(fmt::text_style style, std::string_view text);
|
||||
void Flush();
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef KYTY_COMMON_MAGICENUM_H_
|
||||
#define KYTY_COMMON_MAGICENUM_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "magic_enum.hpp" // IWYU pragma: export
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <typename E>
|
||||
inline std::string EnumName(E v) {
|
||||
auto str = magic_enum::enum_name(v);
|
||||
return std::string(str.data(), str.length());
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline std::string EnumName8(E v) {
|
||||
auto str = magic_enum::enum_name(v);
|
||||
return std::string(str.data(), str.length());
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline E EnumValue(const std::string& str, E default_value) {
|
||||
auto v = magic_enum::enum_cast<E>(str.c_str());
|
||||
if (v.has_value()) {
|
||||
return v.value();
|
||||
}
|
||||
return default_value;
|
||||
}
|
||||
|
||||
#define KYTY_ENUM_RANGE(e, mx, mn) \
|
||||
namespace magic_enum::customize { \
|
||||
template <> \
|
||||
struct enum_range<e> { \
|
||||
static constexpr int min = (mx); \
|
||||
static constexpr int max = (mn); \
|
||||
}; \
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_MAGICENUM_H_ */
|
||||
@@ -30,6 +30,8 @@ struct sys_dbg_stack_info_t {
|
||||
#endif
|
||||
};
|
||||
|
||||
void SysStackUsage(sys_dbg_stack_info_t& s); // NOLINT(google-runtime-references)
|
||||
void SysStackWalk(void** stack, int* depth);
|
||||
void SysStackUsage(sys_dbg_stack_info_t& s); // NOLINT(google-runtime-references)
|
||||
void SysStackUsagePrint(sys_dbg_stack_info_t& stack); // NOLINT(google-runtime-references)
|
||||
|
||||
#endif /* KYTY_COMMON_PLATFORM_SYSDBG_H_ */
|
||||
|
||||
@@ -18,6 +18,14 @@ enum sys_file_cache_type_t {
|
||||
SYS_FILE_CACHE_SEQUENTIAL_SCAN = 2 // NOLINT(readability-identifier-naming)
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct sys_file_find_t {
|
||||
std::filesystem::path path_with_name;
|
||||
SysFileTimeStruct last_access_time;
|
||||
SysFileTimeStruct last_write_time;
|
||||
uint64_t size;
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct sys_dir_entry_t {
|
||||
std::string name;
|
||||
@@ -44,8 +52,8 @@ uint64_t SysFileTell(sys_file_t& f); // NOLINT(google-runt
|
||||
bool SysFileTruncate(sys_file_t& f, uint64_t size); // NOLINT(google-runtime-references)
|
||||
bool SysFileUnlink(sys_file_t& f,
|
||||
const std::filesystem::path& name); // NOLINT(google-runtime-references)
|
||||
void SysFileWrite(uint32_t n, sys_file_t& f); // NOLINT(google-runtime-references)
|
||||
uint64_t SysFileSize(const std::filesystem::path& file_name);
|
||||
bool SysFileGetInfo(const std::filesystem::path& name, bool* is_file, uint64_t* size);
|
||||
bool SysFileIsError(sys_file_t& f); // NOLINT(google-runtime-references)
|
||||
bool SysFileIsDirectoryExisting(const std::filesystem::path& path);
|
||||
bool SysFileIsFileExisting(const std::filesystem::path& name);
|
||||
@@ -68,9 +76,11 @@ bool SysFileSetLastWriteTimeUtc(const std::filesystem::path& name, SysFileTimeSt
|
||||
bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
|
||||
SysFileTimeStruct& access, SysFileTimeStruct& write);
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out);
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out);
|
||||
bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
bool SysFileRenameFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
bool SysFileMoveFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
void SysFileRemoveReadonly(const std::filesystem::path& name);
|
||||
|
||||
#endif /* KYTY_COMMON_PLATFORM_SYSFILEIO_H_ */
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <execinfo.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/types.h>
|
||||
@@ -16,6 +17,56 @@
|
||||
#include <libgen.h> // POSIX basename() lives here on macOS, not in <cstring>
|
||||
#endif
|
||||
|
||||
// Avoid unwinding a guest-owned stack.
|
||||
static bool OnOwnStack() {
|
||||
const char* probe = reinterpret_cast<const char*>(&probe);
|
||||
|
||||
pthread_attr_t attr {};
|
||||
#if defined(__APPLE__)
|
||||
const auto* top = static_cast<const char*>(pthread_get_stackaddr_np(pthread_self()));
|
||||
const auto size = pthread_get_stacksize_np(pthread_self());
|
||||
(void)attr;
|
||||
return top != nullptr && size != 0 && probe < top && probe >= top - size;
|
||||
#else
|
||||
if (pthread_getattr_np(pthread_self(), &attr) != 0) {
|
||||
return false;
|
||||
}
|
||||
void* base = nullptr;
|
||||
size_t size = 0;
|
||||
const bool ok = pthread_attr_getstack(&attr, &base, &size) == 0 && base != nullptr && size != 0;
|
||||
pthread_attr_destroy(&attr);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
const auto* low = static_cast<const char*>(base);
|
||||
return probe >= low && probe < low + size;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SysStackWalk(void** stack, int* depth) {
|
||||
if (stack == nullptr || depth == nullptr || *depth <= 0) {
|
||||
if (depth != nullptr) {
|
||||
*depth = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!OnOwnStack()) {
|
||||
*depth = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const int n = ::backtrace(stack, *depth);
|
||||
*depth = (n < 0 ? 0 : n);
|
||||
}
|
||||
|
||||
void SysStackUsagePrint(sys_dbg_stack_info_t& stack) {
|
||||
printf("stack: (0x%" PRIx64 ", %" PRIu64 ")\n", static_cast<uint64_t>(stack.commited_addr),
|
||||
static_cast<uint64_t>(stack.commited_size));
|
||||
printf("code: (0x%" PRIx64 ", %" PRIu64 ")\n", static_cast<uint64_t>(stack.code_addr),
|
||||
static_cast<uint64_t>(stack.code_size));
|
||||
}
|
||||
|
||||
void SysStackUsage(sys_dbg_stack_info_t& s) {
|
||||
pid_t pid = getpid();
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
@@ -54,7 +53,7 @@ struct sys_file_t {
|
||||
#endif
|
||||
|
||||
static std::filesystem::path get_internal_name(const std::filesystem::path& name) {
|
||||
return name.empty() || name.is_absolute() ? name : (std::filesystem::path(".") / name);
|
||||
return name.is_absolute() ? name : (std::filesystem::path(".") / name);
|
||||
}
|
||||
|
||||
// Pass access-pattern hints to the host.
|
||||
@@ -152,29 +151,46 @@ void SysFileWrite(const void* data, uint32_t size, sys_file_t& f, uint32_t* byte
|
||||
}
|
||||
}
|
||||
|
||||
static sys_file_t* OpenHostFile(const std::filesystem::path& file_name, const char* mode,
|
||||
sys_file_cache_type_t cache_type) {
|
||||
auto* ret = new sys_file_t;
|
||||
ret->f = fopen(get_internal_name(file_name).c_str(), mode);
|
||||
ret->type = ret->f == nullptr ? SYS_FILE_ERROR : SYS_FILE_FILE;
|
||||
if (ret->f != nullptr) {
|
||||
// Independent handles must observe writes and truncation immediately.
|
||||
setvbuf(ret->f, nullptr, _IONBF, 0);
|
||||
apply_cache_hint(ret->f, cache_type);
|
||||
}
|
||||
return ret;
|
||||
void SysFileWrite(uint32_t n, sys_file_t& f) {
|
||||
SysFileWrite(&n, 4, f);
|
||||
}
|
||||
|
||||
sys_file_t* SysFileCreate(const std::filesystem::path& file_name) {
|
||||
auto* ret = OpenHostFile(file_name, "w+", SYS_FILE_CACHE_AUTO);
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto real_name = get_internal_name(file_name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
ret->f = fopen(real_name_str.c_str(), "w+");
|
||||
|
||||
if (ret->f == nullptr) {
|
||||
printf("can't create file: %s\n", file_name.c_str());
|
||||
printf("can't create file: %s\n", real_name_str.c_str());
|
||||
}
|
||||
|
||||
ret->type = SYS_FILE_FILE;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenR(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
|
||||
return OpenHostFile(file_name, "r", cache_type);
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
ret->type = SYS_FILE_FILE;
|
||||
|
||||
auto internal_name = get_internal_name(file_name);
|
||||
auto internal_name_str = internal_name.string();
|
||||
|
||||
FILE* f = fopen(internal_name_str.c_str(), "r");
|
||||
|
||||
if (f == nullptr) {
|
||||
ret->type = SYS_FILE_ERROR;
|
||||
}
|
||||
|
||||
apply_cache_hint(f, cache_type);
|
||||
|
||||
ret->f = f;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpen(uint8_t* buf, uint32_t buf_size) {
|
||||
@@ -202,12 +218,46 @@ sys_file_t* SysFileCreate() {
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenW(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
|
||||
return OpenHostFile(file_name, "r+", cache_type);
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto real_name = get_internal_name(file_name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
FILE* f = fopen(real_name_str.c_str(), "r+");
|
||||
|
||||
if (f == nullptr) {
|
||||
ret->type = SYS_FILE_ERROR;
|
||||
} else {
|
||||
ret->type = SYS_FILE_FILE;
|
||||
}
|
||||
|
||||
apply_cache_hint(f, cache_type);
|
||||
|
||||
ret->f = f;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenRw(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t cache_type) {
|
||||
return OpenHostFile(file_name, "r+", cache_type);
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto real_name = get_internal_name(file_name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
FILE* f = fopen(real_name_str.c_str(), "r+");
|
||||
|
||||
if (f == nullptr) {
|
||||
ret->type = SYS_FILE_ERROR;
|
||||
} else {
|
||||
ret->type = SYS_FILE_FILE;
|
||||
}
|
||||
|
||||
apply_cache_hint(f, cache_type);
|
||||
|
||||
ret->f = f;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SysFileClose(sys_file_t* f) {
|
||||
@@ -250,21 +300,10 @@ uint64_t SysFileSize(sys_file_t& f) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool SysFileGetInfo(const std::filesystem::path& name, bool* is_file, uint64_t* size) {
|
||||
const auto path = get_internal_name(name);
|
||||
struct stat info {};
|
||||
if (stat(path.c_str(), &info) != 0) {
|
||||
return false;
|
||||
}
|
||||
*is_file = !S_ISDIR(info.st_mode);
|
||||
*size = *is_file ? static_cast<uint64_t>(info.st_size) : 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t SysFileSize(const std::filesystem::path& file_name) {
|
||||
bool is_file;
|
||||
uint64_t size = 0;
|
||||
SysFileGetInfo(file_name, &is_file, &size);
|
||||
sys_file_t* f = SysFileOpenR(file_name);
|
||||
uint64_t size = SysFileSize(*f);
|
||||
SysFileClose(f);
|
||||
return size;
|
||||
}
|
||||
|
||||
@@ -317,15 +356,29 @@ bool SysFileIsError(sys_file_t& f) {
|
||||
}
|
||||
|
||||
bool SysFileIsDirectoryExisting(const std::filesystem::path& path) {
|
||||
bool is_file;
|
||||
uint64_t size;
|
||||
return SysFileGetInfo(path, &is_file, &size) && !is_file;
|
||||
auto real_name = get_internal_name(path);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
struct stat s {};
|
||||
|
||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return S_ISDIR(s.st_mode); // NOLINT
|
||||
}
|
||||
|
||||
bool SysFileIsFileExisting(const std::filesystem::path& name) {
|
||||
bool is_file;
|
||||
uint64_t size;
|
||||
return SysFileGetInfo(name, &is_file, &size) && is_file;
|
||||
auto real_name = get_internal_name(name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
struct stat s {};
|
||||
|
||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !S_ISDIR(s.st_mode); // NOLINT
|
||||
}
|
||||
|
||||
bool SysFileCreateDirectory(const std::filesystem::path& path) {
|
||||
@@ -560,6 +613,51 @@ bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
|
||||
// }
|
||||
}
|
||||
|
||||
// Recursively collect regular files.
|
||||
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out) {
|
||||
auto real_path = get_internal_name(path);
|
||||
|
||||
DIR* dir = opendir(real_path.string().c_str());
|
||||
if (dir == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const dirent* entry = readdir(dir); entry != nullptr; entry = readdir(dir)) {
|
||||
const std::string file_name(entry->d_name);
|
||||
|
||||
if (file_name == "." || file_name == "..") {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto child = real_path / file_name;
|
||||
struct stat s {};
|
||||
|
||||
// lstat, so a symlink is never followed into a cycle during the recursive walk.
|
||||
if (0 != lstat(child.string().c_str(), &s)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (S_ISDIR(s.st_mode)) {
|
||||
SysFileFindFiles(child, out);
|
||||
} else if (S_ISREG(s.st_mode)) {
|
||||
sys_file_find_t r {};
|
||||
|
||||
r.path_with_name = child;
|
||||
r.size = static_cast<uint64_t>(s.st_size);
|
||||
r.last_access_time.is_invalid = false;
|
||||
r.last_access_time.time = s.st_atime;
|
||||
r.last_access_time.nanos = KYTY_STAT_ATIME_NS(s);
|
||||
r.last_write_time.is_invalid = false;
|
||||
r.last_write_time.time = s.st_mtime;
|
||||
r.last_write_time.nanos = KYTY_STAT_MTIME_NS(s);
|
||||
|
||||
out.push_back(r);
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
// Keep "." and ".." to match FindFirstFileW.
|
||||
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out) {
|
||||
auto real_path = get_internal_name(path);
|
||||
@@ -595,7 +693,7 @@ bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::pa
|
||||
!error;
|
||||
}
|
||||
|
||||
bool SysFileRenameFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
|
||||
bool SysFileMoveFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
|
||||
auto real_src = get_internal_name(src);
|
||||
auto real_dst = get_internal_name(dst);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#else
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/platform/sysVirtual.h"
|
||||
#include "common/virtualMemory.h"
|
||||
|
||||
#include <atomic>
|
||||
@@ -27,12 +28,13 @@
|
||||
#define KYTY_FIXED_NOREPLACE
|
||||
#endif
|
||||
|
||||
namespace Common::VirtualMemory {
|
||||
namespace Common {
|
||||
|
||||
static pthread_mutex_t g_virtual_mutex {};
|
||||
static std::map<uintptr_t, size_t>* g_allocs = nullptr;
|
||||
static std::map<uintptr_t, size_t>* g_allocs = nullptr;
|
||||
static std::map<uintptr_t, int>* g_protects = nullptr;
|
||||
|
||||
void Init() {
|
||||
void SysVirtualInit() {
|
||||
pthread_mutexattr_t attr {};
|
||||
|
||||
pthread_mutexattr_init(&attr);
|
||||
@@ -44,27 +46,43 @@ void Init() {
|
||||
pthread_mutex_init(&g_virtual_mutex, &attr);
|
||||
pthread_mutexattr_destroy(&attr);
|
||||
|
||||
g_allocs = new std::map<uintptr_t, size_t>;
|
||||
g_allocs = new std::map<uintptr_t, size_t>;
|
||||
g_protects = new std::map<uintptr_t, int>;
|
||||
}
|
||||
|
||||
static int get_protection_flag(Mode mode) {
|
||||
static int get_protection_flag(VirtualMemory::Mode mode) {
|
||||
int protect = PROT_NONE;
|
||||
switch (mode) {
|
||||
case Mode::Read: protect = PROT_READ; break;
|
||||
case Mode::Write:
|
||||
case Mode::ReadWrite: protect = PROT_READ | PROT_WRITE; break; // NOLINT
|
||||
case Mode::Execute: protect = PROT_EXEC; break;
|
||||
case Mode::ExecuteRead: protect = PROT_EXEC | PROT_READ; break; // NOLINT
|
||||
case Mode::ExecuteWrite:
|
||||
case Mode::ExecuteReadWrite:
|
||||
case VirtualMemory::Mode::Read: protect = PROT_READ; break;
|
||||
case VirtualMemory::Mode::Write:
|
||||
case VirtualMemory::Mode::ReadWrite: protect = PROT_READ | PROT_WRITE; break; // NOLINT
|
||||
case VirtualMemory::Mode::Execute: protect = PROT_EXEC; break;
|
||||
case VirtualMemory::Mode::ExecuteRead: protect = PROT_EXEC | PROT_READ; break; // NOLINT
|
||||
case VirtualMemory::Mode::ExecuteWrite:
|
||||
case VirtualMemory::Mode::ExecuteReadWrite:
|
||||
protect = PROT_EXEC | PROT_WRITE | PROT_READ;
|
||||
break; // NOLINT
|
||||
case Mode::NoAccess:
|
||||
case VirtualMemory::Mode::NoAccess:
|
||||
default: protect = PROT_NONE; break;
|
||||
}
|
||||
return protect;
|
||||
}
|
||||
|
||||
static VirtualMemory::Mode get_protection_flag(int mode) {
|
||||
switch (mode) {
|
||||
case PROT_NONE: return VirtualMemory::Mode::NoAccess;
|
||||
case PROT_READ: return VirtualMemory::Mode::Read;
|
||||
case PROT_WRITE: return VirtualMemory::Mode::Write;
|
||||
case PROT_READ | PROT_WRITE: return VirtualMemory::Mode::ReadWrite; // NOLINT
|
||||
case PROT_EXEC: return VirtualMemory::Mode::Execute;
|
||||
case PROT_EXEC | PROT_WRITE: return VirtualMemory::Mode::ExecuteWrite; // NOLINT
|
||||
case PROT_EXEC | PROT_READ: return VirtualMemory::Mode::ExecuteRead; // NOLINT
|
||||
case PROT_EXEC | PROT_WRITE | PROT_READ:
|
||||
return VirtualMemory::Mode::ExecuteReadWrite; // NOLINT
|
||||
default: return VirtualMemory::Mode::NoAccess;
|
||||
}
|
||||
}
|
||||
|
||||
// Keep automatic mappings inside the guest and GPU-addressable low window.
|
||||
#ifdef KYTY_FIXED_NOREPLACE
|
||||
static constexpr uintptr_t LOW_ARENA_LIMIT = 0x000000FC00000000ULL; // libc mspace window ceiling
|
||||
@@ -129,7 +147,7 @@ static void* map_anonymous(uintptr_t addr, size_t size, int protect, int flags)
|
||||
return mmap(nullptr, size, protect, flags, -1, 0); // NOLINT
|
||||
}
|
||||
|
||||
uint64_t Alloc(uint64_t address, uint64_t size, Mode mode) {
|
||||
uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
|
||||
auto addr = static_cast<uintptr_t>(address);
|
||||
@@ -143,6 +161,11 @@ uint64_t Alloc(uint64_t address, uint64_t size, Mode mode) {
|
||||
if (ptr != MAP_FAILED) {
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
record_alloc(ret_addr, size);
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
(*g_protects)[page] = protect;
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
}
|
||||
|
||||
@@ -153,7 +176,8 @@ static uintptr_t align_up(uintptr_t addr, uint64_t alignment) {
|
||||
return (addr + alignment - 1) & ~(alignment - 1);
|
||||
}
|
||||
|
||||
uint64_t AllocAligned(uint64_t address, uint64_t size, Mode mode, uint64_t alignment) {
|
||||
uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
|
||||
uint64_t alignment) {
|
||||
if (alignment == 0) {
|
||||
return 0;
|
||||
}
|
||||
@@ -222,11 +246,16 @@ uint64_t AllocAligned(uint64_t address, uint64_t size, Mode mode, uint64_t align
|
||||
}
|
||||
|
||||
if (ptr == MAP_FAILED) {
|
||||
return AllocAligned(address, size, mode, alignment << 1u);
|
||||
return SysVirtualAllocAligned(address, size, mode, alignment << 1u);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
record_alloc(ret_addr, size);
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
(*g_protects)[page] = protect;
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
|
||||
return ret_addr;
|
||||
@@ -279,7 +308,7 @@ static bool is_mapped(void* ptr, size_t length) {
|
||||
}
|
||||
#endif
|
||||
|
||||
bool AllocFixed(uint64_t address, uint64_t size, Mode mode) {
|
||||
bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
|
||||
auto addr = static_cast<uintptr_t>(address);
|
||||
@@ -308,6 +337,11 @@ bool AllocFixed(uint64_t address, uint64_t size, Mode mode) {
|
||||
if (ptr != MAP_FAILED) {
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
record_alloc(ret_addr, size);
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
(*g_protects)[page] = protect;
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
|
||||
return true;
|
||||
@@ -316,15 +350,15 @@ bool AllocFixed(uint64_t address, uint64_t size, Mode mode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Commit(uint64_t address, uint64_t size, Mode mode) {
|
||||
return Protect(address, size, mode);
|
||||
bool SysVirtualCommit(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
return SysVirtualProtect(address, size, mode);
|
||||
}
|
||||
|
||||
uint64_t Reserve(uint64_t address, uint64_t size) {
|
||||
return ReserveAligned(address, size, 1);
|
||||
uint64_t SysVirtualReserve(uint64_t address, uint64_t size) {
|
||||
return SysVirtualReserveAligned(address, size, 1);
|
||||
}
|
||||
|
||||
uint64_t ReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
|
||||
uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
|
||||
if (alignment == 0) {
|
||||
return 0;
|
||||
}
|
||||
@@ -393,7 +427,7 @@ uint64_t ReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
|
||||
}
|
||||
|
||||
if (ptr == MAP_FAILED) {
|
||||
return ReserveAligned(address, size, alignment << 1u);
|
||||
return SysVirtualReserveAligned(address, size, alignment << 1u);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
@@ -403,7 +437,7 @@ uint64_t ReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
|
||||
return ret_addr;
|
||||
}
|
||||
|
||||
bool ReserveFixed(uint64_t address, uint64_t size) {
|
||||
bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
|
||||
auto addr = static_cast<uintptr_t>(address);
|
||||
@@ -439,9 +473,9 @@ bool ReserveFixed(uint64_t address, uint64_t size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Decommit(uint64_t address, uint64_t size) {
|
||||
bool SysVirtualDecommit(uint64_t address, uint64_t size) {
|
||||
// Drop physical pages while preserving the reservation.
|
||||
if (!Protect(address, size, Mode::NoAccess)) {
|
||||
if (!SysVirtualProtect(address, size, VirtualMemory::Mode::NoAccess)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -465,7 +499,7 @@ bool Decommit(uint64_t address, uint64_t size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Free(uint64_t address) {
|
||||
bool SysVirtualFree(uint64_t address) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
size_t size = 0;
|
||||
|
||||
@@ -482,10 +516,21 @@ bool Free(uint64_t address) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return munmap(reinterpret_cast<void*>(addr), size) == 0;
|
||||
if (munmap(reinterpret_cast<void*>(addr), size) == 0) {
|
||||
uintptr_t page_start = addr >> 12u;
|
||||
uintptr_t page_end = (addr + size - 1) >> 12u;
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
g_protects->erase(page);
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FreeRange(uint64_t address, uint64_t size) {
|
||||
bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
if (size == 0 || (address & 0xfffu) != 0 || (size & 0xfffu) != 0) {
|
||||
return false;
|
||||
@@ -537,22 +582,46 @@ bool FreeRange(uint64_t address, uint64_t size) {
|
||||
if (end < alloc_end) {
|
||||
(*g_allocs)[end] = alloc_end - end;
|
||||
}
|
||||
for (uintptr_t page = addr >> 12u; page <= (end - 1u) >> 12u; page++) {
|
||||
g_protects->erase(page);
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Protect(uint64_t address, uint64_t size, Mode mode) {
|
||||
const auto addr = static_cast<uintptr_t>(address);
|
||||
const auto page_start = addr >> 12u;
|
||||
const auto page_end = (addr + size - 1) >> 12u;
|
||||
return mprotect(reinterpret_cast<void*>(page_start << 12u), (page_end - page_start + 1) << 12u,
|
||||
get_protection_flag(mode)) == 0;
|
||||
bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
|
||||
VirtualMemory::Mode* old_mode) {
|
||||
auto addr = static_cast<uintptr_t>(address);
|
||||
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
if (old_mode != nullptr) {
|
||||
if (auto s = g_protects->find(addr >> 12u); s != g_protects->end()) {
|
||||
*old_mode = get_protection_flag(s->second);
|
||||
} else {
|
||||
*old_mode = VirtualMemory::Mode::NoAccess;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
|
||||
uintptr_t page_start = addr >> 12u;
|
||||
uintptr_t page_end = (addr + size - 1) >> 12u;
|
||||
if (mprotect(reinterpret_cast<void*>(page_start << 12u), (page_end - page_start + 1) << 12u,
|
||||
get_protection_flag(mode)) == 0) {
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
(*g_protects)[page] = get_protection_flag(mode);
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FlushInstructionCache(uint64_t /*address*/, uint64_t /*size*/) {
|
||||
bool SysVirtualFlushInstructionCache(uint64_t /*address*/, uint64_t /*size*/) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Common::VirtualMemory
|
||||
} // namespace Common
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#ifndef KYTY_COMMON_PLATFORM_SYSSWAPBYTEORDER_H_
|
||||
#define KYTY_COMMON_PLATFORM_SYSSWAPBYTEORDER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
inline uint16_t SwapByteOrder16(uint16_t value) {
|
||||
uint16_t hi = value << 8u;
|
||||
uint16_t lo = value >> 8u;
|
||||
return hi | lo;
|
||||
}
|
||||
|
||||
inline uint32_t SwapByteOrder32(uint32_t value) {
|
||||
return __builtin_bswap32(value);
|
||||
}
|
||||
|
||||
inline uint64_t SwapByteOrder64(uint64_t value) {
|
||||
return __builtin_bswap64(value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void SwapByteOrder(T& x) {
|
||||
if (sizeof(x) == 2) {
|
||||
if (std::is_signed_v<T>) {
|
||||
x = static_cast<std::make_signed_t<T>>(
|
||||
SwapByteOrder16(static_cast<std::make_unsigned_t<uint16_t>>(x)));
|
||||
} else {
|
||||
x = SwapByteOrder16(x);
|
||||
}
|
||||
}
|
||||
if (sizeof(x) == 4) {
|
||||
if (std::is_signed_v<T>) {
|
||||
x = static_cast<std::make_signed_t<T>>(
|
||||
SwapByteOrder32(static_cast<std::make_unsigned_t<uint32_t>>(x)));
|
||||
} else {
|
||||
x = SwapByteOrder32(x);
|
||||
}
|
||||
}
|
||||
if (sizeof(x) == 8) {
|
||||
if (std::is_signed_v<T>) {
|
||||
x = static_cast<std::make_signed_t<T>>(
|
||||
SwapByteOrder64(static_cast<std::make_unsigned_t<uint64_t>>(x)));
|
||||
} else {
|
||||
x = SwapByteOrder64(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void NoSwapByteOrder(T& x) {}
|
||||
|
||||
#endif /* KYTY_COMMON_PLATFORM_SYSSWAPBYTEORDER_H_ */
|
||||